[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:
Nick Peihl 2022-06-07 14:40:30 -04:00 committed by GitHub
parent 2dd0ea1b76
commit c11f96132c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 211 additions and 30 deletions

View file

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

View file

@ -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 ODCBy license https://github.com/mattcg/language-subtag-registry
};

View file

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

View file

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

View file

@ -256,6 +256,10 @@ export type HeatmapStyleDescriptor = StyleDescriptor & {
colorRampName: string;
};
export type EMSVectorTileStyleDescriptor = StyleDescriptor & {
color: string;
};
export type StylePropertyOptions =
| LabelBorderSizeOptions
| SymbolizeAsOptions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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
* 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} />
);
}
}

View file

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

View file

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