refactor: simplify geo queries

This commit is contained in:
Thea Schöbl
2022-05-13 08:26:23 +00:00
parent d3c509ccb4
commit 9ce8c58b98
5 changed files with 198 additions and 31 deletions

6
package-lock.json generated
View File

@@ -536,9 +536,9 @@
} }
}, },
"@openstapps/core": { "@openstapps/core": {
"version": "0.65.1", "version": "0.66.0",
"resolved": "https://registry.npmjs.org/@openstapps/core/-/core-0.65.1.tgz", "resolved": "https://registry.npmjs.org/@openstapps/core/-/core-0.66.0.tgz",
"integrity": "sha512-ZffFN3bRMC4eq96KSOnZScfTdx/ioiDuNLVrm0azqBqz+PKncknO65k+hoAClHi8BOlvZc2bPk4/19suas7PQQ==", "integrity": "sha512-79oNo8TLjs/oaq/MzcPQx2xiM89kOI4bjV8sQakycmkuke3cI6sdhAaRSAYCNsjCz74VIx/8ht01zKx3jll+Cg==",
"requires": { "requires": {
"@openstapps/core-tools": "0.30.0", "@openstapps/core-tools": "0.30.0",
"@types/geojson": "1.0.6", "@types/geojson": "1.0.6",

View File

@@ -33,7 +33,7 @@
}, },
"dependencies": { "dependencies": {
"@elastic/elasticsearch": "5.6.22", "@elastic/elasticsearch": "5.6.22",
"@openstapps/core": "0.65.1", "@openstapps/core": "0.66.0",
"@openstapps/core-tools": "0.30.0", "@openstapps/core-tools": "0.30.0",
"@openstapps/logger": "0.8.0", "@openstapps/logger": "0.8.0",
"@types/express-prometheus-middleware": "1.2.1", "@types/express-prometheus-middleware": "1.2.1",

View File

@@ -34,7 +34,7 @@ import {
ESFunctionScoreQuery, ESFunctionScoreQuery,
ESFunctionScoreQueryFunction, ESFunctionScoreQueryFunction,
ESGenericRange, ESGenericRange,
ESGenericSort, ESGenericSort, ESGeoBoundingBoxFilter,
ESGeoDistanceFilter, ESGeoDistanceFilter,
ESGeoDistanceFilterArguments, ESGeoDistanceFilterArguments,
ESGeoDistanceSort, ESGeoDistanceSort,
@@ -96,7 +96,7 @@ export function buildBooleanFilter(booleanFilter: SCSearchBooleanFilter): ESBool
* @param filter A search filter for the retrieval of the data * @param filter A search filter for the retrieval of the data
*/ */
export function buildFilter(filter: SCSearchFilter): export function buildFilter(filter: SCSearchFilter):
ESTermFilter | ESGeoDistanceFilter | ESGeoShapeFilter | ESBooleanFilter<unknown> | ESRangeFilter { ESTermFilter | ESGeoDistanceFilter | ESBooleanFilter<ESGeoShapeFilter | ESGeoBoundingBoxFilter> | ESGeoShapeFilter | ESBooleanFilter<unknown> | ESRangeFilter {
switch (filter.type) { switch (filter.type) {
case 'value': case 'value':
@@ -125,10 +125,10 @@ export function buildFilter(filter: SCSearchFilter):
case 'distance': case 'distance':
const geoObject: ESGeoDistanceFilterArguments = { const geoObject: ESGeoDistanceFilterArguments = {
distance: `${filter.arguments.distance}m`, distance: `${filter.arguments.distance}m`,
}; [`${filter.arguments.field}.point.coordinates`]: {
geoObject[filter.arguments.field] = { lat: filter.arguments.position[1],
lat: filter.arguments.position[1], lon: filter.arguments.position[0],
lon: filter.arguments.position[0], },
}; };
return { return {
@@ -179,12 +179,50 @@ export function buildFilter(filter: SCSearchFilter):
return dateRangeFilter; return dateRangeFilter;
case 'geo': case 'geo':
return { // TODO: on ES upgrade, use just geo_shape filters
[filter.arguments.field]: { const geoShapeFilter: ESGeoShapeFilter = {
shape: filter.arguments.shape, geo_shape: {
relation: filter.arguments.spatialRelation, /**
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-shape-query.html#_ignore_unmapped_3
*/
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore unfortunately, typescript is stupid and won't allow me to map this to an actual type.
ignore_unmapped: true,
[`${filter.arguments.field}.polygon`]: {
shape: filter.arguments.shape,
relation: filter.arguments.spatialRelation,
},
}, },
}; };
if ((typeof filter.arguments.spatialRelation === 'undefined' || filter.arguments.spatialRelation === 'intersects')
&& filter.arguments.shape.type === 'envelope'
) {
return {
bool: {
minimum_should_match: 1,
should: [
geoShapeFilter,
{
geo_bounding_box: {
/**
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-shape-query.html#_ignore_unmapped_3
*/
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore unfortunately, typescript is stupid and won't allow me to map this to an actual type.
ignore_unmapped: true,
[`${filter.arguments.field}.point.coordinates`]: {
bottom_right: filter.arguments.shape.coordinates[0],
top_left: filter.arguments.shape.coordinates[1],
},
},
},
],
},
};
}
return geoShapeFilter;
} }
} }
@@ -395,7 +433,7 @@ export function buildSort(
unit: 'm', unit: 'm',
}; };
args[sort.arguments.field] = { args[`${sort.arguments.field}.point.coordinates`] = {
lat: sort.arguments.position[1], lat: sort.arguments.position[1],
lon: sort.arguments.position[0], lon: sort.arguments.position[0],
}; };

View File

@@ -18,7 +18,7 @@ import {SCThing, SCThingType} from '@openstapps/core';
// tslint:disable-next-line:no-implicit-dependencies // tslint:disable-next-line:no-implicit-dependencies
import {NameList} from 'elasticsearch'; import {NameList} from 'elasticsearch';
// tslint:disable-next-line:no-implicit-dependencies // tslint:disable-next-line:no-implicit-dependencies
import {Polygon} from 'geojson'; import {Polygon, Position} from 'geojson';
/** /**
* An elasticsearch aggregation bucket * An elasticsearch aggregation bucket
@@ -366,23 +366,67 @@ export interface ESGeoDistanceFilter {
geo_distance: ESGeoDistanceFilterArguments; geo_distance: ESGeoDistanceFilterArguments;
} }
/**
* A rectangular geo shape, representing the top-left and bottom-right corners
*
* This is an extension of the Geojson type
* http://geojson.org/geojson-spec.html
*/
export interface ESEnvelope {
/**
* The top-left and bottom-right corners of the bounding box
*/
coordinates: [Position, Position];
/**
* The type of the geometry
*/
type: 'envelope';
}
/**
* An Elasticsearch geo bounding box filter
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-bounding-box-query.html
*/
export interface ESGeoBoundingBoxFilter {
/**
* An Elasticsearch geo bounding box filter
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-bounding-box-query.html
*/
geo_bounding_box: {
[fieldName: string]: {
/**
* Geo Shape
*/
bottom_right: Position;
/**
* Geo Shape
*/
top_left: Position;
};
};
}
/** /**
* An Elasticsearch geo shape filter * An Elasticsearch geo shape filter
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-shape-query.html * @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-shape-query.html
*/ */
export interface ESGeoShapeFilter { export interface ESGeoShapeFilter {
[fieldName: string]: { geo_shape: {
/** [fieldName: string]: {
* Relation of the two shapes /**
* * Relation of the two shapes
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-shape-query.html#_spatial_relations *
*/ * @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-geo-shape-query.html#_spatial_relations
relation?: 'intersects' | 'disjoint' | 'within' | 'contains'; */
relation?: 'intersects' | 'disjoint' | 'within' | 'contains';
/** /**
* Geo Shape * Geo Shape
*/ */
shape: Polygon; shape: Polygon | ESEnvelope;
};
}; };
} }

View File

@@ -32,7 +32,7 @@ import {
ESGeoDistanceFilter, ESGeoDistanceFilter,
ESGeoDistanceSort, ESGeoDistanceSort,
ESTermFilter, ESTermFilter,
ScriptSort ScriptSort,
} from '../../../src/storage/elasticsearch/types/elasticsearch'; } from '../../../src/storage/elasticsearch/types/elasticsearch';
import {configFile} from '../../../src/common'; import {configFile} from '../../../src/common';
import {buildBooleanFilter, buildFilter, buildQuery, buildSort} from '../../../src/storage/elasticsearch/query'; import {buildBooleanFilter, buildFilter, buildQuery, buildSort} from '../../../src/storage/elasticsearch/query';
@@ -205,10 +205,37 @@ describe('Query', function () {
type: 'distance', type: 'distance',
arguments: { arguments: {
distance: 1000, distance: 1000,
field: 'geo.point.coordinates', field: 'geo',
position: [50.123, 8.123], position: [50.123, 8.123],
} }
}, },
geoPoint: {
type: 'geo',
arguments: {
field: 'geo',
shape: {
type: 'envelope',
coordinates: [
[50.123, 8.123],
[50.123, 8.123],
]
}
}
},
geoShape: {
type: 'geo',
arguments: {
field: 'geo',
spatialRelation: 'contains',
shape: {
type: 'envelope',
coordinates: [
[50.123, 8.123],
[50.123, 8.123],
]
}
}
},
boolean: { boolean: {
type: 'boolean', type: 'boolean',
arguments: { arguments: {
@@ -447,6 +474,64 @@ describe('Query', function () {
expect(filter).to.be.eql(expectedFilter); expect(filter).to.be.eql(expectedFilter);
}); });
it('should build geo filter for shapes and points', function () {
const filter = buildFilter(searchFilters.geoPoint);
const expectedFilter = {
bool: {
minimum_should_match: 1,
should: [
{
geo_shape: {
'geo.polygon': {
relation: undefined,
shape: {
type: 'envelope',
coordinates: [
[50.123, 8.123],
[50.123, 8.123]
]
},
},
ignore_unmapped: true,
}
},
{
geo_bounding_box: {
'geo.point.coordinates': {
bottom_right: [50.123, 8.123],
top_left: [50.123, 8.123]
},
ignore_unmapped: true,
},
},
]
}
};
expect(filter).to.be.eql(expectedFilter);
});
it('should build geo filter for shapes only', function () {
const filter = buildFilter(searchFilters.geoShape);
const expectedFilter = {
geo_shape: {
'geo.polygon': {
relation: 'contains',
shape: {
type: 'envelope',
coordinates: [
[50.123, 8.123],
[50.123, 8.123]
]
},
},
ignore_unmapped: true,
}
};
expect(filter).to.be.eql(expectedFilter);
});
it('should build boolean filter', function () { it('should build boolean filter', function () {
const filter = buildFilter(searchFilters.boolean); const filter = buildFilter(searchFilters.boolean);
const expectedFilter: ESBooleanFilter<any> = { const expectedFilter: ESBooleanFilter<any> = {
@@ -497,7 +582,7 @@ describe('Query', function () {
type: 'distance', type: 'distance',
order: 'desc', order: 'desc',
arguments: { arguments: {
field: 'geo.point', field: 'geo',
position: [8.123, 50.123] position: [8.123, 50.123]
}, },
}, },
@@ -523,7 +608,7 @@ describe('Query', function () {
mode: 'avg', mode: 'avg',
order: 'desc', order: 'desc',
unit: 'm', unit: 'm',
'geo.point': { 'geo.point.coordinates': {
lat: 50.123, lat: 50.123,
lon: 8.123 lon: 8.123
} }