@navaramap/three 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/LICENSE +8 -0
  2. package/dist/assets/atmosphere/README.md +1 -0
  3. package/dist/assets/atmosphere/higher_order_scattering.bin +0 -0
  4. package/dist/assets/atmosphere/higher_order_scattering.exr +0 -0
  5. package/dist/assets/atmosphere/irradiance.bin +0 -0
  6. package/dist/assets/atmosphere/irradiance.exr +0 -0
  7. package/dist/assets/atmosphere/scattering.bin +0 -0
  8. package/dist/assets/atmosphere/scattering.exr +0 -0
  9. package/dist/assets/atmosphere/single_mie_scattering.bin +0 -0
  10. package/dist/assets/atmosphere/single_mie_scattering.exr +0 -0
  11. package/dist/assets/atmosphere/stars.bin +0 -0
  12. package/dist/assets/atmosphere/transmittance.bin +0 -0
  13. package/dist/assets/atmosphere/transmittance.exr +0 -0
  14. package/dist/assets/cloud/README.md +1 -0
  15. package/dist/assets/cloud/local_weather.png +0 -0
  16. package/dist/assets/cloud/shape.bin +1 -0
  17. package/dist/assets/cloud/shape_detail.bin +1 -0
  18. package/dist/assets/cloud/turbulence.png +0 -0
  19. package/dist/assets/fontWorker-CEJEfRpb.js +2 -0
  20. package/dist/assets/fontWorker-CEJEfRpb.js.map +1 -0
  21. package/dist/assets/index-BUB-ijMO.js +12 -0
  22. package/dist/assets/index-BUB-ijMO.js.map +1 -0
  23. package/dist/assets/navara_wasm_font_worker_bg-665OLxjy.wasm +0 -0
  24. package/dist/assets/navara_wasm_worker_bg-BXhKC5bE.wasm +0 -0
  25. package/dist/assets/noise/README.md +1 -0
  26. package/dist/assets/noise/stbn.bin +0 -0
  27. package/dist/assets/water/README.md +3 -0
  28. package/dist/assets/water/waternormals.jpg +0 -0
  29. package/dist/index.d.ts +10114 -0
  30. package/dist/index.js +34481 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/index.umd.cjs +6345 -0
  33. package/dist/index.umd.cjs.map +1 -0
  34. package/package.json +95 -0
  35. package/src/Color.ts +111 -0
  36. package/src/ShadowMapViewers.ts +49 -0
  37. package/src/atmosphere.test.ts +76 -0
  38. package/src/atmosphere.ts +364 -0
  39. package/src/bufferView.ts +90 -0
  40. package/src/camera.ts +182 -0
  41. package/src/concurrency.ts +15 -0
  42. package/src/constants/assets.ts +30 -0
  43. package/src/constants/decoder.ts +7 -0
  44. package/src/constants/index.ts +2 -0
  45. package/src/core/BaseDesc.ts +142 -0
  46. package/src/core/BaseHandle.ts +126 -0
  47. package/src/core/DescRegistry.ts +88 -0
  48. package/src/core/EffectDesc.ts +343 -0
  49. package/src/core/EffectDescRegistry.ts +31 -0
  50. package/src/core/InstancedMeshDesc.ts +413 -0
  51. package/src/core/LightDesc.ts +216 -0
  52. package/src/core/LightDescRegistry.ts +32 -0
  53. package/src/core/MeshDesc.ts +457 -0
  54. package/src/core/MeshDescRegistry.ts +32 -0
  55. package/src/core/MeshDescWithSelectiveEffect.ts +129 -0
  56. package/src/core/Registries.ts +59 -0
  57. package/src/core/SelectiveEffectRegistry.ts +90 -0
  58. package/src/core/ViewContext.ts +235 -0
  59. package/src/core/errors.ts +16 -0
  60. package/src/core/index.ts +50 -0
  61. package/src/device.ts +414 -0
  62. package/src/effects/CustomEffectPass.ts +28 -0
  63. package/src/effects/copy.ts +10 -0
  64. package/src/effects/effect.ts +87 -0
  65. package/src/effects/index.ts +4 -0
  66. package/src/effects/render.ts +10 -0
  67. package/src/evaluations/FeatureEvaluator.ts +529 -0
  68. package/src/evaluations/index.ts +1 -0
  69. package/src/event/HillshadeContext.test.ts +232 -0
  70. package/src/event/HillshadeContext.ts +299 -0
  71. package/src/event/context.ts +279 -0
  72. package/src/event/feature.ts +357 -0
  73. package/src/event/featureEvent.ts +81 -0
  74. package/src/event/features/billboard.ts +30 -0
  75. package/src/event/features/model.ts +192 -0
  76. package/src/event/features/modelGpuBytes.test.ts +69 -0
  77. package/src/event/features/modelGpuBytes.ts +87 -0
  78. package/src/event/features/point.ts +25 -0
  79. package/src/event/features/polygon.ts +23 -0
  80. package/src/event/features/polygonOutline.ts +19 -0
  81. package/src/event/features/polyline.ts +19 -0
  82. package/src/event/features/text.ts +71 -0
  83. package/src/event/hillshade.test.ts +842 -0
  84. package/src/event/hillshade.ts +588 -0
  85. package/src/event/index.ts +700 -0
  86. package/src/event/loaders.ts +53 -0
  87. package/src/event/tile.ts +19 -0
  88. package/src/event/worker.ts +999 -0
  89. package/src/event/workerTaskSettlement.test.ts +243 -0
  90. package/src/event/workerTaskSettlement.ts +128 -0
  91. package/src/inMemoryBufferStore.test.ts +60 -0
  92. package/src/inMemoryBufferStore.ts +67 -0
  93. package/src/index.iife.ts +11 -0
  94. package/src/index.ts +2556 -0
  95. package/src/input.ts +144 -0
  96. package/src/layer.ts +183 -0
  97. package/src/layers/effect/FinalCopyEffectDesc.ts +29 -0
  98. package/src/layers/effect/MRTPassEffectDesc.ts +105 -0
  99. package/src/layers/effect/README.md +9 -0
  100. package/src/layers/effect/SelectiveEffectDesc.ts +113 -0
  101. package/src/layers/effect/SkyEnvMapEffectDesc.ts +58 -0
  102. package/src/layers/effect/TransparentPassEffectDesc.ts +62 -0
  103. package/src/layers/effect/index.ts +5 -0
  104. package/src/layers/index.ts +1 -0
  105. package/src/layersManager.ts +63 -0
  106. package/src/light.ts +15 -0
  107. package/src/loaders/AbortableImageLoader.ts +203 -0
  108. package/src/loaders/AbortableTextureLoader.ts +74 -0
  109. package/src/loaders/index.ts +5 -0
  110. package/src/material/enhancer/DESIGN.md +300 -0
  111. package/src/material/enhancer/MaterialEnhancer.ts +112 -0
  112. package/src/material/enhancer/ShaderReplacer.test.ts +114 -0
  113. package/src/material/enhancer/ShaderReplacer.ts +97 -0
  114. package/src/material/enhancer/batchTexture.ts +8 -0
  115. package/src/material/enhancer/index.ts +46 -0
  116. package/src/material/enhancer/instancedSprite/index.ts +7 -0
  117. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.test.ts +214 -0
  118. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.ts +134 -0
  119. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/markers.ts +21 -0
  120. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/material.ts +32 -0
  121. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.test.ts +277 -0
  122. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.ts +151 -0
  123. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.test.ts +114 -0
  124. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.ts +49 -0
  125. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.test.ts +82 -0
  126. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.ts +80 -0
  127. package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/types.ts +146 -0
  128. package/src/material/enhancer/instancedSprite/instancedSpriteMaterialEnhancer.ts +105 -0
  129. package/src/material/enhancer/model/index.ts +25 -0
  130. package/src/material/enhancer/model/modelBaseEnhancer/index.test.ts +74 -0
  131. package/src/material/enhancer/model/modelBaseEnhancer/index.ts +110 -0
  132. package/src/material/enhancer/model/modelBaseEnhancer/markers.ts +27 -0
  133. package/src/material/enhancer/model/modelBaseEnhancer/material.test.ts +66 -0
  134. package/src/material/enhancer/model/modelBaseEnhancer/material.ts +44 -0
  135. package/src/material/enhancer/model/modelBaseEnhancer/mutates.test.ts +84 -0
  136. package/src/material/enhancer/model/modelBaseEnhancer/mutates.ts +47 -0
  137. package/src/material/enhancer/model/modelBaseEnhancer/shader.test.ts +24 -0
  138. package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +162 -0
  139. package/src/material/enhancer/model/modelBaseEnhancer/state.test.ts +103 -0
  140. package/src/material/enhancer/model/modelBaseEnhancer/state.ts +52 -0
  141. package/src/material/enhancer/model/modelBaseEnhancer/types.ts +89 -0
  142. package/src/material/enhancer/model/modelMaterialEnhancer.ts +91 -0
  143. package/src/material/enhancer/model/modelWaterEnhancer/index.test.ts +71 -0
  144. package/src/material/enhancer/model/modelWaterEnhancer/index.ts +175 -0
  145. package/src/material/enhancer/model/modelWaterEnhancer/material.test.ts +48 -0
  146. package/src/material/enhancer/model/modelWaterEnhancer/material.ts +25 -0
  147. package/src/material/enhancer/model/modelWaterEnhancer/mutates.test.ts +89 -0
  148. package/src/material/enhancer/model/modelWaterEnhancer/mutates.ts +89 -0
  149. package/src/material/enhancer/model/modelWaterEnhancer/shader.test.ts +34 -0
  150. package/src/material/enhancer/model/modelWaterEnhancer/shader.ts +111 -0
  151. package/src/material/enhancer/model/modelWaterEnhancer/state.test.ts +105 -0
  152. package/src/material/enhancer/model/modelWaterEnhancer/state.ts +59 -0
  153. package/src/material/enhancer/model/modelWaterEnhancer/types.ts +141 -0
  154. package/src/material/enhancer/model/pntsEnhancer/index.test.ts +96 -0
  155. package/src/material/enhancer/model/pntsEnhancer/index.ts +82 -0
  156. package/src/material/enhancer/model/pntsEnhancer/markers.ts +6 -0
  157. package/src/material/enhancer/model/pntsEnhancer/material.test.ts +37 -0
  158. package/src/material/enhancer/model/pntsEnhancer/material.ts +21 -0
  159. package/src/material/enhancer/model/pntsEnhancer/mutates.test.ts +80 -0
  160. package/src/material/enhancer/model/pntsEnhancer/mutates.ts +29 -0
  161. package/src/material/enhancer/model/pntsEnhancer/shader.test.ts +32 -0
  162. package/src/material/enhancer/model/pntsEnhancer/shader.ts +56 -0
  163. package/src/material/enhancer/model/pntsEnhancer/state.test.ts +87 -0
  164. package/src/material/enhancer/model/pntsEnhancer/state.ts +29 -0
  165. package/src/material/enhancer/model/pntsEnhancer/types.ts +38 -0
  166. package/src/material/enhancer/polygon/index.ts +22 -0
  167. package/src/material/enhancer/polygon/polygonBaseEnhancer/index.test.ts +101 -0
  168. package/src/material/enhancer/polygon/polygonBaseEnhancer/index.ts +130 -0
  169. package/src/material/enhancer/polygon/polygonBaseEnhancer/markers.ts +23 -0
  170. package/src/material/enhancer/polygon/polygonBaseEnhancer/material.test.ts +19 -0
  171. package/src/material/enhancer/polygon/polygonBaseEnhancer/material.ts +38 -0
  172. package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.test.ts +75 -0
  173. package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +122 -0
  174. package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.test.ts +25 -0
  175. package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +249 -0
  176. package/src/material/enhancer/polygon/polygonBaseEnhancer/state.test.ts +102 -0
  177. package/src/material/enhancer/polygon/polygonBaseEnhancer/state.ts +92 -0
  178. package/src/material/enhancer/polygon/polygonBaseEnhancer/types.ts +150 -0
  179. package/src/material/enhancer/polygon/polygonMaterialEnhancer.ts +97 -0
  180. package/src/material/enhancer/polygon/polygonWaterEnhancer/index.test.ts +72 -0
  181. package/src/material/enhancer/polygon/polygonWaterEnhancer/index.ts +164 -0
  182. package/src/material/enhancer/polygon/polygonWaterEnhancer/material.test.ts +48 -0
  183. package/src/material/enhancer/polygon/polygonWaterEnhancer/material.ts +35 -0
  184. package/src/material/enhancer/polygon/polygonWaterEnhancer/mutates.ts +61 -0
  185. package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.test.ts +34 -0
  186. package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.ts +96 -0
  187. package/src/material/enhancer/polygon/polygonWaterEnhancer/state.test.ts +98 -0
  188. package/src/material/enhancer/polygon/polygonWaterEnhancer/state.ts +57 -0
  189. package/src/material/enhancer/polygon/polygonWaterEnhancer/types.ts +115 -0
  190. package/src/material/enhancer/polyline/index.ts +7 -0
  191. package/src/material/enhancer/polyline/polylineBaseEnhancer/index.test.ts +157 -0
  192. package/src/material/enhancer/polyline/polylineBaseEnhancer/index.ts +154 -0
  193. package/src/material/enhancer/polyline/polylineBaseEnhancer/markers.ts +19 -0
  194. package/src/material/enhancer/polyline/polylineBaseEnhancer/material.test.ts +42 -0
  195. package/src/material/enhancer/polyline/polylineBaseEnhancer/material.ts +38 -0
  196. package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.test.ts +120 -0
  197. package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.ts +169 -0
  198. package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.test.ts +67 -0
  199. package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.ts +75 -0
  200. package/src/material/enhancer/polyline/polylineBaseEnhancer/state.test.ts +55 -0
  201. package/src/material/enhancer/polyline/polylineBaseEnhancer/state.ts +91 -0
  202. package/src/material/enhancer/polyline/polylineBaseEnhancer/types.ts +170 -0
  203. package/src/material/enhancer/polyline/polylineMaterialEnhancer.ts +102 -0
  204. package/src/material/enhancer/sdfText/index.ts +7 -0
  205. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +125 -0
  206. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/markers.ts +19 -0
  207. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.test.ts +35 -0
  208. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.ts +32 -0
  209. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +381 -0
  210. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +232 -0
  211. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +36 -0
  212. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +118 -0
  213. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +107 -0
  214. package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +184 -0
  215. package/src/material/enhancer/sdfText/sdfTextMaterialEnhancer.ts +100 -0
  216. package/src/material/enhancer/shadowMap/index.ts +7 -0
  217. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/index.ts +97 -0
  218. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/markers.ts +10 -0
  219. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/material.ts +31 -0
  220. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/mutates.ts +8 -0
  221. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.test.ts +17 -0
  222. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.ts +53 -0
  223. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/state.ts +17 -0
  224. package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/types.ts +26 -0
  225. package/src/material/enhancer/test-utils/shaderCompatibility.ts +124 -0
  226. package/src/material/enhancer/tileComposite/compose.test.ts +95 -0
  227. package/src/material/enhancer/tileComposite/compose.ts +112 -0
  228. package/src/material/enhancer/tileComposite/index.ts +23 -0
  229. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/index.ts +11 -0
  230. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/mutates.ts +132 -0
  231. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/shader.ts +245 -0
  232. package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/types.ts +43 -0
  233. package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/index.ts +29 -0
  234. package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/mutates.ts +68 -0
  235. package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/shader.ts +24 -0
  236. package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/index.ts +29 -0
  237. package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/mutates.ts +34 -0
  238. package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/shader.ts +36 -0
  239. package/src/material/enhancer/tileComposite/tileWaterEnhancer/index.ts +23 -0
  240. package/src/material/enhancer/tileComposite/tileWaterEnhancer/mutates.ts +23 -0
  241. package/src/material/enhancer/tileComposite/tileWaterEnhancer/shader.ts +8 -0
  242. package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/index.ts +23 -0
  243. package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/mutates.ts +34 -0
  244. package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/shader.ts +13 -0
  245. package/src/material/enhancer/tileComposite/types.ts +58 -0
  246. package/src/material/index.ts +3 -0
  247. package/src/material/macro/index.ts +1 -0
  248. package/src/material/macro/tileShader.test.ts +136 -0
  249. package/src/material/macro/tileShader.ts +287 -0
  250. package/src/material/overrideMaterialsForMRT.ts +439 -0
  251. package/src/material/selectiveEffectMaterialSetup.test.ts +52 -0
  252. package/src/material/selectiveEffectMaterialSetup.ts +53 -0
  253. package/src/material/types/index.ts +1 -0
  254. package/src/material/types/uniform.ts +1 -0
  255. package/src/mesh/DrapedMesh.ts +101 -0
  256. package/src/mesh/batchTexture.test.ts +646 -0
  257. package/src/mesh/batchTexture.ts +437 -0
  258. package/src/mesh/batchedFeature.ts +186 -0
  259. package/src/mesh/batchedSdfText.ts +484 -0
  260. package/src/mesh/featureMesh.ts +43 -0
  261. package/src/mesh/index.ts +16 -0
  262. package/src/mesh/instanced.ts +117 -0
  263. package/src/mesh/model.ts +483 -0
  264. package/src/mesh/pickableMesh.ts +55 -0
  265. package/src/mesh/pickableMeshWrapper.ts +529 -0
  266. package/src/mesh/polygon.ts +699 -0
  267. package/src/mesh/polygonOutline.ts +393 -0
  268. package/src/mesh/polyline.ts +523 -0
  269. package/src/mesh/releaseGeometryArrays.ts +57 -0
  270. package/src/mesh/rtcRteHelper.ts +211 -0
  271. package/src/mesh/sdfText.test.ts +200 -0
  272. package/src/mesh/sdfText.ts +1019 -0
  273. package/src/mesh/sprite/billboardAtlas.test.ts +177 -0
  274. package/src/mesh/sprite/billboardAtlas.ts +210 -0
  275. package/src/mesh/sprite/billboardAtlasImageLoader.ts +29 -0
  276. package/src/mesh/sprite/index.ts +1 -0
  277. package/src/mesh/sprite/instancedSprite.ts +694 -0
  278. package/src/mesh/tile.test.ts +393 -0
  279. package/src/mesh/tile.ts +1790 -0
  280. package/src/object3DEvent.ts +6 -0
  281. package/src/orchestrators/RenderPassOrchestrator.ts +147 -0
  282. package/src/orchestrators/index.ts +1 -0
  283. package/src/passes/AllDepthCopyPass.ts +165 -0
  284. package/src/passes/CustomRenderPass.ts +279 -0
  285. package/src/passes/NormalCopyPass.ts +125 -0
  286. package/src/passes/RenderTargetCopyPass.ts +91 -0
  287. package/src/passes/SkyEnvMapPass.ts +58 -0
  288. package/src/passes/index.ts +5 -0
  289. package/src/pick/pickHelper.ts +273 -0
  290. package/src/pick/pickTerrain.ts +254 -0
  291. package/src/plugins/AttributionPlugin/AttributionPlugin.test.ts +210 -0
  292. package/src/plugins/AttributionPlugin/attribution.test.ts +225 -0
  293. package/src/plugins/AttributionPlugin/attribution.ts +355 -0
  294. package/src/plugins/AttributionPlugin/attributionStyles.ts +163 -0
  295. package/src/plugins/AttributionPlugin/index.ts +702 -0
  296. package/src/plugins/index.ts +1 -0
  297. package/src/quality.ts +1 -0
  298. package/src/renderOrder.ts +3 -0
  299. package/src/scene.test.ts +164 -0
  300. package/src/scene.ts +127 -0
  301. package/src/shaders/index.ts +1 -0
  302. package/src/solar.test.ts +81 -0
  303. package/src/solar.ts +101 -0
  304. package/src/source.ts +80 -0
  305. package/src/stats.ts +102 -0
  306. package/src/tasks/computeVertexNormalsAsync.ts +15 -0
  307. package/src/tasks/constructPolygonBatchedFeature.ts +34 -0
  308. package/src/tasks/constructPolylineBatchedFeature.ts +27 -0
  309. package/src/tasks/constructQuantizedMeshTerrainMesh.ts +24 -0
  310. package/src/tasks/constructTerrainMesh.ts +25 -0
  311. package/src/tasks/getImageDataFromBlob.ts +13 -0
  312. package/src/tasks/getImageDataFromImageBitmap.ts +12 -0
  313. package/src/tasks/parseMvtTile.ts +22 -0
  314. package/src/tasks/queueTask.ts +19 -0
  315. package/src/tasks/toCreasedNormalsAsync.ts +18 -0
  316. package/src/tasks/upsampleQuantizedMeshTerrainMesh.ts +44 -0
  317. package/src/tasks/upsampleTerrainMesh.ts +39 -0
  318. package/src/texture.ts +19 -0
  319. package/src/textures.ts +13 -0
  320. package/src/tileTexture/CompositeShader.snapshot.test.ts +59 -0
  321. package/src/tileTexture/CompositeShader.test.ts +168 -0
  322. package/src/tileTexture/CompositeShader.ts +53 -0
  323. package/src/tileTexture/SlotPlanner.test.ts +105 -0
  324. package/src/tileTexture/SlotPlanner.ts +102 -0
  325. package/src/tileTexture/TileTextureCache.test.ts +227 -0
  326. package/src/tileTexture/TileTextureCache.ts +192 -0
  327. package/src/tileTexture/TileTextureCompositor.test.ts +260 -0
  328. package/src/tileTexture/TileTextureCompositor.ts +421 -0
  329. package/src/tileTexture/__snapshots__/CompositeShader.snapshot.test.ts.snap +2649 -0
  330. package/src/tileTexture/index.ts +8 -0
  331. package/src/tileTexture/types.ts +150 -0
  332. package/src/type/index.ts +261 -0
  333. package/src/uniforms.ts +20 -0
  334. package/src/utils/arraysEqual.ts +9 -0
  335. package/src/utils/blendModes.ts +80 -0
  336. package/src/utils/geometry.ts +53 -0
  337. package/src/utils/hillshadeNormalMapGenerator.ts +193 -0
  338. package/src/utils/index.ts +8 -0
  339. package/src/utils/object.test.ts +14 -0
  340. package/src/utils/object.ts +7 -0
  341. package/src/utils/replacer.test.ts +42 -0
  342. package/src/utils/replacer.ts +89 -0
  343. package/src/utils/screen.ts +29 -0
  344. package/src/utils/textureFragmentIndex.test.ts +224 -0
  345. package/src/utils/textureFragmentIndex.ts +97 -0
  346. package/src/utils/worker.ts +3 -0
  347. package/src/vite-env.d.ts +2 -0
  348. package/src/worker/index.ts +13 -0
  349. package/src/worker/tasks/computeVertexNormals.ts +15 -0
  350. package/src/worker/tasks/toCreasedNormals.ts +22 -0
