diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 44eef12057..4b235d5d49 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -31,4 +31,8 @@ This is simply a reminder of what we are going to look for before merging your c - I have added tests that prove my fix is effective or that my feature works (if applicable) - Any dependent changes have been merged and published in downstream modules (if applicable) +## Merging + +Please use the `squash and merge` commit method unless each commit in your branch is vital to the commit history of main. + @nasa-gibs/worldview diff --git a/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_1-Day.md b/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_1-Day.md deleted file mode 100644 index 0a1c996d99..0000000000 --- a/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_1-Day.md +++ /dev/null @@ -1,30 +0,0 @@ -The MODIS Near Real-Time (NRT) Global Flood Product (MCDWD) provides a daily global map of flooding. It is derived from the NRT MODIS Surface Reflectance (MOD09) datasets from both the Terra and Aqua satellites. The Flood Product is available for 3 compositing periods: 1-day, 2-day, and 3-day. For each composite, water detections for all observations (Terra and Aqua) over the compositing period (1, 2, or 3 days) are accumulated, and if the total exceeds the required threshold (1, 2, and 3 observations, respectively), the pixel is marked as water. (Note: 1-day product not yet available in Worldview) - -Users are advised to compare the flood product against the contributing MODIS reflectance imagery (such as 7-2-1 Corrected Reflectance; search for “721” after clicking “Add Layers”) , for the compositing period to ensure reported flood areas do not correspond to areas of cloud shadow. [Learn more...](https://earthdata.nasa.gov/earth-observation-data/near-real-time/mcdwd-nrt#ed-flood-faq) - -For the 1-day product it is important to understand the high potential for cloud-shadow false-positives, if the source imagery (Terra or Aqua) has cloud cover. - -#### Limitations -Common situations in which the flood product may be unable to accurately identify flood include: - -- Surface obscuration: clouds and canopy cover can block view of water on the surface. Buildings can also provide a “dry” roof, diluting the signal from surrounding water. -- Cloud shadow false-positives: cloud shadows are detected as water by the algorithm; when they recur in the same location over the compositing period, false positives are likely to be reported. Longer compositing periods help minimize this. Please check reflectance imagery of dates contributing to composite to rule these out, if reported flood looks unusual or suspicious. -- Terrain shadow false-positives: terrain shadows can create false-positives in mountains, generally only in wintertime. These are typically easy to identify due to their pattern (reflecting topography), and by comparison to reflectance imagery. -- Dark volcanic rock or soils: such areas can be identified as water, and thus will routinely be reported as flood. -- Springtime snow melt ponding on fields: such water can appear as pixellated flood across flat areas of agricultural fields. Although this is unusual water, it is often very shallow, and not moving, and thus typically not a flood in the normal sense. Checking the reflectance imagery will typically show such areas on the edge of larger areas of snow extent, or, looking back in time, will show them recently covered by snow. - - -#### Spatial Coverage -Non-polar global land areas (below 70 degrees latitude), comprising 223 10x10 degree tiles (see Figure 4 in [User Guide](https://www.earthdata.nasa.gov/s3fs-public/imported/MCDWD_UserGuide_RevB.pdf) for included tiles). - -#### Sensor/Image Resolution -Nominal equatorial resolution is ~232 m per pixel, with resolution increasing toward the poles (~116 m at 60 degrees latitude). Note the higher apparent resolution towards the poles is simply an artifact of the lat/lon (geographic) projection used, and not intrinsic to the data. - -#### Frequency -One product per day, per tile. During the day, data products are updated as NRT MOD09 data are received (an initial product may be updated if additional intersecting swath data is later received). - -To help estimate if the final flood product (for the day) is available in Worldview for a given area of interest, users can check if both the Terra and Aqua Corrected Reflectance layers are displaying for the area. If they are, the flood product has likely also been updated (or will be within an hour). - -Flood products displayed in Worldview are updated every 30 minutes, approximately on the hour and at 30 minutes past the hour. - -References: MCDWD_L3_NRT [doi:10.5067/MODIS/MCDWD_L3_NRT.061](https://doi.org/10.5067/MODIS/MCDWD_L3_NRT.061) \ No newline at end of file diff --git a/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_2-Day.md b/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_2-Day.md index b63f7c1e7b..cab0697f81 100644 --- a/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_2-Day.md +++ b/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_2-Day.md @@ -2,6 +2,8 @@ The MODIS Near Real-Time (NRT) Global Flood Product (MCDWD) provides a daily glo Users are advised to compare the flood product against the contributing MODIS reflectance imagery (such as 7-2-1 Corrected Reflectance; search for “721” after clicking “Add Layers”) , for the compositing period to ensure reported flood areas do not correspond to areas of cloud shadow. [Learn more...](https://earthdata.nasa.gov/earth-observation-data/near-real-time/mcdwd-nrt#ed-flood-faq) +As of January 12, 2023, a topographic filter has been applied to remove water detections from mountainous areas, greatly reducing the number of terrain shadow false-positives in such areas. These areas appear in all products as "Insufficient Data" (gray in default Worldview display). + #### Current Issues - The flood layers are displaying a large number of flood pixels in the far north at present (generally above 60N). Most of these are false-positive detections, a result of the large number of overlapping images towards the poles in the source MODIS imagery (due to the nature of the spacecraft's polar orbit). During summer when such regions have long periods of daylight, even more observations are available, but this increases the chance that repeated cloud-shadow false-positives pass the compositing requirement, and contaminate the product. We are working on various adjustments to the algorithm to minimize this. diff --git a/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_3-Day.md b/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_3-Day.md index 8c4e37ee48..e8681953dc 100644 --- a/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_3-Day.md +++ b/config/default/common/config/metadata/layers/modis/combined/MODIS_Combined_Flood_3-Day.md @@ -2,6 +2,8 @@ The MODIS Near Real-Time (NRT) Global Flood Product (MCDWD) provides a daily glo Users are advised to compare the flood product against the contributing MODIS reflectance imagery (such as 7-2-1 Corrected Reflectance; search for “721” after clicking “Add Layers”) , for the compositing period to ensure reported flood areas do not correspond to areas of cloud shadow. [Learn more...](https://earthdata.nasa.gov/earth-observation-data/near-real-time/mcdwd-nrt#ed-flood-faq) +As of January 12, 2023, a topographic filter has been applied to remove water detections from mountainous areas, greatly reducing the number of terrain shadow false-positives in such areas. These areas appear in all products as "Insufficient Data" (gray in default Worldview display). + #### Current Issues - The flood layers are displaying a large number of flood pixels in the far north at present (generally above 60N). Most of these are false-positive detections, a result of the large number of overlapping images towards the poles in the source MODIS imagery (due to the nature of the spacecraft's polar orbit). During summer when such regions have long periods of daylight, even more observations are available, but this increases the chance that repeated cloud-shadow false-positives pass the compositing requirement, and contaminate the product. We are working on various adjustments to the algorithm to minimize this. diff --git a/config/default/common/config/metadata/layers/multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.md b/config/default/common/config/metadata/layers/multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.md new file mode 100644 index 0000000000..fd7a555128 --- /dev/null +++ b/config/default/common/config/metadata/layers/multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.md @@ -0,0 +1 @@ +The OPERA Dynamic Surface Water Extent Provisional imagery layer is a Level-3 (L3) product that maps surface water every few days. The resolution is 30 m and the layer has 5 classifications: Not Water, Open Water, Partial Surface Water, Snow/Ice, and Cloud/Cloud Shadow. The input dataset for generating each product is the Harmonized Landsat Sentinel-2 (HLS) dataset. The OPERA Dynamic Surface Water Extent Provisional (L3) imagery layer is available through the Observational Products for End-Users from Remote Sensing Analysis (OPERA) project. \ No newline at end of file diff --git a/config/default/common/config/wv.json/categories/featured/All.json b/config/default/common/config/wv.json/categories/featured/All.json index 261b7bb8ff..82cf25e0f9 100644 --- a/config/default/common/config/wv.json/categories/featured/All.json +++ b/config/default/common/config/wv.json/categories/featured/All.json @@ -8,6 +8,7 @@ "description": "", "measurements": [ "Land Surface Reflectance - Featured", + "Surface Water Extent - Featured", "Aboveground Biomass", "Land Surface Metrics - Featured", "Geostationary", diff --git a/config/default/common/config/wv.json/categories/hazards_and_disasters/All.json b/config/default/common/config/wv.json/categories/hazards_and_disasters/All.json index 7b49407cd9..87673a4065 100644 --- a/config/default/common/config/wv.json/categories/hazards_and_disasters/All.json +++ b/config/default/common/config/wv.json/categories/hazards_and_disasters/All.json @@ -114,6 +114,7 @@ "Surface Flux", "Surface Albedo", "Surface Pressure", + "Surface Water Extent", "Terrain Elevation", "TOA Albedo", "TOA Flux", diff --git a/config/default/common/config/wv.json/categories/hazards_and_disasters/Floods.json b/config/default/common/config/wv.json/categories/hazards_and_disasters/Floods.json index 8628c6286c..4a8937e38d 100644 --- a/config/default/common/config/wv.json/categories/hazards_and_disasters/Floods.json +++ b/config/default/common/config/wv.json/categories/hazards_and_disasters/Floods.json @@ -19,6 +19,7 @@ "Soil Moisture", "Snow Cover", "Snow Water Equivalent", + "Surface Water Extent", "Water Bodies" ] } diff --git a/config/default/common/config/wv.json/categories/science_disciplines/All.json b/config/default/common/config/wv.json/categories/science_disciplines/All.json index d81da11e94..cc5fa6043e 100644 --- a/config/default/common/config/wv.json/categories/science_disciplines/All.json +++ b/config/default/common/config/wv.json/categories/science_disciplines/All.json @@ -114,6 +114,7 @@ "Surface Flux", "Surface Albedo", "Surface Pressure", + "Surface Water Extent", "Terrain Elevation", "TOA Albedo", "TOA Flux", diff --git a/config/default/common/config/wv.json/categories/science_disciplines/Terrestrial Hydrosphere.json b/config/default/common/config/wv.json/categories/science_disciplines/Terrestrial Hydrosphere.json index 07d2940512..33758a937a 100644 --- a/config/default/common/config/wv.json/categories/science_disciplines/Terrestrial Hydrosphere.json +++ b/config/default/common/config/wv.json/categories/science_disciplines/Terrestrial Hydrosphere.json @@ -18,6 +18,7 @@ "Snow Mass", "Snow Water Equivalent", "Soil Moisture", + "Surface Water Extent", "Water Bodies" ] } diff --git a/config/default/common/config/wv.json/layerOrder.json b/config/default/common/config/wv.json/layerOrder.json index 99e2b820d7..144deff09f 100644 --- a/config/default/common/config/wv.json/layerOrder.json +++ b/config/default/common/config/wv.json/layerOrder.json @@ -111,6 +111,7 @@ "GHRSST_L4_GAMSSA_GDS2_Sea_Ice_Concentration", "VIIRS_SNPP_L2_Sea_Surface_Temp_Day", "VIIRS_SNPP_L2_Sea_Surface_Temp_Night", + "OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional", "MODIS_Combined_Flood_2-Day", "MODIS_Combined_Flood_3-Day", "MODIS_Terra_Aerosol", diff --git a/config/default/common/config/wv.json/layers/multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.json b/config/default/common/config/wv.json/layers/multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.json new file mode 100644 index 0000000000..195b2f620c --- /dev/null +++ b/config/default/common/config/wv.json/layers/multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.json @@ -0,0 +1,12 @@ +{ + "layers": { + "OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional": { + "id": "OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional", + "description": "multi-mission/opera/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional", + "tags": "podaac PO.DAAC DSWx flood", + "group": "overlays", + "layergroup": "Surface Water Extent", + "disableSnapshot": true + } + } +} \ No newline at end of file diff --git a/config/default/common/config/wv.json/measurements/Featured - Surface Water Extent.json b/config/default/common/config/wv.json/measurements/Featured - Surface Water Extent.json new file mode 100644 index 0000000000..219bfe0e9a --- /dev/null +++ b/config/default/common/config/wv.json/measurements/Featured - Surface Water Extent.json @@ -0,0 +1,20 @@ +{ + "measurements": { + "Surface Water Extent - Featured": { + "id": "featured-surface-water-extent", + "title": "Surface Water Extent", + "subtitle": "DSWx-HLS", + "sources": { + "DSWx-HLS": { + "id": "dswx-hls", + "title": "DSWx-HLS", + "description": "", + "image": "", + "settings": [ + "OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional" + ] + } + } + } + } +} diff --git a/config/default/common/config/wv.json/measurements/Flood.json b/config/default/common/config/wv.json/measurements/Flood.json index 33500ad7d4..2928227e14 100644 --- a/config/default/common/config/wv.json/measurements/Flood.json +++ b/config/default/common/config/wv.json/measurements/Flood.json @@ -3,7 +3,7 @@ "Flood": { "id": "flood", "title": "Flood", - "subtitle": "Terra and Aqua/MODIS", + "subtitle": "Terra and Aqua/MODIS, DSWx-HLS", "sources": { "Terra and Aqua/MODIS": { "id": "terra-aqua-modis", @@ -14,6 +14,15 @@ "MODIS_Combined_Flood_2-Day", "MODIS_Combined_Flood_3-Day" ] + }, + "DSWx-HLS": { + "id": "dswx-hls", + "title": "DSWx-HLS", + "description": "", + "image": "", + "settings": [ + "OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional" + ] } } } diff --git a/config/default/common/config/wv.json/measurements/Surface Water Extent.json b/config/default/common/config/wv.json/measurements/Surface Water Extent.json new file mode 100644 index 0000000000..9a89ebde7a --- /dev/null +++ b/config/default/common/config/wv.json/measurements/Surface Water Extent.json @@ -0,0 +1,20 @@ +{ + "measurements": { + "Surface Water Extent": { + "id": "surface-water-extent", + "title": "Surface Water Extent", + "subtitle": "DSWx-HLS", + "sources": { + "DSWx-HLS": { + "id": "dswx-hls", + "title": "DSWx-HLS", + "description": "", + "image": "", + "settings": [ + "OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional" + ] + } + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 582a05a5b9..9a371bdb41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "worldview", - "version": "4.2.0", + "version": "4.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "worldview", - "version": "4.2.0", + "version": "4.3.0", "hasInstallScript": true, "license": "NASA-1.3", "dependencies": { diff --git a/package.json b/package.json index f16508b048..b4855f27b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "worldview", - "version": "4.2.0", + "version": "4.3.0", "description": "Interactive interface for browsing full-resolution, global satellite imagery", "keywords": [ "NASA", diff --git a/web/images/layers/previews/geographic/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.jpg b/web/images/layers/previews/geographic/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.jpg new file mode 100644 index 0000000000..0d8375b1b0 Binary files /dev/null and b/web/images/layers/previews/geographic/OPERA_L3_Dynamic_Surface_Water_Extent-HLS_Provisional.jpg differ diff --git a/web/js/components/layer/product-picker/browse/browse-layers.js b/web/js/components/layer/product-picker/browse/browse-layers.js index 664759c72d..22d59be6d0 100644 --- a/web/js/components/layer/product-picker/browse/browse-layers.js +++ b/web/js/components/layer/product-picker/browse/browse-layers.js @@ -203,15 +203,14 @@ function BrowseLayers (props) { return ( <> { isMobile ? renderMobileDropdown() : renderDesktopTabs() } -
- -
- { renderContent() } + { + isCategoryDisplay + ? ( +
+ +
+ ) : renderContent() + } ); } diff --git a/web/js/components/layer/settings/palette.js b/web/js/components/layer/settings/palette.js index fd9127674a..d1f8ac8fcb 100644 --- a/web/js/components/layer/settings/palette.js +++ b/web/js/components/layer/settings/palette.js @@ -1,84 +1,72 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import lodashIndexOf from 'lodash/indexOf'; import { drawPaletteOnCanvas } from '../../../modules/palettes/util'; import util from '../../../util/util'; import Scrollbar from '../../util/scrollbar'; -class PaletteSelect extends React.Component { - constructor(props) { - super(props); - this.state = { - activePalette: props.activePalette, - }; - } +function PaletteSelect (props) { + const { + activePalette: initialActivePalette, + canvas, + clearCustomPalette, + getCustomPalette, + getDefaultLegend, + groupName, + index, + layer, + paletteOrder, + palettesTranslate, + setCustomPalette, + } = props; - /** - * Render default legend option - */ - renderDefault() { - const { layer, index, getDefaultLegend } = this.props; - const { activePalette } = this.state; + const [activePalette, setActivePalette] = useState(initialActivePalette); + + const renderDefault = () => { const legend = getDefaultLegend(layer.id, index); if (legend.type === 'continuous' || legend.type === 'discrete') { - return this.renderSelectorItemScale( + return renderSelectorItemScale( legend.colors, '__default', legend, activePalette === '__default', ); } - return this.renderSelectorItemSingle( + return renderSelectorItemSingle( legend, '__default', 'Default', activePalette === '__default', ); - } + }; /** - * Pass palette to model after selection - * @param {String} id | custom Palette Id + * Clears the custom palette if Id is set to __default + * @param {String} id | colormap Id */ - onChangePalette(id) { - const { - layer, clearCustomPalette, setCustomPalette, groupName, index, - } = this.props; - - // Applying customs takes a while and - // it looks more natural to make this async - // instead of waiting + const onChangePalette = (id) => { if (id === '__default') { clearCustomPalette(layer.id, index, groupName); } else { setCustomPalette(layer.id, id, index, groupName); } - this.setState({ activePalette: id }); - } + setActivePalette(id); + }; /** - * Apply logic to render correct palette selection - * @param {String} id | Legend Id + * Renders as renderSelectorItemScale or renderSelectorItemSingle depending on if + * the source type is continuous or discrete. + * @param {String} id | colormap Id */ - customLegend(id) { - const { - getDefaultLegend, - getCustomPalette, - layer, - index, - palettesTranslate, - } = this.props; - const { activePalette } = this.state; + const customLegend = (id) => { const source = getDefaultLegend(layer.id, index); const target = getCustomPalette(id); const targetType = target.colors.length === 1 ? 'classification' : 'continuous'; - if ( - (source.type === 'continuous' && targetType === 'continuous') - || (source.type === 'discrete' && targetType === 'continuous') - ) { + if ((source.type === 'continuous' && targetType === 'continuous') + || (source.type === 'discrete' && targetType === 'continuous')) { const translated = palettesTranslate(source.colors, target.colors); - return this.renderSelectorItemScale( + return renderSelectorItemScale( translated, id, target, @@ -86,14 +74,14 @@ class PaletteSelect extends React.Component { ); } if (source.type === 'classification' && targetType === 'classification') { - return this.renderSelectorItemSingle( + return renderSelectorItemSingle( target, id, target.name, activePalette === target.id, ); } - } + }; /** * Render customs palette options @@ -102,8 +90,7 @@ class PaletteSelect extends React.Component { * @param {Object} legend | Legend Object * @param {Boolean} isSelected | is this colormap active */ - renderSelectorItemScale(palette, id, legend, isSelected) { - const { canvas } = this.props; + const renderSelectorItemScale = (palette, id, legend, isSelected) => { const caseDefaultClassName = 'wv-palette-selector-row wv-checkbox wv-checkbox-round gray '; const checkedClassName = isSelected ? 'checked' : ''; const ctx = canvas.getContext('2d'); @@ -113,21 +100,22 @@ class PaletteSelect extends React.Component { canvas.width, canvas.height, ); + const dataURL = canvas.toDataURL('image/png'); return (
this.onChangePalette(id)} + onClick={() => onChangePalette(id)} />
); - } + }; /** * Render classification customs when there is only one @@ -137,7 +125,7 @@ class PaletteSelect extends React.Component { * @param {String} description | Colormap name * @param {Boolean} isSelected | is this colormap active */ - renderSelectorItemSingle(palette, id, description, isSelected) { + const renderSelectorItemSingle = (palette, id, description, isSelected) => { const color = palette.classes ? palette.classes.colors[0] : palette.colors[0]; @@ -151,7 +139,7 @@ class PaletteSelect extends React.Component { id={`wv-palette-radio-${id}`} type="radio" name="wv-palette-radio" - onClick={() => this.onChangePalette(id)} + onClick={() => onChangePalette(id)} />