diff --git a/web/js/map/layerbuilder.js b/web/js/map/layerbuilder.js index 17921c6c4f..fac2989234 100644 --- a/web/js/map/layerbuilder.js +++ b/web/js/map/layerbuilder.js @@ -16,6 +16,8 @@ import SourceVectorTile from 'ol/source/VectorTile'; import OlLayerVector from 'ol/layer/Vector'; import OlSourceVector from 'ol/source/Vector'; import LayerVectorTile from 'ol/layer/VectorTile'; +import Layer from 'ol/layer/Layer'; +import WebGLVectorLayerRenderer from 'ol/renderer/webgl/VectorLayer'; import { Circle, Fill, Stroke, Style, } from 'ol/style'; @@ -940,6 +942,97 @@ export default function mapLayerBuilder(config, cache, store) { const { proj: { selected }, date } = state; const { maxExtent, crs } = selected; const { r, g, b } = def.bandCombo; + const conceptID = def?.conceptIds?.[0]?.value || def?.collectionConceptID; + const dateTime = state.date.selected?.toISOString().split('T'); + dateTime.pop(); + dateTime.push('00:00:00.000Z'); + const zeroedDate = dateTime.join('T'); + const cmrMaxExtent = [-180, -90, 180, 90]; + + const style = { + 'stroke-color': ['*', ['get', 'COLOR'], [220, 220, 220]], + 'stroke-width': 3, + 'stroke-offset': -1, + 'fill-color': ['*', ['get', 'COLOR'], [255, 255, 255, 0.6]], + }; + + class WebGLLayer extends Layer { + createRenderer() { + return new WebGLVectorLayerRenderer(this, { + style, + }); + } + } + + const cmrSource = new OlSourceVector({ + format: new GeoJSON(), + projection: get(crs), + loader: async (extent, resolution, projection, success, failure) => { + // clamp extent to maximum extent allowed by the CMR api + const clampedExtent = extent.map((coord, i) => { + const condition = i <= 1 ? coord > cmrMaxExtent[i] : coord < cmrMaxExtent[i]; + if (condition) { + return coord; + } + return cmrMaxExtent[i]; + }); + const getGranules = () => { + const entries = []; + return async function requestGranules(searchAfter) { + const headers = { + 'Client-Id': 'Worldview', + }; + headers['cmr-search-after'] = searchAfter ?? ''; + const url = `https://cmr.earthdata.nasa.gov/search/granules.json?collection_concept_id=${conceptID}&bounding_box=${clampedExtent.join(',')}&temporal=${zeroedDate}/P0Y0M1DT0H0M&pageSize=2000`; + const cmrRes = await fetch(url, { headers }); + const resHeaders = cmrRes.headers; + const granules = await cmrRes.json(); + const resEntries = granules?.feed?.entry || []; + + entries.push(...resEntries); + + if (resHeaders.has('cmr-search-after')) { + await requestGranules(resHeaders.get('cmr-search-after')); + } + return entries; + }; + }; + + const granuleGetter = getGranules(); + const granules = await granuleGetter(); + + const features = granules.map((granule) => { + const coords = granule.polygons[0][0].split(' ').reduce((acc, coord, i, arr) => { + if (i % 2 !== 0) return acc; + + acc.push([arr[i + 1], coord]); + + return acc; + }, []); + + return { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [coords], + }, + properties: { + granuleId: granule.id, + }, + }; + }); + + const geojson = { + type: 'FeatureCollection', + features, + }; + + const formatedFeatures = cmrSource.getFormat().readFeatures(geojson); + + cmrSource.addFeatures(formatedFeatures); + success(formatedFeatures); + }, + }); const source = config.sources[def.source]; @@ -980,8 +1073,22 @@ export default function mapLayerBuilder(config, cache, store) { minZoom: def.minZoom, extent: maxExtent, }); + // const footprintLayer = new OlLayerVector({ + // source: cmrSource, + // className, + // extent: maxExtent, + // }); + const footprintLayer = new WebGLLayer({ + source: cmrSource, + className, + maxZoom: def.minZoom, + }); - return layer; + const layerGroup = new OlLayerGroup({ + layers: [footprintLayer, layer], + }); + + return layerGroup; }; const createXYZLayer = (def, options, day, state) => {