maplibre-gl-raster 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,9 @@ The main control class implementing MapLibre's `IControl` interface.
114
114
  | `interleaved` | `boolean` | `true` | Render the deck.gl overlay interleaved with the basemap layers |
115
115
  | `defaultUrl` | `string` | `''` | Prefills the Add data URL input (not loaded until the user clicks Load) |
116
116
  | `autoLoad` | `boolean` | `false` | Load `defaultUrl` automatically when the control is added to the map |
117
+ | `sampleData` | `RasterSampleDataset[]` | - | Sample COGs offered as a "Load sample data" dropdown above the URL input; picking one fills the input (hidden when empty) |
118
+ | `sampleDataLabel` | `string` | `'Load sample data...'` | Placeholder shown in the sample-data dropdown |
119
+ | `closeOnOutsideClick` | `boolean` | `true` | Collapse the panel when clicking outside it; set `false` to close only via the header button |
117
120
  | `engine` | `RenderEngine` | `'maplibre-gl-raster'` | Initial rendering backend; switchable at runtime from the panel |
118
121
 
119
122
  #### Raster Methods
@@ -2646,7 +2646,7 @@ var SourceMemory = class SourceMemory {
2646
2646
  //#region node_modules/@developmentseed/geotiff/dist/crs.js
2647
2647
  var PROJJSON_SCHEMA = "https://proj.org/schemas/v0.7/projjson.schema.json";
2648
2648
  var MODEL_TYPE_PROJECTED$1 = 1;
2649
- var MODEL_TYPE_GEOGRAPHIC = 2;
2649
+ var MODEL_TYPE_GEOGRAPHIC$1 = 2;
2650
2650
  var USER_DEFINED$1 = 32767;
2651
2651
  var CT_TRANSVERSE_MERCATOR = 1;
2652
2652
  var CT_TRANSVERSE_MERCATOR_SOUTH = 2;
@@ -2701,7 +2701,7 @@ var LINEAR_UNIT = {
2701
2701
  function crsFromGeoKeys(gkd) {
2702
2702
  const modelType = gkd.modelType;
2703
2703
  if (modelType === MODEL_TYPE_PROJECTED$1) return _projectedCrs(gkd);
2704
- if (modelType === MODEL_TYPE_GEOGRAPHIC) return _geographicCrs(gkd);
2704
+ if (modelType === MODEL_TYPE_GEOGRAPHIC$1) return _geographicCrs(gkd);
2705
2705
  throw new Error(`Unsupported GeoTIFF model type: ${modelType}`);
2706
2706
  }
2707
2707
  function _geographicCrs(gkd) {
@@ -4555,8 +4555,10 @@ var COLORMAP_OPTIONS = COLORMAP_NAMES.map((name) => ({
4555
4555
  //#endregion
4556
4556
  //#region src/lib/raster/repair-geokeys.ts
4557
4557
  var MODEL_TYPE_PROJECTED = 1;
4558
+ var MODEL_TYPE_GEOGRAPHIC = 2;
4558
4559
  var USER_DEFINED = 32767;
4559
4560
  var EPSG_WEB_MERCATOR = 3857;
4561
+ var WKT_ROOT = /\b(?:PROJCS|PROJCRS|GEOGCS|GEOGCRS|GEOCCS|GEODCRS|BOUNDCRS|COMPD_CS|COMPOUNDCRS|VERT_CS|VERTCRS|LOCAL_CS|FITTED_CS)\s*\[/i;
4560
4562
  var WEB_MERCATOR_ALIASES = new Set([
4561
4563
  3785,
4562
4564
  900913,
@@ -4572,6 +4574,26 @@ function citationText(gkd) {
4572
4574
  ].filter((value) => typeof value === "string").join(" ");
4573
4575
  }
4574
4576
  /**
4577
+ * Pull an embedded WKT (or ESRI PE string) definition out of a citation geo
4578
+ * key, if one is present. The projected citation is preferred because that is
4579
+ * where ArcGIS writes the full `PROJCS[...]` for an exotic projection; the
4580
+ * generic and geodetic citations are checked as fallbacks. Returns the WKT from
4581
+ * the first matching root keyword onward (dropping any `ESRI PE String = `
4582
+ * prefix), or `null` when no citation carries one.
4583
+ */
4584
+ function wktFromCitation(gkd) {
4585
+ for (const citation of [
4586
+ gkd.projectedCitation,
4587
+ gkd.citation,
4588
+ gkd.geodeticCitation
4589
+ ]) {
4590
+ if (typeof citation !== "string") continue;
4591
+ const match = WKT_ROOT.exec(citation);
4592
+ if (match) return citation.slice(match.index);
4593
+ }
4594
+ return null;
4595
+ }
4596
+ /**
4575
4597
  * Repair GeoTIFF CRS geo keys so {@link import('@developmentseed/geotiff').Overview.crs}
4576
4598
  * resolves them to the right projection, in place. Two problems are fixed:
4577
4599
  *
@@ -4598,6 +4620,15 @@ function citationText(gkd) {
4598
4620
  * untouched. Must run before any `Overview.crs` access (which caches its
4599
4621
  * result), so callers invoke it immediately after opening the GeoTIFF.
4600
4622
  *
4623
+ * 3. **A projection the geo keys cannot express.** Some projections have no
4624
+ * `ProjCoordTransGeoKey` (e.g. equal-area world CRSes such as ESRI:54009
4625
+ * World Mollweide), so ArcGIS/GDAL leave the model type user-defined and the
4626
+ * full definition only in the citation as an `ESRI PE String = PROJCS[...]`
4627
+ * WKT. `crsFromGeoKeys` then throws `Unsupported GeoTIFF model type: 32767`.
4628
+ * {@link recoverCrsFromWktCitation} seeds the cached CRS from that WKT so the
4629
+ * consuming layer (which calls `parseWkt`/proj4) builds the projection from
4630
+ * it. This is projection-agnostic: proj4 understands the ESRI/OGC names.
4631
+ *
4601
4632
  * @param tiff - The opened GeoTIFF to repair in place.
4602
4633
  */
4603
4634
  function repairUserDefinedProjectedCrs(tiff) {
@@ -4608,6 +4639,30 @@ function repairUserDefinedProjectedCrs(tiff) {
4608
4639
  else if (projectedIsUserDefined && gkd.projMethod !== null && WEB_MERCATOR_CITATION.test(citationText(gkd))) gkd.projectedCRS = EPSG_WEB_MERCATOR;
4609
4640
  if ((gkd.modelType === null || gkd.modelType === USER_DEFINED) && gkd.projMethod !== null) gkd.modelType = MODEL_TYPE_PROJECTED;
4610
4641
  }
4642
+ recoverCrsFromWktCitation(tiff);
4643
+ }
4644
+ /**
4645
+ * Seed the GeoTIFF's cached CRS from an embedded WKT citation when the geo keys
4646
+ * still cannot describe the projection after {@link repairUserDefinedProjectedCrs}
4647
+ * has run. `crsFromGeoKeys` only accepts model type 1 (projected) or 2
4648
+ * (geographic) and throws otherwise; when the model type is still user-defined
4649
+ * (no `ProjMethodGeoKey` to promote it) the only definition is the WKT in the
4650
+ * citation. Setting the (lazily computed, single-shot) `_crs` cache to that WKT
4651
+ * string makes the getter return it instead of throwing, and the consuming layer
4652
+ * resolves it through `parseWkt` (wkt-parser + proj4).
4653
+ *
4654
+ * No-ops when the model type is resolvable, when the CRS is already cached, or
4655
+ * when no citation carries a WKT (so the original, descriptive error still
4656
+ * surfaces).
4657
+ */
4658
+ function recoverCrsFromWktCitation(tiff) {
4659
+ const gkd = tiff.overviews[0]?.gkd;
4660
+ if (!gkd) return;
4661
+ if (gkd.modelType === MODEL_TYPE_PROJECTED || gkd.modelType === MODEL_TYPE_GEOGRAPHIC) return;
4662
+ const wkt = wktFromCitation(gkd);
4663
+ if (!wkt) return;
4664
+ const cache = tiff;
4665
+ if (cache._crs === void 0) cache._crs = wkt;
4611
4666
  }
4612
4667
  //#endregion
4613
4668
  //#region src/lib/raster/load-geotiff.ts
@@ -19036,6 +19091,68 @@ var AddDataSection = class {
19036
19091
  if (e.key === "Enter") submitUrl();
19037
19092
  });
19038
19093
  const urlRow = el("div", { className: "mlr-row" }, input, loadBtn);
19094
+ const samples = options.sampleData ?? [];
19095
+ let sampleRow;
19096
+ if (samples.length > 0) {
19097
+ const placeholder = options.sampleDataLabel ?? "Load sample data...";
19098
+ const triggerLabel = el("span", {
19099
+ className: "mlr-sample-trigger-label",
19100
+ text: placeholder
19101
+ });
19102
+ const trigger = el("button", {
19103
+ className: "mlr-sample-trigger",
19104
+ type: "button",
19105
+ ariaLabel: placeholder,
19106
+ attrs: {
19107
+ "aria-haspopup": "listbox",
19108
+ "aria-expanded": "false"
19109
+ }
19110
+ }, triggerLabel, el("span", {
19111
+ className: "mlr-sample-caret",
19112
+ text: "▾"
19113
+ }));
19114
+ const menu = el("div", {
19115
+ className: "mlr-sample-menu",
19116
+ attrs: { role: "listbox" }
19117
+ });
19118
+ menu.hidden = true;
19119
+ let menuOpen = false;
19120
+ const setMenuOpen = (open) => {
19121
+ menuOpen = open;
19122
+ menu.hidden = !open;
19123
+ trigger.setAttribute("aria-expanded", String(open));
19124
+ trigger.classList.toggle("open", open);
19125
+ if (open) menu.firstElementChild?.focus();
19126
+ };
19127
+ for (const sample of samples) {
19128
+ const option = el("button", {
19129
+ className: "mlr-sample-option",
19130
+ type: "button",
19131
+ text: sample.label,
19132
+ title: sample.url,
19133
+ attrs: { role: "option" }
19134
+ });
19135
+ option.addEventListener("click", () => {
19136
+ setMenuOpen(false);
19137
+ trigger.focus();
19138
+ input.value = sample.url;
19139
+ loadBtn.disabled = input.value.trim().length === 0;
19140
+ });
19141
+ menu.appendChild(option);
19142
+ }
19143
+ trigger.addEventListener("click", () => setMenuOpen(!menuOpen));
19144
+ sampleRow = el("div", { className: "mlr-sample-row" }, trigger, menu);
19145
+ sampleRow.addEventListener("keydown", (e) => {
19146
+ if (e.key === "Escape" && menuOpen) {
19147
+ setMenuOpen(false);
19148
+ trigger.focus();
19149
+ }
19150
+ });
19151
+ sampleRow.addEventListener("focusout", (e) => {
19152
+ const next = e.relatedTarget;
19153
+ if (!next || !sampleRow.contains(next)) setMenuOpen(false);
19154
+ });
19155
+ }
19039
19156
  const fileInput = el("input", {
19040
19157
  type: "file",
19041
19158
  ariaLabel: "raster-file",
@@ -19078,7 +19195,7 @@ var AddDataSection = class {
19078
19195
  this.el = el("div", { className: "mlr-section mlr-add-data" }, el("div", {
19079
19196
  className: "mlr-section-title",
19080
19197
  text: "Add data"
19081
- }), urlRow, dropZone, beforeIdInput);
19198
+ }), ...sampleRow ? [sampleRow] : [], urlRow, dropZone, beforeIdInput);
19082
19199
  }
19083
19200
  };
19084
19201
  //#endregion
@@ -20030,6 +20147,8 @@ var PanelUI = class {
20030
20147
  this._engine = engine;
20031
20148
  const addData = new AddDataSection({
20032
20149
  initialUrl: options?.defaultUrl,
20150
+ sampleData: options?.sampleData,
20151
+ sampleDataLabel: options?.sampleDataLabel,
20033
20152
  onAddUrl: (url, beforeId) => {
20034
20153
  this._manager.addRaster(url, { beforeId }).catch(() => {});
20035
20154
  },
@@ -20307,6 +20426,9 @@ var DEFAULT_OPTIONS = {
20307
20426
  interleaved: true,
20308
20427
  defaultUrl: "",
20309
20428
  autoLoad: false,
20429
+ sampleData: [],
20430
+ sampleDataLabel: "Load sample data...",
20431
+ closeOnOutsideClick: true,
20310
20432
  engine: "maplibre-gl-raster",
20311
20433
  epsgResolver: createResilientEpsgResolver()
20312
20434
  };
@@ -20394,6 +20516,8 @@ var RasterControl = class {
20394
20516
  const autoLoading = this._options.autoLoad && !!this._options.defaultUrl;
20395
20517
  if (content) this._panelUI = new PanelUI(content, this._layerManager, {
20396
20518
  defaultUrl: autoLoading ? "" : this._options.defaultUrl,
20519
+ sampleData: this._options.sampleData,
20520
+ sampleDataLabel: this._options.sampleDataLabel,
20397
20521
  inspect: {
20398
20522
  onToggle: () => this._inspector?.toggle(),
20399
20523
  isActive: () => this._inspector?.enabled ?? false
@@ -20912,13 +21036,15 @@ var RasterControl = class {
20912
21036
  * Setup event listeners for panel positioning and click-outside behavior.
20913
21037
  */
20914
21038
  _setupEventListeners() {
20915
- this._clickOutsideHandler = (e) => {
20916
- const target = e.target;
20917
- if (!target.isConnected) return;
20918
- if (this._inspector?.enabled) return;
20919
- if (this._container && this._panel && !this._container.contains(target) && !this._panel.contains(target)) this.collapse();
20920
- };
20921
- document.addEventListener("click", this._clickOutsideHandler);
21039
+ if (this._options.closeOnOutsideClick !== false) {
21040
+ this._clickOutsideHandler = (e) => {
21041
+ const target = e.target;
21042
+ if (!target.isConnected) return;
21043
+ if (this._inspector?.enabled) return;
21044
+ if (this._container && this._panel && !this._container.contains(target) && !this._panel.contains(target)) this.collapse();
21045
+ };
21046
+ document.addEventListener("click", this._clickOutsideHandler);
21047
+ }
20922
21048
  this._resizeHandler = () => {
20923
21049
  if (!this._state.collapsed) this._updatePanelPosition();
20924
21050
  };
@@ -20991,4 +21117,4 @@ var RasterControl = class {
20991
21117
  //#endregion
20992
21118
  export { MAX_SAMPLE_TILES as C, createResilientEpsgResolver as D, readBandNames as E, statsForBand as S, percentileFromHistogram as T, COLORMAP_OPTIONS as _, isKnownColormap as a, colormaps_default as b, clamp as c, formatNumericValue as d, generateId as f, COLORMAP_NAMES as g, COLORMAP_DISPLAY_NAMES as h, PALETTE_COLORMAP as i, classNames as l, loadGeoTIFF as m, Colorbar as n, loadColormapSprite as o, throttle as p, readPixelValues as r, sampleColormapStops as s, RasterControl as t, debounce as u, COLORMAP_ROW_COUNT as v, computeAutoStats as w, autoRangeFor as x, colormapDisplayName as y };
20993
21119
 
20994
- //# sourceMappingURL=RasterControl-BRFVTd2x.js.map
21120
+ //# sourceMappingURL=RasterControl-CjnDWQDU.js.map