[Spacetime] [Maps] Localized basemaps (#130930)

This commit is contained in:
Nick Peihl 2022-05-19 10:55:09 -04:00 committed by GitHub
parent 3effa893da
commit 0c2d06dd81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 156 additions and 10 deletions

View file

@ -108,7 +108,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.0",
"@elastic/ems-client": "8.3.2",
"@elastic/eui": "55.1.2",
"@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.0': ['Elastic License 2.0'],
'@elastic/ems-client@8.3.2': ['Elastic License 2.0'],
'@elastic/eui@55.1.2': ['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

@ -298,6 +298,8 @@ export const MAPS_NEW_VECTOR_LAYER_META_CREATED_BY = 'maps-new-vector-layer';
export const MAX_DRAWING_SIZE_BYTES = 10485760; // 10MB
export const NO_EMS_LOCALE = 'none';
export const AUTOSELECT_EMS_LOCALE = 'autoselect';
export const emsWorldLayerId = 'world_countries';
export enum WIZARD_ID {

View file

@ -61,6 +61,7 @@ export type LayerDescriptor = {
attribution?: Attribution;
id: string;
label?: string | null;
locale?: string | null;
areLabelsOnTop?: boolean;
minZoom?: number;
maxZoom?: number;

View file

@ -472,6 +472,15 @@ export function updateLayerLabel(id: string, newLabel: string) {
};
}
export function updateLayerLocale(id: string, locale: string) {
return {
type: UPDATE_LAYER_PROP,
id,
propName: 'locale',
newValue: locale,
};
}
export function setLayerAttribution(id: string, attribution: Attribution) {
return {
type: UPDATE_LAYER_PROP,

View file

@ -80,10 +80,11 @@ describe('EMS is enabled', () => {
id: '12345',
includeInFitToBounds: true,
label: null,
locale: 'autoselect',
maxZoom: 24,
minZoom: 0,
source: undefined,
sourceDescriptor: {
id: undefined,
isAutoSelect: true,
lightModeDefault: 'road_map_desaturated',
type: 'EMS_TMS',

View file

@ -14,6 +14,7 @@ import { KibanaTilemapSource } from '../sources/kibana_tilemap_source';
import { RasterTileLayer } from './raster_tile_layer/raster_tile_layer';
import { EmsVectorTileLayer } from './ems_vector_tile_layer/ems_vector_tile_layer';
import { EMSTMSSource } from '../sources/ems_tms_source';
import { AUTOSELECT_EMS_LOCALE } from '../../../common/constants';
export function createBasemapLayerDescriptor(): LayerDescriptor | null {
const tilemapSourceFromKibana = getKibanaTileMap();
@ -27,6 +28,7 @@ export function createBasemapLayerDescriptor(): LayerDescriptor | null {
const isEmsEnabled = getEMSSettings()!.isEMSEnabled();
if (isEmsEnabled) {
const layerDescriptor = EmsVectorTileLayer.createDescriptor({
locale: AUTOSELECT_EMS_LOCALE,
sourceDescriptor: EMSTMSSource.createDescriptor({ isAutoSelect: true }),
});
return layerDescriptor;

View file

@ -55,6 +55,26 @@ describe('EmsVectorTileLayer', () => {
expect(actualErrorMessage).toStrictEqual('network error');
});
describe('getLocale', () => {
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,
});
expect(layer.getLocale()).toBe('none');
});
test('should set locale for new layers', () => {
const layer = new EmsVectorTileLayer({
source: {} as unknown as EMSTMSSource,
layerDescriptor: {
locale: 'xx',
} as unknown as LayerDescriptor,
});
expect(layer.getLocale()).toBe('xx');
});
});
describe('isInitialDataLoadComplete', () => {
test('should return false when tile loading has not started', () => {
const layer = new EmsVectorTileLayer({

View file

@ -6,11 +6,19 @@
*/
import type { Map as MbMap, LayerSpecification, StyleSpecification } from '@kbn/mapbox-gl';
import { TMSService } from '@elastic/ems-client';
import { i18n } from '@kbn/i18n';
import _ from 'lodash';
// @ts-expect-error
import { RGBAImage } from './image_utils';
import { AbstractLayer } from '../layer';
import { SOURCE_DATA_REQUEST_ID, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../../../common/constants';
import {
AUTOSELECT_EMS_LOCALE,
NO_EMS_LOCALE,
SOURCE_DATA_REQUEST_ID,
LAYER_TYPE,
LAYER_STYLE_TYPE,
} from '../../../../common/constants';
import { LayerDescriptor } from '../../../../common/descriptor_types';
import { DataRequest } from '../../util/data_request';
import { isRetina } from '../../../util';
@ -50,6 +58,7 @@ export class EmsVectorTileLayer extends AbstractLayer {
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;
}
@ -87,6 +96,10 @@ export class EmsVectorTileLayer extends AbstractLayer {
return this._style;
}
getLocale() {
return this._descriptor.locale ?? NO_EMS_LOCALE;
}
_canSkipSync({
prevDataRequest,
nextMeta,
@ -309,7 +322,6 @@ export class EmsVectorTileLayer extends AbstractLayer {
return;
}
this._addSpriteSheetToMapFromImageData(newJson, imageData, mbMap);
// sync layers
const layers = vectorStyle.layers ? vectorStyle.layers : [];
layers.forEach((layer) => {
@ -391,6 +403,27 @@ export class EmsVectorTileLayer extends AbstractLayer {
});
}
_setLanguage(mbMap: MbMap, mbLayer: LayerSpecification, mbLayerId: string) {
const locale = this.getLocale();
if (locale === null || locale === NO_EMS_LOCALE) {
if (mbLayer.type !== 'symbol') return;
const textProperty = mbLayer.layout?.['text-field'];
if (mbLayer.layout && textProperty) {
mbMap.setLayoutProperty(mbLayerId, 'text-field', textProperty);
}
return;
}
const textProperty =
locale === AUTOSELECT_EMS_LOCALE
? TMSService.transformLanguageProperty(mbLayer, i18n.getLocale())
: TMSService.transformLanguageProperty(mbLayer, locale);
if (textProperty !== undefined) {
mbMap.setLayoutProperty(mbLayerId, 'text-field', textProperty);
}
}
_setLayerZoomRange(mbMap: MbMap, mbLayer: LayerSpecification, mbLayerId: string) {
let minZoom = this.getMinZoom();
if (typeof mbLayer.minzoom === 'number') {
@ -414,6 +447,7 @@ export class EmsVectorTileLayer extends AbstractLayer {
this.syncVisibilityWithMb(mbMap, mbLayerId);
this._setLayerZoomRange(mbMap, mbLayer, mbLayerId);
this._setOpacityForType(mbMap, mbLayer, mbLayerId);
this._setLanguage(mbMap, mbLayer, mbLayerId);
});
}
@ -425,6 +459,10 @@ export class EmsVectorTileLayer extends AbstractLayer {
return true;
}
supportsLabelLocales(): boolean {
return true;
}
async getLicensedFeatures() {
return this._source.getLicensedFeatures();
}

View file

@ -53,6 +53,7 @@ export interface ILayer {
supportsFitToBounds(): Promise<boolean>;
getAttributions(): Promise<Attribution[]>;
getLabel(): string;
getLocale(): string | null;
hasLegendDetails(): Promise<boolean>;
renderLegendDetails(): ReactElement<any> | null;
showAtZoomLevel(zoom: number): boolean;
@ -101,6 +102,7 @@ export interface ILayer {
isPreviewLayer: () => boolean;
areLabelsOnTop: () => boolean;
supportsLabelsOnTop: () => boolean;
supportsLabelLocales: () => boolean;
isFittable(): Promise<boolean>;
isIncludeInFitToBounds(): boolean;
getLicensedFeatures(): Promise<LICENSED_FEATURES[]>;
@ -250,6 +252,10 @@ export class AbstractLayer implements ILayer {
return this._descriptor.label ? this._descriptor.label : '';
}
getLocale(): string | null {
return null;
}
getLayerIcon(isTocIcon: boolean): LayerIcon {
return {
icon: <EuiIcon size="m" type={this.getLayerTypeIconName()} />,
@ -461,6 +467,10 @@ export class AbstractLayer implements ILayer {
return false;
}
supportsLabelLocales(): boolean {
return false;
}
async getLicensedFeatures(): Promise<LICENSED_FEATURES[]> {
return [];
}

View file

@ -12,6 +12,7 @@ import {
clearLayerAttribution,
setLayerAttribution,
updateLayerLabel,
updateLayerLocale,
updateLayerMaxZoom,
updateLayerMinZoom,
updateLayerAlpha,
@ -26,6 +27,7 @@ function mapDispatchToProps(dispatch: Dispatch<AnyAction>) {
setLayerAttribution: (id: string, attribution: Attribution) =>
dispatch(setLayerAttribution(id, attribution)),
updateLabel: (id: string, label: string) => dispatch(updateLayerLabel(id, label)),
updateLocale: (id: string, locale: string) => dispatch(updateLayerLocale(id, locale)),
updateMinZoom: (id: string, minZoom: number) => dispatch(updateLayerMinZoom(id, minZoom)),
updateMaxZoom: (id: string, maxZoom: number) => dispatch(updateLayerMaxZoom(id, maxZoom)),
updateAlpha: (id: string, alpha: number) => dispatch(updateLayerAlpha(id, alpha)),

View file

@ -11,6 +11,7 @@ import {
EuiPanel,
EuiFormRow,
EuiFieldText,
EuiSelect,
EuiSpacer,
EuiSwitch,
EuiSwitchEvent,
@ -20,7 +21,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ValidatedDualRange } from '@kbn/kibana-react-plugin/public';
import { Attribution } from '../../../../common/descriptor_types';
import { MAX_ZOOM } from '../../../../common/constants';
import { AUTOSELECT_EMS_LOCALE, NO_EMS_LOCALE, MAX_ZOOM } from '../../../../common/constants';
import { AlphaSlider } from '../../../components/alpha_slider';
import { ILayer } from '../../../classes/layers/layer';
import { AttributionFormRow } from './attribution_form_row';
@ -30,6 +31,7 @@ export interface Props {
clearLayerAttribution: (layerId: string) => void;
setLayerAttribution: (id: string, attribution: Attribution) => void;
updateLabel: (layerId: string, label: string) => void;
updateLocale: (layerId: string, locale: string) => void;
updateMinZoom: (layerId: string, minZoom: number) => void;
updateMaxZoom: (layerId: string, maxZoom: number) => void;
updateAlpha: (layerId: string, alpha: number) => void;
@ -48,6 +50,11 @@ export function LayerSettings(props: Props) {
props.updateLabel(layerId, label);
};
const onLocaleChange = (event: ChangeEvent<HTMLSelectElement>) => {
const { value } = event.target;
if (value) props.updateLocale(layerId, value);
};
const onZoomChange = (value: [string, string]) => {
props.updateMinZoom(layerId, Math.max(minVisibilityZoom, parseInt(value[0], 10)));
props.updateMaxZoom(layerId, Math.min(maxVisibilityZoom, parseInt(value[1], 10)));
@ -155,6 +162,58 @@ export function LayerSettings(props: Props) {
);
};
const renderShowLocaleSelector = () => {
if (!props.layer.supportsLabelLocales()) {
return null;
}
const options = [
{
text: i18n.translate(
'xpack.maps.layerPanel.settingsPanel.labelLanguageAutoselectDropDown',
{
defaultMessage: 'Autoselect based on Kibana locale',
}
),
value: AUTOSELECT_EMS_LOCALE,
},
{ value: 'ar', text: 'العربية' },
{ value: 'de', text: 'Deutsch' },
{ value: 'en', text: 'English' },
{ value: 'es', text: 'Español' },
{ value: 'fr-fr', text: 'Français' },
{ value: 'hi-in', text: 'हिन्दी' },
{ value: 'it', text: 'Italiano' },
{ value: 'ja-jp', text: '日本語' },
{ value: 'ko', text: '한국어' },
{ value: 'pt-pt', text: 'Português' },
{ value: 'ru-ru', text: 'русский' },
{ value: 'zh-cn', text: '简体中文' },
{
text: i18n.translate('xpack.maps.layerPanel.settingsPanel.labelLanguageNoneDropDown', {
defaultMessage: 'None',
}),
value: NO_EMS_LOCALE,
},
];
return (
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.maps.layerPanel.settingsPanel.labelLanguageLabel', {
defaultMessage: 'Label language',
})}
>
<EuiSelect
options={options}
value={props.layer.getLocale() ?? NO_EMS_LOCALE}
onChange={onLocaleChange}
compressed
/>
</EuiFormRow>
);
};
return (
<Fragment>
<EuiPanel>
@ -172,6 +231,7 @@ export function LayerSettings(props: Props) {
{renderZoomSliders()}
<AlphaSlider alpha={props.layer.getAlpha()} onChange={onAlphaChange} />
{renderShowLabelsOnTop()}
{renderShowLocaleSelector()}
<AttributionFormRow layer={props.layer} onChange={onAttributionChange} />
{renderIncludeInFitToBounds()}
</EuiPanel>

View file

@ -1483,15 +1483,16 @@
"@elastic/transport" "^8.0.2"
tslib "^2.3.0"
"@elastic/ems-client@8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.3.0.tgz#9d40c02e33c407d433b8e509d83c5edec24c4902"
integrity sha512-DlJDyUQzNrxGbS0AWxGiBNfq1hPQUP3Ib/Zyotgv7+VGGklb0mBwppde7WLVvuj0E+CYc6E63TJsoD8KNUO0MQ==
"@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==
dependencies:
"@types/geojson" "^7946.0.7"
"@types/lru-cache" "^5.1.0"
"@types/topojson-client" "^3.0.0"
"@types/topojson-specification" "^1.0.1"
chroma-js "^2.1.0"
lodash "^4.17.15"
lru-cache "^6.0.0"
semver "^7.3.2"