leaflet-city-mapper 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (515) hide show
  1. package/README.md +1 -1
  2. package/dist/leaflet-city-mapper-src.js.map +1 -1
  3. package/dist/leaflet-city-mapper-worker-src.js.map +1 -1
  4. package/dist/leaflet-city-mapper-worker.js.map +1 -1
  5. package/dist/leaflet-city-mapper.js.map +1 -1
  6. package/libs/leaflet-lens.js +151 -151
  7. package/libs/leaflet.js +3 -3
  8. package/libs/openlayers-bindings.js +1129 -1129
  9. package/libs/openlayers-bindings.min.js.map +1 -1
  10. package/package.json +2 -4
  11. package/src/animations/cancelCameraAnimation.js +19 -19
  12. package/src/animations/mapStopPitchBearingAnimation.js +10 -10
  13. package/src/animations/scheduleCameraAnimation.js +15 -15
  14. package/src/animations/scheduleMarkerPan.js +14 -14
  15. package/src/animations/updateZoomCamera.js +25 -25
  16. package/src/archives/findDirectoryEntry.js +17 -17
  17. package/src/archives/findTile.js +45 -45
  18. package/src/archives/getDirectoryEntries.js +30 -30
  19. package/src/archives/getTileData.js +26 -26
  20. package/src/archives/readArchiveRange.js +48 -48
  21. package/src/astronomy/solarTime.js +21 -21
  22. package/src/camera/cameraMapReady.js +9 -9
  23. package/src/camera/cleanupCameraMap.js +13 -13
  24. package/src/camera/constrainCameraToBounds.js +3 -3
  25. package/src/camera/createSceneCamera.js +25 -25
  26. package/src/camera/getBearing.js +10 -10
  27. package/src/camera/getPitch.js +10 -10
  28. package/src/camera/mapGetBoundsCenterZoom.js +14 -14
  29. package/src/camera/mapLimitOffset.js +17 -17
  30. package/src/camera/mapPanBy.js +14 -14
  31. package/src/camera/setBearing.js +13 -13
  32. package/src/camera/setPitch.js +13 -13
  33. package/src/cog/decodeCogTile.js +37 -37
  34. package/src/cog/finiteCogValues.js +12 -12
  35. package/src/cog/getCogAssetEntry.js +26 -26
  36. package/src/cog/getCogBase.js +22 -22
  37. package/src/cog/getCogHeader.js +10 -10
  38. package/src/cog/getCogProfile.js +25 -25
  39. package/src/cog/getCogTile.js +25 -25
  40. package/src/cog/getColorTile.js +38 -38
  41. package/src/cog/getElevationTile.js +29 -29
  42. package/src/cog/hasCogProjection.js +16 -16
  43. package/src/cog/initCog.js +13 -13
  44. package/src/cog/invalidCogFullResponse.js +10 -10
  45. package/src/cog/openCog.js +40 -40
  46. package/src/cog/openCogAsset.js +22 -22
  47. package/src/cog/readCog.js +39 -39
  48. package/src/cog/readCogBlock.js +19 -19
  49. package/src/cog/readCogBody.js +30 -30
  50. package/src/cog/readCogDirectories.js +32 -32
  51. package/src/cog/readCogDirectory.js +34 -34
  52. package/src/cog/readCogLevel.js +36 -36
  53. package/src/cog/readCogTag.js +28 -28
  54. package/src/cog/validCogDimensions.js +15 -15
  55. package/src/cog/validCogEncoding.js +18 -18
  56. package/src/cog/validCogGeoArrays.js +17 -17
  57. package/src/cog/validCogGrid.js +22 -22
  58. package/src/cog/validCogTileCoordinates.js +13 -13
  59. package/src/cog/validateCogContainer.js +17 -17
  60. package/src/cog/validateCogContentRange.js +18 -18
  61. package/src/cog/validateCogOffsets.js +19 -19
  62. package/src/cog/validateCogRange.js +16 -16
  63. package/src/cog/validateCogResponse.js +26 -26
  64. package/src/collision/collectIntersections.js +41 -41
  65. package/src/collision/hiddenLightHit.js +19 -19
  66. package/src/collision/indexTriangle.js +18 -18
  67. package/src/collision/insideMesh.js +47 -47
  68. package/src/collision/insideWalker.js +52 -52
  69. package/src/collision/intersectMeshes.js +40 -40
  70. package/src/collision/localRay.js +31 -31
  71. package/src/collision/mergeHits.js +19 -19
  72. package/src/collision/rayBounds.js +28 -28
  73. package/src/collision/readTriangle.js +32 -32
  74. package/src/collision/trafficCollision.js +23 -23
  75. package/src/collision/transformTriangle.js +18 -18
  76. package/src/collision/triangleDistance.js +34 -34
  77. package/src/collision/vertexCount.js +11 -11
  78. package/src/collision/worldHit.js +30 -30
  79. package/src/controls/acceptPitchDrag.js +10 -10
  80. package/src/controls/acceptRotateDrag.js +9 -9
  81. package/src/controls/addCameraHandler.js +18 -18
  82. package/src/controls/adjustCameraDrag.js +13 -13
  83. package/src/controls/cameraDragContextMenu.js +12 -12
  84. package/src/controls/cameraDragEnd.js +10 -10
  85. package/src/controls/cameraDragMove.js +31 -31
  86. package/src/controls/changeCameraTouch.js +17 -17
  87. package/src/controls/compassOrientation.js +12 -12
  88. package/src/controls/compassRemoveHooks.js +2 -2
  89. package/src/controls/containerResizeRemoveHooks.js +2 -2
  90. package/src/controls/endCameraTouch.js +18 -18
  91. package/src/controls/failCompassPermission.js +10 -10
  92. package/src/controls/finishCameraDrag.js +14 -14
  93. package/src/controls/finishCompassPermission.js +24 -24
  94. package/src/controls/isEditableTarget.js +9 -9
  95. package/src/controls/loadCameraControls.js +18 -18
  96. package/src/controls/moveCameraTouch.js +34 -34
  97. package/src/controls/refreshRotateFocus.js +5 -5
  98. package/src/controls/removeCameraDrag.js +28 -28
  99. package/src/controls/resizeMap.js +7 -7
  100. package/src/controls/rotateButtonClick.js +16 -16
  101. package/src/controls/rotateButtonKeydown.js +20 -20
  102. package/src/controls/rotateControl.js +12 -12
  103. package/src/controls/shiftRotateRemoveHooks.js +2 -2
  104. package/src/controls/shiftRotateWheel.js +15 -15
  105. package/src/core/cameraMarkerOnDrag.js +16 -16
  106. package/src/core/constructLayer.js +16 -16
  107. package/src/core/extendLeafletClass.js +12 -12
  108. package/src/core/extendTerrainPixelBounds.js +41 -41
  109. package/src/core/layerAddInitHook.js +12 -12
  110. package/src/core/layerMergeOptions.js +14 -14
  111. package/src/core/markerInitInteraction.js +10 -10
  112. package/src/core/markerSetIcon.js +14 -14
  113. package/src/core/markerSetRotation.js +19 -19
  114. package/src/core/projectNativePath.js +30 -30
  115. package/src/core/redrawPath.js +11 -11
  116. package/src/core/removeLayer.js +26 -26
  117. package/src/core/setRotation.js +12 -12
  118. package/src/core/whenReady.js +14 -14
  119. package/src/geometry/addBridges.js +36 -36
  120. package/src/geometry/addGeneratedTriangle.js +10 -10
  121. package/src/geometry/appendAreaTriangle.js +32 -32
  122. package/src/geometry/appendNativeTriangle.js +17 -17
  123. package/src/geometry/appendRouteDash.js +33 -33
  124. package/src/geometry/appendRouteSegment.js +28 -28
  125. package/src/geometry/assembleAreaGeometry.js +48 -48
  126. package/src/geometry/bridgeEdge.js +18 -18
  127. package/src/geometry/bridgeFaces.js +22 -22
  128. package/src/geometry/bridgePaths.js +19 -19
  129. package/src/geometry/bridgePolygon.js +37 -37
  130. package/src/geometry/bridgeSides.js +18 -18
  131. package/src/geometry/buildProjectedPoint.js +32 -32
  132. package/src/geometry/buildProjectedPolygon.js +34 -34
  133. package/src/geometry/circleAt.js +20 -20
  134. package/src/geometry/circleGeometry.js +41 -41
  135. package/src/geometry/createModelGeometry.js +44 -44
  136. package/src/geometry/crossVector.js +15 -15
  137. package/src/geometry/dashValues.js +23 -23
  138. package/src/geometry/dashedSegment.js +33 -33
  139. package/src/geometry/diskAt.js +18 -18
  140. package/src/geometry/emitter.js +18 -18
  141. package/src/geometry/fillNativePolygon.js +28 -28
  142. package/src/geometry/generateTreeGeometry.js +18 -18
  143. package/src/geometry/indexBridge.js +36 -36
  144. package/src/geometry/instanceAreaGeometry.js +37 -37
  145. package/src/geometry/line.js +28 -28
  146. package/src/geometry/lineJoin.js +35 -35
  147. package/src/geometry/markerImageBounds.js +20 -20
  148. package/src/geometry/meshShadowBounds.js +33 -33
  149. package/src/geometry/offsetBelowGround.js +10 -10
  150. package/src/geometry/quad.js +23 -23
  151. package/src/geometry/restoreGeometry.js +29 -29
  152. package/src/geometry/roofCoverage.js +33 -33
  153. package/src/geometry/roofFootprint.js +40 -40
  154. package/src/geometry/samePoint.js +11 -11
  155. package/src/geometry/segment.js +32 -32
  156. package/src/geometry/subtractRoofTile.js +28 -28
  157. package/src/geometry/terrainBuffers.js +36 -36
  158. package/src/geometry/terrainTileGeometry.js +28 -28
  159. package/src/geometry/validateTree.js +19 -19
  160. package/src/globals/L.js +6 -6
  161. package/src/globals/cameraFov.js +8 -8
  162. package/src/globals/cogTags.js +6 -6
  163. package/src/globals/colorTexture.js +11 -11
  164. package/src/globals/cosObliquity.js +8 -8
  165. package/src/globals/degrees.js +6 -6
  166. package/src/globals/earthCirc.js +6 -6
  167. package/src/globals/geometryWorkerUrl.js +8 -8
  168. package/src/globals/halfRadians.js +6 -6
  169. package/src/globals/lampDirections.js +13 -13
  170. package/src/globals/layerControlAddItem.js +8 -8
  171. package/src/globals/leafletLayer.js +8 -8
  172. package/src/globals/lightScale.js +6 -6
  173. package/src/globals/mvtTextDecoder.js +6 -6
  174. package/src/globals/nativeScalableStyleKeys.js +6 -6
  175. package/src/globals/sinObliquity.js +8 -8
  176. package/src/globals/tau.js +6 -6
  177. package/src/globals/vectorStyleNamedColors.js +13 -13
  178. package/src/globals/vectorTileEvents.js +6 -6
  179. package/src/globals/workerCogSource.js +6 -6
  180. package/src/gpu/initializeRendererResources.js +39 -39
  181. package/src/gpu/initializeSharedRenderer.js +59 -59
  182. package/src/gpu/mountRenderer.js +25 -25
  183. package/src/layer-controls/layerControlInputId.js +18 -18
  184. package/src/layer-controls/layerCountTitle.js +16 -16
  185. package/src/layer-controls/placeEntryElement.js +29 -29
  186. package/src/layer-controls/renderLayerCount.js +28 -28
  187. package/src/lighting/visitLightCells.js +26 -26
  188. package/src/lighting/writeLightCell.js +14 -14
  189. package/src/materials/createProgramLayout.js +44 -44
  190. package/src/materials/createProgramUniforms.js +25 -25
  191. package/src/materials/modelMaterialFor.js +29 -29
  192. package/src/materials/prepareMeshCutout.js +21 -21
  193. package/src/materials/terrainRasterProgram.js +65 -65
  194. package/src/materials/validateRoofMaterial.js +17 -17
  195. package/src/models/applyModelTransform.js +18 -18
  196. package/src/models/buildModelNodes.js +24 -24
  197. package/src/models/createModelContext.js +24 -24
  198. package/src/models/loadBuffer.js +16 -16
  199. package/src/models/loadModelResources.js +22 -22
  200. package/src/models/normalizeAccessor.js +21 -21
  201. package/src/models/prepareModel.js +29 -29
  202. package/src/models/prepareModelGeometry.js +35 -35
  203. package/src/models/prepareModelPrimitive.js +25 -25
  204. package/src/models/readAccessorValue.js +16 -16
  205. package/src/models/readModelAttributes.js +32 -32
  206. package/src/models/readModelIndices.js +28 -28
  207. package/src/models/readSparseAccessor.js +31 -31
  208. package/src/models/releaseModelResources.js +15 -15
  209. package/src/mvt/addMvtCrossings.js +23 -23
  210. package/src/mvt/appendMvtBandTriangles.js +42 -42
  211. package/src/mvt/appendMvtCoordinates.js +20 -20
  212. package/src/mvt/appendMvtPolygonTriangles.js +29 -29
  213. package/src/mvt/beginMvtPath.js +19 -19
  214. package/src/mvt/closeMvtPath.js +16 -16
  215. package/src/mvt/collectMvtEdges.js +29 -29
  216. package/src/mvt/createMvtReader.js +14 -14
  217. package/src/mvt/decodeVectorTile.js +39 -39
  218. package/src/mvt/expectMvtWire.js +11 -11
  219. package/src/mvt/finishMvtPath.js +14 -14
  220. package/src/mvt/finiteMvtCoordinate.js +14 -14
  221. package/src/mvt/groupMvtPolygons.js +35 -35
  222. package/src/mvt/isMvtFallbackError.js +13 -13
  223. package/src/mvt/mergeMvtActiveEdges.js +18 -18
  224. package/src/mvt/mvtEdgeX.js +12 -12
  225. package/src/mvt/mvtPathsVertices.js +15 -15
  226. package/src/mvt/mvtPolygonPaths.js +19 -19
  227. package/src/mvt/numericMvtValue.js +11 -11
  228. package/src/mvt/readMvtFeature.js +46 -46
  229. package/src/mvt/readMvtField.js +17 -17
  230. package/src/mvt/readMvtGeometry.js +42 -42
  231. package/src/mvt/readMvtInteger.js +22 -22
  232. package/src/mvt/readMvtKnownValue.js +30 -30
  233. package/src/mvt/readMvtLayer.js +40 -40
  234. package/src/mvt/readMvtMessage.js +18 -18
  235. package/src/mvt/readMvtNumber.js +18 -18
  236. package/src/mvt/readMvtNumbers.js +18 -18
  237. package/src/mvt/readMvtString.js +14 -14
  238. package/src/mvt/readMvtValue.js +29 -29
  239. package/src/mvt/skipMvtField.js +22 -22
  240. package/src/mvt/sortMvtActiveEdges.js +20 -20
  241. package/src/mvt/sortedMvtLevels.js +29 -29
  242. package/src/mvt/takeMvtBytes.js +18 -18
  243. package/src/mvt/tessellateVectorRings.js +26 -26
  244. package/src/mvt/triangulateVectorRings.js +23 -23
  245. package/src/native/beforeNativeRender.js +21 -21
  246. package/src/native/bypassNativeEvent.js +21 -21
  247. package/src/native/canScaleNativeStyle.js +26 -26
  248. package/src/native/compareNativeHits.js +10 -10
  249. package/src/native/createNativeMesh.js +46 -46
  250. package/src/native/forwardNativeError.js +8 -8
  251. package/src/native/getGridTerrainTile.js +47 -47
  252. package/src/native/gridTerrainElevation.js +34 -34
  253. package/src/native/isCurrentNativeLayer.js +11 -11
  254. package/src/native/loadGridTerrain.js +36 -36
  255. package/src/native/nativeEventSource.js +27 -27
  256. package/src/native/nativeProgram.js +28 -28
  257. package/src/native/nativeTerrainElevation.js +27 -27
  258. package/src/native/nativeVectorGeometry.js +30 -30
  259. package/src/native/orderNativeGridLayer.js +20 -20
  260. package/src/native/prepareGridTerrainRequests.js +23 -23
  261. package/src/native/pruneNativeResources.js +25 -25
  262. package/src/native/readyNativeGPU.js +13 -13
  263. package/src/native/rejectNativeReady.js +2 -2
  264. package/src/native/removeNativeEvents.js +20 -20
  265. package/src/native/resolveNativeReady.js +2 -2
  266. package/src/native/restoreNativeEvents.js +8 -8
  267. package/src/native/retainGridTerrain.js +15 -15
  268. package/src/native/styleNativeMesh.js +20 -20
  269. package/src/native/syncGridTile.js +28 -28
  270. package/src/native/syncImageOverlay.js +13 -13
  271. package/src/native/syncNativeStrokeAnchors.js +18 -18
  272. package/src/native/syncNativeStrokeZoom.js +34 -34
  273. package/src/native/syncTerrainGridTile.js +38 -38
  274. package/src/native/terrainGridMesh.js +47 -47
  275. package/src/native/updateNativeHover.js +15 -15
  276. package/src/native/updateNativeStrokeRadius.js +14 -14
  277. package/src/picking/hitColliderCell.js +29 -29
  278. package/src/picking/validateCameraInverse.js +15 -15
  279. package/src/projections/displayContainerPoint.js +32 -32
  280. package/src/projections/projectBridgePoint.js +17 -17
  281. package/src/projections/toPoint.js +13 -13
  282. package/src/projections/transformZoomAnimationPoint.js +8 -8
  283. package/src/projections/verticalGroundY.js +14 -14
  284. package/src/raster/rasterTileRange.js +32 -32
  285. package/src/raster/retainRasterTiles.js +21 -21
  286. package/src/rendering/collectVisibleMesh.js +48 -48
  287. package/src/rendering/compareMeshDepth.js +14 -14
  288. package/src/rendering/drawPipeline.js +44 -44
  289. package/src/rendering/nativeStrokeExpansion.js +11 -11
  290. package/src/rendering/prepareFrameBuffer.js +24 -24
  291. package/src/rendering/prepareMeshUniforms.js +36 -36
  292. package/src/rendering/revealRoofMesh.js +43 -43
  293. package/src/scene/addSceneLayer.js +24 -24
  294. package/src/scene/callLayer.js +34 -34
  295. package/src/scene/createLayer.js +13 -13
  296. package/src/scene/getLocation.js +12 -12
  297. package/src/scene/isNativeLayer.js +19 -19
  298. package/src/scene/removeSceneLayer.js +22 -22
  299. package/src/scene/reportLayerError.js +9 -9
  300. package/src/shaders/materialBaseShader.js +26 -26
  301. package/src/shaders/materialSurfaceShader.js +60 -60
  302. package/src/shaders/materialVertexShader.js +89 -89
  303. package/src/shaders/nativeStrokeShader.js +13 -13
  304. package/src/shaders/roofVertexShader.js +16 -16
  305. package/src/shadows/shadowMeshMatches.js +19 -19
  306. package/src/sources/buildAreaFeatures.js +23 -23
  307. package/src/sources/extrusionStyle.js +21 -21
  308. package/src/sources/featureAnchor.js +21 -21
  309. package/src/sources/groundOffset.js +9 -9
  310. package/src/sources/initVectorTiles.js +21 -21
  311. package/src/sources/isVectorTileSource.js +11 -11
  312. package/src/sources/matchesAreaTile.js +17 -17
  313. package/src/sources/pendingSourceAreas.js +28 -28
  314. package/src/sources/prepareAreaGeometryJob.js +34 -34
  315. package/src/sources/projectAreaPolygons.js +25 -25
  316. package/src/sources/readyVectorTiles.js +17 -17
  317. package/src/sunlight/addTimeControlToMap.js +18 -18
  318. package/src/sunlight/addTimeLayer.js +12 -12
  319. package/src/sunlight/initializeTimeControl.js +18 -18
  320. package/src/sunlight/onAddSun.js +37 -37
  321. package/src/sunlight/onAddTimeControl.js +12 -12
  322. package/src/sunlight/onRemoveSun.js +10 -10
  323. package/src/sunlight/removeSun.js +25 -25
  324. package/src/sunlight/removeTimeControl.js +14 -14
  325. package/src/sunlight/removeTimeLayer.js +11 -11
  326. package/src/sunlight/requestSunlightRender.js +12 -12
  327. package/src/sunlight/styleTimeControl.js +13 -13
  328. package/src/sunlight/tickSun.js +10 -10
  329. package/src/sunlight/updateCelestialDisc.js +23 -23
  330. package/src/sunlight/updateSun.js +44 -44
  331. package/src/sunlight/useLiveTime.js +11 -11
  332. package/src/sunlight/useSelectedTime.js +26 -26
  333. package/src/terrain/applyTerrain.js +30 -30
  334. package/src/terrain/copyTerrainTile.js +20 -20
  335. package/src/terrain/loadReadyTerrain.js +6 -6
  336. package/src/terrain/mapElevationOf.js +19 -19
  337. package/src/terrain/readBridges.js +13 -13
  338. package/src/terrain/readyTerrainOf.js +14 -14
  339. package/src/terrain/sampleTerrainTile.js +16 -16
  340. package/src/terrain/terrainTileQueue.js +34 -34
  341. package/src/textures/addImageState.js +13 -13
  342. package/src/textures/bindMeshTextures.js +57 -57
  343. package/src/textures/collectRoofTiles.js +23 -23
  344. package/src/textures/loadModelImage.js +29 -29
  345. package/src/textures/modelTextureFor.js +47 -47
  346. package/src/textures/prepareMeshTextures.js +31 -31
  347. package/src/textures/removeImageState.js +13 -13
  348. package/src/textures/resizeRoofTexture.js +32 -32
  349. package/src/textures/restoreMaterialTextures.js +22 -22
  350. package/src/textures/updateRoofAtlas.js +29 -29
  351. package/src/textures/uploadMeshTexture.js +32 -32
  352. package/src/textures/uploadNativeImage.js +26 -26
  353. package/src/textures/uploadRoofTile.js +29 -29
  354. package/src/traffic/createTrafficMesh.js +17 -17
  355. package/src/traffic/disposeTrafficObject.js +19 -19
  356. package/src/traffic/failTraffic.js +12 -12
  357. package/src/traffic/failedTrafficLoad.js +10 -10
  358. package/src/traffic/fetchTrafficLanes.js +21 -21
  359. package/src/traffic/loadedTrafficLanes.js +21 -21
  360. package/src/traffic/locateTraffic.js +20 -20
  361. package/src/traffic/moveTraffic.js +21 -21
  362. package/src/traffic/readyTraffic.js +10 -10
  363. package/src/traffic/refreshTrafficTrips.js +42 -42
  364. package/src/traffic/routeColor.js +13 -13
  365. package/src/traffic/startTraffic.js +12 -12
  366. package/src/traffic/trafficNow.js +15 -15
  367. package/src/traffic/trafficStatusText.js +13 -13
  368. package/src/traffic/trafficStorage.js +13 -13
  369. package/src/traffic/updateTraffic.js +13 -13
  370. package/src/traffic/updateTrafficLights.js +23 -23
  371. package/src/traffic/updateTrafficPose.js +15 -15
  372. package/src/traffic/updateTrafficTerrain.js +15 -15
  373. package/src/traffic/uploadTrafficLanes.js +27 -27
  374. package/src/transforms/composeLocalTransform.js +23 -23
  375. package/src/transforms/transformNativePoint.js +19 -19
  376. package/src/transforms/worldMatrixChanged.js +26 -26
  377. package/src/triangulation/clipEars.js +32 -32
  378. package/src/triangulation/connectHole.js +30 -30
  379. package/src/triangulation/cross.js +12 -12
  380. package/src/triangulation/equal.js +11 -11
  381. package/src/triangulation/intersects.js +21 -21
  382. package/src/triangulation/mergeHoles.js +19 -19
  383. package/src/triangulation/on.js +13 -13
  384. package/src/triangulation/polygonRings.js +22 -22
  385. package/src/triangulation/validateArea.js +19 -19
  386. package/src/utils/compareDistance.js +11 -11
  387. package/src/utils/yieldTask.js +12 -12
  388. package/src/vector-style/appendQuotedStyleCharacter.js +30 -30
  389. package/src/vector-style/arrayContainsRange.js +18 -18
  390. package/src/vector-style/evaluateBoolean.js +31 -31
  391. package/src/vector-style/evaluateCoalesceOrCase.js +34 -34
  392. package/src/vector-style/evaluateComparison.js +29 -29
  393. package/src/vector-style/evaluateIExpression.js +21 -21
  394. package/src/vector-style/evaluateInput.js +16 -16
  395. package/src/vector-style/evaluateInterpolate.js +36 -36
  396. package/src/vector-style/evaluateMatch.js +21 -21
  397. package/src/vector-style/evaluateMath.js +26 -26
  398. package/src/vector-style/evaluateMembership.js +29 -29
  399. package/src/vector-style/evaluateNegation.js +26 -26
  400. package/src/vector-style/evaluatePrimitive.js +21 -21
  401. package/src/vector-style/evaluateStep.js +20 -20
  402. package/src/vector-style/evaluateStyle.js +43 -43
  403. package/src/vector-style/evaluateToExpression.js +27 -27
  404. package/src/vector-style/fillVectorPaint.js +26 -26
  405. package/src/vector-style/lineVectorPaint.js +31 -31
  406. package/src/vector-style/parseFunctionalColor.js +23 -23
  407. package/src/vector-style/parseHexColor.js +20 -20
  408. package/src/vector-style/parseVectorStyle.js +20 -20
  409. package/src/vector-style/readPaintValue.js +17 -17
  410. package/src/vector-style/removeTrailingCommas.js +19 -19
  411. package/src/vector-style/scaleDashArray.js +25 -25
  412. package/src/vector-style/skipStyleComment.js +20 -20
  413. package/src/vector-style/stripStyleComments.js +28 -28
  414. package/src/vector-style/styleColor.js +18 -18
  415. package/src/vector-style/validateVectorLayers.js +18 -18
  416. package/src/vector-style/vectorPaint.js +17 -17
  417. package/src/vector-style/visibleStyle.js +16 -16
  418. package/src/vector-tiles/appendVectorBatch.js +34 -34
  419. package/src/vector-tiles/attachVectorTileBatch.js +25 -25
  420. package/src/vector-tiles/attachVectorTileBatches.js +26 -26
  421. package/src/vector-tiles/compareVectorLabelPriority.js +11 -11
  422. package/src/vector-tiles/compareVectorTileDistance.js +11 -11
  423. package/src/vector-tiles/createVectorLabel.js +37 -37
  424. package/src/vector-tiles/forwardVectorTileError.js +10 -10
  425. package/src/vector-tiles/loadVectorSprite.js +14 -14
  426. package/src/vector-tiles/loadVectorTile.js +45 -45
  427. package/src/vector-tiles/onVectorTileAdd.js +44 -44
  428. package/src/vector-tiles/onVectorTileMessage.js +15 -15
  429. package/src/vector-tiles/onVectorTileRemove.js +25 -25
  430. package/src/vector-tiles/onVectorTileWorkerError.js +16 -16
  431. package/src/vector-tiles/paintVectorTileBatch.js +18 -18
  432. package/src/vector-tiles/prepareVectorLabel.js +34 -34
  433. package/src/vector-tiles/prepareVectorStyleLayer.js +38 -38
  434. package/src/vector-tiles/prepareVectorTile.js +29 -29
  435. package/src/vector-tiles/projectVectorPaths.js +25 -25
  436. package/src/vector-tiles/projectVectorPoint.js +15 -15
  437. package/src/vector-tiles/projectVectorTriangles.js +28 -28
  438. package/src/vector-tiles/pruneVectorTiles.js +14 -14
  439. package/src/vector-tiles/pumpVectorTiles.js +21 -21
  440. package/src/vector-tiles/readTileJSON.js +27 -27
  441. package/src/vector-tiles/readVectorStyle.js +28 -28
  442. package/src/vector-tiles/refreshVectorTiles.js +8 -8
  443. package/src/vector-tiles/reserveVectorLabelCells.js +14 -14
  444. package/src/vector-tiles/retireVectorTileGroup.js +20 -20
  445. package/src/vector-tiles/revokeVectorWorkerUrl.js +14 -14
  446. package/src/vector-tiles/rotateVectorLabel.js +19 -19
  447. package/src/vector-tiles/rotateVectorLabels.js +9 -9
  448. package/src/vector-tiles/simplifyVectorPath.js +29 -29
  449. package/src/vector-tiles/updateVectorLabelMarker.js +36 -36
  450. package/src/vector-tiles/updateVectorLabelStyle.js +14 -14
  451. package/src/vector-tiles/updateVectorTileLabels.js +33 -33
  452. package/src/vector-tiles/updateVectorTilePaint.js +25 -25
  453. package/src/vector-tiles/updateVectorTiles.js +32 -32
  454. package/src/vector-tiles/validateVectorTileZooms.js +15 -15
  455. package/src/vector-tiles/vectorFeaturePaint.js +25 -25
  456. package/src/vector-tiles/vectorFeaturePaths.js +26 -26
  457. package/src/vector-tiles/vectorFeatureZoom.js +18 -18
  458. package/src/vector-tiles/vectorLabelAnchors.js +31 -31
  459. package/src/vector-tiles/vectorLabelCandidates.js +18 -18
  460. package/src/vector-tiles/vectorLabelCells.js +32 -32
  461. package/src/vector-tiles/vectorLabelIcon.js +38 -38
  462. package/src/vector-tiles/vectorLabelProperty.js +12 -12
  463. package/src/vector-tiles/vectorLabelText.js +19 -19
  464. package/src/vector-tiles/vectorSpriteIcon.js +19 -19
  465. package/src/vector-tiles/vectorStraightRun.js +23 -23
  466. package/src/vector-tiles/vectorStyleDependencies.js +22 -22
  467. package/src/vector-tiles/vectorStyleValue.js +16 -16
  468. package/src/vector-tiles/vectorStyleZoom.js +12 -12
  469. package/src/vector-tiles/vectorTileAwaited.js +17 -17
  470. package/src/vector-tiles/vectorTileOverlaps.js +12 -12
  471. package/src/vector-tiles/vectorTilePaint.js +18 -18
  472. package/src/vector-tiles/vectorTileSettings.js +27 -27
  473. package/src/vector-tiles/vectorTilesIdle.js +11 -11
  474. package/src/vector-tiles/vectorTolerance.js +11 -11
  475. package/src/vector-tiles/wrapVectorLabel.js +17 -17
  476. package/src/vehicles/boatModel.js +35 -35
  477. package/src/vehicles/busModel.js +13 -13
  478. package/src/vehicles/carModel.js +39 -39
  479. package/src/walk/initializeWalkControl.js +12 -12
  480. package/src/walk/isWalkBlocked.js +15 -15
  481. package/src/walk/landWalker.js +41 -41
  482. package/src/walk/moveWalker.js +37 -37
  483. package/src/walk/onAddWalkControl.js +40 -40
  484. package/src/walk/onRemoveWalkControl.js +17 -17
  485. package/src/walk/onWalkBlur.js +3 -3
  486. package/src/walk/onWalkTerrain.js +3 -3
  487. package/src/walk/resolveWalkerPosition.js +33 -33
  488. package/src/walk/stepWalkerHeight.js +18 -18
  489. package/src/walk/toggleWalking.js +4 -4
  490. package/src/walk/updateWalkerFrame.js +29 -29
  491. package/src/walk/updateWalkerView.js +31 -31
  492. package/src/weakmaps/cameraHandlersByMap.js +6 -6
  493. package/src/weakmaps/geometryWorkersByMap.js +6 -6
  494. package/src/weakmaps/nativeProgramsByRenderer.js +6 -6
  495. package/src/weakmaps/pathProjectionByMap.js +6 -6
  496. package/src/weakmaps/streamTerrainByLayer.js +6 -6
  497. package/src/weakmaps/streamTerrainByMap.js +6 -6
  498. package/src/weakmaps/sunByMap.js +6 -6
  499. package/src/weakmaps/vectorTileOptionsByLayer.js +6 -6
  500. package/src/weakmaps/vectorWorkerByLayer.js +6 -6
  501. package/src/weakmaps/walkByMap.js +6 -6
  502. package/src/weakmaps/workerMapByLayer.js +6 -6
  503. package/src/workers/acquireGeometryWorker.js +39 -39
  504. package/src/workers/buildAreaInWorker.js +24 -24
  505. package/src/workers/closeGeometryWorkers.js +16 -16
  506. package/src/workers/cogWorker.js +27 -27
  507. package/src/workers/createGeometryWorker.js +30 -30
  508. package/src/workers/geometryWorker.js +34 -34
  509. package/src/workers/getCogInWorker.js +18 -18
  510. package/src/workers/modelWorker.js +17 -17
  511. package/src/workers/openCogInWorker.js +22 -22
  512. package/src/workers/runSourceWorker.js +44 -44
  513. package/src/workers/settleGeometryWorker.js +16 -16
  514. package/src/workers/startGeometryWorker.js +23 -23
  515. package/src/workers/vectorTileWorker.js +15 -15
