support filtering by geographic longitude and latitude in insights explorer queries

This commit is contained in:
MaysWind
2026-03-08 20:55:35 +08:00
parent a0fd468309
commit 4d7c3650b5
22 changed files with 244 additions and 20 deletions
+9 -1
View File
@@ -81,7 +81,11 @@ export enum TransactionExplorerConditionOperatorType {
StartsWith = 'startsWith',
NotStartsWith = 'notStartsWith',
EndsWith = 'endsWith',
NotEndsWith = 'notEndsWith'
NotEndsWith = 'notEndsWith',
LatitudeBetween = 'latitudeBetween',
LatitudeNotBetween = 'latitudeNotBetween',
LongitudeBetween = 'longitudeBetween',
LongitudeNotBetween = 'longitudeNotBetween'
}
export class TransactionExplorerConditionOperator implements NameValue {
@@ -107,6 +111,10 @@ export class TransactionExplorerConditionOperator implements NameValue {
public static readonly NotStartsWith = new TransactionExplorerConditionOperator('Not starts with', TransactionExplorerConditionOperatorType.NotStartsWith);
public static readonly EndsWith = new TransactionExplorerConditionOperator('Ends with', TransactionExplorerConditionOperatorType.EndsWith);
public static readonly NotEndsWith = new TransactionExplorerConditionOperator('Not ends with', TransactionExplorerConditionOperatorType.NotEndsWith);
public static readonly LatitudeBetween = new TransactionExplorerConditionOperator('Latitude between', TransactionExplorerConditionOperatorType.LatitudeBetween);
public static readonly LatitudeNotBetween = new TransactionExplorerConditionOperator('Latitude not between', TransactionExplorerConditionOperatorType.LatitudeNotBetween);
public static readonly LongitudeBetween = new TransactionExplorerConditionOperator('Longitude between', TransactionExplorerConditionOperatorType.LongitudeBetween);
public static readonly LongitudeNotBetween = new TransactionExplorerConditionOperator('Longitude not between', TransactionExplorerConditionOperatorType.LongitudeNotBetween);
public readonly name: string;
public readonly value: TransactionExplorerConditionOperatorType;
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Tortendiagramm",
"Bar Chart": "Balkendiagramm",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Pie Chart",
"Bar Chart": "Bar Chart",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "No empieza por",
"Ends with": "Termina en",
"Not ends with": "No termina en",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Gráfico Circular",
"Bar Chart": "Gráfico de Barras",
"Radar Chart": "Gráfico de Radar",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Graphique en secteurs",
"Bar Chart": "Graphique en barres",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Grafico a torta",
"Bar Chart": "Grafico a barre",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "円グラフ",
"Bar Chart": "棒グラフ",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "ಪಾಯಿ ಚಾರ್ಟ್",
"Bar Chart": "ಬಾರ್ ಚಾರ್ಟ್",
"Radar Chart": "ರಡಾರ್ ಚಾರ್ಟ್",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "원형 차트",
"Bar Chart": "막대 차트",
"Radar Chart": "레이더 차트",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Cirkeldiagram",
"Bar Chart": "Balkdiagram",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Gráfico de Pizza",
"Bar Chart": "Gráfico de Barras",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Не начинается с",
"Ends with": "Заканчивается с",
"Not ends with": "Не заканчивается с",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Круговая диаграмма",
"Bar Chart": "Гистограмма",
"Radar Chart": "Лепестковая диаграмма",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Se ne začne z",
"Ends with": "Se konča z",
"Not ends with": "Se ne konča z",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Tortni grafikon",
"Bar Chart": "Vodoravni palični grafikon",
"Radar Chart": "Radarski grafikon",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "தொடங்கவில்லை",
"Ends with": "முடிகிறது",
"Not ends with": "முடியவில்லை",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "வட்ட விளக்கப்படம்",
"Bar Chart": "பட்டை விளக்கப்படம்",
"Radar Chart": "ரேடார் விளக்கப்படம்",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "กราฟวงกลม",
"Bar Chart": "กราฟแท่ง",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Pasta Grafiği",
"Bar Chart": "Çubuk Grafik",
"Radar Chart": "Radar Grafiği",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Кругова діаграма",
"Bar Chart": "Гістограма",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "Not starts with",
"Ends with": "Ends with",
"Not ends with": "Not ends with",
"Latitude between": "Latitude between",
"Latitude not between": "Latitude not between",
"Longitude between": "Longitude between",
"Longitude not between": "Longitude not between",
"Pie Chart": "Biểu đồ tròn",
"Bar Chart": "Biểu đồ cột",
"Radar Chart": "Radar Chart",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "开头不是",
"Ends with": "结尾是",
"Not ends with": "结尾不是",
"Latitude between": "纬度介于",
"Latitude not between": "纬度不介于",
"Longitude between": "经度介于",
"Longitude not between": "经度不介于",
"Pie Chart": "饼图",
"Bar Chart": "条形图",
"Radar Chart": "雷达图",
+4
View File
@@ -1567,6 +1567,10 @@
"Not starts with": "開頭不是",
"Ends with": "結尾是",
"Not ends with": "結尾不是",
"Latitude between": "緯度介於",
"Latitude not between": "緯度不介於",
"Longitude between": "經度介於",
"Longitude not between": "經度不介於",
"Pie Chart": "圓餅圖",
"Bar Chart": "長條圖",
"Radar Chart": "雷達圖",
+106 -14
View File
@@ -317,7 +317,7 @@ export class TransactionExplorerQuery {
condition = new TransactionExplorerDestinationAmountCondition(TransactionExplorerConditionOperatorType.Between, [0, 0]);
break;
case TransactionExplorerConditionField.GeoLocation:
condition = new TransactionExplorerGeoLocationCondition(TransactionExplorerConditionOperatorType.IsNotEmpty, []);
condition = new TransactionExplorerGeoLocationCondition(TransactionExplorerConditionOperatorType.IsNotEmpty, [TransactionExplorerGeoLocationCondition.MIN_LATITUDE, TransactionExplorerGeoLocationCondition.MAX_LATITUDE, TransactionExplorerGeoLocationCondition.MIN_LONGITUDE, TransactionExplorerGeoLocationCondition.MAX_LONGITUDE]);
break;
case TransactionExplorerConditionField.TransactionTag:
condition = new TransactionExplorerTransactionTagCondition(TransactionExplorerConditionOperatorType.HasAny, []);
@@ -670,7 +670,7 @@ export class TransactionExplorerConditionWithRelation {
break;
case TransactionExplorerConditionField.GeoLocation.value:
if (TransactionExplorerGeoLocationCondition.supportedOperators[conditionOperator] && Array.isArray(conditionValue)) {
condition = new TransactionExplorerGeoLocationCondition(conditionOperator as GeoLocationConditionOperator, conditionValue as string[]);
condition = new TransactionExplorerGeoLocationCondition(conditionOperator as GeoLocationConditionOperator, conditionValue as [number, number, number, number]);
}
break;
case TransactionExplorerConditionField.TransactionTag.value:
@@ -984,29 +984,47 @@ export class TransactionExplorerDestinationAmountCondition extends AbstractTrans
}
type GeoLocationConditionOperator = TransactionExplorerConditionOperatorType.IsEmpty |
TransactionExplorerConditionOperatorType.IsNotEmpty;
TransactionExplorerConditionOperatorType.IsNotEmpty |
TransactionExplorerConditionOperatorType.LatitudeBetween |
TransactionExplorerConditionOperatorType.LatitudeNotBetween |
TransactionExplorerConditionOperatorType.LongitudeBetween |
TransactionExplorerConditionOperatorType.LongitudeNotBetween;
export class TransactionExplorerGeoLocationCondition implements TransactionExplorerCondition<TransactionExplorerConditionFieldType.GeoLocation, string[]> {
export class TransactionExplorerGeoLocationCondition implements TransactionExplorerCondition<TransactionExplorerConditionFieldType.GeoLocation, [number, number, number, number]> {
public static readonly supportedOperators: PartialRecord<TransactionExplorerConditionOperatorType, true> = {
[TransactionExplorerConditionOperatorType.IsEmpty]: true,
[TransactionExplorerConditionOperatorType.IsNotEmpty]: true
[TransactionExplorerConditionOperatorType.IsNotEmpty]: true,
[TransactionExplorerConditionOperatorType.LatitudeBetween]: true,
[TransactionExplorerConditionOperatorType.LatitudeNotBetween]: true,
[TransactionExplorerConditionOperatorType.LongitudeBetween]: true,
[TransactionExplorerConditionOperatorType.LongitudeNotBetween]: true
};
public static readonly MIN_LATITUDE: number = -90.0;
public static readonly MAX_LATITUDE: number = 90.0;
public static readonly MIN_LONGITUDE: number = -180.0;
public static readonly MAX_LONGITUDE: number = 180.0;
public readonly field = TransactionExplorerConditionFieldType.GeoLocation;
public readonly operator: GeoLocationConditionOperator = TransactionExplorerConditionOperatorType.IsNotEmpty;
public value: string[];
public value: [number, number, number, number];
constructor(operator: GeoLocationConditionOperator, value: string[]) {
constructor(operator: GeoLocationConditionOperator, value: [number, number, number, number]) {
this.operator = operator;
this.value = value;
this.value = [
value[0] ?? TransactionExplorerGeoLocationCondition.MIN_LATITUDE,
value[1] ?? TransactionExplorerGeoLocationCondition.MAX_LATITUDE,
value[2] ?? TransactionExplorerGeoLocationCondition.MIN_LONGITUDE,
value[3] ?? TransactionExplorerGeoLocationCondition.MAX_LONGITUDE
];
}
public getValueForStore(): string[] {
if (this.operator === TransactionExplorerConditionOperatorType.IsEmpty || this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) {
return [];
}
return [];
public getValueForStore(): [number, number, number, number] {
return [
this.value[0] ?? TransactionExplorerGeoLocationCondition.MIN_LATITUDE,
this.value[1] ?? TransactionExplorerGeoLocationCondition.MAX_LATITUDE,
this.value[2] ?? TransactionExplorerGeoLocationCondition.MIN_LONGITUDE,
this.value[3] ?? TransactionExplorerGeoLocationCondition.MAX_LONGITUDE
];
}
public match(transaction: TransactionInsightDataItem): boolean {
@@ -1014,6 +1032,62 @@ export class TransactionExplorerGeoLocationCondition implements TransactionExplo
return !transaction.geoLocation;
} else if (this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) {
return !!transaction.geoLocation;
} else if (this.operator === TransactionExplorerConditionOperatorType.LatitudeBetween) {
if (!transaction.geoLocation) {
return false;
}
const latitude = transaction.geoLocation.latitude;
if (typeof(this.value[0]) === 'number' && latitude < this.value[0]) {
return false;
}
if (typeof(this.value[1]) === 'number' && latitude > this.value[1]) {
return false;
}
return true;
} else if (this.operator === TransactionExplorerConditionOperatorType.LatitudeNotBetween) {
if (!transaction.geoLocation) {
return false;
}
const latitude = transaction.geoLocation.latitude;
if (typeof(this.value[0]) === 'number' && typeof(this.value[1]) === 'number' && latitude >= this.value[0] && latitude <= this.value[1]) {
return false;
}
return true;
} else if (this.operator === TransactionExplorerConditionOperatorType.LongitudeBetween) {
if (!transaction.geoLocation) {
return false;
}
const longitude = transaction.geoLocation.longitude;
if (typeof(this.value[2]) === 'number' && longitude < this.value[2]) {
return false;
}
if (typeof(this.value[3]) === 'number' && longitude > this.value[3]) {
return false;
}
return true;
} else if (this.operator === TransactionExplorerConditionOperatorType.LongitudeNotBetween) {
if (!transaction.geoLocation) {
return false;
}
const longitude = transaction.geoLocation.longitude;
if (typeof(this.value[2]) === 'number' && typeof(this.value[3]) === 'number' && longitude >= this.value[2] && longitude <= this.value[3]) {
return false;
}
return true;
}
return false;
@@ -1024,6 +1098,24 @@ export class TransactionExplorerGeoLocationCondition implements TransactionExplo
return `geo_location IS EMPTY`;
} else if (this.operator === TransactionExplorerConditionOperatorType.IsNotEmpty) {
return `geo_location IS NOT EMPTY`;
} else if (this.operator === TransactionExplorerConditionOperatorType.LatitudeBetween || this.operator === TransactionExplorerConditionOperatorType.LatitudeNotBetween) {
const minLatitude: number = this.value[0] ?? TransactionExplorerGeoLocationCondition.MIN_LATITUDE;
const maxLatitude: number = this.value[1] ?? TransactionExplorerGeoLocationCondition.MAX_LATITUDE;
if (this.operator === TransactionExplorerConditionOperatorType.LatitudeBetween) {
return `(geo_location.latitude >= ${minLatitude} AND geo_location.latitude <= ${maxLatitude})`;
} else if (this.operator === TransactionExplorerConditionOperatorType.LatitudeNotBetween) {
return `(geo_location.latitude < ${minLatitude} OR geo_location.latitude > ${maxLatitude})`;
}
} else if (this.operator === TransactionExplorerConditionOperatorType.LongitudeBetween || this.operator === TransactionExplorerConditionOperatorType.LongitudeNotBetween) {
const minLongitude: number = this.value[2] ?? TransactionExplorerGeoLocationCondition.MIN_LONGITUDE;
const maxLongitude: number = this.value[3] ?? TransactionExplorerGeoLocationCondition.MAX_LONGITUDE;
if (this.operator === TransactionExplorerConditionOperatorType.LongitudeBetween) {
return `(geo_location.longitude >= ${minLongitude} AND geo_location.longitude <= ${maxLongitude})`;
} else if (this.operator === TransactionExplorerConditionOperatorType.LongitudeNotBetween) {
return `(geo_location.longitude < ${minLongitude} OR geo_location.longitude > ${maxLongitude})`;
}
}
return '';
@@ -236,10 +236,57 @@
/>
</div>
<div class="d-flex w-100 align-center gap-2"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.GeoLocation.value">
<v-text-field disabled density="compact"
:placeholder="tt('None')"
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.GeoLocation.value"
v-if="conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.LatitudeBetween.value &&
conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.LatitudeNotBetween.value &&
conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.LongitudeBetween.value &&
conditionWithRelation.condition.operator !== TransactionExplorerConditionOperator.LongitudeNotBetween.value"
/>
<number-input density="compact"
:disabled="loading || disabled || !!editingQuery"
:min-value="TransactionExplorerGeoLocationCondition.MIN_LATITUDE"
:max-value="TransactionExplorerGeoLocationCondition.MAX_LATITUDE"
:max-decimal-count="6"
v-model="conditionWithRelation.condition.value[0]"
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LatitudeBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LatitudeNotBetween.value"
/>
<number-input density="compact"
:disabled="loading || disabled || !!editingQuery"
:min-value="TransactionExplorerGeoLocationCondition.MIN_LONGITUDE"
:max-value="TransactionExplorerGeoLocationCondition.MAX_LONGITUDE"
:max-decimal-count="6"
v-model="conditionWithRelation.condition.value[2]"
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LongitudeBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LongitudeNotBetween.value"
/>
<span class="ms-2 me-2"
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LatitudeBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LatitudeNotBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LongitudeBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LongitudeNotBetween.value">~</span>
<number-input density="compact"
:disabled="loading || disabled || !!editingQuery"
:min-value="TransactionExplorerGeoLocationCondition.MIN_LATITUDE"
:max-value="TransactionExplorerGeoLocationCondition.MAX_LATITUDE"
:max-decimal-count="6"
v-model="conditionWithRelation.condition.value[1]"
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LatitudeBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LatitudeNotBetween.value"
/>
<number-input density="compact"
:disabled="loading || disabled || !!editingQuery"
:min-value="TransactionExplorerGeoLocationCondition.MIN_LONGITUDE"
:max-value="TransactionExplorerGeoLocationCondition.MAX_LONGITUDE"
:max-decimal-count="6"
v-model="conditionWithRelation.condition.value[3]"
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LongitudeBetween.value ||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.LongitudeNotBetween.value"
/>
</div>
<div class="d-flex w-100" v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.TransactionTag.value">
<v-text-field
@@ -359,7 +406,8 @@ import {
import {
type TransactionExplorerCondition,
TransactionExplorerQuery
TransactionExplorerQuery,
TransactionExplorerGeoLocationCondition
} from '@/models/explorer.ts';
import {