leaflet-city-mapper 0.0.0-stage → 0.0.2
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/LICENSE +674 -0
- package/README.md +78 -2
- package/cucumber.mjs +9 -0
- package/dist/leaflet-city-mapper.js +2 -0
- package/dist/leaflet-city-mapper.js.map +7 -0
- package/dist/utils/CELL.js +2 -0
- package/dist/utils/CELL.js.map +7 -0
- package/dist/utils/addBridges.js +2 -0
- package/dist/utils/addBridges.js.map +7 -0
- package/dist/utils/addRasterTile.js +2 -0
- package/dist/utils/addRasterTile.js.map +7 -0
- package/dist/utils/along.js +2 -0
- package/dist/utils/along.js.map +7 -0
- package/dist/utils/atmosphere.js +2 -0
- package/dist/utils/atmosphere.js.map +7 -0
- package/dist/utils/attach.js +2 -0
- package/dist/utils/attach.js.map +7 -0
- package/dist/utils/bindSunlight.js +2 -0
- package/dist/utils/bindSunlight.js.map +7 -0
- package/dist/utils/box.js +2 -0
- package/dist/utils/box.js.map +7 -0
- package/dist/utils/bridgeElevation.js +2 -0
- package/dist/utils/bridgeElevation.js.map +7 -0
- package/dist/utils/build.js +2 -0
- package/dist/utils/build.js.map +7 -0
- package/dist/utils/cameraControls.js +2 -0
- package/dist/utils/cameraControls.js.map +7 -0
- package/dist/utils/channel.js +2 -0
- package/dist/utils/channel.js.map +7 -0
- package/dist/utils/collider.js +2 -0
- package/dist/utils/collider.js.map +7 -0
- package/dist/utils/colliders.js +2 -0
- package/dist/utils/colliders.js.map +7 -0
- package/dist/utils/computeBounds.js +2 -0
- package/dist/utils/computeBounds.js.map +7 -0
- package/dist/utils/conformModelToTerrain.js +2 -0
- package/dist/utils/conformModelToTerrain.js.map +7 -0
- package/dist/utils/createMesh.js +2 -0
- package/dist/utils/createMesh.js.map +7 -0
- package/dist/utils/createProgram.js +3 -0
- package/dist/utils/createProgram.js.map +7 -0
- package/dist/utils/decodeDirectory.js +2 -0
- package/dist/utils/decodeDirectory.js.map +7 -0
- package/dist/utils/decodeHeader.js +2 -0
- package/dist/utils/decodeHeader.js.map +7 -0
- package/dist/utils/decodeModel.js +2 -0
- package/dist/utils/decodeModel.js.map +7 -0
- package/dist/utils/decompress.js +2 -0
- package/dist/utils/decompress.js.map +7 -0
- package/dist/utils/destroyTexture.js +2 -0
- package/dist/utils/destroyTexture.js.map +7 -0
- package/dist/utils/disposeMesh.js +2 -0
- package/dist/utils/disposeMesh.js.map +7 -0
- package/dist/utils/drawGeometry.js +2 -0
- package/dist/utils/drawGeometry.js.map +7 -0
- package/dist/utils/elevationWGSL.js +24 -0
- package/dist/utils/elevationWGSL.js.map +7 -0
- package/dist/utils/emptyNode.js +2 -0
- package/dist/utils/emptyNode.js.map +7 -0
- package/dist/utils/endZoom.js +2 -0
- package/dist/utils/endZoom.js.map +7 -0
- package/dist/utils/findFeature.js +2 -0
- package/dist/utils/findFeature.js.map +7 -0
- package/dist/utils/geometry.js +1 -0
- package/dist/utils/geometry.js.map +7 -0
- package/dist/utils/getElevation.js +2 -0
- package/dist/utils/getElevation.js.map +7 -0
- package/dist/utils/getHeader.js +2 -0
- package/dist/utils/getHeader.js.map +7 -0
- package/dist/utils/getZxy.js +2 -0
- package/dist/utils/getZxy.js.map +7 -0
- package/dist/utils/glass.js +2 -0
- package/dist/utils/glass.js.map +7 -0
- package/dist/utils/headlight.js +2 -0
- package/dist/utils/headlight.js.map +7 -0
- package/dist/utils/hit.js +2 -0
- package/dist/utils/hit.js.map +7 -0
- package/dist/utils/horizontalPosition.js +2 -0
- package/dist/utils/horizontalPosition.js.map +7 -0
- package/dist/utils/horizontalRay.js +2 -0
- package/dist/utils/horizontalRay.js.map +7 -0
- package/dist/utils/hull.js +2 -0
- package/dist/utils/hull.js.map +7 -0
- package/dist/utils/initSunlight.js +2 -0
- package/dist/utils/initSunlight.js.map +7 -0
- package/dist/utils/initTerrain.js +2 -0
- package/dist/utils/initTerrain.js.map +7 -0
- package/dist/utils/initTraffic.js +40 -0
- package/dist/utils/initTraffic.js.map +7 -0
- package/dist/utils/initWalk.js +2 -0
- package/dist/utils/initWalk.js.map +7 -0
- package/dist/utils/intersectMeshes.js +2 -0
- package/dist/utils/intersectMeshes.js.map +7 -0
- package/dist/utils/isClockWise.js +2 -0
- package/dist/utils/isClockWise.js.map +7 -0
- package/dist/utils/lampPostGeometry.js +2 -0
- package/dist/utils/lampPostGeometry.js.map +7 -0
- package/dist/utils/lightGeometry.js +2 -0
- package/dist/utils/lightGeometry.js.map +7 -0
- package/dist/utils/lightGrid.js +2 -0
- package/dist/utils/lightGrid.js.map +7 -0
- package/dist/utils/limbs.js +2 -0
- package/dist/utils/limbs.js.map +7 -0
- package/dist/utils/loadModel.js +2 -0
- package/dist/utils/loadModel.js.map +7 -0
- package/dist/utils/loadSource.js +2 -0
- package/dist/utils/loadSource.js.map +7 -0
- package/dist/utils/loadTerrain.js +2 -0
- package/dist/utils/loadTerrain.js.map +7 -0
- package/dist/utils/mapCamera.js +2 -0
- package/dist/utils/mapCamera.js.map +7 -0
- package/dist/utils/materialShaders.js +211 -0
- package/dist/utils/materialShaders.js.map +7 -0
- package/dist/utils/mirror.js +2 -0
- package/dist/utils/mirror.js.map +7 -0
- package/dist/utils/moonPosition.js +2 -0
- package/dist/utils/moonPosition.js.map +7 -0
- package/dist/utils/moves.js +2 -0
- package/dist/utils/moves.js.map +7 -0
- package/dist/utils/nativeEvents.js +2 -0
- package/dist/utils/nativeEvents.js.map +7 -0
- package/dist/utils/nativeGeometry.js +2 -0
- package/dist/utils/nativeGeometry.js.map +7 -0
- package/dist/utils/nativeLayer.js +2 -0
- package/dist/utils/nativeLayer.js.map +7 -0
- package/dist/utils/nodeMatrix.js +2 -0
- package/dist/utils/nodeMatrix.js.map +7 -0
- package/dist/utils/onAdd3DLayer.js +2 -0
- package/dist/utils/onAdd3DLayer.js.map +7 -0
- package/dist/utils/onAddSun.js +2 -0
- package/dist/utils/onAddSun.js.map +7 -0
- package/dist/utils/onClick.js +2 -0
- package/dist/utils/onClick.js.map +7 -0
- package/dist/utils/onMapChange.js +2 -0
- package/dist/utils/onMapChange.js.map +7 -0
- package/dist/utils/onPMTileAdd.js +2 -0
- package/dist/utils/onPMTileAdd.js.map +7 -0
- package/dist/utils/onPMTileRemove.js +2 -0
- package/dist/utils/onPMTileRemove.js.map +7 -0
- package/dist/utils/onPointerLeave.js +2 -0
- package/dist/utils/onPointerLeave.js.map +7 -0
- package/dist/utils/onPointerMove.js +2 -0
- package/dist/utils/onPointerMove.js.map +7 -0
- package/dist/utils/onRemove3DLayer.js +2 -0
- package/dist/utils/onRemove3DLayer.js.map +7 -0
- package/dist/utils/onRemoveSun.js +2 -0
- package/dist/utils/onRemoveSun.js.map +7 -0
- package/dist/utils/onTerrainAdd.js +2 -0
- package/dist/utils/onTerrainAdd.js.map +7 -0
- package/dist/utils/onTerrainChange.js +2 -0
- package/dist/utils/onTerrainChange.js.map +7 -0
- package/dist/utils/onTerrainRemove.js +2 -0
- package/dist/utils/onTerrainRemove.js.map +7 -0
- package/dist/utils/onZoomAnim.js +2 -0
- package/dist/utils/onZoomAnim.js.map +7 -0
- package/dist/utils/paints.js +2 -0
- package/dist/utils/paints.js.map +7 -0
- package/dist/utils/parseColor.js +2 -0
- package/dist/utils/parseColor.js.map +7 -0
- package/dist/utils/parts.js +2 -0
- package/dist/utils/parts.js.map +7 -0
- package/dist/utils/projectCelestialBody.js +2 -0
- package/dist/utils/projectCelestialBody.js.map +7 -0
- package/dist/utils/pump.js +2 -0
- package/dist/utils/pump.js.map +7 -0
- package/dist/utils/rasterTiles.js +2 -0
- package/dist/utils/rasterTiles.js.map +7 -0
- package/dist/utils/raycast.js +2 -0
- package/dist/utils/raycast.js.map +7 -0
- package/dist/utils/read.js +2 -0
- package/dist/utils/read.js.map +7 -0
- package/dist/utils/readAccessor.js +2 -0
- package/dist/utils/readAccessor.js.map +7 -0
- package/dist/utils/releaseArea.js +2 -0
- package/dist/utils/releaseArea.js.map +7 -0
- package/dist/utils/removeRasterTile.js +2 -0
- package/dist/utils/removeRasterTile.js.map +7 -0
- package/dist/utils/render.js +15 -0
- package/dist/utils/render.js.map +7 -0
- package/dist/utils/renderSunlight.js +13 -0
- package/dist/utils/renderSunlight.js.map +7 -0
- package/dist/utils/renderers.js +2 -0
- package/dist/utils/renderers.js.map +7 -0
- package/dist/utils/resizeSunlight.js +2 -0
- package/dist/utils/resizeSunlight.js.map +7 -0
- package/dist/utils/roofCoverage.js +2 -0
- package/dist/utils/roofCoverage.js.map +7 -0
- package/dist/utils/routeGeometry.js +2 -0
- package/dist/utils/routeGeometry.js.map +7 -0
- package/dist/utils/scheduledTrips.js +2 -0
- package/dist/utils/scheduledTrips.js.map +7 -0
- package/dist/utils/setGeoJSONData.js +2 -0
- package/dist/utils/setGeoJSONData.js.map +7 -0
- package/dist/utils/setHighlight.js +2 -0
- package/dist/utils/setHighlight.js.map +7 -0
- package/dist/utils/setPMTileOpacity.js +2 -0
- package/dist/utils/setPMTileOpacity.js.map +7 -0
- package/dist/utils/setPMTileZIndex.js +2 -0
- package/dist/utils/setPMTileZIndex.js.map +7 -0
- package/dist/utils/setParent.js +2 -0
- package/dist/utils/setParent.js.map +7 -0
- package/dist/utils/setTerrainHeight.js +2 -0
- package/dist/utils/setTerrainHeight.js.map +7 -0
- package/dist/utils/setVertexColor.js +2 -0
- package/dist/utils/setVertexColor.js.map +7 -0
- package/dist/utils/showFeature.js +2 -0
- package/dist/utils/showFeature.js.map +7 -0
- package/dist/utils/sizes.js +2 -0
- package/dist/utils/sizes.js.map +7 -0
- package/dist/utils/skin.js +2 -0
- package/dist/utils/skin.js.map +7 -0
- package/dist/utils/solarPosition.js +2 -0
- package/dist/utils/solarPosition.js.map +7 -0
- package/dist/utils/sortMeshes.js +2 -0
- package/dist/utils/sortMeshes.js.map +7 -0
- package/dist/utils/speed.js +2 -0
- package/dist/utils/speed.js.map +7 -0
- package/dist/utils/startZoom.js +2 -0
- package/dist/utils/startZoom.js.map +7 -0
- package/dist/utils/taillight.js +2 -0
- package/dist/utils/taillight.js.map +7 -0
- package/dist/utils/terrainAtlas.js +2 -0
- package/dist/utils/terrainAtlas.js.map +7 -0
- package/dist/utils/terrainElevation.js +2 -0
- package/dist/utils/terrainElevation.js.map +7 -0
- package/dist/utils/terrainGrid.js +2 -0
- package/dist/utils/terrainGrid.js.map +7 -0
- package/dist/utils/textureSampler.js +2 -0
- package/dist/utils/textureSampler.js.map +7 -0
- package/dist/utils/tileId.js +2 -0
- package/dist/utils/tileId.js.map +7 -0
- package/dist/utils/toLanes.js +2 -0
- package/dist/utils/toLanes.js.map +7 -0
- package/dist/utils/toSRGB.js +2 -0
- package/dist/utils/toSRGB.js.map +7 -0
- package/dist/utils/tooltip.js +3 -0
- package/dist/utils/tooltip.js.map +7 -0
- package/dist/utils/traffic.js +2 -0
- package/dist/utils/traffic.js.map +7 -0
- package/dist/utils/traverse.js +2 -0
- package/dist/utils/traverse.js.map +7 -0
- package/dist/utils/treeGeometry.js +2 -0
- package/dist/utils/treeGeometry.js.map +7 -0
- package/dist/utils/triangulate.js +2 -0
- package/dist/utils/triangulate.js.map +7 -0
- package/dist/utils/tripsAt.js +2 -0
- package/dist/utils/tripsAt.js.map +7 -0
- package/dist/utils/types.js +2 -0
- package/dist/utils/types.js.map +7 -0
- package/dist/utils/underEntry.js +2 -0
- package/dist/utils/underEntry.js.map +7 -0
- package/dist/utils/update.js +2 -0
- package/dist/utils/update.js.map +7 -0
- package/dist/utils/updateArea.js +2 -0
- package/dist/utils/updateArea.js.map +7 -0
- package/dist/utils/updateCamera.js +2 -0
- package/dist/utils/updateCamera.js.map +7 -0
- package/dist/utils/updatePointLights.js +2 -0
- package/dist/utils/updatePointLights.js.map +7 -0
- package/dist/utils/updateWorldMatrix.js +2 -0
- package/dist/utils/updateWorldMatrix.js.map +7 -0
- package/dist/utils/vehicleOffsets.js +2 -0
- package/dist/utils/vehicleOffsets.js.map +7 -0
- package/dist/utils/vehicles.js +2 -0
- package/dist/utils/vehicles.js.map +7 -0
- package/dist/utils/vertexNormals.js +2 -0
- package/dist/utils/vertexNormals.js.map +7 -0
- package/dist/utils/walk.js +2 -0
- package/dist/utils/walk.js.map +7 -0
- package/dist/utils/white.js +2 -0
- package/dist/utils/white.js.map +7 -0
- package/dist/utils/worldLights.js +2 -0
- package/dist/utils/worldLights.js.map +7 -0
- package/dist/utils/wrap.js +2 -0
- package/dist/utils/wrap.js.map +7 -0
- package/package.json +43 -4
- package/src/index.js +397 -0
- package/src/utils/CELL.js +4 -0
- package/src/utils/addBridges.js +66 -0
- package/src/utils/addRasterTile.js +112 -0
- package/src/utils/along.js +9 -0
- package/src/utils/atmosphere.js +20 -0
- package/src/utils/attach.js +255 -0
- package/src/utils/bindSunlight.js +12 -0
- package/src/utils/box.js +1 -0
- package/src/utils/bridgeElevation.js +18 -0
- package/src/utils/build.js +20 -0
- package/src/utils/cameraControls.js +287 -0
- package/src/utils/channel.js +4 -0
- package/src/utils/collider.js +62 -0
- package/src/utils/colliders.js +2 -0
- package/src/utils/computeBounds.js +17 -0
- package/src/utils/conformModelToTerrain.js +69 -0
- package/src/utils/createMesh.js +31 -0
- package/src/utils/createProgram.js +92 -0
- package/src/utils/decodeDirectory.js +40 -0
- package/src/utils/decodeHeader.js +15 -0
- package/src/utils/decodeModel.js +43 -0
- package/src/utils/decompress.js +5 -0
- package/src/utils/destroyTexture.js +7 -0
- package/src/utils/disposeMesh.js +13 -0
- package/src/utils/drawGeometry.js +122 -0
- package/src/utils/elevationWGSL.js +27 -0
- package/src/utils/emptyNode.js +9 -0
- package/src/utils/endZoom.js +15 -0
- package/src/utils/findFeature.js +49 -0
- package/src/utils/geometry.js +2 -0
- package/src/utils/getElevation.js +10 -0
- package/src/utils/getHeader.js +9 -0
- package/src/utils/getZxy.js +76 -0
- package/src/utils/glass.js +1 -0
- package/src/utils/headlight.js +1 -0
- package/src/utils/hit.js +41 -0
- package/src/utils/horizontalPosition.js +16 -0
- package/src/utils/horizontalRay.js +12 -0
- package/src/utils/hull.js +1 -0
- package/src/utils/initSunlight.js +9 -0
- package/src/utils/initTerrain.js +19 -0
- package/src/utils/initTraffic.js +93 -0
- package/src/utils/initWalk.js +72 -0
- package/src/utils/intersectMeshes.js +121 -0
- package/src/utils/isClockWise.js +12 -0
- package/src/utils/lampPostGeometry.js +26 -0
- package/src/utils/lightGeometry.js +22 -0
- package/src/utils/lightGrid.js +50 -0
- package/src/utils/limbs.js +9 -0
- package/src/utils/loadModel.js +217 -0
- package/src/utils/loadSource.js +17 -0
- package/src/utils/loadTerrain.js +65 -0
- package/src/utils/mapCamera.js +432 -0
- package/src/utils/materialShaders.js +243 -0
- package/src/utils/mirror.js +6 -0
- package/src/utils/moonPosition.js +17 -0
- package/src/utils/moves.js +11 -0
- package/src/utils/nativeEvents.js +77 -0
- package/src/utils/nativeGeometry.js +131 -0
- package/src/utils/nativeLayer.js +317 -0
- package/src/utils/nodeMatrix.js +22 -0
- package/src/utils/onAdd3DLayer.js +31 -0
- package/src/utils/onAddSun.js +147 -0
- package/src/utils/onClick.js +13 -0
- package/src/utils/onMapChange.js +16 -0
- package/src/utils/onPMTileAdd.js +15 -0
- package/src/utils/onPMTileRemove.js +8 -0
- package/src/utils/onPointerLeave.js +11 -0
- package/src/utils/onPointerMove.js +15 -0
- package/src/utils/onRemove3DLayer.js +78 -0
- package/src/utils/onRemoveSun.js +3 -0
- package/src/utils/onTerrainAdd.js +18 -0
- package/src/utils/onTerrainChange.js +13 -0
- package/src/utils/onTerrainRemove.js +40 -0
- package/src/utils/onZoomAnim.js +14 -0
- package/src/utils/paints.js +10 -0
- package/src/utils/parseColor.js +42 -0
- package/src/utils/parts.js +16 -0
- package/src/utils/projectCelestialBody.js +13 -0
- package/src/utils/pump.js +226 -0
- package/src/utils/rasterTiles.js +285 -0
- package/src/utils/raycast.js +60 -0
- package/src/utils/read.js +29 -0
- package/src/utils/readAccessor.js +68 -0
- package/src/utils/releaseArea.js +34 -0
- package/src/utils/removeRasterTile.js +13 -0
- package/src/utils/render.js +406 -0
- package/src/utils/renderSunlight.js +170 -0
- package/src/utils/renderers.js +2 -0
- package/src/utils/resizeSunlight.js +15 -0
- package/src/utils/roofCoverage.js +47 -0
- package/src/utils/routeGeometry.js +22 -0
- package/src/utils/scheduledTrips.js +29 -0
- package/src/utils/setGeoJSONData.js +11 -0
- package/src/utils/setHighlight.js +13 -0
- package/src/utils/setPMTileOpacity.js +7 -0
- package/src/utils/setPMTileZIndex.js +7 -0
- package/src/utils/setParent.js +7 -0
- package/src/utils/setTerrainHeight.js +10 -0
- package/src/utils/setVertexColor.js +14 -0
- package/src/utils/showFeature.js +27 -0
- package/src/utils/sizes.js +1 -0
- package/src/utils/skin.js +2 -0
- package/src/utils/solarPosition.js +17 -0
- package/src/utils/sortMeshes.js +12 -0
- package/src/utils/speed.js +3 -0
- package/src/utils/startZoom.js +5 -0
- package/src/utils/taillight.js +1 -0
- package/src/utils/terrainAtlas.js +21 -0
- package/src/utils/terrainElevation.js +20 -0
- package/src/utils/terrainGrid.js +23 -0
- package/src/utils/textureSampler.js +10 -0
- package/src/utils/tileId.js +18 -0
- package/src/utils/toLanes.js +7 -0
- package/src/utils/toSRGB.js +1 -0
- package/src/utils/tooltip.js +6 -0
- package/src/utils/traffic.js +315 -0
- package/src/utils/traverse.js +6 -0
- package/src/utils/treeGeometry.js +45 -0
- package/src/utils/triangulate.js +100 -0
- package/src/utils/tripsAt.js +16 -0
- package/src/utils/types.js +8 -0
- package/src/utils/underEntry.js +17 -0
- package/src/utils/update.js +18 -0
- package/src/utils/updateArea.js +40 -0
- package/src/utils/updateCamera.js +32 -0
- package/src/utils/updatePointLights.js +38 -0
- package/src/utils/updateWorldMatrix.js +47 -0
- package/src/utils/vehicleOffsets.js +8 -0
- package/src/utils/vehicles.js +65 -0
- package/src/utils/vertexNormals.js +30 -0
- package/src/utils/walk.js +187 -0
- package/src/utils/white.js +1 -0
- package/src/utils/worldLights.js +25 -0
- package/src/utils/wrap.js +1 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import './utils/mapCamera.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `new L.Layer({ source: { type, url, ...settings }, pane, attribution })`: layers with a `source`
|
|
5
|
+
* load their specialized utilities on first use. Native map layers also join the WebGPU scene.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Router for `new L.Layer({ source: { type, url, ...settings }, pane, attribution })`.
|
|
10
|
+
* It imports source utilities on demand and initializes the existing instance;
|
|
11
|
+
* types receive flat options (`{ ...source, pane, attribution }`), so `layer.options.url` etc.
|
|
12
|
+
*
|
|
13
|
+
* Folder layout:
|
|
14
|
+
* - Vector ('3d', 'geojson') and raster ('pmtiles') initialization lives here.
|
|
15
|
+
* - utils/: lazy operations for rendering, archives, geometry and specialized layers:
|
|
16
|
+
* terrain (Float32 DEM), sunlight (clock and lighting), traffic and walking.
|
|
17
|
+
* Operations receive the layer explicitly, without subclasses or prototype replacement.
|
|
18
|
+
* Dataset-specific code and data (routes, timetables, API loaders) belong to the application, e.g. misc/.
|
|
19
|
+
*
|
|
20
|
+
* When `type` is omitted, inline FeatureCollections use 'geojson'; otherwise *.pmtiles URLs use
|
|
21
|
+
* 'pmtiles' and other sources use '3d'.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const layerProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Layer.prototype));
|
|
25
|
+
|
|
26
|
+
// One chain for every pending layer: they become real layers in creation order, whatever the import timing.
|
|
27
|
+
let loading = Promise.resolve();
|
|
28
|
+
let sceneModules, sceneLoading, rasterModules;
|
|
29
|
+
let nativeLoading;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The '3d' layer type: native WebGPU scene synchronized with the map camera.
|
|
33
|
+
* Layers on one map share a canvas, scene and camera; each has its own root transform.
|
|
34
|
+
*
|
|
35
|
+
* @typedef {Object} Layer3DOptions Layer options; pane and attribution remain standard L.Layer options.
|
|
36
|
+
* @property {string} [type] '3d', 'geojson', 'pmtiles', 'terrain', 'sunlight', 'traffic' or 'walk'.
|
|
37
|
+
* @property {number[]} [center] Local origin [lat,lng]; defaults to the first map center.
|
|
38
|
+
* @property {number} [terrainOffset=0] Vertical offset in metres when terrain is enabled.
|
|
39
|
+
* @property {boolean} [terrainConform=false] Fit lower model geometry to local terrain elevation.
|
|
40
|
+
* @property {'OPAQUE'|'MASK'|'BLEND'} [alphaMode] Override every glTF material's alpha mode.
|
|
41
|
+
* @property {number[]} [rotation] Model Euler rotation [x,y,z] in radians, YXZ order.
|
|
42
|
+
* @property {number[]} [scale] Model scale [x,y,z].
|
|
43
|
+
* @property {string} [url] GLB/glTF model URL or PMTiles archive URL.
|
|
44
|
+
* @property {function(Object): (ExtrusionStyle|null)} [style] Source styling; null skips a feature.
|
|
45
|
+
* @property {function(PickedFeature): (Node|string)} [tooltip] Tooltip content; strings are text.
|
|
46
|
+
* @property {string} [ariaLabel='3D scene'] Shared canvas accessible name.
|
|
47
|
+
* @property {boolean} [nightLight=false] Point sources: emissive bulbs and local illumination.
|
|
48
|
+
* @property {boolean} [nightSparkle=false] Model: sparse luminous points at night.
|
|
49
|
+
*
|
|
50
|
+
* @typedef {Object} ExtrusionStyle
|
|
51
|
+
* @property {number} top Roof height in metres; must exceed bottom.
|
|
52
|
+
* @property {number} [bottom=0] Nonnegative base height.
|
|
53
|
+
* @property {number|string|Object} [color=0xd9b38c] RGB/CSS color or linear-space {h,s,l}.
|
|
54
|
+
* @property {number} [trunkRadius] Point tree trunk radius in metres.
|
|
55
|
+
* @property {number} [crownRadius] Point tree crown radius in metres.
|
|
56
|
+
* @property {number} [crownBottom] Point tree crown base in metres.
|
|
57
|
+
* @property {string} [crownShape='round'] Point tree crown shape.
|
|
58
|
+
* @property {number|string|Object} [trunkColor=0x72513a] Point tree bark color.
|
|
59
|
+
* @property {number} [lightRadius] Point light estimated ground reach in metres.
|
|
60
|
+
* @property {number} [lightFlux] Point light luminous flux in lumens.
|
|
61
|
+
*
|
|
62
|
+
* @typedef {Object} PickedFeature
|
|
63
|
+
* @property {string|number} id Source feature id or configured model URL.
|
|
64
|
+
* @property {Object} properties GeoJSON properties or model-level glTF extras.
|
|
65
|
+
* @property {Object} mesh Intersected mesh.
|
|
66
|
+
* @property {number[]} point Intersection [x,y,z] in shared scene metres.
|
|
67
|
+
* @property {Object} [model] Configured model root; absent for source polygons.
|
|
68
|
+
* @property {number} [vertexStart] First polygon vertex in the batched mesh.
|
|
69
|
+
* @property {number} [vertexEnd] Exclusive end of the polygon's vertex range.
|
|
70
|
+
*
|
|
71
|
+
* @typedef {Object} SourceArea
|
|
72
|
+
* @property {Object} coords Archive block {z,x,y}.
|
|
73
|
+
* @property {AbortController} request Cancellation prevents attaching obsolete results.
|
|
74
|
+
* @property {boolean} [done] Geometry built successfully, including empty blocks.
|
|
75
|
+
* @property {Object|null} [mesh] Batched GPU mesh.
|
|
76
|
+
* @property {number} [buildings] Emitted feature count.
|
|
77
|
+
* @property {number} [skipped] Features skipped by style.
|
|
78
|
+
* @property {string} [error] Failed load/build message.
|
|
79
|
+
*
|
|
80
|
+
* @param {L.Layer} backend
|
|
81
|
+
* @param {Layer3DOptions} [options] World origin comes from center or the first map centre.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
L.Layer._renderers = new WeakMap();
|
|
85
|
+
L.Layer._nextId = 0;
|
|
86
|
+
|
|
87
|
+
L.Layer.include({
|
|
88
|
+
_layerAdd(event) {
|
|
89
|
+
const native = !this._3d && (
|
|
90
|
+
this instanceof L.GridLayer ||
|
|
91
|
+
this instanceof L.Polyline ||
|
|
92
|
+
this instanceof L.CircleMarker ||
|
|
93
|
+
this instanceof L.Marker && !(this.options.icon instanceof L.DivIcon) ||
|
|
94
|
+
this instanceof L.ImageOverlay && !(this instanceof L.VideoOverlay) && !(this instanceof L.SVGOverlay)
|
|
95
|
+
);
|
|
96
|
+
if (native) {
|
|
97
|
+
if (this instanceof L.GridLayer || this instanceof L.ImageOverlay) {
|
|
98
|
+
this.options.crossOrigin ||= true;
|
|
99
|
+
}
|
|
100
|
+
if (this instanceof L.Marker) this.options.icon.options.crossOrigin ||= true;
|
|
101
|
+
}
|
|
102
|
+
layerProto._layerAdd.call(this, event);
|
|
103
|
+
if (!native || !event.target.hasLayer(this)) return;
|
|
104
|
+
this._initNativeGPU(event.target);
|
|
105
|
+
},
|
|
106
|
+
_initNativeGPU(map) {
|
|
107
|
+
if (this._nativeGPU) return;
|
|
108
|
+
if (!document.getElementById('leaflet-webgpu-targets')) {
|
|
109
|
+
const style = document.createElement('style');
|
|
110
|
+
style.id = 'leaflet-webgpu-targets';
|
|
111
|
+
style.textContent = '.leaflet-webgpu-target{opacity:0!important}';
|
|
112
|
+
document.head.append(style);
|
|
113
|
+
}
|
|
114
|
+
const state = this._nativeGPU = { source: this, map, hidden: new Set() };
|
|
115
|
+
state.hide = () => {
|
|
116
|
+
const htmlIcon = this instanceof L.Marker && this.options.icon instanceof L.DivIcon;
|
|
117
|
+
const targets = [htmlIcon ? null : this._icon, htmlIcon ? null : this._shadow];
|
|
118
|
+
for (const element of state.hidden) {
|
|
119
|
+
if (targets.includes(element)) continue;
|
|
120
|
+
element.classList.remove('leaflet-webgpu-target');
|
|
121
|
+
state.hidden.delete(element);
|
|
122
|
+
}
|
|
123
|
+
for (const element of targets) {
|
|
124
|
+
if (!element || state.hidden.has(element)) continue;
|
|
125
|
+
element.classList.add('leaflet-webgpu-target');
|
|
126
|
+
state.hidden.add(element);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
state.hide();
|
|
130
|
+
this.once('remove', () => {
|
|
131
|
+
if (this._nativeGPU === state) this._nativeGPU = null;
|
|
132
|
+
state.dispose?.();
|
|
133
|
+
for (const element of state.hidden) {
|
|
134
|
+
element.classList.remove('leaflet-webgpu-target');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
nativeLoading ||= import('./utils/nativeLayer.js');
|
|
139
|
+
this.ready = nativeLoading.then(({ nativeLayer }) => {
|
|
140
|
+
if (this._nativeGPU !== state) return this;
|
|
141
|
+
return nativeLayer(state);
|
|
142
|
+
});
|
|
143
|
+
this.ready.catch(error => this.fire('error', { error }));
|
|
144
|
+
},
|
|
145
|
+
_init3D(options = {}) {
|
|
146
|
+
const layer = this;
|
|
147
|
+
const ready = sceneLoading ||= Promise.all([
|
|
148
|
+
import('./utils/onAdd3DLayer.js'),
|
|
149
|
+
import('./utils/onRemove3DLayer.js'),
|
|
150
|
+
import('./utils/updateArea.js'),
|
|
151
|
+
import('./utils/onTerrainChange.js'),
|
|
152
|
+
]).then(([add, remove, update, terrain]) => sceneModules = { ...add, ...remove, ...update, ...terrain });
|
|
153
|
+
layer._3d = true;
|
|
154
|
+
L.Util.setOptions(layer, { ariaLabel: '3D scene', ...options });
|
|
155
|
+
layer.onAdd = map => {
|
|
156
|
+
if (sceneModules) return sceneModules.onAdd3DLayer(layer, map);
|
|
157
|
+
ready.then(({ onAdd3DLayer }) => {
|
|
158
|
+
if (layer._map === map) onAdd3DLayer(layer, map);
|
|
159
|
+
}).catch(error => layer.fire('error', { error }));
|
|
160
|
+
};
|
|
161
|
+
layer.onRemove = map => {
|
|
162
|
+
if (sceneModules) return sceneModules.onRemove3DLayer(layer, map);
|
|
163
|
+
ready.then(({ onRemove3DLayer }) => onRemove3DLayer(layer, map))
|
|
164
|
+
.catch(error => layer.fire('error', { error }));
|
|
165
|
+
};
|
|
166
|
+
layer.getEvents = () => {
|
|
167
|
+
const events = {};
|
|
168
|
+
if (layer.url) {
|
|
169
|
+
events.moveend = events.zoomend = events.rotate = events.pitch = events.resize = () =>
|
|
170
|
+
sceneModules ? sceneModules.updateArea(layer) : ready.then(({ updateArea }) => updateArea(layer));
|
|
171
|
+
}
|
|
172
|
+
if (layer._geojson || layer.url) {
|
|
173
|
+
events.terrainchange = () => sceneModules
|
|
174
|
+
? sceneModules.onTerrainChange(layer)
|
|
175
|
+
: ready.then(({ onTerrainChange }) => onTerrainChange(layer));
|
|
176
|
+
}
|
|
177
|
+
return events;
|
|
178
|
+
};
|
|
179
|
+
// ponytail: models are recognised by extension; extensionless model URLs need a .glb/.gltf suffix or query.
|
|
180
|
+
const isModel = !layer._geojson && /\.(glb|gltf)(?:[?#]|$)/i.test(layer.options.url);
|
|
181
|
+
layer.modelUrl = isModel ? layer.options.url : null;
|
|
182
|
+
layer.url = layer.options.url && !isModel && !layer._geojson ? new URL(layer.options.url, document.baseURI).href : null;
|
|
183
|
+
layer._directories = new Map();
|
|
184
|
+
if (layer.options.center) {
|
|
185
|
+
layer.location = new L.LatLng(layer.options.center[0], layer.options.center[1]);
|
|
186
|
+
}
|
|
187
|
+
/** @type {Map<string, SourceArea>} Keys are archive x/y, not screen tiles. */
|
|
188
|
+
layer._areas = new Map();
|
|
189
|
+
layer._inflight = 0;
|
|
190
|
+
layer._added = Promise.withResolvers();
|
|
191
|
+
queueMicrotask(async () => {
|
|
192
|
+
try {
|
|
193
|
+
await ready;
|
|
194
|
+
layer.root = (await import('./utils/emptyNode.js')).emptyNode();
|
|
195
|
+
layer.raycaster = { origin: [0, 0, 0], direction: [0, 0, 0] };
|
|
196
|
+
if (layer.url || layer._geojson) {
|
|
197
|
+
layer.group = layer.root;
|
|
198
|
+
if (layer.url) layer.header = await (await import('./utils/getHeader.js')).getHeader(layer);
|
|
199
|
+
}
|
|
200
|
+
if (layer._map) {
|
|
201
|
+
await (await import('./utils/attach.js')).attach(layer, layer._map);
|
|
202
|
+
layer._added.resolve();
|
|
203
|
+
}
|
|
204
|
+
if (layer.modelUrl) await (await import('./utils/loadModel.js')).loadModel(layer);
|
|
205
|
+
if (layer._shared) (await import('./utils/update.js')).update(layer);
|
|
206
|
+
layer.fire('ready');
|
|
207
|
+
} catch (error) {
|
|
208
|
+
layer.fire('error', { error });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
_initRaster(options) {
|
|
213
|
+
const layer = this;
|
|
214
|
+
const [{ read }, { getHeader }, { getZxy }, { onPMTileAdd }, { onPMTileRemove },
|
|
215
|
+
{ getRasterTileSize, updateRasterTiles }, { setPMTileOpacity }, { setPMTileZIndex }, { emptyNode }] = rasterModules;
|
|
216
|
+
layer._read = (offset, length, signal) => read(layer, offset, length, signal);
|
|
217
|
+
layer.getHeader = () => getHeader(layer);
|
|
218
|
+
layer.getZxy = (z, x, y, signal) => getZxy(layer, z, x, y, signal);
|
|
219
|
+
layer.getTileSize = () => getRasterTileSize();
|
|
220
|
+
layer.isLoading = () => layer._loadingTiles > 0;
|
|
221
|
+
layer.setOpacity = opacity => setPMTileOpacity(layer, opacity);
|
|
222
|
+
layer.setZIndex = zIndex => setPMTileZIndex(layer, zIndex);
|
|
223
|
+
layer.onAdd = map => onPMTileAdd(layer, map);
|
|
224
|
+
layer.onRemove = map => onPMTileRemove(layer, map);
|
|
225
|
+
const update = () => updateRasterTiles(layer);
|
|
226
|
+
layer.getEvents = () => ({
|
|
227
|
+
moveend: update,
|
|
228
|
+
zoomend: update,
|
|
229
|
+
rotate: update,
|
|
230
|
+
pitch: update,
|
|
231
|
+
resize: update,
|
|
232
|
+
});
|
|
233
|
+
options = { ariaLabel: 'PMTiles raster', maxZoom: 18, opacity: 1, zIndex: 0, ...options };
|
|
234
|
+
L.Util.setOptions(layer, options);
|
|
235
|
+
layer.root = emptyNode();
|
|
236
|
+
layer._areas = new Map();
|
|
237
|
+
layer._rasterTiles = new Map();
|
|
238
|
+
layer._loadingTiles = 0;
|
|
239
|
+
layer._added = Promise.withResolvers();
|
|
240
|
+
layer.url = new URL(options.url, document.baseURI).href;
|
|
241
|
+
layer._directories = new Map();
|
|
242
|
+
layer.ready = Promise.resolve(layer.options.archive || layer).then(async archive => {
|
|
243
|
+
const header = await archive.getHeader();
|
|
244
|
+
layer.options.archive = archive;
|
|
245
|
+
layer.header = header;
|
|
246
|
+
if (layer.options.maxNativeZoom === undefined) {
|
|
247
|
+
layer.options.maxNativeZoom = header.maxZoom;
|
|
248
|
+
}
|
|
249
|
+
layer.options.maxZoom = Math.max(layer.options.minZoom || 0, layer.options.maxZoom);
|
|
250
|
+
layer.fire('ready');
|
|
251
|
+
return layer;
|
|
252
|
+
}).catch(error => {
|
|
253
|
+
layer.fire('error', { error });
|
|
254
|
+
throw error;
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
initialize(options = {}) {
|
|
258
|
+
if (Object.getPrototypeOf(this) !== L.Layer.prototype || !options.source) {
|
|
259
|
+
if (layerProto.initialize) return layerProto.initialize.apply(this, arguments);
|
|
260
|
+
L.Util.setOptions(this, options);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const {
|
|
264
|
+
source: {
|
|
265
|
+
type = options.source.data?.type === 'FeatureCollection'
|
|
266
|
+
? 'geojson'
|
|
267
|
+
: /\.pmtiles(?:[?#]|$)/i.test(options.source.url) ? 'pmtiles' : '3d',
|
|
268
|
+
...source
|
|
269
|
+
},
|
|
270
|
+
...core
|
|
271
|
+
} = options;
|
|
272
|
+
const settings = { ...source, ...core };
|
|
273
|
+
L.Util.setOptions(this, core);
|
|
274
|
+
this._type = type;
|
|
275
|
+
if (type === '3d') {
|
|
276
|
+
this._init3D(settings);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (type === 'pmtiles' && rasterModules) {
|
|
280
|
+
this._initRaster(settings);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
this.onAdd = () => {};
|
|
284
|
+
this.onRemove = () => {};
|
|
285
|
+
// ponytail: type methods (setOpacity, add, ...) exist only once `ready` resolves.
|
|
286
|
+
loading = loading.catch(() => {}).then(async () => {
|
|
287
|
+
switch (type) {
|
|
288
|
+
case 'geojson': {
|
|
289
|
+
const { setGeoJSONData } = await import('./utils/setGeoJSONData.js');
|
|
290
|
+
return (layer, options) => {
|
|
291
|
+
layer._geojson = true;
|
|
292
|
+
layer.setData = data => setGeoJSONData(layer, data);
|
|
293
|
+
layer._init3D(options);
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
case 'pmtiles': {
|
|
297
|
+
rasterModules = await Promise.all([
|
|
298
|
+
import('./utils/read.js'),
|
|
299
|
+
import('./utils/getHeader.js'),
|
|
300
|
+
import('./utils/getZxy.js'),
|
|
301
|
+
import('./utils/onPMTileAdd.js'),
|
|
302
|
+
import('./utils/onPMTileRemove.js'),
|
|
303
|
+
import('./utils/rasterTiles.js'),
|
|
304
|
+
import('./utils/setPMTileOpacity.js'),
|
|
305
|
+
import('./utils/setPMTileZIndex.js'),
|
|
306
|
+
import('./utils/emptyNode.js'),
|
|
307
|
+
]);
|
|
308
|
+
return (layer, options) => layer._initRaster(options);
|
|
309
|
+
}
|
|
310
|
+
case 'terrain':
|
|
311
|
+
return (await import('./utils/initTerrain.js')).initTerrain;
|
|
312
|
+
case 'sunlight':
|
|
313
|
+
return (await import('./utils/initSunlight.js')).initSunlight;
|
|
314
|
+
case 'traffic':
|
|
315
|
+
return (await import('./utils/initTraffic.js')).initTraffic;
|
|
316
|
+
case 'walk':
|
|
317
|
+
return (await import('./utils/initWalk.js')).initWalk;
|
|
318
|
+
default:
|
|
319
|
+
throw new Error(`Unknown layer type: ${type}`);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
const ready = this.ready = loading.then(initialize => {
|
|
323
|
+
const map = this._map;
|
|
324
|
+
map?.removeLayer(this);
|
|
325
|
+
delete this.onAdd;
|
|
326
|
+
delete this.onRemove;
|
|
327
|
+
initialize(this, settings);
|
|
328
|
+
map?.addLayer(this);
|
|
329
|
+
return this.ready === ready ? this : this.ready;
|
|
330
|
+
});
|
|
331
|
+
ready.catch(error => this.fire('error', { error }));
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
L.Layer.addInitHook(function () {
|
|
336
|
+
if (this instanceof L.Polyline || this instanceof L.CircleMarker) {
|
|
337
|
+
const refresh = () => this._nativeGPU?.refresh?.();
|
|
338
|
+
this.beforeAdd = this.onAdd = this.onRemove = this._updateBounds = () => {};
|
|
339
|
+
this.getEvents = () => ({});
|
|
340
|
+
// Leaflet mutators call this renderer contract, but no SVG/Canvas renderer is created.
|
|
341
|
+
this._renderer = {
|
|
342
|
+
options: { tolerance: 0 },
|
|
343
|
+
_updatePath: refresh,
|
|
344
|
+
_updateStyle: refresh,
|
|
345
|
+
_bringToFront: () => this._nativeGPU?.reorder?.(true),
|
|
346
|
+
_bringToBack: () => this._nativeGPU?.reorder?.(false),
|
|
347
|
+
};
|
|
348
|
+
if (this instanceof L.Polyline) {
|
|
349
|
+
const closest = this.closestLayerPoint;
|
|
350
|
+
this.closestLayerPoint = function (point) {
|
|
351
|
+
this._project();
|
|
352
|
+
this._parts = this._rings;
|
|
353
|
+
return closest.call(this, point);
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (this instanceof L.Circle) {
|
|
357
|
+
const getBounds = this.getBounds;
|
|
358
|
+
this.getBounds = function () {
|
|
359
|
+
this._project();
|
|
360
|
+
return getBounds.call(this);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
} else if (this instanceof L.GridLayer) {
|
|
364
|
+
const initialize = this._initContainer;
|
|
365
|
+
this._initContainer = function () {
|
|
366
|
+
initialize.call(this);
|
|
367
|
+
this._container.remove();
|
|
368
|
+
};
|
|
369
|
+
this._updateOpacity = function () {
|
|
370
|
+
if (!this._map) return;
|
|
371
|
+
const now = Date.now();
|
|
372
|
+
let fading = false, prune = false;
|
|
373
|
+
for (const tile of Object.values(this._tiles || {})) {
|
|
374
|
+
if (!tile.current || !tile.loaded) continue;
|
|
375
|
+
tile._gpuOpacity = this._map._fadeAnimated ? Math.min(1, (now - tile.loaded) / 200) : 1;
|
|
376
|
+
if (tile._gpuOpacity < 1) fading = true;
|
|
377
|
+
else if (!tile.active) {
|
|
378
|
+
this._onOpaqueTile(tile);
|
|
379
|
+
tile.active = true;
|
|
380
|
+
prune = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (prune && !this._noPrune) this._pruneTiles();
|
|
384
|
+
this._nativeGPU?.refresh?.();
|
|
385
|
+
cancelAnimationFrame(this._fadeFrame);
|
|
386
|
+
if (fading) this._fadeFrame = requestAnimationFrame(() => this._updateOpacity());
|
|
387
|
+
};
|
|
388
|
+
} else if (this instanceof L.ImageOverlay && !(this instanceof L.VideoOverlay) && !(this instanceof L.SVGOverlay)) {
|
|
389
|
+
this.onAdd = function () {
|
|
390
|
+
if (!this._image) this._initImage();
|
|
391
|
+
if (this._image.complete && this._image.naturalWidth) queueMicrotask(() => this.fire('load'));
|
|
392
|
+
};
|
|
393
|
+
this.onRemove = () => {};
|
|
394
|
+
this.getEvents = () => ({});
|
|
395
|
+
this._reset = () => this._nativeGPU?.refresh?.();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createMesh } from './createMesh.js';
|
|
2
|
+
import { triangulate } from './triangulate.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bare-earth DEMs drop bridges: `getElevation` returns the deck height inside these polygons (GeoJSON
|
|
6
|
+
* Polygon/MultiPolygon with Z, e.g. IGN BD TOPO), and one mesh draws the 1.2 m thick decks.
|
|
7
|
+
* ponytail: anything under a deck (quays, boats asking `getElevation`) also gets the deck height.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function addBridges(backend, geojson) {
|
|
11
|
+
const map = backend._map, crs = map.options.crs, zoom = backend._terrainZoom;
|
|
12
|
+
const origin = crs.project(backend.location), metres = Math.cos(backend.location.lat * Math.PI / 180);
|
|
13
|
+
const base = backend.root.position[1], position = [], normal = [], color = [];
|
|
14
|
+
const local = ([lng, lat, z]) => {
|
|
15
|
+
const p = crs.project(new L.LatLng(lat, lng));
|
|
16
|
+
return [(p.x - origin.x) * metres, z - base, -(p.y - origin.y) * metres];
|
|
17
|
+
};
|
|
18
|
+
const push = (p, n) => { position.push(...p); normal.push(...n); color.push(0.6, 0.58, 0.54); };
|
|
19
|
+
backend._bridges = new Map();
|
|
20
|
+
for (const { geometry } of geojson.features) {
|
|
21
|
+
const polygons = geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.type === 'MultiPolygon' ? geometry.coordinates : [];
|
|
22
|
+
for (const polygon of polygons) {
|
|
23
|
+
const rings = polygon.map(ring => ring.slice(0, -1)).filter(ring => ring.length >= 3);
|
|
24
|
+
if (!rings.length) continue;
|
|
25
|
+
const pixels = rings.flat().map(([lng, lat, z]) => { const p = map.project([lat, lng], zoom); return [p.x, p.y, z]; });
|
|
26
|
+
const points = rings.flat().map(local);
|
|
27
|
+
let triangles;
|
|
28
|
+
try {
|
|
29
|
+
const starts = rings.map((ring, r) => rings.slice(0, r).reduce((sum, previous) => sum + previous.length, 0));
|
|
30
|
+
const [contour, ...holes] = rings.map((ring, r) => ring.map((c, i) => ({ x: pixels[starts[r] + i][0], y: pixels[starts[r] + i][1] })));
|
|
31
|
+
triangles = triangulate(contour, holes);
|
|
32
|
+
} catch { continue; }
|
|
33
|
+
const bridge = {
|
|
34
|
+
triangles: triangles.map(t => t.map(i => pixels[i])),
|
|
35
|
+
min: [Math.min(...pixels.map(p => p[0])), Math.min(...pixels.map(p => p[1]))],
|
|
36
|
+
max: [Math.max(...pixels.map(p => p[0])), Math.max(...pixels.map(p => p[1]))],
|
|
37
|
+
};
|
|
38
|
+
for (let x = Math.floor(bridge.min[0] / 64); x <= Math.floor(bridge.max[0] / 64); x++) {
|
|
39
|
+
for (let y = Math.floor(bridge.min[1] / 64); y <= Math.floor(bridge.max[1] / 64); y++) {
|
|
40
|
+
const key = `${x}/${y}`;
|
|
41
|
+
backend._bridges.set(key, [...backend._bridges.get(key) || [], bridge]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const below = p => [p[0], p[1] - 1.2, p[2]];
|
|
45
|
+
for (let [a, b, c] of triangles) {
|
|
46
|
+
// Counter-clockwise seen from above, so double-sided shading keeps the top lit.
|
|
47
|
+
const [pa, pb, pc] = [points[a], points[b], points[c]];
|
|
48
|
+
if ((pb[2] - pa[2]) * (pc[0] - pa[0]) - (pb[0] - pa[0]) * (pc[2] - pa[2]) < 0) [b, c] = [c, b];
|
|
49
|
+
for (const i of [a, b, c]) push(points[i], [0, 1, 0]);
|
|
50
|
+
for (const i of [a, c, b]) push(below(points[i]), [0, -1, 0]);
|
|
51
|
+
}
|
|
52
|
+
let start = 0;
|
|
53
|
+
for (const ring of rings) {
|
|
54
|
+
for (let i = 0; i < ring.length; i++) {
|
|
55
|
+
const p = points[start + i], q = points[start + (i + 1) % ring.length];
|
|
56
|
+
const length = Math.hypot(q[0] - p[0], q[2] - p[2]) || 1, n = [(q[2] - p[2]) / length, 0, -(q[0] - p[0]) / length];
|
|
57
|
+
for (const corner of [p, q, below(q), p, below(q), below(p)]) push(corner, n);
|
|
58
|
+
}
|
|
59
|
+
start += ring.length;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (position.length) {
|
|
64
|
+
backend._bridgeMesh = createMesh(backend, { position, normal, color });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createProgram } from './createProgram.js';
|
|
2
|
+
import { destroyTexture } from './destroyTexture.js';
|
|
3
|
+
import { terrainAtlas } from './terrainAtlas.js';
|
|
4
|
+
import { terrainGrid } from './terrainGrid.js';
|
|
5
|
+
import { setParent } from './setParent.js';
|
|
6
|
+
import { update } from './update.js';
|
|
7
|
+
|
|
8
|
+
export async function addRasterTile(backend, layer, coords, tile) {
|
|
9
|
+
const ready = await backend._terrainPromise;
|
|
10
|
+
if (!ready || !backend._map || backend._map._terrain3D !== backend || layer._terrain3D !== backend ||
|
|
11
|
+
tile.request.signal.aborted || !layer._rasterTiles.has(`${coords.z}/${coords.x}/${coords.y}`) ||
|
|
12
|
+
backend._rasterTiles.has(tile)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Match DEM pixel spacing, capped at 64 subdivisions per tile.
|
|
16
|
+
const divisions = Math.max(1, Math.min(64, 256 / 2 ** (coords.z - backend._terrainZoom)));
|
|
17
|
+
const map = backend._map, crs = map.options.crs;
|
|
18
|
+
const origin = crs.project(backend.location), metres = Math.cos(backend.location.lat * Math.PI / 180);
|
|
19
|
+
// Tile pixels map linearly to projected metres: one scaled unit grid per tile, elevation in the vertex shader.
|
|
20
|
+
const scale = crs.scale(coords.z), terrainScale = 2 ** (backend._terrainZoom - coords.z);
|
|
21
|
+
const corner = crs.transformation.untransform(new L.Point(coords.x * 256, coords.y * 256), scale);
|
|
22
|
+
const opposite = crs.transformation.untransform(new L.Point((coords.x + 1) * 256, (coords.y + 1) * 256), scale);
|
|
23
|
+
const device = backend.renderer.device;
|
|
24
|
+
const texture = tile.texture;
|
|
25
|
+
if (!backend._rasterProgram || backend._rasterProgram.device !== device) {
|
|
26
|
+
// DEM atlas over the archive's tile grid; invalid or missing samples become -1e9 (the shader's no-data).
|
|
27
|
+
// ponytail: one texture, capped by maxTextureDimension2D (32×32 DEM tiles at 8192 px); split it for larger archives.
|
|
28
|
+
const bounds = backend._terrainBounds, columns = bounds.max.x - bounds.min.x + 1, rows = bounds.max.y - bounds.min.y + 1;
|
|
29
|
+
const { elevations, low, high } = terrainAtlas(backend._terrainTiles, bounds);
|
|
30
|
+
if (backend._dem) destroyTexture(backend, backend._dem.texture);
|
|
31
|
+
backend._dem = {
|
|
32
|
+
texture: device.createTexture({
|
|
33
|
+
size: [columns * 256, rows * 256],
|
|
34
|
+
format: 'r32float',
|
|
35
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
36
|
+
}),
|
|
37
|
+
low: Number.isFinite(low) ? low : backend._terrainOriginElevation,
|
|
38
|
+
high: Number.isFinite(high) ? high : backend._terrainOriginElevation,
|
|
39
|
+
};
|
|
40
|
+
device.queue.writeTexture({ texture: backend._dem.texture }, elevations,
|
|
41
|
+
{ bytesPerRow: columns * 1024 }, [columns * 256, rows * 256]);
|
|
42
|
+
backend._terrainGrids = new Map();
|
|
43
|
+
backend._rasterProgram = createProgram(backend, {
|
|
44
|
+
baseTexture: texture,
|
|
45
|
+
unlit: true,
|
|
46
|
+
terrain: backend._dem,
|
|
47
|
+
alphaMode: 'BLEND',
|
|
48
|
+
depthWrite: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
let attributes = backend._terrainGrids.get(divisions);
|
|
52
|
+
if (!attributes) {
|
|
53
|
+
attributes = terrainGrid(divisions);
|
|
54
|
+
for (const [name, attribute] of Object.entries(attributes)) {
|
|
55
|
+
attribute.size ||= 1;
|
|
56
|
+
attribute.count = attribute.data.length / attribute.size;
|
|
57
|
+
attribute.indexFormat = 'uint16';
|
|
58
|
+
attribute.buffer = device.createBuffer({
|
|
59
|
+
size: Math.max(4, attribute.data.byteLength),
|
|
60
|
+
usage: name === 'index'
|
|
61
|
+
? GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
62
|
+
: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
63
|
+
});
|
|
64
|
+
device.queue.writeBuffer(attribute.buffer, 0, attribute.data);
|
|
65
|
+
}
|
|
66
|
+
backend._terrainGrids.set(divisions, attributes);
|
|
67
|
+
}
|
|
68
|
+
const base = backend._terrainOriginElevation;
|
|
69
|
+
const bottom = Math.min(backend._dem.low, base) - base, top = Math.max(backend._dem.high, base) - base;
|
|
70
|
+
const sizeX = (opposite.x - corner.x) * metres, sizeZ = -(opposite.y - corner.y) * metres;
|
|
71
|
+
// ponytail: ray picking sees the flat unit grid; read the DEM back if terrain must be pickable.
|
|
72
|
+
const geometry = {
|
|
73
|
+
device,
|
|
74
|
+
attributes,
|
|
75
|
+
drawRange: { start: 0, count: attributes.index.count },
|
|
76
|
+
// Local bounds whose radius, scaled by the largest axis, still covers the displaced tile.
|
|
77
|
+
bounds: {
|
|
78
|
+
min: [0, bottom, 0], max: [1, top, 1], center: [0.5, (bottom + top) / 2, 0.5],
|
|
79
|
+
radius: Math.hypot(sizeX, top - bottom, sizeZ) / 2 / Math.max(sizeX, 1, sizeZ),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const mesh = {
|
|
83
|
+
position: [(corner.x - origin.x) * metres, 0, -(corner.y - origin.y) * metres],
|
|
84
|
+
rotation: [0, 0, 0],
|
|
85
|
+
scale: [sizeX, 1, sizeZ],
|
|
86
|
+
worldMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
87
|
+
children: [],
|
|
88
|
+
visible: true,
|
|
89
|
+
device,
|
|
90
|
+
geometry,
|
|
91
|
+
program: backend._rasterProgram,
|
|
92
|
+
texture,
|
|
93
|
+
// DEM pixel of the tile's corner, DEM pixels per tile, and the elevation at the layer origin.
|
|
94
|
+
terrain: [
|
|
95
|
+
coords.x * 256 * terrainScale - backend._terrainBounds.min.x * 256,
|
|
96
|
+
coords.y * 256 * terrainScale - backend._terrainBounds.min.y * 256,
|
|
97
|
+
256 * terrainScale,
|
|
98
|
+
base,
|
|
99
|
+
],
|
|
100
|
+
tile,
|
|
101
|
+
opacity: layer.options.opacity ?? 1,
|
|
102
|
+
id: L.Layer._nextId++,
|
|
103
|
+
renderOrder: layer.options.zIndex || 0,
|
|
104
|
+
fadeDuration: 250,
|
|
105
|
+
fadeStart: tile.loadedAt,
|
|
106
|
+
ground: true,
|
|
107
|
+
};
|
|
108
|
+
tile.mesh.visible = false;
|
|
109
|
+
setParent(mesh, backend.root);
|
|
110
|
+
update(backend);
|
|
111
|
+
backend._rasterTiles.set(tile, mesh);
|
|
112
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Point and unit direction at distance `s` along `points` ([x, z, y?]) with cumulative `lengths`. */
|
|
2
|
+
|
|
3
|
+
export function along(points, lengths, s) {
|
|
4
|
+
let i = 1;
|
|
5
|
+
while (i < lengths.length - 1 && lengths[i] < s) i++;
|
|
6
|
+
const a = points[i - 1], b = points[i], d = lengths[i] - lengths[i - 1] || 1;
|
|
7
|
+
const t = (s - lengths[i - 1]) / d;
|
|
8
|
+
return { x: a[0] + (b[0] - a[0]) * t, y: (a[2] ?? 0) + ((b[2] ?? 0) - (a[2] ?? 0)) * t, z: a[1] + (b[1] - a[1]) * t, dx: (b[0] - a[0]) / d, dz: (b[1] - a[1]) / d };
|
|
9
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
export function atmosphere(altitude) {
|
|
3
|
+
if (!Number.isFinite(altitude)) {
|
|
4
|
+
throw new Error('Invalid solar altitude');
|
|
5
|
+
}
|
|
6
|
+
const stops = [
|
|
7
|
+
[-18, [3, 7, 20], [17, 28, 54], [0.025, 0.04, 0.09]],
|
|
8
|
+
[-6, [37, 53, 84], [182, 106, 86], [0.12, 0.10, 0.14]],
|
|
9
|
+
[0, [101, 131, 171], [246, 170, 115], [0.38, 0.24, 0.18]],
|
|
10
|
+
[10, [74, 147, 208], [197, 221, 237], [0.80, 0.75, 0.68]],
|
|
11
|
+
[25, [53, 144, 213], [223, 238, 250], [1, 1, 1]],
|
|
12
|
+
];
|
|
13
|
+
const upper = stops.findIndex(stop => stop[0] >= altitude);
|
|
14
|
+
const b = stops[upper < 0 ? stops.length - 1 : upper],
|
|
15
|
+
a = stops[Math.max(0, upper - 1)];
|
|
16
|
+
const t = a === b || upper < 0 ? 1 : (altitude - a[0]) / (b[0] - a[0]);
|
|
17
|
+
const mix = index => a[index].map((value, i) => value + (b[index][i] - value) * t);
|
|
18
|
+
const rgb = values => `rgb(${values.map(Math.round).join(', ')})`;
|
|
19
|
+
return { background: `linear-gradient(${rgb(mix(1))}, ${rgb(mix(2))})`, tint: mix(3) };
|
|
20
|
+
}
|