leaflet-city-mapper 0.0.7 → 0.0.9
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 +2 -2
- package/cucumber.mjs +1 -3
- package/dist/leaflet-city-mapper-src.js +403 -201
- package/dist/leaflet-city-mapper-src.js.map +4 -4
- package/dist/leaflet-city-mapper-worker-src.js +69 -59
- package/dist/leaflet-city-mapper-worker-src.js.map +3 -3
- package/dist/leaflet-city-mapper-worker.js +1 -1
- package/dist/leaflet-city-mapper-worker.js.map +4 -4
- package/dist/leaflet-city-mapper.js +5 -5
- package/dist/leaflet-city-mapper.js.map +4 -4
- package/libs/leaflet-lens.js +44 -27
- package/libs/leaflet.js +4 -4
- package/libs/openlayers-bindings.js +2 -1
- package/libs/openlayers-bindings.min.js +1 -1
- package/libs/openlayers-bindings.min.js.map +3 -3
- package/package.json +5 -3
- package/src/camera/mapCamera.js +7 -7
- package/src/camera/mapTryAnimatedPan.js +4 -4
- package/src/camera/updateMapCamera.js +8 -6
- package/src/controls/cameraControls.js +27 -22
- package/src/core/cameraCircleBounds.js +2 -1
- package/src/core/closestCameraLayerPoint.js +2 -1
- package/src/core/constructLayer.js +9 -0
- package/src/core/extendLeafletClass.js +5 -0
- package/src/core/extendTerrainPixelBounds.js +31 -0
- package/src/core/gridlayerGetTiledPixelBounds.js +2 -27
- package/src/core/layerAddInitHook.js +7 -0
- package/src/core/layerCallInitHooks.js +13 -13
- package/src/core/layerMergeOptions.js +7 -0
- package/src/core/projectNativePath.js +23 -0
- package/src/geometry/circleGeometry.js +4 -3
- package/src/globals/layerControlAddItem.js +3 -0
- package/src/globals/leafletLayer.js +3 -0
- package/src/globals/workerCogSource.js +1 -1
- package/src/gpu/initializeRendererResources.js +10 -12
- package/src/index.js +26 -11
- package/src/layer-controls/layerControlInputId.js +11 -0
- package/src/layer-controls/placeEntry.js +2 -0
- package/src/materials/createProgram.js +7 -6
- package/src/native/gridTerrainElevation.js +7 -11
- package/src/native/nativeTerrainElevation.js +16 -0
- package/src/native/prepareGridTerrainRequests.js +16 -0
- package/src/native/retainGridTerrain.js +10 -0
- package/src/native/syncGridLayer.js +12 -59
- package/src/native/syncGridTile.js +18 -0
- package/src/native/syncTerrainGridTile.js +28 -0
- package/src/rendering/drawGeometry.js +11 -11
- package/src/rendering/resizeRenderer.js +16 -15
- package/src/scene/initializerFor.js +2 -1
- package/src/scene/layerInitialize.js +1 -1
- package/src/shadows/drawShadowMeshes.js +6 -5
- package/src/sources/pumpSourceAreas.js +1 -1
- package/src/sunlight/initSunlight.js +19 -8
- package/src/textures/nativeTextureFor.js +2 -3
- package/src/textures/uploadCanvasTexture.js +11 -2
- package/src/vector-tiles/createVectorLabel.js +22 -0
- package/src/vector-tiles/prepareVectorLabel.js +17 -35
- package/src/vector-tiles/updateVectorLabelStyle.js +8 -0
- package/src/vector-tiles/vectorLabelCells.js +2 -5
- package/src/vector-tiles/vectorLabelIcon.js +1 -1
- package/src/vector-tiles/vectorLabelText.js +10 -0
- package/src/walk/initWalk.js +19 -6
- package/src/weakmaps/pathProjectionByMap.js +1 -0
- package/src/workers/cogWorker.js +4 -5
- package/src/globals/layerCallInitHooks.js +0 -1
|
@@ -1,74 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
14
|
-
const stream =
|
|
15
|
-
|
|
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
|
-
|
|
15
|
+
pending = Boolean(stream);
|
|
30
16
|
continue;
|
|
31
17
|
}
|
|
32
18
|
opacity = (source.options.opacity ?? 1) * (tile._gpuOpacity ?? 1);
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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,
|
|
23
|
-
pass.setVertexBuffer(1,
|
|
24
|
-
if (
|
|
25
|
-
if (
|
|
26
|
-
const instances =
|
|
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
|
|
30
|
-
attribute =
|
|
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 =
|
|
36
|
+
const index = attributes.index;
|
|
37
37
|
if (index) {
|
|
38
38
|
pass.setIndexBuffer(index.buffer, index.indexFormat);
|
|
39
|
-
pass.drawIndexed(
|
|
39
|
+
pass.drawIndexed(drawRange.count, instances?.count ?? 1, drawRange.start, 0, 0);
|
|
40
40
|
} else {
|
|
41
|
-
pass.draw(
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sharedColorTexture.size[0] =
|
|
22
|
-
sharedColorTexture.size[1] =
|
|
23
|
-
sharedColorTexture.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
|
-
|
|
28
|
-
sharedDepthTexture.size[0] =
|
|
29
|
-
sharedDepthTexture.size[1] =
|
|
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
|
-
|
|
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(
|
|
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,
|
|
51
|
+
pass.setVertexBuffer(0, attributes.position.buffer);
|
|
51
52
|
if (instances) pass.setVertexBuffer(1, instances.buffer);
|
|
52
|
-
index =
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!path
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/walk/initWalk.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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();
|
package/src/workers/cogWorker.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
workerCogSource.
|
|
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.
|
|
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 };
|