@walkthru-earth/objex 0.1.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 (367) hide show
  1. package/LICENSE +396 -0
  2. package/README.md +114 -0
  3. package/dist/assets/favicon.svg +17 -0
  4. package/dist/components/CLAUDE.md +44 -0
  5. package/dist/components/browser/Breadcrumb.svelte +50 -0
  6. package/dist/components/browser/Breadcrumb.svelte.d.ts +7 -0
  7. package/dist/components/browser/CreateFolderDialog.svelte +98 -0
  8. package/dist/components/browser/CreateFolderDialog.svelte.d.ts +6 -0
  9. package/dist/components/browser/DeleteConfirmDialog.svelte +90 -0
  10. package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +8 -0
  11. package/dist/components/browser/DropZone.svelte +83 -0
  12. package/dist/components/browser/DropZone.svelte.d.ts +7 -0
  13. package/dist/components/browser/FileBrowser.svelte +229 -0
  14. package/dist/components/browser/FileBrowser.svelte.d.ts +3 -0
  15. package/dist/components/browser/FileRow.svelte +112 -0
  16. package/dist/components/browser/FileRow.svelte.d.ts +9 -0
  17. package/dist/components/browser/FileTreeSidebar.svelte +559 -0
  18. package/dist/components/browser/FileTreeSidebar.svelte.d.ts +8 -0
  19. package/dist/components/browser/RenameDialog.svelte +101 -0
  20. package/dist/components/browser/RenameDialog.svelte.d.ts +8 -0
  21. package/dist/components/browser/SearchBar.svelte +40 -0
  22. package/dist/components/browser/SearchBar.svelte.d.ts +6 -0
  23. package/dist/components/browser/UploadButton.svelte +65 -0
  24. package/dist/components/browser/UploadButton.svelte.d.ts +3 -0
  25. package/dist/components/editor/CodeMirrorEditor.svelte +404 -0
  26. package/dist/components/editor/CodeMirrorEditor.svelte.d.ts +12 -0
  27. package/dist/components/editor/MilkdownEditor.svelte +98 -0
  28. package/dist/components/editor/MilkdownEditor.svelte.d.ts +9 -0
  29. package/dist/components/editor/SqlEditor.svelte +173 -0
  30. package/dist/components/editor/SqlEditor.svelte.d.ts +7 -0
  31. package/dist/components/editor/SqlResultBlock.svelte +199 -0
  32. package/dist/components/editor/SqlResultBlock.svelte.d.ts +9 -0
  33. package/dist/components/layout/ConnectionDialog.svelte +439 -0
  34. package/dist/components/layout/ConnectionDialog.svelte.d.ts +9 -0
  35. package/dist/components/layout/LocaleToggle.svelte +32 -0
  36. package/dist/components/layout/LocaleToggle.svelte.d.ts +3 -0
  37. package/dist/components/layout/SafeLockToggle.svelte +37 -0
  38. package/dist/components/layout/SafeLockToggle.svelte.d.ts +18 -0
  39. package/dist/components/layout/Sidebar.svelte +314 -0
  40. package/dist/components/layout/Sidebar.svelte.d.ts +3 -0
  41. package/dist/components/layout/StatusBar.svelte +73 -0
  42. package/dist/components/layout/StatusBar.svelte.d.ts +3 -0
  43. package/dist/components/layout/TabBar.svelte +102 -0
  44. package/dist/components/layout/TabBar.svelte.d.ts +7 -0
  45. package/dist/components/layout/ThemeToggle.svelte +52 -0
  46. package/dist/components/layout/ThemeToggle.svelte.d.ts +3 -0
  47. package/dist/components/ui/badge/badge.svelte +49 -0
  48. package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
  49. package/dist/components/ui/badge/index.d.ts +1 -0
  50. package/dist/components/ui/badge/index.js +1 -0
  51. package/dist/components/ui/button/button.svelte +82 -0
  52. package/dist/components/ui/button/button.svelte.d.ts +64 -0
  53. package/dist/components/ui/button/index.d.ts +2 -0
  54. package/dist/components/ui/button/index.js +4 -0
  55. package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +40 -0
  56. package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte.d.ts +9 -0
  57. package/dist/components/ui/context-menu/context-menu-content.svelte +28 -0
  58. package/dist/components/ui/context-menu/context-menu-content.svelte.d.ts +10 -0
  59. package/dist/components/ui/context-menu/context-menu-group-heading.svelte +21 -0
  60. package/dist/components/ui/context-menu/context-menu-group-heading.svelte.d.ts +7 -0
  61. package/dist/components/ui/context-menu/context-menu-group.svelte +7 -0
  62. package/dist/components/ui/context-menu/context-menu-group.svelte.d.ts +4 -0
  63. package/dist/components/ui/context-menu/context-menu-item.svelte +27 -0
  64. package/dist/components/ui/context-menu/context-menu-item.svelte.d.ts +8 -0
  65. package/dist/components/ui/context-menu/context-menu-label.svelte +24 -0
  66. package/dist/components/ui/context-menu/context-menu-label.svelte.d.ts +8 -0
  67. package/dist/components/ui/context-menu/context-menu-portal.svelte +7 -0
  68. package/dist/components/ui/context-menu/context-menu-portal.svelte.d.ts +3 -0
  69. package/dist/components/ui/context-menu/context-menu-radio-group.svelte +16 -0
  70. package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +4 -0
  71. package/dist/components/ui/context-menu/context-menu-radio-item.svelte +33 -0
  72. package/dist/components/ui/context-menu/context-menu-radio-item.svelte.d.ts +4 -0
  73. package/dist/components/ui/context-menu/context-menu-separator.svelte +17 -0
  74. package/dist/components/ui/context-menu/context-menu-separator.svelte.d.ts +4 -0
  75. package/dist/components/ui/context-menu/context-menu-shortcut.svelte +20 -0
  76. package/dist/components/ui/context-menu/context-menu-shortcut.svelte.d.ts +5 -0
  77. package/dist/components/ui/context-menu/context-menu-sub-content.svelte +20 -0
  78. package/dist/components/ui/context-menu/context-menu-sub-content.svelte.d.ts +4 -0
  79. package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +29 -0
  80. package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +8 -0
  81. package/dist/components/ui/context-menu/context-menu-sub.svelte +7 -0
  82. package/dist/components/ui/context-menu/context-menu-sub.svelte.d.ts +3 -0
  83. package/dist/components/ui/context-menu/context-menu-trigger.svelte +7 -0
  84. package/dist/components/ui/context-menu/context-menu-trigger.svelte.d.ts +4 -0
  85. package/dist/components/ui/context-menu/context-menu.svelte +7 -0
  86. package/dist/components/ui/context-menu/context-menu.svelte.d.ts +3 -0
  87. package/dist/components/ui/context-menu/index.d.ts +17 -0
  88. package/dist/components/ui/context-menu/index.js +19 -0
  89. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  90. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
  91. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
  92. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
  93. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +29 -0
  94. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
  95. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  96. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
  97. package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  98. package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
  99. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  100. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
  101. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  102. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
  103. package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  104. package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
  105. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  106. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
  107. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +33 -0
  108. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
  109. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  110. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
  111. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  112. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
  113. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
  114. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +4 -0
  115. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  116. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
  117. package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  118. package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
  119. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  120. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
  121. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  122. package/dist/components/ui/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
  123. package/dist/components/ui/dropdown-menu/index.d.ts +18 -0
  124. package/dist/components/ui/dropdown-menu/index.js +18 -0
  125. package/dist/components/ui/input/index.d.ts +2 -0
  126. package/dist/components/ui/input/index.js +4 -0
  127. package/dist/components/ui/input/input.svelte +52 -0
  128. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  129. package/dist/components/ui/resizable/index.d.ts +4 -0
  130. package/dist/components/ui/resizable/index.js +6 -0
  131. package/dist/components/ui/resizable/resizable-handle.svelte +30 -0
  132. package/dist/components/ui/resizable/resizable-handle.svelte.d.ts +8 -0
  133. package/dist/components/ui/resizable/resizable-pane-group.svelte +20 -0
  134. package/dist/components/ui/resizable/resizable-pane-group.svelte.d.ts +7 -0
  135. package/dist/components/ui/scroll-area/index.d.ts +3 -0
  136. package/dist/components/ui/scroll-area/index.js +5 -0
  137. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +31 -0
  138. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte.d.ts +4 -0
  139. package/dist/components/ui/scroll-area/scroll-area.svelte +47 -0
  140. package/dist/components/ui/scroll-area/scroll-area.svelte.d.ts +11 -0
  141. package/dist/components/ui/separator/index.d.ts +2 -0
  142. package/dist/components/ui/separator/index.js +4 -0
  143. package/dist/components/ui/separator/separator.svelte +21 -0
  144. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  145. package/dist/components/ui/sheet/index.d.ts +11 -0
  146. package/dist/components/ui/sheet/index.js +13 -0
  147. package/dist/components/ui/sheet/sheet-close.svelte +7 -0
  148. package/dist/components/ui/sheet/sheet-close.svelte.d.ts +4 -0
  149. package/dist/components/ui/sheet/sheet-content.svelte +62 -0
  150. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +37 -0
  151. package/dist/components/ui/sheet/sheet-description.svelte +17 -0
  152. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
  153. package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
  154. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  155. package/dist/components/ui/sheet/sheet-header.svelte +20 -0
  156. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
  157. package/dist/components/ui/sheet/sheet-overlay.svelte +20 -0
  158. package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
  159. package/dist/components/ui/sheet/sheet-portal.svelte +7 -0
  160. package/dist/components/ui/sheet/sheet-portal.svelte.d.ts +3 -0
  161. package/dist/components/ui/sheet/sheet-title.svelte +13 -0
  162. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
  163. package/dist/components/ui/sheet/sheet-trigger.svelte +7 -0
  164. package/dist/components/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
  165. package/dist/components/ui/sheet/sheet.svelte +7 -0
  166. package/dist/components/ui/sheet/sheet.svelte.d.ts +3 -0
  167. package/dist/components/ui/switch/index.d.ts +2 -0
  168. package/dist/components/ui/switch/index.js +4 -0
  169. package/dist/components/ui/switch/switch.svelte +28 -0
  170. package/dist/components/ui/switch/switch.svelte.d.ts +8 -0
  171. package/dist/components/ui/tabs/index.d.ts +5 -0
  172. package/dist/components/ui/tabs/index.js +7 -0
  173. package/dist/components/ui/tabs/tabs-content.svelte +17 -0
  174. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  175. package/dist/components/ui/tabs/tabs-list.svelte +16 -0
  176. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  177. package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
  178. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  179. package/dist/components/ui/tabs/tabs.svelte +19 -0
  180. package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
  181. package/dist/components/ui/tooltip/index.d.ts +6 -0
  182. package/dist/components/ui/tooltip/index.js +8 -0
  183. package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
  184. package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
  185. package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
  186. package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
  187. package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
  188. package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
  189. package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  190. package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  191. package/dist/components/ui/tooltip/tooltip.svelte +7 -0
  192. package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
  193. package/dist/components/viewers/ArchiveViewer.svelte +586 -0
  194. package/dist/components/viewers/ArchiveViewer.svelte.d.ts +7 -0
  195. package/dist/components/viewers/CLAUDE.md +60 -0
  196. package/dist/components/viewers/CodeViewer.svelte +553 -0
  197. package/dist/components/viewers/CodeViewer.svelte.d.ts +7 -0
  198. package/dist/components/viewers/CogViewer.svelte +1345 -0
  199. package/dist/components/viewers/CogViewer.svelte.d.ts +7 -0
  200. package/dist/components/viewers/CopcViewer.svelte +25 -0
  201. package/dist/components/viewers/CopcViewer.svelte.d.ts +7 -0
  202. package/dist/components/viewers/DatabaseViewer.svelte +169 -0
  203. package/dist/components/viewers/DatabaseViewer.svelte.d.ts +7 -0
  204. package/dist/components/viewers/FileInfo.svelte +174 -0
  205. package/dist/components/viewers/FileInfo.svelte.d.ts +10 -0
  206. package/dist/components/viewers/FlatGeobufViewer.svelte +755 -0
  207. package/dist/components/viewers/FlatGeobufViewer.svelte.d.ts +7 -0
  208. package/dist/components/viewers/GeoParquetMapViewer.svelte +278 -0
  209. package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +17 -0
  210. package/dist/components/viewers/ImageViewer.svelte +233 -0
  211. package/dist/components/viewers/ImageViewer.svelte.d.ts +7 -0
  212. package/dist/components/viewers/LoadProgress.svelte +93 -0
  213. package/dist/components/viewers/LoadProgress.svelte.d.ts +15 -0
  214. package/dist/components/viewers/MapViewer.svelte +234 -0
  215. package/dist/components/viewers/MapViewer.svelte.d.ts +7 -0
  216. package/dist/components/viewers/MarkdownViewer.svelte +478 -0
  217. package/dist/components/viewers/MarkdownViewer.svelte.d.ts +7 -0
  218. package/dist/components/viewers/MediaViewer.svelte +121 -0
  219. package/dist/components/viewers/MediaViewer.svelte.d.ts +7 -0
  220. package/dist/components/viewers/ModelViewer.svelte +164 -0
  221. package/dist/components/viewers/ModelViewer.svelte.d.ts +7 -0
  222. package/dist/components/viewers/NotebookViewer.svelte +389 -0
  223. package/dist/components/viewers/NotebookViewer.svelte.d.ts +7 -0
  224. package/dist/components/viewers/PdfViewer.svelte +278 -0
  225. package/dist/components/viewers/PdfViewer.svelte.d.ts +7 -0
  226. package/dist/components/viewers/PmtilesViewer.svelte +191 -0
  227. package/dist/components/viewers/PmtilesViewer.svelte.d.ts +7 -0
  228. package/dist/components/viewers/QueryHistoryPanel.svelte +159 -0
  229. package/dist/components/viewers/QueryHistoryPanel.svelte.d.ts +8 -0
  230. package/dist/components/viewers/RawViewer.svelte +117 -0
  231. package/dist/components/viewers/RawViewer.svelte.d.ts +7 -0
  232. package/dist/components/viewers/StacMapViewer.svelte +20 -0
  233. package/dist/components/viewers/StacMapViewer.svelte.d.ts +7 -0
  234. package/dist/components/viewers/StyleEditorOverlay.svelte +27 -0
  235. package/dist/components/viewers/StyleEditorOverlay.svelte.d.ts +7 -0
  236. package/dist/components/viewers/TableGrid.svelte +355 -0
  237. package/dist/components/viewers/TableGrid.svelte.d.ts +12 -0
  238. package/dist/components/viewers/TableStatusBar.svelte +92 -0
  239. package/dist/components/viewers/TableStatusBar.svelte.d.ts +11 -0
  240. package/dist/components/viewers/TableToolbar.svelte +382 -0
  241. package/dist/components/viewers/TableToolbar.svelte.d.ts +25 -0
  242. package/dist/components/viewers/TableViewer.svelte +923 -0
  243. package/dist/components/viewers/TableViewer.svelte.d.ts +7 -0
  244. package/dist/components/viewers/ViewerRouter.svelte +70 -0
  245. package/dist/components/viewers/ViewerRouter.svelte.d.ts +7 -0
  246. package/dist/components/viewers/ZarrMapViewer.svelte +288 -0
  247. package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +17 -0
  248. package/dist/components/viewers/ZarrViewer.svelte +256 -0
  249. package/dist/components/viewers/ZarrViewer.svelte.d.ts +7 -0
  250. package/dist/components/viewers/map/AttributeTable.svelte +52 -0
  251. package/dist/components/viewers/map/AttributeTable.svelte.d.ts +8 -0
  252. package/dist/components/viewers/map/MapContainer.svelte +158 -0
  253. package/dist/components/viewers/map/MapContainer.svelte.d.ts +12 -0
  254. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +389 -0
  255. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte.d.ts +10 -0
  256. package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +332 -0
  257. package/dist/components/viewers/pmtiles/PmtilesMapView.svelte.d.ts +11 -0
  258. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +373 -0
  259. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte.d.ts +12 -0
  260. package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte +112 -0
  261. package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte.d.ts +10 -0
  262. package/dist/file-icons/CLAUDE.md +21 -0
  263. package/dist/file-icons/FileTypeIcon.svelte +74 -0
  264. package/dist/file-icons/FileTypeIcon.svelte.d.ts +9 -0
  265. package/dist/file-icons/index.d.ts +56 -0
  266. package/dist/file-icons/index.js +1070 -0
  267. package/dist/i18n/CLAUDE.md +19 -0
  268. package/dist/i18n/ar.d.ts +1 -0
  269. package/dist/i18n/ar.js +404 -0
  270. package/dist/i18n/en.d.ts +1 -0
  271. package/dist/i18n/en.js +404 -0
  272. package/dist/i18n/index.svelte.d.ts +9 -0
  273. package/dist/i18n/index.svelte.js +27 -0
  274. package/dist/index.d.ts +20 -0
  275. package/dist/index.js +13 -0
  276. package/dist/query/CLAUDE.md +22 -0
  277. package/dist/query/engine.d.ts +56 -0
  278. package/dist/query/engine.js +6 -0
  279. package/dist/query/index.d.ts +4 -0
  280. package/dist/query/index.js +19 -0
  281. package/dist/query/wasm.d.ts +20 -0
  282. package/dist/query/wasm.js +890 -0
  283. package/dist/storage/CLAUDE.md +23 -0
  284. package/dist/storage/adapter.d.ts +21 -0
  285. package/dist/storage/adapter.js +1 -0
  286. package/dist/storage/browser-azure.d.ts +25 -0
  287. package/dist/storage/browser-azure.js +271 -0
  288. package/dist/storage/browser-cloud.d.ts +32 -0
  289. package/dist/storage/browser-cloud.js +293 -0
  290. package/dist/storage/index.d.ts +11 -0
  291. package/dist/storage/index.js +37 -0
  292. package/dist/storage/url-adapter.d.ts +19 -0
  293. package/dist/storage/url-adapter.js +51 -0
  294. package/dist/stores/CLAUDE.md +29 -0
  295. package/dist/stores/browser.svelte.d.ts +28 -0
  296. package/dist/stores/browser.svelte.js +160 -0
  297. package/dist/stores/connections.svelte.d.ts +56 -0
  298. package/dist/stores/connections.svelte.js +272 -0
  299. package/dist/stores/credentials.svelte.d.ts +56 -0
  300. package/dist/stores/credentials.svelte.js +79 -0
  301. package/dist/stores/files.svelte.d.ts +20 -0
  302. package/dist/stores/files.svelte.js +76 -0
  303. package/dist/stores/query-history.svelte.d.ts +16 -0
  304. package/dist/stores/query-history.svelte.js +57 -0
  305. package/dist/stores/safelock.svelte.d.ts +8 -0
  306. package/dist/stores/safelock.svelte.js +52 -0
  307. package/dist/stores/settings.svelte.d.ts +11 -0
  308. package/dist/stores/settings.svelte.js +101 -0
  309. package/dist/stores/tab-resources.svelte.d.ts +25 -0
  310. package/dist/stores/tab-resources.svelte.js +61 -0
  311. package/dist/stores/tabs.svelte.d.ts +17 -0
  312. package/dist/stores/tabs.svelte.js +110 -0
  313. package/dist/types/notebookjs.d.ts +14 -0
  314. package/dist/types.d.ts +47 -0
  315. package/dist/types.js +1 -0
  316. package/dist/utils/CLAUDE.md +54 -0
  317. package/dist/utils/analytics.d.ts +10 -0
  318. package/dist/utils/analytics.js +38 -0
  319. package/dist/utils/archive.d.ts +70 -0
  320. package/dist/utils/archive.js +333 -0
  321. package/dist/utils/column-types.d.ts +5 -0
  322. package/dist/utils/column-types.js +137 -0
  323. package/dist/utils/deck.d.ts +98 -0
  324. package/dist/utils/deck.js +208 -0
  325. package/dist/utils/evidence-context.d.ts +22 -0
  326. package/dist/utils/evidence-context.js +56 -0
  327. package/dist/utils/export.d.ts +2 -0
  328. package/dist/utils/export.js +51 -0
  329. package/dist/utils/format.d.ts +14 -0
  330. package/dist/utils/format.js +56 -0
  331. package/dist/utils/geoarrow.d.ts +32 -0
  332. package/dist/utils/geoarrow.js +672 -0
  333. package/dist/utils/hex.d.ts +10 -0
  334. package/dist/utils/hex.js +27 -0
  335. package/dist/utils/host-detection.d.ts +23 -0
  336. package/dist/utils/host-detection.js +289 -0
  337. package/dist/utils/map-selection.d.ts +12 -0
  338. package/dist/utils/map-selection.js +45 -0
  339. package/dist/utils/markdown-sql.d.ts +30 -0
  340. package/dist/utils/markdown-sql.js +73 -0
  341. package/dist/utils/markdown.d.ts +18 -0
  342. package/dist/utils/markdown.js +146 -0
  343. package/dist/utils/model3d.d.ts +13 -0
  344. package/dist/utils/model3d.js +62 -0
  345. package/dist/utils/parquet-metadata.d.ts +58 -0
  346. package/dist/utils/parquet-metadata.js +228 -0
  347. package/dist/utils/pdf.d.ts +8 -0
  348. package/dist/utils/pdf.js +28 -0
  349. package/dist/utils/pmtiles-tile.d.ts +38 -0
  350. package/dist/utils/pmtiles-tile.js +64 -0
  351. package/dist/utils/pmtiles.d.ts +46 -0
  352. package/dist/utils/pmtiles.js +135 -0
  353. package/dist/utils/shiki.d.ts +8 -0
  354. package/dist/utils/shiki.js +98 -0
  355. package/dist/utils/storage-url.d.ts +64 -0
  356. package/dist/utils/storage-url.js +374 -0
  357. package/dist/utils/url-state.d.ts +40 -0
  358. package/dist/utils/url-state.js +113 -0
  359. package/dist/utils/url.d.ts +27 -0
  360. package/dist/utils/url.js +115 -0
  361. package/dist/utils/wkb.d.ts +43 -0
  362. package/dist/utils/wkb.js +345 -0
  363. package/dist/utils/zarr.d.ts +39 -0
  364. package/dist/utils/zarr.js +204 -0
  365. package/dist/utils.d.ts +12 -0
  366. package/dist/utils.js +5 -0
  367. package/package.json +203 -0
