mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] remove dateline check for geo_shape queries, split geo_bounding_box queries that cross dateline into 2 boxes (#64598)
* remove dateline check for geo_shape queries * fix jest test * split bounding box * replace convertMapExtentToPolygon with formatEnvelopeAsPolygon * clamp latitudes * use clampToLatBounds * use single box where left lon is greater then right lon when crossing 180 meridian Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
7526db98e8
commit
431a7eb07a
3 changed files with 165 additions and 221 deletions
|
@ -19,7 +19,7 @@ import {
|
|||
} from '../../selectors/map_selectors';
|
||||
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors';
|
||||
|
||||
import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils';
|
||||
import { formatEnvelopeAsPolygon } from '../../elasticsearch_geo_utils';
|
||||
|
||||
import { copyPersistentState } from '../../reducers/util';
|
||||
import { extractReferences, injectReferences } from '../../../common/migrations/references';
|
||||
|
@ -107,7 +107,7 @@ export function createSavedGisMapClass(services) {
|
|||
openTOCDetails: getOpenTOCDetails(state),
|
||||
});
|
||||
|
||||
this.bounds = convertMapExtentToPolygon(getMapExtent(state));
|
||||
this.bounds = formatEnvelopeAsPolygon(getMapExtent(state));
|
||||
}
|
||||
}
|
||||
return SavedGisMap;
|
||||
|
|
|
@ -225,41 +225,62 @@ export function geoShapeToGeometry(value, accumulator) {
|
|||
accumulator.push(geoJson);
|
||||
}
|
||||
|
||||
function createGeoBoundBoxFilter(geometry, geoFieldName, filterProps = {}) {
|
||||
ensureGeometryType(geometry.type, [GEO_JSON_TYPE.POLYGON]);
|
||||
function createGeoBoundBoxFilter({ maxLat, maxLon, minLat, minLon }, geoFieldName) {
|
||||
const top = clampToLatBounds(maxLat);
|
||||
const bottom = clampToLatBounds(minLat);
|
||||
|
||||
// geo_bounding_box does not support ranges outside of -180 and 180
|
||||
// When the area crosses the 180° meridian,
|
||||
// the value of the lower left longitude will be greater than the value of the upper right longitude.
|
||||
// http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#30
|
||||
let boundingBox;
|
||||
if (maxLon - minLon >= 360) {
|
||||
boundingBox = {
|
||||
top_left: [-180, top],
|
||||
bottom_right: [180, bottom],
|
||||
};
|
||||
} else if (maxLon > 180) {
|
||||
const overflow = maxLon - 180;
|
||||
boundingBox = {
|
||||
top_left: [minLon, top],
|
||||
bottom_right: [-180 + overflow, bottom],
|
||||
};
|
||||
} else if (minLon < -180) {
|
||||
const overflow = Math.abs(minLon) - 180;
|
||||
boundingBox = {
|
||||
top_left: [180 - overflow, top],
|
||||
bottom_right: [maxLon, bottom],
|
||||
};
|
||||
} else {
|
||||
boundingBox = {
|
||||
top_left: [minLon, top],
|
||||
bottom_right: [maxLon, bottom],
|
||||
};
|
||||
}
|
||||
|
||||
const TOP_LEFT_INDEX = 0;
|
||||
const BOTTOM_RIGHT_INDEX = 2;
|
||||
const verticies = geometry.coordinates[POLYGON_COORDINATES_EXTERIOR_INDEX];
|
||||
return {
|
||||
geo_bounding_box: {
|
||||
[geoFieldName]: {
|
||||
top_left: verticies[TOP_LEFT_INDEX],
|
||||
bottom_right: verticies[BOTTOM_RIGHT_INDEX],
|
||||
},
|
||||
[geoFieldName]: boundingBox,
|
||||
},
|
||||
...filterProps,
|
||||
};
|
||||
}
|
||||
|
||||
export function createExtentFilter(mapExtent, geoFieldName, geoFieldType) {
|
||||
ensureGeoField(geoFieldType);
|
||||
|
||||
const safePolygon = convertMapExtentToPolygon(mapExtent);
|
||||
|
||||
// Extent filters are used to dynamically filter data for the current map view port.
|
||||
// Continue to use geo_bounding_box queries for extent filters
|
||||
// 1) geo_bounding_box queries are faster than polygon queries
|
||||
// 2) geo_shape benefits of pre-indexed shapes and
|
||||
// compatability across multi-indices with geo_point and geo_shape do not apply to this use case.
|
||||
if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) {
|
||||
return createGeoBoundBoxFilter(safePolygon, geoFieldName);
|
||||
return createGeoBoundBoxFilter(mapExtent, geoFieldName);
|
||||
}
|
||||
|
||||
return {
|
||||
geo_shape: {
|
||||
[geoFieldName]: {
|
||||
shape: safePolygon,
|
||||
shape: formatEnvelopeAsPolygon(mapExtent),
|
||||
relation: ES_SPATIAL_RELATIONS.INTERSECTS,
|
||||
},
|
||||
},
|
||||
|
@ -376,16 +397,16 @@ export function getBoundingBoxGeometry(geometry) {
|
|||
extent.maxLat = Math.max(exterior[i][LAT_INDEX], extent.maxLat);
|
||||
}
|
||||
|
||||
return convertMapExtentToPolygon(extent);
|
||||
return formatEnvelopeAsPolygon(extent);
|
||||
}
|
||||
|
||||
function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) {
|
||||
export function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) {
|
||||
// GeoJSON mandates that the outer polygon must be counterclockwise to avoid ambiguous polygons
|
||||
// when the shape crosses the dateline
|
||||
const left = minLon;
|
||||
const right = maxLon;
|
||||
const top = maxLat > 90 ? 90 : maxLat;
|
||||
const bottom = minLat < -90 ? -90 : minLat;
|
||||
const top = clampToLatBounds(maxLat);
|
||||
const bottom = clampToLatBounds(minLat);
|
||||
const topLeft = [left, top];
|
||||
const bottomLeft = [left, bottom];
|
||||
const bottomRight = [right, bottom];
|
||||
|
@ -396,45 +417,6 @@ function formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }) {
|
|||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert map bounds to polygon
|
||||
*/
|
||||
export function convertMapExtentToPolygon({ maxLat, maxLon, minLat, minLon }) {
|
||||
const lonDelta = maxLon - minLon;
|
||||
if (lonDelta >= 360) {
|
||||
return formatEnvelopeAsPolygon({
|
||||
maxLat,
|
||||
maxLon: 180,
|
||||
minLat,
|
||||
minLon: -180,
|
||||
});
|
||||
}
|
||||
|
||||
if (maxLon > 180) {
|
||||
// bounds cross dateline east to west
|
||||
const overlapWestOfDateLine = maxLon - 180;
|
||||
return formatEnvelopeAsPolygon({
|
||||
maxLat,
|
||||
maxLon: -180 + overlapWestOfDateLine,
|
||||
minLat,
|
||||
minLon,
|
||||
});
|
||||
}
|
||||
|
||||
if (minLon < -180) {
|
||||
// bounds cross dateline west to east
|
||||
const overlapEastOfDateLine = Math.abs(minLon) - 180;
|
||||
return formatEnvelopeAsPolygon({
|
||||
maxLat,
|
||||
maxLon,
|
||||
minLat,
|
||||
minLon: 180 - overlapEastOfDateLine,
|
||||
});
|
||||
}
|
||||
|
||||
return formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon });
|
||||
}
|
||||
|
||||
export function clampToLatBounds(lat) {
|
||||
return clamp(lat, -89, 89);
|
||||
}
|
||||
|
|
|
@ -17,19 +17,12 @@ import {
|
|||
geoPointToGeometry,
|
||||
geoShapeToGeometry,
|
||||
createExtentFilter,
|
||||
convertMapExtentToPolygon,
|
||||
roundCoordinates,
|
||||
extractFeaturesFromFilters,
|
||||
} from './elasticsearch_geo_utils';
|
||||
import { indexPatterns } from '../../../../src/plugins/data/public';
|
||||
|
||||
const geoFieldName = 'location';
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: -83,
|
||||
minLat: 35,
|
||||
minLon: -89,
|
||||
};
|
||||
|
||||
const flattenHitMock = hit => {
|
||||
const properties = {};
|
||||
|
@ -317,174 +310,143 @@ describe('geoShapeToGeometry', () => {
|
|||
});
|
||||
|
||||
describe('createExtentFilter', () => {
|
||||
it('should return elasticsearch geo_bounding_box filter for geo_point field', () => {
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_point');
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
bottom_right: [-83, 35],
|
||||
top_left: [-89, 39],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return elasticsearch geo_shape filter for geo_shape field', () => {
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_shape');
|
||||
expect(filter).toEqual({
|
||||
geo_shape: {
|
||||
location: {
|
||||
relation: 'INTERSECTS',
|
||||
shape: {
|
||||
coordinates: [
|
||||
[
|
||||
[-89, 39],
|
||||
[-89, 35],
|
||||
[-83, 35],
|
||||
[-83, 39],
|
||||
[-89, 39],
|
||||
],
|
||||
],
|
||||
type: 'Polygon',
|
||||
describe('geo_point field', () => {
|
||||
it('should return elasticsearch geo_bounding_box filter for geo_point field', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: -83,
|
||||
minLat: 35,
|
||||
minLon: -89,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_point');
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [-89, 39],
|
||||
bottom_right: [-83, 35],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should clamp longitudes to -180 to 180', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: 209,
|
||||
minLat: 35,
|
||||
minLon: -191,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_shape');
|
||||
expect(filter).toEqual({
|
||||
geo_shape: {
|
||||
location: {
|
||||
relation: 'INTERSECTS',
|
||||
shape: {
|
||||
coordinates: [
|
||||
[
|
||||
[-180, 39],
|
||||
[-180, 35],
|
||||
[180, 35],
|
||||
[180, 39],
|
||||
[-180, 39],
|
||||
],
|
||||
],
|
||||
type: 'Polygon',
|
||||
it('should clamp longitudes to -180 to 180 and latitudes to -90 to 90', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 120,
|
||||
maxLon: 200,
|
||||
minLat: -100,
|
||||
minLon: -190,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_point');
|
||||
expect(filter).toEqual({
|
||||
geo_bounding_box: {
|
||||
location: {
|
||||
top_left: [-180, 89],
|
||||
bottom_right: [180, -89],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertMapExtentToPolygon', () => {
|
||||
it('should convert bounds to envelope', () => {
|
||||
const bounds = {
|
||||
maxLat: 10,
|
||||
maxLon: 100,
|
||||
minLat: -10,
|
||||
minLon: 90,
|
||||
};
|
||||
expect(convertMapExtentToPolygon(bounds)).toEqual({
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[90, 10],
|
||||
[90, -10],
|
||||
[100, -10],
|
||||
[100, 10],
|
||||
[90, 10],
|
||||
],
|
||||
],
|
||||
it('should make left longitude greater then right longitude when area crosses 180 meridian east to west', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: 200,
|
||||
minLat: 35,
|
||||
minLon: 100,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_point');
|
||||
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],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should make left longitude greater then right longitude when area crosses 180 meridian west to east', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: -100,
|
||||
minLat: 35,
|
||||
minLon: -200,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_point');
|
||||
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],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should clamp longitudes to -180 to 180', () => {
|
||||
const bounds = {
|
||||
maxLat: 10,
|
||||
maxLon: 200,
|
||||
minLat: -10,
|
||||
minLon: -400,
|
||||
};
|
||||
expect(convertMapExtentToPolygon(bounds)).toEqual({
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[-180, 10],
|
||||
[-180, -10],
|
||||
[180, -10],
|
||||
[180, 10],
|
||||
[-180, 10],
|
||||
],
|
||||
],
|
||||
describe('geo_shape field', () => {
|
||||
it('should return elasticsearch geo_shape filter', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: -83,
|
||||
minLat: 35,
|
||||
minLon: -89,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_shape');
|
||||
expect(filter).toEqual({
|
||||
geo_shape: {
|
||||
location: {
|
||||
relation: 'INTERSECTS',
|
||||
shape: {
|
||||
coordinates: [
|
||||
[
|
||||
[-89, 39],
|
||||
[-89, 35],
|
||||
[-83, 35],
|
||||
[-83, 39],
|
||||
[-89, 39],
|
||||
],
|
||||
],
|
||||
type: 'Polygon',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should clamp longitudes to -180 to 180 when bounds span entire globe (360)', () => {
|
||||
const bounds = {
|
||||
maxLat: 10,
|
||||
maxLon: 170,
|
||||
minLat: -10,
|
||||
minLon: -400,
|
||||
};
|
||||
expect(convertMapExtentToPolygon(bounds)).toEqual({
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[-180, 10],
|
||||
[-180, -10],
|
||||
[180, -10],
|
||||
[180, 10],
|
||||
[-180, 10],
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle bounds that cross dateline(east to west)', () => {
|
||||
const bounds = {
|
||||
maxLat: 10,
|
||||
maxLon: 190,
|
||||
minLat: -10,
|
||||
minLon: 170,
|
||||
};
|
||||
expect(convertMapExtentToPolygon(bounds)).toEqual({
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[170, 10],
|
||||
[170, -10],
|
||||
[-170, -10],
|
||||
[-170, 10],
|
||||
[170, 10],
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle bounds that cross dateline(west to east)', () => {
|
||||
const bounds = {
|
||||
maxLat: 10,
|
||||
maxLon: -170,
|
||||
minLat: -10,
|
||||
minLon: -190,
|
||||
};
|
||||
expect(convertMapExtentToPolygon(bounds)).toEqual({
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[170, 10],
|
||||
[170, -10],
|
||||
[-170, -10],
|
||||
[-170, 10],
|
||||
[170, 10],
|
||||
],
|
||||
],
|
||||
it('should not clamp longitudes to -180 to 180', () => {
|
||||
const mapExtent = {
|
||||
maxLat: 39,
|
||||
maxLon: 209,
|
||||
minLat: 35,
|
||||
minLon: -191,
|
||||
};
|
||||
const filter = createExtentFilter(mapExtent, geoFieldName, 'geo_shape');
|
||||
expect(filter).toEqual({
|
||||
geo_shape: {
|
||||
location: {
|
||||
relation: 'INTERSECTS',
|
||||
shape: {
|
||||
coordinates: [
|
||||
[
|
||||
[-191, 39],
|
||||
[-191, 35],
|
||||
[209, 35],
|
||||
[209, 39],
|
||||
[-191, 39],
|
||||
],
|
||||
],
|
||||
type: 'Polygon',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue