leaflet-city-mapper 0.0.7 → 0.0.8

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.
Files changed (65) hide show
  1. package/README.md +2 -2
  2. package/cucumber.mjs +1 -3
  3. package/dist/leaflet-city-mapper-src.js +403 -201
  4. package/dist/leaflet-city-mapper-src.js.map +4 -4
  5. package/dist/leaflet-city-mapper-worker-src.js +69 -59
  6. package/dist/leaflet-city-mapper-worker-src.js.map +3 -3
  7. package/dist/leaflet-city-mapper-worker.js +1 -1
  8. package/dist/leaflet-city-mapper-worker.js.map +4 -4
  9. package/dist/leaflet-city-mapper.js +5 -5
  10. package/dist/leaflet-city-mapper.js.map +4 -4
  11. package/libs/leaflet-lens.js +44 -27
  12. package/libs/leaflet.js +4 -4
  13. package/libs/openlayers-bindings.js +2 -1
  14. package/libs/openlayers-bindings.min.js +1 -1
  15. package/libs/openlayers-bindings.min.js.map +3 -3
  16. package/package.json +2 -2
  17. package/src/camera/mapCamera.js +7 -7
  18. package/src/camera/mapTryAnimatedPan.js +4 -4
  19. package/src/camera/updateMapCamera.js +8 -6
  20. package/src/controls/cameraControls.js +27 -22
  21. package/src/core/cameraCircleBounds.js +2 -1
  22. package/src/core/closestCameraLayerPoint.js +2 -1
  23. package/src/core/constructLayer.js +9 -0
  24. package/src/core/extendLeafletClass.js +5 -0
  25. package/src/core/extendTerrainPixelBounds.js +31 -0
  26. package/src/core/gridlayerGetTiledPixelBounds.js +2 -27
  27. package/src/core/layerAddInitHook.js +7 -0
  28. package/src/core/layerCallInitHooks.js +13 -13
  29. package/src/core/layerMergeOptions.js +7 -0
  30. package/src/core/projectNativePath.js +23 -0
  31. package/src/geometry/circleGeometry.js +4 -3
  32. package/src/globals/layerControlAddItem.js +3 -0
  33. package/src/globals/leafletLayer.js +3 -0
  34. package/src/globals/workerCogSource.js +1 -1
  35. package/src/gpu/initializeRendererResources.js +10 -12
  36. package/src/index.js +26 -11
  37. package/src/layer-controls/layerControlInputId.js +11 -0
  38. package/src/layer-controls/placeEntry.js +2 -0
  39. package/src/materials/createProgram.js +7 -6
  40. package/src/native/gridTerrainElevation.js +7 -11
  41. package/src/native/nativeTerrainElevation.js +16 -0
  42. package/src/native/prepareGridTerrainRequests.js +16 -0
  43. package/src/native/retainGridTerrain.js +10 -0
  44. package/src/native/syncGridLayer.js +12 -59
  45. package/src/native/syncGridTile.js +18 -0
  46. package/src/native/syncTerrainGridTile.js +28 -0
  47. package/src/rendering/drawGeometry.js +11 -11
  48. package/src/rendering/resizeRenderer.js +16 -15
  49. package/src/scene/initializerFor.js +2 -1
  50. package/src/scene/layerInitialize.js +1 -1
  51. package/src/shadows/drawShadowMeshes.js +6 -5
  52. package/src/sources/pumpSourceAreas.js +1 -1
  53. package/src/sunlight/initSunlight.js +19 -8
  54. package/src/textures/nativeTextureFor.js +2 -3
  55. package/src/textures/uploadCanvasTexture.js +11 -2
  56. package/src/vector-tiles/createVectorLabel.js +22 -0
  57. package/src/vector-tiles/prepareVectorLabel.js +17 -35
  58. package/src/vector-tiles/updateVectorLabelStyle.js +8 -0
  59. package/src/vector-tiles/vectorLabelCells.js +2 -5
  60. package/src/vector-tiles/vectorLabelIcon.js +1 -1
  61. package/src/vector-tiles/vectorLabelText.js +10 -0
  62. package/src/walk/initWalk.js +19 -6
  63. package/src/weakmaps/pathProjectionByMap.js +1 -0
  64. package/src/workers/cogWorker.js +4 -5
  65. package/src/globals/layerCallInitHooks.js +0 -1
@@ -1,74 +1,27 @@
1
- import { L } from '../globals/L.js';
2
- import { coreWhite } from '../globals/coreWhite.js';
3
- import { streamTerrainByMap } from '../weakmaps/streamTerrainByMap.js';
4
-
5
- import { styleNativeMesh } from './styleNativeMesh.js';
6
- import { quadNativeMesh } from './quadNativeMesh.js';
7
- import { boundsQuad } from '../geometry/boundsQuad.js';
8
- import { loadGridTerrain } from './loadGridTerrain.js';
9
- import { terrainGridMesh } from './terrainGridMesh.js';
1
+ import { prepareGridTerrainRequests } from './prepareGridTerrainRequests.js';
2
+ import { syncGridTile } from './syncGridTile.js';
3
+ import { syncTerrainGridTile } from './syncTerrainGridTile.js';
4
+ import { retainGridTerrain } from './retainGridTerrain.js';
10
5
 
