[Maps] Show spatial filters on map to provide context when for active filters (#63406)

* [Maps] show spatial filters

* pass data into __dataRequests

* extractFeaturesFromFilters

* geo_shape support

* putting it all together

* lower alpha

* update removeOrphanedSourcesAndLayers to avoid removing spatialFiltersLayer

* change array iteration to forEach

* use less precision when distance filter covers larger distances

* fix double import

* add map settings for to configure spatial filters layer

* add map settings alpha slider

* finish rest of map settings

* review feedback

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2020-04-22 15:10:39 -06:00 committed by GitHub
parent 65264aa790
commit f7ea9b99ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 463 additions and 91 deletions

View file

@ -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';

View file

@ -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 (
<EuiFormRow
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.layerTransparencyLabel', {
defaultMessage: 'Opacity',
})}
display="columnCompressed"
>
<ValidatedRange
min={0}
max={100}
step={1}
value={Math.round(alpha * 100)}
onChange={onAlphaChange}
showInput
showRange
compressed
append={i18n.translate('xpack.maps.layerPanel.settingsPanel.percentageLabel', {
defaultMessage: '%',
description: 'Percentage',
})}
/>
</EuiFormRow>
);
}

View file

@ -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 (
<EuiFormRow
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.layerTransparencyLabel', {
defaultMessage: 'Opacity',
})}
display="columnCompressed"
>
<ValidatedRange
min={0}
max={100}
step={1}
value={alphaPercent}
onChange={onAlphaChange}
showInput
showRange
compressed
append={i18n.translate('xpack.maps.layerPanel.settingsPanel.percentageLabel', {
defaultMessage: '%',
description: 'Percentage',
})}
/>
</EuiFormRow>
);
};
return (
<Fragment>
<EuiPanel>
@ -107,7 +78,7 @@ export function LayerSettings(props) {
<EuiSpacer size="m" />
{renderLabel()}
{renderZoomSliders()}
{renderAlphaSlider()}
<AlphaSlider alpha={props.alpha} onChange={onAlphaChange} />
</EuiPanel>
<EuiSpacer size="s" />

View file

@ -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();

View file

@ -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),

View file

@ -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);
});
});

View file

@ -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);

View file

@ -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 = () => {

View file

@ -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({
<div className="mapLayerPanel__body">
<div className="mapLayerPanel__bodyOverflow">
<NavigationPanel settings={settings} updateMapSetting={updateMapSetting} />
<EuiSpacer size="s" />
<SpatialFiltersPanel settings={settings} updateMapSetting={updateMapSetting} />
</div>
</div>

View file

@ -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 (
<>
<AlphaSlider alpha={settings.spatialFiltersAlpa} onChange={onAlphaChange} />
<EuiFormRow
label={i18n.translate('xpack.maps.mapSettingsPanel.spatialFiltersFillColorLabel', {
defaultMessage: 'Fill color',
})}
display="columnCompressed"
>
<MbValidatedColorPicker
color={settings.spatialFiltersFillColor}
onChange={onFillColorChange}
/>
</EuiFormRow>
<EuiFormRow
label={i18n.translate('xpack.maps.mapSettingsPanel.spatialFiltersLineColorLabel', {
defaultMessage: 'Border color',
})}
display="columnCompressed"
>
<MbValidatedColorPicker
color={settings.spatialFiltersLineColor}
onChange={onLineColorChange}
/>
</EuiFormRow>
</>
);
};
return (
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.maps.mapSettingsPanel.spatialFiltersTitle"
defaultMessage="Spatial filters"
/>
</h5>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFormRow>
<EuiSwitch
label={i18n.translate('xpack.maps.mapSettingsPanel.showSpatialFiltersLabel', {
defaultMessage: 'Show spatial filters on map',
})}
checked={settings.showSpatialFilters}
onChange={onShowSpatialFiltersChange}
compressed
/>
</EuiFormRow>
{renderStyleInputs()}
</EuiPanel>
);
}

View file

@ -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;
}

View file

@ -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([]);
});
});

View file

@ -11,5 +11,9 @@ export function getDefaultMapSettings(): MapSettings {
return {
maxZoom: MAX_ZOOM,
minZoom: MIN_ZOOM,
showSpatialFilters: true,
spatialFiltersAlpa: 0.3,
spatialFiltersFillColor: '#DA8B45',
spatialFiltersLineColor: '#DA8B45',
};
}

View file

@ -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 = {

View file

@ -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;

View file

@ -6,27 +6,26 @@
import { createSelector } from 'reselect';
import _ from 'lodash';
import { TileLayer } from '../layers/tile_layer';
import { VectorTileLayer } from '../layers/vector_tile_layer';
import { VectorLayer } from '../layers/vector_layer';
import { HeatmapLayer } from '../layers/heatmap_layer';
import { BlendedVectorLayer } from '../layers/blended_vector_layer';
import { getTimeFilter } from '../kibana_services';
import { getInspectorAdapters } from '../reducers/non_serializable_instances';
import { TiledVectorLayer } from '../layers/tiled_vector_layer';
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);
@ -195,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,