@@ -0,0 +1,702 @@
1
+ /**
2
+ * AttributionPlugin — Navara Plugin for map data attribution (credit) UI.
3
+ *
4
+ * Renders a non-modal popover (ⓘ button, bottom-right by default) listing data-source
5
+ * attributions, with a separate always-visible logo frame in the bottom-left
6
+ * corner. Each source can carry zoom-banded child credits that switch as the
7
+ * camera zooms, and per-layer feature credits are tracked dynamically.
8
+ *
9
+ * `ThreeView` creates one by default and exposes it as `view.attribution`
10
+ * (pass `defaultAttribution: false` to opt out).
11
+ *
12
+ * ## Usage
13
+ *
14
+ * ```ts
15
+ * import ThreeView from "@navaramap/three";
16
+ *
17
+ * const view = new ThreeView({ container });
18
+ * await view.init();
19
+ *
20
+ * // A 3D-tiles layer whose tiles embed their own copyright (tracked dynamically).
21
+ * const photoreal = view.addLayer({ type: "cesium3dtiles", data: { url } });
22
+ *
23
+ * // `add` / `remove` manage the set of displayed attributions.
24
+ * view.attribution?.add([
25
+ * {
26
+ * attribution: "Geospatial Information Authority of Japan (GSI)",
27
+ * attributionUrl: "https://maps.gsi.go.jp/development/ichiran.html",
28
+ * children: [
29
+ * { attribution: "Nationwide latest aerial photos (seamless)", minZoom: 14, maxZoom: 18 },
30
+ * { attribution: "GRUS画像(© Axelspace)", minZoom: 14, maxZoom: 18 },
31
+ * ],
32
+ * },
33
+ * {
34
+ * // Per-tile copyright is tracked dynamically by resolving this layer id.
35
+ * attribution: "Google Maps Photorealistic 3D Tiles",
36
+ * creditLayerId: photoreal.id,
37
+ * },
38
+ * {
39
+ * attributionHtml:
40
+ * '<a href="https://s2maps.eu">Sentinel-2 cloudless 2020</a> by <a href="https://eox.at">EOX IT Services GmbH</a>',
41
+ * },
42
+ * ]);
43
+ *
44
+ * // Drop a source again when its data leaves the map (matched structurally).
45
+ * view.attribution?.remove([{ attribution: "Google Maps Photorealistic 3D Tiles", creditLayerId: photoreal.id }]);
46
+ *
47
+ * // Re-theme at runtime (e.g. light / dark switch).
48
+ * view.attribution?.setStyle({ backgroundColor: "#14181c", textColor: "#e6e9ee" });
49
+ *
50
+ * // The popover is collapsed by default; show() opens it and hide() collapses
51
+ * // it (the ⓘ button toggles the same state).
52
+ * view.attribution?.hide();
53
+ * view.attribution?.show();
54
+ * ```
55
+ */
56
+ import { Plugin } from "@navaramap/core";
57
+
58
+ import type { ViewContext } from "../../core";
59
+ import type ThreeView from "../../index";
60
+ import type {
61
+ Layer,
62
+ FeatureCreatedParams,
63
+ FeatureRemovedParams,
64
+ FeatureVisibilityChangedParams,
65
+ } from "../../layer";
66
+
67
+ import {
68
+ aggregateCredits,
69
+ appendSanitizedHtml,
70
+ attributionItemKey,
71
+ createSafeAnchor,
72
+ dedupeAttributionItems,
73
+ isAttributionHtml,
74
+ matchesZoom,
75
+ safeHref,
76
+ type AttributionItem,
77
+ type AttributionStyle,
78
+ } from "./attribution";
79
+ import {
80
+ STYLE_ELEMENT_ID,
81
+ STYLE_TEXT,
82
+ SVG_ICON_HTML,
83
+ } from "./attributionStyles";
84
+
85
+ export {
86
+ isAttributionHtml,
87
+ type AttributionItem,
88
+ type AttributionSource,
89
+ type AttributionHtml,
90
+ type AttributionChild,
91
+ type AttributionStyle,
92
+ } from "./attribution";
93
+
94
+ type View = ThreeView;
95
+
96
+ /** Which bottom corner the attribution UI anchors to. */
97
+ export type AttributionPosition = "bottom-left" | "bottom-right";
98
+
99
+ /** Options for {@link AttributionPlugin}. */
100
+ export type AttributionPluginOptions = {
101
+ /** Initial color overrides; tweak later with {@link AttributionPlugin.setStyle}. */
102
+ style?: AttributionStyle;
103
+ /**
104
+ * Bottom corner for the ⓘ button and its popover card. Defaults to
105
+ * `"bottom-right"`; use `"bottom-left"` when the bottom-right corner is
106
+ * occupied (e.g. a page with its own HUD there). The logo frame lives in the
107
+ * bottom-left area in both modes; in `"bottom-left"` the ⓘ takes the far-left
108
+ * corner and the logos shift right to sit beside it.
109
+ */
110
+ position?: AttributionPosition;
111
+ };
112
+
113
+ /**
114
+ * Number of live instances using the shared `<style>` element. The style is
115
+ * injected once and removed only when the last instance tears down, so
116
+ * multiple plugins don't duplicate the id or strip each other's styles.
117
+ */
118
+ let styleRefCount = 0;
119
+
120
+ /**
121
+ * Built-in Navara credit, always shown as the first attribution. It is kept
122
+ * separate from the user-managed set, so `add` / `remove` / `clear` never touch
123
+ * it, and it keeps the UI visible even before any data-source credit is added.
124
+ */
125
+ const NAVARA_CREDIT: AttributionItem = {
126
+ attribution: "Navara",
127
+ attributionUrl: "https://navara-docs.netlify.app/",
128
+ };
129
+
130
+ /**
131
+ * Renders map data attributions as a non-modal popover (ⓘ button, bottom-right
132
+ * by default). Top-level sources can carry nested, optionally zoom-banded child
133
+ * credits.
134
+ *
135
+ * A `ThreeView` creates one by default (`view.attribution`); construct it
136
+ * manually and `view.addPlugin(plugin)` **before** `view.init()` only when the
137
+ * view was created with `defaultAttribution: false`.
138
+ */
139
+ export class AttributionPlugin extends Plugin<View, ViewContext> {
140
+ private view?: View;
141
+ private items: AttributionItem[] = [];
142
+
143
+ private styleEl?: HTMLStyleElement;
144
+ private dock?: HTMLDivElement;
145
+ private card?: HTMLDivElement;
146
+ private listEl?: HTMLUListElement;
147
+ private logosEl?: HTMLDivElement;
148
+ private toggle?: HTMLButtonElement;
149
+ // Collapsed by default; users open it with the toggle.
150
+ private isOpen = false;
151
+
152
+ /** Last computed integer zoom level. Used to skip no-op re-renders. */
153
+ private lastZoomLevel?: number;
154
+ /** Whether any source has zoom-banded children — gates the per-frame poll. */
155
+ private hasZoomBands = false;
156
+
157
+ /** Per-layer dynamic credits, keyed by `layer.id`. */
158
+ private layerCredits = new Map<
159
+ string,
160
+ { credits: Map<bigint, string>; visible: Set<bigint> }
161
+ >();
162
+ private layerCleanups: (() => void)[] = [];
163
+
164
+ /** Color overrides, applied as CSS custom properties on the dock. */
165
+ private style: AttributionStyle;
166
+
167
+ /** Bottom corner for the ⓘ button / popover card. */
168
+ private position: AttributionPosition;
169
+
170
+ private boundKeydown: (event: KeyboardEvent) => void;
171
+ private boundPreRender: () => void;
172
+
173
+ constructor(options: AttributionPluginOptions = {}) {
174
+ super();
175
+ this.style = options.style ?? {};
176
+ this.position = options.position ?? "bottom-right";
177
+ this.boundKeydown = this.handleKeydown.bind(this);
178
+ this.boundPreRender = this.handlePreRender.bind(this);
179
+ }
180
+
181
+ /**
182
+ * The full list to render: the built-in Navara credit first, then the
183
+ * user-managed set. Always non-empty, so the dock stays visible.
184
+ */
185
+ private displayedItems(): AttributionItem[] {
186
+ return [NAVARA_CREDIT, ...this.items];
187
+ }
188
+
189
+ async init(view: View, _ctx: ViewContext): Promise<void> {
190
+ this.view = view;
191
+ // Recompute the zoom level on render. `fovy` is undefined until the first
192
+ // frame, so a camera `moveend` alone would miss the initial value on a
193
+ // static map; `preRender` reliably fires while the scene renders. The
194
+ // level-change gate in `handlePreRender` keeps this from churning the DOM.
195
+ view.on("preRender", this.boundPreRender);
196
+ // Build the UI now that the view exists. The built-in Navara credit is
197
+ // always present, so the dock shows even before any source is added.
198
+ this.apply();
199
+ }
200
+
201
+ /**
202
+ * Add attributions to the displayed set. Merged with the current entries;
203
+ * exact duplicates are dropped so several data sources that share one credit
204
+ * (e.g. multiple Overture themes) render a single line, not one each.
205
+ *
206
+ * Sources that declare a `creditLayerId` have that layer's per-feature credits
207
+ * tracked dynamically; the layer is resolved from the view by id, so callers
208
+ * don't pass the `Layer` object separately.
209
+ *
210
+ * @param items - Attribution entries (sources or raw HTML credits)
211
+ */
212
+ add(items: AttributionItem[]): void {
213
+ this.items = dedupeAttributionItems([...this.items, ...items]);
214
+ this.apply();
215
+ }
216
+
217
+ /**
218
+ * Remove attributions from the displayed set. Entries are matched
219
+ * structurally (same rendered content), so pass the same object shape that
220
+ * was added — no separate id is needed. Unmatched entries are ignored.
221
+ *
222
+ * @param items - Attribution entries to drop
223
+ */
224
+ remove(items: AttributionItem[]): void {
225
+ const keys = new Set(items.map(attributionItemKey));
226
+ this.items = this.items.filter(
227
+ (item) => !keys.has(attributionItemKey(item)),
228
+ );
229
+ this.apply();
230
+ }
231
+
232
+ /**
233
+ * Remove all user-added attributions, keeping the plugin usable. The built-in
234
+ * Navara credit and the dock stay visible; the logo frame hides if no logo
235
+ * remains. Use {@link dispose} to tear the DOM down.
236
+ */
237
+ clear(): void {
238
+ this.items = [];
239
+ this.apply();
240
+ }
241
+
242
+ /** Resolve/track the current items' credit layers and render. */
243
+ private apply(): void {
244
+ const layers = this.resolveCreditLayers(this.items);
245
+ // Retrack only when the credit-layer set changes (avoids wiping tracked credits).
246
+ const nextIds = new Set(layers.map((l) => l.id));
247
+ const sameSet =
248
+ nextIds.size === this.layerCredits.size &&
249
+ [...nextIds].every((id) => this.layerCredits.has(id));
250
+ if (!sameSet) this.trackLayers(layers);
251
+ this.render();
252
+ }
253
+
254
+ /**
255
+ * Resolve the `Layer` objects referenced by sources' `creditLayerId`, deduped
256
+ * by id so a layer referenced by multiple sources is tracked only once.
257
+ */
258
+ private resolveCreditLayers(items: AttributionItem[]): Layer[] {
259
+ const view = this.view;
260
+ if (!view) return [];
261
+ const seen = new Set<string>();
262
+ const layers: Layer[] = [];
263
+ for (const item of items) {
264
+ if (isAttributionHtml(item) || !item.creditLayerId) continue;
265
+ if (seen.has(item.creditLayerId)) continue;
266
+ seen.add(item.creditLayerId);
267
+ const layer = view.findLayerById(item.creditLayerId);
268
+ if (layer) layers.push(layer);
269
+ }
270
+ return layers;
271
+ }
272
+
273
+ /**
274
+ * Open the attribution popover. It is collapsed by default, so call this (or
275
+ * use the ⓘ button, which toggles the same state) to open it. Affects the
276
+ * popover card only — the always-visible logo frame stays put.
277
+ */
278
+ show(): void {
279
+ this.setOpen(true);
280
+ }
281
+
282
+ /**
283
+ * Close the attribution popover. Affects the popover card only; the tracked
284
+ * attributions and the always-visible logo frame are untouched (use
285
+ * {@link remove} to drop entries, {@link dispose} to tear everything down).
286
+ */
287
+ hide(): void {
288
+ this.setOpen(false);
289
+ }
290
+
291
+ /** Release all DOM nodes, camera listeners, and layer listeners. */
292
+ dispose(): void {
293
+ this.view?.off("preRender", this.boundPreRender);
294
+ this.teardownDom();
295
+ this.items = [];
296
+ this.view = undefined;
297
+ }
298
+
299
+ /**
300
+ * Update the UI colors. Merges over the current style and re-themes the live
301
+ * DOM in place (no rebuild), so it suits runtime switches like light ⇄ dark.
302
+ */
303
+ setStyle(style: AttributionStyle): void {
304
+ this.style = { ...this.style, ...style };
305
+ this.applyStyle();
306
+ }
307
+
308
+ /** Push the current style onto the dock as CSS custom properties. */
309
+ private applyStyle(): void {
310
+ const dock = this.dock;
311
+ if (!dock) return;
312
+ const set = (name: string, value: string | undefined): void => {
313
+ if (value !== undefined) dock.style.setProperty(name, value);
314
+ };
315
+ set("--nvr-attr-title", this.style.titleColor);
316
+ set("--nvr-attr-link", this.style.linkColor);
317
+ set("--nvr-attr-bullet", this.style.listStyleColor);
318
+ set("--nvr-attr-text", this.style.textColor);
319
+ set("--nvr-attr-nested", this.style.nestedTextColor);
320
+ set("--nvr-attr-bg", this.style.backgroundColor);
321
+ set("--nvr-attr-border", this.style.borderColor);
322
+ }
323
+
324
+ /**
325
+ * Build / refresh the popover DOM from {@link displayedItems}, filtering
326
+ * zoom-banded children by the current camera zoom.
327
+ */
328
+ private render(): void {
329
+ if (!this.view) return;
330
+ this.ensureDom();
331
+ const items = this.displayedItems();
332
+ this.hasZoomBands = items.some(
333
+ (item) =>
334
+ !isAttributionHtml(item) &&
335
+ (item.children?.some(
336
+ (c) => c.minZoom !== undefined || c.maxZoom !== undefined,
337
+ ) ??
338
+ false),
339
+ );
340
+ this.lastZoomLevel = this.currentZoomLevel();
341
+ this.populateList();
342
+ this.populateLogos();
343
+ // The dock always shows: the built-in Navara credit is always present. The
344
+ // logo frame only shows when at least one source declares a logo.
345
+ if (this.dock) this.dock.hidden = false;
346
+ const hasLogos = items.some(
347
+ (item) => !isAttributionHtml(item) && !!item.logo,
348
+ );
349
+ if (this.logosEl) this.logosEl.hidden = !hasLogos;
350
+ }
351
+
352
+ /** Create the dock DOM and inject (or reuse) the shared styles. */
353
+ private ensureDom(): void {
354
+ if (!this.styleEl) {
355
+ // Reuse a shared style element if another instance already injected it,
356
+ // and count this instance as an owner (removed on the last teardown).
357
+ const existing = document.getElementById(STYLE_ELEMENT_ID);
358
+ if (existing instanceof HTMLStyleElement) {
359
+ this.styleEl = existing;
360
+ } else {
361
+ const style = document.createElement("style");
362
+ style.id = STYLE_ELEMENT_ID;
363
+ style.textContent = STYLE_TEXT;
364
+ document.head.appendChild(style);
365
+ this.styleEl = style;
366
+ }
367
+ styleRefCount += 1;
368
+ }
369
+ if (this.dock) return;
370
+
371
+ const dock = document.createElement("div");
372
+ dock.className = "navara-attr-dock";
373
+ if (this.position === "bottom-left") {
374
+ dock.classList.add("navara-attr-dock--left");
375
+ }
376
+
377
+ const card = document.createElement("div");
378
+ card.className = "navara-attr-card";
379
+ // Reflect the current open intent (see setOpen).
380
+ card.hidden = !this.isOpen;
381
+
382
+ const head = document.createElement("div");
383
+ head.className = "navara-attr-head";
384
+ const title = document.createElement("h3");
385
+ title.textContent = "Attributions";
386
+ const close = document.createElement("button");
387
+ close.className = "navara-attr-close";
388
+ close.type = "button";
389
+ close.textContent = "✕";
390
+ close.setAttribute("aria-label", "Close");
391
+ close.addEventListener("click", () => this.setOpen(false));
392
+ head.appendChild(title);
393
+ head.appendChild(close);
394
+
395
+ const list = document.createElement("ul");
396
+ list.className = "navara-attr-list";
397
+
398
+ card.appendChild(head);
399
+ card.appendChild(list);
400
+
401
+ const toggle = document.createElement("button");
402
+ toggle.className = "navara-attr-toggle";
403
+ toggle.type = "button";
404
+ toggle.innerHTML = SVG_ICON_HTML;
405
+ toggle.setAttribute("aria-expanded", String(this.isOpen));
406
+ toggle.setAttribute(
407
+ "aria-label",
408
+ this.isOpen ? "Hide attributions" : "Show attributions",
409
+ );
410
+ toggle.addEventListener("click", () => this.setOpen());
411
+
412
+ dock.appendChild(card);
413
+ dock.appendChild(toggle);
414
+ document.body.appendChild(dock);
415
+
416
+ // Always-visible logo frame in the separate bottom-left corner, so
417
+ // contractually-mandated marks stay visible independent of the popover.
418
+ const logoFrame = document.createElement("div");
419
+ logoFrame.className = "navara-attr-logoframe";
420
+ // In bottom-left mode the ⓘ button sits at the far left, so shift the logo
421
+ // frame right of it to keep them in one row instead of overlapping.
422
+ if (this.position === "bottom-left") {
423
+ logoFrame.classList.add("navara-attr-logoframe--left");
424
+ }
425
+ document.body.appendChild(logoFrame);
426
+
427
+ document.addEventListener("keydown", this.boundKeydown);
428
+
429
+ this.dock = dock;
430
+ this.card = card;
431
+ this.listEl = list;
432
+ this.logosEl = logoFrame;
433
+ this.toggle = toggle;
434
+ this.applyStyle();
435
+ }
436
+
437
+ /** Create a bullet list item, returning the `<li>` and its text `<span>`. */
438
+ private createItemShell(): { li: HTMLLIElement; text: HTMLSpanElement } {
439
+ const li = document.createElement("li");
440
+ li.className = "navara-attr-item";
441
+ const name = document.createElement("div");
442
+ name.className = "navara-attr-name";
443
+ const bullet = document.createElement("span");
444
+ bullet.className = "navara-attr-bullet";
445
+ name.appendChild(bullet);
446
+ const text = document.createElement("span");
447
+ name.appendChild(text);
448
+ li.appendChild(name);
449
+ return { li, text };
450
+ }
451
+
452
+ /**
453
+ * Rebuild the list. Each source shows its sub-credits — zoom-filtered static
454
+ * `children` plus the dynamic credits of the layer it links via
455
+ * `creditLayerId` — as a nested list.
456
+ */
457
+ private populateList(): void {
458
+ if (!this.listEl) return;
459
+ this.listEl.replaceChildren();
460
+
461
+ for (const item of this.displayedItems()) {
462
+ const { li, text } = this.createItemShell();
463
+
464
+ if (isAttributionHtml(item)) {
465
+ appendSanitizedHtml(text, item.attributionHtml);
466
+ this.listEl.appendChild(li);
467
+ continue;
468
+ }
469
+
470
+ const href = item.attributionUrl
471
+ ? safeHref(item.attributionUrl)
472
+ : undefined;
473
+ if (href) {
474
+ text.appendChild(createSafeAnchor(href, item.attribution));
475
+ } else {
476
+ text.textContent = item.attribution;
477
+ }
478
+
479
+ // Sub-credits: zoom-banded static children, then the linked layer's
480
+ // dynamic credits. Both go through the sanitizer so embedded `<a>` links
481
+ // stay clickable while scripts/handlers from untrusted tile metadata are
482
+ // dropped to text.
483
+ const sub = document.createElement("ul");
484
+ sub.className = "navara-attr-related";
485
+ if (item.children) {
486
+ for (const child of item.children) {
487
+ if (!matchesZoom(child, this.lastZoomLevel)) continue;
488
+ const childLi = document.createElement("li");
489
+ appendSanitizedHtml(childLi, child.attribution);
490
+ sub.appendChild(childLi);
491
+ }
492
+ }
493
+ if (item.creditLayerId) {
494
+ for (const credit of this.layerCreditStrings(item.creditLayerId)) {
495
+ const creditLi = document.createElement("li");
496
+ appendSanitizedHtml(creditLi, credit);
497
+ sub.appendChild(creditLi);
498
+ }
499
+ }
500
+
501
+ if (sub.childElementCount > 0) li.appendChild(sub);
502
+
503
+ this.listEl.appendChild(li);
504
+ }
505
+ }
506
+
507
+ /** Aggregated, deduped credit strings for a tracked layer's visible features. */
508
+ private layerCreditStrings(layerId: string): string[] {
509
+ const state = this.layerCredits.get(layerId);
510
+ if (!state) return [];
511
+ const strings: string[] = [];
512
+ for (const id of state.visible) {
513
+ const credit = state.credits.get(id);
514
+ if (credit) strings.push(credit);
515
+ }
516
+ return aggregateCredits(strings);
517
+ }
518
+
519
+ /**
520
+ * Rebuild the list if the popover is open; while closed, do nothing —
521
+ * `setOpen()` repopulates on open, so deferred updates are picked up then.
522
+ */
523
+ private requestRender(): void {
524
+ if (this.dock && this.isOpen) this.populateList();
525
+ }
526
+
527
+ /** Rebuild the always-visible logo frame from sources that declare a `logo`. */
528
+ private populateLogos(): void {
529
+ if (!this.logosEl) return;
530
+ this.logosEl.replaceChildren();
531
+
532
+ for (const item of this.displayedItems()) {
533
+ if (isAttributionHtml(item) || !item.logo) continue;
534
+ const img = document.createElement("img");
535
+ img.src = item.logo;
536
+ img.alt = item.attribution;
537
+ img.className = "navara-attr-logo";
538
+ img.draggable = false;
539
+
540
+ // Link the logo only when the source declares a (safe) `logoUrl`, opened in
541
+ // a new tab. A mark that must be shown but not turned into a link omits
542
+ // `logoUrl` and stays a plain `<img>`.
543
+ const href = item.logoUrl ? safeHref(item.logoUrl) : undefined;
544
+ let node: HTMLElement = img;
545
+ if (href) {
546
+ node = createSafeAnchor(href);
547
+ node.appendChild(img);
548
+ }
549
+ this.logosEl.appendChild(node);
550
+ }
551
+ }
552
+
553
+ /** Open / close the popover. Non-modal: no backdrop is added. */
554
+ private setOpen(open?: boolean): void {
555
+ // Record intent before the DOM guard; ensureDom() applies it when the card is built.
556
+ this.isOpen = open ?? !this.isOpen;
557
+ if (!this.card || !this.toggle) return;
558
+ this.card.hidden = !this.isOpen;
559
+ this.toggle.setAttribute("aria-expanded", String(this.isOpen));
560
+ // Keep the accessible name in sync with what activating the button does.
561
+ this.toggle.setAttribute(
562
+ "aria-label",
563
+ this.isOpen ? "Hide attributions" : "Show attributions",
564
+ );
565
+ // On open, refresh once: the per-frame zoom poll is skipped while closed,
566
+ // so the zoom band may be stale.
567
+ if (this.isOpen) {
568
+ if (this.hasZoomBands) this.lastZoomLevel = this.currentZoomLevel();
569
+ this.populateList();
570
+ }
571
+ }
572
+
573
+ private handleKeydown(event: KeyboardEvent): void {
574
+ if (event.key === "Escape" && this.isOpen) this.setOpen(false);
575
+ }
576
+
577
+ /**
578
+ * Recompute the zoom and, only when the integer level changed, re-render the
579
+ * list (a "quiet" update — no flicker, no animation). Called on every
580
+ * `preRender`; the level-change gate makes it a no-op unless the band changes.
581
+ */
582
+ private handlePreRender(): void {
583
+ // Poll only while the popover is open and a source has zoom bands. The
584
+ // `camera.zoom` poll crosses the WASM boundary, so skipping it while closed
585
+ // (or when nothing is zoom-banded) avoids per-frame overhead for users who
586
+ // never open the UI; `setOpen()` refreshes the level on open.
587
+ if (!this.dock || !this.hasZoomBands || !this.isOpen) return;
588
+ const level = this.currentZoomLevel();
589
+ if (level === this.lastZoomLevel) return;
590
+ this.lastZoomLevel = level;
591
+ this.requestRender();
592
+ }
593
+
594
+ /**
595
+ * Current integer Web Mercator zoom level. The engine computes the fractional
596
+ * zoom (altitude/FOV/viewport) in Rust via `camera.zoom`; floor it to a tile
597
+ * level for band matching. `undefined` (e.g. before the first frame) shows all
598
+ * bands.
599
+ */
600
+ private currentZoomLevel(): number | undefined {
601
+ const z = this.view?.camera.zoom;
602
+ return z === undefined ? undefined : Math.floor(z);
603
+ }
604
+
605
+ /**
606
+ * Remove this instance's DOM nodes, document + layer listeners, and state.
607
+ * The shared style element is removed only when the last instance tears down.
608
+ */
609
+ private teardownDom(): void {
610
+ for (const off of this.layerCleanups) off();
611
+ this.layerCleanups = [];
612
+
613
+ if (this.dock) {
614
+ document.removeEventListener("keydown", this.boundKeydown);
615
+ this.dock.remove();
616
+ }
617
+ this.logosEl?.remove();
618
+ if (this.styleEl) {
619
+ // Only remove the shared style when the last owning instance tears down.
620
+ styleRefCount = Math.max(0, styleRefCount - 1);
621
+ if (styleRefCount === 0) this.styleEl.remove();
622
+ }
623
+ this.dock = undefined;
624
+ this.card = undefined;
625
+ this.listEl = undefined;
626
+ this.logosEl = undefined;
627
+ this.toggle = undefined;
628
+ this.styleEl = undefined;
629
+ this.isOpen = false;
630
+
631
+ this.layerCredits.clear();
632
+ }
633
+
634
+ /**
635
+ * Subscribe to the given layers' feature events and merge per-feature credits
636
+ * into the rendered list as features appear / disappear. Detaches any
637
+ * previously registered listeners first, so repeated `add()` / `remove()`
638
+ * don't leak.
639
+ */
640
+ private trackLayers(layers: Layer[]): void {
641
+ for (const off of this.layerCleanups) off();
642
+ this.layerCleanups = [];
643
+ this.layerCredits.clear();
644
+
645
+ for (const layer of layers) {
646
+ const state = {
647
+ credits: new Map<bigint, string>(),
648
+ visible: new Set<bigint>(),
649
+ };
650
+ this.layerCredits.set(layer.id, state);
651
+
652
+ // Track only features that actually carry a credit: layers can emit many
653
+ // credit-less features (e.g. PLATEAU), and tracking them would grow these
654
+ // sets unbounded and churn the UI with no visible effect.
655
+ const onCreated = ({ featureSetId, credit }: FeatureCreatedParams) => {
656
+ if (!credit) return;
657
+ state.credits.set(featureSetId, credit);
658
+ state.visible.add(featureSetId);
659
+ this.requestRender();
660
+ };
661
+ const onRemoved = ({ featureSetId }: FeatureRemovedParams) => {
662
+ if (!state.credits.has(featureSetId)) return;
663
+ state.credits.delete(featureSetId);
664
+ state.visible.delete(featureSetId);
665
+ this.requestRender();
666
+ };
667
+ const onVisibility = ({
668
+ featureSetId,
669
+ visible,
670
+ }: FeatureVisibilityChangedParams) => {
671
+ if (!state.credits.has(featureSetId)) return;
672
+ if (visible) state.visible.add(featureSetId);
673
+ else state.visible.delete(featureSetId);
674
+ this.requestRender();
675
+ };
676
+ // Assigned below; declared first so `onDeleted` can reference it.
677
+ let detach = () => {};
678
+ // `deleted` fires once for the layer (no per-feature `featureRemoved`).
679
+ // Fully release the layer — drop its credits entry and detach its
680
+ // listeners (and this cleanup) — so a deleted layer doesn't linger until
681
+ // the next add()/remove()/clear() or dispose().
682
+ const onDeleted = () => {
683
+ detach();
684
+ this.layerCleanups = this.layerCleanups.filter((c) => c !== detach);
685
+ this.layerCredits.delete(layer.id);
686
+ this.requestRender();
687
+ };
688
+ detach = () => {
689
+ layer.off("featureCreated", onCreated);
690
+ layer.off("featureRemoved", onRemoved);
691
+ layer.off("featureVisibilityChanged", onVisibility);
692
+ layer.off("deleted", onDeleted);
693
+ };
694
+
695
+ layer.on("featureCreated", onCreated);
696
+ layer.on("featureRemoved", onRemoved);
697
+ layer.on("featureVisibilityChanged", onVisibility);
698
+ layer.on("deleted", onDeleted);
699
+ this.layerCleanups.push(detach);
700
+ }
701
+ }
702
+ }
@@ -0,0 +1 @@
1
+ export * from "./AttributionPlugin";
package/src/quality.ts ADDED
@@ -0,0 +1 @@
1
+ export type Quality = "low" | "medium" | "high" | "ultra";