@map-zero/ol 0.2.1 → 0.3.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/README.md +8 -0
- package/package.json +18 -1
- package/src/index.js +215 -58
- package/src/labels.js +95 -7
package/README.md
ADDED
package/package.json
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@map-zero/ol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenLayers integration helper for map-zero packages.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/mploscos/map-zero#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mploscos/map-zero.git",
|
|
11
|
+
"directory": "packages/ol"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/mploscos/map-zero/issues"
|
|
15
|
+
},
|
|
6
16
|
"main": "./src/index.js",
|
|
7
17
|
"exports": {
|
|
8
18
|
".": "./src/index.js"
|
|
9
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@map-zero/raster": "0.3.1"
|
|
26
|
+
},
|
|
10
27
|
"peerDependencies": {
|
|
11
28
|
"ol": "^10.9.0",
|
|
12
29
|
"pmtiles": "^4.4.1"
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,10 @@ import VectorTileSource from 'ol/source/VectorTile.js';
|
|
|
7
7
|
import { createXYZ } from 'ol/tilegrid.js';
|
|
8
8
|
import { PMTiles } from 'pmtiles';
|
|
9
9
|
|
|
10
|
+
import { isAipLayer, layerAlias } from '../../raster/src/shared/layers.js';
|
|
11
|
+
import { pmtilesInfo } from '../../raster/src/shared/manifest.js';
|
|
12
|
+
import { clampInteger, clampNumber } from '../../raster/src/shared/math.js';
|
|
13
|
+
|
|
10
14
|
import {
|
|
11
15
|
activeLabelLayerIdsForZoom,
|
|
12
16
|
createMapZeroLabelLayer,
|
|
@@ -15,6 +19,35 @@ import {
|
|
|
15
19
|
|
|
16
20
|
let autoInstanceCounter = 0;
|
|
17
21
|
|
|
22
|
+
const ROAD_HIGHWAY_DRAW_ORDER = [
|
|
23
|
+
'platform',
|
|
24
|
+
'corridor',
|
|
25
|
+
'steps',
|
|
26
|
+
'footway',
|
|
27
|
+
'cycleway',
|
|
28
|
+
'path',
|
|
29
|
+
'track',
|
|
30
|
+
'service',
|
|
31
|
+
'construction',
|
|
32
|
+
'proposed',
|
|
33
|
+
'living_street',
|
|
34
|
+
'pedestrian',
|
|
35
|
+
'road',
|
|
36
|
+
'unclassified',
|
|
37
|
+
'residential',
|
|
38
|
+
'busway',
|
|
39
|
+
'tertiary_link',
|
|
40
|
+
'tertiary',
|
|
41
|
+
'secondary_link',
|
|
42
|
+
'secondary',
|
|
43
|
+
'primary_link',
|
|
44
|
+
'primary',
|
|
45
|
+
'trunk_link',
|
|
46
|
+
'trunk',
|
|
47
|
+
'motorway_link',
|
|
48
|
+
'motorway'
|
|
49
|
+
];
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* @typedef {{
|
|
20
53
|
* id?: string,
|
|
@@ -25,7 +58,6 @@ let autoInstanceCounter = 0;
|
|
|
25
58
|
* source?: 'auto' | 'pmtiles' | 'dynamic',
|
|
26
59
|
* renderMode?: 'vector' | 'raster-worker',
|
|
27
60
|
* workerUrl?: string | URL,
|
|
28
|
-
* rasterWorkerUrl?: string | URL,
|
|
29
61
|
* rasterPixelRatio?: number,
|
|
30
62
|
* overzoomLevels?: number,
|
|
31
63
|
* edgeGuardPixels?: number,
|
|
@@ -208,7 +240,7 @@ function createRasterWorkerController(context, options) {
|
|
|
208
240
|
manifestUrl: context.manifestUrl,
|
|
209
241
|
styleDocument: context.styleDocument,
|
|
210
242
|
layers: context.orderedLayers.map((layer) => layer.id),
|
|
211
|
-
workerUrl: options.
|
|
243
|
+
workerUrl: options.workerUrl ?? new URL('@map-zero/raster/imagery-worker.js', import.meta.url),
|
|
212
244
|
rasterPixelRatio: options.rasterPixelRatio,
|
|
213
245
|
overzoomLevels: options.overzoomLevels,
|
|
214
246
|
edgeGuardPixels: options.edgeGuardPixels
|
|
@@ -244,7 +276,7 @@ function createRasterWorkerController(context, options) {
|
|
|
244
276
|
|
|
245
277
|
const refresh = () => {
|
|
246
278
|
worker.setVisibleLayers(context.layerVisibility);
|
|
247
|
-
source
|
|
279
|
+
refreshRasterSource(source);
|
|
248
280
|
layer.changed();
|
|
249
281
|
};
|
|
250
282
|
|
|
@@ -269,13 +301,18 @@ function createRasterWorkerController(context, options) {
|
|
|
269
301
|
layer.setOpacity(clamp(Number(opacity), 0, 1));
|
|
270
302
|
},
|
|
271
303
|
destroy() {
|
|
272
|
-
source
|
|
304
|
+
refreshRasterSource(source);
|
|
273
305
|
worker.destroy();
|
|
274
306
|
layer.dispose();
|
|
275
307
|
}
|
|
276
308
|
};
|
|
277
309
|
}
|
|
278
310
|
|
|
311
|
+
function refreshRasterSource(source) {
|
|
312
|
+
source.clear();
|
|
313
|
+
source.refresh?.();
|
|
314
|
+
}
|
|
315
|
+
|
|
279
316
|
class MapZeroRasterTileWorker {
|
|
280
317
|
/**
|
|
281
318
|
* @param {{
|
|
@@ -283,10 +320,10 @@ class MapZeroRasterTileWorker {
|
|
|
283
320
|
* manifestUrl: string,
|
|
284
321
|
* styleDocument: Record<string, unknown>,
|
|
285
322
|
* layers: string[],
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
323
|
+
* workerUrl: string | URL,
|
|
324
|
+
* rasterPixelRatio?: number,
|
|
325
|
+
* overzoomLevels?: number,
|
|
326
|
+
* edgeGuardPixels?: number
|
|
290
327
|
* }} options
|
|
291
328
|
*/
|
|
292
329
|
constructor(options) {
|
|
@@ -964,11 +1001,54 @@ function createWebGlStyles(context) {
|
|
|
964
1001
|
function createRoadStyleRules(filter, rule, layerOpacity) {
|
|
965
1002
|
return [
|
|
966
1003
|
...createRoadSemanticUnderlayRules(filter, rule, layerOpacity),
|
|
967
|
-
...
|
|
1004
|
+
...createRoadPriorityStyleRules(filter, rule, layerOpacity),
|
|
968
1005
|
...createRoadSemanticOverlayRules(filter, rule, layerOpacity)
|
|
969
1006
|
];
|
|
970
1007
|
}
|
|
971
1008
|
|
|
1009
|
+
/**
|
|
1010
|
+
* Draw road classes from least important to most important so local/darker
|
|
1011
|
+
* roads stay below brighter major roads at intersections.
|
|
1012
|
+
*
|
|
1013
|
+
* @param {unknown[]} filter
|
|
1014
|
+
* @param {Record<string, unknown>} rule
|
|
1015
|
+
* @param {Map<string, number>} layerOpacity
|
|
1016
|
+
* @returns {Array<{ filter: unknown[], style: Record<string, unknown> }>}
|
|
1017
|
+
*/
|
|
1018
|
+
function createRoadPriorityStyleRules(filter, rule, layerOpacity) {
|
|
1019
|
+
const highwayRules = roadHighwayRules(rule);
|
|
1020
|
+
if (!highwayRules) {
|
|
1021
|
+
return createLayerStyleRules(filter, rule, 'line', 'roads', layerOpacity);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const entries = [];
|
|
1025
|
+
const orderedHighways = orderedRoadHighwayValues(highwayRules);
|
|
1026
|
+
const baseRule = removeStylePropertyRules(rule, 'highway');
|
|
1027
|
+
const fallbackFilter = ['all', filter, ['!', propertyIn('highway', orderedHighways)]];
|
|
1028
|
+
entries.push({
|
|
1029
|
+
filter: fallbackFilter,
|
|
1030
|
+
rule: baseRule
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
for (const highway of orderedHighways) {
|
|
1034
|
+
const override = highwayRules[highway];
|
|
1035
|
+
if (styleOverrideVisible(override) === false) {
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const highwayFilter = roadOverrideZoomFilter(
|
|
1040
|
+
['all', filter, propertyEquals('highway', highway)],
|
|
1041
|
+
override
|
|
1042
|
+
);
|
|
1043
|
+
entries.push({
|
|
1044
|
+
filter: highwayFilter,
|
|
1045
|
+
rule: mergeStyleOverride(baseRule, override)
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
return createPhasedRoadStyleRules(entries, layerOpacity);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
972
1052
|
/**
|
|
973
1053
|
* @param {unknown[]} filter
|
|
974
1054
|
* @param {Record<string, unknown>} rule
|
|
@@ -1225,6 +1305,132 @@ function createLayerStyleRules(filter, rule, layerType, layerId, layerOpacity) {
|
|
|
1225
1305
|
}));
|
|
1226
1306
|
}
|
|
1227
1307
|
|
|
1308
|
+
/**
|
|
1309
|
+
* @param {Array<{ filter: unknown[], rule: Record<string, unknown> }>} entries
|
|
1310
|
+
* @param {Map<string, number>} layerOpacity
|
|
1311
|
+
* @returns {Array<{ filter: unknown[], style: Record<string, unknown> }>}
|
|
1312
|
+
*/
|
|
1313
|
+
function createPhasedRoadStyleRules(entries, layerOpacity) {
|
|
1314
|
+
const phases = [];
|
|
1315
|
+
for (const entry of entries) {
|
|
1316
|
+
const visibleFilter = createPropertyVisibilityFilter(entry.filter, entry.rule);
|
|
1317
|
+
const parts = createLayerStyleParts(entry.rule, 'line', 'roads', layerOpacity);
|
|
1318
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
1319
|
+
if (!phases[index]) {
|
|
1320
|
+
phases[index] = [];
|
|
1321
|
+
}
|
|
1322
|
+
phases[index].push({
|
|
1323
|
+
filter: visibleFilter,
|
|
1324
|
+
style: parts[index]
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
return phases.flat();
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* @param {Record<string, unknown>} rule
|
|
1334
|
+
* @returns {Record<string, unknown> | null}
|
|
1335
|
+
*/
|
|
1336
|
+
function roadHighwayRules(rule) {
|
|
1337
|
+
const byProperty = rule.byProperty && typeof rule.byProperty === 'object'
|
|
1338
|
+
? /** @type {Record<string, unknown>} */ (rule.byProperty)
|
|
1339
|
+
: null;
|
|
1340
|
+
const highway = byProperty?.highway;
|
|
1341
|
+
return highway && typeof highway === 'object'
|
|
1342
|
+
? /** @type {Record<string, unknown>} */ (highway)
|
|
1343
|
+
: null;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* @param {Record<string, unknown>} values
|
|
1348
|
+
* @returns {string[]}
|
|
1349
|
+
*/
|
|
1350
|
+
function orderedRoadHighwayValues(values) {
|
|
1351
|
+
const configured = Object.keys(values);
|
|
1352
|
+
const ordered = ROAD_HIGHWAY_DRAW_ORDER.filter((value) => configured.includes(value));
|
|
1353
|
+
return [
|
|
1354
|
+
...configured.filter((value) => !ROAD_HIGHWAY_DRAW_ORDER.includes(value)),
|
|
1355
|
+
...ordered
|
|
1356
|
+
];
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* @param {Record<string, unknown>} rule
|
|
1361
|
+
* @param {string} property
|
|
1362
|
+
* @returns {Record<string, unknown>}
|
|
1363
|
+
*/
|
|
1364
|
+
function removeStylePropertyRules(rule, property) {
|
|
1365
|
+
const byProperty = rule.byProperty && typeof rule.byProperty === 'object'
|
|
1366
|
+
? { .../** @type {Record<string, unknown>} */ (rule.byProperty) }
|
|
1367
|
+
: null;
|
|
1368
|
+
if (!byProperty || !(property in byProperty)) {
|
|
1369
|
+
return rule;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
delete byProperty[property];
|
|
1373
|
+
const stripped = { ...rule };
|
|
1374
|
+
if (Object.keys(byProperty).length === 0) {
|
|
1375
|
+
delete stripped.byProperty;
|
|
1376
|
+
} else {
|
|
1377
|
+
stripped.byProperty = byProperty;
|
|
1378
|
+
}
|
|
1379
|
+
return stripped;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* @param {Record<string, unknown>} rule
|
|
1384
|
+
* @param {unknown} override
|
|
1385
|
+
* @returns {Record<string, unknown>}
|
|
1386
|
+
*/
|
|
1387
|
+
function mergeStyleOverride(rule, override) {
|
|
1388
|
+
if (!override || typeof override !== 'object') {
|
|
1389
|
+
return rule;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const merged = {
|
|
1393
|
+
...rule,
|
|
1394
|
+
.../** @type {Record<string, unknown>} */ (override)
|
|
1395
|
+
};
|
|
1396
|
+
for (const key of ['body', 'casing', 'center', 'centerLine', 'glow', 'semantics', 'visibility']) {
|
|
1397
|
+
const baseValue = rule[key];
|
|
1398
|
+
const overrideValue = /** @type {Record<string, unknown>} */ (override)[key];
|
|
1399
|
+
if (
|
|
1400
|
+
baseValue && typeof baseValue === 'object' &&
|
|
1401
|
+
overrideValue && typeof overrideValue === 'object'
|
|
1402
|
+
) {
|
|
1403
|
+
merged[key] = {
|
|
1404
|
+
.../** @type {Record<string, unknown>} */ (baseValue),
|
|
1405
|
+
.../** @type {Record<string, unknown>} */ (overrideValue)
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
return normalizeStyleRule(merged);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* @param {unknown[]} filter
|
|
1415
|
+
* @param {unknown} override
|
|
1416
|
+
* @returns {unknown[]}
|
|
1417
|
+
*/
|
|
1418
|
+
function roadOverrideZoomFilter(filter, override) {
|
|
1419
|
+
if (!override || typeof override !== 'object') {
|
|
1420
|
+
return filter;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const normalized = normalizeStyleRule(/** @type {Record<string, unknown>} */ (override));
|
|
1424
|
+
let next = filter;
|
|
1425
|
+
if (Number.isFinite(normalized.minZoom)) {
|
|
1426
|
+
next = ['all', next, ['>=', ['zoom'], Number(normalized.minZoom)]];
|
|
1427
|
+
}
|
|
1428
|
+
if (Number.isFinite(normalized.maxZoom)) {
|
|
1429
|
+
next = ['all', next, ['<=', ['zoom'], Number(normalized.maxZoom)]];
|
|
1430
|
+
}
|
|
1431
|
+
return next;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1228
1434
|
/**
|
|
1229
1435
|
* @param {unknown[]} filter
|
|
1230
1436
|
* @param {Record<string, unknown>} rule
|
|
@@ -1476,24 +1682,6 @@ function getLayerRule(styleDocument, layer) {
|
|
|
1476
1682
|
return normalizeStyleRule(layers[id] || layers[layerAlias(id)] || {});
|
|
1477
1683
|
}
|
|
1478
1684
|
|
|
1479
|
-
/**
|
|
1480
|
-
* @param {string} layerId
|
|
1481
|
-
* @returns {boolean}
|
|
1482
|
-
*/
|
|
1483
|
-
function isAipLayer(layerId) {
|
|
1484
|
-
return layerId === 'aip' || layerId === 'aviation';
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
/**
|
|
1488
|
-
* @param {string} layerId
|
|
1489
|
-
* @returns {string}
|
|
1490
|
-
*/
|
|
1491
|
-
function layerAlias(layerId) {
|
|
1492
|
-
if (layerId === 'aip') return 'aviation';
|
|
1493
|
-
if (layerId === 'aviation') return 'aip';
|
|
1494
|
-
return layerId;
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
1685
|
/**
|
|
1498
1686
|
* Normalize the public declarative style schema to the internal style keys.
|
|
1499
1687
|
*
|
|
@@ -1908,15 +2096,6 @@ function isValidTileCoord(z, x, y) {
|
|
|
1908
2096
|
return Number.isInteger(x) && Number.isInteger(y) && x >= 0 && y >= 0 && x < maxIndex && y < maxIndex;
|
|
1909
2097
|
}
|
|
1910
2098
|
|
|
1911
|
-
/**
|
|
1912
|
-
* @param {Record<string, unknown>} manifest
|
|
1913
|
-
* @returns {{ url?: string, minZoom?: unknown, maxZoom?: unknown }}
|
|
1914
|
-
*/
|
|
1915
|
-
function pmtilesInfo(manifest) {
|
|
1916
|
-
const tiles = manifest.tiles && typeof manifest.tiles === 'object' ? manifest.tiles : {};
|
|
1917
|
-
return /** @type {{ url?: string, minZoom?: unknown, maxZoom?: unknown }} */ (tiles);
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
2099
|
/**
|
|
1921
2100
|
* @param {number} width
|
|
1922
2101
|
* @param {number} height
|
|
@@ -1949,28 +2128,6 @@ function preferNearestZoomLevel(value, high, low) {
|
|
|
1949
2128
|
return value - low * Math.sqrt(high / low);
|
|
1950
2129
|
}
|
|
1951
2130
|
|
|
1952
|
-
/**
|
|
1953
|
-
* @param {unknown} value
|
|
1954
|
-
* @param {number} min
|
|
1955
|
-
* @param {number} max
|
|
1956
|
-
* @returns {number}
|
|
1957
|
-
*/
|
|
1958
|
-
function clampInteger(value, min, max) {
|
|
1959
|
-
const number = Math.trunc(Number(value));
|
|
1960
|
-
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
/**
|
|
1964
|
-
* @param {unknown} value
|
|
1965
|
-
* @param {number} min
|
|
1966
|
-
* @param {number} max
|
|
1967
|
-
* @returns {number}
|
|
1968
|
-
*/
|
|
1969
|
-
function clampNumber(value, min, max) {
|
|
1970
|
-
const number = Number(value);
|
|
1971
|
-
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
2131
|
/**
|
|
1975
2132
|
* @param {number} z
|
|
1976
2133
|
* @param {number} x
|
package/src/labels.js
CHANGED
|
@@ -525,30 +525,38 @@ function aviationLabel(feature, aeroway, zoom) {
|
|
|
525
525
|
const name = cleanText(feature.get('name'));
|
|
526
526
|
|
|
527
527
|
if (aeroway === 'aerodrome' || aeroway === 'heliport') {
|
|
528
|
-
const text = name || aviationRefText(ref) || (aeroway
|
|
529
|
-
return isRenderableAviationText(text, aeroway) && zoom >=
|
|
528
|
+
const text = name || aviationRefText(ref) || aviationFallbackText(aeroway);
|
|
529
|
+
return isRenderableAviationText(text, aeroway) && zoom >= 9
|
|
530
530
|
? { text, placement: 'point', priorityClass: 'critical', zIndex: 980 }
|
|
531
531
|
: null;
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
if (aeroway === 'runway') {
|
|
535
535
|
const text = aviationRefText(ref) || name || 'RWY';
|
|
536
|
-
return isRenderableAviationText(text, aeroway) && zoom >=
|
|
536
|
+
return isRenderableAviationText(text, aeroway) && zoom >= 11 ? { text, placement: 'line', priorityClass: 'important', zIndex: 900 } : null;
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
if (aeroway === 'terminal' || aeroway === 'apron') {
|
|
540
|
-
const text = name || ref;
|
|
541
|
-
return isMeaningfulLabel(text) && zoom >=
|
|
540
|
+
const text = name || ref || aviationFallbackText(aeroway);
|
|
541
|
+
return isMeaningfulLabel(text) && zoom >= 14 ? { text, placement: 'point', priorityClass: 'normal', zIndex: 720 } : null;
|
|
542
542
|
}
|
|
543
543
|
|
|
544
544
|
if (aeroway === 'helipad') {
|
|
545
545
|
const text = aviationRefText(ref) || name || 'H';
|
|
546
|
-
return isRenderableAviationText(text, aeroway) && zoom >=
|
|
546
|
+
return isRenderableAviationText(text, aeroway) && zoom >= 12 ? { text, placement: 'point', priorityClass: 'critical', zIndex: 980 } : null;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
return null;
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
function aviationFallbackText(aeroway) {
|
|
553
|
+
if (aeroway === 'aerodrome') return 'Aerodrome';
|
|
554
|
+
if (aeroway === 'heliport') return 'Heliport';
|
|
555
|
+
if (aeroway === 'terminal') return 'Terminal';
|
|
556
|
+
if (aeroway === 'apron') return 'Apron';
|
|
557
|
+
return cleanText(aeroway).replace(/_/g, ' ');
|
|
558
|
+
}
|
|
559
|
+
|
|
552
560
|
/**
|
|
553
561
|
* @param {{
|
|
554
562
|
* cache: Map<string, Style>,
|
|
@@ -664,7 +672,81 @@ function poiLabelText(feature) {
|
|
|
664
672
|
}
|
|
665
673
|
}
|
|
666
674
|
|
|
667
|
-
return
|
|
675
|
+
return poiFallbackLabel(feature);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function poiFallbackLabel(feature) {
|
|
679
|
+
const candidates = [
|
|
680
|
+
['military', {
|
|
681
|
+
airbase: 'Airbase',
|
|
682
|
+
airfield: 'Airfield',
|
|
683
|
+
barracks: 'Barracks',
|
|
684
|
+
bunker: 'Bunker',
|
|
685
|
+
checkpoint: 'Checkpoint',
|
|
686
|
+
naval_base: 'Naval base',
|
|
687
|
+
range: 'Range',
|
|
688
|
+
training_area: 'Training area',
|
|
689
|
+
danger_area: 'Danger area',
|
|
690
|
+
ammunition_dump: 'Ammunition dump',
|
|
691
|
+
base: 'Base'
|
|
692
|
+
}],
|
|
693
|
+
['emergency', {
|
|
694
|
+
ambulance_station: 'Ambulance station',
|
|
695
|
+
siren: 'Siren',
|
|
696
|
+
assembly_point: 'Assembly point',
|
|
697
|
+
disaster_response: 'Disaster response'
|
|
698
|
+
}],
|
|
699
|
+
['amenity', {
|
|
700
|
+
hospital: 'Hospital',
|
|
701
|
+
police: 'Police',
|
|
702
|
+
fire_station: 'Fire station',
|
|
703
|
+
shelter: 'Shelter',
|
|
704
|
+
bunker: 'Bunker',
|
|
705
|
+
checkpoint: 'Checkpoint',
|
|
706
|
+
depot: 'Depot',
|
|
707
|
+
warehouse: 'Warehouse',
|
|
708
|
+
prison: 'Prison',
|
|
709
|
+
courthouse: 'Courthouse',
|
|
710
|
+
townhall: 'Town hall',
|
|
711
|
+
bus_station: 'Bus station',
|
|
712
|
+
ferry_terminal: 'Ferry terminal'
|
|
713
|
+
}],
|
|
714
|
+
['power', {
|
|
715
|
+
plant: 'Power plant',
|
|
716
|
+
substation: 'Substation',
|
|
717
|
+
generator: 'Generator',
|
|
718
|
+
tower: 'Power tower',
|
|
719
|
+
transformer: 'Transformer',
|
|
720
|
+
line: 'Power line'
|
|
721
|
+
}],
|
|
722
|
+
['man_made', {
|
|
723
|
+
communications_tower: 'Comms tower',
|
|
724
|
+
mast: 'Mast',
|
|
725
|
+
antenna: 'Antenna'
|
|
726
|
+
}],
|
|
727
|
+
['boundary', {
|
|
728
|
+
protected_area: 'Protected area'
|
|
729
|
+
}],
|
|
730
|
+
['landuse', {
|
|
731
|
+
industrial: 'Industrial'
|
|
732
|
+
}],
|
|
733
|
+
['industrial', {
|
|
734
|
+
refinery: 'Refinery',
|
|
735
|
+
depot: 'Depot',
|
|
736
|
+
storage: 'Storage',
|
|
737
|
+
logistics: 'Logistics'
|
|
738
|
+
}]
|
|
739
|
+
];
|
|
740
|
+
|
|
741
|
+
for (const [property, labels] of candidates) {
|
|
742
|
+
const value = cleanText(feature.get(property));
|
|
743
|
+
if (value && labels[value]) {
|
|
744
|
+
return labels[value];
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const category = poiCategoryForFeature(feature);
|
|
749
|
+
return category === 'consumer' ? '' : titleCase(category);
|
|
668
750
|
}
|
|
669
751
|
|
|
670
752
|
/**
|
|
@@ -804,6 +886,12 @@ function cleanText(value) {
|
|
|
804
886
|
return String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
805
887
|
}
|
|
806
888
|
|
|
889
|
+
function titleCase(value) {
|
|
890
|
+
return cleanText(value)
|
|
891
|
+
.replace(/_/g, ' ')
|
|
892
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
893
|
+
}
|
|
894
|
+
|
|
807
895
|
/**
|
|
808
896
|
* @param {string} text
|
|
809
897
|
* @returns {boolean}
|