@map-zero/ol 0.2.0 → 0.3.0
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 +217 -64
- 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.0",
|
|
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.0"
|
|
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,
|
|
@@ -131,7 +163,7 @@ export async function createMapZeroOpenLayersLayers(options) {
|
|
|
131
163
|
applyLayerZIndex(layer, orderedLayers, styleDocument, options.zIndexBase);
|
|
132
164
|
if (labelController) {
|
|
133
165
|
tagOpenLayersLayer(labelController.layer, instanceId, orderedLayers.map((item) => item.id), 'labels');
|
|
134
|
-
labelController.layer.setZIndex(layer.getZIndex() +
|
|
166
|
+
labelController.layer.setZIndex(layer.getZIndex() + 1);
|
|
135
167
|
}
|
|
136
168
|
|
|
137
169
|
const refresh = () => {
|
|
@@ -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) {
|
|
@@ -533,11 +570,7 @@ function createLayerVisibility(orderedLayers, styleDocument, visibleLayers) {
|
|
|
533
570
|
* @param {number | undefined} zIndexBase
|
|
534
571
|
*/
|
|
535
572
|
function applyLayerZIndex(layer, orderedLayers, styleDocument, zIndexBase) {
|
|
536
|
-
|
|
537
|
-
const order = Number(getLayerRule(styleDocument, item).order);
|
|
538
|
-
return Number.isFinite(order) ? Math.max(max, order) : max;
|
|
539
|
-
}, 0);
|
|
540
|
-
layer.setZIndex((Number.isFinite(Number(zIndexBase)) ? Number(zIndexBase) : 0) + maxOrder);
|
|
573
|
+
layer.setZIndex(Number.isFinite(Number(zIndexBase)) ? Number(zIndexBase) : 0);
|
|
541
574
|
}
|
|
542
575
|
|
|
543
576
|
/**
|
|
@@ -968,11 +1001,54 @@ function createWebGlStyles(context) {
|
|
|
968
1001
|
function createRoadStyleRules(filter, rule, layerOpacity) {
|
|
969
1002
|
return [
|
|
970
1003
|
...createRoadSemanticUnderlayRules(filter, rule, layerOpacity),
|
|
971
|
-
...
|
|
1004
|
+
...createRoadPriorityStyleRules(filter, rule, layerOpacity),
|
|
972
1005
|
...createRoadSemanticOverlayRules(filter, rule, layerOpacity)
|
|
973
1006
|
];
|
|
974
1007
|
}
|
|
975
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
|
+
|
|
976
1052
|
/**
|
|
977
1053
|
* @param {unknown[]} filter
|
|
978
1054
|
* @param {Record<string, unknown>} rule
|
|
@@ -1229,6 +1305,132 @@ function createLayerStyleRules(filter, rule, layerType, layerId, layerOpacity) {
|
|
|
1229
1305
|
}));
|
|
1230
1306
|
}
|
|
1231
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
|
+
|
|
1232
1434
|
/**
|
|
1233
1435
|
* @param {unknown[]} filter
|
|
1234
1436
|
* @param {Record<string, unknown>} rule
|
|
@@ -1480,24 +1682,6 @@ function getLayerRule(styleDocument, layer) {
|
|
|
1480
1682
|
return normalizeStyleRule(layers[id] || layers[layerAlias(id)] || {});
|
|
1481
1683
|
}
|
|
1482
1684
|
|
|
1483
|
-
/**
|
|
1484
|
-
* @param {string} layerId
|
|
1485
|
-
* @returns {boolean}
|
|
1486
|
-
*/
|
|
1487
|
-
function isAipLayer(layerId) {
|
|
1488
|
-
return layerId === 'aip' || layerId === 'aviation';
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
* @param {string} layerId
|
|
1493
|
-
* @returns {string}
|
|
1494
|
-
*/
|
|
1495
|
-
function layerAlias(layerId) {
|
|
1496
|
-
if (layerId === 'aip') return 'aviation';
|
|
1497
|
-
if (layerId === 'aviation') return 'aip';
|
|
1498
|
-
return layerId;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
1685
|
/**
|
|
1502
1686
|
* Normalize the public declarative style schema to the internal style keys.
|
|
1503
1687
|
*
|
|
@@ -1912,15 +2096,6 @@ function isValidTileCoord(z, x, y) {
|
|
|
1912
2096
|
return Number.isInteger(x) && Number.isInteger(y) && x >= 0 && y >= 0 && x < maxIndex && y < maxIndex;
|
|
1913
2097
|
}
|
|
1914
2098
|
|
|
1915
|
-
/**
|
|
1916
|
-
* @param {Record<string, unknown>} manifest
|
|
1917
|
-
* @returns {{ url?: string, minZoom?: unknown, maxZoom?: unknown }}
|
|
1918
|
-
*/
|
|
1919
|
-
function pmtilesInfo(manifest) {
|
|
1920
|
-
const tiles = manifest.tiles && typeof manifest.tiles === 'object' ? manifest.tiles : {};
|
|
1921
|
-
return /** @type {{ url?: string, minZoom?: unknown, maxZoom?: unknown }} */ (tiles);
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
2099
|
/**
|
|
1925
2100
|
* @param {number} width
|
|
1926
2101
|
* @param {number} height
|
|
@@ -1953,28 +2128,6 @@ function preferNearestZoomLevel(value, high, low) {
|
|
|
1953
2128
|
return value - low * Math.sqrt(high / low);
|
|
1954
2129
|
}
|
|
1955
2130
|
|
|
1956
|
-
/**
|
|
1957
|
-
* @param {unknown} value
|
|
1958
|
-
* @param {number} min
|
|
1959
|
-
* @param {number} max
|
|
1960
|
-
* @returns {number}
|
|
1961
|
-
*/
|
|
1962
|
-
function clampInteger(value, min, max) {
|
|
1963
|
-
const number = Math.trunc(Number(value));
|
|
1964
|
-
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
/**
|
|
1968
|
-
* @param {unknown} value
|
|
1969
|
-
* @param {number} min
|
|
1970
|
-
* @param {number} max
|
|
1971
|
-
* @returns {number}
|
|
1972
|
-
*/
|
|
1973
|
-
function clampNumber(value, min, max) {
|
|
1974
|
-
const number = Number(value);
|
|
1975
|
-
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
2131
|
/**
|
|
1979
2132
|
* @param {number} z
|
|
1980
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}
|