11
6
 
12
7
  export function syncGridLayer(state) {
13
- const { source, textures, meshes } = state;
14
- const stream = streamTerrainByMap.get(state.map);
15
- if (state.terrainStream !== stream) {
16
- state.terrainStream = stream;
17
- state.terrainRequests = stream ? /* alloc */ new Map() : null;
18
- }
19
- let key, tile, opacity, retained, mesh, nw, se, request, pending = false;
20
- if (state.terrainRequests) {
21
- for (key of state.terrainRequests.keys()) {
22
- if (!source._tiles[key]) state.terrainRequests.delete(key);
23
- }
24
- }
8
+ const { source } = state;
9
+ const stream = prepareGridTerrainRequests(state);
10
+ let key, tile, opacity, mesh, pending = false;
25
11
  for (key in source._tiles) {
26
12
  if (!Object.hasOwn(source._tiles, key)) continue;
27
13
  tile = source._tiles[key];
28
14
  if (!tile.loaded || tile.el instanceof HTMLImageElement && !tile.el.naturalWidth) {
29
- if (stream) pending = true;
15
+ pending = Boolean(stream);
30
16
  continue;
31
17
  }
32
18
  opacity = (source.options.opacity ?? 1) * (tile._gpuOpacity ?? 1);
33
- retained = textures.get(key);
34
- mesh = meshes.get(key);
35
- if (state.terrainRequests) {
36
- request = state.terrainRequests.get(key);
37
- if (!request || request.tile !== tile) {
38
- request = /* alloc */ { tile, stream };
39
- state.terrainRequests.set(key, request);
40
- loadGridTerrain(state, key, tile, request);
41
- }
42
- if (!request.tiles) {
43
- pending = true;
44
- if (mesh && retained?.image === tile.el) {
45
- retained.used = true;
46
- styleNativeMesh(state, mesh, coreWhite, opacity);
47
- }
48
- continue;
49
- }
50
- if (mesh && mesh._terrainRequest === request && retained?.image === tile.el &&
51
- retained.url === (tile.el.currentSrc || tile.el.src)) {
52
- retained.used = true;
53
- styleNativeMesh(state, mesh, coreWhite, opacity);
54
- } else mesh = terrainGridMesh(state, key, tile, request, opacity);
55
- } else if (mesh && !mesh._terrainRequest && retained?.image === tile.el && retained.url === (tile.el.currentSrc || tile.el.src)) {
56
- retained.used = true;
57
- styleNativeMesh(state, mesh, coreWhite, opacity);
58
- } else {
59
- [nw, se] = source._tileCoordsToNwSe(tile.coords);
60
- mesh = quadNativeMesh(state, key, tile.el, boundsQuad(state,
61
- /* alloc */ new L.LatLngBounds(nw, se)), opacity);
62
- }
19
+ mesh = state.terrainRequests ? syncTerrainGridTile(state, key, tile, opacity)
20
+ : syncGridTile(state, key, tile, opacity);
21
+ if (state.terrainRequests && !state.terrainRequests.get(key).tiles) pending = true;
63
22
  if (mesh) mesh.renderOrder += tile.coords.z / 100;
64
23
  }
65
24
  if (stream && (pending || !Object.keys(source._tiles).length)) {
66
- for ([key, mesh] of meshes) {
67
- if (source._tiles[key] || !mesh._terrainRequest) continue;
68
- retained = textures.get(key);
69
- if (!retained) continue;
70
- retained.used = true;
71
- mesh._nativeUsed = true;
72
- }
25
+ retainGridTerrain(state);
73
26
  }
74
27
  }
