diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index 6f9c0985f5f4a..fd972219563a8 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -215,3 +215,5 @@ export enum SCALING_TYPES {
}
export const RGBA_0000 = 'rgba(0,0,0,0)';
+
+export const SPATIAL_FILTERS_LAYER_ID = 'SPATIAL_FILTERS_LAYER_ID';
diff --git a/x-pack/plugins/maps/public/components/alpha_slider.tsx b/x-pack/plugins/maps/public/components/alpha_slider.tsx
new file mode 100644
index 0000000000000..921c386292050
--- /dev/null
+++ b/x-pack/plugins/maps/public/components/alpha_slider.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiFormRow } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+// @ts-ignore
+import { ValidatedRange } from './validated_range';
+
+interface Props {
+ alpha: number;
+ onChange: (alpha: number) => void;
+}
+
+export function AlphaSlider({ alpha, onChange }: Props) {
+ const onAlphaChange = (newAlpha: number) => {
+ onChange(newAlpha / 100);
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js
index 168c735ab7a6c..d84d05260f982 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js
@@ -8,7 +8,7 @@ import React, { Fragment } from 'react';
import { EuiTitle, EuiPanel, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui';
-import { ValidatedRange } from '../../../components/validated_range';
+import { AlphaSlider } from '../../../components/alpha_slider';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ValidatedDualRange } from '../../../../../../../src/plugins/kibana_react/public';
@@ -24,8 +24,7 @@ export function LayerSettings(props) {
};
const onAlphaChange = alpha => {
- const alphaDecimal = alpha / 100;
- props.updateAlpha(props.layerId, alphaDecimal);
+ props.updateAlpha(props.layerId, alpha);
};
const renderZoomSliders = () => {
@@ -64,34 +63,6 @@ export function LayerSettings(props) {
);
};
- const renderAlphaSlider = () => {
- const alphaPercent = Math.round(props.alpha * 100);
-
- return (
-
-
-
- );
- };
-
return (
@@ -107,7 +78,7 @@ export function LayerSettings(props) {
{renderLabel()}
{renderZoomSliders()}
- {renderAlphaSlider()}
+
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
index d20faa39d6492..a69e06458a6a0 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
@@ -64,13 +64,28 @@ export class DrawControl extends React.Component {
if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
const circle = e.features[0];
- roundCoordinates(circle.properties.center);
+ const distanceKm = _.round(
+ circle.properties.radiusKm,
+ circle.properties.radiusKm > 10 ? 0 : 2
+ );
+ // Only include as much precision as needed for distance
+ let precision = 2;
+ if (distanceKm <= 1) {
+ precision = 5;
+ } else if (distanceKm <= 10) {
+ precision = 4;
+ } else if (distanceKm <= 100) {
+ precision = 3;
+ }
const filter = createDistanceFilterWithMeta({
alias: this.props.drawState.filterLabel,
- distanceKm: _.round(circle.properties.radiusKm, circle.properties.radiusKm > 10 ? 0 : 2),
+ distanceKm,
geoFieldName: this.props.drawState.geoFieldName,
indexPatternId: this.props.drawState.indexPatternId,
- point: circle.properties.center,
+ point: [
+ _.round(circle.properties.center[0], precision),
+ _.round(circle.properties.center[1], precision),
+ ],
});
this.props.addFilters([filter]);
this.props.disableDrawState();
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/plugins/maps/public/connected_components/map/mb/index.js
index 459b38d422694..f8daf0804265b 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/index.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/index.js
@@ -23,6 +23,7 @@ import {
isInteractiveDisabled,
isTooltipControlDisabled,
isViewControlHidden,
+ getSpatialFiltersLayer,
getMapSettings,
} from '../../../selectors/map_selectors';
@@ -33,6 +34,7 @@ function mapStateToProps(state = {}) {
isMapReady: getMapReady(state),
settings: getMapSettings(state),
layerList: getLayerList(state),
+ spatialFiltersLayer: getSpatialFiltersLayer(state),
goto: getGoto(state),
inspectorAdapters: getInspectorAdapters(state),
scrollZoom: getScrollZoom(state),
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js b/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js
index a8c4f61a00da3..4774cdc556c24 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js
@@ -5,6 +5,7 @@
*/
import { removeOrphanedSourcesAndLayers, syncLayerOrderForSingleLayer } from './utils';
+import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants';
import _ from 'lodash';
class MockMbMap {
@@ -121,7 +122,8 @@ function makeMultiSourceMockLayer(layerId) {
);
}
-describe('mb/utils', () => {
+describe('removeOrphanedSourcesAndLayers', () => {
+ const spatialFilterLayer = makeMultiSourceMockLayer(SPATIAL_FILTERS_LAYER_ID);
test('should remove foo and bar layer', async () => {
const bazLayer = makeSingleSourceMockLayer('baz');
const fooLayer = makeSingleSourceMockLayer('foo');
@@ -133,7 +135,7 @@ describe('mb/utils', () => {
const currentStyle = getMockStyle(currentLayerList);
const mockMbMap = new MockMbMap(currentStyle);
- removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList);
+ removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer);
const removedStyle = mockMbMap.getStyle();
const nextStyle = getMockStyle(nextLayerList);
@@ -151,7 +153,7 @@ describe('mb/utils', () => {
const currentStyle = getMockStyle(currentLayerList);
const mockMbMap = new MockMbMap(currentStyle);
- removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList);
+ removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer);
const removedStyle = mockMbMap.getStyle();
const nextStyle = getMockStyle(nextLayerList);
@@ -169,13 +171,23 @@ describe('mb/utils', () => {
const currentStyle = getMockStyle(currentLayerList);
const mockMbMap = new MockMbMap(currentStyle);
- removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList);
+ removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer);
const removedStyle = mockMbMap.getStyle();
const nextStyle = getMockStyle(nextLayerList);
expect(removedStyle).toEqual(nextStyle);
});
+ test('should not remove spatial filter layer and sources when spatialFilterLayer is provided', async () => {
+ const styleWithSpatialFilters = getMockStyle([spatialFilterLayer]);
+ const mockMbMap = new MockMbMap(styleWithSpatialFilters);
+
+ removeOrphanedSourcesAndLayers(mockMbMap, [], spatialFilterLayer);
+ expect(mockMbMap.getStyle()).toEqual(styleWithSpatialFilters);
+ });
+});
+
+describe('syncLayerOrderForSingleLayer', () => {
test('should move bar layer in front of foo layer', async () => {
const fooLayer = makeSingleSourceMockLayer('foo');
const barLayer = makeSingleSourceMockLayer('bar');
@@ -250,40 +262,4 @@ describe('mb/utils', () => {
const nextStyle = getMockStyle(nextLayerListOrder);
expect(orderedStyle).toEqual(nextStyle);
});
-
- test('should reorder foo and bar and remove baz', async () => {
- const bazLayer = makeSingleSourceMockLayer('baz');
- const fooLayer = makeSingleSourceMockLayer('foo');
- const barLayer = makeSingleSourceMockLayer('bar');
-
- const currentLayerOrder = [bazLayer, fooLayer, barLayer];
- const nextLayerListOrder = [barLayer, fooLayer];
-
- const currentStyle = getMockStyle(currentLayerOrder);
- const mockMbMap = new MockMbMap(currentStyle);
- removeOrphanedSourcesAndLayers(mockMbMap, nextLayerListOrder);
- syncLayerOrderForSingleLayer(mockMbMap, nextLayerListOrder);
- const orderedStyle = mockMbMap.getStyle();
-
- const nextStyle = getMockStyle(nextLayerListOrder);
- expect(orderedStyle).toEqual(nextStyle);
- });
-
- test('should reorder foo and bar and remove baz, when having multi-source multi-layer data', async () => {
- const bazLayer = makeMultiSourceMockLayer('baz');
- const fooLayer = makeSingleSourceMockLayer('foo');
- const barLayer = makeMultiSourceMockLayer('bar');
-
- const currentLayerOrder = [bazLayer, fooLayer, barLayer];
- const nextLayerListOrder = [barLayer, fooLayer];
-
- const currentStyle = getMockStyle(currentLayerOrder);
- const mockMbMap = new MockMbMap(currentStyle);
- removeOrphanedSourcesAndLayers(mockMbMap, nextLayerListOrder);
- syncLayerOrderForSingleLayer(mockMbMap, nextLayerListOrder);
- const orderedStyle = mockMbMap.getStyle();
-
- const nextStyle = getMockStyle(nextLayerListOrder);
- expect(orderedStyle).toEqual(nextStyle);
- });
});
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/plugins/maps/public/connected_components/map/mb/utils.js
index 7be2cd9e67084..adf109a087d27 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/utils.js
@@ -7,11 +7,16 @@
import _ from 'lodash';
import { RGBAImage } from './image_utils';
-export function removeOrphanedSourcesAndLayers(mbMap, layerList) {
+export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLayer) {
const mbStyle = mbMap.getStyle();
const mbLayerIdsToRemove = [];
mbStyle.layers.forEach(mbLayer => {
+ // ignore mapbox layers from spatial filter layer
+ if (spatialFilterLayer.ownsMbLayerId(mbLayer.id)) {
+ return;
+ }
+
const layer = layerList.find(layer => {
return layer.ownsMbLayerId(mbLayer.id);
});
@@ -24,6 +29,11 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList) {
const mbSourcesToRemove = [];
for (const mbSourceId in mbStyle.sources) {
if (mbStyle.sources.hasOwnProperty(mbSourceId)) {
+ // ignore mapbox sources from spatial filter layer
+ if (spatialFilterLayer.ownsMbSourceId(mbSourceId)) {
+ return;
+ }
+
const layer = layerList.find(layer => {
return layer.ownsMbSourceId(mbSourceId);
});
@@ -35,6 +45,21 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList) {
mbSourcesToRemove.forEach(mbSourceId => mbMap.removeSource(mbSourceId));
}
+export function moveLayerToTop(mbMap, layer) {
+ const mbStyle = mbMap.getStyle();
+
+ if (!mbStyle.layers || mbStyle.layers.length === 0) {
+ return;
+ }
+
+ layer.getMbLayerIds().forEach(mbLayerId => {
+ const mbLayer = mbMap.getLayer(mbLayerId);
+ if (mbLayer) {
+ mbMap.moveLayer(mbLayerId);
+ }
+ });
+}
+
/**
* This is function assumes only a single layer moved in the layerList, compared to mbMap
* It is optimized to minimize the amount of mbMap.moveLayer calls.
@@ -47,9 +72,12 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) {
}
const mbLayers = mbMap.getStyle().layers.slice();
- const layerIds = mbLayers.map(mbLayer => {
+ const layerIds = [];
+ mbLayers.forEach(mbLayer => {
const layer = layerList.find(layer => layer.ownsMbLayerId(mbLayer.id));
- return layer.getId();
+ if (layer) {
+ layerIds.push(layer.getId());
+ }
});
const currentLayerOrderLayerIds = _.uniq(layerIds);
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js
index 71c1af44e493b..6bb5a4fed6e52 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js
@@ -11,6 +11,7 @@ import {
syncLayerOrderForSingleLayer,
removeOrphanedSourcesAndLayers,
addSpritesheetToMap,
+ moveLayerToTop,
} from './utils';
import { getGlyphUrl, isRetina } from '../../../meta';
import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants';
@@ -74,7 +75,7 @@ export class MBMapContainer extends React.Component {
}
_debouncedSync = _.debounce(() => {
- if (this._isMounted || !this.props.isMapReady) {
+ if (this._isMounted && this.props.isMapReady) {
if (!this.state.hasSyncedLayerList) {
this.setState(
{
@@ -86,6 +87,7 @@ export class MBMapContainer extends React.Component {
}
);
}
+ this.props.spatialFiltersLayer.syncLayerWithMB(this.state.mbMap);
this._syncSettings();
}
}, 256);
@@ -260,9 +262,14 @@ export class MBMapContainer extends React.Component {
};
_syncMbMapWithLayerList = () => {
- removeOrphanedSourcesAndLayers(this.state.mbMap, this.props.layerList);
+ removeOrphanedSourcesAndLayers(
+ this.state.mbMap,
+ this.props.layerList,
+ this.props.spatialFiltersLayer
+ );
this.props.layerList.forEach(layer => layer.syncLayerWithMB(this.state.mbMap));
syncLayerOrderForSingleLayer(this.state.mbMap, this.props.layerList);
+ moveLayerToTop(this.state.mbMap, this.props.spatialFiltersLayer);
};
_syncMbMapWithInspector = () => {
diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx
index 36ed29e92cf69..a89f4461fff06 100644
--- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx
+++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx
@@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { MapSettings } from '../../reducers/map';
import { NavigationPanel } from './navigation_panel';
+import { SpatialFiltersPanel } from './spatial_filters_panel';
interface Props {
cancelChanges: () => void;
@@ -60,6 +61,8 @@ export function MapSettingsPanel({
diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/spatial_filters_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/spatial_filters_panel.tsx
new file mode 100644
index 0000000000000..cae703e982966
--- /dev/null
+++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/spatial_filters_panel.tsx
@@ -0,0 +1,98 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiFormRow, EuiPanel, EuiSpacer, EuiSwitch, EuiSwitchEvent, EuiTitle } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { MapSettings } from '../../reducers/map';
+import { AlphaSlider } from '../../components/alpha_slider';
+import { MbValidatedColorPicker } from '../../layers/styles/vector/components/color/mb_validated_color_picker';
+
+interface Props {
+ settings: MapSettings;
+ updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => void;
+}
+
+export function SpatialFiltersPanel({ settings, updateMapSetting }: Props) {
+ const onAlphaChange = (alpha: number) => {
+ updateMapSetting('spatialFiltersAlpa', alpha);
+ };
+
+ const onFillColorChange = (color: string) => {
+ updateMapSetting('spatialFiltersFillColor', color);
+ };
+
+ const onLineColorChange = (color: string) => {
+ updateMapSetting('spatialFiltersLineColor', color);
+ };
+
+ const onShowSpatialFiltersChange = (event: EuiSwitchEvent) => {
+ updateMapSetting('showSpatialFilters', event.target.checked);
+ };
+
+ const renderStyleInputs = () => {
+ if (!settings.showSpatialFilters) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {renderStyleInputs()}
+
+ );
+}
diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js
index 417c5d84f8916..888fce7e7afe0 100644
--- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js
+++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js
@@ -18,6 +18,7 @@ import {
} from '../common/constants';
import { getEsSpatialRelationLabel } from '../common/i18n_getters';
import { SPATIAL_FILTER_TYPE } from './kibana_services';
+import turfCircle from '@turf/circle';
function ensureGeoField(type) {
const expectedTypes = [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE];
@@ -330,7 +331,7 @@ export function createDistanceFilterWithMeta({
values: {
distanceKm,
geoFieldName,
- pointLabel: point.join(','),
+ pointLabel: point.join(', '),
},
}),
};
@@ -451,3 +452,40 @@ export function clamp(val, min, max) {
return val;
}
}
+
+export function extractFeaturesFromFilters(filters) {
+ const features = [];
+ filters
+ .filter(filter => {
+ return filter.meta.key && filter.meta.type === SPATIAL_FILTER_TYPE;
+ })
+ .forEach(filter => {
+ let geometry;
+ if (filter.geo_distance && filter.geo_distance[filter.meta.key]) {
+ const distanceSplit = filter.geo_distance.distance.split('km');
+ const distance = parseFloat(distanceSplit[0]);
+ const circleFeature = turfCircle(filter.geo_distance[filter.meta.key], distance);
+ geometry = circleFeature.geometry;
+ } else if (
+ filter.geo_shape &&
+ filter.geo_shape[filter.meta.key] &&
+ filter.geo_shape[filter.meta.key].shape
+ ) {
+ geometry = filter.geo_shape[filter.meta.key].shape;
+ } else {
+ // do not know how to convert spatial filter to geometry
+ // this includes pre-indexed shapes
+ return;
+ }
+
+ features.push({
+ type: 'Feature',
+ geometry,
+ properties: {
+ filter: filter.meta.alias,
+ },
+ });
+ });
+
+ return features;
+}
diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js
index fc02e19173843..d13291a8e2ba5 100644
--- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js
+++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js
@@ -19,6 +19,7 @@ import {
createExtentFilter,
convertMapExtentToPolygon,
roundCoordinates,
+ extractFeaturesFromFilters,
} from './elasticsearch_geo_utils';
import { indexPatterns } from '../../../../src/plugins/data/public';
@@ -503,3 +504,131 @@ describe('roundCoordinates', () => {
]);
});
});
+
+describe('extractFeaturesFromFilters', () => {
+ it('should ignore non-spatial filers', () => {
+ const phraseFilter = {
+ meta: {
+ alias: null,
+ disabled: false,
+ index: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ key: 'machine.os',
+ negate: false,
+ params: {
+ query: 'ios',
+ },
+ type: 'phrase',
+ },
+ query: {
+ match_phrase: {
+ 'machine.os': 'ios',
+ },
+ },
+ };
+ expect(extractFeaturesFromFilters([phraseFilter])).toEqual([]);
+ });
+
+ it('should convert geo_distance filter to feature', () => {
+ const spatialFilter = {
+ geo_distance: {
+ distance: '1096km',
+ 'geo.coordinates': [-89.87125, 53.49454],
+ },
+ meta: {
+ alias: 'geo.coordinates within 1096km of -89.87125,53.49454',
+ disabled: false,
+ index: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ key: 'geo.coordinates',
+ negate: false,
+ type: 'spatial_filter',
+ value: '',
+ },
+ };
+
+ const features = extractFeaturesFromFilters([spatialFilter]);
+ expect(features[0].geometry.coordinates[0][0]).toEqual([-89.87125, 63.35109118642093]);
+ expect(features[0].properties).toEqual({
+ filter: 'geo.coordinates within 1096km of -89.87125,53.49454',
+ });
+ });
+
+ it('should convert geo_shape filter to feature', () => {
+ const spatialFilter = {
+ geo_shape: {
+ 'geo.coordinates': {
+ relation: 'INTERSECTS',
+ shape: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ },
+ ignore_unmapped: true,
+ },
+ meta: {
+ alias: 'geo.coordinates in bounds',
+ disabled: false,
+ index: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ key: 'geo.coordinates',
+ negate: false,
+ type: 'spatial_filter',
+ value: '',
+ },
+ };
+
+ expect(extractFeaturesFromFilters([spatialFilter])).toEqual([
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ },
+ properties: {
+ filter: 'geo.coordinates in bounds',
+ },
+ },
+ ]);
+ });
+
+ it('should ignore geo_shape filter with pre-index shape', () => {
+ const spatialFilter = {
+ geo_shape: {
+ 'geo.coordinates': {
+ indexed_shape: {
+ id: 's5gldXEBkTB2HMwpC8y0',
+ index: 'world_countries_v1',
+ path: 'coordinates',
+ },
+ relation: 'INTERSECTS',
+ },
+ ignore_unmapped: true,
+ },
+ meta: {
+ alias: 'geo.coordinates in multipolygon',
+ disabled: false,
+ index: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ key: 'geo.coordinates',
+ negate: false,
+ type: 'spatial_filter',
+ value: '',
+ },
+ };
+
+ expect(extractFeaturesFromFilters([spatialFilter])).toEqual([]);
+ });
+});
diff --git a/x-pack/plugins/maps/public/reducers/default_map_settings.ts b/x-pack/plugins/maps/public/reducers/default_map_settings.ts
index 81622ea9581b0..fe21b37434edd 100644
--- a/x-pack/plugins/maps/public/reducers/default_map_settings.ts
+++ b/x-pack/plugins/maps/public/reducers/default_map_settings.ts
@@ -11,5 +11,9 @@ export function getDefaultMapSettings(): MapSettings {
return {
maxZoom: MAX_ZOOM,
minZoom: MIN_ZOOM,
+ showSpatialFilters: true,
+ spatialFiltersAlpa: 0.3,
+ spatialFiltersFillColor: '#DA8B45',
+ spatialFiltersLineColor: '#DA8B45',
};
}
diff --git a/x-pack/plugins/maps/public/reducers/map.d.ts b/x-pack/plugins/maps/public/reducers/map.d.ts
index af2d96eb75562..be0700d4bdd6d 100644
--- a/x-pack/plugins/maps/public/reducers/map.d.ts
+++ b/x-pack/plugins/maps/public/reducers/map.d.ts
@@ -42,6 +42,10 @@ export type MapContext = {
export type MapSettings = {
maxZoom: number;
minZoom: number;
+ showSpatialFilters: boolean;
+ spatialFiltersAlpa: number;
+ spatialFiltersFillColor: string;
+ spatialFiltersLineColor: string;
};
export type MapState = {
diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.d.ts b/x-pack/plugins/maps/public/selectors/map_selectors.d.ts
index fed344b7744fe..4d0f652af982a 100644
--- a/x-pack/plugins/maps/public/selectors/map_selectors.d.ts
+++ b/x-pack/plugins/maps/public/selectors/map_selectors.d.ts
@@ -8,6 +8,7 @@ import { AnyAction } from 'redux';
import { MapCenter } from '../../common/descriptor_types';
import { MapStoreState } from '../reducers/store';
import { MapSettings } from '../reducers/map';
+import { IVectorLayer } from '../layers/vector_layer';
export function getHiddenLayerIds(state: MapStoreState): string[];
@@ -20,3 +21,5 @@ export function getQueryableUniqueIndexPatternIds(state: MapStoreState): string[
export function getMapSettings(state: MapStoreState): MapSettings;
export function hasMapSettingsChanges(state: MapStoreState): boolean;
+
+export function getSpatialFiltersLayer(state: MapStoreState): IVectorLayer;
diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.js b/x-pack/plugins/maps/public/selectors/map_selectors.js
index 61703cb91bdbb..f1b5371472970 100644
--- a/x-pack/plugins/maps/public/selectors/map_selectors.js
+++ b/x-pack/plugins/maps/public/selectors/map_selectors.js
@@ -17,6 +17,15 @@ import { getInspectorAdapters } from '../reducers/non_serializable_instances';
import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../reducers/util';
import { InnerJoin } from '../layers/joins/inner_join';
import { getSourceByType } from '../layers/sources/source_registry';
+import { GeojsonFileSource } from '../layers/sources/client_file_source';
+import {
+ LAYER_TYPE,
+ SOURCE_DATA_ID_ORIGIN,
+ STYLE_TYPE,
+ VECTOR_STYLES,
+ SPATIAL_FILTERS_LAYER_ID,
+} from '../../common/constants';
+import { extractFeaturesFromFilters } from '../elasticsearch_geo_utils';
function createLayerInstance(layerDescriptor, inspectorAdapters) {
const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters);
@@ -185,6 +194,53 @@ export const getDataFilters = createSelector(
}
);
+export const getSpatialFiltersLayer = createSelector(
+ getFilters,
+ getMapSettings,
+ (filters, settings) => {
+ const featureCollection = {
+ type: 'FeatureCollection',
+ features: extractFeaturesFromFilters(filters),
+ };
+ const geoJsonSourceDescriptor = GeojsonFileSource.createDescriptor(
+ featureCollection,
+ 'spatialFilters'
+ );
+
+ return new VectorLayer({
+ layerDescriptor: {
+ id: SPATIAL_FILTERS_LAYER_ID,
+ visible: settings.showSpatialFilters,
+ alpha: settings.spatialFiltersAlpa,
+ type: LAYER_TYPE.VECTOR,
+ __dataRequests: [
+ {
+ dataId: SOURCE_DATA_ID_ORIGIN,
+ data: featureCollection,
+ },
+ ],
+ style: {
+ properties: {
+ [VECTOR_STYLES.FILL_COLOR]: {
+ type: STYLE_TYPE.STATIC,
+ options: {
+ color: settings.spatialFiltersFillColor,
+ },
+ },
+ [VECTOR_STYLES.LINE_COLOR]: {
+ type: STYLE_TYPE.STATIC,
+ options: {
+ color: settings.spatialFiltersLineColor,
+ },
+ },
+ },
+ },
+ },
+ source: new GeojsonFileSource(geoJsonSourceDescriptor),
+ });
+ }
+);
+
export const getLayerList = createSelector(
getLayerListRaw,
getInspectorAdapters,