@elastic/ems-client 8.2.0 → 8.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.backportrc.json CHANGED
@@ -2,9 +2,11 @@
2
2
  "upstream": "elastic/ems-client",
3
3
  "branches": [
4
4
  {
5
- "name": "8.1",
5
+ "name": "8.3",
6
6
  "checked": true
7
7
  },
8
+ "8.2",
9
+ "8.1",
8
10
  "8.0",
9
11
  "7.17",
10
12
  "7.16",
@@ -15,4 +15,5 @@
15
15
  allow-whitelist-orgs-as-admins: true
16
16
  github-hooks: true
17
17
  cancel-builds-on-update: true
18
+ status-context: ems-client
18
19
  publishers: []
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
6
 
7
+ ## [8.3.2] - 2022-05-16
8
+
9
+ - Export `blendMode` type #111
10
+ - Fix transforming color definitions with stops #112
11
+
12
+ ## [8.3.1] - 2022-05-06
13
+
14
+ - Added static methods to help translating basemap labels and blending colors to paint properties #105
15
+ - Add optional `format` argument to `getMinZoom` and `getMaxZoom` #90
16
+
17
+ ## [8.3.0] - 2022-04-18
18
+
19
+ ### Changed
20
+ - Default EMS version is 8.3 #104
21
+ - Bump maplibre-gl to 2.1.9 #103
22
+
7
23
  ## [8.2.0] - 2022-03-24
8
24
 
9
25
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elastic/ems-client",
3
- "version": "8.2.0",
3
+ "version": "8.3.2",
4
4
  "description": "JavaScript client library for the Elastic Maps Service",
5
5
  "main": "target/node/index.js",
6
6
  "browser": "target/web/index.js",
@@ -39,6 +39,7 @@
39
39
  "@types/lru-cache": "^5.1.0",
40
40
  "@types/topojson-client": "^3.0.0",
41
41
  "@types/topojson-specification": "^1.0.1",
42
+ "chroma-js": "^2.1.0",
42
43
  "lodash": "^4.17.15",
43
44
  "lru-cache": "^6.0.0",
44
45
  "semver": "^7.3.2",
@@ -50,6 +51,7 @@
50
51
  "@babel/plugin-proposal-class-properties": "^7.5.5",
51
52
  "@babel/preset-env": "^7.5.5",
52
53
  "@babel/preset-typescript": "^7.9.0",
54
+ "@types/chroma-js": "^2.1.3",
53
55
  "@types/jest": "^26.0.10",
54
56
  "@types/lodash": "^4.14.150",
55
57
  "@types/node": "^13.13.5",
@@ -63,10 +65,10 @@
63
65
  "eslint-plugin-prettier": "^3.1.3",
64
66
  "husky": "^4.2.5",
65
67
  "jest": "^26.4.0",
66
- "maplibre-gl": "^1.15.2",
68
+ "maplibre-gl": "2.1.9",
67
69
  "node-fetch": "^2.6.1",
68
70
  "prettier": "^2.0.5",
69
71
  "ts-jest": "^26.2.0",
70
- "typescript": "^3.9.2"
72
+ "typescript": "4.6.3"
71
73
  }
72
74
  }
package/src/ems_client.ts CHANGED
@@ -16,7 +16,7 @@ import { toAbsoluteUrl } from './utils';
16
16
  import { ParsedUrlQueryInput } from 'querystring';
17
17
  import LRUCache from 'lru-cache';
18
18
 
19
- const DEFAULT_EMS_VERSION = '8.2';
19
+ const DEFAULT_EMS_VERSION = '8.3';
20
20
 