@@ -0,0 +1,18 @@
1
+ import { L } from '../globals/L.js';
2
+ import { coreWhite } from '../globals/coreWhite.js';
3
+ import { styleNativeMesh } from './styleNativeMesh.js';
4
+ import { quadNativeMesh } from './quadNativeMesh.js';
5
+ import { boundsQuad } from '../geometry/boundsQuad.js';
6
+
7
+ export function syncGridTile(state, key, tile, opacity) {
8
+ const retained = state.textures.get(key), mesh = state.meshes.get(key);
9
+ if (mesh && !mesh._terrainRequest && retained?.image === tile.el &&
10
+ retained.url === (tile.el.currentSrc || tile.el.src)) {
11
+ retained.used = true;
12
+ styleNativeMesh(state, mesh, coreWhite, opacity);
13
+ return mesh;
14
+ }
15
+ const [nw, se] = state.source._tileCoordsToNwSe(tile.coords);
16
+ return quadNativeMesh(state, key, tile.el,
17
+ boundsQuad(state, /* alloc */ new L.LatLngBounds(nw, se)), opacity);
18
+ }
@@ -0,0 +1,28 @@
1
+ import { coreWhite } from '../globals/coreWhite.js';
2
+ import { loadGridTerrain } from './loadGridTerrain.js';
3
+ import { styleNativeMesh } from './styleNativeMesh.js';
4
+ import { terrainGridMesh } from './terrainGridMesh.js';
5
+
6
+ export function syncTerrainGridTile(state, key, tile, opacity) {
7
+ const retained = state.textures.get(key), mesh = state.meshes.get(key);
8
+ let request = state.terrainRequests.get(key);
9
+ if (!request || request.tile !== tile) {
10
+ request = /* alloc */ { tile, stream: state.terrainStream };
11
+ state.terrainRequests.set(key, request);
12
+ loadGridTerrain(state, key, tile, request);
13
+ }
14
+ if (!request.tiles) {
15
+ if (mesh && retained?.image === tile.el) {
16
+ retained.used = true;
17
+ styleNativeMesh(state, mesh, coreWhite, opacity);
18
+ }
19
+ return null;
20
+ }
21
+ if (mesh && mesh._terrainRequest === request && retained?.image === tile.el &&
22
+ retained.url === (tile.el.currentSrc || tile.el.src)) {
23
+ retained.used = true;
24
+ styleNativeMesh(state, mesh, coreWhite, opacity);
25
+ return mesh;
26
+ }
27
+ return terrainGridMesh(state, key, tile, request, opacity);
28
+ }
@@ -7,7 +7,7 @@ import { prepareMeshTextures } from '../textures/prepareMeshTextures.js';
7
7
 
8
8
  /** Upload retained CPU data and draw one mesh in the active WebGPU render pass. */
9
9
  export function drawGeometry(layer, mesh, pass, cullMode, frontFace) {
10
- const { geometry } = mesh;
10
+ const { geometry } = mesh, { attributes, drawRange } = geometry;
11
11
  const renderer = rendererByLayer.get(layer);
12
12
  const { device } = renderer;
13
13
  const pipeline = drawPipeline(renderer, mesh, cullMode, frontFace);
@@ -19,25 +19,25 @@ export function drawGeometry(layer, mesh, pass, cullMode, frontFace) {
19
19
  meshOffsets
20
20
  ));
21
21
 
22
- pass.setVertexBuffer(0, geometry.attributes.position.buffer);
23
- pass.setVertexBuffer(1, geometry.attributes.normal.buffer);
24
- if (geometry.attributes.color) pass.setVertexBuffer(2, geometry.attributes.color.buffer);
25
- if (geometry.attributes.uv) pass.setVertexBuffer(3, geometry.attributes.uv.buffer);
26
- const instances = geometry.attributes.instanceOffset;
22
+ pass.setVertexBuffer(0, attributes.position.buffer);
23
+ pass.setVertexBuffer(1, attributes.normal.buffer);
24
+ if (attributes.color) pass.setVertexBuffer(2, attributes.color.buffer);
25
+ if (attributes.uv) pass.setVertexBuffer(3, attributes.uv.buffer);
26
+ const instances = attributes.instanceOffset;
27
27
  if (instances) pass.setVertexBuffer(4, instances.buffer);
28
28
  let name, attribute;
29
- for (name in geometry.attributes) {
30
- attribute = geometry.attributes[name];
29
+ for (name in attributes) {
30
+ attribute = attributes[name];
31
31
  if (!attribute.needsUpdate) continue;
32
32
  device.queue.writeBuffer(attribute.buffer, 0, attribute.data);
33
33
  attribute.needsUpdate = false;
34
34
  if (name === 'position' || name === 'instanceOffset') geometry.bounds = null;
35
35
  }
36
- const index = geometry.attributes.index;
36
+ const index = attributes.index;
37
37
  if (index) {
38
38
  pass.setIndexBuffer(index.buffer, index.indexFormat);
39
- pass.drawIndexed(geometry.drawRange.count, instances?.count ?? 1, geometry.drawRange.start, 0, 0);
39
+ pass.drawIndexed(drawRange.count, instances?.count ?? 1, drawRange.start, 0, 0);
40
40
  } else {
41
- pass.draw(geometry.drawRange.count, instances?.count ?? 1, geometry.drawRange.start, 0);
41
+ pass.draw(drawRange.count, instances?.count ?? 1, drawRange.start, 0);
42
42
  }
43
43
  }
