@teamimpact/veda-ui-blocks 0.1.0-beta.11 → 0.1.0-beta.13

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/dist/default.css CHANGED
@@ -47784,7 +47784,7 @@ example:
47784
47784
  .blocks-card-detailed__description {
47785
47785
  color: #1b1b1b;
47786
47786
  font-family: Public Sans Web, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
47787
- font-size: 0.75rem;
47787
+ font-size: 1rem;
47788
47788
  font-style: normal;
47789
47789
  font-weight: normal;
47790
47790
  line-height: 1.6;
@@ -48681,7 +48681,7 @@ example:
48681
48681
  .blocks-card-detailed__description {
48682
48682
  color: #0d313d;
48683
48683
  font-family: Inter Variable, system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
48684
- font-size: 0.75rem;
48684
+ font-size: 1rem;
48685
48685
  font-style: normal;
48686
48686
  font-weight: normal;
48687
48687
  line-height: 1.6;
package/dist/index.d.ts CHANGED
@@ -63,11 +63,13 @@ type CardProps = {
63
63
  callToAction?: {
64
64
  label: string;
65
65
  href: string;
66
+ isExternal?: boolean;
66
67
  };
67
68
  /** Optional: Secondary outlined call to action for the card. */
68
69
  callToActionSecondary?: {
69
70
  label: string;
70
71
  href: string;
72
+ isExternal?: boolean;
71
73
  };
72
74
  /** Required: media element rendered as either the card background or
73
75
  * an image within the card.
@@ -122,11 +124,13 @@ type CardDetailedProps = {
122
124
  callToAction?: {
123
125
  label: string;
124
126
  href: string;
127
+ isExternal?: boolean;
125
128
  };
126
129
  /** Optional: Secondary outlined call to action for the card. */
127
130
  callToActionSecondary?: {
128
131
  label: string;
129
132
  href: string;
133
+ isExternal?: boolean;
130
134
  };
131
135
  /** Required: media element rendered as either the card background or
132
136
  * an image within the card.
@@ -318,9 +322,6 @@ type TagProps = {
318
322
  */
319
323
  declare function Tag({ variant, size, color, bgColor, borderColor, textColor, icon, onClose, children, className, }: TagProps): react_jsx_runtime.JSX.Element;
320
324
 
321
- /** NASA Blue Marble imagery basemap */
322
- declare const NASA_BLUE_MARBLE_BASEMAP_STYLE: StyleSpecification;
323
-
324
325
  interface GeoConfigProviderProps {
325
326
  stacApiUrl?: string;
326
327
  titilerBaseUrl?: string;
@@ -337,20 +338,22 @@ type TileMatrixSet = "WebMercatorQuad" | "GDALWebMercatorQuad" | "WorldCRS84Quad
337
338
  type StacRasterLayerConfig = {
338
339
  type: "raster";
339
340
  collectionId: string;
341
+ collectionAssetId: string;
340
342
  dateRange: DateRange;
341
343
  tileMatrixSet?: TileMatrixSet;
344
+ hideLegend?: boolean;
342
345
  };
343
346
  interface GradientLegendProps {
344
347
  type: "gradient";
345
- colorStops: string[];
348
+ colorStops?: string[];
346
349
  title?: string;
347
350
  description?: string;
348
351
  provider?: string;
349
352
  spatialExtent?: string;
350
353
  timeDensity?: string;
351
354
  unit?: string;
352
- min: number;
353
- max: number;
355
+ min?: number;
356
+ max?: number;
354
357
  }
355
358
  type LegendProps = GradientLegendProps & {
356
359
  initialExpanded?: boolean;
@@ -358,9 +361,32 @@ type LegendProps = GradientLegendProps & {
358
361
 
359
362
  declare function Legend({ initialExpanded, ...props }: LegendProps): react_jsx_runtime.JSX.Element;
360
363
 
364
+ /** NASA Blue Marble imagery basemap */
365
+ declare const NASA_BLUE_MARBLE_BASEMAP_STYLE: StyleSpecification;
366
+ /** Carto Dark Matter raster basemap without labels */
367
+ declare const CARTO_DARK_BASEMAP_STYLE: StyleSpecification;
368
+ /**
369
+ * Carto Dark Matter vector style with labels.
370
+ * Data layers should render beneath `waterway_label`.
371
+ */
372
+ declare const CARTO_DARK_WITH_LABELS_BASEMAP_STYLE = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json";
373
+ declare const CARTO_LABELS_LAYER_ID = "waterway_label";
374
+ /**
375
+ * OpenFreeMap Dark vector style.
376
+ * Data layers should render beneath `highway_name_other`.
377
+ */
378
+ declare const OPEN_FREE_MAP_DARK_BASEMAP_STYLE = "https://tiles.openfreemap.org/styles/dark";
379
+ declare const OPEN_FREE_MAP_LABELS_LAYER_ID = "highway_name_other";
380
+ /**
381
+ * Maps vector basemap styles to the first label layer that should render
382
+ * above data overlays.
383
+ */
384
+ declare const BASEMAP_LABEL_LAYER_IDS: Record<string, string>;
385
+ type BaseMaps = keyof typeof BASEMAP_LABEL_LAYER_IDS;
386
+
361
387
  type StacCompareMapProps = {
362
- /** MapLibre base map style URL or style object. */
363
- baseMapStyle?: string | StyleSpecification;
388
+ /** MapLibre base map style URL or style object. Defaults to Carto Dark with labels. For known vector styles the label layer is derived automatically so STAC data renders beneath labels. */
389
+ baseMapStyle?: BaseMaps | StyleSpecification;
364
390
  /** Initial map center (longitude/latitude), zoom level, and optional bearing/pitch. MapLibre applies defaults for any properties not provided. */
365
391
  initialViewState?: Partial<ViewState>;
366
392
  /** Layer config for the left panel. */
@@ -394,8 +420,8 @@ type StacCompareMapProps = {
394
420
  declare function StacCompareMap({ baseMapStyle, initialViewState, leftLayerConfig, rightLayerConfig, className, }: StacCompareMapProps): react_jsx_runtime.JSX.Element;
395
421
 
396
422
  type StacSingleLayerMapProps = {
397
- /** MapLibre base map style URL or style object. Defaults to NASA Blue Marble imagery. */
398
- baseMapStyle?: string | StyleSpecification;
423
+ /** MapLibre base map style URL or style object. Defaults to Carto Dark with labels. For known vector styles the label layer is derived automatically so STAC data renders beneath labels. */
424
+ baseMapStyle?: BaseMaps | StyleSpecification;
399
425
  /** Initial map center (longitude/latitude), zoom level, and optional bearing/pitch. MapLibre applies defaults for any properties not provided. */
400
426
  initialViewState?: Partial<ViewState>;
401
427
  /** STAC collection and date range to fetch and display as a raster tile layer. */
@@ -420,4 +446,4 @@ type StacSingleLayerMapProps = {
420
446
  */
421
447
  declare function StacSingleLayerMap({ baseMapStyle, initialViewState, layerConfig, }: StacSingleLayerMapProps): react_jsx_runtime.JSX.Element;
422
448
 
423
- export { Banner, type BannerProps, Card, CardCTA, type CardCTAProps, CardDetailed, type CardDetailedProps, CardMini, type CardMiniProps, type CardProps, CardSimple, type CardSimpleProps, Footer, type FooterProps, GeoConfigProvider, type GeoConfigProviderProps, Header, type HeaderProps, Legend, type LegendProps, Link, type LinkProps, NASA_BLUE_MARBLE_BASEMAP_STYLE, StacCompareMap, type StacCompareMapProps, StacSingleLayerMap, type StacSingleLayerMapProps, Tag, type TagProps };
449
+ export { Banner, type BannerProps, CARTO_DARK_BASEMAP_STYLE, CARTO_DARK_WITH_LABELS_BASEMAP_STYLE, CARTO_LABELS_LAYER_ID, Card, CardCTA, type CardCTAProps, CardDetailed, type CardDetailedProps, CardMini, type CardMiniProps, type CardProps, CardSimple, type CardSimpleProps, Footer, type FooterProps, GeoConfigProvider, type GeoConfigProviderProps, Header, type HeaderProps, Legend, type LegendProps, Link, type LinkProps, NASA_BLUE_MARBLE_BASEMAP_STYLE, OPEN_FREE_MAP_DARK_BASEMAP_STYLE, OPEN_FREE_MAP_LABELS_LAYER_ID, StacCompareMap, type StacCompareMapProps, StacSingleLayerMap, type StacSingleLayerMapProps, Tag, type TagProps };
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var isArray = (as) => Array.isArray(as);
26
26
  var isEmptyArray = (as) => isArray(as) && as.length === 0;
27
27
  var isNonEmptyArray = (as) => isArray(as) && as.length > 0;
28
28
  var getTypedValues = Object.values;
29
+ var getTypedEntries = Object.entries;
29
30
 
30
31
  // src/utils/component-utils.tsx
31
32
  var get_external_anchor_props = (isExternal) => isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {};
@@ -103,6 +104,7 @@ var DEFAULT_EXPANDED_CONTENT_JSX = /* @__PURE__ */ jsxs("div", { className: "blo
103
104
  ] });
104
105
 
105
106
  // src/components/Cards/Card/Card.tsx
107
+ import { Icon as Icon2 } from "@trussworks/react-uswds";
106
108
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
107
109
  function Card({
108
110
  tag,
@@ -135,8 +137,30 @@ function Card({
135
137
  subtitle && /* @__PURE__ */ jsx2("div", { className: "blocks-card__subtitle", children: subtitle }),
136
138
  description && /* @__PURE__ */ jsx2("p", { className: "blocks-card__description", children: description }),
137
139
  (callToAction || callToActionSecondary) && /* @__PURE__ */ jsxs2("div", { className: "blocks-card__actions", children: [
138
- callToAction && /* @__PURE__ */ jsx2("a", { href: callToAction.href, className: "usa-button", children: callToAction.label }),
139
- callToActionSecondary && /* @__PURE__ */ jsx2("a", { href: callToActionSecondary.href, className: "usa-button usa-button--outline", children: callToActionSecondary.label })
140
+ callToAction && /* @__PURE__ */ jsxs2(
141
+ "a",
142
+ {
143
+ href: callToAction.href,
144
+ className: "usa-button",
145
+ ...get_external_anchor_props(callToAction.isExternal),
146
+ children: [
147
+ callToAction.label,
148
+ callToAction.isExternal && /* @__PURE__ */ jsx2(Icon2.Launch, { "aria-hidden": true, focusable: false, size: 3 })
149
+ ]
150
+ }
151
+ ),
152
+ callToActionSecondary && /* @__PURE__ */ jsxs2(
153
+ "a",
154
+ {
155
+ href: callToActionSecondary.href,
156
+ className: "usa-button usa-button--outline",
157
+ ...get_external_anchor_props(callToActionSecondary.isExternal),
158
+ children: [
159
+ callToActionSecondary.label,
160
+ callToActionSecondary.isExternal && /* @__PURE__ */ jsx2(Icon2.Launch, { "aria-hidden": true, focusable: false, size: 3 })
161
+ ]
162
+ }
163
+ )
140
164
  ] })
141
165
  ] })
142
166
  ] }),
@@ -198,6 +222,7 @@ function CardCTA({
198
222
  }
199
223
 
200
224
  // src/components/Cards/CardDetailed/CardDetailed.tsx
225
+ import { Icon as Icon3 } from "@trussworks/react-uswds";
201
226
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
202
227
  function CardDetailed({
203
228
  intro,
@@ -227,8 +252,30 @@ function CardDetailed({
227
252
  isNonEmptyArray(tags) && /* @__PURE__ */ jsx5("div", { className: "blocks-card-detailed__tags", children: tags }),
228
253
  description && /* @__PURE__ */ jsx5("p", { className: "blocks-card-detailed__description", children: description }),
229
254
  (callToAction || callToActionSecondary) && /* @__PURE__ */ jsxs5("div", { className: "blocks-card-detailed__actions", children: [
230
- callToAction && /* @__PURE__ */ jsx5("a", { href: callToAction.href, className: "usa-button", children: callToAction.label }),
231
- callToActionSecondary && /* @__PURE__ */ jsx5("a", { href: callToActionSecondary.href, className: "usa-button usa-button--outline", children: callToActionSecondary.label })
255
+ callToAction && /* @__PURE__ */ jsxs5(
256
+ "a",
257
+ {
258
+ href: callToAction.href,
259
+ className: "usa-button",
260
+ ...get_external_anchor_props(callToAction.isExternal),
261
+ children: [
262
+ callToAction.label,
263
+ callToAction.isExternal && /* @__PURE__ */ jsx5(Icon3.Launch, { "aria-hidden": true, focusable: false, size: 3 })
264
+ ]
265
+ }
266
+ ),
267
+ callToActionSecondary && /* @__PURE__ */ jsxs5(
268
+ "a",
269
+ {
270
+ href: callToActionSecondary.href,
271
+ className: "usa-button usa-button--outline",
272
+ ...get_external_anchor_props(callToActionSecondary.isExternal),
273
+ children: [
274
+ callToActionSecondary.label,
275
+ callToActionSecondary.isExternal && /* @__PURE__ */ jsx5(Icon3.Launch, { "aria-hidden": true, focusable: false, size: 3 })
276
+ ]
277
+ }
278
+ )
232
279
  ] })
233
280
  ] })
234
281
  ] });
@@ -285,7 +332,7 @@ function CardSimple({
285
332
  }
286
333
 
287
334
  // src/components/Footer/Footer.tsx
288
- import { GridContainer, Icon as Icon2, Link, Footer as USWDSFooter } from "@trussworks/react-uswds";
335
+ import { GridContainer, Icon as Icon4, Link, Footer as USWDSFooter } from "@trussworks/react-uswds";
289
336
  import { Fragment, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
290
337
  function Footer({
291
338
  portalDetails,
@@ -341,7 +388,7 @@ function Footer({
341
388
  portalDetails.contacts?.length ? /* @__PURE__ */ jsx8("nav", { className: "blocks-footer__contacts-nav", "aria-label": "Site contacts", children: /* @__PURE__ */ jsx8("ul", { children: portalDetails.contacts.map(({ label, name, email }) => /* @__PURE__ */ jsxs8("li", { children: [
342
389
  /* @__PURE__ */ jsx8("span", { children: label }),
343
390
  /* @__PURE__ */ jsxs8(Link, { href: `mailto:${email}`, ...get_external_anchor_props(), children: [
344
- /* @__PURE__ */ jsx8(Icon2.Mail, { "aria-hidden": "true", focusable: false }),
391
+ /* @__PURE__ */ jsx8(Icon4.Mail, { "aria-hidden": "true", focusable: false }),
345
392
  /* @__PURE__ */ jsx8("span", { children: name })
346
393
  ] })
347
394
  ] }, `${label}-${email}`)) }) }) : null
@@ -737,43 +784,17 @@ function Tag({
737
784
  ] });
738
785
  }
739
786
 
787
+ // src/geo/GeoConfigProvider.tsx
788
+ import { StacApiProvider } from "@developmentseed/stac-react";
789
+ import { createContext, useContext } from "react";
790
+
740
791
  // src/geo/constants.ts
741
- var NASA_BLUE_MARBLE_BASEMAP_STYLE = {
742
- version: 8,
743
- sources: {
744
- "nasa-blue-marble": {
745
- type: "raster",
746
- tiles: [
747
- "https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/BlueMarble_NextGeneration/default/GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpeg"
748
- ],
749
- tileSize: 256,
750
- maxzoom: 8,
751
- attribution: "Imagery courtesy NASA EOSDIS/GIBS"
752
- }
753
- },
754
- layers: [
755
- {
756
- id: "nasa-blue-marble",
757
- type: "raster",
758
- source: "nasa-blue-marble"
759
- }
760
- ]
761
- };
762
792
  var STAC_API_URL = "https://openveda.cloud/api/stac";
763
793
  var TITILER_BASE_URL = "https://openveda.cloud/api/raster";
764
- var DEFAULT_RENDER_PARAMS = {
765
- assets: ["cog_default"],
766
- rescale: "0,0.0001",
767
- resampling: "bilinear",
768
- colormap_name: "viridis",
769
- bidx: [1]
770
- };
771
794
  var DEFAULT_TILE_MATRIX_SET = "WebMercatorQuad";
772
795
  var STAC_COLORSTOP_RGBA_ARG_COUNT = 4;
773
796
 
774
797
  // src/geo/GeoConfigProvider.tsx
775
- import { StacApiProvider } from "@developmentseed/stac-react";
776
- import { createContext, useContext } from "react";
777
798
  import { jsx as jsx14 } from "react/jsx-runtime";
778
799
  var GeoConfigContext = createContext(null);
779
800
  function useGeoConfig() {
@@ -790,9 +811,9 @@ function GeoConfigProvider({
790
811
  }
791
812
 
792
813
  // src/geo/Legend/Legend.tsx
793
- import { Icon as Icon3 } from "@trussworks/react-uswds";
814
+ import { Icon as Icon5 } from "@trussworks/react-uswds";
794
815
  import { useState as useState3 } from "react";
795
- import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
816
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
796
817
  function Legend({ initialExpanded = false, ...props }) {
797
818
  const { title, description, unit, min, max, colorStops, provider, spatialExtent, timeDensity } = props;
798
819
  const [isExpanded, setIsExpanded] = useState3(initialExpanded);
@@ -811,23 +832,25 @@ function Legend({ initialExpanded = false, ...props }) {
811
832
  "aria-expanded": isExpanded,
812
833
  "aria-label": "Toggle layer description",
813
834
  onClick: () => setIsExpanded((v) => !v),
814
- children: /* @__PURE__ */ jsx15(Icon3.InfoOutline, { "aria-hidden": true, focusable: false, size: 3 })
835
+ children: /* @__PURE__ */ jsx15(Icon5.InfoOutline, { "aria-hidden": true, focusable: false, size: 3 })
815
836
  }
816
837
  )
817
838
  ] }),
818
- /* @__PURE__ */ jsx15("div", { className: "blocks-legend__gradient-bar", children: /* @__PURE__ */ jsx15(
819
- "div",
820
- {
821
- className: "blocks-legend__gradient",
822
- style: {
823
- background: `linear-gradient(to right, ${colorStops.join(", ")})`
839
+ colorStops && /* @__PURE__ */ jsxs13(Fragment3, { children: [
840
+ /* @__PURE__ */ jsx15("div", { className: "blocks-legend__gradient-bar", children: /* @__PURE__ */ jsx15(
841
+ "div",
842
+ {
843
+ className: "blocks-legend__gradient",
844
+ style: {
845
+ background: `linear-gradient(to right, ${colorStops.join(", ")})`
846
+ }
824
847
  }
825
- }
826
- ) }),
827
- /* @__PURE__ */ jsxs13("div", { className: "blocks-legend__labels", children: [
828
- /* @__PURE__ */ jsx15("span", { className: "blocks-legend__min", children: min }),
829
- /* @__PURE__ */ jsx15("span", { className: "blocks-legend__unit", children: unit }),
830
- /* @__PURE__ */ jsx15("span", { className: "blocks-legend__max", children: max })
848
+ ) }),
849
+ isDefined(min) && isDefined(max) && /* @__PURE__ */ jsxs13("div", { className: "blocks-legend__labels", children: [
850
+ /* @__PURE__ */ jsx15("span", { className: "blocks-legend__min", children: min }),
851
+ /* @__PURE__ */ jsx15("span", { className: "blocks-legend__unit", children: unit }),
852
+ /* @__PURE__ */ jsx15("span", { className: "blocks-legend__max", children: max })
853
+ ] })
831
854
  ] }),
832
855
  isExpanded && description && /* @__PURE__ */ jsx15("div", { className: "blocks-legend__description", children: /* @__PURE__ */ jsx15("div", { className: "blocks-legend__description-inner", children: description }) })
833
856
  ] });
@@ -850,7 +873,17 @@ function AttributionMapControl({ position = "top-right" }) {
850
873
  return null;
851
874
  }
852
875
 
853
- // src/geo/utils/formatDateRange.ts
876
+ // src/geo/utils/stac.formatters.ts
877
+ var formatSpatialExtent = (spatialExtent) => {
878
+ const [minX, minY, maxX, maxY] = spatialExtent?.bbox?.[0] ?? [];
879
+ if (minX === void 0 || minY === void 0 || maxX === void 0 || maxY === void 0)
880
+ return void 0;
881
+ return maxX - minX >= 359 && maxY - minY >= 179 ? "global" : "regional";
882
+ };
883
+ function formatDateToISODatetime(date, isEndOfDay) {
884
+ const datetime = /* @__PURE__ */ new Date(`${date}${isEndOfDay ? "T23:59:59Z" : "T00:00:00Z"}`);
885
+ return datetime.toISOString();
886
+ }
854
887
  function formatDateRange(dateRange) {
855
888
  const from = /* @__PURE__ */ new Date(`${dateRange.from}T00:00:00Z`);
856
889
  const to = /* @__PURE__ */ new Date(`${dateRange.to}T00:00:00Z`);
@@ -875,6 +908,18 @@ function formatDateRange(dateRange) {
875
908
  });
876
909
  return `${fromStr} \u2013 ${toStr}`;
877
910
  }
911
+ var formatRenderPropertiesToQuery = (renderProperties) => {
912
+ const query = new URLSearchParams();
913
+ getTypedEntries(renderProperties).forEach(([key, value]) => {
914
+ if (!isDefined(value)) return;
915
+ if (isArray(value)) {
916
+ value.map((v) => query.append(key, String(v)));
917
+ } else {
918
+ query.append(key, String(value));
919
+ }
920
+ });
921
+ return query.toString();
922
+ };
878
923
 
879
924
  // src/geo/DateChip/DateChip.tsx
880
925
  import { jsx as jsx16 } from "react/jsx-runtime";
@@ -886,43 +931,11 @@ function DateChip({ left, right }) {
886
931
  // src/geo/hooks/useStacRasterLayer.ts
887
932
  import { useCollection } from "@developmentseed/stac-react";
888
933
 
889
- // src/geo/hooks/useMosaicTiles.ts
934
+ // src/geo/hooks/useRasterTiles.ts
890
935
  import { useQuery } from "@tanstack/react-query";
891
936
 
892
- // src/geo/api/titiler/fetchTilejson.ts
893
- async function fetchTilejson(tilejsonUrl, queryString) {
894
- const response = await fetch(`${tilejsonUrl}?${queryString}`);
895
- if (!response.ok) {
896
- const error = { detail: `Failed to fetch tilejson: ${response.statusText}` };
897
- throw error;
898
- }
899
- const tilejson = await response.json();
900
- if (!tilejson.tiles?.[0]) {
901
- const error = { detail: "No tile URL found in response" };
902
- throw error;
903
- }
904
- return tilejson.tiles[0];
905
- }
906
-
907
- // src/geo/api/titiler/mosaicLinks.ts
908
- function findTileLink(links) {
909
- return links.find((link) => link.rel === "tiles") ?? links.at(1) ?? links.at(0) ?? null;
910
- }
911
- function buildTilejsonUrl(tileLinkHref, tileMatrixSet) {
912
- return tileLinkHref.replace("/{tileMatrixSetId}", `/${tileMatrixSet}`);
913
- }
914
- function extractTileUrl(registrationResponse, tileMatrixSet) {
915
- const tileLink = findTileLink(registrationResponse.links);
916
- if (!tileLink?.href) {
917
- const error = { detail: "No tile URL found in registration response" };
918
- throw error;
919
- }
920
- const tilejsonUrl = buildTilejsonUrl(tileLink.href, tileMatrixSet);
921
- return tilejsonUrl;
922
- }
923
-
924
- // src/geo/api/titiler/registerMosaic.ts
925
- async function registerMosaic(request) {
937
+ // src/geo/api/titiler/rasterSearchRegister.ts
938
+ async function rasterSearchRegister(request) {
926
939
  const { rasterApiUrl, collectionId, datetime } = request;
927
940
  const response = await fetch(`${rasterApiUrl}/searches/register`, {
928
941
  method: "POST",
@@ -939,93 +952,70 @@ async function registerMosaic(request) {
939
952
  };
940
953
  throw error;
941
954
  }
942
- return response.json();
955
+ const responseJson = await response.json();
956
+ return responseJson;
943
957
  }
944
958
 
945
- // src/geo/api/titiler/fetchMosaicTileUrl.ts
946
- var TILE_PARAMS = {
947
- ASSETS: "assets",
948
- RESCALE: "rescale",
949
- RESAMPLING: "resampling",
950
- COLORMAP_NAME: "colormap_name",
951
- BIDX: "bidx"
952
- };
953
- function buildQueryParams(sourceParams) {
954
- const params = new URLSearchParams();
955
- if (sourceParams.assets) params.append(TILE_PARAMS.ASSETS, sourceParams.assets.join(","));
956
- if (sourceParams.rescale) params.append(TILE_PARAMS.RESCALE, sourceParams.rescale);
957
- if (sourceParams.resampling) params.append(TILE_PARAMS.RESAMPLING, sourceParams.resampling);
958
- if (sourceParams.colormap_name)
959
- params.append(TILE_PARAMS.COLORMAP_NAME, sourceParams.colormap_name);
960
- if (sourceParams.bidx) params.append(TILE_PARAMS.BIDX, sourceParams.bidx.join(","));
961
- return params;
962
- }
963
- async function fetchMosaicTileUrl(request) {
964
- const { sourceParams, tileMatrixSet } = request;
965
- const queryParams = buildQueryParams(sourceParams);
966
- const queryString = queryParams.toString();
967
- const registrationResponse = await registerMosaic(request);
968
- const tilejsonUrl = extractTileUrl(registrationResponse, tileMatrixSet);
969
- return fetchTilejson(tilejsonUrl, queryString);
959
+ // src/geo/api/titiler/rasterSearchTileJson.ts
960
+ async function rasterSearchTileJson({
961
+ rasterApiUrl,
962
+ searchId,
963
+ renderProperties,
964
+ tileMatrixSet
965
+ }) {
966
+ const tilejsonUrl = `${rasterApiUrl}/searches/${searchId}/${tileMatrixSet}/tilejson.json`;
967
+ const queryString = formatRenderPropertiesToQuery(renderProperties);
968
+ const response = await fetch(`${tilejsonUrl}?${queryString}`);
969
+ if (!response.ok) {
970
+ const error = { detail: `Failed to fetch tilejson: ${response.statusText}` };
971
+ throw error;
972
+ }
973
+ const responseJson = await response.json();
974
+ return responseJson;
970
975
  }
971
976
 
972
- // src/geo/utils/resolveAssetName.ts
973
- function resolveAssetName(collection) {
974
- if (collection.renders?.dashboard?.assets?.[0]) {
975
- return collection.renders.dashboard.assets[0];
976
- }
977
- if (collection.item_assets?.cog_default) {
978
- return "cog_default";
979
- }
980
- if (collection.item_assets) {
981
- const firstKey = Object.keys(collection.item_assets)[0];
982
- if (firstKey) return firstKey;
983
- }
984
- return "cog_default";
977
+ // src/geo/api/titiler/fetchRasterTileUrl.ts
978
+ async function fetchRasterTileUrl(request) {
979
+ const { collectionId, datetime, rasterApiUrl, renderProperties, tileMatrixSet } = request;
980
+ const { id: searchId } = await rasterSearchRegister({ collectionId, datetime, rasterApiUrl });
981
+ const { tiles } = await rasterSearchTileJson({
982
+ rasterApiUrl,
983
+ searchId,
984
+ renderProperties,
985
+ tileMatrixSet
986
+ });
987
+ return tiles[0];
985
988
  }
986
989
 
987
- // src/geo/hooks/useMosaicTiles.ts
988
- function useMosaicTiles(rasterLayerConfig, collection) {
990
+ // src/geo/hooks/useRasterTiles.ts
991
+ function useRasterTiles(rasterLayerConfig, collection) {
989
992
  const { titilerBaseUrl } = useGeoConfig();
990
- const { collectionId, dateRange, tileMatrixSet = DEFAULT_TILE_MATRIX_SET } = rasterLayerConfig;
991
- const datetime = dateRange.from === dateRange.to ? dateRange.from : `${dateRange.from}/${dateRange.to}`;
992
- const assetName = collection ? resolveAssetName(collection) : DEFAULT_RENDER_PARAMS.assets[0];
993
- const colormapName = collection?.renders?.dashboard?.colormap_name ?? DEFAULT_RENDER_PARAMS.colormap_name;
994
- const rescale = collection?.renders?.dashboard?.rescale?.join(",") ?? DEFAULT_RENDER_PARAMS.rescale;
995
- const resampling = collection?.renders?.dashboard?.resampling ?? DEFAULT_RENDER_PARAMS.resampling;
996
- const request = {
997
- rasterApiUrl: titilerBaseUrl,
993
+ const {
998
994
  collectionId,
999
- datetime,
1000
- sourceParams: {
1001
- ...DEFAULT_RENDER_PARAMS,
1002
- assets: [assetName],
1003
- colormap_name: colormapName,
1004
- rescale,
1005
- resampling
1006
- },
1007
- tileMatrixSet
1008
- };
995
+ collectionAssetId,
996
+ dateRange,
997
+ tileMatrixSet = DEFAULT_TILE_MATRIX_SET
998
+ } = rasterLayerConfig;
999
+ const datetime = `${formatDateToISODatetime(dateRange.from)}/${formatDateToISODatetime(dateRange.to, true)}`;
1000
+ const renderProperties = collection?.renders?.[collectionAssetId] ?? {};
1009
1001
  return useQuery({
1010
- queryKey: [collectionId, dateRange, titilerBaseUrl, assetName],
1011
- queryFn: () => fetchMosaicTileUrl(request),
1002
+ queryKey: [collectionId, collectionAssetId, datetime, titilerBaseUrl],
1003
+ queryFn: () => fetchRasterTileUrl({
1004
+ rasterApiUrl: titilerBaseUrl,
1005
+ collectionId,
1006
+ datetime,
1007
+ renderProperties,
1008
+ tileMatrixSet
1009
+ }),
1012
1010
  staleTime: 30 * 60 * 1e3,
1013
1011
  gcTime: 60 * 60 * 1e3,
1014
- enabled: !!collectionId && !!dateRange?.from && !!dateRange?.to
1012
+ enabled: isDefined(collection) && !isEmptyObject(collection) && !isEmptyObject(renderProperties)
1015
1013
  });
1016
1014
  }
1017
1015
 
1018
1016
  // src/geo/hooks/useStacRasterLegend.ts
1019
1017
  import { useQuery as useQuery2 } from "@tanstack/react-query";
1020
1018
 
1021
- // src/geo/utils/stac.formatters.ts
1022
- var formatSpatialExtent = (spatialExtent) => {
1023
- const [minX, minY, maxX, maxY] = spatialExtent?.bbox?.[0] ?? [];
1024
- if (minX === void 0 || minY === void 0 || maxX === void 0 || maxY === void 0)
1025
- return void 0;
1026
- return maxX - minX >= 359 && maxY - minY >= 179 ? "global" : "regional";
1027
- };
1028
-
1029
1019
  // src/geo/utils/stac.typeguards.ts
1030
1020
  var isRgbaColorStopMap = (value) => {
1031
1021
  return !isNil(value) && isObject(value) && !isEmptyObject(value) && getTypedValues(value).every(
@@ -1034,53 +1024,58 @@ var isRgbaColorStopMap = (value) => {
1034
1024
  };
1035
1025
 
1036
1026
  // src/geo/utils/extractLegendProps.ts
1037
- function extractLegendProps(collection, colorStopMap) {
1038
- const rescale = collection.renders?.dashboard?.rescale?.[0];
1039
- if (!rescale) return null;
1040
- if (!isRgbaColorStopMap(colorStopMap)) return null;
1041
- const colorStops = getTypedValues(colorStopMap).map((rgba) => `rgba(${rgba.join(",")})`);
1027
+ function extractLegendProps(collection, collectionAssetId, colorStopMap) {
1028
+ const rescale = collection.renders?.[collectionAssetId]?.rescale?.[0];
1029
+ let colorStops;
1030
+ if (isRgbaColorStopMap(colorStopMap)) {
1031
+ colorStops = getTypedValues(colorStopMap).map((rgba) => `rgba(${rgba.join(",")})`);
1032
+ }
1042
1033
  const title = collection.title;
1043
1034
  const description = collection.description;
1044
1035
  const provider = collection.providers?.[0]?.name;
1045
1036
  const timeDensity = collection.properties?.["dashboard:time_density"];
1046
1037
  const spatialExtent = formatSpatialExtent(collection.extent?.spatial);
1047
- const assetName = resolveAssetName(collection);
1048
- const unit = collection.item_assets?.[assetName]?.["raster:bands"]?.[0]?.unit;
1038
+ const unit = collection.item_assets?.[collectionAssetId]?.["raster:bands"]?.[0]?.unit;
1049
1039
  return {
1050
1040
  type: "gradient",
1051
- ...title && { title },
1052
- ...description && { description },
1053
- min: rescale[0],
1054
- max: rescale[1],
1055
- colorStops,
1056
- ...provider && { provider },
1057
- ...timeDensity && { timeDensity },
1058
- ...spatialExtent && { spatialExtent },
1059
- ...unit && { unit }
1041
+ ...{ title },
1042
+ ...{ description },
1043
+ // TODO 271: existing datasets for use do not include rescale, are we certain this is required
1044
+ // is there another way to determine min and max
1045
+ ...isDefined(rescale) ? { min: rescale[0] } : {},
1046
+ ...isDefined(rescale) ? { max: rescale[1] } : {},
1047
+ ...{ colorStops },
1048
+ ...{ provider },
1049
+ ...{ timeDensity },
1050
+ ...{ spatialExtent },
1051
+ ...{ unit }
1060
1052
  };
1061
1053
  }
1062
1054
 
1063
1055
  // src/geo/hooks/useStacRasterLegend.ts
1064
- function useStacRasterLegend(collection) {
1056
+ function useStacRasterLegend(collectionAssetId, collection, options) {
1065
1057
  const { titilerBaseUrl } = useGeoConfig();
1058
+ const { hideLegend } = options ?? {};
1066
1059
  const query = useQuery2({
1067
- queryKey: ["legend", collection, titilerBaseUrl],
1060
+ queryKey: ["legend", collection, collectionAssetId, titilerBaseUrl],
1068
1061
  queryFn: async () => {
1069
1062
  if (!collection) return null;
1070
- const colormapName = collection?.renders?.dashboard?.colormap_name;
1071
- if (!colormapName) return null;
1072
- const response = await fetch(`${titilerBaseUrl}/colorMaps/${colormapName}`);
1073
- if (!response.ok) {
1074
- const error = {
1075
- detail: `Colormap "${colormapName}" not found (HTTP ${response.status})`,
1076
- status: response.status
1077
- };
1078
- throw error;
1063
+ let colormap;
1064
+ const colormapName = collection?.renders?.[collectionAssetId]?.colormap_name;
1065
+ if (colormapName) {
1066
+ const response = await fetch(`${titilerBaseUrl}/colorMaps/${colormapName}`);
1067
+ if (!response.ok) {
1068
+ const error = {
1069
+ detail: `Colormap "${colormapName}" not found (HTTP ${response.status})`,
1070
+ status: response.status
1071
+ };
1072
+ throw error;
1073
+ }
1074
+ colormap = await response.json();
1079
1075
  }
1080
- const colorMap = await response.json();
1081
- return extractLegendProps(collection, colorMap);
1076
+ return extractLegendProps(collection, collectionAssetId, colormap);
1082
1077
  },
1083
- enabled: !!collection
1078
+ enabled: !!collection && !hideLegend
1084
1079
  });
1085
1080
  return {
1086
1081
  legend: query.data ?? null,
@@ -1091,7 +1086,7 @@ function useStacRasterLegend(collection) {
1091
1086
 
1092
1087
  // src/geo/hooks/useStacRasterLayer.ts
1093
1088
  function useStacRasterLayer(rasterLayerConfig) {
1094
- const { collectionId } = rasterLayerConfig;
1089
+ const { collectionId, collectionAssetId, hideLegend } = rasterLayerConfig;
1095
1090
  const {
1096
1091
  collection,
1097
1092
  isLoading: isLoadingCollection,
@@ -1102,12 +1097,12 @@ function useStacRasterLayer(rasterLayerConfig) {
1102
1097
  data: tiles,
1103
1098
  isLoading: tilesetLoading,
1104
1099
  error: tilesetError
1105
- } = useMosaicTiles(rasterLayerConfig, extCollection);
1100
+ } = useRasterTiles(rasterLayerConfig, extCollection);
1106
1101
  const {
1107
1102
  legend,
1108
1103
  isLoading: isLoadingLegend,
1109
1104
  error: legendError
1110
- } = useStacRasterLegend(extCollection);
1105
+ } = useStacRasterLegend(collectionAssetId, extCollection, { hideLegend });
1111
1106
  return {
1112
1107
  mapSource: {
1113
1108
  tiles: tiles ?? null
@@ -1118,6 +1113,53 @@ function useStacRasterLayer(rasterLayerConfig) {
1118
1113
  };
1119
1114
  }
1120
1115
 
1116
+ // src/geo/stac.basemaps.ts
1117
+ var createRasterBasemapStyle = ({
1118
+ id,
1119
+ tiles,
1120
+ maxzoom,
1121
+ attribution
1122
+ }) => ({
1123
+ version: 8,
1124
+ sources: {
1125
+ [id]: {
1126
+ type: "raster",
1127
+ tiles,
1128
+ tileSize: 256,
1129
+ maxzoom,
1130
+ attribution
1131
+ }
1132
+ },
1133
+ layers: [{ id, type: "raster", source: id }]
1134
+ });
1135
+ var NASA_BLUE_MARBLE_BASEMAP_STYLE = createRasterBasemapStyle({
1136
+ id: "nasa-blue-marble",
1137
+ tiles: [
1138
+ "https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/BlueMarble_NextGeneration/default/GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpeg"
1139
+ ],
1140
+ maxzoom: 8,
1141
+ attribution: "Imagery courtesy NASA EOSDIS/GIBS"
1142
+ });
1143
+ var CARTO_DARK_BASEMAP_STYLE = createRasterBasemapStyle({
1144
+ id: "carto-dark",
1145
+ tiles: [
1146
+ "https://a.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
1147
+ "https://b.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
1148
+ "https://c.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
1149
+ "https://d.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png"
1150
+ ],
1151
+ maxzoom: 19,
1152
+ attribution: "Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
1153
+ });
1154
+ var CARTO_DARK_WITH_LABELS_BASEMAP_STYLE = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json";
1155
+ var CARTO_LABELS_LAYER_ID = "waterway_label";
1156
+ var OPEN_FREE_MAP_DARK_BASEMAP_STYLE = "https://tiles.openfreemap.org/styles/dark";
1157
+ var OPEN_FREE_MAP_LABELS_LAYER_ID = "highway_name_other";
1158
+ var BASEMAP_LABEL_LAYER_IDS = {
1159
+ [CARTO_DARK_WITH_LABELS_BASEMAP_STYLE]: CARTO_LABELS_LAYER_ID,
1160
+ [OPEN_FREE_MAP_DARK_BASEMAP_STYLE]: OPEN_FREE_MAP_LABELS_LAYER_ID
1161
+ };
1162
+
1121
1163
  // src/geo/StacCompareMap/StacCompareMap.tsx
1122
1164
  import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1123
1165
  var MAP_PANEL_STYLE = {
@@ -1127,12 +1169,13 @@ var MAP_PANEL_STYLE = {
1127
1169
  width: "100%"
1128
1170
  };
1129
1171
  function StacCompareMap({
1130
- baseMapStyle,
1172
+ baseMapStyle = CARTO_DARK_WITH_LABELS_BASEMAP_STYLE,
1131
1173
  initialViewState,
1132
1174
  leftLayerConfig,
1133
1175
  rightLayerConfig,
1134
1176
  className
1135
1177
  }) {
1178
+ const resolvedBeforeLayerId = typeof baseMapStyle === "string" ? BASEMAP_LABEL_LAYER_IDS[baseMapStyle] : void 0;
1136
1179
  const instanceId = useId();
1137
1180
  const leftMapRef = useRef3(null);
1138
1181
  const rightMapRef = useRef3(null);
@@ -1174,7 +1217,7 @@ function StacCompareMap({
1174
1217
  };
1175
1218
  }, []);
1176
1219
  const mapProps = {
1177
- ...baseMapStyle && { mapStyle: baseMapStyle },
1220
+ mapStyle: baseMapStyle,
1178
1221
  ...initialViewState && { initialViewState },
1179
1222
  style: MAP_PANEL_STYLE
1180
1223
  };
@@ -1189,7 +1232,14 @@ function StacCompareMap({
1189
1232
  children: [
1190
1233
  /* @__PURE__ */ jsx17(AttributionMapControl, {}),
1191
1234
  /* @__PURE__ */ jsx17(NavigationControl, { position: "top-left", showCompass: false }),
1192
- left.mapSource.tiles && /* @__PURE__ */ jsx17(Source, { id: leftSourceId, type: "raster", tiles: [left.mapSource.tiles], children: /* @__PURE__ */ jsx17(Layer, { id: leftLayerId, type: "raster" }) })
1235
+ left.mapSource.tiles && /* @__PURE__ */ jsx17(Source, { id: leftSourceId, type: "raster", tiles: [left.mapSource.tiles], children: /* @__PURE__ */ jsx17(
1236
+ Layer,
1237
+ {
1238
+ id: leftLayerId,
1239
+ type: "raster",
1240
+ ...resolvedBeforeLayerId && { beforeId: resolvedBeforeLayerId }
1241
+ }
1242
+ ) })
1193
1243
  ]
1194
1244
  }
1195
1245
  ),
@@ -1202,7 +1252,14 @@ function StacCompareMap({
1202
1252
  onLoad: () => setRightMapLoaded(true),
1203
1253
  children: [
1204
1254
  /* @__PURE__ */ jsx17(AttributionMapControl, {}),
1205
- right.mapSource.tiles && /* @__PURE__ */ jsx17(Source, { id: rightSourceId, type: "raster", tiles: [right.mapSource.tiles], children: /* @__PURE__ */ jsx17(Layer, { id: rightLayerId, type: "raster" }) })
1255
+ right.mapSource.tiles && /* @__PURE__ */ jsx17(Source, { id: rightSourceId, type: "raster", tiles: [right.mapSource.tiles], children: /* @__PURE__ */ jsx17(
1256
+ Layer,
1257
+ {
1258
+ id: rightLayerId,
1259
+ type: "raster",
1260
+ ...resolvedBeforeLayerId && { beforeId: resolvedBeforeLayerId }
1261
+ }
1262
+ ) })
1206
1263
  ]
1207
1264
  }
1208
1265
  ),
@@ -1222,10 +1279,11 @@ import {
1222
1279
  } from "react-map-gl/maplibre";
1223
1280
  import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1224
1281
  function StacSingleLayerMap({
1225
- baseMapStyle = NASA_BLUE_MARBLE_BASEMAP_STYLE,
1282
+ baseMapStyle = CARTO_DARK_WITH_LABELS_BASEMAP_STYLE,
1226
1283
  initialViewState,
1227
1284
  layerConfig
1228
1285
  }) {
1286
+ const resolvedBeforeLayerId = typeof baseMapStyle === "string" ? BASEMAP_LABEL_LAYER_IDS[baseMapStyle] : void 0;
1229
1287
  const instanceId = useId2();
1230
1288
  const sourceId = `${layerConfig.collectionId}-raster-source-${instanceId}`;
1231
1289
  const layerId = `${layerConfig.collectionId}-raster-layer-${instanceId}`;
@@ -1241,7 +1299,14 @@ function StacSingleLayerMap({
1241
1299
  children: [
1242
1300
  /* @__PURE__ */ jsx18(AttributionMapControl, {}),
1243
1301
  /* @__PURE__ */ jsx18(NavigationControl2, { position: "top-left", showCompass: false }),
1244
- mapSource.tiles && /* @__PURE__ */ jsx18(Source2, { id: sourceId, type: "raster", tiles: [mapSource.tiles], children: /* @__PURE__ */ jsx18(Layer2, { id: layerId, type: "raster" }) })
1302
+ mapSource.tiles && /* @__PURE__ */ jsx18(Source2, { id: sourceId, type: "raster", tiles: [mapSource.tiles], children: /* @__PURE__ */ jsx18(
1303
+ Layer2,
1304
+ {
1305
+ id: layerId,
1306
+ type: "raster",
1307
+ ...resolvedBeforeLayerId && { beforeId: resolvedBeforeLayerId }
1308
+ }
1309
+ ) })
1245
1310
  ]
1246
1311
  }
1247
1312
  ),
@@ -1251,6 +1316,9 @@ function StacSingleLayerMap({
1251
1316
  }
1252
1317
  export {
1253
1318
  Banner,
1319
+ CARTO_DARK_BASEMAP_STYLE,
1320
+ CARTO_DARK_WITH_LABELS_BASEMAP_STYLE,
1321
+ CARTO_LABELS_LAYER_ID,
1254
1322
  Card,
1255
1323
  CardCTA,
1256
1324
  CardDetailed,
@@ -1262,6 +1330,8 @@ export {
1262
1330
  Legend,
1263
1331
  Link3 as Link,
1264
1332
  NASA_BLUE_MARBLE_BASEMAP_STYLE,
1333
+ OPEN_FREE_MAP_DARK_BASEMAP_STYLE,
1334
+ OPEN_FREE_MAP_LABELS_LAYER_ID,
1265
1335
  StacCompareMap,
1266
1336
  StacSingleLayerMap,
1267
1337
  Tag
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamimpact/veda-ui-blocks",
3
- "version": "0.1.0-beta.11",
3
+ "version": "0.1.0-beta.13",
4
4
  "description": "UI component library for VEDA-based portals, built on USWDS and React",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -67,7 +67,7 @@
67
67
  "storybook": "^10.2.17",
68
68
  "tsup": "^8.5.1",
69
69
  "typescript": "5.9.3",
70
- "vitest": "^4.0.18"
70
+ "vitest": "^4.1.0"
71
71
  },
72
72
  "scripts": {
73
73
  "build": "tsup src/index.ts --format esm --dts && pnpm run build:css",