@@ -1,1129 +1,1129 @@
1
- // Leaflet-compatible classes backed by OpenLayers. Load after ol.js and before the plugin.
2
- (() => {
3
- if (!window.ol) throw new Error('Unable to load OpenLayers');
4
-
5
- const style = document.createElement('style');
6
- style.dataset.openlayersBindings = '';
7
- style.textContent = `
8
- .openlayers-city-mapper {
9
- position: relative;
10
- overflow: hidden;
11
- touch-action: none;
12
- }
13
- .ol-viewport {
14
- position: absolute !important;
15
- inset: 0;
16
- }
17
- .ol-overlaycontainer-stopevent { z-index: 1000 !important; }
18
- .ol-city-corner {
19
- position: absolute;
20
- display: flex;
21
- flex-direction: column;
22
- gap: .5em;
23
- pointer-events: none;
24
- }
25
- .ol-city-topleft { top: 5em; left: .5em; }
26
- .ol-city-topright { top: 10px; right: 10px; }
27
- .ol-city-bottomleft { bottom: .5em; left: .5em; }
28
- .ol-city-bottomright { bottom: 4em; right: .5em; }
29
- .ol-attribution {
30
- text-align: justify;
31
- border-radius: 0 !important;
32
- left: 0;
33
- right: 0 !important;
34
- max-width: unset;
35
- font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
36
- font-size: .75rem;
37
- line-height: 1.5;
38
- }
39
- .ol-attribution a { color: #0078A8; }
40
- .ol-attribution * { text-shadow: none !important; }
41
- .ol-city-control {
42
- position: static;
43
- pointer-events: auto;
44
- font: 13px/1.5 sans-serif;
45
- }
46
- .ol-city-rotate {
47
- padding: 0;
48
- border: 1px solid #0003;
49
- border-radius: 4px;
50
- background: #fff;
51
- background-clip: padding-box;
52
- }
53
- .ol-city-rotate a {
54
- display: block;
55
- width: 30px;
56
- height: 30px;
57
- border-radius: 2px;
58
- color: #000;
59
- text-decoration: none;
60
- }
61
- .ol-city-rotate a:hover { background: #f4f4f4; }
62
- .ol-city-rotate a:focus-visible { outline: 2px solid #2675c9; }
63
- .ol-city-layers {
64
- max-height: calc(100vh - 5em);
65
- overflow: auto;
66
- border-radius: 4px;
67
- }
68
- .ol-city-layers section { padding: .5em; background: var(--ol-background-color); }
69
- .ol-city-layers label { display: block; cursor: pointer; white-space: nowrap; }
70
- .ol-city-layers input { vertical-align: middle; }
71
- .ol-city-layers hr { border: 0; border-top: 1px solid #ccc; }
72
- .ol-city-layers .ol-control { background: transparent; }
73
- .ol-city-layers .sunlight-time button { width: auto; padding: 0 .4em; flex-shrink: 0; }
74
- .openlayers-city-mapper .leaflet-pane {
75
- position: absolute;
76
- inset: 0;
77
- pointer-events: none;
78
- }
79
- .ol-zoom button { width: 30px; height: 30px; }
80
- .openlayers-city-mapper .leaflet-marker-icon,
81
- .openlayers-city-mapper .leaflet-marker-shadow {
82
- position: absolute;
83
- top: 0;
84
- left: 0;
85
- }
86
- .openlayers-city-mapper .leaflet-marker-icon { pointer-events: auto; cursor: pointer; }
87
- .openlayers-city-mapper .leaflet-div-icon { background: #fff; border: 1px solid #666; }
88
- .ol-city-popup {
89
- padding: 10px;
90
- background: white;
91
- border: 1px solid #888;
92
- border-radius: 4px;
93
- font: 14px sans-serif;
94
- }
95
- .ol-city-popup button { float: right; margin-left: 12px; }
96
- .ol-city-tooltip {
97
- position: absolute;
98
- z-index: 1100;
99
- padding: 4px 8px;
100
- background: white;
101
- border: 1px solid #888;
102
- pointer-events: none;
103
- }
104
- `;
105
- document.head.appendChild(style);
106
-
107
- const L = window.L = { version: '1.9.4-openlayers', control: {} };
108
- let lastId = 0;
109
- const stamp = object => object._leaflet_id ||= ++lastId;
110
- const setOptions = (object, options) => object.options = { ...object.options, ...options };
111
- const zoomTiming = new Animation(new KeyframeEffect(null, [], {
112
- duration: 1, fill: 'both', easing: 'cubic-bezier(0,0,0.25,1)',
113
- }));
114
-
115
- function Class(...args) {
116
- this.initialize?.(...args);
117
- this.callInitHooks();
118
- }
119
- Class.extend = function (properties) {
120
- function Child(...args) {
121
- this.initialize?.(...args);
122
- this.callInitHooks();
123
- }
124
- Object.setPrototypeOf(Child, this);
125
- Child.prototype = Object.create(this.prototype);
126
- Object.assign(Child.prototype, properties, { constructor: Child });
127
- Child.prototype.options = { ...this.prototype.options, ...properties.options };
128
- Child.prototype._initHooks = [];
129
- return Child;
130
- };
131
- Class.include = function (properties) { Object.assign(this.prototype, properties); return this; };
132
- Class.mergeOptions = function (options) {
133
- this.prototype.options = { ...this.prototype.options, ...options };
134
- return this;
135
- };
136
- Class.addInitHook = function (hook, ...args) {
137
- if (!Object.hasOwn(this.prototype, '_initHooks')) this.prototype._initHooks = [];
138
- this.prototype._initHooks.push(typeof hook === 'function' ? hook : function () { this[hook](...args); });
139
- return this;
140
- };
141
- Class.prototype.callInitHooks = function () {
142
- if (this._initHooksCalled) return;
143
- this._initHooksCalled = true;
144
- const prototypes = [];
145
- for (let proto = Object.getPrototypeOf(this); proto; proto = Object.getPrototypeOf(proto)) prototypes.unshift(proto);
146
- for (const proto of prototypes) {
147
- if (Object.hasOwn(proto, '_initHooks')) for (const hook of proto._initHooks) hook.call(this);
148
- }
149
- };
150
- L.Evented = Class.extend({
151
- on(types, fn, context) {
152
- if (typeof types === 'object') {
153
- for (const [type, handler] of Object.entries(types)) this.on(type, handler, fn);
154
- } else {
155
- this._events ||= {};
156
- for (const type of types.split(/\s+/)) {
157
- const events = this._events[type] ||= [];
158
- if (!events.some(event => event.fn === fn && event.context === context)) events.push({ fn, context });
159
- }
160
- }
161
- return this;
162
- },
163
- off(types, fn, context) {
164
- if (!types) this._events = {};
165
- else if (typeof types === 'object') {
166
- for (const [type, handler] of Object.entries(types)) this.off(type, handler, fn);
167
- } else {
168
- for (const type of types.split(/\s+/)) {
169
- if (this._events?.[type]) this._events[type] = fn
170
- ? this._events[type].filter(event => event.fn !== fn || event.context !== context) : [];
171
- }
172
- }
173
- return this;
174
- },
175
- once(types, fn, context) {
176
- const handler = event => { this.off(types, handler); fn.call(context || this, event); };
177
- return this.on(types, handler);
178
- },
179
- fire(type, data) {
180
- for (const event of [...this._events?.[type] || []]) {
181
- event.fn.call(event.context || this, { ...data, type, target: this, sourceTarget: this });
182
- }
183
- return this;
184
- },
185
- listens(type) { return Boolean(this._events?.[type]?.length); },
186
- });
187
- L.Util = {
188
- stamp, setOptions,
189
- throttle(fn, interval, context) {
190
- let timer, pending;
191
- const run = (...args) => {
192
- if (timer) { pending = args; return; }
193
- fn.apply(context, args);
194
- timer = setTimeout(() => {
195
- timer = null;
196
- if (pending) { const args = pending; pending = null; run(...args); }
197
- }, interval);
198
- };
199
- return run;
200
- },
201
- };
202
-
203
- L.Point = function (x, y, round) {
204
- this.x = round ? Math.round(x) : x;
205
- this.y = round ? Math.round(y) : y;
206
- };
207
- const point = value => value instanceof L.Point ? value
208
- : Array.isArray(value) ? new L.Point(...value) : new L.Point(value.x, value.y);
209
- L.Point.prototype = {
210
- clone() { return new L.Point(this.x, this.y); },
211
- add(value) { return this.clone()._add(point(value)); },
212
- _add(value) { this.x += value.x; this.y += value.y; return this; },
213
- subtract(value) { return this.clone()._subtract(point(value)); },
214
- _subtract(value) { this.x -= value.x; this.y -= value.y; return this; },
215
- multiplyBy(value) { return new L.Point(this.x * value, this.y * value); },
216
- divideBy(value) { return new L.Point(this.x / value, this.y / value); },
217
- scaleBy(value) { return new L.Point(this.x * value.x, this.y * value.y); },
218
- unscaleBy(value) { return new L.Point(this.x / value.x, this.y / value.y); },
219
- round() { return new L.Point(Math.round(this.x), Math.round(this.y)); },
220
- floor() { return new L.Point(Math.floor(this.x), Math.floor(this.y)); },
221
- ceil() { return new L.Point(Math.ceil(this.x), Math.ceil(this.y)); },
222
- distanceTo(value) { value = point(value); return Math.hypot(this.x - value.x, this.y - value.y); },
223
- equals(value) { value = point(value); return this.x === value.x && this.y === value.y; },
224
- contains(value) { value = point(value); return Math.abs(value.x) <= Math.abs(this.x) && Math.abs(value.y) <= Math.abs(this.y); },
225
- };
226
- L.LatLng = function (lat, lng, alt) {
227
- if (!Number.isFinite(Number(lat)) || !Number.isFinite(Number(lng))) throw new Error('Invalid LatLng');
228
- this.lat = Number(lat); this.lng = Number(lng);
229
- if (alt !== undefined) this.alt = alt;
230
- };
231
- const latLng = value => value instanceof L.LatLng ? value
232
- : Array.isArray(value) ? new L.LatLng(...value) : new L.LatLng(value.lat, value.lng);
233
- L.LatLng.prototype = {
234
- equals(value, margin = 1e-9) {
235
- value = latLng(value);
236
- return Math.max(Math.abs(this.lat - value.lat), Math.abs(this.lng - value.lng)) <= margin;
237
- },
238
- distanceTo(value) { value = latLng(value); return ol.sphere.getDistance([this.lng, this.lat], [value.lng, value.lat]); },
239
- wrap() { return new L.LatLng(this.lat, ((this.lng + 180) % 360 + 360) % 360 - 180, this.alt); },
240
- };
241
- L.Bounds = function (a, b) {
242
- if (a) for (const value of b ? [a, b] : Array.isArray(a) ? a : [a]) this.extend(value);
243
- };
244
- L.Bounds.prototype = {
245
- extend(value) {
246
- if (value instanceof L.Bounds) { this.extend(value.min); this.extend(value.max); return this; }
247
- value = point(value);
248
- if (!this.min) { this.min = value.clone(); this.max = value.clone(); }
249
- else {
250
- this.min.x = Math.min(this.min.x, value.x); this.min.y = Math.min(this.min.y, value.y);
251
- this.max.x = Math.max(this.max.x, value.x); this.max.y = Math.max(this.max.y, value.y);
252
- }
253
- return this;
254
- },
255
- getCenter() { return this.min.add(this.max).divideBy(2); },
256
- getSize() { return this.max.subtract(this.min); },
257
- contains(value) {
258
- const min = value.min || point(value), max = value.max || min;
259
- return min.x >= this.min.x && max.x <= this.max.x && min.y >= this.min.y && max.y <= this.max.y;
260
- },
261
- intersects(value) {
262
- return value.max.x >= this.min.x && value.min.x <= this.max.x && value.max.y >= this.min.y && value.min.y <= this.max.y;
263
- },
264
- };
265
- L.LatLngBounds = function (a, b) {
266
- if (a) for (const value of b ? [a, b] : a) this.extend(value);
267
- };
268
- L.LatLngBounds.prototype = {
269
- extend(value) {
270
- if (value instanceof L.LatLngBounds) { this.extend(value._southWest); this.extend(value._northEast); return this; }
271
- value = latLng(value);
272
- if (!this._southWest) {
273
- this._southWest = new L.LatLng(value.lat, value.lng);
274
- this._northEast = new L.LatLng(value.lat, value.lng);
275
- } else {
276
- this._southWest.lat = Math.min(this._southWest.lat, value.lat);
277
- this._southWest.lng = Math.min(this._southWest.lng, value.lng);
278
- this._northEast.lat = Math.max(this._northEast.lat, value.lat);
279
- this._northEast.lng = Math.max(this._northEast.lng, value.lng);
280
- }
281
- return this;
282
- },
283
- getSouthWest() { return this._southWest; },
284
- getNorthEast() { return this._northEast; },
285
- getNorthWest() { return new L.LatLng(this.getNorth(), this.getWest()); },
286
- getSouthEast() { return new L.LatLng(this.getSouth(), this.getEast()); },
287
- getNorth() { return this._northEast.lat; },
288
- getSouth() { return this._southWest.lat; },
289
- getEast() { return this._northEast.lng; },
290
- getWest() { return this._southWest.lng; },
291
- getCenter() { return new L.LatLng((this.getNorth() + this.getSouth()) / 2, (this.getEast() + this.getWest()) / 2); },
292
- contains(value) {
293
- value = latLng(value);
294
- return value.lat >= this.getSouth() && value.lat <= this.getNorth() && value.lng >= this.getWest() && value.lng <= this.getEast();
295
- },
296
- isValid() { return Boolean(this._southWest && this._northEast); },
297
- };
298
-
299
- const radius = 6378137, circumference = 2 * Math.PI * radius;
300
- const crs = {
301
- scale: zoom => 256 * 2 ** zoom,
302
- zoom: scale => Math.log2(scale / 256),
303
- project(value) { value = latLng(value); return point(ol.proj.fromLonLat([value.lng, Math.max(-85.0511287798, Math.min(85.0511287798, value.lat))])); },
304
- unproject(value) { const [lng, lat] = ol.proj.toLonLat([value.x, value.y]); return new L.LatLng(lat, lng); },
305
- transformation: {
306
- transform(value, scale) { return new L.Point((value.x / circumference + 0.5) * scale, (0.5 - value.y / circumference) * scale); },
307
- untransform(value, scale) { return new L.Point((value.x / scale - 0.5) * circumference, (0.5 - value.y / scale) * circumference); },
308
- },
309
- };
310
-
311
- L.DomUtil = {
312
- create(tag, className = '', parent) {
313
- const element = document.createElement(tag);
314
- element.className = className; parent?.append(element); return element;
315
- },
316
- setPosition(element, value) { element._leaflet_pos = point(value); element.style.transform = `translate(${value.x}px,${value.y}px)`; },
317
- getPosition(element) { return element._leaflet_pos || new L.Point(0, 0); },
318
- };
319
- const domListeners = new WeakMap();
320
- function domEvent(add, element, types, fn, context) {
321
- if (typeof types === 'object') {
322
- for (const [type, handler] of Object.entries(types)) domEvent(add, element, type, handler, fn);
323
- return L.DomEvent;
324
- }
325
- let listeners = domListeners.get(element);
326
- if (!listeners) domListeners.set(element, listeners = []);
327
- for (const type of types.split(/\s+/)) {
328
- const existing = listeners.find(item => item.type === type && item.fn === fn && item.context === context);
329
- if (add && !existing) {
330
- const handler = context ? fn.bind(context) : fn;
331
- listeners.push({ type, fn, context, handler });
332
- element.addEventListener(type, handler, { passive: false });
333
- } else if (!add && existing) {
334
- element.removeEventListener(type, existing.handler);
335
- listeners.splice(listeners.indexOf(existing), 1);
336
- }
337
- }
338
- return L.DomEvent;
339
- }
340
- const stopPropagation = event => event.stopPropagation();
341
- L.DomEvent = {
342
- on: (...args) => domEvent(true, ...args),
343
- off: (...args) => domEvent(false, ...args),
344
- stop(event) { event.preventDefault(); event.stopPropagation(); },
345
- disableClickPropagation(element) {
346
- element.classList.add('leaflet-control');
347
- L.DomEvent.on(element, 'click dblclick pointerdown mousedown touchstart contextmenu', stopPropagation);
348
- return this;
349
- },
350
- disableScrollPropagation(element) { return this.on(element, 'wheel', stopPropagation); },
351
- };
352
- L.Handler = Class.extend({
353
- initialize(map) { this._map = map; },
354
- enable() { if (!this._enabled) { this._enabled = true; this.addHooks?.(); } return this; },
355
- disable() { if (this._enabled) { this._enabled = false; this.removeHooks?.(); } return this; },
356
- enabled() { return Boolean(this._enabled); },
357
- moving() { return Boolean(this._moving); },
358
- });
359
- L.Layer = L.Evented.extend({
360
- initialize(options) { setOptions(this, options); },
361
- addTo(map) { map.addLayer(this); return this; },
362
- remove() { return this.removeFrom(this._map || this._mapToAdd); },
363
- removeFrom(map) { map?.removeLayer(this); return this; },
364
- getAttribution() { return this.options.attribution; },
365
- getPane(name) { return this._map.getPane(name || this.options.pane); },
366
- _layerAdd({ target: map }) {
367
- if (!map.hasLayer(this)) return;
368
- this._map = map;
369
- if (this.getEvents) { this._mapEvents = this.getEvents(); map.on(this._mapEvents, this); }
370
- this.onAdd?.(map);
371
- this.fire('add'); map.fire('layeradd', { layer: this });
372
- },
373
- });
374
- L.LayerGroup = L.Layer.extend({
375
- initialize(layers = [], options) {
376
- setOptions(this, options); this._layers = {};
377
- for (const layer of layers) this.addLayer(layer);
378
- },
379
- addLayer(layer) { this._layers[stamp(layer)] = layer; this._map?.addLayer(layer); return this; },
380
- removeLayer(layer) { delete this._layers[stamp(layer)]; this._map?.removeLayer(layer); return this; },
381
- hasLayer(layer) { return stamp(layer) in this._layers; },
382
- eachLayer(fn, context) { for (const layer of Object.values(this._layers)) fn.call(context || this, layer); return this; },
383
- onAdd(map) { this.eachLayer(map.addLayer, map); },
384
- onRemove(map) { this.eachLayer(map.removeLayer, map); },
385
- });
386
- // Declare the hierarchy before the plugin installs its instanceof checks and hooks.
387
- for (const [name, parent] of [
388
- ['GridLayer', L.Layer], ['Marker', L.Layer], ['Path', L.Layer],
389
- ['Polyline', null], ['Polygon', null], ['CircleMarker', null], ['Circle', null],
390
- ['ImageOverlay', L.Layer], ['VideoOverlay', null], ['SVGOverlay', null],
391
- ['Popup', L.Layer], ['Tooltip', L.Layer], ['DivIcon', Class],
392
- ]) {
393
- const base = parent || ({ Polyline: L.Path, Polygon: L.Polyline, CircleMarker: L.Path,
394
- Circle: L.CircleMarker, VideoOverlay: L.ImageOverlay, SVGOverlay: L.ImageOverlay })[name];
395
- L[name] = base.extend({
396
- initialize() { throw new Error(`L.${name} is not supported by the OpenLayers bindings; use L.Layer with a source`); },
397
- getEvents() { return {}; },
398
- });
399
- }
400
-
401
- L.Map = L.Evented.extend({
402
- options: { crs, minZoom: 0, maxZoom: 24, zoomSnap: 1, touchZoom: true,
403
- scrollWheelZoom: true, keyboard: true, doubleClickZoom: true,
404
- wheelDebounceTime: 40, wheelPxPerZoomLevel: 60 },
405
- initialize(target, options) {
406
- setOptions(this, options);
407
- this._container = typeof target === 'string' ? document.getElementById(target) : target;
408
- if (!this._container) throw new Error('Map container not found');
409
- stamp(this._container);
410
- this._container.classList.add('openlayers-city-mapper');
411
- this._layers = {}; this._panes = {}; this._handlers = []; this._controls = new Set();
412
- const center = crs.project(this.options.center || [0, 0]);
413
- this.ol = new ol.Map({
414
- target: this._container, controls: [new ol.control.Zoom(), new ol.control.Attribution({collapsible: false })],
415
- interactions: [],
416
- view: new ol.View({ center: [center.x, center.y], zoom: this.options.zoom || 0,
417
- minZoom: this.getMinZoom(), maxZoom: this.getMaxZoom() }),
418
- });
419
- for (const control of this.ol.getControls().getArray()) {
420
- control.element.classList.add('leaflet-control');
421
- for (const button of control.element.querySelectorAll('button[title]')) button.setAttribute('aria-label', button.title);
422
- if (control instanceof ol.control.Zoom) {
423
- for (const [selector, delta] of [['.ol-zoom-in', 1], ['.ol-zoom-out', -1]]) {
424
- control.element.querySelector(selector).addEventListener('click', event => {
425
- L.DomEvent.stop(event);
426
- event.stopImmediatePropagation();
427
- this._zoomBy(delta * (event.shiftKey ? 3 : 1));
428
- }, true);
429
- }
430
- }
431
- }
432
- this._loaded = true;
433
- this._zoomAnimated = false;
434
- this.dragging = new L.Handler(this).enable();
435
- this.touchZoom = new L.Handler(this);
436
- if (this.options.touchZoom) this.touchZoom.enable();
437
- this._handlers.push(this.dragging, this.touchZoom);
438
- for (const name of ['scrollWheelZoom', 'keyboard', 'doubleClickZoom']) this.addHandler(name, L.Handler);
439
- if (!this._container.hasAttribute('tabindex')) this._container.tabIndex = 0;
440
- const view = this.ol.getView();
441
- this._viewZoom = view.getZoom();
442
- view.on(['change:center', 'change:resolution'], () => {
443
- if (this._settingView) return;
444
- const zoomChanged = view.getZoom() !== this._viewZoom;
445
- if (!this._viewMoving) { this._viewMoving = true; this.fire('movestart'); }
446
- if (zoomChanged && !this._viewZooming) { this._viewZooming = true; this.fire('zoomstart'); }
447
- this.fire('move');
448
- if (zoomChanged) this.fire('zoom');
449
- this._viewZoom = view.getZoom();
450
- if (!view.getAnimating() && !view.getInteracting()) this._endViewChange();
451
- });
452
- this.ol.on('moveend', () => this._endViewChange());
453
- // OpenLayers interactions need the pitched ground coordinate, not the flat viewport.
454
- this.ol.getCoordinateFromPixel = pixel => {
455
- const coordinate = crs.project(this.containerPointToLatLng(point(pixel)));
456
- return [coordinate.x, coordinate.y];
457
- };
458
- this._wheelDelta = 0;
459
- this.ol.addInteraction(new ol.interaction.Interaction({
460
- handleEvent: event => {
461
- const original = event.originalEvent;
462
- if (original.target.closest('.ol-control')) return true;
463
- if (event.type === 'dblclick' && this.doubleClickZoom.enabled()) {
464
- this._zoomBy(original.shiftKey ? -1 : 1, point(event.pixel));
465
- } else if (event.type === 'wheel' && this.scrollWheelZoom.enabled() && !original.shiftKey) {
466
- const ratio = window.devicePixelRatio || 1;
467
- const factor = /Linux/.test(navigator.platform) && /Chrome/.test(navigator.userAgent) ? ratio
468
- : /Mac/.test(navigator.platform) ? 3 * ratio : 2 * ratio;
469
- this._wheelDelta -= original.deltaY * (original.deltaMode === 1 ? 20
470
- : original.deltaMode === 2 ? 60 : 1 / factor);
471
- this._wheelPixel = point(event.pixel);
472
- this._wheelStart ??= performance.now();
473
- clearTimeout(this._wheelTimer);
474
- this._wheelTimer = setTimeout(() => {
475
- const magnitude = 4 * Math.log2(2 / (1 + Math.exp(-Math.abs(this._wheelDelta /
476
- (this.options.wheelPxPerZoomLevel * 4)))));
477
- const snap = this.options.zoomSnap;
478
- const delta = Math.sign(this._wheelDelta) * (snap ? Math.ceil(magnitude / snap) * snap : magnitude);
479
- this._wheelDelta = 0;
480
- this._wheelStart = null;
481
- if (this._loaded && this.scrollWheelZoom.enabled()) this._zoomBy(delta,
482
- this.options.scrollWheelZoom === 'center' ? undefined : this._wheelPixel);
483
- }, Math.max(0, this.options.wheelDebounceTime - (performance.now() - this._wheelStart)));
484
- } else return true;
485
- original.preventDefault();
486
- return false;
487
- },
488
- }));
489
- this._controlCorners = {};
490
- for (const position of ['topleft', 'topright', 'bottomleft', 'bottomright']) {
491
- this._controlCorners[position] = L.DomUtil.create('div',
492
- `ol-city-corner ol-city-${position}`, this.ol.getOverlayContainerStopEvent());
493
- }
494
- const attributionSource = new ol.source.Vector();
495
- this.ol.addLayer(new ol.layer.Vector({ source: attributionSource }));
496
- const updateAttribution = () => {
497
- const values = new Set(['<a href="https://openlayers.org/">OpenLayers</a>']);
498
- this.eachLayer(layer => { if (layer.getAttribution?.()) values.add(layer.getAttribution()); });
499
- attributionSource.setAttributions([...values]);
500
- };
501
- this.on('layeradd layerremove', updateAttribution);
502
- updateAttribution();
503
- let anchor, pointerId;
504
- const pixel = event => {
505
- const rect = this._container.getBoundingClientRect();
506
- return new L.Point(event.clientX - rect.left, event.clientY - rect.top);
507
- };
508
- this._onPointer = event => {
509
- if (event.type === 'pointerdown') {
510
- if (!this.dragging.enabled() || event.button !== 0 || !event.isPrimary || event.target.closest('.ol-control')) return;
511
- anchor = this.containerPointToLatLng(pixel(event)); pointerId = event.pointerId;
512
- this._moved = this.dragging._moving = false;
513
- this._container.focus({ preventScroll: true });
514
- this._container.setPointerCapture(pointerId);
515
- } else if (event.pointerId === pointerId && event.type === 'pointermove' && this.dragging.enabled()) {
516
- const delta = this.project(anchor).subtract(this.project(this.containerPointToLatLng(pixel(event))));
517
- if (Math.hypot(delta.x, delta.y) > 1) this._moved = this.dragging._moving = true;
518
- this.setView(this.unproject(this.project(this.getCenter()).add(delta)), this.getZoom());
519
- } else if (event.pointerId === pointerId && event.type !== 'pointermove') {
520
- if (this._container.hasPointerCapture(pointerId)) this._container.releasePointerCapture(pointerId);
521
- anchor = null; pointerId = null;
522
- this.dragging._moving = false;
523
- }
524
- };
525
- this._onKey = event => {
526
- if (!this.keyboard.enabled() || event.target !== this._container || event.altKey || event.ctrlKey || event.metaKey) return;
527
- const offsets = { ArrowLeft: [-80, 0], ArrowRight: [80, 0], ArrowUp: [0, -80], ArrowDown: [0, 80] };
528
- if (offsets[event.key]) this.panBy(offsets[event.key]);
529
- else if (['+', '=', '-', '_'].includes(event.key)) this._zoomBy(
530
- (['-', '_'].includes(event.key) ? -1 : 1) * (event.shiftKey ? 3 : 1));
531
- else return;
532
- event.preventDefault();
533
- };
534
- L.DomEvent.on(this._container, 'pointerdown pointermove pointerup pointercancel', this._onPointer);
535
- L.DomEvent.on(this._container, 'keydown', this._onKey);
536
- this.ol.on('change:size', () => this.fire('resize', { newSize: this.getSize() }));
537
- this.ol.updateSize();
538
- },
539
- getContainer() { return this._container; },
540
- getSize() { return point(this.ol.getSize() || [this._container.clientWidth, this._container.clientHeight]); },
541
- getCenter() { return crs.unproject(point(this.ol.getView().getCenter())); },
542
- getZoom() { return this.ol.getView().getZoom(); },
543
- getMinZoom() { return this.options.minZoom; },
544
- getMaxZoom() { return this.options.maxZoom; },
545
- getZoomScale(to, from = this.getZoom()) { return 2 ** (to - from); },
546
- getScaleZoom(scale, from = this.getZoom()) { return from + Math.log2(scale); },
547
- _limitZoom(zoom) { return Math.max(this.getMinZoom(), Math.min(this.getMaxZoom(), zoom)); },
548
- project(value, zoom = this.getZoom()) { return crs.transformation.transform(crs.project(value), crs.scale(zoom)); },
549
- unproject(value, zoom = this.getZoom()) { return crs.unproject(crs.transformation.untransform(point(value), crs.scale(zoom))); },
550
- getPixelOrigin() { return this.project(this.getCenter()).subtract(this.getSize().divideBy(2)); },
551
- _getMapPanePos() { return new L.Point(0, 0); },
552
- latLngToLayerPoint(value) { return this.project(value).subtract(this.getPixelOrigin()); },
553
- layerPointToLatLng(value) { return this.unproject(point(value).add(this.getPixelOrigin())); },
554
- layerPointToContainerPoint(value) { return point(value).clone(); },
555
- containerPointToLayerPoint(value) { return point(value).clone(); },
556
- latLngToContainerPoint(value) { return this.layerPointToContainerPoint(this.latLngToLayerPoint(value)); },
557
- containerPointToLatLng(value) { return this.layerPointToLatLng(this.containerPointToLayerPoint(value)); },
558
- getBounds() { return new L.LatLngBounds(this.containerPointToLatLng([0, 0]), this.containerPointToLatLng(this.getSize())); },
559
- _endViewChange() {
560
- if (!this._viewMoving) return;
561
- const zooming = this._viewZooming;
562
- this._viewMoving = this._viewZooming = false;
563
- if (zooming) this.fire('zoomend');
564
- this.fire('moveend');
565
- },
566
- _zoomBy(delta, pixel) {
567
- if (!delta || this.ol.getView().getAnimating()) return this;
568
- const zoom = this._limitZoom(this.getZoom() + delta);
569
- return pixel ? this.setZoomAround(pixel, zoom, { animate: true })
570
- : this.setZoom(zoom, { animate: true });
571
- },
572
- setView(center, zoom = this.getZoom(), options = {}) {
573
- center = latLng(center); zoom = this._limitZoom(zoom);
574
- if ((options.animate === true || options.zoom?.animate === true) && zoom !== this.getZoom()) {
575
- const scale = this.getZoomScale(zoom), projected = crs.project(center);
576
- // Leaflet animates CSS scale, not resolution; convert the same native CSS easing.
577
- this.ol.getView().animate({
578
- center: [projected.x, projected.y], zoom, duration: 250,
579
- easing: time => {
580
- zoomTiming.currentTime = time;
581
- const progress = zoomTiming.effect.getComputedTiming().progress;
582
- return (1 - 1 / (1 + (scale - 1) * progress)) / (1 - 1 / scale);
583
- },
584
- });
585
- return this;
586
- }
587
- this.ol.getView().cancelAnimations();
588
- this._endViewChange();
589
- const zoomChanged = zoom !== this.getZoom(), moved = !center.equals(this.getCenter());
590
- if (!zoomChanged && !moved) return this;
591
- this.fire('movestart');
592
- if (zoomChanged) this.fire('zoomstart');
593
- this._settingView = true;
594
- const projected = crs.project(center);
595
- this.ol.getView().setCenter([projected.x, projected.y]);
596
- this.ol.getView().setZoom(zoom);
597
- this._viewZoom = this.getZoom();
598
- this._settingView = false;
599
- if (zoomChanged) this.fire('zoom');
600
- this.fire('move').fire('viewreset');
601
- if (zoomChanged) this.fire('zoomend');
602
- return this.fire('moveend');
603
- },
604
- setZoom(zoom, options) { return this.setView(this.getCenter(), zoom, options); },
605
- panTo(center) { return this.setView(center); },
606
- panBy(offset) { return this.panTo(this.unproject(this.project(this.getCenter()).add(point(offset)))); },
607
- setZoomAround(value, zoom, options) {
608
- const anchor = value instanceof L.Point ? value : this.latLngToContainerPoint(value);
609
- const half = this.getSize().divideBy(2), scale = this.getZoomScale(zoom);
610
- return this.setView(this.unproject(this.project(this.getCenter()).add(anchor.subtract(half).multiplyBy(1 - 1 / scale))), zoom, options);
611
- },
612
- getBoundsZoom(bounds, inside, padding = [0, 0]) {
613
- const size = this.project(bounds.getSouthEast()).subtract(this.project(bounds.getNorthWest()));
614
- const available = this.getSize().subtract(point(padding));
615
- const scale = Math[inside ? 'max' : 'min'](available.x / size.x, available.y / size.y);
616
- return this._limitZoom(this.getScaleZoom(scale));
617
- },
618
- _getBoundsCenterZoom(bounds, options = {}) { return { center: bounds.getCenter(), zoom: this.getBoundsZoom(bounds, false, options.padding) }; },
619
- fitBounds(bounds, options) { const view = this._getBoundsCenterZoom(bounds, options); return this.setView(view.center, view.zoom); },
620
- _limitCenter(center) { return center; },
621
- _limitOffset(offset) { return offset; },
622
- _tryAnimatedPan() { return false; },
623
- panInside(value) { if (!this.getBounds().contains(value)) this.panTo(value); return this; },
624
- whenReady(fn, context) { if (this._loaded) fn.call(context || this, { target: this }); else this.once('load', fn, context); return this; },
625
- addLayer(layer) {
626
- const id = stamp(layer);
627
- if (this._layers[id]) return this;
628
- this._layers[id] = layer; layer._mapToAdd = this;
629
- layer.beforeAdd?.(this);
630
- this.whenReady(layer._layerAdd, layer);
631
- return this;
632
- },
633
- removeLayer(layer) {
634
- if (!this.hasLayer(layer)) return this;
635
- layer.onRemove?.(this);
636
- if (layer._mapEvents) this.off(layer._mapEvents, layer);
637
- delete this._layers[stamp(layer)];
638
- this.fire('layerremove', { layer }); layer.fire('remove');
639
- layer._map = layer._mapToAdd = null;
640
- return this;
641
- },
642
- hasLayer(layer) { return Boolean(this._layers[stamp(layer)]); },
643
- eachLayer(fn, context) { for (const layer of Object.values(this._layers)) fn.call(context || this, layer); return this; },
644
- createPane(name, parent = this._container) { return this._panes[name] = L.DomUtil.create('div', `leaflet-pane leaflet-${name}`, parent); },
645
- getPane(name = 'overlayPane') { return this._panes[name]; },
646
- addHandler(name, Handler) { this[name] = new Handler(this); this._handlers.push(this[name]); if (this.options[name]) this[name].enable(); return this; },
647
- invalidateSize() { this.ol.updateSize(); return this; },
648
- _draggableMoved() { return Boolean(this._moved); },
649
- remove() {
650
- clearTimeout(this._wheelTimer);
651
- this.ol.getView().cancelAnimations();
652
- this.fire('unload');
653
- for (const control of [...this._controls]) control.remove();
654
- this.eachLayer(this.removeLayer, this);
655
- for (const handler of this._handlers) handler.disable();
656
- L.DomEvent.off(this._container, 'pointerdown pointermove pointerup pointercancel', this._onPointer);
657
- L.DomEvent.off(this._container, 'keydown', this._onKey);
658
- this.ol.setTarget(undefined); this.ol.dispose();
659
- for (const corner of Object.values(this._controlCorners)) corner.remove();
660
- for (const pane of Object.values(this._panes)) pane.remove();
661
- this._loaded = false;
662
- delete this._container._leaflet_id;
663
- return this.off();
664
- },
665
- });
666
-
667
- L.Control = Class.extend({
668
- options: { position: 'topright' },
669
- initialize(options) { setOptions(this, options); },
670
- getPosition() { return this.options.position; },
671
- getContainer() { return this._container; },
672
- addTo(map) {
673
- this.remove(); this._map = map;
674
- this._container = this.onAdd(map);
675
- if (L.Control.Rotate && this instanceof L.Control.Rotate) {
676
- this._container.classList.add('ol-city-rotate');
677
- }
678
- this._container.classList.add('leaflet-control', 'ol-control', 'ol-city-control');
679
- this._olControl = new ol.control.Control({
680
- element: this._container, target: map._controlCorners[this.getPosition()],
681
- });
682
- map.ol.addControl(this._olControl);
683
- map._controls.add(this);
684
- return this;
685
- },
686
- remove() {
687
- if (!this._map) return this;
688
- const map = this._map;
689
- this.onRemove?.(map); this._container?.remove();
690
- map.ol.removeControl(this._olControl);
691
- map._controls.delete(this); this._map = null;
692
- return this;
693
- },
694
- });
695
- L.Control.Layers = L.Control.extend({
696
- options: { collapsed: true },
697
- initialize(base = {}, overlays = {}, options) {
698
- setOptions(this, options); this._layers = [];
699
- for (const [name, layer] of Object.entries(base)) this._layers.push({ name, layer, overlay: false });
700
- for (const [name, layer] of Object.entries(overlays)) this._layers.push({ name, layer, overlay: true });
701
- },
702
- onAdd(map) {
703
- const container = L.DomUtil.create('div', 'ol-city-layers');
704
- L.DomEvent.disableClickPropagation(container); L.DomEvent.disableScrollPropagation(container);
705
- const list = L.DomUtil.create('section', '', container);
706
- list.setAttribute('aria-label', 'Map layers');
707
- const base = L.DomUtil.create('div', '', list);
708
- L.DomUtil.create('hr', '', list);
709
- const overlays = L.DomUtil.create('div', '', list);
710
- this._inputs = [];
711
- for (const entry of this._layers) {
712
- const label = L.DomUtil.create('label', '', entry.overlay ? overlays : base);
713
- const input = L.DomUtil.create('input', 'leaflet-control-layers-selector', label);
714
- input.type = entry.overlay ? 'checkbox' : 'radio';
715
- input.name = `base-${stamp(this)}`; input.layerId = stamp(entry.layer);
716
- input.checked = map.hasLayer(entry.layer);
717
- label.append(document.createTextNode(` ${entry.name}`));
718
- this._inputs.push(input);
719
- input.onchange = () => {
720
- const checked = input.checked;
721
- if (!entry.overlay) for (const other of this._layers) {
722
- if (!other.overlay && other !== entry) map.removeLayer(other.layer);
723
- }
724
- if (checked) map.addLayer(entry.layer); else map.removeLayer(entry.layer);
725
- };
726
- }
727
- map.on('layeradd layerremove', this._update, this);
728
- return container;
729
- },
730
- _update() { for (const input of this._inputs) input.checked = Boolean(this._map._layers[input.layerId]); },
731
- onRemove(map) { map.off('layeradd layerremove', this._update, this); },
732
- });
733
-
734
- // Native layer contracts consumed by the plugin's WebGPU renderer.
735
- const bounds = value => value instanceof L.LatLngBounds ? value : new L.LatLngBounds(value);
736
- const coordinates = values => values.map(value => Array.isArray(value) && typeof value[0] !== 'number'
737
- ? coordinates(value) : latLng(value));
738
- const image = (url, options, className) => {
739
- const element = document.createElement('img');
740
- element.className = className;
741
- if (options.crossOrigin) element.crossOrigin = options.crossOrigin === true ? '' : options.crossOrigin;
742
- element.src = url;
743
- element.draggable = false;
744
- return element;
745
- };
746
-
747
- L.Layer.include({
748
- bindPopup(content) {
749
- this._popupContent = content;
750
- this.off('click', this.openPopup, this).on('click', this.openPopup, this);
751
- this.once('remove', this.closePopup, this);
752
- if (this instanceof L.LayerGroup) this.eachLayer(layer => layer.bindPopup(content));
753
- return this;
754
- },
755
- openPopup(event) {
756
- if (!this._map) return this;
757
- this.closePopup();
758
- const location = event?.latlng || this.getLatLng?.() || this.getBounds?.().getCenter();
759
- if (!location) throw new Error('Popup requires a location');
760
- const element = L.DomUtil.create('div', 'ol-city-popup leaflet-popup');
761
- element.setAttribute('role', 'dialog');
762
- const close = L.DomUtil.create('button', '', element);
763
- close.type = 'button'; close.textContent = '\u00d7'; close.setAttribute('aria-label', 'Close popup');
764
- close.onclick = () => this.closePopup();
765
- const content = L.DomUtil.create('div', '', element);
766
- const value = typeof this._popupContent === 'function' ? this._popupContent(this) : this._popupContent;
767
- if (value instanceof Node) content.append(value); else content.innerHTML = value;
768
- L.DomEvent.disableClickPropagation(element);
769
- this._popupOverlay = new ol.Overlay({ element, positioning: 'bottom-center', offset: [0, -12] });
770
- this._map.ol.addOverlay(this._popupOverlay);
771
- this._popupLocation = latLng(location);
772
- this._map.on('move zoom rotate pitch', this._positionPopup, this);
773
- this._positionPopup();
774
- return this;
775
- },
776
- _positionPopup() {
777
- const screen = this._map.latLngToContainerPoint(this._popupLocation);
778
- this._popupOverlay.setPosition(this._map.ol.getCoordinateFromPixelInternal([screen.x, screen.y]));
779
- },
780
- closePopup() {
781
- if (this._popupOverlay) {
782
- this._map?.off('move zoom rotate pitch', this._positionPopup, this);
783
- this._map?.ol.removeOverlay(this._popupOverlay);
784
- this._popupOverlay = null;
785
- }
786
- return this;
787
- },
788
- bindTooltip(content) {
789
- this._tooltipContent = content;
790
- this.on('mouseover', this._showTooltip, this).on('mouseout remove', this._hideTooltip, this);
791
- if (this instanceof L.LayerGroup) this.eachLayer(layer => layer.bindTooltip(content));
792
- return this;
793
- },
794
- _showTooltip(event) {
795
- this._hideTooltip();
796
- if (!this._map || !event.latlng) return;
797
- const element = this._tooltipElement = L.DomUtil.create('div', 'ol-city-tooltip leaflet-tooltip', this._map.getContainer());
798
- element.setAttribute('role', 'tooltip');
799
- const value = typeof this._tooltipContent === 'function' ? this._tooltipContent(this) : this._tooltipContent;
800
- if (value instanceof Node) element.append(value); else element.innerHTML = value;
801
- const pixel = this._map.latLngToContainerPoint(event.latlng);
802
- element.style.left = `${pixel.x + 12}px`; element.style.top = `${pixel.y + 12}px`;
803
- },
804
- _hideTooltip() { this._tooltipElement?.remove(); this._tooltipElement = null; },
805
- });
806
- L.LayerGroup.include({
807
- getLayers() { return Object.values(this._layers); },
808
- getBounds() {
809
- const result = new L.LatLngBounds();
810
- this.eachLayer(layer => result.extend(layer.getBounds?.() || layer.getLatLng()));
811
- return result;
812
- },
813
- });
814
- L.FeatureGroup = L.LayerGroup.extend({});
815
-
816
- L.Path.include({
817
- options: { pane: 'overlayPane', interactive: true, stroke: true, color: '#3388ff',
818
- weight: 3, opacity: 1, lineCap: 'round', lineJoin: 'round', fill: false, fillOpacity: 0.2 },
819
- initialize(options) { L.Util.setOptions(this, options); },
820
- setStyle(options) { L.Util.setOptions(this, options); return this.redraw(); },
821
- redraw() { return this; },
822
- bringToFront() { return this; },
823
- bringToBack() { return this; },
824
- });
825
- L.Polyline.include({
826
- initialize(values, options) { L.Util.setOptions(this, options); this.setLatLngs(values); },
827
- getLatLngs() { return this._latlngs; },
828
- setLatLngs(values) { this._latlngs = coordinates(values); return this.redraw(); },
829
- addLatLng(value) { this._latlngs.push(latLng(value)); return this.redraw(); },
830
- getBounds() {
831
- const result = new L.LatLngBounds();
832
- const visit = values => { for (const value of values) Array.isArray(value) ? visit(value) : result.extend(value); };
833
- visit(this._latlngs);
834
- return result;
835
- },
836
- _project() {
837
- this._rings = [];
838
- const visit = values => {
839
- if (!values.length) return;
840
- if (Array.isArray(values[0])) values.forEach(visit);
841
- else this._rings.push(values.map(value => this._map.latLngToLayerPoint(value)));
842
- };
843
- visit(this._latlngs);
844
- },
845
- closestLayerPoint(value) {
846
- this._project();
847
- const paths = this._rings.map(ring => ring.map(value => [value.x, value.y]));
848
- const closest = new ol.geom.MultiLineString(paths).getClosestPoint([value.x, value.y]);
849
- return point(closest);
850
- },
851
- });
852
- L.Polyline.mergeOptions(L.Path.prototype.options);
853
- L.Polygon.include({ initialize: L.Polyline.prototype.initialize });
854
- L.Polygon.mergeOptions(L.Path.prototype.options);
855
- L.Polygon.mergeOptions({ fill: true });
856
- L.Rectangle = L.Polygon.extend({
857
- initialize(value, options) { L.Util.setOptions(this, options); this.setBounds(value); },
858
- setBounds(value) {
859
- value = bounds(value);
860
- return this.setLatLngs([value.getSouthWest(), value.getNorthWest(), value.getNorthEast(), value.getSouthEast()]);
861
- },
862
- });
863
- L.CircleMarker.include({
864
- options: { ...L.Path.prototype.options, fill: true, radius: 10 },
865
- initialize(value, options) { L.Util.setOptions(this, options); this._latlng = latLng(value); },
866
- getLatLng() { return this._latlng; },
867
- setLatLng(value) { this._latlng = latLng(value); this.fire('move', { latlng: this._latlng }); return this.redraw(); },
868
- getRadius() { return this.options.radius; },
869
- setRadius(radius) { this.options.radius = radius; return this.redraw(); },
870
- });
871
- L.Circle.include({
872
- initialize: L.CircleMarker.prototype.initialize,
873
- _project() {
874
- this._point = this._map.latLngToLayerPoint(this._latlng);
875
- this._radius = this._radiusY = this.getRadius() * this._map.options.crs.scale(this._map.getZoom()) /
876
- (2 * Math.PI * 6378137 * Math.cos(this._latlng.lat * Math.PI / 180));
877
- },
878
- getBounds() {
879
- const north = this.getRadius() / 6378137 * 180 / Math.PI;
880
- const east = north / Math.cos(this._latlng.lat * Math.PI / 180);
881
- return new L.LatLngBounds([this._latlng.lat - north, this._latlng.lng - east],
882
- [this._latlng.lat + north, this._latlng.lng + east]);
883
- },
884
- });
885
- L.Circle.mergeOptions(L.CircleMarker.prototype.options);
886
- L.GeoJSON = L.FeatureGroup.extend({
887
- initialize(data, options) {
888
- L.LayerGroup.prototype.initialize.call(this, [], options);
889
- if (data) this.addData(data);
890
- },
891
- addData(data) {
892
- const features = new ol.format.GeoJSON().readFeatures(data);
893
- const convert = (geometry, feature) => {
894
- const values = geometry.getCoordinates();
895
- const locations = value => typeof value[0] === 'number' ? [value[1], value[0]] : value.map(locations);
896
- switch (geometry.getType()) {
897
- case 'Point': return this.options.pointToLayer
898
- ? this.options.pointToLayer(feature, latLng(locations(values))) : new L.Marker(locations(values));
899
- case 'MultiPoint': return new L.FeatureGroup(values.map(value => convert(new ol.geom.Point(value), feature)));
900
- case 'LineString': case 'MultiLineString': return new L.Polyline(locations(values), this.options);
901
- case 'Polygon': case 'MultiPolygon': return new L.Polygon(locations(values), this.options);
902
- default: throw new Error(`Unsupported GeoJSON geometry: ${geometry.getType()}`);
903
- }
904
- };
905
- for (const feature of features) {
906
- const layer = convert(feature.getGeometry(), new ol.format.GeoJSON().writeFeatureObject(feature));
907
- this.addLayer(layer);
908
- }
909
- return this;
910
- },
911
- });
912
-
913
- L.GridLayer.include({
914
- options: { pane: 'tilePane', tileSize: 256, opacity: 1, minZoom: 0, maxZoom: 24, updateInterval: 200 },
915
- initialize(options) { L.Util.setOptions(this, options); this._tiles = {}; },
916
- getTileSize() { return typeof this.options.tileSize === 'number'
917
- ? new L.Point(this.options.tileSize, this.options.tileSize) : point(this.options.tileSize); },
918
- getEvents() { return { moveend: this._onMoveEnd, zoomend: this._onMoveEnd, resize: this._onMoveEnd }; },
919
- _initContainer() { this._container = L.DomUtil.create('div', '', this.getPane()); },
920
- onAdd() { this._initContainer(); this._onMoveEnd(); },
921
- onRemove() {
922
- for (const tile of Object.values(this._tiles)) this.fire('tileunload', { tile: tile.el, coords: tile.coords });
923
- this._tiles = {}; this._container?.remove();
924
- },
925
- _getTiledPixelBounds() {
926
- return new L.Bounds(this._map.project(this._map.getBounds().getNorthWest(), this._tileZoom),
927
- this._map.project(this._map.getBounds().getSouthEast(), this._tileZoom));
928
- },
929
- _onMoveEnd() {
930
- if (!this._map) return;
931
- this._tileZoom = Math.max(this.options.minZoom, Math.min(this.options.maxZoom, Math.round(this._map.getZoom())));
932
- const size = this.getTileSize(), extent = this._getTiledPixelBounds(this._map.getCenter());
933
- const min = extent.min.unscaleBy(size).floor(), max = extent.max.unscaleBy(size).floor();
934
- const retained = new Set(), limit = 2 ** this._tileZoom;
935
- for (let x = min.x; x <= max.x; x++) for (let y = min.y; y <= max.y; y++) {
936
- if (y < 0 || y >= limit) continue;
937
- const key = `${this._tileZoom}:${x}:${y}`;
938
- retained.add(key);
939
- if (this._tiles[key]) continue;
940
- const coords = new L.Point(x, y); coords.z = this._tileZoom;
941
- const tile = this._tiles[key] = { coords, current: true, loaded: 0 };
942
- const done = (error, element) => {
943
- if (this._tiles[key] !== tile) return;
944
- tile.el = element;
945
- if (error) { this.fire('tileerror', { error, tile: element, coords }); return; }
946
- tile.loaded = Date.now();
947
- this.fire('tileload', { tile: element, coords });
948
- this.fire('load');
949
- };
950
- tile.el = this.createTile(coords, done);
951
- if (this.createTile.length < 2) done(null, tile.el);
952
- }
953
- for (const [key, tile] of Object.entries(this._tiles)) {
954
- if (retained.has(key)) continue;
955
- delete this._tiles[key];
956
- this.fire('tileunload', { tile: tile.el, coords: tile.coords });
957
- }
958
- },
959
- _tileCoordsToNwSe(coords) {
960
- const size = this.getTileSize(), nw = new L.Point(coords.x * size.x, coords.y * size.y);
961
- return [this._map.unproject(nw, coords.z), this._map.unproject(nw.add(size), coords.z)];
962
- },
963
- setOpacity(opacity) { this.options.opacity = opacity; return this; },
964
- setZIndex(zIndex) { this.options.zIndex = zIndex; return this; },
965
- redraw() { if (this._map) { this.onRemove(); this._onMoveEnd(); } return this; },
966
- });
967
- L.TileLayer = L.GridLayer.extend({
968
- initialize(url, options) {
969
- L.GridLayer.prototype.initialize.call(this, options); this.setUrl(url);
970
- },
971
- setUrl(url) { this._url = url; this._source = new ol.source.XYZ({ url, crossOrigin: 'anonymous' }); return this.redraw(); },
972
- createTile(coords, done) {
973
- const url = this._source.getTileUrlFunction()([coords.z, coords.x, coords.y], 1, ol.proj.get('EPSG:3857'));
974
- const tile = image(url, this.options, '');
975
- tile.onload = () => done(null, tile);
976
- tile.onerror = () => done(new Error(`Unable to load tile: ${url}`), tile);
977
- return tile;
978
- },
979
- });
980
- L.TileLayer.WMS = L.TileLayer.extend({
981
- setUrl(url) {
982
- this._url = url;
983
- this._source = new ol.source.TileWMS({ url, crossOrigin: 'anonymous', params: {
984
- LAYERS: this.options.layers, FORMAT: this.options.format || 'image/png',
985
- TRANSPARENT: this.options.transparent || false,
986
- } });
987
- return this.redraw();
988
- },
989
- });
990
- L.ImageOverlay.include({
991
- options: { pane: 'overlayPane', opacity: 1, interactive: false },
992
- initialize(url, value, options) { L.Util.setOptions(this, options); this._url = url; this._bounds = bounds(value); },
993
- _initImage() {
994
- this._image = image(this._url, this.options, '');
995
- this._image.onload = () => this.fire('load');
996
- this._image.onerror = () => this.fire('error', { error: new Error(`Unable to load image: ${this._url}`) });
997
- },
998
- getBounds() { return this._bounds; },
999
- setBounds(value) { this._bounds = bounds(value); return this; },
1000
- setUrl(url) { this._url = url; if (this._image) this._image.src = url; return this; },
1001
- setOpacity(opacity) { this.options.opacity = opacity; return this; },
1002
- setZIndex(zIndex) { this.options.zIndex = zIndex; return this; },
1003
- });
1004
-
1005
- L.Icon = L.Evented.extend({
1006
- initialize(options) { L.Util.setOptions(this, options); },
1007
- createIcon() { return this._create('icon'); },
1008
- createShadow() { return this.options.shadowUrl ? this._create('shadow') : null; },
1009
- _create(kind) {
1010
- const element = image(this.options[`${kind}Url`], this.options, `leaflet-marker-${kind}`);
1011
- const size = this.options[`${kind}Size`], anchor = this.options[`${kind}Anchor`] || this.options.iconAnchor;
1012
- if (size) { element.style.width = `${size[0]}px`; element.style.height = `${size[1]}px`; }
1013
- if (anchor) { element.style.marginLeft = `${-anchor[0]}px`; element.style.marginTop = `${-anchor[1]}px`; }
1014
- return element;
1015
- },
1016
- });
1017
- L.Icon.Default = L.Icon.extend({
1018
- options: {
1019
- iconUrl: 'https://unpkg.com/leaflet@1.9/dist/images/marker-icon.png',
1020
- shadowUrl: 'https://unpkg.com/leaflet@1.9/dist/images/marker-shadow.png',
1021
- iconSize: [25, 41], iconAnchor: [12, 41], shadowSize: [41, 41], shadowAnchor: [12, 41],
1022
- },
1023
- });
1024
- L.DivIcon = L.Icon.extend({
1025
- createIcon() {
1026
- const element = L.DomUtil.create('div', `leaflet-marker-icon ${this.options.className || ''}`);
1027
- if (this.options.html instanceof Node) element.append(this.options.html); else element.innerHTML = this.options.html || '';
1028
- const size = this.options.iconSize || [12, 12], anchor = this.options.iconAnchor || size.map(value => value / 2);
1029
- element.style.cssText = `width:${size[0]}px;height:${size[1]}px;margin-left:${-anchor[0]}px;margin-top:${-anchor[1]}px`;
1030
- return element;
1031
- },
1032
- createShadow() { return null; },
1033
- });
1034
- L.Marker.include({
1035
- options: { pane: 'markerPane', icon: new L.Icon.Default(), opacity: 1, interactive: true, zIndexOffset: 0 },
1036
- initialize(value, options) { L.Util.setOptions(this, options); this._latlng = latLng(value); },
1037
- getLatLng() { return this._latlng; },
1038
- setLatLng(value) { this._latlng = latLng(value); this.update(); return this.fire('move', { latlng: this._latlng }); },
1039
- setIcon(icon) { this.options.icon = icon; if (this._map) { this.onRemove(); this.onAdd(); } return this; },
1040
- getEvents() { return { zoom: this.update, viewreset: this.update }; },
1041
- onAdd() {
1042
- this._icon = this.options.icon.createIcon(); this._shadow = this.options.icon.createShadow();
1043
- this._icon.tabIndex = 0; this._icon.setAttribute('role', 'button');
1044
- this._icon.setAttribute('aria-label', this.options.title || 'Marker');
1045
- this._icon.title = this.options.title || '';
1046
- for (const element of [this._shadow, this._icon]) if (element) this.getPane().append(element);
1047
- this._initInteraction(); this.update();
1048
- },
1049
- onRemove() { this.dragging?.disable(); this._icon?.remove(); this._shadow?.remove(); },
1050
- update() { if (this._map && this._icon) this._setPos(this._map.latLngToLayerPoint(this._latlng)); return this; },
1051
- _setPos(value) {
1052
- for (const element of [this._icon, this._shadow]) if (element) L.DomUtil.setPosition(element, value);
1053
- this._zIndex = value.y + this.options.zIndexOffset; this._updateZIndex(0);
1054
- },
1055
- _updateZIndex(offset) { if (this._icon) this._icon.style.zIndex = this._zIndex + offset; },
1056
- setOpacity(opacity) { this.options.opacity = opacity; return this; },
1057
- setZIndexOffset(value) { this.options.zIndexOffset = value; return this.update(); },
1058
- _initInteraction() {
1059
- this._icon._cityLayer = this;
1060
- const drag = this.dragging = new L.Handler(this._map);
1061
- const icon = this._icon;
1062
- L.DomEvent.on(icon, 'pointerdown', event => {
1063
- this._map._moved = false;
1064
- event.stopPropagation();
1065
- });
1066
- L.DomEvent.on(icon, 'keydown', event => {
1067
- if (event.key !== 'Enter' && event.key !== ' ') return;
1068
- L.DomEvent.stop(event);
1069
- this.fire('click', { latlng: this._latlng, originalEvent: event });
1070
- });
1071
- drag._marker = this;
1072
- drag._draggable = new L.Evented();
1073
- drag._onDrag = function () {
1074
- const marker = this._marker;
1075
- marker._latlng = marker._map.layerPointToLatLng(L.DomUtil.getPosition(marker._icon));
1076
- marker.fire('move', { latlng: marker._latlng }).fire('drag', { latlng: marker._latlng });
1077
- };
1078
- drag._adjustPan = function () {};
1079
- drag._draggable.on('drag', drag._onDrag, drag);
1080
- let pointer, anchor, origin, moved;
1081
- const handle = event => {
1082
- if (!drag.enabled()) return;
1083
- if (event.type === 'pointerdown') {
1084
- if (event.button !== 0) return;
1085
- event.preventDefault(); event.stopPropagation();
1086
- pointer = event.pointerId; anchor = [event.clientX, event.clientY];
1087
- origin = this._map.latLngToContainerPoint(this._latlng); moved = false;
1088
- icon.setPointerCapture(pointer);
1089
- this.fire('dragstart');
1090
- } else if (event.pointerId === pointer && event.type === 'pointermove') {
1091
- const value = new L.Point(origin.x + event.clientX - anchor[0], origin.y + event.clientY - anchor[1]);
1092
- L.DomUtil.setPosition(icon, value);
1093
- drag._draggable._newPos = value;
1094
- drag._draggable.fire('drag', { originalEvent: event });
1095
- moved = true;
1096
- } else if (event.pointerId === pointer) {
1097
- if (icon.hasPointerCapture(pointer)) icon.releasePointerCapture(pointer);
1098
- pointer = null; this.fire('dragend'); this._map._moved = moved;
1099
- }
1100
- };
1101
- drag.addHooks = () => L.DomEvent.on(icon, 'pointerdown pointermove pointerup pointercancel', handle);
1102
- drag.removeHooks = () => L.DomEvent.off(icon, 'pointerdown pointermove pointerup pointercancel', handle);
1103
- if (this.options.draggable) drag.enable();
1104
- },
1105
- });
1106
-
1107
- L.Map.include({
1108
- _fireDOMEvent(event, type = event.type, targets) {
1109
- const rect = this.getContainer().getBoundingClientRect();
1110
- const pixel = new L.Point(event.clientX - rect.left, event.clientY - rect.top);
1111
- const location = this.containerPointToLatLng(pixel);
1112
- const layer = event.target.closest?.('.leaflet-marker-icon')?._cityLayer;
1113
- if (type === 'click' && this._draggableMoved(layer)) return;
1114
- for (const target of targets || (layer ? [layer] : [this])) {
1115
- target.fire(type, { originalEvent: event, latlng: location, containerPoint: pixel,
1116
- layerPoint: this.containerPointToLayerPoint(pixel) });
1117
- }
1118
- },
1119
- });
1120
- L.Map.addInitHook(function () {
1121
- const mapPane = this.createPane('mapPane');
1122
- for (const [name, order] of [['tilePane', 200], ['overlayPane', 400], ['markerPane', 600]]) {
1123
- this.createPane(name, mapPane).style.zIndex = order;
1124
- }
1125
- const handler = event => this._fireDOMEvent(event);
1126
- L.DomEvent.on(this.getContainer(), 'click mousemove mouseout mouseover', handler);
1127
- this.once('unload', () => L.DomEvent.off(this.getContainer(), 'click mousemove mouseout mouseover', handler));
1128
- });
1129
- })();
1
+ // Leaflet-compatible classes backed by OpenLayers. Load after ol.js and before the plugin.
2
+ (() => {
3
+ if (!window.ol) throw new Error('Unable to load OpenLayers');
4
+
5
+ const style = document.createElement('style');
6
+ style.dataset.openlayersBindings = '';
7
+ style.textContent = `
8
+ .openlayers-city-mapper {
9
+ position: relative;
10
+ overflow: hidden;
11
+ touch-action: none;
12
+ }
13
+ .ol-viewport {
14
+ position: absolute !important;
15
+ inset: 0;
16
+ }
17
+ .ol-overlaycontainer-stopevent { z-index: 1000 !important; }
18
+ .ol-city-corner {
19
+ position: absolute;
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: .5em;
23
+ pointer-events: none;
24
+ }
25
+ .ol-city-topleft { top: 5em; left: .5em; }
26
+ .ol-city-topright { top: 10px; right: 10px; }
27
+ .ol-city-bottomleft { bottom: .5em; left: .5em; }
28
+ .ol-city-bottomright { bottom: 4em; right: .5em; }
29
+ .ol-attribution {
30
+ text-align: justify;
31
+ border-radius: 0 !important;
32
+ left: 0;
33
+ right: 0 !important;
34
+ max-width: unset;
35
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
36
+ font-size: .75rem;
37
+ line-height: 1.5;
38
+ }
39
+ .ol-attribution a { color: #0078A8; }
40
+ .ol-attribution * { text-shadow: none !important; }
41
+ .ol-city-control {
42
+ position: static;
43
+ pointer-events: auto;
44
+ font: 13px/1.5 sans-serif;
45
+ }
46
+ .ol-city-rotate {
47
+ padding: 0;
48
+ border: 1px solid #0003;
49
+ border-radius: 4px;
50
+ background: #fff;
51
+ background-clip: padding-box;
52
+ }
53
+ .ol-city-rotate a {
54
+ display: block;
55
+ width: 30px;
56
+ height: 30px;
57
+ border-radius: 2px;
58
+ color: #000;
59
+ text-decoration: none;
60
+ }
61
+ .ol-city-rotate a:hover { background: #f4f4f4; }
62
+ .ol-city-rotate a:focus-visible { outline: 2px solid #2675c9; }
63
+ .ol-city-layers {
64
+ max-height: calc(100vh - 5em);
65
+ overflow: auto;
66
+ border-radius: 4px;
67
+ }
68
+ .ol-city-layers section { padding: .5em; background: var(--ol-background-color); }
69
+ .ol-city-layers label { display: block; cursor: pointer; white-space: nowrap; }
70
+ .ol-city-layers input { vertical-align: middle; }
71
+ .ol-city-layers hr { border: 0; border-top: 1px solid #ccc; }
72
+ .ol-city-layers .ol-control { background: transparent; }
73
+ .ol-city-layers .sunlight-time button { width: auto; padding: 0 .4em; flex-shrink: 0; }
74
+ .openlayers-city-mapper .leaflet-pane {
75
+ position: absolute;
76
+ inset: 0;
77
+ pointer-events: none;
78
+ }
79
+ .ol-zoom button { width: 30px; height: 30px; }
80
+ .openlayers-city-mapper .leaflet-marker-icon,
81
+ .openlayers-city-mapper .leaflet-marker-shadow {
82
+ position: absolute;
83
+ top: 0;
84
+ left: 0;
85
+ }
86
+ .openlayers-city-mapper .leaflet-marker-icon { pointer-events: auto; cursor: pointer; }
87
+ .openlayers-city-mapper .leaflet-div-icon { background: #fff; border: 1px solid #666; }
88
+ .ol-city-popup {
89
+ padding: 10px;
90
+ background: white;
91
+ border: 1px solid #888;
92
+ border-radius: 4px;
93
+ font: 14px sans-serif;
94
+ }
95
+ .ol-city-popup button { float: right; margin-left: 12px; }
96
+ .ol-city-tooltip {
97
+ position: absolute;
98
+ z-index: 1100;
99
+ padding: 4px 8px;
100
+ background: white;
101
+ border: 1px solid #888;
102
+ pointer-events: none;
103
+ }
104
+ `;
105
+ document.head.appendChild(style);
106
+
107
+ const L = window.L = { version: '1.9.4-openlayers', control: {} };
108
+ let lastId = 0;
109
+ const stamp = object => object._leaflet_id ||= ++lastId;
110
+ const setOptions = (object, options) => object.options = { ...object.options, ...options };
111
+ const zoomTiming = new Animation(new KeyframeEffect(null, [], {
112
+ duration: 1, fill: 'both', easing: 'cubic-bezier(0,0,0.25,1)',
113
+ }));
114
+
115
+ function Class(...args) {
116
+ this.initialize?.(...args);
117
+ this.callInitHooks();
118
+ }
119
+ Class.extend = function (properties) {
120
+ function Child(...args) {
121
+ this.initialize?.(...args);
122
+ this.callInitHooks();
123
+ }
124
+ Object.setPrototypeOf(Child, this);
125
+ Child.prototype = Object.create(this.prototype);
126
+ Object.assign(Child.prototype, properties, { constructor: Child });
127
+ Child.prototype.options = { ...this.prototype.options, ...properties.options };
128
+ Child.prototype._initHooks = [];
129
+ return Child;
130
+ };
131
+ Class.include = function (properties) { Object.assign(this.prototype, properties); return this; };
132
+ Class.mergeOptions = function (options) {
133
+ this.prototype.options = { ...this.prototype.options, ...options };
134
+ return this;
135
+ };
136
+ Class.addInitHook = function (hook, ...args) {
137
+ if (!Object.hasOwn(this.prototype, '_initHooks')) this.prototype._initHooks = [];
138
+ this.prototype._initHooks.push(typeof hook === 'function' ? hook : function () { this[hook](...args); });
139
+ return this;
140
+ };
141
+ Class.prototype.callInitHooks = function () {
142
+ if (this._initHooksCalled) return;
143
+ this._initHooksCalled = true;
144
+ const prototypes = [];
145
+ for (let proto = Object.getPrototypeOf(this); proto; proto = Object.getPrototypeOf(proto)) prototypes.unshift(proto);
146
+ for (const proto of prototypes) {
147
+ if (Object.hasOwn(proto, '_initHooks')) for (const hook of proto._initHooks) hook.call(this);
148
+ }
149
+ };
150
+ L.Evented = Class.extend({
151
+ on(types, fn, context) {
152
+ if (typeof types === 'object') {
153
+ for (const [type, handler] of Object.entries(types)) this.on(type, handler, fn);
154
+ } else {
155
+ this._events ||= {};
156
+ for (const type of types.split(/\s+/)) {
157
+ const events = this._events[type] ||= [];
158
+ if (!events.some(event => event.fn === fn && event.context === context)) events.push({ fn, context });
159
+ }
160
+ }
161
+ return this;
162
+ },
163
+ off(types, fn, context) {
164
+ if (!types) this._events = {};
165
+ else if (typeof types === 'object') {
166
+ for (const [type, handler] of Object.entries(types)) this.off(type, handler, fn);
167
+ } else {
168
+ for (const type of types.split(/\s+/)) {
169
+ if (this._events?.[type]) this._events[type] = fn
170
+ ? this._events[type].filter(event => event.fn !== fn || event.context !== context) : [];
171
+ }
172
+ }
173
+ return this;
174
+ },
175
+ once(types, fn, context) {
176
+ const handler = event => { this.off(types, handler); fn.call(context || this, event); };
177
+ return this.on(types, handler);
178
+ },
179
+ fire(type, data) {
180
+ for (const event of [...this._events?.[type] || []]) {
181
+ event.fn.call(event.context || this, { ...data, type, target: this, sourceTarget: this });
182
+ }
183
+ return this;
184
+ },
185
+ listens(type) { return Boolean(this._events?.[type]?.length); },
186
+ });
187
+ L.Util = {
188
+ stamp, setOptions,
189
+ throttle(fn, interval, context) {
190
+ let timer, pending;
191
+ const run = (...args) => {
192
+ if (timer) { pending = args; return; }
193
+ fn.apply(context, args);
194
+ timer = setTimeout(() => {
195
+ timer = null;
196
+ if (pending) { const args = pending; pending = null; run(...args); }
197
+ }, interval);
198
+ };
199
+ return run;
200
+ },
201
+ };
202
+
203
+ L.Point = function (x, y, round) {
204
+ this.x = round ? Math.round(x) : x;
205
+ this.y = round ? Math.round(y) : y;
206
+ };
207
+ const point = value => value instanceof L.Point ? value
208
+ : Array.isArray(value) ? new L.Point(...value) : new L.Point(value.x, value.y);
209
+ L.Point.prototype = {
210
+ clone() { return new L.Point(this.x, this.y); },
211
+ add(value) { return this.clone()._add(point(value)); },
212
+ _add(value) { this.x += value.x; this.y += value.y; return this; },
213
+ subtract(value) { return this.clone()._subtract(point(value)); },
214
+ _subtract(value) { this.x -= value.x; this.y -= value.y; return this; },
215
+ multiplyBy(value) { return new L.Point(this.x * value, this.y * value); },
216
+ divideBy(value) { return new L.Point(this.x / value, this.y / value); },
217
+ scaleBy(value) { return new L.Point(this.x * value.x, this.y * value.y); },
218
+ unscaleBy(value) { return new L.Point(this.x / value.x, this.y / value.y); },
219
+ round() { return new L.Point(Math.round(this.x), Math.round(this.y)); },
220
+ floor() { return new L.Point(Math.floor(this.x), Math.floor(this.y)); },
221
+ ceil() { return new L.Point(Math.ceil(this.x), Math.ceil(this.y)); },
222
+ distanceTo(value) { value = point(value); return Math.hypot(this.x - value.x, this.y - value.y); },
223
+ equals(value) { value = point(value); return this.x === value.x && this.y === value.y; },
224
+ contains(value) { value = point(value); return Math.abs(value.x) <= Math.abs(this.x) && Math.abs(value.y) <= Math.abs(this.y); },
225
+ };
226
+ L.LatLng = function (lat, lng, alt) {
227
+ if (!Number.isFinite(Number(lat)) || !Number.isFinite(Number(lng))) throw new Error('Invalid LatLng');
228
+ this.lat = Number(lat); this.lng = Number(lng);
229
+ if (alt !== undefined) this.alt = alt;
230
+ };
231
+ const latLng = value => value instanceof L.LatLng ? value
232
+ : Array.isArray(value) ? new L.LatLng(...value) : new L.LatLng(value.lat, value.lng);
233
+ L.LatLng.prototype = {
234
+ equals(value, margin = 1e-9) {
235
+ value = latLng(value);
236
+ return Math.max(Math.abs(this.lat - value.lat), Math.abs(this.lng - value.lng)) <= margin;
237
+ },
238
+ distanceTo(value) { value = latLng(value); return ol.sphere.getDistance([this.lng, this.lat], [value.lng, value.lat]); },
239
+ wrap() { return new L.LatLng(this.lat, ((this.lng + 180) % 360 + 360) % 360 - 180, this.alt); },
240
+ };
241
+ L.Bounds = function (a, b) {
242
+ if (a) for (const value of b ? [a, b] : Array.isArray(a) ? a : [a]) this.extend(value);
243
+ };
244
+ L.Bounds.prototype = {
245
+ extend(value) {
246
+ if (value instanceof L.Bounds) { this.extend(value.min); this.extend(value.max); return this; }
247
+ value = point(value);
248
+ if (!this.min) { this.min = value.clone(); this.max = value.clone(); }
249
+ else {
250
+ this.min.x = Math.min(this.min.x, value.x); this.min.y = Math.min(this.min.y, value.y);
251
+ this.max.x = Math.max(this.max.x, value.x); this.max.y = Math.max(this.max.y, value.y);
252
+ }
253
+ return this;
254
+ },
255
+ getCenter() { return this.min.add(this.max).divideBy(2); },
256
+ getSize() { return this.max.subtract(this.min); },
257
+ contains(value) {
258
+ const min = value.min || point(value), max = value.max || min;
259
+ return min.x >= this.min.x && max.x <= this.max.x && min.y >= this.min.y && max.y <= this.max.y;
260
+ },
261
+ intersects(value) {
262
+ return value.max.x >= this.min.x && value.min.x <= this.max.x && value.max.y >= this.min.y && value.min.y <= this.max.y;
263
+ },
264
+ };
265
+ L.LatLngBounds = function (a, b) {
266
+ if (a) for (const value of b ? [a, b] : a) this.extend(value);
267
+ };
268
+ L.LatLngBounds.prototype = {
269
+ extend(value) {
270
+ if (value instanceof L.LatLngBounds) { this.extend(value._southWest); this.extend(value._northEast); return this; }
271
+ value = latLng(value);
272
+ if (!this._southWest) {
273
+ this._southWest = new L.LatLng(value.lat, value.lng);
274
+ this._northEast = new L.LatLng(value.lat, value.lng);
275
+ } else {
276
+ this._southWest.lat = Math.min(this._southWest.lat, value.lat);
277
+ this._southWest.lng = Math.min(this._southWest.lng, value.lng);
278
+ this._northEast.lat = Math.max(this._northEast.lat, value.lat);
279
+ this._northEast.lng = Math.max(this._northEast.lng, value.lng);
280
+ }
281
+ return this;
282
+ },
283
+ getSouthWest() { return this._southWest; },
284
+ getNorthEast() { return this._northEast; },
285
+ getNorthWest() { return new L.LatLng(this.getNorth(), this.getWest()); },
286
+ getSouthEast() { return new L.LatLng(this.getSouth(), this.getEast()); },
287
+ getNorth() { return this._northEast.lat; },
288
+ getSouth() { return this._southWest.lat; },
289
+ getEast() { return this._northEast.lng; },
290
+ getWest() { return this._southWest.lng; },
291
+ getCenter() { return new L.LatLng((this.getNorth() + this.getSouth()) / 2, (this.getEast() + this.getWest()) / 2); },
292
+ contains(value) {
293
+ value = latLng(value);
294
+ return value.lat >= this.getSouth() && value.lat <= this.getNorth() && value.lng >= this.getWest() && value.lng <= this.getEast();
295
+ },
296
+ isValid() { return Boolean(this._southWest && this._northEast); },
297
+ };
298
+
299
+ const radius = 6378137, circumference = 2 * Math.PI * radius;
300
+ const crs = {
301
+ scale: zoom => 256 * 2 ** zoom,
302
+ zoom: scale => Math.log2(scale / 256),
303
+ project(value) { value = latLng(value); return point(ol.proj.fromLonLat([value.lng, Math.max(-85.0511287798, Math.min(85.0511287798, value.lat))])); },
304
+ unproject(value) { const [lng, lat] = ol.proj.toLonLat([value.x, value.y]); return new L.LatLng(lat, lng); },
305
+ transformation: {
306
+ transform(value, scale) { return new L.Point((value.x / circumference + 0.5) * scale, (0.5 - value.y / circumference) * scale); },
307
+ untransform(value, scale) { return new L.Point((value.x / scale - 0.5) * circumference, (0.5 - value.y / scale) * circumference); },
308
+ },
309
+ };
310
+
311
+ L.DomUtil = {
312
+ create(tag, className = '', parent) {
313
+ const element = document.createElement(tag);
314
+ element.className = className; parent?.append(element); return element;
315
+ },
316
+ setPosition(element, value) { element._leaflet_pos = point(value); element.style.transform = `translate(${value.x}px,${value.y}px)`; },
317
+ getPosition(element) { return element._leaflet_pos || new L.Point(0, 0); },
318
+ };
319
+ const domListeners = new WeakMap();
320
+ function domEvent(add, element, types, fn, context) {
321
+ if (typeof types === 'object') {
322
+ for (const [type, handler] of Object.entries(types)) domEvent(add, element, type, handler, fn);
323
+ return L.DomEvent;
324
+ }
325
+ let listeners = domListeners.get(element);
326
+ if (!listeners) domListeners.set(element, listeners = []);
327
+ for (const type of types.split(/\s+/)) {
328
+ const existing = listeners.find(item => item.type === type && item.fn === fn && item.context === context);
329
+ if (add && !existing) {
330
+ const handler = context ? fn.bind(context) : fn;
331
+ listeners.push({ type, fn, context, handler });
332
+ element.addEventListener(type, handler, { passive: false });
333
+ } else if (!add && existing) {
334
+ element.removeEventListener(type, existing.handler);
335
+ listeners.splice(listeners.indexOf(existing), 1);
336
+ }
337
+ }
338
+ return L.DomEvent;
339
+ }
340
+ const stopPropagation = event => event.stopPropagation();
341
+ L.DomEvent = {
342
+ on: (...args) => domEvent(true, ...args),
343
+ off: (...args) => domEvent(false, ...args),
344
+ stop(event) { event.preventDefault(); event.stopPropagation(); },
345
+ disableClickPropagation(element) {
346
+ element.classList.add('leaflet-control');
347
+ L.DomEvent.on(element, 'click dblclick pointerdown mousedown touchstart contextmenu', stopPropagation);
348
+ return this;
349
+ },
350
+ disableScrollPropagation(element) { return this.on(element, 'wheel', stopPropagation); },
351
+ };
352
+ L.Handler = Class.extend({
353
+ initialize(map) { this._map = map; },
354
+ enable() { if (!this._enabled) { this._enabled = true; this.addHooks?.(); } return this; },
355
+ disable() { if (this._enabled) { this._enabled = false; this.removeHooks?.(); } return this; },
356
+ enabled() { return Boolean(this._enabled); },
357
+ moving() { return Boolean(this._moving); },
358
+ });
359
+ L.Layer = L.Evented.extend({
360
+ initialize(options) { setOptions(this, options); },
361
+ addTo(map) { map.addLayer(this); return this; },
362
+ remove() { return this.removeFrom(this._map || this._mapToAdd); },
363
+ removeFrom(map) { map?.removeLayer(this); return this; },
364
+ getAttribution() { return this.options.attribution; },
365
+ getPane(name) { return this._map.getPane(name || this.options.pane); },
366
+ _layerAdd({ target: map }) {
367
+ if (!map.hasLayer(this)) return;
368
+ this._map = map;
369
+ if (this.getEvents) { this._mapEvents = this.getEvents(); map.on(this._mapEvents, this); }
370
+ this.onAdd?.(map);
371
+ this.fire('add'); map.fire('layeradd', { layer: this });
372
+ },
373
+ });
374
+ L.LayerGroup = L.Layer.extend({
375
+ initialize(layers = [], options) {
376
+ setOptions(this, options); this._layers = {};
377
+ for (const layer of layers) this.addLayer(layer);
378
+ },
379
+ addLayer(layer) { this._layers[stamp(layer)] = layer; this._map?.addLayer(layer); return this; },
380
+ removeLayer(layer) { delete this._layers[stamp(layer)]; this._map?.removeLayer(layer); return this; },
381
+ hasLayer(layer) { return stamp(layer) in this._layers; },
382
+ eachLayer(fn, context) { for (const layer of Object.values(this._layers)) fn.call(context || this, layer); return this; },
383
+ onAdd(map) { this.eachLayer(map.addLayer, map); },
384
+ onRemove(map) { this.eachLayer(map.removeLayer, map); },
385
+ });
386
+ // Declare the hierarchy before the plugin installs its instanceof checks and hooks.
387
+ for (const [name, parent] of [
388
+ ['GridLayer', L.Layer], ['Marker', L.Layer], ['Path', L.Layer],
389
+ ['Polyline', null], ['Polygon', null], ['CircleMarker', null], ['Circle', null],
390
+ ['ImageOverlay', L.Layer], ['VideoOverlay', null], ['SVGOverlay', null],
391
+ ['Popup', L.Layer], ['Tooltip', L.Layer], ['DivIcon', Class],
392
+ ]) {
393
+ const base = parent || ({ Polyline: L.Path, Polygon: L.Polyline, CircleMarker: L.Path,
394
+ Circle: L.CircleMarker, VideoOverlay: L.ImageOverlay, SVGOverlay: L.ImageOverlay })[name];
395
+ L[name] = base.extend({
396
+ initialize() { throw new Error(`L.${name} is not supported by the OpenLayers bindings; use L.Layer with a source`); },
397
+ getEvents() { return {}; },
398
+ });
399
+ }
400
+
401
+ L.Map = L.Evented.extend({
402
+ options: { crs, minZoom: 0, maxZoom: 24, zoomSnap: 1, touchZoom: true,
403
+ scrollWheelZoom: true, keyboard: true, doubleClickZoom: true,
404
+ wheelDebounceTime: 40, wheelPxPerZoomLevel: 60 },
405
+ initialize(target, options) {
406
+ setOptions(this, options);
407
+ this._container = typeof target === 'string' ? document.getElementById(target) : target;
408
+ if (!this._container) throw new Error('Map container not found');
409
+ stamp(this._container);
410
+ this._container.classList.add('openlayers-city-mapper');
411
+ this._layers = {}; this._panes = {}; this._handlers = []; this._controls = new Set();
412
+ const center = crs.project(this.options.center || [0, 0]);
413
+ this.ol = new ol.Map({
414
+ target: this._container, controls: [new ol.control.Zoom(), new ol.control.Attribution({collapsible: false })],
415
+ interactions: [],
416
+ view: new ol.View({ center: [center.x, center.y], zoom: this.options.zoom || 0,
417
+ minZoom: this.getMinZoom(), maxZoom: this.getMaxZoom() }),
418
+ });
419
+ for (const control of this.ol.getControls().getArray()) {
420
+ control.element.classList.add('leaflet-control');
421
+ for (const button of control.element.querySelectorAll('button[title]')) button.setAttribute('aria-label', button.title);
422
+ if (control instanceof ol.control.Zoom) {
423
+ for (const [selector, delta] of [['.ol-zoom-in', 1], ['.ol-zoom-out', -1]]) {
424
+ control.element.querySelector(selector).addEventListener('click', event => {
425
+ L.DomEvent.stop(event);
426
+ event.stopImmediatePropagation();
427
+ this._zoomBy(delta * (event.shiftKey ? 3 : 1));
428
+ }, true);
429
+ }
430
+ }
431
+ }
432
+ this._loaded = true;
433
+ this._zoomAnimated = false;
434
+ this.dragging = new L.Handler(this).enable();
435
+ this.touchZoom = new L.Handler(this);
436
+ if (this.options.touchZoom) this.touchZoom.enable();
437
+ this._handlers.push(this.dragging, this.touchZoom);
438
+ for (const name of ['scrollWheelZoom', 'keyboard', 'doubleClickZoom']) this.addHandler(name, L.Handler);
439
+ if (!this._container.hasAttribute('tabindex')) this._container.tabIndex = 0;
440
+ const view = this.ol.getView();
441
+ this._viewZoom = view.getZoom();
442
+ view.on(['change:center', 'change:resolution'], () => {
443
+ if (this._settingView) return;
444
+ const zoomChanged = view.getZoom() !== this._viewZoom;
445
+ if (!this._viewMoving) { this._viewMoving = true; this.fire('movestart'); }
446
+ if (zoomChanged && !this._viewZooming) { this._viewZooming = true; this.fire('zoomstart'); }
447
+ this.fire('move');
448
+ if (zoomChanged) this.fire('zoom');
449
+ this._viewZoom = view.getZoom();
450
+ if (!view.getAnimating() && !view.getInteracting()) this._endViewChange();
451
+ });
452
+ this.ol.on('moveend', () => this._endViewChange());
453
+ // OpenLayers interactions need the pitched ground coordinate, not the flat viewport.
454
+ this.ol.getCoordinateFromPixel = pixel => {
455
+ const coordinate = crs.project(this.containerPointToLatLng(point(pixel)));
456
+ return [coordinate.x, coordinate.y];
457
+ };
458
+ this._wheelDelta = 0;
459
+ this.ol.addInteraction(new ol.interaction.Interaction({
460
+ handleEvent: event => {
461
+ const original = event.originalEvent;
462
+ if (original.target.closest('.ol-control')) return true;
463
+ if (event.type === 'dblclick' && this.doubleClickZoom.enabled()) {
464
+ this._zoomBy(original.shiftKey ? -1 : 1, point(event.pixel));
465
+ } else if (event.type === 'wheel' && this.scrollWheelZoom.enabled() && !original.shiftKey) {
466
+ const ratio = window.devicePixelRatio || 1;
467
+ const factor = /Linux/.test(navigator.platform) && /Chrome/.test(navigator.userAgent) ? ratio
468
+ : /Mac/.test(navigator.platform) ? 3 * ratio : 2 * ratio;
469
+ this._wheelDelta -= original.deltaY * (original.deltaMode === 1 ? 20
470
+ : original.deltaMode === 2 ? 60 : 1 / factor);
471
+ this._wheelPixel = point(event.pixel);
472
+ this._wheelStart ??= performance.now();
473
+ clearTimeout(this._wheelTimer);
474
+ this._wheelTimer = setTimeout(() => {
475
+ const magnitude = 4 * Math.log2(2 / (1 + Math.exp(-Math.abs(this._wheelDelta /
476
+ (this.options.wheelPxPerZoomLevel * 4)))));
477
+ const snap = this.options.zoomSnap;
478
+ const delta = Math.sign(this._wheelDelta) * (snap ? Math.ceil(magnitude / snap) * snap : magnitude);
479
+ this._wheelDelta = 0;
480
+ this._wheelStart = null;
481
+ if (this._loaded && this.scrollWheelZoom.enabled()) this._zoomBy(delta,
482
+ this.options.scrollWheelZoom === 'center' ? undefined : this._wheelPixel);
483
+ }, Math.max(0, this.options.wheelDebounceTime - (performance.now() - this._wheelStart)));
484
+ } else return true;
485
+ original.preventDefault();
486
+ return false;
487
+ },
488
+ }));
489
+ this._controlCorners = {};
490
+ for (const position of ['topleft', 'topright', 'bottomleft', 'bottomright']) {
491
+ this._controlCorners[position] = L.DomUtil.create('div',
492
+ `ol-city-corner ol-city-${position}`, this.ol.getOverlayContainerStopEvent());
493
+ }
494
+ const attributionSource = new ol.source.Vector();
495
+ this.ol.addLayer(new ol.layer.Vector({ source: attributionSource }));
496
+ const updateAttribution = () => {
497
+ const values = new Set(['<a href="https://openlayers.org/">OpenLayers</a>']);
498
+ this.eachLayer(layer => { if (layer.getAttribution?.()) values.add(layer.getAttribution()); });
499
+ attributionSource.setAttributions([...values]);
500
+ };
501
+ this.on('layeradd layerremove', updateAttribution);
502
+ updateAttribution();
503
+ let anchor, pointerId;
504
+ const pixel = event => {
505
+ const rect = this._container.getBoundingClientRect();
506
+ return new L.Point(event.clientX - rect.left, event.clientY - rect.top);
507
+ };
508
+ this._onPointer = event => {
509
+ if (event.type === 'pointerdown') {
510
+ if (!this.dragging.enabled() || event.button !== 0 || !event.isPrimary || event.target.closest('.ol-control')) return;
511
+ anchor = this.containerPointToLatLng(pixel(event)); pointerId = event.pointerId;
512
+ this._moved = this.dragging._moving = false;
513
+ this._container.focus({ preventScroll: true });
514
+ this._container.setPointerCapture(pointerId);
515
+ } else if (event.pointerId === pointerId && event.type === 'pointermove' && this.dragging.enabled()) {
516
+ const delta = this.project(anchor).subtract(this.project(this.containerPointToLatLng(pixel(event))));
517
+ if (Math.hypot(delta.x, delta.y) > 1) this._moved = this.dragging._moving = true;
518
+ this.setView(this.unproject(this.project(this.getCenter()).add(delta)), this.getZoom());
519
+ } else if (event.pointerId === pointerId && event.type !== 'pointermove') {
520
+ if (this._container.hasPointerCapture(pointerId)) this._container.releasePointerCapture(pointerId);
521
+ anchor = null; pointerId = null;
522
+ this.dragging._moving = false;
523
+ }
524
+ };
525
+ this._onKey = event => {
526
+ if (!this.keyboard.enabled() || event.target !== this._container || event.altKey || event.ctrlKey || event.metaKey) return;
527
+ const offsets = { ArrowLeft: [-80, 0], ArrowRight: [80, 0], ArrowUp: [0, -80], ArrowDown: [0, 80] };
528
+ if (offsets[event.key]) this.panBy(offsets[event.key]);
529
+ else if (['+', '=', '-', '_'].includes(event.key)) this._zoomBy(
530
+ (['-', '_'].includes(event.key) ? -1 : 1) * (event.shiftKey ? 3 : 1));
531
+ else return;
532
+ event.preventDefault();
533
+ };
534
+ L.DomEvent.on(this._container, 'pointerdown pointermove pointerup pointercancel', this._onPointer);
535
+ L.DomEvent.on(this._container, 'keydown', this._onKey);
536
+ this.ol.on('change:size', () => this.fire('resize', { newSize: this.getSize() }));
537
+ this.ol.updateSize();
538
+ },
539
+ getContainer() { return this._container; },
540
+ getSize() { return point(this.ol.getSize() || [this._container.clientWidth, this._container.clientHeight]); },
541
+ getCenter() { return crs.unproject(point(this.ol.getView().getCenter())); },
542
+ getZoom() { return this.ol.getView().getZoom(); },
543
+ getMinZoom() { return this.options.minZoom; },
544
+ getMaxZoom() { return this.options.maxZoom; },
545
+ getZoomScale(to, from = this.getZoom()) { return 2 ** (to - from); },
546
+ getScaleZoom(scale, from = this.getZoom()) { return from + Math.log2(scale); },
547
+ _limitZoom(zoom) { return Math.max(this.getMinZoom(), Math.min(this.getMaxZoom(), zoom)); },
548
+ project(value, zoom = this.getZoom()) { return crs.transformation.transform(crs.project(value), crs.scale(zoom)); },
549
+ unproject(value, zoom = this.getZoom()) { return crs.unproject(crs.transformation.untransform(point(value), crs.scale(zoom))); },
550
+ getPixelOrigin() { return this.project(this.getCenter()).subtract(this.getSize().divideBy(2)); },
551
+ _getMapPanePos() { return new L.Point(0, 0); },
552
+ latLngToLayerPoint(value) { return this.project(value).subtract(this.getPixelOrigin()); },
553
+ layerPointToLatLng(value) { return this.unproject(point(value).add(this.getPixelOrigin())); },
554
+ layerPointToContainerPoint(value) { return point(value).clone(); },
555
+ containerPointToLayerPoint(value) { return point(value).clone(); },
556
+ latLngToContainerPoint(value) { return this.layerPointToContainerPoint(this.latLngToLayerPoint(value)); },
557
+ containerPointToLatLng(value) { return this.layerPointToLatLng(this.containerPointToLayerPoint(value)); },
558
+ getBounds() { return new L.LatLngBounds(this.containerPointToLatLng([0, 0]), this.containerPointToLatLng(this.getSize())); },
559
+ _endViewChange() {
560
+ if (!this._viewMoving) return;
561
+ const zooming = this._viewZooming;
562
+ this._viewMoving = this._viewZooming = false;
563
+ if (zooming) this.fire('zoomend');
564
+ this.fire('moveend');
565
+ },
566
+ _zoomBy(delta, pixel) {
567
+ if (!delta || this.ol.getView().getAnimating()) return this;
568
+ const zoom = this._limitZoom(this.getZoom() + delta);
569
+ return pixel ? this.setZoomAround(pixel, zoom, { animate: true })
570
+ : this.setZoom(zoom, { animate: true });
571
+ },
572
+ setView(center, zoom = this.getZoom(), options = {}) {
573
+ center = latLng(center); zoom = this._limitZoom(zoom);
574
+ if ((options.animate === true || options.zoom?.animate === true) && zoom !== this.getZoom()) {
575
+ const scale = this.getZoomScale(zoom), projected = crs.project(center);
576
+ // Leaflet animates CSS scale, not resolution; convert the same native CSS easing.
577
+ this.ol.getView().animate({
578
+ center: [projected.x, projected.y], zoom, duration: 250,
579
+ easing: time => {
580
+ zoomTiming.currentTime = time;
581
+ const progress = zoomTiming.effect.getComputedTiming().progress;
582
+ return (1 - 1 / (1 + (scale - 1) * progress)) / (1 - 1 / scale);
583
+ },
584
+ });
585
+ return this;
586
+ }
587
+ this.ol.getView().cancelAnimations();
588
+ this._endViewChange();
589
+ const zoomChanged = zoom !== this.getZoom(), moved = !center.equals(this.getCenter());
590
+ if (!zoomChanged && !moved) return this;
591
+ this.fire('movestart');
592
+ if (zoomChanged) this.fire('zoomstart');
593
+ this._settingView = true;
594
+ const projected = crs.project(center);
595
+ this.ol.getView().setCenter([projected.x, projected.y]);
596
+ this.ol.getView().setZoom(zoom);
597
+ this._viewZoom = this.getZoom();
598
+ this._settingView = false;
599
+ if (zoomChanged) this.fire('zoom');
600
+ this.fire('move').fire('viewreset');
601
+ if (zoomChanged) this.fire('zoomend');
602
+ return this.fire('moveend');
603
+ },
604
+ setZoom(zoom, options) { return this.setView(this.getCenter(), zoom, options); },
605
+ panTo(center) { return this.setView(center); },
606
+ panBy(offset) { return this.panTo(this.unproject(this.project(this.getCenter()).add(point(offset)))); },
607
+ setZoomAround(value, zoom, options) {
608
+ const anchor = value instanceof L.Point ? value : this.latLngToContainerPoint(value);
609
+ const half = this.getSize().divideBy(2), scale = this.getZoomScale(zoom);
610
+ return this.setView(this.unproject(this.project(this.getCenter()).add(anchor.subtract(half).multiplyBy(1 - 1 / scale))), zoom, options);
611
+ },
612
+ getBoundsZoom(bounds, inside, padding = [0, 0]) {
613
+ const size = this.project(bounds.getSouthEast()).subtract(this.project(bounds.getNorthWest()));
614
+ const available = this.getSize().subtract(point(padding));
615
+ const scale = Math[inside ? 'max' : 'min'](available.x / size.x, available.y / size.y);
616
+ return this._limitZoom(this.getScaleZoom(scale));
617
+ },
618
+ _getBoundsCenterZoom(bounds, options = {}) { return { center: bounds.getCenter(), zoom: this.getBoundsZoom(bounds, false, options.padding) }; },
619
+ fitBounds(bounds, options) { const view = this._getBoundsCenterZoom(bounds, options); return this.setView(view.center, view.zoom); },
620
+ _limitCenter(center) { return center; },
621
+ _limitOffset(offset) { return offset; },
622
+ _tryAnimatedPan() { return false; },
623
+ panInside(value) { if (!this.getBounds().contains(value)) this.panTo(value); return this; },
624
+ whenReady(fn, context) { if (this._loaded) fn.call(context || this, { target: this }); else this.once('load', fn, context); return this; },
625
+ addLayer(layer) {
626
+ const id = stamp(layer);
627
+ if (this._layers[id]) return this;
628
+ this._layers[id] = layer; layer._mapToAdd = this;
629
+ layer.beforeAdd?.(this);
630
+ this.whenReady(layer._layerAdd, layer);
631
+ return this;
632
+ },
633
+ removeLayer(layer) {
634
+ if (!this.hasLayer(layer)) return this;
635
+ layer.onRemove?.(this);
636
+ if (layer._mapEvents) this.off(layer._mapEvents, layer);
637
+ delete this._layers[stamp(layer)];
638
+ this.fire('layerremove', { layer }); layer.fire('remove');
639
+ layer._map = layer._mapToAdd = null;
640
+ return this;
641
+ },
642
+ hasLayer(layer) { return Boolean(this._layers[stamp(layer)]); },
643
+ eachLayer(fn, context) { for (const layer of Object.values(this._layers)) fn.call(context || this, layer); return this; },
644
+ createPane(name, parent = this._container) { return this._panes[name] = L.DomUtil.create('div', `leaflet-pane leaflet-${name}`, parent); },
645
+ getPane(name = 'overlayPane') { return this._panes[name]; },
646
+ addHandler(name, Handler) { this[name] = new Handler(this); this._handlers.push(this[name]); if (this.options[name]) this[name].enable(); return this; },
647
+ invalidateSize() { this.ol.updateSize(); return this; },
648
+ _draggableMoved() { return Boolean(this._moved); },
649
+ remove() {
650
+ clearTimeout(this._wheelTimer);
651
+ this.ol.getView().cancelAnimations();
652
+ this.fire('unload');
653
+ for (const control of [...this._controls]) control.remove();
654
+ this.eachLayer(this.removeLayer, this);
655
+ for (const handler of this._handlers) handler.disable();
656
+ L.DomEvent.off(this._container, 'pointerdown pointermove pointerup pointercancel', this._onPointer);
657
+ L.DomEvent.off(this._container, 'keydown', this._onKey);
658
+ this.ol.setTarget(undefined); this.ol.dispose();
659
+ for (const corner of Object.values(this._controlCorners)) corner.remove();
660
+ for (const pane of Object.values(this._panes)) pane.remove();
661
+ this._loaded = false;
662
+ delete this._container._leaflet_id;
663
+ return this.off();
664
+ },
665
+ });
666
+
667
+ L.Control = Class.extend({
668
+ options: { position: 'topright' },
669
+ initialize(options) { setOptions(this, options); },
670
+ getPosition() { return this.options.position; },
671
+ getContainer() { return this._container; },
672
+ addTo(map) {
673
+ this.remove(); this._map = map;
674
+ this._container = this.onAdd(map);
675
+ if (L.Control.Rotate && this instanceof L.Control.Rotate) {
676
+ this._container.classList.add('ol-city-rotate');
677
+ }
678
+ this._container.classList.add('leaflet-control', 'ol-control', 'ol-city-control');
679
+ this._olControl = new ol.control.Control({
680
+ element: this._container, target: map._controlCorners[this.getPosition()],
681
+ });
682
+ map.ol.addControl(this._olControl);
683
+ map._controls.add(this);
684
+ return this;
685
+ },
686
+ remove() {
687
+ if (!this._map) return this;
688
+ const map = this._map;
689
+ this.onRemove?.(map); this._container?.remove();
690
+ map.ol.removeControl(this._olControl);
691
+ map._controls.delete(this); this._map = null;
692
+ return this;
693
+ },
694
+ });
695
+ L.Control.Layers = L.Control.extend({
696
+ options: { collapsed: true },
697
+ initialize(base = {}, overlays = {}, options) {
698
+ setOptions(this, options); this._layers = [];
699
+ for (const [name, layer] of Object.entries(base)) this._layers.push({ name, layer, overlay: false });
700
+ for (const [name, layer] of Object.entries(overlays)) this._layers.push({ name, layer, overlay: true });
701
+ },
702
+ onAdd(map) {
703
+ const container = L.DomUtil.create('div', 'ol-city-layers');
704
+ L.DomEvent.disableClickPropagation(container); L.DomEvent.disableScrollPropagation(container);
705
+ const list = L.DomUtil.create('section', '', container);
706
+ list.setAttribute('aria-label', 'Map layers');
707
+ const base = L.DomUtil.create('div', '', list);
708
+ L.DomUtil.create('hr', '', list);
709
+ const overlays = L.DomUtil.create('div', '', list);
710
+ this._inputs = [];
711
+ for (const entry of this._layers) {
712
+ const label = L.DomUtil.create('label', '', entry.overlay ? overlays : base);
713
+ const input = L.DomUtil.create('input', 'leaflet-control-layers-selector', label);
714
+ input.type = entry.overlay ? 'checkbox' : 'radio';
715
+ input.name = `base-${stamp(this)}`; input.layerId = stamp(entry.layer);
716
+ input.checked = map.hasLayer(entry.layer);
717
+ label.append(document.createTextNode(` ${entry.name}`));
718
+ this._inputs.push(input);
719
+ input.onchange = () => {
720
+ const checked = input.checked;
721
+ if (!entry.overlay) for (const other of this._layers) {
722
+ if (!other.overlay && other !== entry) map.removeLayer(other.layer);
723
+ }
724
+ if (checked) map.addLayer(entry.layer); else map.removeLayer(entry.layer);
725
+ };
726
+ }
727
+ map.on('layeradd layerremove', this._update, this);
728
+ return container;
729
+ },
730
+ _update() { for (const input of this._inputs) input.checked = Boolean(this._map._layers[input.layerId]); },
731
+ onRemove(map) { map.off('layeradd layerremove', this._update, this); },
732
+ });
733
+
734
+ // Native layer contracts consumed by the plugin's WebGPU renderer.
735
+ const bounds = value => value instanceof L.LatLngBounds ? value : new L.LatLngBounds(value);
736
+ const coordinates = values => values.map(value => Array.isArray(value) && typeof value[0] !== 'number'
737
+ ? coordinates(value) : latLng(value));
738
+ const image = (url, options, className) => {
739
+ const element = document.createElement('img');
740
+ element.className = className;
741
+ if (options.crossOrigin) element.crossOrigin = options.crossOrigin === true ? '' : options.crossOrigin;
742
+ element.src = url;
743
+ element.draggable = false;
744
+ return element;
745
+ };
746
+
747
+ L.Layer.include({
748
+ bindPopup(content) {
749
+ this._popupContent = content;
750
+ this.off('click', this.openPopup, this).on('click', this.openPopup, this);
751
+ this.once('remove', this.closePopup, this);
752
+ if (this instanceof L.LayerGroup) this.eachLayer(layer => layer.bindPopup(content));
753
+ return this;
754
+ },
755
+ openPopup(event) {
756
+ if (!this._map) return this;
757
+ this.closePopup();
758
+ const location = event?.latlng || this.getLatLng?.() || this.getBounds?.().getCenter();
759
+ if (!location) throw new Error('Popup requires a location');
760
+ const element = L.DomUtil.create('div', 'ol-city-popup leaflet-popup');
761
+ element.setAttribute('role', 'dialog');
762
+ const close = L.DomUtil.create('button', '', element);
763
+ close.type = 'button'; close.textContent = '\u00d7'; close.setAttribute('aria-label', 'Close popup');
764
+ close.onclick = () => this.closePopup();
765
+ const content = L.DomUtil.create('div', '', element);
766
+ const value = typeof this._popupContent === 'function' ? this._popupContent(this) : this._popupContent;
767
+ if (value instanceof Node) content.append(value); else content.innerHTML = value;
768
+ L.DomEvent.disableClickPropagation(element);
769
+ this._popupOverlay = new ol.Overlay({ element, positioning: 'bottom-center', offset: [0, -12] });
770
+ this._map.ol.addOverlay(this._popupOverlay);
771
+ this._popupLocation = latLng(location);
772
+ this._map.on('move zoom rotate pitch', this._positionPopup, this);
773
+ this._positionPopup();
774
+ return this;
775
+ },
776
+ _positionPopup() {
777
+ const screen = this._map.latLngToContainerPoint(this._popupLocation);
778
+ this._popupOverlay.setPosition(this._map.ol.getCoordinateFromPixelInternal([screen.x, screen.y]));
779
+ },
780
+ closePopup() {
781
+ if (this._popupOverlay) {
782
+ this._map?.off('move zoom rotate pitch', this._positionPopup, this);
783
+ this._map?.ol.removeOverlay(this._popupOverlay);
784
+ this._popupOverlay = null;
785
+ }
786
+ return this;
787
+ },
788
+ bindTooltip(content) {
789
+ this._tooltipContent = content;
790
+ this.on('mouseover', this._showTooltip, this).on('mouseout remove', this._hideTooltip, this);
791
+ if (this instanceof L.LayerGroup) this.eachLayer(layer => layer.bindTooltip(content));
792
+ return this;
793
+ },
794
+ _showTooltip(event) {
795
+ this._hideTooltip();
796
+ if (!this._map || !event.latlng) return;
797
+ const element = this._tooltipElement = L.DomUtil.create('div', 'ol-city-tooltip leaflet-tooltip', this._map.getContainer());
798
+ element.setAttribute('role', 'tooltip');
799
+ const value = typeof this._tooltipContent === 'function' ? this._tooltipContent(this) : this._tooltipContent;
800
+ if (value instanceof Node) element.append(value); else element.innerHTML = value;
801
+ const pixel = this._map.latLngToContainerPoint(event.latlng);
802
+ element.style.left = `${pixel.x + 12}px`; element.style.top = `${pixel.y + 12}px`;
803
+ },
804
+ _hideTooltip() { this._tooltipElement?.remove(); this._tooltipElement = null; },
805
+ });
806
+ L.LayerGroup.include({
807
+ getLayers() { return Object.values(this._layers); },
808
+ getBounds() {
809
+ const result = new L.LatLngBounds();
810
+ this.eachLayer(layer => result.extend(layer.getBounds?.() || layer.getLatLng()));
811
+ return result;
812
+ },
813
+ });
814
+ L.FeatureGroup = L.LayerGroup.extend({});
815
+
816
+ L.Path.include({
817
+ options: { pane: 'overlayPane', interactive: true, stroke: true, color: '#3388ff',
818
+ weight: 3, opacity: 1, lineCap: 'round', lineJoin: 'round', fill: false, fillOpacity: 0.2 },
819
+ initialize(options) { L.Util.setOptions(this, options); },
820
+ setStyle(options) { L.Util.setOptions(this, options); return this.redraw(); },
821
+ redraw() { return this; },
822
+ bringToFront() { return this; },
823
+ bringToBack() { return this; },
824
+ });
825
+ L.Polyline.include({
826
+ initialize(values, options) { L.Util.setOptions(this, options); this.setLatLngs(values); },
827
+ getLatLngs() { return this._latlngs; },
828
+ setLatLngs(values) { this._latlngs = coordinates(values); return this.redraw(); },
829
+ addLatLng(value) { this._latlngs.push(latLng(value)); return this.redraw(); },
830
+ getBounds() {
831
+ const result = new L.LatLngBounds();
832
+ const visit = values => { for (const value of values) Array.isArray(value) ? visit(value) : result.extend(value); };
833
+ visit(this._latlngs);
834
+ return result;
835
+ },
836
+ _project() {
837
+ this._rings = [];
838
+ const visit = values => {
839
+ if (!values.length) return;
840
+ if (Array.isArray(values[0])) values.forEach(visit);
841
+ else this._rings.push(values.map(value => this._map.latLngToLayerPoint(value)));
842
+ };
843
+ visit(this._latlngs);
844
+ },
845
+ closestLayerPoint(value) {
846
+ this._project();
847
+ const paths = this._rings.map(ring => ring.map(value => [value.x, value.y]));
848
+ const closest = new ol.geom.MultiLineString(paths).getClosestPoint([value.x, value.y]);
849
+ return point(closest);
850
+ },
851
+ });
852
+ L.Polyline.mergeOptions(L.Path.prototype.options);
853
+ L.Polygon.include({ initialize: L.Polyline.prototype.initialize });
854
+ L.Polygon.mergeOptions(L.Path.prototype.options);
855
+ L.Polygon.mergeOptions({ fill: true });
856
+ L.Rectangle = L.Polygon.extend({
857
+ initialize(value, options) { L.Util.setOptions(this, options); this.setBounds(value); },
858
+ setBounds(value) {
859
+ value = bounds(value);
860
+ return this.setLatLngs([value.getSouthWest(), value.getNorthWest(), value.getNorthEast(), value.getSouthEast()]);
861
+ },
862
+ });
863
+ L.CircleMarker.include({
864
+ options: { ...L.Path.prototype.options, fill: true, radius: 10 },
865
+ initialize(value, options) { L.Util.setOptions(this, options); this._latlng = latLng(value); },
866
+ getLatLng() { return this._latlng; },
867
+ setLatLng(value) { this._latlng = latLng(value); this.fire('move', { latlng: this._latlng }); return this.redraw(); },
868
+ getRadius() { return this.options.radius; },
869
+ setRadius(radius) { this.options.radius = radius; return this.redraw(); },
870
+ });
871
+ L.Circle.include({
872
+ initialize: L.CircleMarker.prototype.initialize,
873
+ _project() {
874
+ this._point = this._map.latLngToLayerPoint(this._latlng);
875
+ this._radius = this._radiusY = this.getRadius() * this._map.options.crs.scale(this._map.getZoom()) /
876
+ (2 * Math.PI * 6378137 * Math.cos(this._latlng.lat * Math.PI / 180));
877
+ },
878
+ getBounds() {
879
+ const north = this.getRadius() / 6378137 * 180 / Math.PI;
880
+ const east = north / Math.cos(this._latlng.lat * Math.PI / 180);
881
+ return new L.LatLngBounds([this._latlng.lat - north, this._latlng.lng - east],
882
+ [this._latlng.lat + north, this._latlng.lng + east]);
883
+ },
884
+ });
885
+ L.Circle.mergeOptions(L.CircleMarker.prototype.options);
886
+ L.GeoJSON = L.FeatureGroup.extend({
887
+ initialize(data, options) {
888
+ L.LayerGroup.prototype.initialize.call(this, [], options);
889
+ if (data) this.addData(data);
890
+ },
891
+ addData(data) {
892
+ const features = new ol.format.GeoJSON().readFeatures(data);
893
+ const convert = (geometry, feature) => {
894
+ const values = geometry.getCoordinates();
895
+ const locations = value => typeof value[0] === 'number' ? [value[1], value[0]] : value.map(locations);
896
+ switch (geometry.getType()) {
897
+ case 'Point': return this.options.pointToLayer
898
+ ? this.options.pointToLayer(feature, latLng(locations(values))) : new L.Marker(locations(values));
899
+ case 'MultiPoint': return new L.FeatureGroup(values.map(value => convert(new ol.geom.Point(value), feature)));
900
+ case 'LineString': case 'MultiLineString': return new L.Polyline(locations(values), this.options);
901
+ case 'Polygon': case 'MultiPolygon': return new L.Polygon(locations(values), this.options);
902
+ default: throw new Error(`Unsupported GeoJSON geometry: ${geometry.getType()}`);
903
+ }
904
+ };
905
+ for (const feature of features) {
906
+ const layer = convert(feature.getGeometry(), new ol.format.GeoJSON().writeFeatureObject(feature));
907
+ this.addLayer(layer);
908
+ }
909
+ return this;
910
+ },
911
+ });
912
+
913
+ L.GridLayer.include({
914
+ options: { pane: 'tilePane', tileSize: 256, opacity: 1, minZoom: 0, maxZoom: 24, updateInterval: 200 },
915
+ initialize(options) { L.Util.setOptions(this, options); this._tiles = {}; },
916
+ getTileSize() { return typeof this.options.tileSize === 'number'
917
+ ? new L.Point(this.options.tileSize, this.options.tileSize) : point(this.options.tileSize); },
918
+ getEvents() { return { moveend: this._onMoveEnd, zoomend: this._onMoveEnd, resize: this._onMoveEnd }; },
919
+ _initContainer() { this._container = L.DomUtil.create('div', '', this.getPane()); },
920
+ onAdd() { this._initContainer(); this._onMoveEnd(); },
921
+ onRemove() {
922
+ for (const tile of Object.values(this._tiles)) this.fire('tileunload', { tile: tile.el, coords: tile.coords });
923
+ this._tiles = {}; this._container?.remove();
924
+ },
925
+ _getTiledPixelBounds() {
926
+ return new L.Bounds(this._map.project(this._map.getBounds().getNorthWest(), this._tileZoom),
927
+ this._map.project(this._map.getBounds().getSouthEast(), this._tileZoom));
928
+ },
929
+ _onMoveEnd() {
930
+ if (!this._map) return;
931
+ this._tileZoom = Math.max(this.options.minZoom, Math.min(this.options.maxZoom, Math.round(this._map.getZoom())));
932
+ const size = this.getTileSize(), extent = this._getTiledPixelBounds(this._map.getCenter());
933
+ const min = extent.min.unscaleBy(size).floor(), max = extent.max.unscaleBy(size).floor();
934
+ const retained = new Set(), limit = 2 ** this._tileZoom;
935
+ for (let x = min.x; x <= max.x; x++) for (let y = min.y; y <= max.y; y++) {
936
+ if (y < 0 || y >= limit) continue;
937
+ const key = `${this._tileZoom}:${x}:${y}`;
938
+ retained.add(key);
939
+ if (this._tiles[key]) continue;
940
+ const coords = new L.Point(x, y); coords.z = this._tileZoom;
941
+ const tile = this._tiles[key] = { coords, current: true, loaded: 0 };
942
+ const done = (error, element) => {
943
+ if (this._tiles[key] !== tile) return;
944
+ tile.el = element;
945
+ if (error) { this.fire('tileerror', { error, tile: element, coords }); return; }
946
+ tile.loaded = Date.now();
947
+ this.fire('tileload', { tile: element, coords });
948
+ this.fire('load');
949
+ };
950
+ tile.el = this.createTile(coords, done);
951
+ if (this.createTile.length < 2) done(null, tile.el);
952
+ }
953
+ for (const [key, tile] of Object.entries(this._tiles)) {
954
+ if (retained.has(key)) continue;
955
+ delete this._tiles[key];
956
+ this.fire('tileunload', { tile: tile.el, coords: tile.coords });
957
+ }
958
+ },
959
+ _tileCoordsToNwSe(coords) {
960
+ const size = this.getTileSize(), nw = new L.Point(coords.x * size.x, coords.y * size.y);
961
+ return [this._map.unproject(nw, coords.z), this._map.unproject(nw.add(size), coords.z)];
962
+ },
963
+ setOpacity(opacity) { this.options.opacity = opacity; return this; },
964
+ setZIndex(zIndex) { this.options.zIndex = zIndex; return this; },
965
+ redraw() { if (this._map) { this.onRemove(); this._onMoveEnd(); } return this; },
966
+ });
967
+ L.TileLayer = L.GridLayer.extend({
968
+ initialize(url, options) {
969
+ L.GridLayer.prototype.initialize.call(this, options); this.setUrl(url);
970
+ },
971
+ setUrl(url) { this._url = url; this._source = new ol.source.XYZ({ url, crossOrigin: 'anonymous' }); return this.redraw(); },
972
+ createTile(coords, done) {
973
+ const url = this._source.getTileUrlFunction()([coords.z, coords.x, coords.y], 1, ol.proj.get('EPSG:3857'));
974
+ const tile = image(url, this.options, '');
975
+ tile.onload = () => done(null, tile);
976
+ tile.onerror = () => done(new Error(`Unable to load tile: ${url}`), tile);
977
+ return tile;
978
+ },
979
+ });
980
+ L.TileLayer.WMS = L.TileLayer.extend({
981
+ setUrl(url) {
982
+ this._url = url;
983
+ this._source = new ol.source.TileWMS({ url, crossOrigin: 'anonymous', params: {
984
+ LAYERS: this.options.layers, FORMAT: this.options.format || 'image/png',
985
+ TRANSPARENT: this.options.transparent || false,
986
+ } });
987
+ return this.redraw();
988
+ },
989
+ });
990
+ L.ImageOverlay.include({
991
+ options: { pane: 'overlayPane', opacity: 1, interactive: false },
992
+ initialize(url, value, options) { L.Util.setOptions(this, options); this._url = url; this._bounds = bounds(value); },
993
+ _initImage() {
994
+ this._image = image(this._url, this.options, '');
995
+ this._image.onload = () => this.fire('load');
996
+ this._image.onerror = () => this.fire('error', { error: new Error(`Unable to load image: ${this._url}`) });
997
+ },
998
+ getBounds() { return this._bounds; },
999
+ setBounds(value) { this._bounds = bounds(value); return this; },
1000
+ setUrl(url) { this._url = url; if (this._image) this._image.src = url; return this; },
1001
+ setOpacity(opacity) { this.options.opacity = opacity; return this; },
1002
+ setZIndex(zIndex) { this.options.zIndex = zIndex; return this; },
1003
+ });
1004
+
1005
+ L.Icon = L.Evented.extend({
1006
+ initialize(options) { L.Util.setOptions(this, options); },
1007
+ createIcon() { return this._create('icon'); },
1008
+ createShadow() { return this.options.shadowUrl ? this._create('shadow') : null; },
1009
+ _create(kind) {
1010
+ const element = image(this.options[`${kind}Url`], this.options, `leaflet-marker-${kind}`);
1011
+ const size = this.options[`${kind}Size`], anchor = this.options[`${kind}Anchor`] || this.options.iconAnchor;
1012
+ if (size) { element.style.width = `${size[0]}px`; element.style.height = `${size[1]}px`; }
1013
+ if (anchor) { element.style.marginLeft = `${-anchor[0]}px`; element.style.marginTop = `${-anchor[1]}px`; }
1014
+ return element;
1015
+ },
1016
+ });
1017
+ L.Icon.Default = L.Icon.extend({
1018
+ options: {
1019
+ iconUrl: 'https://unpkg.com/leaflet@1.9/dist/images/marker-icon.png',
1020
+ shadowUrl: 'https://unpkg.com/leaflet@1.9/dist/images/marker-shadow.png',
1021
+ iconSize: [25, 41], iconAnchor: [12, 41], shadowSize: [41, 41], shadowAnchor: [12, 41],
1022
+ },
1023
+ });
1024
+ L.DivIcon = L.Icon.extend({
1025
+ createIcon() {
1026
+ const element = L.DomUtil.create('div', `leaflet-marker-icon ${this.options.className || ''}`);
1027
+ if (this.options.html instanceof Node) element.append(this.options.html); else element.innerHTML = this.options.html || '';
1028
+ const size = this.options.iconSize || [12, 12], anchor = this.options.iconAnchor || size.map(value => value / 2);
1029
+ element.style.cssText = `width:${size[0]}px;height:${size[1]}px;margin-left:${-anchor[0]}px;margin-top:${-anchor[1]}px`;
1030
+ return element;
1031
+ },
1032
+ createShadow() { return null; },
1033
+ });
1034
+ L.Marker.include({
1035
+ options: { pane: 'markerPane', icon: new L.Icon.Default(), opacity: 1, interactive: true, zIndexOffset: 0 },
1036
+ initialize(value, options) { L.Util.setOptions(this, options); this._latlng = latLng(value); },
1037
+ getLatLng() { return this._latlng; },
1038
+ setLatLng(value) { this._latlng = latLng(value); this.update(); return this.fire('move', { latlng: this._latlng }); },
1039
+ setIcon(icon) { this.options.icon = icon; if (this._map) { this.onRemove(); this.onAdd(); } return this; },
1040
+ getEvents() { return { zoom: this.update, viewreset: this.update }; },
1041
+ onAdd() {
1042
+ this._icon = this.options.icon.createIcon(); this._shadow = this.options.icon.createShadow();
1043
+ this._icon.tabIndex = 0; this._icon.setAttribute('role', 'button');
1044
+ this._icon.setAttribute('aria-label', this.options.title || 'Marker');
1045
+ this._icon.title = this.options.title || '';
1046
+ for (const element of [this._shadow, this._icon]) if (element) this.getPane().append(element);
1047
+ this._initInteraction(); this.update();
1048
+ },
1049
+ onRemove() { this.dragging?.disable(); this._icon?.remove(); this._shadow?.remove(); },
1050
+ update() { if (this._map && this._icon) this._setPos(this._map.latLngToLayerPoint(this._latlng)); return this; },
1051
+ _setPos(value) {
1052
+ for (const element of [this._icon, this._shadow]) if (element) L.DomUtil.setPosition(element, value);
1053
+ this._zIndex = value.y + this.options.zIndexOffset; this._updateZIndex(0);
1054
+ },
1055
+ _updateZIndex(offset) { if (this._icon) this._icon.style.zIndex = this._zIndex + offset; },
1056
+ setOpacity(opacity) { this.options.opacity = opacity; return this; },
1057
+ setZIndexOffset(value) { this.options.zIndexOffset = value; return this.update(); },
1058
+ _initInteraction() {
1059
+ this._icon._cityLayer = this;
1060
+ const drag = this.dragging = new L.Handler(this._map);
1061
+ const icon = this._icon;
1062
+ L.DomEvent.on(icon, 'pointerdown', event => {
1063
+ this._map._moved = false;
1064
+ event.stopPropagation();
1065
+ });
1066
+ L.DomEvent.on(icon, 'keydown', event => {
1067
+ if (event.key !== 'Enter' && event.key !== ' ') return;
1068
+ L.DomEvent.stop(event);
1069
+ this.fire('click', { latlng: this._latlng, originalEvent: event });
1070
+ });
1071
+ drag._marker = this;
1072
+ drag._draggable = new L.Evented();
1073
+ drag._onDrag = function () {
1074
+ const marker = this._marker;
1075
+ marker._latlng = marker._map.layerPointToLatLng(L.DomUtil.getPosition(marker._icon));
1076
+ marker.fire('move', { latlng: marker._latlng }).fire('drag', { latlng: marker._latlng });
1077
+ };
1078
+ drag._adjustPan = function () {};
1079
+ drag._draggable.on('drag', drag._onDrag, drag);
1080
+ let pointer, anchor, origin, moved;
1081
+ const handle = event => {
1082
+ if (!drag.enabled()) return;
1083
+ if (event.type === 'pointerdown') {
1084
+ if (event.button !== 0) return;
1085
+ event.preventDefault(); event.stopPropagation();
1086
+ pointer = event.pointerId; anchor = [event.clientX, event.clientY];
1087
+ origin = this._map.latLngToContainerPoint(this._latlng); moved = false;
1088
+ icon.setPointerCapture(pointer);
1089
+ this.fire('dragstart');
1090
+ } else if (event.pointerId === pointer && event.type === 'pointermove') {
1091
+ const value = new L.Point(origin.x + event.clientX - anchor[0], origin.y + event.clientY - anchor[1]);
1092
+ L.DomUtil.setPosition(icon, value);
1093
+ drag._draggable._newPos = value;
1094
+ drag._draggable.fire('drag', { originalEvent: event });
1095
+ moved = true;
1096
+ } else if (event.pointerId === pointer) {
1097
+ if (icon.hasPointerCapture(pointer)) icon.releasePointerCapture(pointer);
1098
+ pointer = null; this.fire('dragend'); this._map._moved = moved;
1099
+ }
1100
+ };
1101
+ drag.addHooks = () => L.DomEvent.on(icon, 'pointerdown pointermove pointerup pointercancel', handle);
1102
+ drag.removeHooks = () => L.DomEvent.off(icon, 'pointerdown pointermove pointerup pointercancel', handle);
1103
+ if (this.options.draggable) drag.enable();
1104
+ },
1105
+ });
1106
+
1107
+ L.Map.include({
1108
+ _fireDOMEvent(event, type = event.type, targets) {
1109
+ const rect = this.getContainer().getBoundingClientRect();
1110
+ const pixel = new L.Point(event.clientX - rect.left, event.clientY - rect.top);
1111
+ const location = this.containerPointToLatLng(pixel);
1112
+ const layer = event.target.closest?.('.leaflet-marker-icon')?._cityLayer;
1113
+ if (type === 'click' && this._draggableMoved(layer)) return;
1114
+ for (const target of targets || (layer ? [layer] : [this])) {
1115
+ target.fire(type, { originalEvent: event, latlng: location, containerPoint: pixel,
1116
+ layerPoint: this.containerPointToLayerPoint(pixel) });
1117
+ }
1118
+ },
1119
+ });
1120
+ L.Map.addInitHook(function () {
1121
+ const mapPane = this.createPane('mapPane');
1122
+ for (const [name, order] of [['tilePane', 200], ['overlayPane', 400], ['markerPane', 600]]) {
1123
+ this.createPane(name, mapPane).style.zIndex = order;
1124
+ }
1125
+ const handler = event => this._fireDOMEvent(event);
1126
+ L.DomEvent.on(this.getContainer(), 'click mousemove mouseout mouseover', handler);
1127
+ this.once('unload', () => L.DomEvent.off(this.getContainer(), 'click mousemove mouseout mouseover', handler));
1128
+ });
1129
+ })();