@@ -8,25 +8,26 @@ import { colorTexture as sharedColorTexture } from '../globals/colorTexture.js';
8
8
  import { destroyTexture } from '../textures/destroyTexture.js';
9
9
 
10
10
  export function resizeRenderer(layer, size) {
11
+ const renderer = rendererByLayer.get(layer);
11
12
  if (!sizeByLayer.get(layer) || !sizeByLayer.get(layer).equals(size)) {
12
- rendererByLayer.get(layer).width = size.x;
13
- rendererByLayer.get(layer).height = size.y;
14
- rendererByLayer.get(layer).canvas.width = Math.round(size.x * rendererByLayer.get(layer).dpr);
15
- rendererByLayer.get(layer).canvas.height = Math.round(size.y * rendererByLayer.get(layer).dpr);
16
- rendererByLayer.get(layer).canvas.style.width = `${size.x}px`;
17
- rendererByLayer.get(layer).canvas.style.height = `${size.y}px`;
18
- if (rendererByLayer.get(layer).depthTexture) destroyTexture(layer, rendererByLayer.get(layer).depthTexture);
19
- rendererByLayer.get(layer).colorTexture?.destroy();
20
- rendererByLayer.get(layer).colorTexture = rendererByLayer.get(layer).device.createTexture((
21
- sharedColorTexture.size[0] = rendererByLayer.get(layer).canvas.width,
22
- sharedColorTexture.size[1] = rendererByLayer.get(layer).canvas.height,
23
- sharedColorTexture.format = rendererByLayer.get(layer).format,
13
+ renderer.width = size.x;
14
+ renderer.height = size.y;
15
+ renderer.canvas.width = Math.round(size.x * renderer.dpr);
16
+ renderer.canvas.height = Math.round(size.y * renderer.dpr);
17
+ renderer.canvas.style.width = `${size.x}px`;
18
+ renderer.canvas.style.height = `${size.y}px`;
19
+ if (renderer.depthTexture) destroyTexture(layer, renderer.depthTexture);
20
+ renderer.colorTexture?.destroy();
21
+ renderer.colorTexture = renderer.device.createTexture((
22
+ sharedColorTexture.size[0] = renderer.canvas.width,
23
+ sharedColorTexture.size[1] = renderer.canvas.height,
24
+ sharedColorTexture.format = renderer.format,
24
25
  sharedColorTexture.usage = GPUTextureUsage.RENDER_ATTACHMENT,
25
26
  sharedColorTexture
26
27
  ));
27
- rendererByLayer.get(layer).depthTexture = rendererByLayer.get(layer).device.createTexture((
28
- sharedDepthTexture.size[0] = rendererByLayer.get(layer).canvas.width,
29
- sharedDepthTexture.size[1] = rendererByLayer.get(layer).canvas.height,
28
+ renderer.depthTexture = renderer.device.createTexture((
29
+ sharedDepthTexture.size[0] = renderer.canvas.width,
30
+ sharedDepthTexture.size[1] = renderer.canvas.height,
30
31
  sharedDepthTexture.usage = GPUTextureUsage.RENDER_ATTACHMENT,
31
32
  sharedDepthTexture
32
33
  ));
@@ -2,8 +2,9 @@ import { initRaster } from '../raster/initRaster.js';
2
2
  import { initTerrain } from '../terrain/initTerrain.js';
3
3
  import { initTraffic } from '../traffic/initTraffic.js';
4
4
  import { initCog } from '../cog/initCog.js';
5
+ import { sceneEmptyOptions } from '../globals/sceneEmptyOptions.js';
5
6
 
6
- export function initializerFor(type, options = {}) {
7
+ export function initializerFor(type, options = sceneEmptyOptions) {
7
8
  switch (type) {
8
9
  case 'raster': return options.asset !== undefined || /\.tiff?(?:[?#]|$)/i.test(options.url)
9
10
  ? initCog : initRaster;
@@ -36,7 +36,7 @@ export function layerInitialize(options = sceneEmptyOptions) {
36
36
  if (type === 'vector') {
37
37
  if (isVectorTileSource(settings)) {
38
38
  initVectorTiles(this, settings);
39
- } else if (settings.data !== undefined ||
39
+ } else if (settings.data !== undefined || settings.style !== undefined && !settings.url ||
40
40
  settings.url && !/\.(?:pmtiles|glb|gltf)(?:[?#]|$)/i.test(settings.url)) {
41
41
  initializeGeoJSON(this, settings);
42
42
  } else {
@@ -15,9 +15,10 @@ export function drawShadowMeshes(layer, encoder, meshes) {
15
15
  let pipeline = sun.pipeline;
16
16
  pass.setPipeline(pipeline);
17
17
  pass.setBindGroup(1, sun.depthBindGroup);
18
- let mesh, instances, nextPipeline, offset, attribute, index, range;
18
+ let mesh, attributes, instances, nextPipeline, offset, attribute, index, range;
19
19
  for (mesh of meshes) {
20
- instances = mesh.geometry.attributes.instanceOffset;
20
+ attributes = mesh.geometry.attributes;
21
+ instances = attributes.instanceOffset;
21
22
  nextPipeline = instances ? sun.instancePipeline : sun.pipeline;
22
23
  if (nextPipeline !== pipeline) {
23
24
  pipeline = nextPipeline;
@@ -36,7 +37,7 @@ export function drawShadowMeshes(layer, encoder, meshes) {
36
37
  shadowBindGroup.layout = null;
37
38
  shadowBindGroup.entries[0].resource.buffer = null;
38
39
  }
39
- for (attribute of Object.values(mesh.geometry.attributes)) {
40
+ for (attribute of Object.values(attributes)) {
40
41
  if (attribute.needsUpdate) {
41
42
  device.queue.writeBuffer(attribute.buffer, 0, attribute.data);
42
43
  attribute.needsUpdate = false;
@@ -47,9 +48,9 @@ export function drawShadowMeshes(layer, encoder, meshes) {
47
48
  shadowOffsets
48
49
  ));
49
50
 
50
- pass.setVertexBuffer(0, mesh.geometry.attributes.position.buffer);
51
+ pass.setVertexBuffer(0, attributes.position.buffer);
51
52
  if (instances) pass.setVertexBuffer(1, instances.buffer);
52
- index = mesh.geometry.attributes.index, range = mesh.geometry.drawRange;
53
+ index = attributes.index, range = mesh.geometry.drawRange;
53
54
  if (index) {
54
55
  pass.setIndexBuffer(index.buffer, index.indexFormat);
55
56
  pass.drawIndexed(range.count, instances?.count ?? 1, range.start, 0, 0);
@@ -16,7 +16,7 @@ import { pendingSourceAreas } from './pendingSourceAreas.js';
16
16
  /** Share four load/build slots across source layers, nearest ground blocks first. */
17
17
  export function pump(layer, map = layer._map) {
18
18
  const shared = map && renderers.get(map);
19
- if (!shared) return;
19
+ if (!shared || !layersByLayer.has(shared)) return;
20
20
  const layers = /* alloc */ [];
21
21
  let sourceLayer, area, areaBackend;
22
22
  for (sourceLayer of layersByLayer.get(shared)) {
@@ -6,12 +6,23 @@ import { onAdd } from './onAddTimeControl.js';
6
6
  import { addTo } from './addTimeControlToMap.js';
7
7
  import { remove } from './removeTimeControl.js';
8
8
  import { onRemoveSun } from './onRemoveSun.js';
9
+ import { extendLeafletClass } from '../core/extendLeafletClass.js';
9
10
 
10
- L.Control.Time = L.Control.extend({
11
- options: { position: 'bottomright' },
12
- initialize,
13
- onAdd,
14
- onRemove: onRemoveSun,
15
- addTo,
16
- remove,
17
- });
11
+ class TimeControl extends extendLeafletClass(L.Control) {
12
+ static initHooks = true;
13
+ static defaultOptions = { position: 'bottomright' };
14
+
15
+ constructor(options) {
16
+ super(options);
17
+ if (L.version.startsWith('2.')) {
18
+ initialize.call(this, options);
19
+ this.callInitHooks();
20
+ }
21
+ }
22
+ }
23
+ Object.assign(TimeControl.prototype, { onAdd, onRemove: onRemoveSun, addTo, remove });
24
+ if (L.version.startsWith('1.')) {
25
+ TimeControl.prototype.options = Object.assign(Object.create(L.Control.prototype.options), TimeControl.defaultOptions);
26
+ TimeControl.prototype.initialize = initialize;
27
+ }
28
+ L.Control.Time = TimeControl;
@@ -17,8 +17,7 @@ export function textureFor(state, key, image) {
17
17
  const url = image.currentSrc || image.src;
18
18
  if (!texture || texture.image !== image || texture.url !== url) {
19
19
  if (texture) {
20
- const mesh = meshes.get(key);
21
- if (mesh) disposeMesh(layer, mesh);
20
+ disposeMesh(layer, meshes.get(key));
22
21
  meshes.delete(key);
23
22
  destroyTexture(layer, texture.texture);
24
23
  textures.delete(key);
@@ -35,7 +34,7 @@ export function textureFor(state, key, image) {
35
34
  sampler: device.createSampler(coreSampler),
36
35
  };
37
36
  textures.set(key, texture);
38
- if (image instanceof HTMLCanvasElement) {
37
+ if (image instanceof HTMLCanvasElement || url.startsWith('data:image/svg+xml')) {
39
38
  texture.pending = true;
40
39
  uploadCanvasTexture(state, key, texture, image, device, width, height);
41
40
  } else {
@@ -2,9 +2,18 @@ import { current } from '../native/isCurrentNativeLayer.js';
2
2
  import { refreshNativeLayer } from '../native/refreshNativeLayer.js';
3
3
 
4
4
  export async function uploadCanvasTexture(state, key, texture, image, device, width, height) {
5
- let bitmap;
5
+ let bitmap, source = image;
6
6
  try {
7
- bitmap = await createImageBitmap(image);
7
+ if (image instanceof HTMLImageElement) {
8
+ const canvas = document.createElement('canvas');
9
+ canvas.width = width;
10
+ canvas.height = height;
11
+ const context = canvas.getContext('2d');
12
+ if (!context) throw /* alloc */ new Error('Unable to rasterize SVG image');
13
+ context.drawImage(image, 0, 0, width, height);
14
+ source = canvas;
15
+ }
16
+ bitmap = await createImageBitmap(source);
8
17
  if (!current(state) || state.textures.get(key) !== texture) return;
9
18
  device.queue.copyExternalImageToTexture(
10
19
  /* alloc */ { source: bitmap },
@@ -0,0 +1,22 @@
1
+ import { evaluateStyle } from '../vector-style/evaluateStyle.js';
2
+ import { projectVectorPoint } from './projectVectorPoint.js';
3
+ import { vectorStyleValue } from './vectorStyleValue.js';
4
+
5
+ export function createVectorLabel(job, layer, entry, properties, order, text, icon, anchor, size) {
6
+ return /* alloc */ {
7
+ text, latlng: projectVectorPoint(anchor.point, job.coords, layer.extent),
8
+ order, entry, properties, size, pixelBase: anchor.length * 256 / layer.extent / 2 ** job.coords.z,
9
+ along: anchor.a ? /* alloc */ [
10
+ projectVectorPoint(anchor.a, job.coords, layer.extent),
11
+ projectVectorPoint(anchor.b, job.coords, layer.extent),
12
+ ] : null,
13
+ icon: text.trim() ? null : icon,
14
+ iconOffset: evaluateStyle(entry.layout?.['icon-offset'], properties, job.zoom) || /* alloc */ [0, 0],
15
+ allowOverlap: entry.layout?.['icon-allow-overlap'] === true,
16
+ color: vectorStyleValue(entry.paint, 'text-color', '#000', properties, job.zoom),
17
+ halo: vectorStyleValue(entry.paint, 'text-halo-color', '#fff', properties, job.zoom),
18
+ haloWidth: vectorStyleValue(entry.paint, 'text-halo-width', 0, properties, job.zoom),
19
+ priority: /* alloc */ ['place_labels', 'place'].includes(entry['source-layer']) ? 0
20
+ : /* alloc */ ['street_labels', 'transportation_name'].includes(entry['source-layer']) ? 1 : 2,
21
+ };
22
+ }
@@ -1,43 +1,25 @@
1
1
  import { evaluateStyle } from '../vector-style/evaluateStyle.js';
2
- import { projectVectorPoint } from './projectVectorPoint.js';
3
- import { vectorLabelProperty } from './vectorLabelProperty.js';
4
2
  import { vectorStyleValue } from './vectorStyleValue.js';
5
- import { vectorLabelAnchors } from './vectorLabelAnchors.js';
3
+ import { vectorLabelAnchors } from './vectorLabelAnchors.js';
4
+ import { vectorLabelText } from './vectorLabelText.js';
5
+ import { createVectorLabel } from './createVectorLabel.js';
6
6
 
7
7
  export function prepareVectorLabel(job, layer, entry, feature, order) {
8
- let text = entry.layout?.['text-field'];
9
8
  const { properties } = feature, { zoom } = job;
9
+ const text = vectorLabelText(entry, properties, zoom);
10
10
  const icon = entry.layout?.['symbol-placement'] === 'line' ? null
11
11
  : evaluateStyle(entry.layout?.['icon-image'], properties, zoom);
12
- if (Array.isArray(text)) text = evaluateStyle(text, properties, zoom);
13
- if (typeof text !== 'string') text = '';
14
- text = text.replace(/\{([^}]+)\}/g, vectorLabelProperty.bind(null, properties));
15
12
  if (!text.trim() && typeof icon !== 'string') return;
16
- if (entry.layout?.['text-transform'] === 'uppercase') text = text.toUpperCase();
17
- const size = vectorStyleValue(entry.layout, 'text-size', 12, properties, zoom), line = feature.type === 2 && text.trim();
18
- const spacing = layer.extent / 16, anchors = /* alloc */ [];
19
- let path, anchor;
20
- for (path of line ? feature.geometry : feature.geometry.slice(0, 1)) {
21
- if (!path?.length) continue;
22
- if (!line) anchors.push(/* alloc */ { point: path[Math.floor(path.length / 2)], a: null });
23
- else for (anchor of vectorLabelAnchors(path, spacing)) anchors.push(anchor);
24
- }
25
- for (anchor of anchors) {
26
- if (anchor.point[0] < 0 || anchor.point[0] >= layer.extent || anchor.point[1] < 0 || anchor.point[1] >= layer.extent) continue;
27
- job.labels.push(/* alloc */ { text, latlng: projectVectorPoint(anchor.point, job.coords, layer.extent),
28
- order, entry, properties, size, pixelBase: anchor.length * 256 / layer.extent / 2 ** job.coords.z,
29
- along: anchor.a ? /* alloc */ [
30
- projectVectorPoint(anchor.a, job.coords, layer.extent),
31
- projectVectorPoint(anchor.b, job.coords, layer.extent),
32
- ] : null,
33
- icon: text.trim() ? null : icon,
34
- iconOffset: evaluateStyle(entry.layout?.['icon-offset'], properties, zoom) || /* alloc */ [0, 0],
35
- allowOverlap: entry.layout?.['icon-allow-overlap'] === true,
36
- color: vectorStyleValue(entry.paint, 'text-color', '#000', properties, zoom),
37
- halo: vectorStyleValue(entry.paint, 'text-halo-color', '#fff', properties, zoom),
38
- haloWidth: vectorStyleValue(entry.paint, 'text-halo-width', 0, properties, zoom),
39
- priority: /* alloc */ ['place_labels', 'place'].includes(entry['source-layer']) ? 0
40
- : /* alloc */ ['street_labels', 'transportation_name'].includes(entry['source-layer']) ? 1 : 2,
41
- });
42
- }
43
- }
13
+ const size = vectorStyleValue(entry.layout, 'text-size', 12, properties, zoom), line = feature.type === 2 && text.trim();
14
+ const spacing = layer.extent / 16, anchors = /* alloc */ [];
15
+ let path, anchor;
16
+ for (path of line ? feature.geometry : feature.geometry.slice(0, 1)) {
17
+ if (!path?.length) continue;
18
+ if (!line) anchors.push(/* alloc */ { point: path[Math.floor(path.length / 2)], a: null });
19
+ else for (anchor of vectorLabelAnchors(path, spacing)) anchors.push(anchor);
20
+ }
21
+ for (anchor of anchors) {
22
+ if (anchor.point[0] < 0 || anchor.point[0] >= layer.extent || anchor.point[1] < 0 || anchor.point[1] >= layer.extent) continue;
23
+ job.labels.push(createVectorLabel(job, layer, entry, properties, order, text, icon, anchor, size));
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ import { evaluateStyle } from '../vector-style/evaluateStyle.js';
2
+
3
+ export function updateVectorLabelStyle(label, zoom) {
4
+ label.size = evaluateStyle(label.entry.layout?.['text-size'] ?? 12, label.properties, zoom);
5
+ label.color = evaluateStyle(label.entry.paint?.['text-color'] ?? '#000', label.properties, zoom);
6
+ label.halo = evaluateStyle(label.entry.paint?.['text-halo-color'] ?? '#fff', label.properties, zoom);
7
+ label.haloWidth = evaluateStyle(label.entry.paint?.['text-halo-width'] ?? 0, label.properties, zoom);
8
+ }
@@ -1,17 +1,14 @@
1
- import { evaluateStyle } from '../vector-style/evaluateStyle.js';
2
1
  import { vectorStyleZoom } from './vectorStyleZoom.js';
3
2
  import { vectorTileOptionsByLayer } from '../weakmaps/vectorTileOptionsByLayer.js';
4
3
  import { visibleStyle } from '../vector-style/visibleStyle.js';
4
+ import { updateVectorLabelStyle } from './updateVectorLabelStyle.js';
5
5
 
6
6
  export function vectorLabelCells(layer, label, size) {
7
7
  const map = layer._map, zoom = vectorStyleZoom(layer);
8
8
  if (!visibleStyle(label.entry, label.properties, zoom)) return null;
9
- label.size = evaluateStyle(label.entry.layout?.['text-size'] ?? 12, label.properties, zoom);
9
+ updateVectorLabelStyle(label, zoom);
10
10
  if (!(label.size > 0)) return null;
11
11
  if (label.icon && !vectorTileOptionsByLayer.get(layer).spriteSheet?.index[label.icon]) return null;
12
- label.color = evaluateStyle(label.entry.paint?.['text-color'] ?? '#000', label.properties, zoom);
13
- label.halo = evaluateStyle(label.entry.paint?.['text-halo-color'] ?? '#fff', label.properties, zoom);
14
- label.haloWidth = evaluateStyle(label.entry.paint?.['text-halo-width'] ?? 0, label.properties, zoom);
15
12
  if (label.along && label.pixelBase * 2 ** map.getZoom() < label.text.length * label.size * 0.6) return null;
16
13
  const point = map.latLngToContainerPoint(label.latlng);
17
14
  if (point.x < 0 || point.y < 0 || point.x > size.x || point.y > size.y) return null;
@@ -5,7 +5,7 @@ import { wrapVectorLabel } from './wrapVectorLabel.js';
5
5
  export function vectorLabelIcon(label, sheet) {
6
6
  if (label.icon) return vectorSpriteIcon(label, sheet);
7
7
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
8
- const lines = label.along ? [label.text] : wrapVectorLabel(label.text);
8
+ const lines = label.along ? /* alloc */ [label.text] : wrapVectorLabel(label.text);
9
9
  let longest = 0, line, key, text, index;
10
10
  for (line of lines) longest = Math.max(longest, Array.from(line).length);
11
11
  const width = Math.max(40, longest * label.size * 0.65 + 12);
@@ -0,0 +1,10 @@
1
+ import { evaluateStyle } from '../vector-style/evaluateStyle.js';
2
+ import { vectorLabelProperty } from './vectorLabelProperty.js';
3
+
4
+ export function vectorLabelText(entry, properties, zoom) {
5
+ let text = entry.layout?.['text-field'];
6
+ if (Array.isArray(text)) text = evaluateStyle(text, properties, zoom);
7
+ if (typeof text !== 'string') text = '';
8
+ text = text.replace(/\{([^}]+)\}/g, vectorLabelProperty.bind(null, properties));
9
+ return entry.layout?.['text-transform'] === 'uppercase' ? text.toUpperCase() : text;
10
+ }
@@ -2,6 +2,7 @@ import { L } from '../globals/L.js';
2
2
  import { initialize } from './initializeWalkControl.js';
3
3
  import { onAdd } from './onAddWalkControl.js';
4
4
  import { onRemove } from './onRemoveWalkControl.js';
5
+ import { extendLeafletClass } from '../core/extendLeafletClass.js';
5
6
 
6
7
  /**
7
8
  * Third-person walk mode, toggled by a map button (and Esc): a pedestrian at the map centre runs with W/S (or ↑/↓) and
@@ -11,9 +12,21 @@ import { onRemove } from './onRemoveWalkControl.js';
11
12
  * Options: `speed` (m/s, default 70), `distance` (camera distance in metres, default 20), `position` (button corner, default 'bottomright').
12
13
  * Buildings, trees, models, streetlights and traffic block movement; the walker is moved out of newly loaded obstacles.
13
14
  */
14
- L.Control.Walk = L.Control.extend({
15
- options: { position: 'bottomright', speed: 70, distance: 20 },
16
- initialize,
17
- onAdd,
18
- onRemove,
19
- });
15
+ class WalkControl extends extendLeafletClass(L.Control) {
16
+ static initHooks = true;
17
+ static defaultOptions = { position: 'bottomright', speed: 70, distance: 20 };
18
+
19
+ constructor(options) {
20
+ super(options);
21
+ if (L.version.startsWith('2.')) {
22
+ initialize.call(this, options);
23
+ this.callInitHooks();
24
+ }
25
+ }
26
+ }
27
+ Object.assign(WalkControl.prototype, { onAdd, onRemove });
28
+ if (L.version.startsWith('1.')) {
29
+ WalkControl.prototype.options = Object.assign(Object.create(L.Control.prototype.options), WalkControl.defaultOptions);
30
+ WalkControl.prototype.initialize = initialize;
31
+ }
32
+ L.Control.Walk = WalkControl;
@@ -0,0 +1 @@
1
+ export const pathProjectionByMap = new WeakMap();
@@ -4,12 +4,11 @@ import { workerCogSource } from '../globals/workerCogSource.js';
4
4
  export async function onCogMessage({ data }) {
5
5
  try {
6
6
  const key = JSON.stringify(/* alloc */ [data.url, data.asset]);
7
- // ponytail: one cached COG per worker; use a bounded per-source cache if interleaved archives cause churn.
8
- if (workerCogSource.key !== key) {
9
- workerCogSource.key = key;
10
- workerCogSource.archive = openCog(data.url, data.asset);
7
+ if (!workerCogSource.has(key)) {
8
+ workerCogSource.set(key, openCog(data.url, data.asset));
9
+ if (workerCogSource.size > 4) workerCogSource.delete(workerCogSource.keys().next().value);
11
10
  }
12
- const archive = await workerCogSource.archive;
11
+ const archive = await workerCogSource.get(key);
13
12
  const result = data.z === undefined ? await archive.getHeader() : await archive.getZxy(data.z, data.x, data.y);
14
13
  const transfers = /* alloc */ [];
15
14
  if (result?.data) transfers.push(result.data);
@@ -1 +0,0 @@
1
- export const layerCallInitHooks = { value: undefined };