@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 +15 -0
- package/README.md +15 -0
- package/package.json +3 -2
- package/src/basemap.js +12 -2
- package/src/index.js +2 -1
- package/src/style.js +251 -0
- package/types/index.d.ts +19 -2
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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';
|