mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Spacetime] [Maps] Customizable colors in basemaps (#131576)
* Customizable colors in basemaps * Add operations to color selector * Only use default color operations per style Allowing the user to choose the operation is probably unnecessary at this step. The default operations work very well. * Fix types * Clean up cruft * Update ems-client * Fix license override * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Fix test * Unneccesary type assertion * Refactor color filters to use new EMSVectorTileStyle * lint * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Review feedback Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2dd0ea1b76
commit
c11f96132c
14 changed files with 211 additions and 30 deletions
|
@ -109,7 +109,7 @@
|
|||
"@elastic/charts": "46.0.1",
|
||||
"@elastic/datemath": "5.0.3",
|
||||
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.2.0-canary.2",
|
||||
"@elastic/ems-client": "8.3.2",
|
||||
"@elastic/ems-client": "8.3.3",
|
||||
"@elastic/eui": "55.1.4",
|
||||
"@elastic/filesaver": "1.1.2",
|
||||
"@elastic/node-crypto": "1.2.1",
|
||||
|
|
|
@ -76,7 +76,7 @@ export const DEV_ONLY_LICENSE_ALLOWED = ['MPL-2.0'];
|
|||
export const LICENSE_OVERRIDES = {
|
||||
'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts
|
||||
'@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint
|
||||
'@elastic/ems-client@8.3.2': ['Elastic License 2.0'],
|
||||
'@elastic/ems-client@8.3.3': ['Elastic License 2.0'],
|
||||
'@elastic/eui@55.1.4': ['SSPL-1.0 OR Elastic License 2.0'],
|
||||
'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry
|
||||
};
|
||||
|
|
|
@ -195,6 +195,7 @@ export enum LAYER_STYLE_TYPE {
|
|||
VECTOR = 'VECTOR',
|
||||
HEATMAP = 'HEATMAP',
|
||||
TILE = 'TILE',
|
||||
EMS_VECTOR_TILE = 'EMS_VECTOR_TILE',
|
||||
}
|
||||
|
||||
export enum COLOR_MAP_TYPE {
|
||||
|
@ -288,6 +289,7 @@ export enum DATA_MAPPING_FUNCTION {
|
|||
INTERPOLATE = 'INTERPOLATE',
|
||||
PERCENTILES = 'PERCENTILES',
|
||||
}
|
||||
|
||||
export const DEFAULT_PERCENTILES = [50, 75, 90, 95, 99];
|
||||
|
||||
export type RawValue = string | string[] | number | boolean | undefined | null;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import { Query } from '@kbn/data-plugin/public';
|
||||
import { Feature } from 'geojson';
|
||||
import {
|
||||
EMSVectorTileStyleDescriptor,
|
||||
HeatmapStyleDescriptor,
|
||||
StyleDescriptor,
|
||||
VectorStyleDescriptor,
|
||||
|
@ -83,3 +84,8 @@ export type HeatmapLayerDescriptor = LayerDescriptor & {
|
|||
type: LAYER_TYPE.HEATMAP;
|
||||
style: HeatmapStyleDescriptor;
|
||||
};
|
||||
|
||||
export type EMSVectorTileLayerDescriptor = LayerDescriptor & {
|
||||
type: LAYER_TYPE.EMS_VECTOR_TILE;
|
||||
style: EMSVectorTileStyleDescriptor;
|
||||
};
|
||||
|
|
|
@ -256,6 +256,10 @@ export type HeatmapStyleDescriptor = StyleDescriptor & {
|
|||
colorRampName: string;
|
||||
};
|
||||
|
||||
export type EMSVectorTileStyleDescriptor = StyleDescriptor & {
|
||||
color: string;
|
||||
};
|
||||
|
||||
export type StylePropertyOptions =
|
||||
| LabelBorderSizeOptions
|
||||
| SymbolizeAsOptions
|
||||
|
|
|
@ -89,7 +89,7 @@ describe('EMS is enabled', () => {
|
|||
lightModeDefault: 'road_map_desaturated',
|
||||
type: 'EMS_TMS',
|
||||
},
|
||||
style: { type: 'TILE' },
|
||||
style: { type: 'EMS_VECTOR_TILE', color: '' },
|
||||
type: 'EMS_VECTOR_TILE',
|
||||
visible: true,
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { SOURCE_TYPES } from '../../../../common/constants';
|
||||
import {
|
||||
DataFilters,
|
||||
LayerDescriptor,
|
||||
EMSVectorTileLayerDescriptor,
|
||||
XYZTMSSourceDescriptor,
|
||||
} from '../../../../common/descriptor_types';
|
||||
import { ILayer } from '../layer';
|
||||
|
@ -34,7 +34,7 @@ describe('EmsVectorTileLayer', () => {
|
|||
urlTemplate: 'https://example.com/{x}/{y}/{z}.png',
|
||||
id: 'mockSourceId',
|
||||
} as XYZTMSSourceDescriptor,
|
||||
},
|
||||
} as unknown as EMSVectorTileLayerDescriptor,
|
||||
});
|
||||
|
||||
let actualMeta;
|
||||
|
@ -59,7 +59,7 @@ describe('EmsVectorTileLayer', () => {
|
|||
test('should set locale to none for existing layers where locale is not defined', () => {
|
||||
const layer = new EmsVectorTileLayer({
|
||||
source: {} as unknown as EMSTMSSource,
|
||||
layerDescriptor: {} as unknown as LayerDescriptor,
|
||||
layerDescriptor: {} as unknown as EMSVectorTileLayerDescriptor,
|
||||
});
|
||||
expect(layer.getLocale()).toBe('none');
|
||||
});
|
||||
|
@ -69,7 +69,7 @@ describe('EmsVectorTileLayer', () => {
|
|||
source: {} as unknown as EMSTMSSource,
|
||||
layerDescriptor: {
|
||||
locale: 'xx',
|
||||
} as unknown as LayerDescriptor,
|
||||
} as unknown as EMSVectorTileLayerDescriptor,
|
||||
});
|
||||
expect(layer.getLocale()).toBe('xx');
|
||||
});
|
||||
|
@ -79,7 +79,7 @@ describe('EmsVectorTileLayer', () => {
|
|||
test('should return false when tile loading has not started', () => {
|
||||
const layer = new EmsVectorTileLayer({
|
||||
source: {} as unknown as EMSTMSSource,
|
||||
layerDescriptor: {} as unknown as LayerDescriptor,
|
||||
layerDescriptor: {} as unknown as EMSVectorTileLayerDescriptor,
|
||||
});
|
||||
expect(layer.isInitialDataLoadComplete()).toBe(false);
|
||||
});
|
||||
|
@ -89,7 +89,7 @@ describe('EmsVectorTileLayer', () => {
|
|||
source: {} as unknown as EMSTMSSource,
|
||||
layerDescriptor: {
|
||||
__areTilesLoaded: false,
|
||||
} as unknown as LayerDescriptor,
|
||||
} as unknown as EMSVectorTileLayerDescriptor,
|
||||
});
|
||||
expect(layer.isInitialDataLoadComplete()).toBe(false);
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ describe('EmsVectorTileLayer', () => {
|
|||
source: {} as unknown as EMSTMSSource,
|
||||
layerDescriptor: {
|
||||
__areTilesLoaded: true,
|
||||
} as unknown as LayerDescriptor,
|
||||
} as unknown as EMSVectorTileLayerDescriptor,
|
||||
});
|
||||
expect(layer.isInitialDataLoadComplete()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { Map as MbMap, LayerSpecification, StyleSpecification } from '@kbn/mapbox-gl';
|
||||
import { type EmsSpriteSheet, TMSService } from '@elastic/ems-client';
|
||||
import { type blendMode, type EmsSpriteSheet, TMSService } from '@elastic/ems-client';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import _ from 'lodash';
|
||||
// @ts-expect-error
|
||||
|
@ -17,14 +17,13 @@ import {
|
|||
NO_EMS_LOCALE,
|
||||
SOURCE_DATA_REQUEST_ID,
|
||||
LAYER_TYPE,
|
||||
LAYER_STYLE_TYPE,
|
||||
} from '../../../../common/constants';
|
||||
import { LayerDescriptor } from '../../../../common/descriptor_types';
|
||||
import { EMSVectorTileLayerDescriptor } from '../../../../common/descriptor_types';
|
||||
import { DataRequest } from '../../util/data_request';
|
||||
import { isRetina } from '../../../util';
|
||||
import { DataRequestContext } from '../../../actions';
|
||||
import { EMSTMSSource } from '../../sources/ems_tms_source';
|
||||
import { TileStyle } from '../../styles/tile/tile_style';
|
||||
import { EMSVectorTileStyle } from '../../styles/ems/ems_vector_tile_style';
|
||||
|
||||
interface SourceRequestMeta {
|
||||
tileLayerId: string;
|
||||
|
@ -40,26 +39,35 @@ interface SourceRequestData {
|
|||
}
|
||||
|
||||
export class EmsVectorTileLayer extends AbstractLayer {
|
||||
static createDescriptor(options: Partial<LayerDescriptor>) {
|
||||
const tileLayerDescriptor = super.createDescriptor(options);
|
||||
tileLayerDescriptor.type = LAYER_TYPE.EMS_VECTOR_TILE;
|
||||
tileLayerDescriptor.alpha = _.get(options, 'alpha', 1);
|
||||
tileLayerDescriptor.locale = _.get(options, 'locale', AUTOSELECT_EMS_LOCALE);
|
||||
tileLayerDescriptor.style = { type: LAYER_STYLE_TYPE.TILE };
|
||||
return tileLayerDescriptor;
|
||||
}
|
||||
private readonly _style: EMSVectorTileStyle;
|
||||
|
||||
private readonly _style: TileStyle;
|
||||
static createDescriptor(
|
||||
options: Partial<EMSVectorTileLayerDescriptor>
|
||||
): EMSVectorTileLayerDescriptor {
|
||||
const emsVectorTileLayerDescriptor = super.createDescriptor(
|
||||
options
|
||||
) as EMSVectorTileLayerDescriptor;
|
||||
emsVectorTileLayerDescriptor.type = LAYER_TYPE.EMS_VECTOR_TILE;
|
||||
emsVectorTileLayerDescriptor.alpha = _.get(options, 'alpha', 1);
|
||||
emsVectorTileLayerDescriptor.locale = _.get(options, 'locale', AUTOSELECT_EMS_LOCALE);
|
||||
emsVectorTileLayerDescriptor.style = EMSVectorTileStyle.createDescriptor();
|
||||
return emsVectorTileLayerDescriptor;
|
||||
}
|
||||
|
||||
constructor({
|
||||
source,
|
||||
layerDescriptor,
|
||||
}: {
|
||||
source: EMSTMSSource;
|
||||
layerDescriptor: LayerDescriptor;
|
||||
layerDescriptor: EMSVectorTileLayerDescriptor;
|
||||
}) {
|
||||
super({ source, layerDescriptor });
|
||||
this._style = new TileStyle();
|
||||
if (!layerDescriptor.style) {
|
||||
const defaultStyle = EMSVectorTileStyle.createDescriptor();
|
||||
this._style = new EMSVectorTileStyle(defaultStyle);
|
||||
} else {
|
||||
this._style = new EMSVectorTileStyle(layerDescriptor.style);
|
||||
}
|
||||
}
|
||||
|
||||
isInitialDataLoadComplete(): boolean {
|
||||
|
@ -377,6 +385,26 @@ export class EmsVectorTileLayer extends AbstractLayer {
|
|||
return [];
|
||||
}
|
||||
|
||||
_setColorFilter(mbMap: MbMap, mbLayer: LayerSpecification, mbLayerId: string) {
|
||||
const color = this.getCurrentStyle().getColor();
|
||||
|
||||
const colorOperation = TMSService.colorOperationDefaults.find(({ style }) => {
|
||||
return style === this.getSource().getTileLayerId();
|
||||
});
|
||||
if (!colorOperation) return;
|
||||
const { operation, percentage } = colorOperation;
|
||||
|
||||
const properties = TMSService.transformColorProperties(
|
||||
mbLayer,
|
||||
color,
|
||||
operation as unknown as blendMode,
|
||||
percentage
|
||||
);
|
||||
for (const { property, color: newColor } of properties) {
|
||||
mbMap.setPaintProperty(mbLayerId, property, newColor);
|
||||
}
|
||||
}
|
||||
|
||||
_setOpacityForType(mbMap: MbMap, mbLayer: LayerSpecification, mbLayerId: string) {
|
||||
this._getOpacityProps(mbLayer.type).forEach((opacityProp) => {
|
||||
const mbPaint = mbLayer.paint as { [key: string]: unknown } | undefined;
|
||||
|
@ -433,6 +461,7 @@ export class EmsVectorTileLayer extends AbstractLayer {
|
|||
this.syncVisibilityWithMb(mbMap, mbLayerId);
|
||||
this._setLayerZoomRange(mbMap, mbLayer, mbLayerId);
|
||||
this._setOpacityForType(mbMap, mbLayer, mbLayerId);
|
||||
this._setColorFilter(mbMap, mbLayer, mbLayerId);
|
||||
this._setLanguage(mbMap, mbLayer, mbLayerId);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EMSVectorTileStyleEditor is rendered 1`] = `
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="columnCompressed"
|
||||
fullWidth={false}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Color filter"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiColorPicker
|
||||
aria-label="Color"
|
||||
aria-placeholder="No filter"
|
||||
color="#4A412A"
|
||||
compressed={true}
|
||||
format="hex"
|
||||
isClearable={true}
|
||||
onChange={[Function]}
|
||||
placeholder="No filter"
|
||||
secondaryInputDisplay="top"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
`;
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EMSVectorTileStyleEditor } from './ems_vector_tile_style_editor';
|
||||
|
||||
describe('EMSVectorTileStyleEditor', () => {
|
||||
test('is rendered', () => {
|
||||
const component = shallow(
|
||||
<EMSVectorTileStyleEditor color="#4A412A" onColorChange={() => {}} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { EuiFormRow, EuiColorPicker } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
onColorChange: ({ color }: { color: string }) => void;
|
||||
}
|
||||
|
||||
export function EMSVectorTileStyleEditor({ color, onColorChange }: Props) {
|
||||
const onChange = (selectedColor: string) => {
|
||||
onColorChange({
|
||||
color: selectedColor,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.maps.emsVectorTileStyleEditor.colorFilterPickerLabel', {
|
||||
defaultMessage: 'Color filter',
|
||||
})}
|
||||
>
|
||||
<EuiColorPicker
|
||||
compressed
|
||||
aria-label="Color"
|
||||
color={color}
|
||||
onChange={onChange}
|
||||
secondaryInputDisplay="top"
|
||||
isClearable
|
||||
format="hex"
|
||||
placeholder="No filter"
|
||||
aria-placeholder="No filter"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { IStyle } from '../style';
|
||||
import { EMSVectorTileStyleDescriptor, StyleDescriptor } from '../../../../common/descriptor_types';
|
||||
import { LAYER_STYLE_TYPE } from '../../../../common/constants';
|
||||
import { EMSVectorTileStyleEditor } from './components/ems_vector_tile_style_editor';
|
||||
|
||||
export class EMSVectorTileStyle implements IStyle {
|
||||
readonly _descriptor: EMSVectorTileStyleDescriptor;
|
||||
|
||||
constructor(descriptor: { color: string } = { color: '' }) {
|
||||
this._descriptor = EMSVectorTileStyle.createDescriptor(descriptor.color);
|
||||
}
|
||||
|
||||
static createDescriptor(color?: string) {
|
||||
return {
|
||||
type: LAYER_STYLE_TYPE.EMS_VECTOR_TILE,
|
||||
color: color ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
getType() {
|
||||
return LAYER_STYLE_TYPE.EMS_VECTOR_TILE;
|
||||
}
|
||||
|
||||
getColor() {
|
||||
return this._descriptor.color;
|
||||
}
|
||||
|
||||
renderEditor(onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void) {
|
||||
const onColorChange = ({ color }: { color: string }) => {
|
||||
const styleDescriptor = EMSVectorTileStyle.createDescriptor(color);
|
||||
onStyleDescriptorChange(styleDescriptor);
|
||||
};
|
||||
|
||||
return (
|
||||
<EMSVectorTileStyleEditor color={this._descriptor.color} onColorChange={onColorChange} />
|
||||
);
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ import {
|
|||
DataRequestDescriptor,
|
||||
CustomIcon,
|
||||
DrawState,
|
||||
EMSVectorTileLayerDescriptor,
|
||||
EditState,
|
||||
Goto,
|
||||
HeatmapLayerDescriptor,
|
||||
|
@ -79,7 +80,10 @@ export function createLayerInstance(
|
|||
case LAYER_TYPE.RASTER_TILE:
|
||||
return new RasterTileLayer({ layerDescriptor, source: source as ITMSSource });
|
||||
case LAYER_TYPE.EMS_VECTOR_TILE:
|
||||
return new EmsVectorTileLayer({ layerDescriptor, source: source as EMSTMSSource });
|
||||
return new EmsVectorTileLayer({
|
||||
layerDescriptor: layerDescriptor as EMSVectorTileLayerDescriptor,
|
||||
source: source as EMSTMSSource,
|
||||
});
|
||||
case LAYER_TYPE.HEATMAP:
|
||||
return new HeatmapLayer({
|
||||
layerDescriptor: layerDescriptor as HeatmapLayerDescriptor,
|
||||
|
|
|
@ -1483,10 +1483,10 @@
|
|||
"@elastic/transport" "^8.0.2"
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@elastic/ems-client@8.3.2":
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.3.2.tgz#a12eafcfd9ac8d3068da78a5a77503ea8a89f67c"
|
||||
integrity sha512-81u+Z7+4Y2Fu+sTl9QOKdG3SVeCzzpfyCsHFR8X0V2WFCpQa+SU4sSN9WhdLHz/pe9oi6Gtt5eFMF90TOO/ckg==
|
||||
"@elastic/ems-client@8.3.3":
|
||||
version "8.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.3.3.tgz#16ddd582e1029055a5a77e7bebbc5b57b3246ff7"
|
||||
integrity sha512-vcgPwnAw7QjcR68IddErobZqwJdH0T4h/6U4swUR3XEF+9jojNZnxBkvM3mEV9Z7QLQxDprebJJu04d37lzlUw==
|
||||
dependencies:
|
||||
"@types/geojson" "^7946.0.7"
|
||||
"@types/lru-cache" "^5.1.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue