mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] convert elasticsearch_utils to TS (#93984)
* [Maps] convert elasticsearch_utils to TS * tslint * clean up * i18n cleanup * update elasticsearch_geo_utils tests * fix unit test * review feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ebd92a6e5d
commit
6264c563d1
10 changed files with 194 additions and 166 deletions
|
@ -131,15 +131,15 @@ export enum ES_SPATIAL_RELATIONS {
|
|||
WITHIN = 'WITHIN',
|
||||
}
|
||||
|
||||
export const GEO_JSON_TYPE = {
|
||||
POINT: 'Point',
|
||||
MULTI_POINT: 'MultiPoint',
|
||||
LINE_STRING: 'LineString',
|
||||
MULTI_LINE_STRING: 'MultiLineString',
|
||||
POLYGON: 'Polygon',
|
||||
MULTI_POLYGON: 'MultiPolygon',
|
||||
GEOMETRY_COLLECTION: 'GeometryCollection',
|
||||
};
|
||||
export enum GEO_JSON_TYPE {
|
||||
POINT = 'Point',
|
||||
MULTI_POINT = 'MultiPoint',
|
||||
LINE_STRING = 'LineString',
|
||||
MULTI_LINE_STRING = 'MultiLineString',
|
||||
POLYGON = 'Polygon',
|
||||
MULTI_POLYGON = 'MultiPolygon',
|
||||
GEOMETRY_COLLECTION = 'GeometryCollection',
|
||||
}
|
||||
|
||||
export const POLYGON_COORDINATES_EXTERIOR_INDEX = 0;
|
||||
export const LON_INDEX = 0;
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FeatureCollection, GeoJsonProperties, Polygon } from 'geojson';
|
||||
import { MapExtent } from '../descriptor_types';
|
||||
import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';
|
||||
|
||||
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent;
|
||||
|
||||
export function turfBboxToBounds(turfBbox: unknown): MapExtent;
|
||||
|
||||
export function clampToLatBounds(lat: number): number;
|
||||
|
||||
export function clampToLonBounds(lon: number): number;
|
||||
|
||||
export function hitsToGeoJson(
|
||||
hits: Array<Record<string, unknown>>,
|
||||
flattenHit: (elasticSearchHit: Record<string, unknown>) => GeoJsonProperties,
|
||||
geoFieldName: string,
|
||||
geoFieldType: ES_GEO_FIELD_TYPE,
|
||||
epochMillisFields: string[]
|
||||
): FeatureCollection;
|
||||
|
||||
export interface ESBBox {
|
||||
top_left: number[];
|
||||
bottom_right: number[];
|
||||
}
|
||||
|
||||
export interface ESGeoBoundingBoxFilter {
|
||||
geo_bounding_box: {
|
||||
[geoFieldName: string]: ESBBox;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ESPolygonFilter {
|
||||
geo_shape: {
|
||||
[geoFieldName: string]: {
|
||||
shape: Polygon;
|
||||
relation: ES_SPATIAL_RELATIONS.INTERSECTS;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function createExtentFilter(
|
||||
mapExtent: MapExtent,
|
||||
geoFieldName: string
|
||||
): ESPolygonFilter | ESGeoBoundingBoxFilter;
|
||||
|
||||
export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBox;
|
|
@ -397,12 +397,10 @@ describe('createExtentFilter', () => {
|
|||
minLon: -89,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName);
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [-89, 39],
|
||||
bottom_right: [-83, 35],
|
||||
},
|
||||
expect(filter.geo_bounding_box).toEqual({
|
||||
location: {
|
||||
top_left: [-89, 39],
|
||||
bottom_right: [-83, 35],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -415,12 +413,10 @@ describe('createExtentFilter', () => {
|
|||
minLon: -190,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName);
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [-180, 89],
|
||||
bottom_right: [180, -89],
|
||||
},
|
||||
expect(filter.geo_bounding_box).toEqual({
|
||||
location: {
|
||||
top_left: [-180, 89],
|
||||
bottom_right: [180, -89],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -436,12 +432,10 @@ describe('createExtentFilter', () => {
|
|||
const leftLon = filter.geo_bounding_box.location.top_left[0];
|
||||
const rightLon = filter.geo_bounding_box.location.bottom_right[0];
|
||||
expect(leftLon).toBeGreaterThan(rightLon);
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [100, 39],
|
||||
bottom_right: [-160, 35],
|
||||
},
|
||||
expect(filter.geo_bounding_box).toEqual({
|
||||
location: {
|
||||
top_left: [100, 39],
|
||||
bottom_right: [-160, 35],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -457,12 +451,10 @@ describe('createExtentFilter', () => {
|
|||
const leftLon = filter.geo_bounding_box.location.top_left[0];
|
||||
const rightLon = filter.geo_bounding_box.location.bottom_right[0];
|
||||
expect(leftLon).toBeGreaterThan(rightLon);
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [160, 39],
|
||||
bottom_right: [-100, 35],
|
||||
},
|
||||
expect(filter.geo_bounding_box).toEqual({
|
||||
location: {
|
||||
top_left: [160, 39],
|
||||
bottom_right: [-100, 35],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -475,12 +467,10 @@ describe('createExtentFilter', () => {
|
|||
minLon: -191,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName);
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [-180, 39],
|
||||
bottom_right: [180, 35],
|
||||
},
|
||||
expect(filter.geo_bounding_box).toEqual({
|
||||
location: {
|
||||
top_left: [-180, 39],
|
||||
bottom_right: [180, 35],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-expect-error
|
||||
import { parse } from 'wellknown';
|
||||
// @ts-expect-error
|
||||
import turfCircle from '@turf/circle';
|
||||
import { Feature, FeatureCollection, Geometry, Polygon, Point, Position } from 'geojson';
|
||||
import { BBox } from '@turf/helpers';
|
||||
import {
|
||||
DECIMAL_DEGREES_PRECISION,
|
||||
ES_GEO_FIELD_TYPE,
|
||||
|
@ -18,14 +23,54 @@ import {
|
|||
LAT_INDEX,
|
||||
} from '../constants';
|
||||
import { getEsSpatialRelationLabel } from '../i18n_getters';
|
||||
import { FILTERS } from '../../../../../src/plugins/data/common';
|
||||
import turfCircle from '@turf/circle';
|
||||
import { Filter, FILTERS } from '../../../../../src/plugins/data/common';
|
||||
import { MapExtent } from '../descriptor_types';
|
||||
|
||||
const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER;
|
||||
|
||||
function ensureGeoField(type) {
|
||||
type Coordinates = Position | Position[] | Position[][] | Position[][][];
|
||||
|
||||
// Elasticsearch stores more then just GeoJSON.
|
||||
// 1) geometry.type as lower case string
|
||||
// 2) circle and envelope types
|
||||
interface ESGeometry {
|
||||
type: string;
|
||||
coordinates: Coordinates;
|
||||
}
|
||||
|
||||
export interface ESBBox {
|
||||
top_left: number[];
|
||||
bottom_right: number[];
|
||||
}
|
||||
|
||||
interface GeoShapeQueryBody {
|
||||
shape?: Polygon;
|
||||
relation?: ES_SPATIAL_RELATIONS;
|
||||
indexed_shape?: PreIndexedShape;
|
||||
}
|
||||
|
||||
export type GeoFilter = Filter & {
|
||||
geo_bounding_box?: {
|
||||
[geoFieldName: string]: ESBBox;
|
||||
};
|
||||
geo_distance?: {
|
||||
distance: string;
|
||||
[geoFieldName: string]: Position | { lat: number; lon: number } | string;
|
||||
};
|
||||
geo_shape?: {
|
||||
[geoFieldName: string]: GeoShapeQueryBody;
|
||||
};
|
||||
};
|
||||
|
||||
export interface PreIndexedShape {
|
||||
index: string;
|
||||
id: string | number;
|
||||
path: string;
|
||||
}
|
||||
|
||||
function ensureGeoField(type: string) {
|
||||
const expectedTypes = [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE];
|
||||
if (!expectedTypes.includes(type)) {
|
||||
if (!expectedTypes.includes(type as ES_GEO_FIELD_TYPE)) {
|
||||
const errorMessage = i18n.translate(
|
||||
'xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage',
|
||||
{
|
||||
|
@ -41,8 +86,8 @@ function ensureGeoField(type) {
|
|||
}
|
||||
}
|
||||
|
||||
function ensureGeometryType(type, expectedTypes) {
|
||||
if (!expectedTypes.includes(type)) {
|
||||
function ensureGeometryType(type: string, expectedTypes: GEO_JSON_TYPE[]) {
|
||||
if (!expectedTypes.includes(type as GEO_JSON_TYPE)) {
|
||||
const errorMessage = i18n.translate(
|
||||
'xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage',
|
||||
{
|
||||
|
@ -68,36 +113,48 @@ function ensureGeometryType(type, expectedTypes) {
|
|||
* @param {string} geoFieldType Geometry field type ["geo_point", "geo_shape"]
|
||||
* @returns {number}
|
||||
*/
|
||||
export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType, epochMillisFields) {
|
||||
const features = [];
|
||||
const tmpGeometriesAccumulator = [];
|
||||
export function hitsToGeoJson(
|
||||
hits: Array<Record<string, unknown>>,
|
||||
flattenHit: (elasticSearchHit: Record<string, unknown>) => Record<string, unknown>,
|
||||
geoFieldName: string,
|
||||
geoFieldType: ES_GEO_FIELD_TYPE,
|
||||
epochMillisFields: string[]
|
||||
): FeatureCollection {
|
||||
const features: Feature[] = [];
|
||||
const tmpGeometriesAccumulator: Geometry[] = [];
|
||||
|
||||
for (let i = 0; i < hits.length; i++) {
|
||||
const properties = flattenHit(hits[i]);
|
||||
|
||||
tmpGeometriesAccumulator.length = 0; //truncate accumulator
|
||||
tmpGeometriesAccumulator.length = 0; // truncate accumulator
|
||||
|
||||
ensureGeoField(geoFieldType);
|
||||
if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) {
|
||||
geoPointToGeometry(properties[geoFieldName], tmpGeometriesAccumulator);
|
||||
geoPointToGeometry(
|
||||
properties[geoFieldName] as string | string[] | undefined,
|
||||
tmpGeometriesAccumulator
|
||||
);
|
||||
} else {
|
||||
geoShapeToGeometry(properties[geoFieldName], tmpGeometriesAccumulator);
|
||||
geoShapeToGeometry(
|
||||
properties[geoFieldName] as string | string[] | ESGeometry | ESGeometry[] | undefined,
|
||||
tmpGeometriesAccumulator
|
||||
);
|
||||
}
|
||||
|
||||
// There is a bug in Elasticsearch API where epoch_millis are returned as a string instead of a number
|
||||
// https://github.com/elastic/elasticsearch/issues/50622
|
||||
// Convert these field values to integers.
|
||||
for (let i = 0; i < epochMillisFields.length; i++) {
|
||||
const fieldName = epochMillisFields[i];
|
||||
for (let k = 0; k < epochMillisFields.length; k++) {
|
||||
const fieldName = epochMillisFields[k];
|
||||
if (typeof properties[fieldName] === 'string') {
|
||||
properties[fieldName] = parseInt(properties[fieldName]);
|
||||
properties[fieldName] = parseInt(properties[fieldName] as string, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// don't include geometry field value in properties
|
||||
delete properties[geoFieldName];
|
||||
|
||||
//create new geojson Feature for every individual geojson geometry.
|
||||
// create new geojson Feature for every individual geojson geometry.
|
||||
for (let j = 0; j < tmpGeometriesAccumulator.length; j++) {
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
|
@ -112,7 +169,7 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType, epoc
|
|||
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: features,
|
||||
features,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -120,7 +177,10 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType, epoc
|
|||
// Either
|
||||
// 1) Array of latLon strings
|
||||
// 2) latLon string
|
||||
export function geoPointToGeometry(value, accumulator) {
|
||||
export function geoPointToGeometry(
|
||||
value: string[] | string | undefined,
|
||||
accumulator: Geometry[]
|
||||
): void {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
@ -138,10 +198,10 @@ export function geoPointToGeometry(value, accumulator) {
|
|||
accumulator.push({
|
||||
type: GEO_JSON_TYPE.POINT,
|
||||
coordinates: [lon, lat],
|
||||
});
|
||||
} as Point);
|
||||
}
|
||||
|
||||
export function convertESShapeToGeojsonGeometry(value) {
|
||||
export function convertESShapeToGeojsonGeometry(value: ESGeometry): Geometry {
|
||||
const geoJson = {
|
||||
type: value.type,
|
||||
coordinates: value.coordinates,
|
||||
|
@ -183,12 +243,13 @@ export function convertESShapeToGeojsonGeometry(value) {
|
|||
);
|
||||
throw new Error(invalidGeometrycollectionError);
|
||||
case 'envelope':
|
||||
const envelopeCoords = geoJson.coordinates as Position[];
|
||||
// format defined here https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html#_envelope
|
||||
const polygon = formatEnvelopeAsPolygon({
|
||||
minLon: geoJson.coordinates[0][0],
|
||||
maxLon: geoJson.coordinates[1][0],
|
||||
minLat: geoJson.coordinates[1][1],
|
||||
maxLat: geoJson.coordinates[0][1],
|
||||
minLon: envelopeCoords[0][0],
|
||||
maxLon: envelopeCoords[1][0],
|
||||
minLat: envelopeCoords[1][1],
|
||||
maxLat: envelopeCoords[0][1],
|
||||
});
|
||||
geoJson.type = polygon.type;
|
||||
geoJson.coordinates = polygon.coordinates;
|
||||
|
@ -205,10 +266,10 @@ export function convertESShapeToGeojsonGeometry(value) {
|
|||
);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
return geoJson;
|
||||
return (geoJson as unknown) as Geometry;
|
||||
}
|
||||
|
||||
function convertWKTStringToGeojson(value) {
|
||||
function convertWKTStringToGeojson(value: string): Geometry {
|
||||
try {
|
||||
return parse(value);
|
||||
} catch (e) {
|
||||
|
@ -222,7 +283,10 @@ function convertWKTStringToGeojson(value) {
|
|||
}
|
||||
}
|
||||
|
||||
export function geoShapeToGeometry(value, accumulator) {
|
||||
export function geoShapeToGeometry(
|
||||
value: string | ESGeometry | string[] | ESGeometry[] | undefined,
|
||||
accumulator: Geometry[]
|
||||
): void {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
@ -243,8 +307,9 @@ export function geoShapeToGeometry(value, accumulator) {
|
|||
value.type === GEO_JSON_TYPE.GEOMETRY_COLLECTION ||
|
||||
value.type === 'geometrycollection'
|
||||
) {
|
||||
for (let i = 0; i < value.geometries.length; i++) {
|
||||
geoShapeToGeometry(value.geometries[i], accumulator);
|
||||
const geometryCollection = (value as unknown) as { geometries: ESGeometry[] };
|
||||
for (let i = 0; i < geometryCollection.geometries.length; i++) {
|
||||
geoShapeToGeometry(geometryCollection.geometries[i], accumulator);
|
||||
}
|
||||
} else {
|
||||
const geoJson = convertESShapeToGeojsonGeometry(value);
|
||||
|
@ -252,7 +317,7 @@ export function geoShapeToGeometry(value, accumulator) {
|
|||
}
|
||||
}
|
||||
|
||||
export function makeESBbox({ maxLat, maxLon, minLat, minLon }) {
|
||||
export function makeESBbox({ maxLat, maxLon, minLat, minLon }: MapExtent): ESBBox {
|
||||
const bottom = clampToLatBounds(minLat);
|
||||
const top = clampToLatBounds(maxLat);
|
||||
let esBbox;
|
||||
|
@ -280,11 +345,16 @@ export function makeESBbox({ maxLat, maxLon, minLat, minLon }) {
|
|||
return esBbox;
|
||||
}
|
||||
|
||||
export function createExtentFilter(mapExtent, geoFieldName) {
|
||||
const boundingBox = makeESBbox(mapExtent);
|
||||
export function createExtentFilter(mapExtent: MapExtent, geoFieldName: string): GeoFilter {
|
||||
return {
|
||||
geo_bounding_box: {
|
||||
[geoFieldName]: boundingBox,
|
||||
[geoFieldName]: makeESBbox(mapExtent),
|
||||
},
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
negate: false,
|
||||
key: geoFieldName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -297,6 +367,14 @@ export function createSpatialFilterWithGeometry({
|
|||
geoFieldName,
|
||||
geoFieldType,
|
||||
relation = ES_SPATIAL_RELATIONS.INTERSECTS,
|
||||
}: {
|
||||
preIndexedShape?: PreIndexedShape;
|
||||
geometry: Polygon;
|
||||
geometryLabel: string;
|
||||
indexPatternId: string;
|
||||
geoFieldName: string;
|
||||
geoFieldType: ES_GEO_FIELD_TYPE;
|
||||
relation: ES_SPATIAL_RELATIONS;
|
||||
}) {
|
||||
ensureGeoField(geoFieldType);
|
||||
|
||||
|
@ -315,7 +393,7 @@ export function createSpatialFilterWithGeometry({
|
|||
alias: `${geoFieldName} ${relationLabel} ${geometryLabel}`,
|
||||
};
|
||||
|
||||
const shapeQuery = {
|
||||
const shapeQuery: GeoShapeQueryBody = {
|
||||
// geo_shape query with geo_point field only supports intersects relation
|
||||
relation: isGeoPoint ? ES_SPATIAL_RELATIONS.INTERSECTS : relation,
|
||||
};
|
||||
|
@ -341,6 +419,12 @@ export function createDistanceFilterWithMeta({
|
|||
geoFieldName,
|
||||
indexPatternId,
|
||||
point,
|
||||
}: {
|
||||
alias: string;
|
||||
distanceKm: number;
|
||||
geoFieldName: string;
|
||||
indexPatternId: string;
|
||||
point: Position;
|
||||
}) {
|
||||
const meta = {
|
||||
type: SPATIAL_FILTER_TYPE,
|
||||
|
@ -368,7 +452,7 @@ export function createDistanceFilterWithMeta({
|
|||
};
|
||||
}
|
||||
|
||||
export function roundCoordinates(coordinates) {
|
||||
export function roundCoordinates(coordinates: Coordinates): void {
|
||||
for (let i = 0; i < coordinates.length; i++) {
|
||||
const value = coordinates[i];
|
||||
if (Array.isArray(value)) {
|
||||
|
@ -382,10 +466,10 @@ export function roundCoordinates(coordinates) {
|
|||
/*
|
||||
* returns Polygon geometry where coordinates define a bounding box that contains the input geometry
|
||||
*/
|
||||
export function getBoundingBoxGeometry(geometry) {
|
||||
export function getBoundingBoxGeometry(geometry: Geometry): Polygon {
|
||||
ensureGeometryType(geometry.type, [GEO_JSON_TYPE.POLYGON]);
|
||||
|
||||
const exterior = geometry.coordinates[POLYGON_COORDINATES_EXTERIOR_INDEX];
|
||||
const exterior = (geometry as Polygon).coordinates[POLYGON_COORDINATES_EXTERIOR_INDEX];
|
||||
const extent = {
|
||||
minLon: exterior[0][LON_INDEX],
|
||||
minLat: exterior[0][LAT_INDEX],
|
||||
|
@ -402,7 +486,7 @@ export function getBoundingBoxGeometry(geometry) {
|
|||
return formatEnvelopeAsPolygon(extent);
|
||||
}
|
||||
|
||||
export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) {
|
||||
export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }: MapExtent): Polygon {
|
||||
// GeoJSON mandates that the outer polygon must be counterclockwise to avoid ambiguous polygons
|
||||
// when the shape crosses the dateline
|
||||
const lonDelta = maxLon - minLon;
|
||||
|
@ -410,25 +494,25 @@ export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) {
|
|||
const right = lonDelta > 360 ? 180 : maxLon;
|
||||
const top = clampToLatBounds(maxLat);
|
||||
const bottom = clampToLatBounds(minLat);
|
||||
const topLeft = [left, top];
|
||||
const bottomLeft = [left, bottom];
|
||||
const bottomRight = [right, bottom];
|
||||
const topRight = [right, top];
|
||||
const topLeft = [left, top] as Position;
|
||||
const bottomLeft = [left, bottom] as Position;
|
||||
const bottomRight = [right, bottom] as Position;
|
||||
const topRight = [right, top] as Position;
|
||||
return {
|
||||
type: GEO_JSON_TYPE.POLYGON,
|
||||
coordinates: [[topLeft, bottomLeft, bottomRight, topRight, topLeft]],
|
||||
};
|
||||
} as Polygon;
|
||||
}
|
||||
|
||||
export function clampToLatBounds(lat) {
|
||||
export function clampToLatBounds(lat: number): number {
|
||||
return clamp(lat, -89, 89);
|
||||
}
|
||||
|
||||
export function clampToLonBounds(lon) {
|
||||
export function clampToLonBounds(lon: number): number {
|
||||
return clamp(lon, -180, 180);
|
||||
}
|
||||
|
||||
export function clamp(val, min, max) {
|
||||
export function clamp(val: number, min: number, max: number): number {
|
||||
if (val > max) {
|
||||
return max;
|
||||
} else if (val < min) {
|
||||
|
@ -438,25 +522,26 @@ export function clamp(val, min, max) {
|
|||
}
|
||||
}
|
||||
|
||||
export function extractFeaturesFromFilters(filters) {
|
||||
const features = [];
|
||||
export function extractFeaturesFromFilters(filters: GeoFilter[]): Feature[] {
|
||||
const features: Feature[] = [];
|
||||
filters
|
||||
.filter((filter) => {
|
||||
return filter.meta.key && filter.meta.type === SPATIAL_FILTER_TYPE;
|
||||
})
|
||||
.forEach((filter) => {
|
||||
const geoFieldName = filter.meta.key!;
|
||||
let geometry;
|
||||
if (filter.geo_distance && filter.geo_distance[filter.meta.key]) {
|
||||
if (filter.geo_distance && filter.geo_distance[geoFieldName]) {
|
||||
const distanceSplit = filter.geo_distance.distance.split('km');
|
||||
const distance = parseFloat(distanceSplit[0]);
|
||||
const circleFeature = turfCircle(filter.geo_distance[filter.meta.key], distance);
|
||||
const circleFeature = turfCircle(filter.geo_distance[geoFieldName], distance);
|
||||
geometry = circleFeature.geometry;
|
||||
} else if (
|
||||
filter.geo_shape &&
|
||||
filter.geo_shape[filter.meta.key] &&
|
||||
filter.geo_shape[filter.meta.key].shape
|
||||
filter.geo_shape[geoFieldName] &&
|
||||
filter.geo_shape[geoFieldName].shape
|
||||
) {
|
||||
geometry = filter.geo_shape[filter.meta.key].shape;
|
||||
geometry = filter.geo_shape[geoFieldName].shape;
|
||||
} else {
|
||||
// do not know how to convert spatial filter to geometry
|
||||
// this includes pre-indexed shapes
|
||||
|
@ -475,7 +560,7 @@ export function extractFeaturesFromFilters(filters) {
|
|||
return features;
|
||||
}
|
||||
|
||||
export function scaleBounds(bounds, scaleFactor) {
|
||||
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent {
|
||||
const width = bounds.maxLon - bounds.minLon;
|
||||
const height = bounds.maxLat - bounds.minLat;
|
||||
return {
|
||||
|
@ -486,7 +571,7 @@ export function scaleBounds(bounds, scaleFactor) {
|
|||
};
|
||||
}
|
||||
|
||||
export function turfBboxToBounds(turfBbox) {
|
||||
export function turfBboxToBounds(turfBbox: BBox): MapExtent {
|
||||
return {
|
||||
minLon: turfBbox[0],
|
||||
minLat: turfBbox[1],
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import { ES_SPATIAL_RELATIONS } from './constants';
|
||||
|
||||
export function getAppTitle() {
|
||||
|
@ -34,7 +33,7 @@ export function getUrlLabel() {
|
|||
});
|
||||
}
|
||||
|
||||
export function getEsSpatialRelationLabel(spatialRelation: $Values<typeof ES_SPATIAL_RELATIONS>) {
|
||||
export function getEsSpatialRelationLabel(spatialRelation: ES_SPATIAL_RELATIONS) {
|
||||
switch (spatialRelation) {
|
||||
case ES_SPATIAL_RELATIONS.INTERSECTS:
|
||||
return i18n.translate('xpack.maps.common.esSpatialRelation.intersectsLabel', {
|
||||
|
|
|
@ -206,6 +206,12 @@ describe('ESGeoGridSource', () => {
|
|||
expect(getProperty('filter')).toEqual([
|
||||
{
|
||||
geo_bounding_box: { bar: { bottom_right: [180, -82.67628], top_left: [-180, 82.67628] } },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
key: 'bar',
|
||||
negate: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(getProperty('aggs')).toEqual({
|
||||
|
@ -277,7 +283,7 @@ describe('ESGeoGridSource', () => {
|
|||
expect(urlTemplateWithMeta.minSourceZoom).toBe(0);
|
||||
expect(urlTemplateWithMeta.maxSourceZoom).toBe(24);
|
||||
expect(urlTemplateWithMeta.urlTemplate).toBe(
|
||||
"rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point"
|
||||
"rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628))),meta:(alias:!n,disabled:!f,key:bar,negate:!f)))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point"
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -288,7 +294,7 @@ describe('ESGeoGridSource', () => {
|
|||
});
|
||||
|
||||
expect(urlTemplateWithMeta.urlTemplate).toBe(
|
||||
"rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1"
|
||||
"rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628))),meta:(alias:!n,disabled:!f,key:bar,negate:!f)))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,12 @@ import { IFieldType, IndexPattern } from 'src/plugins/data/public';
|
|||
import { GeoJsonProperties } from 'geojson';
|
||||
import { AbstractESSource } from '../es_source';
|
||||
import { getHttp, getSearchService } from '../../../kibana_services';
|
||||
import { addFieldToDSL, getField, hitsToGeoJson } from '../../../../common/elasticsearch_util';
|
||||
import {
|
||||
addFieldToDSL,
|
||||
getField,
|
||||
hitsToGeoJson,
|
||||
PreIndexedShape,
|
||||
} from '../../../../common/elasticsearch_util';
|
||||
// @ts-expect-error
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
|
||||
|
@ -43,7 +48,7 @@ import {
|
|||
VectorSourceSyncMeta,
|
||||
} from '../../../../common/descriptor_types';
|
||||
import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters';
|
||||
import { ImmutableSourceProperty, PreIndexedShape, SourceEditorArgs } from '../source';
|
||||
import { ImmutableSourceProperty, SourceEditorArgs } from '../source';
|
||||
import { IField } from '../../fields/field';
|
||||
import {
|
||||
GeoJsonWithMeta,
|
||||
|
|
|
@ -238,7 +238,6 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
|
|||
: searchFilters.buffer;
|
||||
const extentFilter = createExtentFilter(buffer, geoField.name);
|
||||
|
||||
// @ts-expect-error
|
||||
allFilters.push(extentFilter);
|
||||
}
|
||||
if (searchFilters.applyGlobalTime && (await this.isTimeAware())) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
|
|||
import { AbstractSourceDescriptor } from '../../../common/descriptor_types';
|
||||
import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view';
|
||||
import { LICENSED_FEATURES } from '../../licensed_features';
|
||||
import { PreIndexedShape } from '../../../common/elasticsearch_util';
|
||||
|
||||
export type SourceEditorArgs = {
|
||||
onChange: (...args: OnSourceChangeArgs[]) => void;
|
||||
|
@ -35,12 +36,6 @@ export type Attribution = {
|
|||
label: string;
|
||||
};
|
||||
|
||||
export type PreIndexedShape = {
|
||||
index: string;
|
||||
id: string | number;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export interface ISource {
|
||||
destroy(): void;
|
||||
getDisplayName(): Promise<string>;
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
// - only fields from the response are packed in the tile (more efficient)
|
||||
// - query-dsl submitted from the client, which was generated by the IndexPattern
|
||||
// todo: Ideally, this should adapt/reuse from https://github.com/elastic/kibana/blob/52b42a81faa9dd5c102b9fbb9a645748c3623121/src/plugins/data/common/index_patterns/index_patterns/flatten_hit.ts#L26
|
||||
import { GeoJsonProperties } from 'geojson';
|
||||
|
||||
export function flattenHit(geometryField: string, hit: Record<string, unknown>): GeoJsonProperties {
|
||||
const flat: GeoJsonProperties = {};
|
||||
export function flattenHit(
|
||||
geometryField: string,
|
||||
hit: Record<string, unknown>
|
||||
): Record<string, any> {
|
||||
const flat: Record<string, any> = {};
|
||||
if (hit) {
|
||||
flattenSource(flat, '', hit._source as Record<string, unknown>, geometryField);
|
||||
if (hit.fields) {
|
||||
|
@ -30,11 +32,11 @@ export function flattenHit(geometryField: string, hit: Record<string, unknown>):
|
|||
}
|
||||
|
||||
function flattenSource(
|
||||
accum: GeoJsonProperties,
|
||||
accum: Record<string, any>,
|
||||
path: string,
|
||||
properties: Record<string, unknown> = {},
|
||||
geometryField: string
|
||||
): GeoJsonProperties {
|
||||
): Record<string, any> {
|
||||
accum = accum || {};
|
||||
for (const key in properties) {
|
||||
if (properties.hasOwnProperty(key)) {
|
||||
|
@ -58,7 +60,7 @@ function flattenSource(
|
|||
return accum;
|
||||
}
|
||||
|
||||
function flattenFields(accum: GeoJsonProperties = {}, fields: Array<Record<string, unknown>>) {
|
||||
function flattenFields(accum: Record<string, any> = {}, fields: Array<Record<string, unknown>>) {
|
||||
accum = accum || {};
|
||||
for (const key in fields) {
|
||||
if (fields.hasOwnProperty(key)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue