[Maps] Improve ES search decoding to deal with large geometries (#36062) (#36181)

- do not clone geometries when decoding results from ES
- reduce number of object-allocations when decoding results
This commit is contained in:
Thomas Neirynck 2019-05-07 14:03:55 -04:00 committed by GitHub
parent 650c7d6d8e
commit c6f89244f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 38 deletions

View file

@ -19,14 +19,16 @@ import { i18n } from '@kbn/i18n';
*/
export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType) {
const features = [];
hits.forEach(hit => {
const properties = flattenHit(hit);
const tmpGeometriesAccumulator = [];
let geometries;
for (let i = 0; i < hits.length; i++) {
const properties = flattenHit(hits[i]);
tmpGeometriesAccumulator.length = 0;//truncate accumulator
if (geoFieldType === 'geo_point') {
geometries = geoPointToGeometry(properties[geoFieldName]);
geoPointToGeometry(properties[geoFieldName], tmpGeometriesAccumulator);
} else if (geoFieldType === 'geo_shape') {
geometries = geoShapeToGeometry(properties[geoFieldName]);
geoShapeToGeometry(properties[geoFieldName], tmpGeometriesAccumulator);
} else {
const errorMessage = i18n.translate('xpack.maps.elasticsearch_geo_utils.unsupportedFieldTypeErrorMessage', {
defaultMessage: 'Unsupported field type, expected: geo_shape or geo_point, you provided: {geoFieldType}',
@ -34,18 +36,18 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType) {
});
throw new Error(errorMessage);
}
// don't include geometry field value in properties
delete properties[geoFieldName];
return geometries.map(geometry => {
//create new geojson Feature for every individual geojson geometry.
for (let j = 0; j < tmpGeometriesAccumulator.length; j++) {
features.push({
type: 'Feature',
geometry: geometry,
geometry: tmpGeometriesAccumulator[j],
properties: properties
});
});
});
}
}
return {
type: 'FeatureCollection',
@ -60,9 +62,9 @@ function pointGeometryFactory(lat, lon) {
};
}
export function geoPointToGeometry(value) {
export function geoPointToGeometry(value, accumulator) {
if (!value) {
return [];
return;
}
if (typeof value === 'string') {
@ -77,12 +79,14 @@ export function geoPointToGeometry(value) {
// Geo-point expressed as a string with the format: "lat,lon".
const lat = parseFloat(commaSplit[0]);
const lon = parseFloat(commaSplit[1]);
return [pointGeometryFactory(lat, lon)];
accumulator.push(pointGeometryFactory(lat, lon));
return;
}
if (typeof value === 'object' && _.has(value, 'lat') && _.has(value, 'lon')) {
// Geo-point expressed as an object with the format: { lon, lat }
return [pointGeometryFactory(value.lat, value.lon)];
accumulator.push(pointGeometryFactory(value.lat, value.lon));
return;
}
if (!Array.isArray(value)) {
@ -101,31 +105,27 @@ export function geoPointToGeometry(value) {
// Geo-point expressed as an array with the format: [lon, lat]
const lat = value[1];
const lon = value[0];
return [pointGeometryFactory(lat, lon)];
accumulator.push(pointGeometryFactory(lat, lon));
return;
}
// Geo-point expressed as an array of values
return value.reduce(
(points, itemInValueArray) => {
return points.concat(geoPointToGeometry(itemInValueArray));
},
[]
);
for (let i = 0; i < value.length; i++) {
geoPointToGeometry(value[i], accumulator);
}
}
export function geoShapeToGeometry(value) {
export function geoShapeToGeometry(value, accumulator) {
if (!value) {
return [];
return;
}
if (Array.isArray(value)) {
// value expressed as an array of values
return value.reduce(
(shapes, itemInValueArray) => {
return shapes.concat(geoShapeToGeometry(itemInValueArray));
},
[]
);
for (let i = 0; i < value.length; i++) {
geoShapeToGeometry(value[i], accumulator);
}
return;
}
// TODO handle case where value is WKT and convert to geojson
@ -136,13 +136,18 @@ export function geoShapeToGeometry(value) {
throw new Error(errorMessage);
}
const geoJson = _.cloneDeep(value);
const geoJson = {
type: value.type,
coordinates: value.coordinates
};
// https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html#input-structure
// For some unknown compatibility nightmarish reason, Elasticsearch types are not capitalized the same as geojson types
// For example: 'LineString' geojson type is 'linestring' in elasticsearch
// Convert feature types to geojson spec values
switch (geoJson.type) {
// Sometimes, the type in ES is capitalized correctly. Sometimes it is not. It depends on how the doc was ingested
// The below is the correction in-place.
switch (value.type) {
case 'point':
geoJson.type = 'Point';
break;
@ -170,7 +175,7 @@ export function geoShapeToGeometry(value) {
throw new Error(`Unable to convert ${geoJson.type} geometry to geojson, not supported`);
}
return [geoJson];
accumulator.push(geoJson);
}
const POLYGON_COORDINATES_EXTERIOR_INDEX = 0;

View file

@ -200,7 +200,8 @@ describe('geoPointToGeometry', () => {
it('Should convert value stored as geo-point string', () => {
const value = `${lat},${lon}`;
const points = geoPointToGeometry(value);
const points = [];
geoPointToGeometry(value, points);
expect(points.length).toBe(1);
expect(points[0].type).toBe('Point');
expect(points[0].coordinates).toEqual([lon, lat]);
@ -208,7 +209,8 @@ describe('geoPointToGeometry', () => {
it('Should convert value stored as geo-point array', () => {
const value = [lon, lat];
const points = geoPointToGeometry(value);
const points = [];
geoPointToGeometry(value, points);
expect(points.length).toBe(1);
expect(points[0].type).toBe('Point');
expect(points[0].coordinates).toEqual([lon, lat]);
@ -219,7 +221,8 @@ describe('geoPointToGeometry', () => {
lat,
lon,
};
const points = geoPointToGeometry(value);
const points = [];
geoPointToGeometry(value, points);
expect(points.length).toBe(1);
expect(points[0].type).toBe('Point');
expect(points[0].coordinates).toEqual([lon, lat]);
@ -235,7 +238,8 @@ describe('geoPointToGeometry', () => {
},
`${lat2},${lon2}`
];
const points = geoPointToGeometry(value);
const points = [];
geoPointToGeometry(value, points);
expect(points.length).toBe(2);
expect(points[0].coordinates).toEqual([lon, lat]);
expect(points[1].coordinates).toEqual([lon2, lat2]);
@ -249,7 +253,8 @@ describe('geoShapeToGeometry', () => {
type: 'linestring',
coordinates: coordinates
};
const shapes = geoShapeToGeometry(value);
const shapes = [];
geoShapeToGeometry(value, shapes);
expect(shapes.length).toBe(1);
expect(shapes[0].type).toBe('LineString');
expect(shapes[0].coordinates).toEqual(coordinates);
@ -268,7 +273,8 @@ describe('geoShapeToGeometry', () => {
coordinates: pointCoordinates
}
];
const shapes = geoShapeToGeometry(value);
const shapes = [];
geoShapeToGeometry(value, shapes);
expect(shapes.length).toBe(2);
expect(shapes[0].type).toBe('LineString');
expect(shapes[0].coordinates).toEqual(linestringCoordinates);