diff --git a/lonboard/experimental/_geotiff.py b/lonboard/experimental/_geotiff.py new file mode 100644 index 00000000..87bad3a6 --- /dev/null +++ b/lonboard/experimental/_geotiff.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np +import traitlets as t +from affine import Affine + +from lonboard.experimental.traits import TextureTrait +from lonboard.layer import BaseLayer + +if TYPE_CHECKING: + import sys + from typing import Any + + from numpy.typing import NDArray + from rasterio.io import DatasetReader + + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + + +def load_arr_and_transform( + src: DatasetReader, + *, + downscale: int | None, +) -> tuple[NDArray[np.uint8], Affine]: + """Load array and transform from rasterio source.""" + if downscale is None: + return src.read(), src.transform + + # Read overview array from src + overview_height = int(src.height / downscale) + overview_width = int(src.width / downscale) + overview_shape = (src.count, overview_height, overview_width) + + arr: np.ndarray = src.read(out_shape=overview_shape) + overview_transform = Affine( + src.transform.a * downscale, # pixel width + src.transform.b, # rotation/skew x + src.transform.c, # top-left x + src.transform.d, # rotation/skew y + src.transform.e * downscale, # pixel height (usually negative) + src.transform.f, # top-left y + ) + return arr, overview_transform + + +def apply_colormap( + arr: NDArray[np.uint8], + cmap: dict[int, tuple[int, int, int] | tuple[int, int, int, int]], +) -> NDArray[np.uint8]: + """Apply rasterio colormap to single-band array.""" + lut = np.zeros((max(cmap.keys()) + 1, 4), dtype=np.uint8) + for k, v in cmap.items(): + lut[k] = v + + return lut[arr] + + +class GeoTiffLayer(BaseLayer): + """GeoTiffLayer.""" + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) # type: ignore + + @classmethod + def from_rasterio( + cls, + src: DatasetReader, + *, + downscale: int | None = None, + **kwargs: Any, + ) -> Self: + import rasterio.plot + + arr, transform = load_arr_and_transform(src, downscale=downscale) + + if arr.shape[0] == 1 and src.colormap(1) is not None: + image_arr = apply_colormap(arr[0], src.colormap(1)) + else: + # swap axes order from (bands, rows, columns) to (rows, columns, bands) + image_arr = rasterio.plot.reshape_as_image(arr) + + image_height = image_arr.shape[0] + image_width = image_arr.shape[1] + + return cls( + source_projection=src.crs.to_wkt(), + geotransform=transform, + texture=image_arr, + width=image_width, + height=image_height, + **kwargs, + ) + + _layer_type = t.Unicode("geotiff").tag(sync=True) + + source_projection = t.Unicode().tag(sync=True) + """The source projection of the GeoTIFF, in Proj4 or WKT format""" + + geotransform = t.List(t.Float()).tag(sync=True) + """The GeoTIFF geotransform as a list of 6 floats in affine ordering.""" + + texture = TextureTrait().tag(sync=True) + + wireframe = t.Bool(None, allow_none=True).tag(sync=True) + """Whether to render the mesh in wireframe mode. + + - Type: `bool`, optional + - Default: `False` + """ + + width = t.Int().tag(sync=True) + """The width of the GeoTIFF in pixels.""" + + height = t.Int().tag(sync=True) + """The height of the GeoTIFF in pixels.""" diff --git a/package-lock.json b/package-lock.json index a4f364ff..265a90c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@deck.gl/mapbox": "^9.2.2", "@deck.gl/mesh-layers": "^9.2.2", "@deck.gl/react": "^9.2.2", + "@developmentseed/deck.gl-raster": "file:../deck.gl-raster", "@geoarrow/deck.gl-layers": "^0.4.0-beta.6", "@geoarrow/geoarrow-js": "^0.3.2", "@nextui-org/react": "^2.4.8", @@ -20,11 +21,13 @@ "apache-arrow": "^21.1.0", "esbuild-sass-plugin": "^3.3.1", "framer-motion": "^12.23.19", + "geotiff": "^2.1.4-beta.1", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "maplibre-gl": "^5.9.0", "memoize-one": "^6.0.0", "parquet-wasm": "0.7.1", + "proj4": "^2.20.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-map-gl": "^8.1.0", @@ -60,6 +63,26 @@ "typescript-eslint": "^8.16.0" } }, + "../deck.gl-raster": { + "name": "@developmentseed/deck.gl-raster", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "geotiff": "^2.1.3", + "proj4": "^2.20.2" + }, + "devDependencies": { + "@types/node": "^22.10.1", + "@typescript-eslint/eslint-plugin": "^8.16.0", + "@typescript-eslint/parser": "^8.16.0", + "eslint": "^9.15.0", + "jsdom": "^27.2.0", + "prettier": "^3.3.3", + "tsup": "^8.3.5", + "typescript": "^5.7.2", + "vitest": "^2.1.5" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -104,8 +127,7 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.0.tgz", "integrity": "sha512-fdRs9PSrBF7QUntpZpq6BTw58fhgGJojgg39m9oFOJGZT+nip9b0so5cYY1oWl5pvemDLr0cPPsH46vwThEbpQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)", - "peer": true + "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@csstools/cascade-layer-name-parser": { "version": "2.0.5", @@ -219,6 +241,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -242,6 +265,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1356,6 +1380,7 @@ "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-9.2.2.tgz", "integrity": "sha512-ZvCV8kcC730t62Q+iiCn8SOPgDCEyvV6i/GvIXf59Rnyl8pRvo7wcbl5zorbIEFng+a+EcYv/tEAZcAkwe1oEA==", "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/core": "^4.2.0", "@loaders.gl/images": "^4.2.0", @@ -1381,6 +1406,7 @@ "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.2.2.tgz", "integrity": "sha512-DwbUai9Gm3wdKUj7rflMeOTsBHXlvm5Zah10qNHehv4TNgt8iZ1FFvbZoTi5ocj8s5wo0JdyLQuT98r5LGplrw==", "license": "MIT", + "peer": true, "dependencies": { "@luma.gl/constants": "^9.2.2", "@luma.gl/shadertools": "^9.2.2", @@ -1432,6 +1458,7 @@ "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.2.2.tgz", "integrity": "sha512-O1tmE6I7KQs3Wv2W+KkLo3mOW6CqWn92jwkCq7i5b7OZwxNfzdEQFK3bDpQTfDEp1BTwR6bMP1TkhGBVksQJ2Q==", "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/images": "^4.2.0", "@loaders.gl/schema": "^4.2.0", @@ -1470,6 +1497,7 @@ "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.2.2.tgz", "integrity": "sha512-OhH11zy2yyETXY6rwinJnaKk3eTzAk4BHH31ZltK+DI4XHBUVPpXHIyI9soPsI06TObfdx54LgLxdA/PIDdscg==", "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/gltf": "^4.2.0", "@loaders.gl/schema": "^4.2.0", @@ -1510,6 +1538,10 @@ "@luma.gl/core": "~9.2.2" } }, + "node_modules/@developmentseed/deck.gl-raster": { + "resolved": "../deck.gl-raster", + "link": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", @@ -2460,7 +2492,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.3.4.tgz", "integrity": "sha512-JQ3y3p/KlZP7lfobwON5t7H9WinXEYTvuo3SRQM8TBKhM+koEYZhvI2GwzoXx54MbBbY+s3fm1dq5UAAmaTsZw==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/compression": "4.3.4", "@loaders.gl/crypto": "4.3.4", @@ -2485,15 +2516,13 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@loaders.gl/compression": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@loaders.gl/compression/-/compression-4.3.4.tgz", "integrity": "sha512-+o+5JqL9Sx8UCwdc2MTtjQiUHYQGJALHbYY/3CT+b9g/Emzwzez2Ggk9U9waRfdHiBCzEgRBivpWZEOAtkimXQ==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/worker-utils": "4.3.4", @@ -2518,6 +2547,7 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.4.tgz", "integrity": "sha512-cG0C5fMZ1jyW6WCsf4LoHGvaIAJCEVA/ioqKoYRwoSfXkOf+17KupK1OUQyUCw5XoRn+oWA1FulJQOYlXnb9Gw==", "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/schema": "4.3.4", @@ -2530,7 +2560,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/crypto/-/crypto-4.3.4.tgz", "integrity": "sha512-3VS5FgB44nLOlAB9Q82VOQnT1IltwfRa1miE0mpHCe1prYu1M/dMnEyynusbrsp+eDs3EKbxpguIS9HUsFu5dQ==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/worker-utils": "4.3.4", @@ -2560,7 +2589,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-4.3.4.tgz", "integrity": "sha512-8xub38lSWW7+ZXWuUcggk7agRHJUy6RdipLNKZ90eE0ZzLNGDstGD1qiBwkvqH0AkG+uz4B7Kkiptyl7w2Oa6g==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/schema": "4.3.4", @@ -2621,7 +2649,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-4.3.4.tgz", "integrity": "sha512-UJrlHys1fp9EUO4UMnqTCqvKvUjJVCbYZ2qAKD7tdGzHJYT8w/nsP7f/ZOYFc//JlfC3nq+5ogvmdpq2pyu3TA==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/images": "4.3.4", "@loaders.gl/loader-utils": "4.3.4", @@ -2636,7 +2663,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-4.3.4.tgz", "integrity": "sha512-9DrJX8RQf14htNtxsPIYvTso5dUce9WaJCWCIY/79KYE80Be6dhcEYMknxBS4w3+PAuImaAe66S5xo9B7Erm5A==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/gis": "4.3.4", "@loaders.gl/images": "4.3.4", @@ -2667,7 +2693,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-4.3.4.tgz", "integrity": "sha512-JszbRJGnxL5Fh82uA2U8HgjlsIpzYoCNNjy3cFsgCaxi4/dvjz3BkLlBilR7JlbX8Ka+zlb4GAbDDChiXLMJ/g==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/images": "4.3.4", "@loaders.gl/loader-utils": "4.3.4", @@ -2701,7 +2726,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-4.3.4.tgz", "integrity": "sha512-oC0zJfyvGox6Ag9ABF8fxOkx9yEFVyzTa9ryHXl2BqLiQoR1v3p+0tIJcEbh5cnzHfoTZzUis1TEAZluPRsHBQ==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/math": "4.3.4", @@ -2720,7 +2744,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-4.3.4.tgz", "integrity": "sha512-yXF0wuYzJUdzAJQrhLIua6DnjOiBJusaY1j8gpvuH1VYs3mzvWlIRuZKeUd9mduQZKK88H2IzHZbj2RGOauq4w==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/images": "4.3.4", "@loaders.gl/loader-utils": "4.3.4", @@ -2747,7 +2770,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-4.3.4.tgz", "integrity": "sha512-p+y/KskajsvyM3a01BwUgjons/j/dUhniqd5y1p6keLOuwoHlY/TfTKd+XluqfyP14vFrdAHCZTnFCWLblN10w==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.4", "@loaders.gl/schema": "4.3.4", @@ -2762,7 +2784,6 @@ "resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.3.4.tgz", "integrity": "sha512-bHY4XdKYJm3vl9087GMoxnUqSURwTxPPh6DlAGOmz6X9Mp3JyWuA2gk3tQ1UIuInfjXKph3WAUfGe6XRIs1sfw==", "license": "MIT", - "peer": true, "dependencies": { "@loaders.gl/compression": "4.3.4", "@loaders.gl/crypto": "4.3.4", @@ -2778,13 +2799,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.2.2.tgz", "integrity": "sha512-XURMF0gSh0ImZltYa/PCe9KgmopQJiOA6y1m1PxDxJY8OCLma7ZJyvomLn7TQBvPtWTYZsibTW7blu7RwThsaQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@luma.gl/core": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.2.2.tgz", "integrity": "sha512-X63BnXXDlC9AmoG4sUVsfxLn+DoNovbX/z5ZXxnhpxx47536Ss/SLzwnLvm/ZoDhK9/s5qdI95mSZKuqzKCkjw==", "license": "MIT", + "peer": true, "dependencies": { "@math.gl/types": "^4.1.0", "@probe.gl/env": "^4.0.8", @@ -2798,6 +2821,7 @@ "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.2.2.tgz", "integrity": "sha512-Upq/jVPvgi/rjwgSGYyW+jobJBotKR/aNTDwyHAubx6wXWluZqnR0ZBwctiO9i7w2RIzZGboMYs4dIgVw0ULaQ==", "license": "MIT", + "peer": true, "dependencies": { "@math.gl/core": "^4.1.0", "@math.gl/types": "^4.1.0", @@ -2832,6 +2856,7 @@ "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.2.2.tgz", "integrity": "sha512-ChskCXE8Q+5/rC8zPR7pHBSfERGRui5qvw6bZnOZMVwTvGbW5tI5od5Wu9ytGi45kWus66M+M/o5LpP3hfc4Hg==", "license": "MIT", + "peer": true, "dependencies": { "@math.gl/core": "^4.1.0", "@math.gl/types": "^4.1.0", @@ -3029,15 +3054,13 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/@mapbox/martini/-/martini-0.2.0.tgz", "integrity": "sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@mapbox/tiny-sdf": { "version": "2.0.7", @@ -3056,7 +3079,6 @@ "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@mapbox/point-geometry": "~0.1.0" } @@ -3148,7 +3170,6 @@ "resolved": "https://registry.npmjs.org/@math.gl/culling/-/culling-4.1.0.tgz", "integrity": "sha512-jFmjFEACnP9kVl8qhZxFNhCyd47qPfSVmSvvjR0/dIL6R9oD5zhR1ub2gN16eKDO/UM7JF9OHKU3EBIfeR7gtg==", "license": "MIT", - "peer": true, "dependencies": { "@math.gl/core": "4.1.0", "@math.gl/types": "4.1.0" @@ -3159,7 +3180,6 @@ "resolved": "https://registry.npmjs.org/@math.gl/geospatial/-/geospatial-4.1.0.tgz", "integrity": "sha512-BzsUhpVvnmleyYF6qdqJIip6FtIzJmnWuPTGhlBuPzh7VBHLonCFSPtQpbkRuoyAlbSyaGXcVt6p6lm9eK2vtg==", "license": "MIT", - "peer": true, "dependencies": { "@math.gl/core": "4.1.0", "@math.gl/types": "4.1.0" @@ -3170,6 +3190,7 @@ "resolved": "https://registry.npmjs.org/@math.gl/polygon/-/polygon-4.1.0.tgz", "integrity": "sha512-YA/9PzaCRHbIP5/0E9uTYrqe+jsYTQoqoDWhf6/b0Ixz8bPZBaGDEafLg3z7ffBomZLacUty9U3TlPjqMtzPjA==", "license": "MIT", + "peer": true, "dependencies": { "@math.gl/core": "4.1.0" } @@ -4327,6 +4348,7 @@ "resolved": "https://registry.npmjs.org/@nextui-org/system/-/system-2.4.6.tgz", "integrity": "sha512-6ujAriBZMfQ16n6M6Ad9g32KJUa1CzqIVaHN/tymadr/3m8hrr7xDw6z50pVjpCRq2PaaA1hT8Hx7EFU3f2z3Q==", "license": "MIT", + "peer": true, "dependencies": { "@internationalized/date": "3.6.0", "@nextui-org/react-utils": "2.1.3", @@ -4421,6 +4443,7 @@ "resolved": "https://registry.npmjs.org/@nextui-org/theme/-/theme-2.4.5.tgz", "integrity": "sha512-c7Y17n+hBGiFedxMKfg7Qyv93iY5MteamLXV4Po4c1VF1qZJI6I+IKULFh3FxPWzAoz96r6NdYT7OLFjrAJdWg==", "license": "MIT", + "peer": true, "dependencies": { "@nextui-org/shared-utils": "2.1.2", "clsx": "^1.2.1", @@ -5079,6 +5102,12 @@ "node": ">=0.10" } }, + "node_modules/@petamoriken/float16": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", + "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -7386,7 +7415,6 @@ "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-5.1.5.tgz", "integrity": "sha512-FqbmEEOJ4rU4/2t7FKx0HUWmjFEVqR+NJrFP7ymGSjja2SQ7Q91nnBihGuT+yuHHl6ElMjQ3ttsB/eTmyCycxA==", "license": "MIT", - "peer": true, "dependencies": { "@turf/helpers": "^5.1.5", "@turf/invariant": "^5.1.5" @@ -7397,7 +7425,6 @@ "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-5.1.5.tgz", "integrity": "sha512-//pITsQ8xUdcQ9pVb4JqXiSqG4dos5Q9N4sYFoWghX21tfOV2dhc5TGqYOhnHrQS7RiKQL1vQ48kIK34gQ5oRg==", "license": "MIT", - "peer": true, "dependencies": { "@turf/helpers": "^5.1.5" } @@ -7406,15 +7433,13 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz", "integrity": "sha512-/lF+JR+qNDHZ8bF9d+Cp58nxtZWJ3sqFe6n3u3Vpj+/0cqkjk4nXKYBSY0azm+GIYB5mWKxUXvuP/m0ZnKj1bw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@turf/invariant": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz", "integrity": "sha512-28RCBGvCYsajVkw2EydpzLdcYyhSA77LovuOvgCJplJWaNVyJYH6BOR3HR9w50MEkPqb/Vc/jdo6I6ermlRtQA==", "license": "MIT", - "peer": true, "dependencies": { "@turf/helpers": "^5.1.5" } @@ -7424,7 +7449,6 @@ "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz", "integrity": "sha512-ZjQ3Ii62X9FjnK4hhdsbT+64AYRpaI8XMBMcyftEOGSmPMUVnkbvuv3C9geuElAXfQU7Zk1oWGOcrGOD9zr78Q==", "license": "MIT", - "peer": true, "dependencies": { "@turf/helpers": "^5.1.5" } @@ -7434,7 +7458,6 @@ "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-5.1.5.tgz", "integrity": "sha512-Gdem7JXNu+G4hMllQHXRFRihJl3+pNl7qY+l4qhQFxq+hiU1cQoVFnyoleIqWKIrdK/i2YubaSwc3SCM7N5mMw==", "license": "MIT", - "peer": true, "dependencies": { "@turf/boolean-clockwise": "^5.1.5", "@turf/clone": "^5.1.5", @@ -7459,7 +7482,6 @@ "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", "integrity": "sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -7480,8 +7502,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", @@ -7573,14 +7594,14 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/react": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -7664,6 +7685,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -7973,7 +7995,6 @@ "resolved": "https://registry.npmjs.org/a5-js/-/a5-js-0.5.0.tgz", "integrity": "sha512-VAw19sWdYadhdovb0ViOIi1SdKx6H6LwcGMRFKwMfgL5gcmL/1fKJHfgsNgNaJ7xC/eEyjs6VK+VVd4N0a+peg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "gl-matrix": "^3.4.3" } @@ -7984,6 +8005,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8069,6 +8091,7 @@ "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-21.1.0.tgz", "integrity": "sha512-kQrYLxhC+NTVVZ4CCzGF6L/uPVOzJmD1T3XgbiUnP7oTeVFOFgEUu6IKNwCDkpFoBVqDKQivlX4RUFqqnWFlEA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/helpers": "^0.5.11", "@types/command-line-args": "^5.2.3", @@ -8383,8 +8406,7 @@ } ], "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/baseline-browser-mapping": { "version": "2.8.20", @@ -8437,7 +8459,6 @@ "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "base64-js": "^1.1.2" } @@ -8462,6 +8483,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -8481,7 +8503,6 @@ "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", "integrity": "sha512-Bvx4xH00qweepGc43xFvMs5BKASXTbHaHm6+kDYIK9p/4iFwjATQkmPKHQSgJZzKbAymhztRbXUf1Nqhzl73/Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8490,8 +8511,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", - "license": "MIT/X11", - "peer": true + "license": "MIT/X11" }, "node_modules/bytewise": { "version": "1.1.0", @@ -8637,7 +8657,6 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": "*" } @@ -8738,8 +8757,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/command-line-args": { "version": "6.0.1", @@ -8845,7 +8863,6 @@ "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", "integrity": "sha512-IG97qShIP+nrJCXMCgkNZgH7jZQ4n8RpPyPeXX++T6avR/KhLhgLiHKoEn5Rc1KjfycSfA9DMa6m+4C4eguHhw==", "license": "MIT", - "peer": true, "dependencies": { "buf-compare": "^1.0.0", "is-error": "^2.2.0" @@ -8858,8 +8875,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -8880,7 +8896,6 @@ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": "*" } @@ -9001,8 +9016,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/data-view-buffer": { "version": "1.0.2", @@ -9093,7 +9107,6 @@ "resolved": "https://registry.npmjs.org/deep-strict-equal/-/deep-strict-equal-0.2.0.tgz", "integrity": "sha512-3daSWyvZ/zwJvuMGlzG1O+Ow0YSadGfb3jsh9xoCutv2tWyB9dA4YvR9L9/fSdDZa2dByYQe+TqapSGUrjnkoA==", "license": "MIT", - "peer": true, "dependencies": { "core-assert": "^0.2.0" }, @@ -9437,6 +9450,7 @@ "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -9516,6 +9530,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9968,7 +9983,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "strnum": "^1.1.1" }, @@ -9989,8 +10003,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", @@ -10138,6 +10151,7 @@ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", "license": "MIT", + "peer": true, "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", @@ -10240,6 +10254,31 @@ "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", "license": "ISC" }, + "node_modules/geotiff": { + "version": "2.1.4-beta.1", + "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.1.4-beta.1.tgz", + "integrity": "sha512-3cW7V2XjnBWDJ8Gj/vCidvl3xiaN5c3az1Tfym9MBFq0LI3pi37YYWt9YxOjXF7njO135iKRGTJcbbsTiPdk2Q==", + "license": "MIT", + "dependencies": { + "@petamoriken/float16": "^3.4.7", + "lerc": "^3.0.0", + "pako": "^2.0.4", + "parse-headers": "^2.0.2", + "quick-lru": "^6.1.1", + "web-worker": "^1.5.0", + "xml-utils": "^1.10.2", + "zstddec": "^0.2.0-alpha.3" + }, + "engines": { + "node": ">=10.19" + } + }, + "node_modules/geotiff/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -10442,7 +10481,6 @@ "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-4.3.0.tgz", "integrity": "sha512-zgvyHZz5bEKeuyYGh0bF9/kYSxJ2SqroopkXHqKnD3lfjaZawcxulcI9nWbNC54gakl/2eObRLHWueTf1iLSaA==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=4", "npm": ">=3", @@ -10572,8 +10610,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -10608,8 +10645,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/immutable": { "version": "5.1.4", @@ -10648,8 +10684,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/input-otp": { "version": "1.4.1", @@ -10781,8 +10816,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-callable": { "version": "1.2.7", @@ -10851,8 +10885,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-extendable": { "version": "0.1.1", @@ -11167,8 +11200,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -11380,7 +11412,6 @@ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "license": "(MIT OR GPL-3.0-or-later)", - "peer": true, "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -11410,6 +11441,12 @@ "integrity": "sha512-FeA3g56ksdFNwjXJJsc1CCc7co+AJYDp6ipIp878zZ2bU8kWROatLYf39TQEd4/XRSUvBXovQ8gaVKWPXsCLEQ==", "license": "MIT" }, + "node_modules/lerc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", + "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==", + "license": "Apache-2.0" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -11451,7 +11488,6 @@ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "license": "MIT", - "peer": true, "dependencies": { "immediate": "~3.0.5" } @@ -11805,7 +11841,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=0.6" } @@ -11834,15 +11869,13 @@ "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==", "license": "ISC", - "optional": true, - "peer": true + "optional": true }, "node_modules/lzo-wasm": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/lzo-wasm/-/lzo-wasm-0.0.4.tgz", "integrity": "sha512-VKlnoJRFrB8SdJhlVKvW5vI1gGwcZ+mvChEXcSX6r2xDNc/Q2FD9esfBmGCuPZdrJ1feO+YcVFd2PTk0c137Gw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/magic-string": { "version": "0.30.21", @@ -11941,7 +11974,6 @@ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -12395,8 +12427,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)", - "peer": true + "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", @@ -12417,6 +12448,12 @@ "integrity": "sha512-fjEGpMApzt3mpI2pUxdRgQGu5G+s4nr0vm5xn43JO7jxdYzzu2fHrVrTHtfeEhtB6vfvTzJBz0WydDYzLWvszQ==", "license": "MIT OR Apache-2.0" }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", + "license": "MIT" + }, "node_modules/partysocket": { "version": "0.0.25", "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-0.0.25.tgz", @@ -12480,7 +12517,6 @@ "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" @@ -12554,6 +12590,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13477,6 +13514,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13502,7 +13540,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -13538,13 +13575,12 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/proj4": { - "version": "2.19.10", - "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.19.10.tgz", - "integrity": "sha512-uL6/C6kA8+ncJAEDmUeV8PmNJcTlRLDZZa4/87CzRpb8My4p+Ame4LhC4G3H/77z2icVqcu3nNL9h5buSdnY+g==", + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.20.2.tgz", + "integrity": "sha512-ipfBRfQly0HhHTO7hnC1GfaX8bvroO7VV4KH889ehmADSE8C/qzp2j+Jj6783S9Tj6c2qX/hhYm7oH0kgXzBAA==", "license": "MIT", "dependencies": { "mgrs": "1.0.0", @@ -13623,6 +13659,18 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/quickselect": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", @@ -13634,6 +13682,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13643,6 +13692,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13712,7 +13762,6 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13879,7 +13928,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -13915,8 +13963,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/safe-identifier": { "version": "0.4.2", @@ -14001,7 +14048,6 @@ "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz", "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==", "license": "MIT", - "peer": true, "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", @@ -14051,7 +14097,6 @@ ], "license": "MIT", "optional": true, - "peer": true, "dependencies": { "sass": "1.93.2" } @@ -14068,7 +14113,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14085,7 +14129,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14102,7 +14145,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14119,7 +14161,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14136,7 +14177,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14153,7 +14193,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14170,7 +14209,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14187,7 +14225,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14204,7 +14241,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14221,7 +14257,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14238,7 +14273,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14255,7 +14289,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14272,7 +14305,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14289,7 +14321,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14306,7 +14337,6 @@ "!linux", "!win32" ], - "peer": true, "dependencies": { "sass": "1.93.2" } @@ -14323,7 +14353,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14340,7 +14369,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=14.0.0" } @@ -14350,7 +14378,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14482,8 +14509,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -14633,8 +14659,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.6.1.tgz", "integrity": "sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sort-asc": { "version": "0.2.0", @@ -14742,7 +14767,6 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -14981,8 +15005,7 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sucrase": { "version": "3.35.0", @@ -15057,7 +15080,6 @@ "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", "license": "MIT", - "peer": true, "dependencies": { "sync-message-port": "^1.0.0" }, @@ -15070,7 +15092,6 @@ "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.0.0" } @@ -15444,6 +15465,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15721,8 +15743,13 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" }, "node_modules/wgsl_reflect": { "version": "1.2.3", @@ -15960,6 +15987,7 @@ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -15976,11 +16004,18 @@ } } }, + "node_modules/xml-utils": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", + "integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==", + "license": "CC0-1.0" + }, "node_modules/xstate": { "version": "5.23.0", "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.23.0.tgz", "integrity": "sha512-jo126xWXkU6ySQ91n51+H2xcgnMuZcCQpQoD3FQ79d32a6RQvryRh8rrDHnH4WDdN/yg5xNjlIRol9ispMvzeg==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/xstate" @@ -16013,6 +16048,7 @@ "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lib0": "^0.2.99" }, @@ -16043,8 +16079,13 @@ "resolved": "https://registry.npmjs.org/zstd-codec/-/zstd-codec-0.1.5.tgz", "integrity": "sha512-v3fyjpK8S/dpY/X5WxqTK3IoCnp/ZOLxn144GZVlNUjtwAchzrVo03h+oMATFhCIiJ5KTr4V3vDQQYz4RU684g==", "license": "MIT", - "optional": true, - "peer": true + "optional": true + }, + "node_modules/zstddec": { + "version": "0.2.0-alpha.3", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0-alpha.3.tgz", + "integrity": "sha512-uHyE3TN8jRFOaMVwdhERfrcaabyoUUawIRDKXE6x0nCU7mzyIZO0LndJ3AtVUiKLF0lC+8F5bMSySWEF586PSA==", + "license": "MIT AND BSD-3-Clause" } } } diff --git a/package.json b/package.json index 380a2e05..d8002cd4 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@deck.gl/mapbox": "^9.2.2", "@deck.gl/mesh-layers": "^9.2.2", "@deck.gl/react": "^9.2.2", + "@developmentseed/deck.gl-raster": "file:../deck.gl-raster", "@geoarrow/deck.gl-layers": "^0.4.0-beta.6", "@geoarrow/geoarrow-js": "^0.3.2", "@nextui-org/react": "^2.4.8", @@ -15,11 +16,13 @@ "apache-arrow": "^21.1.0", "esbuild-sass-plugin": "^3.3.1", "framer-motion": "^12.23.19", + "geotiff": "^2.1.4-beta.1", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "maplibre-gl": "^5.9.0", "memoize-one": "^6.0.0", "parquet-wasm": "0.7.1", + "proj4": "^2.20.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-map-gl": "^8.1.0", diff --git a/src/model/layer/geotiff.ts b/src/model/layer/geotiff.ts new file mode 100644 index 00000000..bf0b9f1a --- /dev/null +++ b/src/model/layer/geotiff.ts @@ -0,0 +1,238 @@ +import { SimpleMeshLayer, SimpleMeshLayerProps } from "@deck.gl/mesh-layers"; +import { reprojection } from "@developmentseed/deck.gl-raster"; +import type { WidgetModel } from "@jupyter-widgets/base"; +import proj4 from "proj4"; +import type { PROJJSONDefinition } from "proj4/dist/lib/core.js"; +import type Projection from "proj4/dist/lib/Proj.js"; + +import { BaseLayerModel } from "./base.js"; +import { isDefined } from "../../util.js"; + +const OGC_84: PROJJSONDefinition = { + $schema: "https://proj.org/schemas/v0.7/projjson.schema.json", + type: "GeographicCRS", + name: "WGS 84 (CRS84)", + datum_ensemble: { + name: "World Geodetic System 1984 ensemble", + members: [ + { + name: "World Geodetic System 1984 (Transit)", + id: { authority: "EPSG", code: 1166 }, + }, + { + name: "World Geodetic System 1984 (G730)", + id: { authority: "EPSG", code: 1152 }, + }, + { + name: "World Geodetic System 1984 (G873)", + id: { authority: "EPSG", code: 1153 }, + }, + { + name: "World Geodetic System 1984 (G1150)", + id: { authority: "EPSG", code: 1154 }, + }, + { + name: "World Geodetic System 1984 (G1674)", + id: { authority: "EPSG", code: 1155 }, + }, + { + name: "World Geodetic System 1984 (G1762)", + id: { authority: "EPSG", code: 1156 }, + }, + { + name: "World Geodetic System 1984 (G2139)", + id: { authority: "EPSG", code: 1309 }, + }, + ], + ellipsoid: { + name: "WGS 84", + semi_major_axis: 6378137, + inverse_flattening: 298.257223563, + }, + accuracy: "2.0", + id: { authority: "EPSG", code: 6326 }, + }, + coordinate_system: { + subtype: "ellipsoidal", + axis: [ + { + name: "Geodetic longitude", + abbreviation: "Lon", + direction: "east", + unit: "degree", + }, + { + name: "Geodetic latitude", + abbreviation: "Lat", + direction: "north", + unit: "degree", + }, + ], + }, + scope: "Not known.", + area: "World.", + bbox: { + south_latitude: -90, + west_longitude: -180, + north_latitude: 90, + east_longitude: 180, + }, + // @ts-expect-error - proj4 types are incomplete + id: { authority: "OGC", code: "CRS84" }, +}; + +export class GeotiffModel extends BaseLayerModel { + static layerType = "geotiff"; + + protected sourceProjection!: Projection; + protected geotransform!: [number, number, number, number, number, number]; + protected width!: number; + protected height!: number; + + protected texture?: + | string + | { + width: number; + height: number; + data: DataView; + }; + protected wireframe: SimpleMeshLayerProps["wireframe"]; + + protected reprojectionMesh: reprojection.RasterReprojector; + + constructor(model: WidgetModel, updateStateCallback: () => void) { + super(model, updateStateCallback); + + this.initRegularAttribute("source_projection", "sourceProjection"); + this.initRegularAttribute("geotransform", "geotransform"); + this.initRegularAttribute("texture", "texture"); + this.initRegularAttribute("wireframe", "wireframe"); + this.initRegularAttribute("width", "width"); + this.initRegularAttribute("height", "height"); + + const reprojectors = createReprojectionFunctions( + this.geotransform, + this.sourceProjection, + ); + + console.time("RasterReprojector initialization"); + const reprojectionMesh = new reprojection.RasterReprojector( + reprojectors, + this.width, + this.height, + ); + console.timeEnd("RasterReprojector initialization"); + + console.time("RasterReprojector mesh generation"); + reprojectionMesh.run(0.125); + console.timeEnd("RasterReprojector mesh generation"); + + this.reprojectionMesh = reprojectionMesh; + } + + /** + * Prepare texture input from Python side to something deck.gl can consume. + * + * I initially tried to pass in raw texture parameters, but I kept getting + * WebGL errors. For now, it's simplest to go through an ImageData object. + */ + prepareTexture(): SimpleMeshLayerProps["texture"] { + if (!isDefined(this.texture)) { + return undefined; + } + + if (typeof this.texture === "string") { + return this.texture; + } + + const data: Uint8ClampedArray = new Uint8ClampedArray( + this.texture.data.buffer, + this.texture.data.byteOffset, + this.texture.data.byteLength, + ); + + // @ts-expect-error: ImageData constructor typing + return new ImageData(data, this.texture.width, this.texture.height); + } + + createReprojectors(): reprojection.ReprojectionFns { + return createReprojectionFunctions( + this.geotransform, + this.sourceProjection, + ); + } + + layerProps(): SimpleMeshLayerProps { + const mesh = buildDeckMeshFromDelatin(this.reprojectionMesh); + return { + id: this.model.model_id, + // Dummy data because we're only rendering _one_ instance of this mesh + // https://github.com/visgl/deck.gl/blob/93111b667b919148da06ff1918410cf66381904f/modules/geo-layers/src/terrain-layer/terrain-layer.ts#L241 + data: [1], + mesh, + ...(isDefined(this.texture) && { texture: this.prepareTexture() }), + ...(isDefined(this.wireframe) && { wireframe: this.wireframe }), + // We're only rendering a single mesh, without instancing + // https://github.com/visgl/deck.gl/blob/93111b667b919148da06ff1918410cf66381904f/modules/geo-layers/src/terrain-layer/terrain-layer.ts#L244 + _instanced: false, + // Dummy accessors for the dummy data + // We place our mesh at the coordinate origin + getPosition: [0, 0, 0], + // We give a white color to turn off color mixing with the texture + getColor: [255, 255, 255], + }; + } + + render(): SimpleMeshLayer { + return new SimpleMeshLayer({ + ...this.baseLayerProps(), + ...this.layerProps(), + }); + } +} + +function createReprojectionFunctions( + geotransform: [number, number, number, number, number, number], + sourceProjection: Projection, +): reprojection.ReprojectionFns { + const converter = proj4(sourceProjection, OGC_84); + + const inverseGeotransform = + reprojection.affine.invertGeoTransform(geotransform); + return { + pixelToInputCRS: (x: number, y: number) => + reprojection.affine.applyAffine(x, y, geotransform), + inputCRSToPixel: (x: number, y: number) => + reprojection.affine.applyAffine(x, y, inverseGeotransform), + forwardReproject: (x: number, y: number) => + converter.forward([x, y], false), + inverseReproject: (x: number, y: number) => + converter.inverse([x, y], false), + }; +} + +function buildDeckMeshFromDelatin(reprojector: reprojection.RasterReprojector) { + const vertexCount = reprojector.uvs.length / 2; + + const positions = new Float32Array(vertexCount * 3); + const texCoords = new Float32Array(reprojector.uvs); + + for (let i = 0; i < vertexCount; i++) { + positions[i * 3 + 0] = reprojector.exactOutputPositions[i * 2]; + positions[i * 3 + 1] = reprojector.exactOutputPositions[i * 2 + 1]; + positions[i * 3 + 2] = 0.0; + } + + const indices = new Uint32Array(reprojector.triangles); + + return { + indices: { + value: indices, + size: 1, + }, + attributes: { + POSITION: { value: positions, size: 3 }, + TEXCOORD_0: { value: texCoords, size: 2 }, + }, + }; +} diff --git a/src/model/layer/index.ts b/src/model/layer/index.ts index 0c5c62c9..eb7525de 100644 --- a/src/model/layer/index.ts +++ b/src/model/layer/index.ts @@ -4,6 +4,7 @@ import { ArcModel } from "./arc.js"; import { BaseLayerModel } from "./base.js"; import { BitmapModel, BitmapTileModel } from "./bitmap.js"; import { ColumnModel } from "./column.js"; +import { GeotiffModel } from "./geotiff.js"; import { HeatmapModel } from "./heatmap.js"; import { PathModel } from "./path.js"; import { PointCloudModel } from "./point-cloud.js"; @@ -68,6 +69,10 @@ export async function initializeLayer( layerModel = new GeohashModel(model, updateStateCallback); break; + case GeotiffModel.layerType: + layerModel = new GeotiffModel(model, updateStateCallback); + break; + case H3HexagonModel.layerType: layerModel = new H3HexagonModel(model, updateStateCallback); break;