21
21
  type URLMeaningfulParts = {
22
22
  auth?: string | null;
@@ -6,9 +6,16 @@
6
6
  */
7
7
 
8
8
  import _ from 'lodash';
9
- import { Style as MapLibreStyle, VectorSource as MapLibreVectorSource } from 'maplibre-gl';
9
+ import {
10
+ DataDrivenPropertyValueSpecification,
11
+ FormattedSpecification,
12
+ LayerSpecification,
13
+ StyleSpecification,
14
+ VectorSourceSpecification,
15
+ } from 'maplibre-gl';
10
16
  import { EMSClient, EmsTmsFormat, TMSServiceConfig } from './ems_client';
11
17
  import { AbstractEmsService } from './ems_service';
18
+ import { layerPaintProperty, colorizeColor, blendMode, mbColorDefinition } from './utils';
12
19
 
13
20
  export type EmsSprite = {
14
21
  height: number;
@@ -24,10 +31,10 @@ export type EmsSpriteSheet = {
24
31
  };
25
32
 
26
33
  type EmsVectorSources = {
27
- [sourceName: string]: MapLibreVectorSource;
34
+ [sourceName: string]: VectorSourceSpecification;
28
35
  };
29
36
 
30
- type EmsVectorStyle = MapLibreStyle & {
37
+ type EmsVectorStyle = StyleSpecification & {
31
38
  sources: EmsVectorSources;
32
39
  };
33
40
 
@@ -45,6 +52,84 @@ type EmsRasterStyle = {
45
52
  };
46
53
 
47
54
  export class TMSService extends AbstractEmsService {
55
+ /*
56
+ List of supported languages with labels and OMT codes extracted
57
+ from https://openmaptiles.org/languages
58
+
59
+ Keys are values for i18.locale from Kibana settings
60
+ and OMT codes for the rest.
61
+ */
62
+ public static SupportedLanguages = [
63
+ { key: 'ar', omt: 'ar' },
64
+ { key: 'az', omt: 'az' },
65
+ { key: 'be', omt: 'be' },
66
+ { key: 'bg', omt: 'bg' },
67
+ { key: 'br', omt: 'br' },
68
+ { key: 'bs', omt: 'bs' },
69
+ { key: 'ca', omt: 'ca' },
70
+ { key: 'cs', omt: 'cs' },
71
+ { key: 'cy', omt: 'cy' },
72
+ { key: 'da', omt: 'da' },
73
+ { key: 'de', omt: 'de' },
74
+ { key: 'el', omt: 'el' },
75
+ { key: 'en', omt: 'en' },
76
+ { key: 'eo', omt: 'eo' },
77
+ { key: 'es', omt: 'es' },
78
+ { key: 'et', omt: 'et' },
79
+ { key: 'fi', omt: 'fi' },
80
+ { key: 'fr-fr', omt: 'fr' },
81
+ { key: 'fy', omt: 'fy' },
82
+ { key: 'ga', omt: 'ga' },
83
+ { key: 'gd', omt: 'gd' },
84
+ { key: 'he', omt: 'he' },
85
+ { key: 'hi-in', omt: 'hi' },
86
+ { key: 'hr', omt: 'hr' },
87
+ { key: 'hu', omt: 'hu' },
88
+ { key: 'hy', omt: 'hy' },
89
+ { key: 'is', omt: 'is' },
90
+ { key: 'it', omt: 'it' },
91
+ { key: 'ja_kana', omt: 'ja_kana' },
92
+ { key: 'ja_rm', omt: 'ja_rm' },
93
+ { key: 'ja-jp', omt: 'ja' },
94
+ { key: 'ka', omt: 'ka' },
95
+ { key: 'kk', omt: 'kk' },
96
+ { key: 'kn', omt: 'kn' },
97
+ { key: 'ko_rm', omt: 'ko_rm' },
98
+ { key: 'ko', omt: 'ko' },
99
+ { key: 'la', omt: 'la' },
100
+ { key: 'lb', omt: 'lb' },
101
+ { key: 'lt', omt: 'lt' },
102
+ { key: 'lv', omt: 'lv' },
103
+ { key: 'mk', omt: 'mk' },
104
+ { key: 'mt', omt: 'mt' },
105
+ { key: 'nl', omt: 'nl' },
106
+ { key: 'no', omt: 'no' },
107
+ { key: 'pl', omt: 'pl' },
108
+ { key: 'pt-pt', omt: 'pt' },
109
+ { key: 'rm', omt: 'rm' },
110
+ { key: 'ro', omt: 'ro' },
111
+ { key: 'ru-ru', omt: 'ru' },
112
+ { key: 'sk', omt: 'sk' },
113
+ { key: 'sl', omt: 'sl' },
114
+ { key: 'sq', omt: 'sq' },
115
+ { key: 'sr-Ltn', omt: 'sr-Ltn' },
116
+ { key: 'sr', omt: 'sr' },
117
+ { key: 'sv', omt: 'sv' },
118
+ { key: 'th', omt: 'th' },
119
+ { key: 'tr', omt: 'tr' },
120
+ { key: 'uk', omt: 'uk' },
121
+ { key: 'zh-cn', omt: 'zh' },
122
+ ];
123
+
124
+ /*
125
+ Suggested default operations for the different EMS styles
126
+ */
127
+ public static colorOperationDefaults = [
128
+ { style: 'road_map', operation: 'mix', percentage: 0.25 },
129
+ { style: 'road_map_desaturated', operation: 'screen', percentage: 0.25 },
130
+ { style: 'dark_map', operation: 'dodge', percentage: 0.25 },
131
+ ];
132
+
48
133
  protected readonly _config: TMSServiceConfig;
49
134
 
50
135
  private _getRasterStyleJson = _.once(async (): Promise<EmsRasterStyle | undefined> => {
@@ -80,7 +165,9 @@ export class TMSService extends AbstractEmsService {
80
165
  if (url) {
81
166
  const sourceUrl = this._proxyPath + this._getAbsoluteUrl(url);
82
167
  const extendedUrl = this._emsClient.extendUrlWithParams(sourceUrl);
83
- const sourceJson = await this._emsClient.getManifest<MapLibreVectorSource>(extendedUrl);
168
+ const sourceJson = await this._emsClient.getManifest<VectorSourceSpecification>(
169
+ extendedUrl
170
+ );
84
171
  const tiles = sourceJson?.tiles?.map((tileUrl) => {
85
172
  const directUrl = this._proxyPath + this._getAbsoluteUrl(tileUrl);
86
173
  return this._emsClient.extendUrlWithParams(directUrl);
@@ -108,6 +195,91 @@ export class TMSService extends AbstractEmsService {
108
195
  this._config = config;
109
196
  }
110
197
 
198
+ /*
199
+ This method returns an array of objects with the layers and the new
200
+ layout['text-field'] to apply using map.setLayoutProperty
201
+ */
202
+ public static transformLanguageProperty(
203
+ layer: LayerSpecification,
204
+ lang: string
205
+ ): DataDrivenPropertyValueSpecification<FormattedSpecification> | undefined {
206
+ const supportedLang = this.SupportedLanguages.find((l) => l.key === lang);
207
+ if (layer.type === 'symbol' && layer.layout !== undefined && supportedLang !== undefined) {
208
+ const omtLang = supportedLang.omt;
209
+ const textField = layer.layout['text-field'];
210
+ if (textField && typeof textField === 'string') {
211
+ return TMSService._getTextField(textField, omtLang);
212
+ }
213
+ }
214
+ return;
215
+ }
216
+
217
+ /*
218
+ This method returns an array of objects per layer, containing a list of
219
+ properties with new colors to apply using map.setPaintProperty
220
+ */
221
+ public static transformColorProperties(
222
+ layer: LayerSpecification,
223
+ color: string,
224
+ operation: blendMode,
225
+ percentage: number
226
+ ): { property: keyof layerPaintProperty; color: mbColorDefinition | undefined }[] {
227
+ if (['background', 'fill', 'line', 'symbol'].indexOf(layer.type) !== -1 && layer.paint) {
228
+ const paint = layer.paint as layerPaintProperty;
229
+ if (layer.type === 'symbol' && Object.keys(paint).length === 0) {
230
+ paint['text-color'] = 'rgb(0,0,0)';
231
+ }
232
+ const types = Object.keys(paint).filter((key) => {
233
+ return (
234
+ [
235
+ 'background-color',
236
+ 'circle-color',
237
+ 'circle-stroke-color',
238
+ 'fill-color',
239
+ 'fill-extrusion-color',
240
+ 'fill-outline-color',
241
+ 'icon-color',
242
+ 'icon-halo-color',
243
+ 'line-color',
244
+ 'text-color',
245
+ 'text-halo-color',
246
+ ].indexOf(key) !== -1
247
+ );
248
+ }) as Array<keyof layerPaintProperty>;
249
+ return types.map((type) => {
250
+ const paintColor = paint[type];
251
+ return {
252
+ property: type,
253
+ color: paintColor ? colorizeColor(paintColor, color, operation, percentage) : paintColor,
254
+ };
255
+ });
256
+ } else {
257
+ return [];
258
+ }
259
+ }
260
+
261
+ private static _getTextField(
262
+ label: string,
263
+ lang: string
264
+ ): DataDrivenPropertyValueSpecification<string> {
265
+ // Capture {name:xx} labels
266
+ const labelMatch = label.match(/\{name([:_])(.{2})\}/);
267
+ if (labelMatch && labelMatch[1] != lang) {
268
+ // Only apply if the languages are different
269
+ return ['coalesce', ['get', `name:${lang}`], ['get', `name${labelMatch[1]}${labelMatch[2]}`]];
270
+ } else if (label.includes('latin') && label.includes('nonlatin')) {
271
+ // Capture the common pattern {name:latin}\n{name:nonlatin}
272
+ return [
273
+ 'coalesce',
274
+ ['get', `name:${lang}`],
275
+ ['concat', ['get', 'name:latin'], '\n', ['get', 'name:nonlatin']],
276
+ ];
277
+ }
278
+
279
+ // If no case is found, return the input label
280
+ return label;
281
+ }
282
+
111
283
  async getDefaultRasterStyle(): Promise<EmsRasterStyle | undefined> {
112
284
  const tileJson = await this._getRasterStyleJson();
113
285
  if (tileJson) {
@@ -193,21 +365,37 @@ export class TMSService extends AbstractEmsService {
193
365
  return this._emsClient.getValueInLanguage(this._config.name);
194
366
  }
195
367
 
196
- async getMinZoom(): Promise<number | undefined> {
197
- const tileJson = await this._getRasterStyleJson();
198
- if (tileJson) {
199
- return tileJson.minzoom;
200
- } else {
201
- return;
368
+ async getMinZoom(format = 'vector'): Promise<number | undefined> {
369
+ switch (format) {
370
+ case 'vector':
371
+ const { sources } = (await this._getVectorStyleJsonInlined()) || { sources: {} };
372
+ return Math.min(
373
+ ...Object.values(sources)
374
+ .map(({ minzoom }) => minzoom)
375
+ .filter((minzoom): minzoom is number => Number.isFinite(minzoom))
376
+ );
377
+ case 'raster':
378
+ const { minzoom } = (await this._getRasterStyleJson()) || {};
379
+ return minzoom;
380
+ default:
381
+ return;
202
382
  }
203
383
  }
204
384
 
205
- async getMaxZoom(): Promise<number | undefined> {
206
- const tileJson = await this._getRasterStyleJson();
207
- if (tileJson) {
208
- return tileJson.maxzoom;
209
- } else {
210
- return;
385
+ async getMaxZoom(format = 'vector'): Promise<number | undefined> {
386
+ switch (format) {
387
+ case 'vector':
388
+ const { sources } = (await this._getVectorStyleJsonInlined()) || { sources: {} };
389
+ return Math.max(
390
+ ...Object.values(sources)
391
+ .map(({ maxzoom }) => maxzoom)
392
+ .filter((maxzoom): maxzoom is number => Number.isFinite(maxzoom))
393
+ );
394
+ case 'raster':
395
+ const { maxzoom } = (await this._getRasterStyleJson()) || {};
396
+ return maxzoom;
397
+ default:
398
+ return;
211
399
  }
212
400
  }
213
401
 
package/src/utils.ts CHANGED
@@ -5,6 +5,13 @@
5
5
  * 2.0.
6
6
  */
7
7
 
8
+ import chroma, { ChromaStatic } from 'chroma-js';
9
+ import {
10
+ ColorSpecification,
11
+ DataDrivenPropertyValueSpecification,
12
+ PropertyValueSpecification,
13
+ } from 'maplibre-gl';
14
+
8
15
  /**
9
16
  * Resolves a target URL path relative to the host.
10
17
  * This is specifically useed by the Kibana proxy re-routing.
@@ -25,3 +32,85 @@ export function toAbsoluteUrl(host: string | undefined, path: string): string {
25
32
  return host + '/' + path;
26
33
  }
27
34
  }
35
+
36
+ /*
37
+ Type with the possible ways to define a color in maplibre
38
+ */
39
+ export type mbColorDefinition =
40
+ | PropertyValueSpecification<ColorSpecification>
41
+ | DataDrivenPropertyValueSpecification<ColorSpecification>;
42
+
43
+ /*
44
+ A selection of the different maplibre-gl-js properties that refer to a color.
45
+ Taken from maplibre https://github.com/maplibre/maplibre-gl-js/blob/main/src/style-spec/types.g.ts
46
+ */
47
+ export type layerPaintProperty = {
48
+ 'background-color'?: PropertyValueSpecification<ColorSpecification>;
49
+ 'circle-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
50
+ 'circle-stroke-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
51
+ 'fill-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
52
+ 'fill-extrusion-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
53
+ 'fill-outline-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
54
+ 'icon-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
55
+ 'icon-halo-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
56
+ 'line-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
57
+ 'text-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
58
+ 'text-halo-color'?: DataDrivenPropertyValueSpecification<ColorSpecification>;
59
+ };
60
+
61
+ export type blendMode = Parameters<ChromaStatic['blend']>[2] | 'mix';
62
+
63
+ /*
64
+ Function to transform a maplibre color definition by a given function.
65
+ */
66
+ function transformColor(paintColor: mbColorDefinition, func: Function): mbColorDefinition {
67
+ if (typeof paintColor == 'string') {
68
+ const modifiedColor = func(paintColor);
69
+ return `rgba(${modifiedColor.rgba().join(',')})`;
70
+ } else if (
71
+ typeof paintColor == 'object' &&
72
+ 'stops' in paintColor &&
73
+ Array.isArray(paintColor?.stops)
74
+ ) {
75
+ const stops = paintColor['stops'].map((stop) => {
76
+ const newColor = transformColor(stop[1], func);
77
+ return [stop[0], newColor];
78
+ });
79
+ const newPaintColor = { ...paintColor, ...stops };
80
+ return newPaintColor;
81
+ } else return paintColor;
82
+ }
83
+
84
+ /*
85
+ This is the function used to generate the current EMS desaturated roadmap
86
+ */
87
+ export function desaturateColor(paintColor: mbColorDefinition): mbColorDefinition {
88
+ return transformColor(paintColor, (color: string) => {
89
+ return chroma(color).desaturate(1.1).brighten(0.33);
90
+ });
91
+ }
92
+
93
+ /*
94
+ Blends an original maplibre color definition with a destination
95
+ color. Accepts different blending modes and an additional `mix`
96
+ option that needs a percentage.
97
+
98
+ More details:
99
+
100
+ https://gka.github.io/chroma.js/#chroma-blend
101
+ https://gka.github.io/chroma.js/#chroma-mix
102
+ */
103
+ export function colorizeColor(
104
+ sourceColor: mbColorDefinition,
105
+ destColor: string,
106
+ operation: blendMode = 'screen',
107
+ percentage: number = 0.5
108
+ ): mbColorDefinition {
109
+ return transformColor(sourceColor, (color: string) => {
110
+ if (operation !== 'mix') {
111
+ return chroma.blend(chroma(color), destColor, operation);
112
+ } else {
113
+ return chroma.mix(chroma(color), destColor, percentage);
114
+ }
115
+ });
116
+ }
@@ -23,7 +23,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
23
23
 
24
24
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
25
25
 
26
- const DEFAULT_EMS_VERSION = '8.2';
26
+ const DEFAULT_EMS_VERSION = '8.3';
27
27
 
28
28
  /**
29
29
  * plugins cannot have upstream dependencies on core/*-kibana.