@superrouteca/smap-arcgis 1.1.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.1 — 2026-10-27
4
+
5
+ - The vector basemap draws: water, land, roads, labels and icons appear in every colour scheme and label language
6
+ (with 1.1.0, `createSMapBasemap()` and `createVectorTileLayer()` loaded without an error and drew nothing). The style
7
+ is converted into what ArcGIS's vector tile layer supports before it is handed over; sMap's own style is unchanged.
8
+ - What the conversion simplifies, compared with the sMap web map: a label shows the name in the label language and,
9
+ for a name in another script, the local name on a second line, in one font (the per-script fonts are not used);
10
+ labels that sMap places at one of several positions keep the first position; layer properties ArcGIS does not
11
+ have are left out, and 3D buildings are drawn flat. `layer.smap.simplified` lists every simplification of the
12
+ style shown.
13
+ - `toArcGISStyle()` converts any style the same way (for a `VectorTileLayer` of your own); `convertStyle: false`
14
+ hands sMap's style to ArcGIS unchanged.
15
+ - Chinese, Japanese and Korean labels show in the vector basemap: sMap 1.1.1 serves them in its fonts. Chinese
16
+ characters take the Simplified Chinese form in every language.
17
+
3
18
  ## 1.1.0 — 2026-10-27
4
19
 
5
20
  - First version.