@@ -0,0 +1,755 @@
1
+ <script lang="ts">
2
+ import LocateIcon from '@lucide/svelte/icons/locate';
3
+ import { geojson as fgbGeojson } from 'flatgeobuf';
4
+ import { magicbytes } from 'flatgeobuf/lib/mjs/constants.js';
5
+ import { buildHeader as fgbBuildHeader } from 'flatgeobuf/lib/mjs/generic/featurecollection.js';
6
+ import type { HeaderMeta } from 'flatgeobuf/lib/mjs/header-meta.js';
7
+ import { HttpReader } from 'flatgeobuf/lib/mjs/http-reader.js';
8
+ import type maplibregl from 'maplibre-gl';
9
+ import proj4 from 'proj4';
10
+ import { onDestroy, untrack } from 'svelte';
11
+ import { t } from '../../i18n/index.svelte.js';
12
+ import { settings } from '../../stores/settings.svelte.js';
13
+ import { tabResources } from '../../stores/tab-resources.svelte.js';
14
+ import type { Tab } from '../../types';
15
+ import {
16
+ buildSelectionLayer,
17
+ geojsonFillColor,
18
+ geojsonLineColor,
19
+ hoverCursor,
20
+ loadDeckModules
21
+ } from '../../utils/deck.js';
22
+ import { buildHttpsUrl } from '../../utils/url.js';
23
+ import AttributeTable from './map/AttributeTable.svelte';
24
+ import MapContainer from './map/MapContainer.svelte';
25
+
26
+ let { tab }: { tab: Tab } = $props();
27
+
28
+ const BATCH_SIZE = 1000;
29
+
30
+ let loading = $state(true);
31
+ let streaming = $state(false);
32
+ let error = $state<string | null>(null);
33
+ let featureCount = $state(0);
34
+ let totalFeatures = $state<number | null>(null);
35
+ let selectedFeature = $state<Record<string, any> | null>(null);
36
+ let selectedGeoFeature: GeoJSON.Feature | null = null;
37
+ let showAttributes = $state(false);
38
+ let showInfo = $state(false);
39
+ let bounds = $state<[number, number, number, number] | undefined>();
40
+ let hasMore = $state(false);
41
+
42
+ let firstFeatureCoord = $state<[number, number] | null>(null);
43
+
44
+ let deckModules: { MapboxOverlay: any; GeoJsonLayer: any } | null = null;
45
+ let overlay: any = null;
46
+ let mapRef: maplibregl.Map | null = null;
47
+ let features: GeoJSON.Feature[] = [];
48
+ /** Monotonic counter — bump to signal deck.gl that data changed (avoids spreading the features array). */
49
+ let dataVersion = 0;
50
+ let abortController: AbortController | null = null;
51
+ let activeStreamCancel: (() => void) | null = null;
52
+ let resolveMapReady: (() => void) | null = null;
53
+ let mapReadyPromise: Promise<void> | null = null;
54
+
55
+ // Stored from preview for load-all (skip index)
56
+ let storedHeader: HeaderMeta | null = null;
57
+ let storedFeatureOffset = 0;
58
+
59
+ // proj4 converter for reprojecting from source CRS → WGS84
60
+ let proj4Forward: ((coord: [number, number]) => [number, number]) | null = null;
61
+
62
+ // Header metadata from FlatGeobuf
63
+ let headerInfo = $state<{
64
+ geometryType: string;
65
+ featuresCount: number;
66
+ columns: { name: string; type: number }[];
67
+ crs: { org: string | null; code: number; name: string | null } | null;
68
+ title: string | null;
69
+ description: string | null;
70
+ hasIndex: boolean;
71
+ } | null>(null);
72
+
73
+ const WGS84_CODES = new Set([4326, 4979, 4267, 4269]);
74
+ const CRS84_NAMES = ['CRS84', 'CRS 84', 'OGC:CRS84'];
75
+
76
+ /** Returns true if the header CRS is WGS84/CRS84 or absent (assumed WGS84). */
77
+ function isWgs84Crs(crs: HeaderMeta['crs']): boolean {
78
+ if (!crs) return true; // no CRS declared → assume WGS84
79
+ if (crs.code && WGS84_CODES.has(crs.code)) return true;
80
+ if (crs.name && CRS84_NAMES.some((n) => crs.name!.includes(n))) return true;
81
+ return false;
82
+ }
83
+
84
+ /** Fetch a proj4 definition string from epsg.io for the given EPSG code. */
85
+ async function fetchProj4Def(code: number): Promise<string> {
86
+ const resp = await fetch(`https://epsg.io/${code}.proj4`);
87
+ if (!resp.ok) throw new Error(`Failed to fetch proj4 definition for EPSG:${code}`);
88
+ const text = (await resp.text()).trim();
89
+ if (!text || text.startsWith('<')) throw new Error(`Invalid proj4 definition for EPSG:${code}`);
90
+ return text;
91
+ }
92
+
93
+ /** Recursively transform all coordinates in a GeoJSON geometry in-place. */
94
+ function reprojectGeometry(
95
+ geometry: GeoJSON.Geometry,
96
+ forward: (coord: [number, number]) => [number, number]
97
+ ) {
98
+ if (!geometry) return;
99
+ if (geometry.type === 'GeometryCollection') {
100
+ for (const g of geometry.geometries) reprojectGeometry(g, forward);
101
+ return;
102
+ }
103
+ reprojectCoords((geometry as any).coordinates, forward);
104
+ }
105
+
106
+ function reprojectCoords(coords: any, forward: (coord: [number, number]) => [number, number]) {
107
+ if (!coords) return;
108
+ if (typeof coords[0] === 'number' && coords.length >= 2) {
109
+ const [x, y] = forward([coords[0], coords[1]]);
110
+ coords[0] = x;
111
+ coords[1] = y;
112
+ } else if (Array.isArray(coords[0])) {
113
+ for (const c of coords) reprojectCoords(c, forward);
114
+ }
115
+ }
116
+
117
+ /** Map exotic OGC/ISO geometry types to standard GeoJSON equivalents.
118
+ * deck.gl only understands the 7 GeoJSON types. */
119
+ const GEOJSON_TYPE_MAP: Record<string, string> = {
120
+ MultiSurface: 'MultiPolygon',
121
+ CurvePolygon: 'Polygon',
122
+ Surface: 'Polygon',
123
+ CompoundCurve: 'LineString',
124
+ CircularString: 'LineString',
125
+ Curve: 'LineString',
126
+ MultiCurve: 'MultiLineString',
127
+ PolyhedralSurface: 'MultiPolygon',
128
+ TIN: 'MultiPolygon',
129
+ Triangle: 'Polygon'
130
+ };
131
+
132
+ /** Normalize a feature's geometry type to valid GeoJSON if needed. */
133
+ function normalizeGeometryType(geometry: GeoJSON.Geometry) {
134
+ const mapped = GEOJSON_TYPE_MAP[geometry.type];
135
+ if (mapped) (geometry as any).type = mapped;
136
+ }
137
+
138
+ const GEOM_TYPE_NAMES: Record<number, string> = {
139
+ 0: 'Unknown',
140
+ 1: 'Point',
141
+ 2: 'LineString',
142
+ 3: 'Polygon',
143
+ 4: 'MultiPoint',
144
+ 5: 'MultiLineString',
145
+ 6: 'MultiPolygon',
146
+ 7: 'GeometryCollection',
147
+ 8: 'CircularString',
148
+ 9: 'CompoundCurve',
149
+ 10: 'CurvePolygon',
150
+ 11: 'MultiCurve',
151
+ 12: 'MultiSurface',
152
+ 13: 'Curve',
153
+ 14: 'Surface',
154
+ 15: 'PolyhedralSurface',
155
+ 16: 'TIN',
156
+ 17: 'Triangle'
157
+ };
158
+
159
+ function populateHeaderInfo(header: HeaderMeta) {
160
+ totalFeatures = header.featuresCount;
161
+ headerInfo = {
162
+ geometryType: GEOM_TYPE_NAMES[header.geometryType] ?? `Type ${header.geometryType}`,
163
+ featuresCount: header.featuresCount,
164
+ columns: (header.columns ?? []).map((c) => ({ name: c.name, type: c.type })),
165
+ crs: header.crs ? { org: header.crs.org, code: header.crs.code, name: header.crs.name } : null,
166
+ title: header.title,
167
+ description: header.description,
168
+ hasIndex: header.indexNodeSize > 0
169
+ };
170
+
171
+ if (header.envelope && header.envelope.length >= 4) {
172
+ const [minX, minY, maxX, maxY] = header.envelope;
173
+ // Only use envelope for fitBounds if coordinates are within WGS84 range.
174
+ // Projected CRS files (e.g. EPSG:3310) have envelope in meters — skip fitBounds
175
+ // and let flyToFeaturesBounds() handle it after features are streamed.
176
+ if (minX >= -180 && maxX <= 180 && minY >= -90 && maxY <= 90) {
177
+ bounds = [minX, minY, maxX, maxY];
178
+ mapRef?.fitBounds(bounds, { padding: 40 });
179
+ }
180
+ }
181
+ }
182
+
183
+ /** Drill into nested coordinate arrays to find the first [lng, lat] pair. */
184
+ function extractFirstCoord(coords: any): [number, number] | null {
185
+ if (!Array.isArray(coords) || coords.length === 0) return null;
186
+ if (typeof coords[0] === 'number') return [coords[0] as number, coords[1] as number];
187
+ return extractFirstCoord(coords[0]);
188
+ }
189
+
190
+ function flyToFirstFeature() {
191
+ if (!mapRef || !firstFeatureCoord) return;
192
+ mapRef.flyTo({ center: firstFeatureCoord, zoom: 14 });
193
+ }
194
+
195
+ function cleanup() {
196
+ resolveMapReady?.();
197
+ resolveMapReady = null;
198
+ hasMore = false;
199
+ firstFeatureCoord = null;
200
+ // Force-close any active stream + HTTP connection
201
+ activeStreamCancel?.();
202
+ activeStreamCancel = null;
203
+ if (abortController) {
204
+ abortController.abort();
205
+ abortController = null;
206
+ }
207
+ if (overlay && mapRef) {
208
+ try {
209
+ mapRef.removeControl(overlay);
210
+ } catch {
211
+ /* already removed */
212
+ }
213
+ }
214
+ overlay = null;
215
+ mapRef = null;
216
+ features = [];
217
+ dataVersion = 0;
218
+ storedHeader = null;
219
+ storedFeatureOffset = 0;
220
+ proj4Forward = null;
221
+ }
222
+
223
+ $effect(() => {
224
+ if (!tab) return;
225
+ const _tabId = tab.id;
226
+ untrack(() => {
227
+ loadFlatGeobuf();
228
+ });
229
+ });
230
+
231
+ $effect(() => {
232
+ const id = tab.id;
233
+ const unregister = tabResources.register(id, cleanup);
234
+ return unregister;
235
+ });
236
+ onDestroy(cleanup);
237
+
238
+ async function loadFlatGeobuf() {
239
+ console.log('[FGB]', 'loadFlatGeobuf() start');
240
+ cleanup();
241
+
242
+ loading = true;
243
+ streaming = false;
244
+ error = null;
245
+ features = [];
246
+ featureCount = 0;
247
+ totalFeatures = null;
248
+ headerInfo = null;
249
+ bounds = undefined;
250
+ hasMore = false;
251
+
252
+ try {
253
+ mapReadyPromise = new Promise<void>((r) => {
254
+ resolveMapReady = r;
255
+ });
256
+
257
+ deckModules = await loadDeckModules();
258
+ loading = false;
259
+ streaming = true;
260
+
261
+ await mapReadyPromise;
262
+ if (!overlay) return;
263
+
264
+ // Read header via range requests (fast: 1-2 small requests)
265
+ // Gets metadata + feature offset to skip the spatial index
266
+ await readHeaderWithRangeRequests();
267
+
268
+ // Set up on-the-fly reprojection if the file uses a non-WGS84 CRS
269
+ proj4Forward = null;
270
+ if (storedHeader?.crs && !isWgs84Crs(storedHeader.crs)) {
271
+ const code = storedHeader.crs.code;
272
+ const crsLabel =
273
+ storedHeader.crs.org && code
274
+ ? `${storedHeader.crs.org}:${code}`
275
+ : (storedHeader.crs.name ?? 'unknown');
276
+ console.log('[FGB]', 'Projected CRS detected:', crsLabel, '→ fetching proj4 definition');
277
+
278
+ try {
279
+ const proj4Def = await fetchProj4Def(code);
280
+ const converter = proj4(proj4Def, 'EPSG:4326') as any;
281
+ proj4Forward = (coord) => converter.forward(coord);
282
+ console.log('[FGB]', 'Reprojection ready:', crsLabel, '→ WGS84');
283
+
284
+ // Reproject envelope bounds so fitBounds works
285
+ if (storedHeader.envelope && storedHeader.envelope.length >= 4) {
286
+ const [x0, y0, x1, y1] = storedHeader.envelope;
287
+ const sw = proj4Forward([x0, y0]);
288
+ const ne = proj4Forward([x1, y1]);
289
+ bounds = [sw[0], sw[1], ne[0], ne[1]];
290
+ mapRef?.fitBounds(bounds, { padding: 40 });
291
+ }
292
+ } catch (err) {
293
+ console.error('[FGB]', 'Failed to set up reprojection:', err);
294
+ error = `Cannot reproject CRS ${crsLabel} → WGS84: ${err instanceof Error ? err.message : err}`;
295
+ streaming = false;
296
+ return;
297
+ }
298
+ }
299
+
300
+ // Stream features (skips index if header was read, else sequential)
301
+ await streamFeatures(settings.featureLimit);
302
+ } catch (err) {
303
+ console.error('[FGB]', 'loadFlatGeobuf error:', err);
304
+ if (err instanceof DOMException && err.name === 'AbortError') return;
305
+ error = err instanceof Error ? err.message : String(err);
306
+ loading = false;
307
+ } finally {
308
+ console.log('[FGB]', 'done, features:', features.length);
309
+ streaming = false;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Read header via range requests (fast: 1-2 small requests).
315
+ * Stores header + feature offset for the composite stream approach.
316
+ */
317
+ async function readHeaderWithRangeRequests(): Promise<boolean> {
318
+ const url = buildHttpsUrl(tab);
319
+
320
+ let reader: HttpReader;
321
+ try {
322
+ reader = await HttpReader.open(url, false);
323
+ } catch (e) {
324
+ console.warn('[FGB]', 'HttpReader.open failed:', e);
325
+ return false;
326
+ }
327
+
328
+ const header = reader.header;
329
+ console.log('[FGB]', 'header:', {
330
+ geometryType: header.geometryType,
331
+ featuresCount: header.featuresCount,
332
+ indexNodeSize: header.indexNodeSize,
333
+ envelope: header.envelope ? Array.from(header.envelope) : null,
334
+ columns: header.columns?.length
335
+ });
336
+ populateHeaderInfo(header);
337
+
338
+ storedHeader = header;
339
+ storedFeatureOffset = reader.lengthBeforeFeatures();
340
+ console.log(
341
+ '[FGB]',
342
+ 'featureOffset:',
343
+ storedFeatureOffset,
344
+ '(index ~',
345
+ ((storedFeatureOffset - 12) / 1024 / 1024).toFixed(1),
346
+ 'MB skipped)'
347
+ );
348
+ return true;
349
+ }
350
+
351
+ /** Load all features — skips the spatial index using a Range request. */
352
+ async function loadAllFeatures() {
353
+ if (!overlay) return;
354
+ hasMore = false;
355
+ streaming = true;
356
+
357
+ try {
358
+ features = [];
359
+ featureCount = 0;
360
+ await streamFeatures();
361
+ } catch (err) {
362
+ console.error('[FGB]', 'loadAllFeatures error:', err);
363
+ if (err instanceof DOMException && err.name === 'AbortError') return;
364
+ error = err instanceof Error ? err.message : String(err);
365
+ } finally {
366
+ console.log('[FGB]', 'loadAllFeatures done, features:', features.length);
367
+ streaming = false;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Stream features sequentially.
373
+ * If storedHeader is available, skips the index with a Range request + composite stream.
374
+ */
375
+ async function streamFeatures(limit?: number) {
376
+ const ac = new AbortController();
377
+ abortController = ac;
378
+ const url = buildHttpsUrl(tab);
379
+ const t0 = performance.now();
380
+
381
+ let iter: AsyncGenerator;
382
+ activeStreamCancel = null;
383
+
384
+ if (storedHeader && storedFeatureOffset > 0) {
385
+ const fakeHeaderBytes = fgbBuildHeader({
386
+ ...storedHeader,
387
+ indexNodeSize: 0,
388
+ envelope: null
389
+ });
390
+
391
+ const featureResp = await fetch(url, {
392
+ headers: { Range: `bytes=${storedFeatureOffset}-` },
393
+ signal: ac.signal
394
+ });
395
+ console.log(
396
+ '[FGB]',
397
+ 'Range fetch:',
398
+ featureResp.status,
399
+ featureResp.headers.get('content-range')
400
+ );
401
+ if (!featureResp.ok && featureResp.status !== 206)
402
+ throw new Error(`HTTP ${featureResp.status}: ${featureResp.statusText}`);
403
+ if (!featureResp.body) throw new Error('No response body');
404
+
405
+ const composite = createCompositeStream(fakeHeaderBytes, featureResp.body);
406
+ activeStreamCancel = composite.cancel;
407
+ // Don't pass populateHeaderInfo — we already have the real header from
408
+ // readHeaderWithRangeRequests(). The composite stream has a fake header
409
+ // (indexNodeSize: 0, envelope: null) that would overwrite the real metadata.
410
+ iter = fgbGeojson.deserialize(composite.stream) as AsyncGenerator;
411
+ } else {
412
+ const response = await fetch(url, { signal: ac.signal });
413
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
414
+ if (!response.body) throw new Error('No response body');
415
+ iter = fgbGeojson.deserialize(response.body, undefined, (h: any) =>
416
+ populateHeaderInfo(h)
417
+ ) as AsyncGenerator;
418
+ }
419
+
420
+ let batchCount = 0;
421
+ let lastUpdateTime = Date.now();
422
+ let flewToFeatures = false;
423
+ let hitLimit = false;
424
+
425
+ try {
426
+ for await (const feature of iter) {
427
+ if (ac.signal.aborted) return;
428
+
429
+ const f = feature as GeoJSON.Feature;
430
+ if (f.geometry) {
431
+ normalizeGeometryType(f.geometry);
432
+ if (proj4Forward) reprojectGeometry(f.geometry, proj4Forward);
433
+ }
434
+ // Detect actual geometry type from first feature when header says Unknown
435
+ if (features.length === 0 && f.geometry && headerInfo?.geometryType === 'Unknown') {
436
+ headerInfo = { ...headerInfo, geometryType: f.geometry.type };
437
+ }
438
+ // Capture first feature coordinate for fly-to button
439
+ if (features.length === 0 && f.geometry) {
440
+ firstFeatureCoord = extractFirstCoord((f.geometry as any).coordinates);
441
+ }
442
+ features.push(f);
443
+ batchCount++;
444
+
445
+ const now = Date.now();
446
+ if (batchCount >= BATCH_SIZE || now - lastUpdateTime > 200) {
447
+ featureCount = features.length;
448
+ updateLayer();
449
+ if (!flewToFeatures) {
450
+ flyToFeaturesBounds();
451
+ flewToFeatures = true;
452
+ }
453
+ batchCount = 0;
454
+ lastUpdateTime = now;
455
+ await new Promise((r) => setTimeout(r, 0));
456
+ if (ac.signal.aborted) return;
457
+ }
458
+
459
+ if (limit && features.length >= limit) {
460
+ hitLimit = true;
461
+ break;
462
+ }
463
+ }
464
+ } finally {
465
+ // Force-close the HTTP connection. break from for-await doesn't release
466
+ // the flatgeobuf library's internal stream reader, so the browser keeps
467
+ // downloading gigabytes in the background unless we explicitly cancel.
468
+ if (hitLimit) {
469
+ activeStreamCancel?.();
470
+ activeStreamCancel = null;
471
+ ac.abort();
472
+ console.log('[FGB]', 'force-closed connection after', features.length, 'features');
473
+ }
474
+ }
475
+
476
+ if (features.length === 0) {
477
+ error = 'No features found in FlatGeobuf file';
478
+ return;
479
+ }
480
+
481
+ featureCount = features.length;
482
+ updateLayer();
483
+ if (!flewToFeatures) flyToFeaturesBounds();
484
+ console.log(
485
+ '[FGB]',
486
+ 'stream done:',
487
+ features.length,
488
+ 'features in',
489
+ (performance.now() - t0).toFixed(0),
490
+ 'ms'
491
+ );
492
+
493
+ if (hitLimit) {
494
+ hasMore = totalFeatures != null && totalFeatures > features.length;
495
+ } else {
496
+ hasMore = false;
497
+ }
498
+ }
499
+
500
+ /** Compute bounding box of current features and fly the map to it. */
501
+ function flyToFeaturesBounds() {
502
+ if (!mapRef || features.length === 0) return;
503
+ let minLng = Infinity,
504
+ minLat = Infinity,
505
+ maxLng = -Infinity,
506
+ maxLat = -Infinity;
507
+
508
+ function processCoords(coords: any) {
509
+ if (!coords) return;
510
+ if (typeof coords[0] === 'number' && coords.length >= 2) {
511
+ minLng = Math.min(minLng, coords[0]);
512
+ minLat = Math.min(minLat, coords[1]);
513
+ maxLng = Math.max(maxLng, coords[0]);
514
+ maxLat = Math.max(maxLat, coords[1]);
515
+ } else if (Array.isArray(coords[0])) {
516
+ for (const c of coords) processCoords(c);
517
+ }
518
+ }
519
+
520
+ for (const f of features) {
521
+ processCoords((f.geometry as any)?.coordinates);
522
+ }
523
+
524
+ if (minLng !== Infinity && minLng >= -180 && maxLng <= 180 && minLat >= -90 && maxLat <= 90) {
525
+ bounds = [minLng, minLat, maxLng, maxLat];
526
+ mapRef.fitBounds(bounds, { padding: 40 });
527
+ }
528
+ }
529
+
530
+ /** Create a ReadableStream: magic bytes + header bytes + feature data stream.
531
+ * Returns both the stream and a cancel() handle to force-close the connection. */
532
+ function createCompositeStream(
533
+ headerBytes: Uint8Array,
534
+ featureStream: ReadableStream<Uint8Array>
535
+ ): { stream: ReadableStream<Uint8Array>; cancel: () => void } {
536
+ const featureReader = featureStream.getReader();
537
+ let headerSent = false;
538
+
539
+ const stream = new ReadableStream({
540
+ pull(controller) {
541
+ if (!headerSent) {
542
+ controller.enqueue(new Uint8Array(magicbytes));
543
+ controller.enqueue(headerBytes);
544
+ headerSent = true;
545
+ return;
546
+ }
547
+ return featureReader.read().then(({ value, done }) => {
548
+ if (done) {
549
+ controller.close();
550
+ } else {
551
+ controller.enqueue(value);
552
+ }
553
+ });
554
+ },
555
+ cancel() {
556
+ featureReader.cancel();
557
+ }
558
+ });
559
+
560
+ return {
561
+ stream,
562
+ cancel: () => {
563
+ try {
564
+ featureReader.cancel();
565
+ } catch {
566
+ /* already released */
567
+ }
568
+ }
569
+ };
570
+ }
571
+
572
+ function updateLayer() {
573
+ if (!overlay || !deckModules) return;
574
+
575
+ // Bump version so deck.gl sees a new data identity without copying the array.
576
+ dataVersion++;
577
+ const fc = { type: 'FeatureCollection' as const, features, _v: dataVersion };
578
+
579
+ const { GeoJsonLayer } = deckModules;
580
+ overlay.setProps({
581
+ layers: [
582
+ new GeoJsonLayer({
583
+ id: 'flatgeobuf-data',
584
+ data: fc,
585
+ dataComparator: (a: any, b: any) => a._v === b._v,
586
+ pickable: true,
587
+ stroked: true,
588
+ filled: true,
589
+ pointType: 'circle',
590
+ getFillColor: geojsonFillColor as any,
591
+ getLineColor: geojsonLineColor as any,
592
+ getPointRadius: 6,
593
+ getLineWidth: 2.5,
594
+ lineWidthMinPixels: 1.5,
595
+ pointRadiusMinPixels: 4,
596
+ pointRadiusMaxPixels: 12,
597
+ autoHighlight: true,
598
+ highlightColor: [255, 255, 255, 100],
599
+ onHover: mapRef ? hoverCursor(mapRef) : undefined,
600
+ onClick: (info: any) => {
601
+ if (info.object?.properties) {
602
+ selectedFeature = { ...info.object.properties };
603
+ selectedGeoFeature = info.object;
604
+ showAttributes = true;
605
+ // Re-render with selection layer
606
+ updateLayer();
607
+ }
608
+ }
609
+ }),
610
+ buildSelectionLayer(GeoJsonLayer, selectedGeoFeature)
611
+ ].filter(Boolean)
612
+ });
613
+ mapRef?.triggerRepaint();
614
+ }
615
+
616
+ function onMapReady(map: maplibregl.Map) {
617
+ if (!deckModules) return;
618
+ mapRef = map;
619
+
620
+ const { MapboxOverlay } = deckModules;
621
+ overlay = new MapboxOverlay({
622
+ interleaved: false,
623
+ layers: []
624
+ });
625
+ map.addControl(overlay as any);
626
+ resolveMapReady?.();
627
+
628
+ if (bounds) map.fitBounds(bounds, { padding: 40 });
629
+ }
630
+ </script>
631
+
632
+ <div class="relative flex h-full overflow-hidden">
633
+ {#if loading}
634
+ <div class="flex flex-1 items-center justify-center">
635
+ <p class="text-sm text-zinc-400">{t('map.loadingFgb')}</p>
636
+ </div>
637
+ {:else if error && featureCount === 0}
638
+ <div class="flex flex-1 items-center justify-center">
639
+ <p class="text-sm text-red-400">{error}</p>
640
+ </div>
641
+ {:else}
642
+ <div class="flex-1">
643
+ <MapContainer {onMapReady} {bounds} />
644
+ </div>
645
+
646
+ <!-- Floating feature count badge + fly-to + load-all button -->
647
+ <div class="absolute left-2 top-2 z-10 flex flex-col gap-1">
648
+ <div class="flex items-center gap-1">
649
+ <div
650
+ class="pointer-events-none rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm"
651
+ >
652
+ {#if streaming}
653
+ <span class="animate-pulse">
654
+ {featureCount.toLocaleString()} features...
655
+ </span>
656
+ {:else if featureCount > 0}
657
+ {featureCount.toLocaleString()} features{#if totalFeatures && featureCount >= settings.featureLimit}{' '}
658
+ <span class="text-amber-300">
659
+ of {totalFeatures.toLocaleString()} (limit)
660
+ </span>
661
+ {/if}
662
+ {/if}
663
+ </div>
664
+ {#if firstFeatureCoord && !streaming}
665
+ <button
666
+ class="rounded bg-card/80 p-1.5 text-card-foreground backdrop-blur-sm hover:bg-card"
667
+ onclick={flyToFirstFeature}
668
+ title={t('map.flyToFirst')}
669
+ >
670
+ <LocateIcon class="size-3.5" />
671
+ </button>
672
+ {/if}
673
+ </div>
674
+ {#if hasMore && !streaming}
675
+ <button
676
+ class="rounded bg-primary/90 px-2 py-1 text-xs text-primary-foreground backdrop-blur-sm hover:bg-primary"
677
+ onclick={loadAllFeatures}
678
+ >
679
+ Stream all {(totalFeatures ?? 0).toLocaleString()}
680
+ </button>
681
+ {/if}
682
+ </div>
683
+
684
+ <!-- Floating toggle buttons -->
685
+ <div class="absolute right-2 top-2 z-10 flex gap-1">
686
+ {#if headerInfo}
687
+ <button
688
+ class="rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm hover:bg-card"
689
+ class:ring-1={showInfo}
690
+ class:ring-primary={showInfo}
691
+ onclick={() => (showInfo = !showInfo)}
692
+ >
693
+ {t('map.info')}
694
+ </button>
695
+ {/if}
696
+ {#if selectedFeature}
697
+ <button
698
+ class="rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm hover:bg-card"
699
+ class:ring-1={showAttributes}
700
+ class:ring-primary={showAttributes}
701
+ onclick={() => (showAttributes = !showAttributes)}
702
+ >
703
+ {t('map.attributes')}
704
+ </button>
705
+ {/if}
706
+ </div>
707
+
708
+ {#if showInfo && headerInfo}
709
+ <div
710
+ class="absolute right-2 top-10 z-10 max-h-[70vh] w-64 overflow-auto rounded bg-card/90 p-3 text-xs text-card-foreground backdrop-blur-sm"
711
+ >
712
+ <h3 class="mb-2 font-medium">{t('map.flatgeobufInfo')}</h3>
713
+ <dl class="space-y-1.5">
714
+ {#if headerInfo.title}
715
+ <dt class="text-muted-foreground">{t('mapInfo.title')}</dt>
716
+ <dd>{headerInfo.title}</dd>
717
+ {/if}
718
+ {#if headerInfo.description}
719
+ <dt class="text-muted-foreground">{t('mapInfo.description')}</dt>
720
+ <dd class="opacity-80">{headerInfo.description}</dd>
721
+ {/if}
722
+ <dt class="text-muted-foreground">{t('mapInfo.geometryType')}</dt>
723
+ <dd>{headerInfo.geometryType}</dd>
724
+ <dt class="text-muted-foreground">{t('mapInfo.totalFeatures')}</dt>
725
+ <dd>{headerInfo.featuresCount.toLocaleString()}</dd>
726
+ <dt class="text-muted-foreground">{t('mapInfo.spatialIndex')}</dt>
727
+ <dd>{headerInfo.hasIndex ? t('mapInfo.yesRTree') : t('mapInfo.no')}</dd>
728
+ {#if headerInfo.crs}
729
+ <dt class="text-muted-foreground">{t('mapInfo.crs')}</dt>
730
+ <dd>
731
+ {#if headerInfo.crs.org && headerInfo.crs.code}
732
+ {headerInfo.crs.org}:{headerInfo.crs.code}
733
+ {/if}
734
+ {#if headerInfo.crs.name}
735
+ ({headerInfo.crs.name})
736
+ {/if}
737
+ </dd>
738
+ {/if}
739
+ {#if headerInfo.columns.length > 0}
740
+ <dt class="text-muted-foreground">{t('mapInfo.columns')} ({headerInfo.columns.length})</dt>
741
+ {#each headerInfo.columns as col}
742
+ <dd class="ms-2 opacity-80">- {col.name}</dd>
743
+ {/each}
744
+ {/if}
745
+ </dl>
746
+ </div>
747
+ {/if}
748
+
749
+ <AttributeTable
750
+ feature={selectedFeature}
751
+ visible={showAttributes}
752
+ onClose={() => (showAttributes = false)}
753
+ />
754
+ {/if}
755
+ </div>