package/README.md CHANGED
@@ -48,6 +48,21 @@ An existing ArcGIS app switches its basemap and keeps every layer: `map.basemap
48
48
 
49
49
  The layers on their own: `createVectorTileLayer(options)` and `createRasterTileLayer(options)`.
50
50
 
51
+ ### The vector style in ArcGIS
52
+
53
+ ArcGIS's `VectorTileLayer` supports part of the Style Specification, so the vector basemap converts sMap's style
54
+ before handing it over (`toArcGISStyle(style)` does the same for a `VectorTileLayer` of your own; `convertStyle: false`
55
+ hands the style over unchanged). Compared with the sMap web map:
56
+
57
+ - a label shows the name in the label language and, for a name in another script, the local name on a second line,
58
+ in one font;
59
+ - a label that sMap places at one of several positions keeps the first one;
60
+ - layer properties ArcGIS does not have are left out, and 3D buildings are drawn flat;
61
+ - Chinese, Japanese and Korean labels are drawn with sMap's own fonts (sMap 1.1.1 or later); Chinese characters take
62
+ the Simplified Chinese form in every language.
63
+
64
+ `basemap.smap.simplified` lists each simplification of the style shown (`"<layer>: <what>"`).
65
+
51
66
  ### Keys, tokens and nodes
52
67
 
53
68
  `createSMapBasemap` (and the layer functions) install one request interceptor per session
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superrouteca/smap-arcgis",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "sMap for the ArcGIS Maps SDK for JavaScript: sMap basemaps (vector and raster), sMap layers as graphics, and ArcGIS REST services on sMap maps.",
5
5
  "license": "MIT",
6
6
  "author": "Goodneo Solutions",
@@ -50,7 +50,7 @@
50
50
  "typecheck": "tsc -p test/tsconfig.json"
51
51
  },
52
52
  "dependencies": {
53
- "@superrouteca/smap": "1.1.0"
53
+ "@superrouteca/smap": "1.1.1"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@arcgis/core": "^5.0.0"
@@ -62,6 +62,7 @@
62
62
  },
63
63
  "devDependencies": {
64
64
  "@arcgis/core": "5.1.27",
65
+ "@maplibre/maplibre-gl-style-spec": "26.4.4",
65
66
  "typescript": "6.0.3"
66
67
  }
67
68
  }
package/src/basemap.js CHANGED
@@ -8,6 +8,7 @@ import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js';
8
8
  import WebTileLayer from '@arcgis/core/layers/WebTileLayer.js';
9
9
  import { applyBasemapOverrides } from '@superrouteca/smap';
10
10
  import { createSession, ATTRIBUTION } from './session.js';
11
+ import { toArcGISStyle } from './style.js';
11
12
 
12
13
  const installed = new WeakMap();
13
14
 
@@ -76,10 +77,17 @@ export async function createVectorTileLayer(options = {}) {
76
77
  let style = options.style || 'light';
77
78
  let language = options.language;
78
79
  let overrides = options.basemap || null;
79
- // Basemap options (contract §1: show, hide and recolour by class), applied by the SDK's own function.
80
+ let simplified = [];
81
+ // Basemap options (contract §1: show, hide and recolour by class), applied by the SDK's own function; then the
82
+ // style is converted into what ArcGIS's VectorTileLayer draws (style.js; `convertStyle: false` hands it over as is).
80
83
  const styled = async () => {
81
84
  const doc = await session.style(style, language);
82
- return overrides && Object.keys(overrides).length ? applyBasemapOverrides(doc, overrides) : doc;
85
+ const own = overrides && Object.keys(overrides).length ? applyBasemapOverrides(doc, overrides) : doc;
86
+ if (options.convertStyle === false) { simplified = []; return own; }
87
+ const changes = [];
88
+ const out = toArcGISStyle(own, { language, changes });
89
+ simplified = changes;
90
+ return out;
83
91
  };
84
92
  const layer = new VectorTileLayer({
85
93
  id: options.id || 'smap-vector',
@@ -93,6 +101,8 @@ export async function createVectorTileLayer(options = {}) {
93
101
  get style() { return style; },
94
102
  get language() { return language; },
95
103
  basemap() { return overrides ? { ...overrides } : {}; },
104
+ /** What the conversion for ArcGIS simplified in the style now shown, one line per layer and property. */
105
+ get simplified() { return simplified.slice(); },
96
106
  setStyle(next) { style = next; return reload(); },
97
107
  setLanguage(next) { language = next; return reload(); },
98
108
  setBasemap(next) { overrides = next && Object.keys(next).length ? { ...next } : null; return reload(); },
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @superrouteca/smap-arcgis — sMap and the ArcGIS Maps SDK for JavaScript both ways.
2
2
  export { Session, createSession, FLAVORS, ATTRIBUTION } from './session.js';
3
3
  export { installInterceptor, createVectorTileLayer, createRasterTileLayer, createSMapBasemap, addAttribution } from './basemap.js';
4
+ export { toArcGISStyle, styleLanguage, nameExpression } from './style.js';
4
5
  export { createGraphicsLayer, symbolFor, toArcGisGeometries, toFeatures, zoomToScale } from './graphics.js';
5
6
  export { addArcGisService, queryFeatures, serviceInfo, parseServiceUrl, withToken, symbolToSimplestyle, rendererStyle } from './services.js';
6
- export const version = '1.1.0';
7
+ export const version = '1.1.1';
package/src/style.js ADDED
@@ -0,0 +1,251 @@
1
+ // sMap's styles made readable by the ArcGIS Maps SDK's VectorTileLayer, which implements only part of the style
2
+ // specification. Measured against @arcgis/core 5.1.27 (its views/2d/engine/vectorTiles: expression.js, Filter.js,
3
+ // StyleProperty.js, StyleDefinition.js) and checked in a browser:
4
+ //
5
+ // - `is-supported-script` is not implemented: a text-field that uses it fails to parse and the tiles holding such
6
+ // features draw nothing at all (with sMap's label expressions: the whole basemap stays empty). It becomes `true`.
7
+ // - `format` joins its sections with commas: one section becomes its text, several are joined with `concat`.
8
+ // - `coalesce` stops at the first value that is not null, but a missing property reads as undefined there, so
9
+ // `coalesce(get name:en, get name)` gives nothing for a feature without name:en: a chain of `get` becomes
10
+ // `case(has …)`.
11
+ // - a filter that mixes the legacy syntax and expressions (`["all", ["==", "kind", "x"], ["!", ["has", "y"]]]`) is
12
+ // converted as if it were all legacy and breaks: every filter is written as an expression.
13
+ // - properties it has no definition for (text-variable-anchor, text-radial-offset, the *-sort-key of fill / line /
14
+ // circle, line-gradient, …) are left out or approximated, layer types it cannot draw are left out (fill-extrusion
15
+ // becomes a flat fill).
16
+ //
17
+ // The server's style is never changed: the conversion works on a copy. `changes`, when given, receives one line per
18
+ // simplification.
19
+
20
+ const UNIMPLEMENTED = new Set(['collator', 'image', 'number-format', 'accumulated', 'feature-state', 'line-progress',
21
+ 'within', 'heatmap-density', 'distance', 'global-state', 'elevation']);
22
+ const COMPARISONS = new Set(['==', '!=', '<', '<=', '>', '>=']);
23
+ const LAYER_TYPES = new Set(['background', 'fill', 'line', 'symbol', 'circle']);
24
+ // Layout and paint properties of the style specification that @arcgis/core 5.1.27 has no definition for.
25
+ const DROPPED = new Set(['fill-sort-key', 'fill-layer-opacity', 'line-sort-key', 'line-gradient', 'line-layer-opacity',
26
+ 'icon-pitch-alignment', 'icon-text-fit', 'icon-text-fit-padding', 'symbol-height-anchor', 'symbol-height-offset',
27
+ 'symbol-z-order', 'text-pitch-alignment', 'text-variable-anchor-offset', 'circle-sort-key', 'circle-pitch-alignment',
28
+ 'circle-pitch-scale']);
29
+ const ANCHOR_OFFSET = { center: [0, 0], top: [0, 1], bottom: [0, -1], left: [1, 0], right: [-1, 0],
30
+ 'top-left': [0.7, 0.7], 'top-right': [-0.7, 0.7], 'bottom-left': [0.7, -0.7], 'bottom-right': [-0.7, -0.7] };
31
+
32
+ class Unsupported extends Error {}
33
+
34
+ const isGet = (e) => Array.isArray(e) && e[0] === 'get' && e.length === 2 && typeof e[1] === 'string';
35
+ const clone = (v) => (v === undefined ? v : JSON.parse(JSON.stringify(v)));
36
+
37
+ /** The language of sMap's labels: the first `name:<lang>` the style reads. */
38
+ export function styleLanguage(style) {
39
+ const m = JSON.stringify((style && style.layers) || []).match(/"name:([A-Za-z-]+)"/);
40
+ return m ? m[1] : null;
41
+ }
42
+
43
+ /** A label in `language` (else the name in the local language), in expressions ArcGIS evaluates. */
44
+ export function nameExpression(language) {
45
+ const chain = [];
46
+ if (language) { chain.push(['get', 'name:' + language]); }
47
+ chain.push(['get', 'pgf:name'], ['get', 'name']);
48
+ return rewrite(['coalesce', ...chain], { note() {} });
49
+ }
50
+
51
+ /** Rewrites one expression into what ArcGIS evaluates; throws Unsupported where it cannot. */
52
+ function rewrite(e, ctx) {
53
+ if (!Array.isArray(e) || e.length === 0 || typeof e[0] !== 'string') {
54
+ return Array.isArray(e) ? e.map((x) => rewrite(x, ctx)) : e;
55
+ }
56
+ const op = e[0];
57
+ if (op === 'literal') { return e; }
58
+ if (op === 'is-supported-script') { ctx.note('is-supported-script read as true (local names are always shown)'); return true; }
59
+ if (op === 'resolved-locale') { return ctx.language || 'en'; }
60
+ if (op === 'image') { return rewrite(e[1], ctx); }
61
+ if (op === 'number-format') { return ['to-string', rewrite(e[1], ctx)]; }
62
+ if (op === 'format') {
63
+ const texts = [];
64
+ let options = false;
65
+ for (let i = 1; i < e.length; i++) {
66
+ const a = e[i];
67
+ if (a && typeof a === 'object' && !Array.isArray(a)) { if (Object.keys(a).length) { options = true; } continue; }
68
+ texts.push(rewrite(a, ctx));
69
+ }
70
+ if (texts.length > 1 || options) { ctx.note('format: sections joined into one text' + (options ? ', section fonts and sizes dropped' : '')); }
71
+ return texts.length === 1 ? texts[0] : ['concat', ...texts];
72
+ }
73
+ if (op === 'coalesce') {
74
+ const args = e.slice(1).map((x) => rewrite(x, ctx));
75
+ let out = args[args.length - 1];
76
+ for (let i = args.length - 2; i >= 0; i--) {
77
+ out = isGet(args[i]) ? ['case', ['has', args[i][1]], args[i], out] : ['coalesce', args[i], out];
78
+ }
79
+ return out;
80
+ }
81
+ if (COMPARISONS.has(op) && e.length === 4) { ctx.note(op + ': collator dropped'); return [op, rewrite(e[1], ctx), rewrite(e[2], ctx)]; }
82
+ if (UNIMPLEMENTED.has(op)) { throw new Unsupported(op); }
83
+ return [op, ...e.slice(1).map((x) => rewrite(x, ctx))];
84
+ }
85
+
86
+ // maplibre's test of whether a filter is written as an expression (the legacy syntax otherwise).
87
+ function isExpressionFilter(f) {
88
+ if (f === true || f === false) { return true; }
89
+ if (!Array.isArray(f) || f.length === 0) { return false; }
90
+ switch (f[0]) {
91
+ case 'has': return f.length >= 2 && f[1] !== '$id' && f[1] !== '$type';
92
+ case 'in': return f.length >= 3 && (typeof f[1] !== 'string' || Array.isArray(f[2]));
93
+ case '!in': case '!has': case 'none': return false;
94
+ case '==': case '!=': case '>': case '>=': case '<': case '<=': return f.length !== 3 || Array.isArray(f[1]) || Array.isArray(f[2]);
95
+ case 'any': case 'all':
96
+ for (const x of f.slice(1)) { if (!isExpressionFilter(x) && typeof x !== 'boolean') { return false; } }
97
+ return true;
98
+ default: return true;
99
+ }
100
+ }
101
+
102
+ function legacyKey(k) { return k === '$type' ? ['geometry-type'] : k === '$id' ? ['id'] : ['get', k]; }
103
+
104
+ // A legacy filter as an expression (the style specification's own conversion, without its type guards).
105
+ function legacyToExpression(f) {
106
+ if (!Array.isArray(f) || f.length === 0) { return true; }
107
+ const [op, ...a] = f;
108
+ switch (op) {
109
+ case 'any': case 'all': return [op, ...a.map(filterExpression)];
110
+ case 'none': return ['!', ['any', ...a.map(filterExpression)]];
111
+ case 'has': return a[0] === '$type' ? true : a[0] === '$id' ? ['has-id'] : ['has', a[0]];
112
+ case '!has': return ['!', legacyToExpression(['has', a[0]])];
113
+ case 'in': return ['in', legacyKey(a[0]), ['literal', a.slice(1)]];
114
+ case '!in': return ['!', ['in', legacyKey(a[0]), ['literal', a.slice(1)]]];
115
+ case '!=': return ['!', ['==', legacyKey(a[0]), a[1]]];
116
+ case '==': case '<': case '<=': case '>': case '>=': return [op, legacyKey(a[0]), a[1]];
117
+ default: return true;
118
+ }
119
+ }
120
+
121
+ function filterExpression(f) {
122
+ if (typeof f === 'boolean') { return f; }
123
+ return isExpressionFilter(f) ? f : legacyToExpression(f);
124
+ }
125
+
126
+ // ArcGIS reads a bare boolean inside all / any as a legacy filter (and then the whole filter as legacy): booleans are
127
+ // folded away.
128
+ function foldBooleans(f) {
129
+ if (!Array.isArray(f) || (f[0] !== 'all' && f[0] !== 'any' && f[0] !== '!')) { return f; }
130
+ if (f[0] === '!') { const x = foldBooleans(f[1]); return typeof x === 'boolean' ? !x : ['!', x]; }
131
+ const all = f[0] === 'all';
132
+ const kept = [];
133
+ for (const x of f.slice(1).map(foldBooleans)) {
134
+ if (x === all) { continue; }
135
+ if (x === !all) { return !all; }
136
+ kept.push(x);
137
+ }
138
+ return kept.length === 0 ? all : kept.length === 1 ? kept[0] : [f[0], ...kept];
139
+ }
140
+
141
+ // text-variable-anchor (and text-radial-offset) as one text-anchor and text-offset: the first anchor of each list.
142
+ function fixedAnchor(layout, note) {
143
+ const first = (v) => (Array.isArray(v) && v[0] === 'literal' ? first(v[1]) : Array.isArray(v) ? v[0] : v);
144
+ const anchors = layout['text-variable-anchor'];
145
+ const radial = layout['text-radial-offset'];
146
+ delete layout['text-variable-anchor'];
147
+ delete layout['text-radial-offset'];
148
+ if (anchors === undefined) {
149
+ if (radial !== undefined) { note('text-radial-offset dropped'); }
150
+ return;
151
+ }
152
+ const r = typeof radial === 'number' ? radial : 0;
153
+ const offset = (a) => (ANCHOR_OFFSET[a] || [0, 0]).map((x) => x * r);
154
+ if (Array.isArray(anchors) && anchors[0] === 'step') {
155
+ const anchor = ['step', anchors[1]];
156
+ const off = ['step', anchors[1]];
157
+ for (let i = 2; i < anchors.length; i++) {
158
+ if (i % 2 === 1) { anchor.push(anchors[i]); off.push(anchors[i]); continue; }
159
+ const a = first(anchors[i]);
160
+ anchor.push(a);
161
+ off.push(['literal', offset(a)]);
162
+ }
163
+ if (layout['text-anchor'] === undefined) { layout['text-anchor'] = anchor; }
164
+ if (r && layout['text-offset'] === undefined) { layout['text-offset'] = off; }
165
+ } else {
166
+ const a = first(anchors);
167
+ if (layout['text-anchor'] === undefined && typeof a === 'string') { layout['text-anchor'] = a; }
168
+ if (r && layout['text-offset'] === undefined) { layout['text-offset'] = offset(a); }
169
+ }
170
+ note('text-variable-anchor: the first anchor only' + (radial !== undefined ? ', text-radial-offset as text-offset' : ''));
171
+ }
172
+
173
+ function convertLayer(layer, ctx) {
174
+ const l = clone(layer);
175
+ const note = (what) => ctx.changes.push(l.id + ': ' + what);
176
+ if (l.type === 'fill-extrusion') {
177
+ l.type = 'fill';
178
+ const p = l.paint || {};
179
+ l.paint = {};
180
+ if (p['fill-extrusion-color'] !== undefined) { l.paint['fill-color'] = p['fill-extrusion-color']; }
181
+ if (p['fill-extrusion-opacity'] !== undefined) { l.paint['fill-opacity'] = p['fill-extrusion-opacity']; }
182
+ if (p['fill-extrusion-pattern'] !== undefined) { l.paint['fill-pattern'] = p['fill-extrusion-pattern']; }
183
+ note('fill-extrusion drawn as a flat fill');
184
+ }
185
+ if (!LAYER_TYPES.has(l.type)) { note('layer type ' + l.type + ' left out'); return null; }
186
+ if (l.type !== 'background' && ctx.sources && !(ctx.sources[l.source] && ctx.sources[l.source].type === 'vector')) {
187
+ note('layer of a ' + ((ctx.sources[l.source] && ctx.sources[l.source].type) || 'missing') + ' source left out');
188
+ return null;
189
+ }
190
+ const lctx = { language: ctx.language, note };
191
+ if (l.filter !== undefined) {
192
+ try {
193
+ l.filter = foldBooleans(rewrite(filterExpression(l.filter), lctx));
194
+ if (l.filter === true) { delete l.filter; } else if (l.filter === false) { l.filter = ['==', 1, 0]; }
195
+ } catch (e) {
196
+ if (!(e instanceof Unsupported)) { throw e; }
197
+ note('filter with ' + e.message + ' dropped');
198
+ delete l.filter;
199
+ }
200
+ }
201
+ if (l.layout && (l.layout['text-variable-anchor'] !== undefined || l.layout['text-radial-offset'] !== undefined)) { fixedAnchor(l.layout, note); }
202
+ for (const kind of ['layout', 'paint']) {
203
+ const props = l[kind];
204
+ if (!props) { continue; }
205
+ for (const name of Object.keys(props)) {
206
+ if (DROPPED.has(name)) { delete props[name]; note(name + ' dropped'); continue; }
207
+ if (name === 'text-overlap' || name === 'icon-overlap') {
208
+ const v = props[name];
209
+ delete props[name];
210
+ const allow = name.replace('-overlap', '-allow-overlap');
211
+ if (props[allow] === undefined && typeof v === 'string') { props[allow] = v !== 'never'; }
212
+ note(name + ' as ' + allow);
213
+ continue;
214
+ }
215
+ try { props[name] = rewrite(props[name], lctx); } catch (e) {
216
+ if (!(e instanceof Unsupported)) { throw e; }
217
+ if (name === 'text-field') {
218
+ props[name] = nameExpression(ctx.language);
219
+ note('text-field with ' + e.message + ' read as the name in the label language');
220
+ } else {
221
+ delete props[name];
222
+ note(name + ' with ' + e.message + ' dropped');
223
+ }
224
+ }
225
+ }
226
+ }
227
+ return l;
228
+ }
229
+
230
+ /**
231
+ * A copy of an sMap style (or any style of the specification) that ArcGIS's VectorTileLayer draws: filters written
232
+ * as expressions, labels without the operators ArcGIS lacks, and properties it has no definition for left out or
233
+ * approximated (see the top of this file).
234
+ *
235
+ * @param {object} style a style document
236
+ * @param {{ language?: string, changes?: string[] }} [options] `language` of the fallback label (read from the
237
+ * style when not given); `changes` receives one line per simplification
238
+ * @returns {object} the converted copy
239
+ */
240
+ export function toArcGISStyle(style, options = {}) {
241
+ if (!style || !Array.isArray(style.layers)) { return style; }
242
+ const changes = [];
243
+ const ctx = { language: options.language || styleLanguage(style), sources: style.sources || null, changes };
244
+ const out = { ...style, layers: [] };
245
+ for (const layer of style.layers) {
246
+ const l = convertLayer(layer, ctx);
247
+ if (l) { out.layers.push(l); }
248
+ }
249
+ if (Array.isArray(options.changes)) { options.changes.push(...[...new Set(changes)]); }
250
+ return out;
251
+ }
package/types/index.d.ts CHANGED
@@ -86,10 +86,27 @@ export interface SMapVectorInfo extends SMapLayerInfo {
86
86
  /** Basemap options by class (contract §1), replacing the previous ones; `{}` clears. */
87
87
  setBasemap(overrides: BasemapOverrides): Promise<VectorTileLayer>;
88
88
  basemap(): BasemapOverrides;
89
+ /** What the conversion for ArcGIS simplified in the style now shown ("<layer id>: <what>"), see toArcGISStyle. */
90
+ readonly simplified: string[];
89
91
  }
90
- export declare function createVectorTileLayer(options?: LayerCommon & { basemap?: BasemapOverrides }): Promise<VectorTileLayer & { smap: SMapVectorInfo }>;
92
+ interface VectorOptions {
93
+ basemap?: BasemapOverrides;
94
+ /** false hands sMap's style to ArcGIS unconverted (default true: see toArcGISStyle). */
95
+ convertStyle?: boolean;
96
+ }
97
+ export declare function createVectorTileLayer(options?: LayerCommon & VectorOptions): Promise<VectorTileLayer & { smap: SMapVectorInfo }>;
91
98
  export declare function createRasterTileLayer(options?: LayerCommon & { retina?: boolean }): Promise<WebTileLayer & { smap: SMapLayerInfo }>;
92
- export declare function createSMapBasemap(options?: LayerCommon & { raster?: boolean; retina?: boolean; basemapId?: string; basemap?: BasemapOverrides }): Promise<Basemap & { smap: SMapLayerInfo | SMapVectorInfo }>;
99
+ export declare function createSMapBasemap(options?: LayerCommon & VectorOptions & { raster?: boolean; retina?: boolean; basemapId?: string }): Promise<Basemap & { smap: SMapLayerInfo | SMapVectorInfo }>;
100
+ /**
101
+ * A copy of a style that ArcGIS's VectorTileLayer draws: filters as expressions, labels without the operators ArcGIS
102
+ * lacks (is-supported-script, format, coalesce of properties), properties and layer types ArcGIS has no definition
103
+ * for left out or approximated. `changes` receives one line per simplification.
104
+ */
105
+ export declare function toArcGISStyle<T extends { layers?: unknown[] } | null | undefined>(style: T, options?: { language?: string; changes?: string[] }): T;
106
+ /** The label language of an sMap style (the first name:<lang> it reads), or null. */
107
+ export declare function styleLanguage(style: { layers?: unknown[] }): string | null;
108
+ /** The label expression the conversion falls back to: the name in `language`, else the local name. */
109
+ export declare function nameExpression(language?: string | null): unknown[];
93
110
  export declare function addAttribution(view: { ui: { add(el: HTMLElement, position: string): void }; container?: HTMLElement | null }, options?: { document?: Document }): HTMLElement;
94
111
 
95
112
  export type GraphicsKind = 'points' | 'lines' | 'areas';