From d07e7bf5a8064346a1fa0f46ab934a64858dfd52 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 20 Oct 2021 11:29:06 -0600 Subject: [PATCH 01/35] remove unused measurement descriptions --- .../metadata/layers/amsre/SnowDepthOverIce.md | 10 --- .../modis/terra/SeaSurfaceTemperature.md | 44 ------------ .../WindSpeedOverIceFreeOceans.md | 13 ---- .../metadata/layers/seawifs/SWDBAerosol.md | 11 --- .../layers/smap/BrightnessTemperature.md | 67 ------------------- .../smap/CorrectedBrightnessTemperature.md | 12 ---- .../config/metadata/layers/smap/FreezeThaw.md | 32 --------- .../layers/smap/RootZoneSoilMoisture.md | 21 ------ 8 files changed, 210 deletions(-) delete mode 100644 config/default/common/config/metadata/layers/amsre/SnowDepthOverIce.md delete mode 100644 config/default/common/config/metadata/layers/modis/terra/SeaSurfaceTemperature.md delete mode 100644 config/default/common/config/metadata/layers/multi-mission/WindSpeedOverIceFreeOceans.md delete mode 100644 config/default/common/config/metadata/layers/seawifs/SWDBAerosol.md delete mode 100644 config/default/common/config/metadata/layers/smap/BrightnessTemperature.md delete mode 100644 config/default/common/config/metadata/layers/smap/CorrectedBrightnessTemperature.md delete mode 100644 config/default/common/config/metadata/layers/smap/FreezeThaw.md delete mode 100644 config/default/common/config/metadata/layers/smap/RootZoneSoilMoisture.md diff --git a/config/default/common/config/metadata/layers/amsre/SnowDepthOverIce.md b/config/default/common/config/metadata/layers/amsre/SnowDepthOverIce.md deleted file mode 100644 index 94598560db..0000000000 --- a/config/default/common/config/metadata/layers/amsre/SnowDepthOverIce.md +++ /dev/null @@ -1,10 +0,0 @@ -### AMSR-E Snow Depth Over Ice, 12 km -Temporal coverage: 18 June 2002 - 4 October 2011 - -The Advanced Microwave Scanning Radiometer - Earth Observing System (AMSR-E) "Snow Depth Over Ice, 12 km" layer displays snow depth in centimeters over sea ice in the Polar regions from the daily half-orbit passes of the AMSR-E B feedhorn at a 12.5 km resolution. Snow depth over sea ice is a running 5-day average based on the current day and the 4 previous days. - -Onboard NASA's Aqua satellite, the AMSR-E radiometer measured terrestrial, oceanic, and atmospheric parameters used to investigate global water and energy cycles. The imagery resolution is 2 km and sensor resolution is 12.5 km. The temporal resolution was daily. - -References: [AMSR-E/Aqua Daily L3 12.5 km Brightness Temperature, Sea Ice Concentration, & Snow Depth Polar Grids](https://nsidc.org/data/ae_si12) - -Data fields: `SI_12km_NH_SNOWDEPTH_5DAY`; `SI_12km_SH_SNOWDEPTH_5DAY` diff --git a/config/default/common/config/metadata/layers/modis/terra/SeaSurfaceTemperature.md b/config/default/common/config/metadata/layers/modis/terra/SeaSurfaceTemperature.md deleted file mode 100644 index 822b5a6079..0000000000 --- a/config/default/common/config/metadata/layers/modis/terra/SeaSurfaceTemperature.md +++ /dev/null @@ -1,44 +0,0 @@ -### Sea Surface Temperature (Day, L2) -Temporal coverage: 1 April - 3 April 2020; 6 June 2020 - present - -### Sea Surface Temperature (Night, L2) -Temporal coverage: 1 April - 3 April 2020; 6 June 2020 - present - -### Sea Surface Temperature (L3, Night, Daily | 8-Day | Monthly | Annual, Mid-IR, 4km) -Temporal coverage: 26 February 2000 - present - -The MODIS L3 SST 4km layer shows global nighttime sea surface temperature (SST) at a depth of a few micrometers with ranges from -1.8 to 32 degree Celsius. The SST is derived with a Mid-Infrared (Short–Wave) SST Algorithm that uses MODIS bands 22 and 23 at 3.959 and 4.050 μm. This Level 3 product is derived from native 1 km Level 2 SST observations that are mapped to a global 4.63 km grid. The temporal resolution of MODIS L3 SST is daily, 8-Day, monthly, and annually. More information and access to the data sources is available at these locations: [MODIS Terra Level 3 SST Mid-IR Daily 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_DAILY_4KM_NIGHTTIME_V2014.0) (daily); [MODIS Terra Level 3 SST Mid-IR 8-Day 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_8DAY_4KM_NIGHTTIME_V2014.0) (8-Day); [MODIS Terra Level 3 SST Mid-IR Monthly 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_MONTHLY_4KM_NIGHTTIME_V2014.0) (monthly); and [MODIS Terra Level 3 SST Mid-IR Annual 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_ANNUAL_4KM_NIGHTTIME_V2014.0) (annually) - -### Sea Surface Temperature (L3, Night, Daily | 8-Day | Monthly | Annual, Mid-IR, 9km) -Temporal coverage: 26 February 2000 - present - -The MODIS L3 SST 9km layer shows global nighttime sea surface temperature (SST) at a depth of a few micrometers with ranges from -1.8 to 32 degree Celsius. The SST is derived with a Mid-Infrared (Short–Wave) SST Algorithm that uses MODIS bands 22 and 23 at 3.959 and 4.050 μm. This Level 3 product is derived from native 1 km Level 2 SST observations that are mapped to a global 9 km grid. The temporal resolution of MODIS L3 SST is daily, 8-Day, monthly, and annually. More information and access to the data sources is available at these locations: -[MODIS Terra Level 3 SST Mid-IR Daily 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_DAILY_9KM_NIGHTTIME_V2014.0) (daily); [MODIS Terra Level 3 SST Mid-IR 8-Day 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_8DAY_9KM_NIGHTTIME_V2014.0) (8-Day); [MODIS Terra Level 3 SST Mid-IR Monthly 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_MONTHLY_9KM_NIGHTTIME_V2014.0) (monthly); and [MODIS Terra Level 3 SST Mid-IR Annual 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_MID-IR_ANNUAL_9KM_NIGHTTIME_V2014.0) (annually) - -### Sea Surface Temperature (L3, Day, Daily | 8-Day | Monthly | Annual, Thermal, 4km) -Temporal coverage: 26 February 2000 - present - -The MODIS L3 SST 4km layer shows global daytime sea surface temperature (SST) at a depth of a few micrometers with ranges from -1.8 to 32 degree Celsius. The SST is derived with a Thermal (Long–Wave) SST Algorithm that uses MODIS bands 31 and 32 at 11 and 12 μm. This Level 3 product is derived from native 1 km Level 2 SST observations that are mapped to a global 4.63 km grid. The temporal resolution of MODIS L3 SST is daily, 8-Day, monthly, and annually. More information and access to the data sources is available at these locations: -[MODIS Terra Level 3 SST Thermal Daily 4km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_DAILY_4KM_DAYTIME_V2014.0) (daily); [MODIS Terra Level 3 SST Thermal 8-Day 4km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_8DAY_4KM_DAYTIME_V2014.0) (8-Day); [MODIS Terra Level 3 SST Thermal Monthly 4km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_MONTHLY_4KM_DAYTIME_V2014.0) (monthly); and [MODIS Terra Level 3 SST Thermal Annual 4km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_ANNUAL_4KM_DAYTIME_V2014.0) (annually) - -### Sea Surface Temperature (L3, Night, Daily | 8-Day | Monthly | Annual, Thermal, 4km) -Temporal coverage: 26 February 2000 - present - -The MODIS L3 SST 4km layer shows global nighttime sea surface temperature (SST) at a depth of a few micrometers with ranges from -1.8 to 32 degree Celsius. The SST is derived with a Thermal (Long–Wave) SST Algorithm that uses MODIS bands 31 and 32 at 11 and 12 μm. This Level 3 product is derived from native 1 km Level 2 SST observations that are mapped to a global 4.63 km grid. The temporal resolution of MODIS L3 SST is daily, 8-Day, monthly, and annually. More information and access to the data sources is available at these locations: -[MODIS Terra Level 3 SST Thermal Daily 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_DAILY_4KM_NIGHTTIME_V2014.0) (daily); [MODIS Terra Level 3 SST Thermal 8-Day 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_8DAY_4KM_NIGHTTIME_V2014.0) (8-Day); [MODIS Terra Level 3 SST Thermal Monthly 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_MONTHLY_4KM_NIGHTTIME_V2014.0) (monthly); and [MODIS Terra Level 3 SST Thermal Annual 4km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_ANNUAL_4KM_NIGHTTIME_V2014.0) (annually) - -### Sea Surface Temperature (L3, Day, Daily | 8-Day | Monthly | Annual, Thermal, 9km) -Temporal coverage: 26 February 2000 - present - -The MODIS L3 SST 9km layer shows global daytime sea surface temperature (SST) at a depth of a few micrometers with ranges from -1.8 to 32 degree Celsius. The SST is derived with a Thermal-Infrared (Long–Wave) SST Algorithm that uses MODIS bands 31 and 32 at 11 and 12 μm. This Level 3 product is derived from native 1 km Level 2 SST observations that are mapped to a global 9 km grid. The temporal resolution of MODIS L3 SST is daily, 8-Day, monthly, and annually. More information and access to the data sources is available at these locations: -[MODIS Terra Level 3 SST Thermal Daily 9km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_DAILY_9KM_DAYTIME_V2014.0) (daily); [MODIS Terra Level 3 SST Thermal 8-Day 9km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_8DAY_9KM_DAYTIME_V2014.0) (8-Day); [MODIS Terra Level 3 SST Thermal Monthly 9km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_MONTHLY_9KM_DAYTIME_V2014.0) (monthly); and [MODIS Terra Level 3 SST Thermal Annual 9km Daytime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_ANNUAL_9KM_DAYTIME_V2014.0) (annually) - -### Sea Surface Temperature (L3, Night, Daily | 8-Day | Monthly | Annual, Thermal, 9km) -Temporal coverage: 26 February 2000 - present - -The MODIS L3 SST 9km layer shows global nighttime sea surface temperature (SST) at a depth of a few micrometers with ranges from -1.8 to 32 degree Celsius. The SST is derived with a Thermal-Infrared (Long–Wave) SST Algorithm that uses MODIS bands 31 and 32 at 11 and 12 μm. This Level 3 product is derived from native 1 km Level 2 SST observations that are mapped to a global 9 km grid. The temporal resolution of MODIS L3 SST is daily, 8-Day, monthly, and annually. More information and access to the data sources is available at these locations: -[MODIS Terra Level 3 SST Thermal Daily 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_DAILY_9KM_NIGHTTIME_V2014.0) (daily); [MODIS Terra Level 3 SST Thermal 8-Day 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_8DAY_9KM_NIGHTTIME_V2014.0) (8-Day); [MODIS Terra Level 3 SST Thermal Monthly 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_MONTHLY_9KM_NIGHTTIME_V2014.0) (monthly); and [MODIS Terra Level 3 SST Thermal Annual 9km Nighttime v2014.0](https://podaac.jpl.nasa.gov/dataset/MODIS_TERRA_L3_SST_THERMAL_ANNUAL_9KM_NIGHTTIME_V2014.0) (annually) - -References: Details of the [algorithm](https://oceancolor.gsfc.nasa.gov/atbd/sst4/) can be found at Ocean Biology Processing Group (OBPG/OB.DAAC) website. - -P. J. Minnett et al., "Sea-surface temperature measurements from the Moderate-Resolution Imaging Spectroradiometer (MODIS) on Aqua and Terra," IGARSS 2004. 2004 IEEE International Geoscience and Remote Sensing Symposium, Anchorage, AK, 2004, pp. 4576-4579 vol.7. [doi: 10.1109/IGARSS.2004.1370173](https://doi.org/10.1109/IGARSS.2004.1370173). diff --git a/config/default/common/config/metadata/layers/multi-mission/WindSpeedOverIceFreeOceans.md b/config/default/common/config/metadata/layers/multi-mission/WindSpeedOverIceFreeOceans.md deleted file mode 100644 index edbb7462a9..0000000000 --- a/config/default/common/config/metadata/layers/multi-mission/WindSpeedOverIceFreeOceans.md +++ /dev/null @@ -1,13 +0,0 @@ -### Wind Speed over Ice-Free Oceans (Monthly, Average) -Temporal Coverage: January 1988 - August 2016 - -The RSS Wind Speed over Ice-Free Oceans layer indicates the average wind speed within a month in meters per second over ocean areas that are not covered in ice. The imagery resolution is about 1 degree, or about 111 kilometers, and the temporal resolution is monthly. This layer was constructed using data collected from all of the SSM/I, SSMIS F16, SSMIS F17, AMSR-E, AMSR-2, and WindSat instruments. This layer was constructed by first creating a monthly wind speed 1-degree map from each satellite. Then, quality control measures were applied to this map and combined with wind speed measurements from each instrument to output this layer. - -The Special Sensor Microwave/Imager (SSM/I) is a seven channel passive microwave radiometer operating at four frequencies and dual-polarization. The Special Sensor Microwave Imager Sounder (SSMIS) is also a microwave radiometer that includes a sounder. The Advanced Microwave Scanning Radiometer - Earth Observing System (AMSR-E) is a twelve-channel, six-frequency, total power passive-microwave radiometer. The Advanced Microwave Scanning Radiometer 2 (AMSR2) is a remote sensing instrument measuring weak microwave emissions from the surface and the atmosphere of the Earth providing microwave emission and scattering intensity measurements. Finally, WindSat is a polarimetric microwave radiometer which measures ocean surface wind vectors. - -References: [GHRC: RSS Monthly 1-Deg Merged Wind Climatology NetCDF V7R01](https://doi.org/10.5067/MEASURES/MULTIPLE/WIND_CLIMATOLOGY/DATA302); -[RSS: Merged Wind Speed 1-deg Monthly Climate Product](http://www.remss.com/measurements/wind/wspd-1-deg-product); -[RSS: AMSR2/AMSRE](http://www.remss.com/missions/amsr); -[RSS: SSMI/SSMIS](http://www.remss.com/missions/ssmi/); -[GCOM-W1: AMSR2](https://suzaku.eorc.jaxa.jp/GCOM_W/w_amsr2/whats_amsr2.html); -[WindSat Quick Guide by NASA / SPoRT](https://weather.msfc.nasa.gov/sport/survey/windSat/WindSat_Reference_Guide.pdf) diff --git a/config/default/common/config/metadata/layers/seawifs/SWDBAerosol.md b/config/default/common/config/metadata/layers/seawifs/SWDBAerosol.md deleted file mode 100644 index 5f9de41817..0000000000 --- a/config/default/common/config/metadata/layers/seawifs/SWDBAerosol.md +++ /dev/null @@ -1,11 +0,0 @@ -### Aerosol Angstrom Exponent (Daily) -Temporal coverage: 4 September 1997 - 8 February 2000; 1 January 2010 - 30 January 2010; 1 December 2010 - -### Aerosol Angstrom Exponent (Monthly) -Temporal coverage: October 1997 - April 2004; January - December 2010 - -### Aerosol Optical Thickness 550nm (Daily) -Temporal coverage: 4 September 1997 - 25 November 1997; 1 January 2010 - 30 January 2010; 1 December 2010 - -### Aerosol Optical Thickness 550nm (Monthly) -Temporal coverage: October 1997 - May 1999; January - December 2010 diff --git a/config/default/common/config/metadata/layers/smap/BrightnessTemperature.md b/config/default/common/config/metadata/layers/smap/BrightnessTemperature.md deleted file mode 100644 index 9c67ab6850..0000000000 --- a/config/default/common/config/metadata/layers/smap/BrightnessTemperature.md +++ /dev/null @@ -1,67 +0,0 @@ -### Uncorrected Brightness Temperature 36 km (L1, Passive, Fore | Aft, H Polarization | V Polarization) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) "Uncorrected Brightness Temperature 36 km" layers display brightness temperatures (TBs) posted to a 36 km EASE-Grid 2.0 and uncorrected for the presence of water in Kelvin (K) for the horizontal (H) and vertical (V) polarizations of the fore and aft scans from the SMAP radiometer. At the L-band frequency used by SMAP, the TB of the land surface is proportional to its emissivity multiplied by its physical temperature. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: SPL1CTB [doi: 10.5067/JJ5FL7FRLKJI](https://doi.org/10.5067/JJ5FL7FRLKJI) - -Data fields: `cell_tb_h_fore`, `cell_tb_h_aft`, `cell_tb_v_fore`, `cell_tb_v_aft` - -### Uncorrected Brightness Temperature 36 km QA and RFI Flags -Temporal coverage: 31 March 2015 - present - -### Uncorrected Brightness Temperature 36 km QA (L1, Passive, Fore | Aft, H Polarization | V Polarization) -The Soil Moisture Active Passive (SMAP) "Uncorrected Brightness Temperature 36 km QA” layers display quality assurance (QA) flags for uncorrected brightness temperatures (TBs) on a 36 km EASE-Grid 2.0 for the horizontal (H) and vertical (V) polarizations of the fore and aft scans from the SMAP radiometer. - -Within the image, green indicates that TB observations have acceptable quality for science use, yellow indicates that caution should be used with the TB observations as one or more quality-impacting conditions have been identified, and red indicates that TB observations are flagged as bad due to unacceptable quality. - - -### Uncorrected Brightness Temperature 36 km RFI (L1, Passive, Fore | Aft, H Polarization | V Polarization) -The Soil Moisture Active Passive (SMAP) "Uncorrected Brightness Temperature 36 km RFI” layers displays Radio Frequency Interference (RFI) quality flags of uncorrected brightness temperatures (TBs) on a 36 km EASE-Grid 2.0 for the horizontal (H) and vertical (V) polarizations of the fore and aft scans from the SMAP radiometer. RFI refers to the interference in measurements from other transmitters operating at frequencies adjacent to the SMAP L-band frequency. - -Within the image, green indicates that TB observations are free of RFI and approved for science use, yellow indicates that caution should be used with the TB observations as RFI was detected but mitigated, and red indicates that TB observations are flagged as bad due to RFI. - -References: SPL1CTB [doi: 10.5067/JJ5FL7FRLKJI](https://doi.org/10.5067/JJ5FL7FRLKJI) - -Data fields: `cell_tb_qual_flag_h_fore`, `cell_tb_qual_flag_h_aft`, `cell_tb_qual_flag_v_fore`, `cell_tb_qual_flag_v_aft` - -### Uncorrected Brightness Temperature 9 km (L1, Passive, Fore | Aft, H Polarization | V Polarization) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) "Uncorrected Brightness Temperature 9 km" layers display brightness temperatures (TBs) uncorrected for the presence of water in Kelvin (K) for the horizontal (H) and vertical (V) polarizations of the fore and aft scans from the SMAP radiometer. To enhance the grid resolution, Backus-Gilbert optimal interpolation techniques are used to extract maximum information from SMAP antenna temperatures and convert them to brightness temperatures, which are posted to the 9 km EASE-Grid 2.0 in a global cylindrical projection. At the L-band frequency used by SMAP, the TB of the land surface is proportional to its emissivity multiplied by its physical temperature. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: SPL1CTB_E [doi:10.5067/XB8K63YM4U8O](https://doi.org/10.5067/XB8K63YM4U8O) - -Data fields: `cell_tb_h_fore`, `cell_tb_h_aft`, `cell_tb_v_fore`, `cell_tb_v_aft` - -### Uncorrected Brightness Temperature 9 km QA and RFI Flags -Temporal coverage: 31 March 2015 - present - -### Uncorrected Brightness Temperature 9 km QA (L1, Passive, Fore | Aft, H Polarization | V Polarization) -The Soil Moisture Active Passive (SMAP) "Uncorrected Brightness Temperature 9 km QA” layers display quality assurance (QA) flags for uncorrected brightness temperatures (TBs) posted to a 9 km EASE-Grid 2.0 for the horizontal (H) and vertical (V) polarizations of the fore and aft scans from the SMAP radiometer. - -Within the image, green indicates that TB observations have acceptable quality for science use, yellow indicates that caution should be used with the TB observations as one or more quality-impacting conditions have been identified, and red indicates that TB observations are flagged as bad due to unacceptable quality. - -### Uncorrected Brightness Temperature 9 km RFI (L1, Passive, Fore | Aft, H Polarization | V Polarization) -The Soil Moisture Active Passive (SMAP) "Uncorrected Brightness Temperature 9 km RFI” layers displays Radio Frequency Interference (RFI) quality flags of uncorrected brightness temperatures (TBs) posted to a 9 km EASE-Grid 2.0 for the horizontal (H) and vertical (V) polarizations of the fore and aft scans from the SMAP radiometer. RFI refers to the interference in measurements from other transmitters operating at frequencies adjacent to the SMAP L-band frequency. - -Within the image, green indicates that TB observations are free of RFI and approved for science use, yellow indicates that caution should be used with the TB observations as RFI was detected but mitigated, and red indicates that TB observations are flagged as bad due to RFI. - -References: SPL1CTB_E [doi:10.5067/XB8K63YM4U8O](https://doi.org/10.5067/XB8K63YM4U8O) - -Data fields: `cell_tb_qual_flag_h_fore`, `cell_tb_qual_flag_h_aft`, `cell_tb_qual_flag_v_fore`, `cell_tb_qual_flag_v_aft` - -### Corrected Brightness Temperature (L3, Passive, H Polarization | V Polarization) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) "Corrected Brightness Temperature" layers display brightness temperatures (TBs) in Kelvin (K) for the horizontal (H) and vertical (V) polarizations from the SMAP radiometer. The TBs are corrected for the presence of water wherever water fraction is below a threshold. At the L-band frequency used by SMAP, the TB of the land surface is proportional to its emissivity multiplied by its physical temperature. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: [SMAP L3 Radiometer Global Daily 36 km EASE-Grid Soil Moisture](https://nsidc.org/data/SPL3SMP) - -Data fields: `tb_h_corrected`, `tb_v_corrected` diff --git a/config/default/common/config/metadata/layers/smap/CorrectedBrightnessTemperature.md b/config/default/common/config/metadata/layers/smap/CorrectedBrightnessTemperature.md deleted file mode 100644 index c1b37f9d5e..0000000000 --- a/config/default/common/config/metadata/layers/smap/CorrectedBrightnessTemperature.md +++ /dev/null @@ -1,12 +0,0 @@ -### Corrected Brightness Temperature (L3, Passive, H Polarization | V Polarization) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) "Corrected Brightness Temperature" layers display brightness temperatures (TBs) in Kelvin (K) for the horizontal (H) and vertical (V) polarizations from the SMAP radiometer. - -The TBs are corrected for the presence of water wherever water fraction is below a threshold. At the L-band frequency used by SMAP, the TB of the land surface is proportional to its emissivity multiplied by its physical temperature. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. The imagery resolution is 2 km and sensor resolution is 36 km. The temporal resolution is daily. - -References: [SMAP L3 Radiometer Global Daily 36 km EASE-Grid Soil Moisture](https://nsidc.org/data/SPL3SMP) - -Data fields: `tb_h_corrected`, `tb_v_corrected` diff --git a/config/default/common/config/metadata/layers/smap/FreezeThaw.md b/config/default/common/config/metadata/layers/smap/FreezeThaw.md deleted file mode 100644 index 2145bb29b4..0000000000 --- a/config/default/common/config/metadata/layers/smap/FreezeThaw.md +++ /dev/null @@ -1,32 +0,0 @@ -### Freeze/Thaw 36 km (L3, Passive) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) “Freeze/Thaw 36 km (L3, Passive)” layer displays freeze/thaw state posted on a 36 km EASE-Grid 2.0 for Global land suface areas and land surface areas north of 45°N latitude from the SMAP radiometer. Freeze/thaw is detected by identifying the temporal response of the normalized polarization ratio (NPR) of the brightness temperature, which is sensitive to changes in the dielectric constant of the landscape components that occur as the water within the components transitions between frozen and non-frozen conditions. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: [SMAP L3 Radiometer Global and Northern Hemisphere Daily 36 km EASE-Grid Freeze/Thaw State](https://nsidc.org/data/spl3ftp) - -Data field: `freeze_thaw` - -### Freeze/Thaw 9 km (L3, Passive) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) “Freeze/Thaw 9 km (L3, Passive)” layer displays freeze/thaw state for Global land surface areas and land surface areas north of 45°N latitude from the SMAP radiometer. To enhance the grid resolution, Backus-Gilbert optimal interpolation techniques are used to extract maximum information from SMAP antenna temperatures and convert them to brightness temperatures, which are posted to the 9 km EASE-Grid 2.0. Freeze/thaw is detected by identifying the temporal response of the normalized polarization ratio (NPR) of the brightness temperature, which is sensitive to changes in the dielectric constant of the landscape components that occur as the water within the components transitions between frozen and non-frozen conditions. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: [SMAP Enhanced L3 Radiometer Global and Northern Hemisphere Daily 9 km EASE-Grid Freeze/Thaw State](https://nsidc.org/data/spl3ftp_e) - -Data field: `freeze_thaw` - -### Freeze/Thaw 3 km (L3, Active) -Temporal coverage: 13 April 2015 - 7 July 2015 - -The Soil Moisture Active Passive (SMAP) “Freeze/Thaw 3 km (L3, Active)” layer displays freeze/thaw state posted on a 3 km EASE-Grid 2.0 for land surface areas north of 45°N latitude from the SMAP radar. Freeze/thaw is detected by identifying the temporal response of radar backscatter to changes in the dielectric constant of the landscape components that occur as the water within the components transitions between frozen and non-frozen conditions. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: [SMAP L3 Radar Northern Hemisphere Daily 3 km EASE-Grid Freeze/Thaw State](https://nsidc.org/data/spl3fta/) - -Data field: `freeze_thaw` diff --git a/config/default/common/config/metadata/layers/smap/RootZoneSoilMoisture.md b/config/default/common/config/metadata/layers/smap/RootZoneSoilMoisture.md deleted file mode 100644 index c8154e7fcb..0000000000 --- a/config/default/common/config/metadata/layers/smap/RootZoneSoilMoisture.md +++ /dev/null @@ -1,21 +0,0 @@ -### Root Zone Soil Moisture 9 km (L4, 12z Instantaneous, Model Value-Added) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) “Root Zone Soil Moisture 9 km (L4, 12z Instantaneous, Model Value-Added)” layer displays model-derived global estimates of root zone soil moisture of the top 100 cm of the soil column in m3/m3 posted on a 9 km EASE-Grid 2.0. Root zone soil moisture estimates are obtained by merging SMAP observations with estimates from a land surface model in a soil moisture ensemble Kalman data assimilation system. 12z (or 12:00:00 UTC) indicates the time of the analysis update. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: [SMAP L4 9 km EASE-Grid Surface and Root Zone Soil Moisture Analysis Update](https://nsidc.org/data/spl4smau/) - -Data field: `sm_rootzone_analysis` - -### Root Zone Soil Moisture 9 km Uncertainty (L4, 12z Instantaneous, Model Value-Added) -Temporal coverage: 31 March 2015 - present - -The Soil Moisture Active Passive (SMAP) “Root Zone Soil Moisture Uncertainty (L4,12z Instantaneous, Model Value-Added)” layer displays uncertainty estimates of model-derived global root zone soil moisture of the top 100 cm of the soil column in m3/m3 posted on a 9 km EASE-Grid 2.0. Root zone estimates are obtained by merging SMAP observations with estimates from a land surface model in a soil moisture ensemble data assimilation system. Error estimates are derived from the ensemble standard deviation of the analyzed fields. 12z (or 12:00:00 UTC) indicates the time of the analysis update. - -The SMAP spacecraft carries two instruments, a radar (active) and a radiometer (passive), that together make global measurements of land surface soil moisture and freeze/thaw state. It is useful for monitoring and predicting natural hazards such as floods and droughts, understanding the linkages between Earth’s water, energy and carbon cycles, and reducing uncertainties in predicting weather and climate. - -References: [SMAP L4 9 km EASE-Grid Surface and Root Zone Soil Moisture Analysis Update](https://nsidc.org/data/spl4smau/) - -Data field: `sm_rootzone_analysis_ensstd` From c2d2b55cd0526778607e487e6882afe510f969d4 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 20 Oct 2021 12:47:27 -0600 Subject: [PATCH 02/35] WIP --- .../date-selector/date-input-column.js | 6 ++-- .../components/date-selector/date-selector.js | 8 ++--- web/js/components/date-selector/util.js | 18 ++--------- web/js/components/sidebar/event.js | 2 +- .../custom-interval-selector.js | 12 +++---- .../components/timeline/mobile-date-picker.js | 6 ++-- .../timeline/timeline-axis/timeline-axis.js | 4 +-- .../axis-timescale-change.js | 6 ++-- .../timescale-interval-change.js | 10 +++--- .../timeline-coverage/coverage-item-list.js | 6 ++-- web/js/containers/animation-widget.js | 14 ++++---- web/js/containers/gif.js | 15 +++++---- web/js/containers/image-download.js | 6 ++-- web/js/containers/sidebar/events.js | 1 + web/js/containers/timeline/timeline.js | 19 +++++------ web/js/location.js | 11 +++---- web/js/modules/animation/actions.js | 6 ++-- web/js/modules/animation/selectors.js | 10 +++--- web/js/modules/date/constants.js | 32 +++++++++---------- web/js/modules/date/selectors.js | 4 +-- web/js/modules/layers/selectors.js | 5 +++ web/js/util/util.js | 28 ++++------------ 22 files changed, 102 insertions(+), 127 deletions(-) diff --git a/web/js/components/date-selector/date-input-column.js b/web/js/components/date-selector/date-input-column.js index 783338839f..57706613f1 100644 --- a/web/js/components/date-selector/date-input-column.js +++ b/web/js/components/date-selector/date-input-column.js @@ -8,8 +8,8 @@ import { dayValidation, hourValidation, minuteValidation, - monthStringArray, } from './util'; +import { MONTH_STRING_ARRAY } from '../../modules/date/constants'; /* * DateInputColumn used in DateSelector within @@ -191,7 +191,7 @@ class DateInputColumn extends Component { // transform month number to string (e.g., 3 -> 'MAR') // eslint-disable-next-line no-restricted-globals if (newDate !== null && !isNaN(value)) { - value = monthStringArray[value - 1]; + value = MONTH_STRING_ARRAY[value - 1]; } break; case 'day': @@ -254,7 +254,7 @@ class DateInputColumn extends Component { // eslint-disable-next-line no-restricted-globals if (type === 'month' && !isNaN(newValue)) { - newValue = monthStringArray[newValue - 1]; + newValue = MONTH_STRING_ARRAY[newValue - 1]; } else if (newValue.length === 1) { newValue = `0${newValue}`; } diff --git a/web/js/components/date-selector/date-selector.js b/web/js/components/date-selector/date-selector.js index 063645af9d..23dbcb8cc0 100644 --- a/web/js/components/date-selector/date-selector.js +++ b/web/js/components/date-selector/date-selector.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import DateInputColumn from './date-input-column'; import util from '../../util/util'; -import { monthStringArray } from './util'; +import { MONTH_STRING_ARRAY } from '../../modules/date/constants'; /* * DateSelector used within Timeline and AnimationWidget. @@ -168,7 +168,7 @@ class DateSelector extends Component { } if (month) { - const realMonth = util.stringInArray(monthStringArray, month); + const realMonth = util.stringInArray(dAY, month); const maxDatePrev = new Date( date.getUTCFullYear(), date.getUTCMonth() + 1, @@ -221,7 +221,7 @@ class DateSelector extends Component { let dateCheck; if (day <= maxDayDate) { - const realMonth = util.stringInArray(monthStringArray, month); + const realMonth = util.stringInArray(MONTH_STRING_ARRAY, month); date = new Date(new Date(date).setUTCDate(day)); dateCheck = new Date(inputDate); dateCheck = new Date(new Date(date).setUTCDate(1)); @@ -370,7 +370,7 @@ class DateSelector extends Component { }; const yearValue = year || date.getUTCFullYear(); - const monthValue = month || monthStringArray[date.getUTCMonth()]; + const monthValue = month || MONTH_STRING_ARRAY[date.getUTCMonth()]; const dayValue = day || util.pad(date.getUTCDate(), 2, '0'); const hourValue = hour || util.pad(date.getUTCHours(), 2, '0'); const minuteValue = minute || util.pad(date.getUTCMinutes(), 2, '0'); diff --git a/web/js/components/date-selector/util.js b/web/js/components/date-selector/util.js index 6565c02df9..876f802491 100644 --- a/web/js/components/date-selector/util.js +++ b/web/js/components/date-selector/util.js @@ -1,19 +1,5 @@ import util from '../../util/util'; - -export const monthStringArray = [ - 'JAN', - 'FEB', - 'MAR', - 'APR', - 'MAY', - 'JUN', - 'JUL', - 'AUG', - 'SEP', - 'OCT', - 'NOV', - 'DEC', -]; +import { MONTH_STRING_ARRAY } from '../../modules/date/constants'; /** * @@ -47,7 +33,7 @@ export const monthValidation = (value, dateParam, validate) => { } return null; } - const realMonth = util.stringInArray(monthStringArray, value); + const realMonth = util.stringInArray(MONTH_STRING_ARRAY, value); if (realMonth !== false) { const day = date.getUTCDate(); const zeroDay = new Date(date.setUTCDate(1)); diff --git a/web/js/components/sidebar/event.js b/web/js/components/sidebar/event.js index 7a262d754a..676fdaf6c2 100644 --- a/web/js/components/sidebar/event.js +++ b/web/js/components/sidebar/event.js @@ -66,7 +66,7 @@ function Event (props) { style={!isSelected ? { display: 'none' } : { display: 'block' }} > {event.geometry.map((geometry, index) => { - const date = geometry.date.split('T')[0]; + const date = util.toISOStringDateMonthAbbrev(new Date(geometry.date)); return (
  • { const { changeCustomInterval, customDelta } = this.props; - changeCustomInterval(customDelta, timeScaleToNumberKey[zoomLevel]); + changeCustomInterval(customDelta, TIME_SCALE_TO_NUMBER[zoomLevel]); } handleKeyPress= (e) => { @@ -94,7 +94,7 @@ class CustomIntervalSelector extends PureComponent { /> diff --git a/web/js/components/timeline/mobile-date-picker.js b/web/js/components/timeline/mobile-date-picker.js index 4dbeaa616b..afcd34d69c 100644 --- a/web/js/components/timeline/mobile-date-picker.js +++ b/web/js/components/timeline/mobile-date-picker.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import DatePicker from 'react-mobile-datepicker'; import { getDisplayDate, getISODateFormatted } from './date-util'; -import { monthMap } from '../../modules/date/constants'; +import { MONTH_STRING_ARRAY } from '../../modules/date/constants'; import HoverTooltip from '../util/hover-tooltip'; // https://www.npmjs.com/package/react-mobile-datepicker @@ -14,7 +14,7 @@ const defaultDateConfig = { step: 1, }, month: { - format: (value) => monthMap[value.getMonth() + 1], + format: (value) => MONTH_STRING_ARRAY[value.getMonth()], caption: 'Mon', step: 1, }, @@ -32,7 +32,7 @@ const subDailyDateConfig = { step: 1, }, month: { - format: (value) => monthMap[value.getMonth() + 1], + format: (value) => MONTH_STRING_ARRAY[value.getMonth()], caption: 'Mon', step: 1, }, diff --git a/web/js/components/timeline/timeline-axis/timeline-axis.js b/web/js/components/timeline/timeline-axis/timeline-axis.js index 1544bc64bb..429d00224a 100644 --- a/web/js/components/timeline/timeline-axis/timeline-axis.js +++ b/web/js/components/timeline/timeline-axis/timeline-axis.js @@ -11,7 +11,7 @@ import GridRange from './grid-range/grid-range'; import getTimeRange from './date-calc'; import { timeScaleOptions, - timeScaleToNumberKey, + TIME_SCALE_TO_NUMBER, } from '../../../modules/date/constants'; import { getIsBetween, @@ -346,7 +346,7 @@ class TimelineAxis extends Component { if (!isYearOrMonth) { // determine if changing timeScale from greater to lesser (e.g., 'year' to 'month') const greaterToLesserTimescale = timeScale && previousTimeScale - ? timeScaleToNumberKey[timeScale] < timeScaleToNumberKey[previousTimeScale] + ? TIME_SCALE_TO_NUMBER[timeScale] < TIME_SCALE_TO_NUMBER[previousTimeScale] : null; if (greaterToLesserTimescale) { diff --git a/web/js/components/timeline/timeline-controls/axis-timescale-change.js b/web/js/components/timeline/timeline-controls/axis-timescale-change.js index c64e6cffc9..23696deaeb 100644 --- a/web/js/components/timeline/timeline-controls/axis-timescale-change.js +++ b/web/js/components/timeline/timeline-controls/axis-timescale-change.js @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import AxisTimeScaleChangeControls from './axis-timescale-change-controls'; -import { timeScaleToNumberKey } from '../../../modules/date/constants'; +import { TIME_SCALE_TO_NUMBER } from '../../../modules/date/constants'; /* * Parent element for timeScale change controls and tooltip @@ -58,7 +58,7 @@ class AxisTimeScaleChange extends PureComponent { hasSubdailyLayers, timeScale, } = this.props; - const timeScaleNumber = timeScaleToNumberKey[timeScale]; + const timeScaleNumber = TIME_SCALE_TO_NUMBER[timeScale]; const maxTimeScaleNumber = hasSubdailyLayers ? 5 : 3; if (timeScaleNumber < maxTimeScaleNumber) { changeTimeScale(timeScaleNumber + 1); @@ -71,7 +71,7 @@ class AxisTimeScaleChange extends PureComponent { changeTimeScale, timeScale, } = this.props; - const timeScaleNumber = timeScaleToNumberKey[timeScale]; + const timeScaleNumber = TIME_SCALE_TO_NUMBER[timeScale]; if (timeScaleNumber > 1) { changeTimeScale(timeScaleNumber - 1); } diff --git a/web/js/components/timeline/timeline-controls/timescale-interval-change.js b/web/js/components/timeline/timeline-controls/timescale-interval-change.js index 212bee8a35..280bb718f8 100644 --- a/web/js/components/timeline/timeline-controls/timescale-interval-change.js +++ b/web/js/components/timeline/timeline-controls/timescale-interval-change.js @@ -2,8 +2,8 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { - timeScaleToNumberKey, - timeScaleFromNumberKey, + TIME_SCALE_TO_NUMBER, + TIME_SCALE_FROM_NUMBER, } from '../../../modules/date/constants'; import { toggleCustomModal as toggleCustomModalAction, @@ -105,7 +105,7 @@ class TimeScaleIntervalChange extends PureComponent { newTimeScale = customInterval; delta = customDelta; } else { - newTimeScale = Number(timeScaleToNumberKey[newTimeScale]); + newTimeScale = Number(TIME_SCALE_TO_NUMBER[newTimeScale]); delta = 1; } selectInterval(delta, newTimeScale, customSelected); @@ -115,7 +115,7 @@ class TimeScaleIntervalChange extends PureComponent { setCustomIntervalText = () => { const { customDelta, customInterval } = this.props; this.setState({ - customIntervalText: `${customDelta} ${timeScaleFromNumberKey[customInterval]}`, + customIntervalText: `${customDelta} ${TIME_SCALE_FROM_NUMBER[customInterval]}`, }); } @@ -150,7 +150,7 @@ class TimeScaleIntervalChange extends PureComponent { id="current-interval" className={`no-drag interval-btn interval-btn-active${customSelected ? ' custom-interval-text' : ''}`} > - {customSelected ? customIntervalText : `${1} ${timeScaleFromNumberKey[interval]}`} + {customSelected ? customIntervalText : `${1} ${TIME_SCALE_FROM_NUMBER[interval]}`} {/* hover timeScale unit dialog / entry point to Custom selector */} diff --git a/web/js/components/timeline/timeline-coverage/coverage-item-list.js b/web/js/components/timeline/timeline-coverage/coverage-item-list.js index b56c64f1d6..204adcca04 100644 --- a/web/js/components/timeline/timeline-coverage/coverage-item-list.js +++ b/web/js/components/timeline/timeline-coverage/coverage-item-list.js @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { datesInDateRanges } from '../../../modules/layers/util'; import util from '../../../util/util'; import { - timeScaleToNumberKey, + TIME_SCALE_TO_NUMBER, } from '../../../modules/date/constants'; import CoverageItemContainer from './coverage-item-container'; @@ -380,8 +380,8 @@ class CoverageItemList extends Component { let layerPeriod = this.getFormattedTimePeriod(period); // get layer scale number to determine relation to current axis zoom level - const timeScaleNumber = timeScaleToNumberKey[timeScale]; - const layerScaleNumber = timeScaleToNumberKey[layerPeriod]; + const timeScaleNumber = TIME_SCALE_TO_NUMBER[timeScale]; + const layerScaleNumber = TIME_SCALE_TO_NUMBER[layerPeriod]; const isLayerGreaterIncrementThanZoom = layerScaleNumber < timeScaleNumber; const isLayerEqualIncrementThanZoom = layerScaleNumber === timeScaleNumber; diff --git a/web/js/containers/animation-widget.js b/web/js/containers/animation-widget.js index 534a721598..f1de33bcea 100644 --- a/web/js/containers/animation-widget.js +++ b/web/js/containers/animation-widget.js @@ -29,8 +29,8 @@ import { toggleCustomModal, } from '../modules/date/actions'; import { - timeScaleFromNumberKey, - timeScaleToNumberKey, + TIME_SCALE_FROM_NUMBER, + TIME_SCALE_TO_NUMBER, customModalType, } from '../modules/date/constants'; import { @@ -39,7 +39,7 @@ import { snapToIntervalDelta, } from '../modules/animation/util'; import { - hasSubDaily as hasSubDailySelector, + subdailyLayersActive, getActiveLayers, getAllActiveLayers, dateRange as getDateRange, @@ -274,7 +274,7 @@ class AnimationWidget extends React.Component { timeScale = customInterval; delta = customDelta; } else { - timeScale = Number(timeScaleToNumberKey[timeScale]); + timeScale = Number(TIME_SCALE_TO_NUMBER[timeScale]); delta = 1; } onIntervalSelect(delta, timeScale, customSelected); @@ -651,7 +651,7 @@ function mapStateToProps(state) { customInterval, } = date; const activeLayers = getActiveLayers(state); - const hasSubdailyLayers = hasSubDailySelector(activeLayers); + const hasSubdailyLayers = subdailyLayersActive(state); const activeLayersForProj = getAllActiveLayers(state); const hasFutureLayers = activeLayersForProj.filter((layer) => layer.futureTime).length > 0; const layerDateRange = getDateRange({}, activeLayersForProj); @@ -687,7 +687,7 @@ function mapStateToProps(state) { const numberOfFrames = util.getNumberOfDays( startDate, endDate, - timeScaleFromNumberKey[useInterval], + TIME_SCALE_FROM_NUMBER[useInterval], customSelected && customDelta ? customDelta : delta, maxFrames, ); @@ -710,7 +710,7 @@ function mapStateToProps(state) { hasSubdailyLayers, subDailyMode, delta: customSelected && customDelta ? customDelta : delta, - interval: timeScaleFromNumberKey[useInterval] || 'day', + interval: TIME_SCALE_FROM_NUMBER[useInterval] || 'day', customDelta: customDelta || 1, customInterval: customInterval || 3, numberOfFrames, diff --git a/web/js/containers/gif.js b/web/js/containers/gif.js index 8a0c545504..3b04f946c3 100644 --- a/web/js/containers/gif.js +++ b/web/js/containers/gif.js @@ -20,11 +20,12 @@ import { imageUtilCalculateResolution, imageUtilGetCoordsFromPixelValues, } from '../modules/image-download/util'; -import { timeScaleFromNumberKey } from '../modules/date/constants'; +import { TIME_SCALE_FROM_NUMBER } from '../modules/date/constants'; import GifResults from '../components/animation-widget/gif-post-creation'; import getImageArray from '../modules/animation/selectors'; import { getStampProps, svgToPng } from '../modules/animation/util'; import { changeCropBounds } from '../modules/animation/actions'; +import { subdailyLayersActive } from '../modules/layers/selectors'; const DEFAULT_URL = 'http://localhost:3002/api/v1/snapshot'; const gifStream = new GifStream(); @@ -385,8 +386,8 @@ function mapStateToProps(state) { customSelected, interval, customInterval, customDelta, } = date; const increment = customSelected - ? `${customDelta} ${timeScaleFromNumberKey[customInterval]}` - : `1 ${timeScaleFromNumberKey[interval]}`; + ? `${customDelta} ${TIME_SCALE_FROM_NUMBER[customInterval]}` + : `1 ${TIME_SCALE_FROM_NUMBER[interval]}`; let url = DEFAULT_URL; if (config.features.imageDownload && config.features.imageDownload.url) { url = config.features.imageDownload.url; @@ -402,8 +403,8 @@ function mapStateToProps(state) { boundaries, proj: proj.selected, isActive: animation.gifActive, - startDate: util.toISOStringMinutes(startDate), - endDate: util.toISOStringMinutes(endDate), + startDate: util.toISOStringDateMonthAbbrev(startDate, subdailyLayersActive(state)), + endDate: util.toISOStringDateMonthAbbrev(endDate, subdailyLayersActive(state)), increment: `${increment} Between Frames`, speed, map, @@ -412,8 +413,8 @@ function mapStateToProps(state) { startDate, endDate, customSelected - ? timeScaleFromNumberKey[customInterval] - : timeScaleFromNumberKey[interval], + ? TIME_SCALE_FROM_NUMBER[customInterval] + : TIME_SCALE_FROM_NUMBER[interval], customSelected ? customDelta : 1, ), getImageArray: (gifComponentProps, gifComponentState, dimensions) => getImageArray( diff --git a/web/js/containers/image-download.js b/web/js/containers/image-download.js index 9681428abd..e81d247c6e 100644 --- a/web/js/containers/image-download.js +++ b/web/js/containers/image-download.js @@ -14,9 +14,8 @@ import { } from '../modules/image-download/util'; import util from '../util/util'; import { - hasSubDaily as hasSubDailySelector, getLayers, - getActiveLayers, + subdailyLayersActive, } from '../modules/layers/selectors'; import { getSelectedDate } from '../modules/date/selectors'; import { @@ -168,8 +167,7 @@ function mapStateToProps(state) { } = imageDownload; const { screenWidth, screenHeight } = browser; const markerCoordinates = locationSearch.coordinates; - const activeLayers = getActiveLayers(state); - const hasSubdailyLayers = hasSubDailySelector(activeLayers); + const hasSubdailyLayers = subdailyLayersActive(state); let url = DEFAULT_URL; if (config.features.imageDownload && config.features.imageDownload.url) { url = config.features.imageDownload.url; diff --git a/web/js/containers/sidebar/events.js b/web/js/containers/sidebar/events.js index 632f77b2e9..12d00717d5 100644 --- a/web/js/containers/sidebar/events.js +++ b/web/js/containers/sidebar/events.js @@ -1,3 +1,4 @@ + import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; diff --git a/web/js/containers/timeline/timeline.js b/web/js/containers/timeline/timeline.js index aeae5ae72c..8bfc9c35b8 100644 --- a/web/js/containers/timeline/timeline.js +++ b/web/js/containers/timeline/timeline.js @@ -34,7 +34,7 @@ import { } from '../../components/timeline/date-util'; import { dateRange as getDateRange, - hasSubDaily, + subdailyLayersActive, getActiveLayers, } from '../../modules/layers/selectors'; import { getSelectedDate, getDeltaIntervalUnit } from '../../modules/date/selectors'; @@ -61,8 +61,8 @@ import { changeEndDate, } from '../../modules/animation/actions'; import { - timeScaleFromNumberKey, - timeScaleToNumberKey, + TIME_SCALE_FROM_NUMBER, + TIME_SCALE_TO_NUMBER, timeScaleOptions, customModalType, } from '../../modules/date/constants'; @@ -492,7 +492,7 @@ class Timeline extends React.Component { const { hasSubdailyLayers, timeScale } = this.props; // prevent left/right arrows changing date within inputs if (e.target.tagName !== 'INPUT' && e.target.className !== 'rc-slider-handle' && !e.ctrlKey && !e.metaKey && !isTimelineDragging) { - const timeScaleNumber = Number(timeScaleToNumberKey[timeScale]); + const timeScaleNumber = Number(TIME_SCALE_TO_NUMBER[timeScale]); const maxTimeScaleNumber = hasSubdailyLayers ? 5 : 3; if (e.keyCode === 38) { e.preventDefault(); @@ -544,7 +544,7 @@ class Timeline extends React.Component { timeScale, hasSubdailyLayers, } = this.props; - const timeScaleNumber = Number(timeScaleToNumberKey[timeScale]); + const timeScaleNumber = Number(TIME_SCALE_TO_NUMBER[timeScale]); const maxTimeScaleNumber = hasSubdailyLayers ? 5 : 3; // handle time scale change on y axis wheel event @@ -1303,9 +1303,10 @@ function mapStateToProps(state) { const activeLayers = getActiveLayers(state); const projection = proj.id; const activeLayersFiltered = filterProjLayersWithStartDate(activeLayers, projection); - const hasSubdailyLayers = isCompareModeActive - ? hasSubDaily(layers.active.layers) || hasSubDaily(layers.activeB.layers) - : hasSubDaily(activeLayers); + // const hasSubdailyLayers = isCompareModeActive + // ? hasSubDaily(layers.active.layers) || hasSubDaily(layers.activeB.layers) + // : hasSubDaily(activeLayers); + const hasSubdailyLayers = subdailyLayersActive(state); // if future layers are included, timeline axis end date will extend past appNow const hasFutureLayers = checkHasFutureLayers(state); @@ -1378,7 +1379,7 @@ function mapStateToProps(state) { animEndLocationDate: animation.endDate, axisWidth: dimensionsAndOffsetValues.width, selectedDate, - timeScale: timeScaleFromNumberKey[updatedSelectedZoom.toString()], + timeScale: TIME_SCALE_FROM_NUMBER[updatedSelectedZoom.toString()], timeScaleChangeUnit: unit, customIntervalValue: customDelta || 1, customIntervalZoomLevel: updatedCustomInterval || 3, diff --git a/web/js/location.js b/web/js/location.js index c4c90c2945..88c07cc732 100644 --- a/web/js/location.js +++ b/web/js/location.js @@ -33,7 +33,7 @@ import { serializeGroupOverlays, mapLocationToLayerState, } from './modules/layers/util'; -import { resetLayers, hasSubDaily, getActiveLayers } from './modules/layers/selectors'; +import { resetLayers, subdailyLayersActive } from './modules/layers/selectors'; import { getInitialEventsState } from './modules/natural-events/reducers'; import { mapLocationToPaletteState } from './modules/palettes/util'; import { mapLocationToEmbedState } from './modules/embed/util'; @@ -177,8 +177,7 @@ const getParameters = function(config, parameters) { let zoom = currentItemState; // check if subdaily timescale zoom to determine if reset is needed if (zoom > 3) { - const hasSubdailyLayers = hasSubDaily(getActiveLayers(state)); - if (!hasSubdailyLayers) { + if (!subdailyLayersActive(state)) { zoom = 3; // reset to day } } @@ -196,8 +195,7 @@ const getParameters = function(config, parameters) { let interval = currentItemState; // check if subdaily timescale zoom to determine if reset is needed if (interval > 3) { - const hasSubdailyLayers = hasSubDaily(getActiveLayers(state)); - if (!hasSubdailyLayers) { + if (!subdailyLayersActive(state)) { interval = 3; // reset to day } } @@ -233,8 +231,7 @@ const getParameters = function(config, parameters) { let customInterval = currentItemState; // check if subdaily customInterval to determine if reset is needed if (customInterval > 3) { - const hasSubdailyLayers = hasSubDaily(getActiveLayers(state)); - if (!hasSubdailyLayers) { + if (!subdailyLayersActive(state)) { customInterval = 3; // reset to day } } diff --git a/web/js/modules/animation/actions.js b/web/js/modules/animation/actions.js index e6fbee57af..dc693d611a 100644 --- a/web/js/modules/animation/actions.js +++ b/web/js/modules/animation/actions.js @@ -12,7 +12,7 @@ import { TOGGLE_GIF, } from './constants'; import util from '../../util/util'; -import { timeScaleFromNumberKey } from '../date/constants'; +import { TIME_SCALE_FROM_NUMBER } from '../date/constants'; import { getSelectedDate } from '../date/selectors'; export function onActivate() { @@ -24,8 +24,8 @@ export function onActivate() { const activeDate = getSelectedDate(getState()); if (!animation.startDate || !animation.endDate) { const timeScaleChangeUnit = customSelected - ? timeScaleFromNumberKey[customInterval] - : timeScaleFromNumberKey[interval]; + ? TIME_SCALE_FROM_NUMBER[customInterval] + : TIME_SCALE_FROM_NUMBER[interval]; const deltaChangeAmt = customSelected ? customDelta : delta; const tenFrameDelta = 10 * deltaChangeAmt; const tenFramesBefore = util.dateAdd(activeDate, timeScaleChangeUnit, -tenFrameDelta); diff --git a/web/js/modules/animation/selectors.js b/web/js/modules/animation/selectors.js index abcbd8d1ca..9fc468c444 100644 --- a/web/js/modules/animation/selectors.js +++ b/web/js/modules/animation/selectors.js @@ -4,8 +4,8 @@ import { imageUtilGetCoordsFromPixelValues, getDownloadUrl, } from '../image-download/util'; -import { getActiveLayers, getLayers, hasSubDaily } from '../layers/selectors'; -import { timeScaleFromNumberKey } from '../date/constants'; +import { subdailyLayersActive, getLayers } from '../layers/selectors'; +import { TIME_SCALE_FROM_NUMBER } from '../date/constants'; /* * loops through dates and created image * download urls and pushs them to an @@ -35,7 +35,7 @@ export default function getImageArray( const fromDate = new Date(startDate); const toDate = new Date(endDate); const markerCoordinates = locationSearch.coordinates; - const isSubDaily = hasSubDaily(getActiveLayers(state)); + const isSubDaily = subdailyLayersActive(state); let current = fromDate; let j = 0; let src; @@ -43,8 +43,8 @@ export default function getImageArray( let products; const useDelta = customSelected && customDelta ? customDelta : delta; const increment = customSelected - ? timeScaleFromNumberKey[customInterval] - : timeScaleFromNumberKey[interval]; + ? TIME_SCALE_FROM_NUMBER[customInterval] + : TIME_SCALE_FROM_NUMBER[interval]; while (current <= toDate) { j += 1; diff --git a/web/js/modules/date/constants.js b/web/js/modules/date/constants.js index af5a273508..274e64746a 100644 --- a/web/js/modules/date/constants.js +++ b/web/js/modules/date/constants.js @@ -10,21 +10,21 @@ export const ARROW_UP = 'DATE/ARROW_UP'; export const SET_PRELOAD = 'DATE/SET_PRELOAD'; export const CLEAR_PRELOAD = 'DATE/CLEAR_PRELOAD'; -export const monthMap = { - 1: 'Jan', - 2: 'Feb', - 3: 'Mar', - 4: 'Apr', - 5: 'May', - 6: 'Jun', - 7: 'Jul', - 8: 'Aug', - 9: 'Sep', - 10: 'Oct', - 11: 'Nov', - 12: 'Dec', -}; -export const timeScaleFromNumberKey = { +export const MONTH_STRING_ARRAY = [ + 'JAN', + 'FEB', + 'MAR', + 'APR', + 'MAY', + 'JUN', + 'JUL', + 'AUG', + 'SEP', + 'OCT', + 'NOV', + 'DEC', +]; +export const TIME_SCALE_FROM_NUMBER = { 0: 'custom', 1: 'year', 2: 'month', @@ -32,7 +32,7 @@ export const timeScaleFromNumberKey = { 4: 'hour', 5: 'minute', }; -export const timeScaleToNumberKey = { +export const TIME_SCALE_TO_NUMBER = { custom: 0, year: 1, month: 2, diff --git a/web/js/modules/date/selectors.js b/web/js/modules/date/selectors.js index 562417a166..e59b581c56 100644 --- a/web/js/modules/date/selectors.js +++ b/web/js/modules/date/selectors.js @@ -1,4 +1,4 @@ -import { timeScaleFromNumberKey } from './constants'; +import { TIME_SCALE_FROM_NUMBER } from './constants'; export function getDates (state) { const { date } = state; @@ -20,7 +20,7 @@ export function getDeltaIntervalUnit (state) { } = state.date; const useDelta = customSelected ? customDelta : delta; const useInterval = customSelected ? customInterval : interval; - const changeUnit = timeScaleFromNumberKey[useInterval]; + const changeUnit = TIME_SCALE_FROM_NUMBER[useInterval]; return { delta: useDelta, diff --git a/web/js/modules/layers/selectors.js b/web/js/modules/layers/selectors.js index 02e13b1cfb..09436d56ed 100644 --- a/web/js/modules/layers/selectors.js +++ b/web/js/modules/layers/selectors.js @@ -214,6 +214,11 @@ export function hasSubDaily(layers) { return false; } +export const subdailyLayersActive = () => createSelector( + [getActiveLayers], + (layers) => hasSubDaily(layers), +); + export function addLayer(id, spec = {}, layersParam, layerConfig, overlayLength, projection, groupOverlays) { let layers = lodashCloneDeep(layersParam); if (projection) { diff --git a/web/js/util/util.js b/web/js/util/util.js index de1c17c7f3..0f50f1a87e 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -7,6 +7,7 @@ import browser from './browser'; import events from './events'; import load from './load'; import safeLocalStorage from './local-storage'; +import { MONTH_STRING_ARRAY } from '../modules/date/constants'; const { COORDINATE_FORMAT } = safeLocalStorage.keys; @@ -34,6 +35,7 @@ export default (function(self) { } return value; }; + /** * Creates an object representation of a query string. * @@ -232,7 +234,7 @@ export default (function(self) { const parsedDate = this.parseDate(date); switch (period) { case 'subdaily': - dateString = `${moment(parsedDate).format('DD MMMM YYYY HH:mm')}Z`; + dateString = `${moment(parsedDate).format('YYYY MMMM DD HH:mm')}Z`; break; case 'yearly': @@ -242,11 +244,11 @@ export default (function(self) { case 'monthly': if (dateType === 'END-DATE') parsedDate.setMonth(parsedDate.getMonth() - 1); - dateString = moment(parsedDate).format('MMMM YYYY'); + dateString = moment(parsedDate).format('YYYY MMM'); break; default: - dateString = moment(parsedDate).format('DD MMMM YYYY'); + dateString = moment(parsedDate).format('YYYY MMM DD '); break; } @@ -279,25 +281,9 @@ export default (function(self) { * @return {string} ISO string in the form of ``YYYY-MM-DD``. */ self.toISOStringDate = function(date) { - return date.toISOString() - .split('T')[0]; + return date.toISOString().split('T')[0]; }; - self.monthStringArray = [ - 'JAN', - 'FEB', - 'MAR', - 'APR', - 'MAY', - 'JUN', - 'JUL', - 'AUG', - 'SEP', - 'OCT', - 'NOV', - 'DEC', - ]; - /** * Converts a date into an ISO string with only the date portion and month abbreviation. * @@ -313,7 +299,7 @@ export default (function(self) { const month = stringDate[1]; const day = stringDate[2]; - const monthAbbrev = self.monthStringArray[Number(month) - 1]; + const monthAbbrev = MONTH_STRING_ARRAY[Number(month) - 1]; if (hasSubdaily) { return `${year} ${monthAbbrev} ${day} ${self.toHourMinutes(date)}Z`; From 858466addcd00d2f01e6d7b49725eb9535f14a1e Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Thu, 21 Oct 2021 09:53:03 -0600 Subject: [PATCH 03/35] format event title card date --- web/js/components/sidebar/event.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/web/js/components/sidebar/event.js b/web/js/components/sidebar/event.js index 676fdaf6c2..2a1053c33f 100644 --- a/web/js/components/sidebar/event.js +++ b/web/js/components/sidebar/event.js @@ -17,11 +17,7 @@ function Event (props) { sources, } = props; const eventDate = util.parseDateUTC(event.geometry[0].date); - const weekday = util.giveWeekDay(eventDate); - const day = eventDate.getUTCDate(); - const month = util.giveMonth(eventDate); - const year = eventDate.getUTCFullYear(); - const dateString = `${weekday}, ${month} ${day}, ${year}`; + const dateString = util.toISOStringDateMonthAbbrev(eventDate); const itemClass = isSelected ? 'item-selected selectorItem item' : 'selectorItem item'; From 5c8b76ae76cd826764c04cea7e9148b1e88245d7 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Thu, 21 Oct 2021 10:00:06 -0600 Subject: [PATCH 04/35] uppercase months --- web/js/components/layer/info/info.js | 8 ++------ web/js/util/util.js | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/web/js/components/layer/info/info.js b/web/js/components/layer/info/info.js index f1f2882400..9b66acef29 100644 --- a/web/js/components/layer/info/info.js +++ b/web/js/components/layer/info/info.js @@ -4,10 +4,6 @@ import { dateOverlap } from '../../../modules/layers/util'; import DateRanges from './date-ranges'; import util from '../../../util/util'; -function configureTemporalDate(dateType, date, period) { - return util.coverageDateFormatter(dateType, date, period); -} - export default function LayerInfo ({ layer, measurementDescriptionPath }) { const { dateRanges, @@ -63,13 +59,13 @@ export default function LayerInfo ({ layer, measurementDescriptionPath }) { {startDate ? `Temporal coverage: ${ - configureTemporalDate('START-DATE', startDate, period)}` + util.coverageDateFormatter('START-DATE', startDate, period)}` : ''} {startDate && endDate ? ` - ${ - configureTemporalDate('END-DATE', endDate, period)}` + util.coverageDateFormatter('END-DATE', endDate, period)}` : startDate ? ' - Present' : ''} diff --git a/web/js/util/util.js b/web/js/util/util.js index 0f50f1a87e..3381ee5c8c 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -234,7 +234,7 @@ export default (function(self) { const parsedDate = this.parseDate(date); switch (period) { case 'subdaily': - dateString = `${moment(parsedDate).format('YYYY MMMM DD HH:mm')}Z`; + dateString = `${moment(parsedDate).format('YYYY MMMM DD HH:mm')}Z`.toUpperCase(); break; case 'yearly': @@ -244,11 +244,11 @@ export default (function(self) { case 'monthly': if (dateType === 'END-DATE') parsedDate.setMonth(parsedDate.getMonth() - 1); - dateString = moment(parsedDate).format('YYYY MMM'); + dateString = moment(parsedDate).format('YYYY MMM').toUpperCase(); break; default: - dateString = moment(parsedDate).format('YYYY MMM DD '); + dateString = moment(parsedDate).format('YYYY MMM DD ').toUpperCase(); break; } From 29d4f6e4f6e5b65f65f2bf34afdb3e90319e6d44 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Thu, 21 Oct 2021 10:34:02 -0600 Subject: [PATCH 05/35] fix selector, undo timeline changes --- web/js/components/date-selector/date-selector.js | 2 +- web/js/containers/timeline/timeline.js | 9 +++++---- web/js/modules/layers/selectors.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/js/components/date-selector/date-selector.js b/web/js/components/date-selector/date-selector.js index 23dbcb8cc0..10a8a70374 100644 --- a/web/js/components/date-selector/date-selector.js +++ b/web/js/components/date-selector/date-selector.js @@ -168,7 +168,7 @@ class DateSelector extends Component { } if (month) { - const realMonth = util.stringInArray(dAY, month); + const realMonth = util.stringInArray(day, month); const maxDatePrev = new Date( date.getUTCFullYear(), date.getUTCMonth() + 1, diff --git a/web/js/containers/timeline/timeline.js b/web/js/containers/timeline/timeline.js index 8bfc9c35b8..2050bd3134 100644 --- a/web/js/containers/timeline/timeline.js +++ b/web/js/containers/timeline/timeline.js @@ -34,6 +34,7 @@ import { } from '../../components/timeline/date-util'; import { dateRange as getDateRange, + hasSubDaily, subdailyLayersActive, getActiveLayers, } from '../../modules/layers/selectors'; @@ -1303,10 +1304,10 @@ function mapStateToProps(state) { const activeLayers = getActiveLayers(state); const projection = proj.id; const activeLayersFiltered = filterProjLayersWithStartDate(activeLayers, projection); - // const hasSubdailyLayers = isCompareModeActive - // ? hasSubDaily(layers.active.layers) || hasSubDaily(layers.activeB.layers) - // : hasSubDaily(activeLayers); - const hasSubdailyLayers = subdailyLayersActive(state); + const hasSubdailyLayers = isCompareModeActive + ? hasSubDaily(layers.active.layers) || hasSubDaily(layers.activeB.layers) + : subdailyLayersActive(state); + // if future layers are included, timeline axis end date will extend past appNow const hasFutureLayers = checkHasFutureLayers(state); diff --git a/web/js/modules/layers/selectors.js b/web/js/modules/layers/selectors.js index 09436d56ed..03d47cf039 100644 --- a/web/js/modules/layers/selectors.js +++ b/web/js/modules/layers/selectors.js @@ -214,7 +214,7 @@ export function hasSubDaily(layers) { return false; } -export const subdailyLayersActive = () => createSelector( +export const subdailyLayersActive = createSelector( [getActiveLayers], (layers) => hasSubDaily(layers), ); From a755360cc203a799188c60da1ca3d0e531c4b42e Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Thu, 28 Oct 2021 11:20:50 -0600 Subject: [PATCH 06/35] fix some e2e --- e2e/features/animation/gif-test.js | 4 +-- e2e/features/timeline/timeline-mobile-test.js | 36 +++++++++---------- .../components/date-selector/date-selector.js | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/e2e/features/animation/gif-test.js b/e2e/features/animation/gif-test.js index 1a2fb44ee2..01cd7426b9 100644 --- a/e2e/features/animation/gif-test.js +++ b/e2e/features/animation/gif-test.js @@ -79,13 +79,13 @@ module.exports = { .useCss() .assert.containsText( localSelectors.gifPreviewStartDate, - '2018-03-28', + '2018 MAR 28', ); client .useCss() .assert.containsText( localSelectors.gifPreviewEndDate, - '2018-04-04', + '2018 APR 04', ); client .useCss() diff --git a/e2e/features/timeline/timeline-mobile-test.js b/e2e/features/timeline/timeline-mobile-test.js index 58d673cbfd..3ccc44cd77 100644 --- a/e2e/features/timeline/timeline-mobile-test.js +++ b/e2e/features/timeline/timeline-mobile-test.js @@ -56,16 +56,16 @@ module.exports = { c.assert.containsText(datepickerHeader, '2012 MAR 15'); }, 'date.mob.range.4: Date in header should be 2012 JAN 15 after month drag': (c) => { - c.moveToElement('xpath', "//li[text()='Mar']", 10, 10) + c.moveToElement('xpath', "//li[text()='MAR']", 10, 10) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Apr']", 10, 10) + .moveToElement('xpath', "//li[text()='APR']", 10, 10) .pause(300) .mouseButtonUp(0) - .moveToElement('xpath', "//li[text()='Feb']", 10, 10) + .moveToElement('xpath', "//li[text()='FEB']", 10, 10) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Mar']", 10, 10) + .moveToElement('xpath', "//li[text()='MAR']", 10, 10) .pause(300) .mouseButtonUp(0); c.pause(300); @@ -128,34 +128,34 @@ module.exports = { }); }, 'date.mob.nav.3: Date in header should be 2013 FEB 20 after year drag': (c) => { - c.moveToElement('xpath', "//li[text()='Jul']", 15, 15) + c.moveToElement('xpath', "//li[text()='JUL']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Aug']", 10, 10) + .moveToElement('xpath', "//li[text()='AUG']", 10, 10) .pause(300) .mouseButtonUp(0) - .moveToElement('xpath', "//li[text()='Jun']", 15, 15) + .moveToElement('xpath', "//li[text()='JUN']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Jul']", 10, 10) + .moveToElement('xpath', "//li[text()='JUL']", 10, 10) .pause(300) .mouseButtonUp(0) - .moveToElement('xpath', "//li[text()='May']", 15, 15) + .moveToElement('xpath', "//li[text()='MAY']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Jun']", 10, 10) + .moveToElement('xpath', "//li[text()='JUN']", 10, 10) .pause(300) .mouseButtonUp(0) - .moveToElement('xpath', "//li[text()='Apr']", 15, 15) + .moveToElement('xpath', "//li[text()='APR']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='May']", 10, 10) + .moveToElement('xpath', "//li[text()='MAY']", 10, 10) .pause(300) .mouseButtonUp(0) - .moveToElement('xpath', "//li[text()='Mar']", 15, 15) + .moveToElement('xpath', "//li[text()='MAR']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Apr']", 10, 10) + .moveToElement('xpath', "//li[text()='APR']", 10, 10) .pause(300) .mouseButtonUp(0); c.pause(300); @@ -174,16 +174,16 @@ module.exports = { c.assert.containsText(datepickerHeader, '2014 FEB 20'); }, 'date.mob.nav.5: Date in header should be 2013 DEC 20 after year drag': (c) => { - c.moveToElement('xpath', "//li[text()='Feb']", 15, 15) + c.moveToElement('xpath', "//li[text()='FEB']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Mar']", 10, 10) + .moveToElement('xpath', "//li[text()='MAR']", 10, 10) .pause(300) .mouseButtonUp(0) - .moveToElement('xpath', "//li[text()='Jan']", 15, 15) + .moveToElement('xpath', "//li[text()='JAN']", 15, 15) .pause(300) .mouseButtonDown(0) - .moveToElement('xpath', "//li[text()='Feb']", 10, 10) + .moveToElement('xpath', "//li[text()='FEB']", 10, 10) .pause(300) .mouseButtonUp(0); c.pause(300); diff --git a/web/js/components/date-selector/date-selector.js b/web/js/components/date-selector/date-selector.js index 10a8a70374..f84de8a717 100644 --- a/web/js/components/date-selector/date-selector.js +++ b/web/js/components/date-selector/date-selector.js @@ -168,7 +168,7 @@ class DateSelector extends Component { } if (month) { - const realMonth = util.stringInArray(day, month); + const realMonth = util.stringInArray(MONTH_STRING_ARRAY, month); const maxDatePrev = new Date( date.getUTCFullYear(), date.getUTCMonth() + 1, From c85825d1650a40c894a3caf23c98e0425f8a426f Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 17 Nov 2021 11:06:17 -0700 Subject: [PATCH 07/35] consolidate date formatting functions - move various date utils to date/util.js --- web/js/components/layer/info/date-ranges.js | 6 +- web/js/components/layer/info/info.js | 6 +- web/js/components/selected-date.js | 4 +- web/js/components/sidebar/event.js | 8 +- web/js/components/timeline/date-util.js | 4 +- .../timeline-coverage/coverage-item-list.js | 5 +- .../timeline-coverage/coverage-line.js | 10 +- web/js/containers/gif.js | 5 +- web/js/containers/sidebar/events.js | 7 +- web/js/containers/sidebar/layer-row.js | 5 +- web/js/containers/sidebar/smart-handoff.js | 3 +- web/js/map/natural-events/event-track.js | 7 +- web/js/modules/compare/util.js | 6 +- web/js/modules/date/util.js | 76 ++++++++++++ web/js/modules/image-download/util.js | 5 +- web/js/modules/layers/util.js | 9 +- .../modules/product-picker/format-config.js | 4 +- .../modules/product-picker/search-config.js | 4 +- web/js/util/util.js | 111 +----------------- 19 files changed, 133 insertions(+), 152 deletions(-) diff --git a/web/js/components/layer/info/date-ranges.js b/web/js/components/layer/info/date-ranges.js index 59d00f8b83..57de7f6753 100644 --- a/web/js/components/layer/info/date-ranges.js +++ b/web/js/components/layer/info/date-ranges.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ListGroup, ListGroupItem } from 'reactstrap'; -import util from '../../../util/util'; import Scrollbar from '../../util/scrollbar'; +import { coverageDateFormatter } from '../../../modules/date/util'; export default class DateRanges extends React.Component { constructor(props) { @@ -19,10 +19,10 @@ export default class DateRanges extends React.Component { let listItemStartDate; let listItemEndDate; if (l.startDate) { - listItemStartDate = util.coverageDateFormatter('START-DATE', l.startDate, layer.period); + listItemStartDate = coverageDateFormatter('START-DATE', l.startDate, layer.period); } if (l.endDate) { - listItemEndDate = util.coverageDateFormatter('END-DATE', l.endDate, layer.period); + listItemEndDate = coverageDateFormatter('END-DATE', l.endDate, layer.period); } return ( diff --git a/web/js/components/layer/info/info.js b/web/js/components/layer/info/info.js index 9b66acef29..89695f0125 100644 --- a/web/js/components/layer/info/info.js +++ b/web/js/components/layer/info/info.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { dateOverlap } from '../../../modules/layers/util'; import DateRanges from './date-ranges'; -import util from '../../../util/util'; +import { coverageDateFormatter } from '../../../modules/date/util'; export default function LayerInfo ({ layer, measurementDescriptionPath }) { const { @@ -59,13 +59,13 @@ export default function LayerInfo ({ layer, measurementDescriptionPath }) { {startDate ? `Temporal coverage: ${ - util.coverageDateFormatter('START-DATE', startDate, period)}` + coverageDateFormatter('START-DATE', startDate, period)}` : ''} {startDate && endDate ? ` - ${ - util.coverageDateFormatter('END-DATE', endDate, period)}` + coverageDateFormatter('END-DATE', endDate, period)}` : startDate ? ' - Present' : ''} diff --git a/web/js/components/selected-date.js b/web/js/components/selected-date.js index 6de21596a3..7820634744 100644 --- a/web/js/components/selected-date.js +++ b/web/js/components/selected-date.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; -import moment from 'moment'; import { getSelectedDate } from '../modules/date/selectors'; +import { formatDisplayDate } from '../modules/date/util'; // A simple component to re-use anywhere we want to display the selected date const SelectedDate = ({ selectedDate }) => (<>{selectedDate}); @@ -14,7 +14,7 @@ SelectedDate.propTypes = { const mapStateToProps = (state) => { const selectedDate = getSelectedDate(state); return { - selectedDate: moment.utc(selectedDate).format('YYYY MMM DD'), + selectedDate: formatDisplayDate(selectedDate), }; }; diff --git a/web/js/components/sidebar/event.js b/web/js/components/sidebar/event.js index 2a1053c33f..3bbf331bfc 100644 --- a/web/js/components/sidebar/event.js +++ b/web/js/components/sidebar/event.js @@ -6,6 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { getDefaultEventDate } from '../../modules/natural-events/util'; import util from '../../util/util'; import EventIcon from './event-icon'; +import { formatDisplayDate } from '../../modules/date/util'; function Event (props) { const { @@ -16,8 +17,7 @@ function Event (props) { selectEvent, sources, } = props; - const eventDate = util.parseDateUTC(event.geometry[0].date); - const dateString = util.toISOStringDateMonthAbbrev(eventDate); + const dateString = formatDisplayDate(event.geometry[0].date); const itemClass = isSelected ? 'item-selected selectorItem item' : 'selectorItem item'; @@ -62,7 +62,7 @@ function Event (props) { style={!isSelected ? { display: 'none' } : { display: 'block' }} > {event.geometry.map((geometry, index) => { - const date = util.toISOStringDateMonthAbbrev(new Date(geometry.date)); + const date = util.toISOStringDate(geometry.date); return (
  • - {date} + {formatDisplayDate(date)}
  • ); diff --git a/web/js/components/timeline/date-util.js b/web/js/components/timeline/date-util.js index 6202249ea0..a485bd1c3c 100644 --- a/web/js/components/timeline/date-util.js +++ b/web/js/components/timeline/date-util.js @@ -1,4 +1,4 @@ -import util from '../../util/util'; +import { formatDisplayDate } from '../../modules/date/util'; // return boolean based on is dateChecking is between front and back dates export const getIsBetween = (date, frontDate, backDate) => { @@ -22,7 +22,7 @@ export const getISODateFormatted = (date) => `${new Date(date).toISOString().spl // display date as '2000 OCT 28' for default or '2000 OCT 28 20:28Z' for subdaily export const getDisplayDate = (date, isSubdaily) => { - const displayDate = util.toISOStringDateMonthAbbrev(new Date(date), isSubdaily); + const displayDate = formatDisplayDate(new Date(date), isSubdaily); return displayDate; }; diff --git a/web/js/components/timeline/timeline-coverage/coverage-item-list.js b/web/js/components/timeline/timeline-coverage/coverage-item-list.js index 204adcca04..e5e82f55cc 100644 --- a/web/js/components/timeline/timeline-coverage/coverage-item-list.js +++ b/web/js/components/timeline/timeline-coverage/coverage-item-list.js @@ -8,6 +8,7 @@ import { TIME_SCALE_TO_NUMBER, } from '../../../modules/date/constants'; import CoverageItemContainer from './coverage-item-container'; +import { formatDisplayDate } from '../../../modules/date/util'; const { events } = util; @@ -172,7 +173,7 @@ class CoverageItemList extends Component { } = layer; let dateRangeStart; if (startDate) { - dateRangeStart = util.toISOStringDateMonthAbbrev(new Date(startDate)); + dateRangeStart = formatDisplayDate(new Date(startDate)); } else { dateRangeStart = 'Start'; } @@ -180,7 +181,7 @@ class CoverageItemList extends Component { // get end date -or- 'present' let dateRangeEnd; if (endDate) { - dateRangeEnd = util.toISOStringDateMonthAbbrev(new Date(endDate)); + dateRangeEnd = formatDisplayDate(new Date(endDate)); } else { dateRangeEnd = 'Present'; } diff --git a/web/js/components/timeline/timeline-coverage/coverage-line.js b/web/js/components/timeline/timeline-coverage/coverage-line.js index 328aa449b4..327af697ec 100644 --- a/web/js/components/timeline/timeline-coverage/coverage-line.js +++ b/web/js/components/timeline/timeline-coverage/coverage-line.js @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import util from '../../../util/util'; +import { formatDisplayDate } from '../../../modules/date/util'; /* * Coverage Line for DOM Element layer coverage. @@ -28,8 +28,8 @@ class CoverageLine extends PureComponent { // eslint-disable-next-line default-case switch (lineType) { case 'SINGLE': - dateRangeStart = (startDate && util.toISOStringDateMonthAbbrev(new Date(startDate))) || 'Start'; - dateRangeEnd = (endDate && util.toISOStringDateMonthAbbrev(new Date(endDate))) || 'Present'; + dateRangeStart = (startDate && formatDisplayDate(new Date(startDate))) || 'Start'; + dateRangeEnd = (endDate && formatDisplayDate(new Date(endDate))) || 'Present'; toolTipText = `${dateRangeStart} to ${dateRangeEnd}`; break; case 'MULTI': @@ -43,8 +43,8 @@ class CoverageLine extends PureComponent { dateRangeStart = dateRangeStart.replace(/[.:]/g, '_'); dateRangeEnd = dateRangeEnd.replace(/[.:]/g, '_'); } else { - dateRangeStart = util.toISOStringDateMonthAbbrev(new Date(startDate)); - dateRangeEnd = util.toISOStringDateMonthAbbrev(new Date(endDate)); + dateRangeStart = formatDisplayDate(new Date(startDate)); + dateRangeEnd = formatDisplayDate(new Date(endDate)); toolTipText = `${dateRangeStart} to ${dateRangeEnd}`; } break; diff --git a/web/js/containers/gif.js b/web/js/containers/gif.js index 3b04f946c3..d1398a9c1f 100644 --- a/web/js/containers/gif.js +++ b/web/js/containers/gif.js @@ -26,6 +26,7 @@ import getImageArray from '../modules/animation/selectors'; import { getStampProps, svgToPng } from '../modules/animation/util'; import { changeCropBounds } from '../modules/animation/actions'; import { subdailyLayersActive } from '../modules/layers/selectors'; +import { formatDisplayDate } from '../modules/date/util'; const DEFAULT_URL = 'http://localhost:3002/api/v1/snapshot'; const gifStream = new GifStream(); @@ -403,8 +404,8 @@ function mapStateToProps(state) { boundaries, proj: proj.selected, isActive: animation.gifActive, - startDate: util.toISOStringDateMonthAbbrev(startDate, subdailyLayersActive(state)), - endDate: util.toISOStringDateMonthAbbrev(endDate, subdailyLayersActive(state)), + startDate: formatDisplayDate(startDate, subdailyLayersActive(state)), + endDate: formatDisplayDate(endDate, subdailyLayersActive(state)), increment: `${increment} Between Frames`, speed, map, diff --git a/web/js/containers/sidebar/events.js b/web/js/containers/sidebar/events.js index 12d00717d5..09c815bb07 100644 --- a/web/js/containers/sidebar/events.js +++ b/web/js/containers/sidebar/events.js @@ -2,11 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import moment from 'moment'; import { Button, } from 'reactstrap'; - import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Event from '../../components/sidebar/event'; import EventIcon from '../../components/sidebar/event-icon'; @@ -20,6 +18,7 @@ import { collapseSidebar } from '../../modules/sidebar/actions'; import { getSelectedDate } from '../../modules/date/selectors'; import { toggleCustomContent } from '../../modules/modal/actions'; import util from '../../util/util'; +import { formatDisplayDate } from '../../modules/date/util'; function Events(props) { const { @@ -46,8 +45,8 @@ function Events(props) { const maxHeight = Math.max(height - filterControlHeight, 166); const scrollbarMaxHeight = isEmbedModeActive ? '50vh' : `${maxHeight}px`; - const startDate = moment(selectedStartDate).format('YYYY MMM DD'); - const endDate = moment(selectedEndDate).format('YYYY MMM DD'); + const startDate = formatDisplayDate(selectedStartDate); + const endDate = formatDisplayDate(selectedEndDate); const errorOrLoadingText = isLoading ? 'Loading ...' diff --git a/web/js/containers/sidebar/layer-row.js b/web/js/containers/sidebar/layer-row.js index b9318ef12d..1e4e954c39 100644 --- a/web/js/containers/sidebar/layer-row.js +++ b/web/js/containers/sidebar/layer-row.js @@ -29,6 +29,7 @@ import Zot from './zot'; import { isVectorLayerClickable } from '../../modules/layers/util'; import { MODAL_PROPERTIES } from '../../modules/alerts/constants'; import { getActiveLayers, makeGetDescription } from '../../modules/layers/selectors'; +import { coverageDateFormatter } from '../../modules/date/util'; const { events } = util; const { vectorModalProps } = MODAL_PROPERTIES; @@ -126,12 +127,12 @@ function LayerRow (props) { // start date let layerStartDate; if (startDate) { - layerStartDate = util.coverageDateFormatter('START-DATE', startDate, period); + layerStartDate = coverageDateFormatter('START-DATE', startDate, period); } // end date let layerEndDate; if (endDate) { - layerEndDate = util.coverageDateFormatter('END-DATE', endDate, period); + layerEndDate = coverageDateFormatter('END-DATE', endDate, period); } if (layerStartDate && layerEndDate) { diff --git a/web/js/containers/sidebar/smart-handoff.js b/web/js/containers/sidebar/smart-handoff.js index efc363137c..1d62ddc1c8 100644 --- a/web/js/containers/sidebar/smart-handoff.js +++ b/web/js/containers/sidebar/smart-handoff.js @@ -24,6 +24,7 @@ import { getSelectedDate } from '../../modules/date/selectors'; import safeLocalStorage from '../../util/local-storage'; import openEarthDataSearch from '../../components/smart-handoffs/util'; import selectCollection from '../../modules/smart-handoff/actions'; +import { formatDisplayDate } from '../../modules/date/util'; const STD_NRT_MAP = { STD: 'Standard', @@ -614,7 +615,7 @@ const mapStateToProps = (state) => { const selectedDate = getSelectedDate(state); const selectedDateFormatted = moment.utc(selectedDate).format('YYYY-MM-DD'); // 2020-01-01 - const displayDate = moment.utc(selectedDate).format('YYYY MMM DD'); // 2020 JAN 01 + const displayDate = formatDisplayDate(selectedDate); // 2020 JAN 01 const filterForSmartHandoff = (layer) => { const { id, projections, disableSmartHandoff, conceptIds, diff --git a/web/js/map/natural-events/event-track.js b/web/js/map/natural-events/event-track.js index bc145811d3..d321c9596e 100644 --- a/web/js/map/natural-events/event-track.js +++ b/web/js/map/natural-events/event-track.js @@ -24,6 +24,7 @@ import { import { mapUtilZoomAction } from '../util'; import { selectEvent as selectEventAction } from '../../modules/natural-events/actions'; import { getFilteredEvents } from '../../modules/natural-events/selectors'; +import { formatDisplayDate } from '../../modules/date/util'; const firstClusterObj = naturalEventsClusterCreateObject(); // Cluster before selected event const secondClusterObj = naturalEventsClusterCreateObject(); // Cluster after selected event @@ -276,7 +277,6 @@ const naturalEventsTrackPoint = function( const circleEl = document.createElement('div'); const textEl = document.createElement('span'); const { properties } = clusterPoint; - const content = document.createTextNode(properties.date); const { date } = properties; const eventID = properties.event_id; let { coordinates } = clusterPoint.geometry; @@ -291,6 +291,8 @@ const naturalEventsTrackPoint = function( overlayEl.onclick = function() { callback(eventID, date); }; + const displayDate = formatDisplayDate(date); + const content = document.createTextNode(displayDate); textEl.appendChild(content); textEl.className = 'track-marker-date'; circleEl.className = `track-marker track-marker-${date}`; @@ -602,8 +604,9 @@ function getClusterPointEl(proj, cluster, map, pointClusterObj) { const clusterId = properties.cluster_id; const number = properties.point_count_abbreviated; const numberEl = document.createTextNode(number); + const { startDate, endDate } = properties; const dateRangeTextEl = document.createTextNode( - `${properties.startDate} to ${properties.endDate}`, + `${formatDisplayDate(startDate)} to ${formatDisplayDate(endDate)}`, ); let { coordinates } = cluster.geometry; const mapView = map.getView(); diff --git a/web/js/modules/compare/util.js b/web/js/modules/compare/util.js index ec8bf0a784..d3d53ae6d8 100644 --- a/web/js/modules/compare/util.js +++ b/web/js/modules/compare/util.js @@ -1,6 +1,6 @@ import update from 'immutability-helper'; import { initialCompareState } from './reducers'; -import util from '../../util/util'; +import { formatDisplayDate } from '../date/util'; export function mapLocationToCompareState(parameters, stateFromLocation) { if (parameters.ca !== undefined) { @@ -45,7 +45,7 @@ export function isFromActiveCompareRegion(coords, layerAttributes, compareModel, } export const getFormattedMonthAbbrevDates = function(selected, selectedB) { - const dateA = util.toISOStringDateMonthAbbrev(selected); - const dateB = util.toISOStringDateMonthAbbrev(selectedB); + const dateA = formatDisplayDate(selected); + const dateB = formatDisplayDate(selectedB); return { dateA, dateB }; }; diff --git a/web/js/modules/date/util.js b/web/js/modules/date/util.js index 7d0f98c658..7c821da3d2 100644 --- a/web/js/modules/date/util.js +++ b/web/js/modules/date/util.js @@ -10,6 +10,46 @@ import { getSelectedDate, getDeltaIntervalUnit } from './selectors'; export const filterProjLayersWithStartDate = (layers, projId) => layers.filter((layer) => layer.startDate && layer.projections[projId]); +/** + * Parses a UTC ISO 8601 date to a non UTC date + * + * @method parseDate + * @static + * @param str {string} Date to parse in the form of YYYY-MM-DDTHH:MM:SSZ`. + * @return {Date} converted string as a non UTC date object, throws an exception if + * the string is invalid + */ +export const parseDate = (dateAsString) => { + const dateTimeArr = dateAsString.split(/T/); + + const yyyymmdd = dateTimeArr[0].split(/[\s-]+/); + + // Parse elements of date and time + const year = yyyymmdd[0]; + const month = yyyymmdd[1] - 1; + const day = yyyymmdd[2]; + + let hour = 0; + let minute = 0; + let second = 0; + let millisecond = 0; + + // Use default of midnight if time is not specified + if (dateTimeArr.length > 1) { + const hhmmss = dateTimeArr[1].split(/[:.Z]/); + hour = hhmmss[0] || 0; + minute = hhmmss[1] || 0; + second = hhmmss[2] || 0; + millisecond = hhmmss[3] || 0; + } + const date = new Date(year, month, day, hour, minute, second, millisecond); + // eslint-disable-next-line no-restricted-globals + if (isNaN(date.getTime())) { + throw new Error(`Invalid date: ${dateAsString}`); + } + return date; +}; + export function serializeDate(date) { return ( `${date.toISOString().split('T')[0] @@ -310,3 +350,39 @@ export const outOfStepChange = (state, newDate) => { const prevStepDate = getNextDateTime(state, -1, previousSelectedDate).toISOString(); return date !== nextStepDate && date !== prevStepDate; }; + +export const coverageDateFormatter = (dateType, date, period) => { + let dateString; + const parsedDate = parseDate(date); + switch (period) { + case 'subdaily': + dateString = `${moment(parsedDate).format('YYYY MMM DD HH:mm')}Z`.toUpperCase(); + break; + + case 'yearly': + if (dateType === 'END-DATE') parsedDate.setFullYear(parsedDate.getFullYear() - 1); + dateString = moment(parsedDate).format('YYYY'); + break; + + case 'monthly': + if (dateType === 'END-DATE') parsedDate.setMonth(parsedDate.getMonth() - 1); + dateString = moment(parsedDate).format('YYYY MMM').toUpperCase(); + break; + + default: + dateString = formatDisplayDate(parsedDate); + break; + } + + return dateString; +}; + +export const formatDisplayDate = (date, subdaily) => { + const format = subdaily ? 'YYYY MMM DD HH:mmZ' : 'YYYY MMM DD'; + return moment + .utc(date) + .format(format) + .toUpperCase(); +}; + +export const formatISODate = (date) => moment(date).format('YYYY-MM-DD'); diff --git a/web/js/modules/image-download/util.js b/web/js/modules/image-download/util.js index 7596d777f7..4df03d3a79 100644 --- a/web/js/modules/image-download/util.js +++ b/web/js/modules/image-download/util.js @@ -4,6 +4,7 @@ import { } from 'lodash'; import { boundingExtent, containsCoordinate } from 'ol/extent'; import util from '../../util/util'; +import { formatDisplayDate } from '../date/util'; import { nearestInterval } from '../layers/util'; import { coordinatesCRSTransform } from '../projection/util'; @@ -425,8 +426,8 @@ export function getAlertMessageIfCrossesDateline(date, geolonlat1, geolonlat2, p const crossesNextDay = geolonlat1[0] < maxExtent[0]; const crossesPrevDay = geolonlat2[0] > maxExtent[2]; const zeroedDate = util.clearTimeUTC(date); - const nextDay = util.toISOStringDateMonthAbbrev(util.dateAdd(zeroedDate, 'day', 1)); - const prevDay = util.toISOStringDateMonthAbbrev(util.dateAdd(zeroedDate, 'day', -1)); + const nextDay = formatDisplayDate(util.dateAdd(zeroedDate, 'day', 1)); + const prevDay = formatDisplayDate(util.dateAdd(zeroedDate, 'day', -1)); const buildString = (lineStr, dateStr) => `The selected snapshot area crosses ${lineStr} and uses imagery from the ${dateStr}.`; if (crossesNextDay && crossesPrevDay) { // snapshot extends over both map wings diff --git a/web/js/modules/layers/util.js b/web/js/modules/layers/util.js index 92372214c5..15f0513ef5 100644 --- a/web/js/modules/layers/util.js +++ b/web/js/modules/layers/util.js @@ -21,6 +21,7 @@ import { import { getPaletteAttributeArray } from '../palettes/util'; import { getVectorStyleAttributeArray } from '../vector-styles/util'; import util from '../../util/util'; +import { parseDate } from '../date/util'; export function getOrbitTrackTitle(def) { const { track } = def; @@ -861,9 +862,9 @@ export function serializeGroupOverlays (groupOverlays, state, activeString) { export function dateOverlap(period, dateRanges) { const sortedRanges = dateRanges.sort((previous, current) => { // get the start date from previous and current - let previousTime = util.parseDate(previous.startDate); + let previousTime = parseDate(previous.startDate); previousTime = previousTime.getTime(); - let currentTime = util.parseDate(current.startDate); + let currentTime = parseDate(current.startDate); currentTime = currentTime.getTime(); // if the previous is earlier than the current @@ -889,7 +890,7 @@ export function dateOverlap(period, dateRanges) { const previous = arr[idx - 1]; // check for any overlap - let previousEnd = util.parseDate(previous.endDate); + let previousEnd = parseDate(previous.endDate); // Add dateInterval if (previous.dateInterval > 1 && period === 'daily') { previousEnd = new Date( @@ -914,7 +915,7 @@ export function dateOverlap(period, dateRanges) { } previousEnd = previousEnd.getTime(); - let currentStart = util.parseDate(current.startDate); + let currentStart = parseDate(current.startDate); currentStart = currentStart.getTime(); const overlap = previousEnd >= currentStart; diff --git a/web/js/modules/product-picker/format-config.js b/web/js/modules/product-picker/format-config.js index add0366c07..86adaf924d 100644 --- a/web/js/modules/product-picker/format-config.js +++ b/web/js/modules/product-picker/format-config.js @@ -3,9 +3,9 @@ import { map as lodashMap, get as lodashGet, } from 'lodash'; -import moment from 'moment'; import { available } from '../layers/selectors'; import util from '../../util/util'; +import { formatDisplayDate } from '../date/util'; const periodIntervalMap = { daily: 'Day', @@ -114,7 +114,7 @@ function setCoverageFacetProp(layer, selectedDate) { if (!startDate && !endDate && !dateRanges) { layer.coverage = ['Always Available']; } else if (available(id, selectedDate, [layer], {})) { - layer.coverage = [`Available ${moment.utc(selectedDate).format('YYYY MMM DD')}`]; + layer.coverage = [`Available ${formatDisplayDate(selectedDate)}`]; } } diff --git a/web/js/modules/product-picker/search-config.js b/web/js/modules/product-picker/search-config.js index b3b93348b9..6c4787abce 100644 --- a/web/js/modules/product-picker/search-config.js +++ b/web/js/modules/product-picker/search-config.js @@ -5,11 +5,11 @@ import { forEach as lodashForEach, toLower as lodashToLower, } from 'lodash'; -import moment from 'moment'; import { getTitles } from '../layers/selectors'; import { getLayersForProjection } from './selectors'; import facetConfig from './facet-config'; import { getSelectedDate } from '../date/selectors'; +import { formatDisplayDate } from '../date/util'; let initialLayersArray; let configRef; @@ -134,7 +134,7 @@ function filterSearch (layer, val, terms) { */ function updateCoverageFilter (filters, selectedDate) { if (!filters || !filters.length) return; - const formattedDate = moment.utc(selectedDate).format('YYYY MMM DD'); + const formattedDate = formatDisplayDate(selectedDate); const oldValueMatch = (value) => !value.includes(formattedDate) && !value.includes('Always'); filters.forEach((f) => { diff --git a/web/js/util/util.js b/web/js/util/util.js index 3381ee5c8c..bef45b6184 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -2,12 +2,10 @@ import { isObject as lodashIsObject, each as lodashEach, } from 'lodash'; -import moment from 'moment'; import browser from './browser'; import events from './events'; import load from './load'; import safeLocalStorage from './local-storage'; -import { MONTH_STRING_ARRAY } from '../modules/date/constants'; const { COORDINATE_FORMAT } = safeLocalStorage.keys; @@ -169,6 +167,7 @@ export default (function(self) { } return date; }; + self.appendAttributesForURL = function(item) { if (lodashIsObject(item)) { let part = item.id || ''; @@ -188,72 +187,6 @@ export default (function(self) { self.warn(`Is not an object: ${item}`); return ''; }; - /** - * Parses a UTC ISO 8601 date to a non UTC date - * - * @method parseDate - * @static - * @param str {string} Date to parse in the form of YYYY-MM-DDTHH:MM:SSZ`. - * @return {Date} converted string as a non UTC date object, throws an exception if - * the string is invalid - */ - self.parseDate = function(dateAsString) { - const dateTimeArr = dateAsString.split(/T/); - - const yyyymmdd = dateTimeArr[0].split(/[\s-]+/); - - // Parse elements of date and time - const year = yyyymmdd[0]; - const month = yyyymmdd[1] - 1; - const day = yyyymmdd[2]; - - let hour = 0; - let minute = 0; - let second = 0; - let millisecond = 0; - - // Use default of midnight if time is not specified - if (dateTimeArr.length > 1) { - const hhmmss = dateTimeArr[1].split(/[:.Z]/); - hour = hhmmss[0] || 0; - minute = hhmmss[1] || 0; - second = hhmmss[2] || 0; - millisecond = hhmmss[3] || 0; - } - const date = new Date(year, month, day, hour, minute, second, - millisecond); - // eslint-disable-next-line no-restricted-globals - if (isNaN(date.getTime())) { - throw new Error(`Invalid date: ${dateAsString}`); - } - return date; - }; - - self.coverageDateFormatter = function(dateType, date, period) { - let dateString; - const parsedDate = this.parseDate(date); - switch (period) { - case 'subdaily': - dateString = `${moment(parsedDate).format('YYYY MMMM DD HH:mm')}Z`.toUpperCase(); - break; - - case 'yearly': - if (dateType === 'END-DATE') parsedDate.setFullYear(parsedDate.getFullYear() - 1); - dateString = moment(parsedDate).format('YYYY'); - break; - - case 'monthly': - if (dateType === 'END-DATE') parsedDate.setMonth(parsedDate.getMonth() - 1); - dateString = moment(parsedDate).format('YYYY MMM').toUpperCase(); - break; - - default: - dateString = moment(parsedDate).format('YYYY MMM DD ').toUpperCase(); - break; - } - - return dateString; - }; /** * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. @@ -281,30 +214,9 @@ export default (function(self) { * @return {string} ISO string in the form of ``YYYY-MM-DD``. */ self.toISOStringDate = function(date) { - return date.toISOString().split('T')[0]; - }; - - /** - * Converts a date into an ISO string with only the date portion and month abbreviation. - * - * @method toISOStringDateMonthAbbrev - * @static - * @param date {Date} the date to convert - * @param hasSubdaily {Boolean} has subdaily date - * @return {string} ISO string in the form of `YYYY MMM DD` -or- `YYYY MMM DD HH:SSZ` for subdaily. - */ - self.toISOStringDateMonthAbbrev = function(date, hasSubdaily) { - const stringDate = self.toISOStringDate(date).split('-'); - const year = stringDate[0]; - const month = stringDate[1]; - const day = stringDate[2]; - - const monthAbbrev = MONTH_STRING_ARRAY[Number(month) - 1]; - - if (hasSubdaily) { - return `${year} ${monthAbbrev} ${day} ${self.toHourMinutes(date)}Z`; - } - return `${year} ${monthAbbrev} ${day}`; + const isString = typeof date === 'string' || date instanceof String; + const dateString = isString ? date : date.toISOString(); + return dateString.split('T')[0]; }; /** @@ -332,21 +244,6 @@ export default (function(self) { return `${parts[0]}:${parts[1]}Z`; }; - /** - * Converts a time into a HH:MM string - * - * @method toHourMinutes - * @static - * @param date {Date} the date to convert - * @return {string} ISO string in the form of HH:MM`. - */ - self.toHourMinutes = function(date) { - const time = date.toISOString() - .split('T')[1]; - const parts = time.split('.')[0].split(':'); - return `${parts[0]}:${parts[1]}`; - }; - /** * Round input time to one minute * From aca4b0b9906b39a9d748e09d076ace886281a010 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 17 Nov 2021 11:17:56 -0700 Subject: [PATCH 08/35] format gif display dates --- web/js/modules/animation/selectors.js | 8 +++----- web/js/util/util.js | 13 ------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/web/js/modules/animation/selectors.js b/web/js/modules/animation/selectors.js index 9fc468c444..cc98175a79 100644 --- a/web/js/modules/animation/selectors.js +++ b/web/js/modules/animation/selectors.js @@ -6,6 +6,8 @@ import { } from '../image-download/util'; import { subdailyLayersActive, getLayers } from '../layers/selectors'; import { TIME_SCALE_FROM_NUMBER } from '../date/constants'; +import { formatDisplayDate } from '../date/util'; + /* * loops through dates and created image * download urls and pushs them to an @@ -48,11 +50,7 @@ export default function getImageArray( while (current <= toDate) { j += 1; - if (isSubDaily) { - strDate = util.toISOStringMinutes(current); - } else { - strDate = util.toISOStringDate(current); - } + strDate = formatDisplayDate(current, isSubDaily); products = getProducts(current, state); const lonlats = imageUtilGetCoordsFromPixelValues(boundaries, map.ui.selected); diff --git a/web/js/util/util.js b/web/js/util/util.js index bef45b6184..7025046b95 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -231,19 +231,6 @@ export default (function(self) { return `${date.toISOString().split('.')[0]}Z`; }; - /** - * Converts a time into an ISO string without seconds. - * - * @method toISOStringMinutes - * @static - * @param {Date} date the date to convert - * @return {string} ISO string in the form of `YYYY-MM-DDThh:mmZ`. - */ - self.toISOStringMinutes = function(date) { - const parts = date.toISOString().split(':'); - return `${parts[0]}:${parts[1]}Z`; - }; - /** * Round input time to one minute * From e615bf946ce2de5491e4af50eaade5418e6313ed Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 17 Nov 2021 11:21:56 -0700 Subject: [PATCH 09/35] fix and update tests --- web/js/util/util.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/js/util/util.test.js b/web/js/util/util.test.js index 13b8bc6f8c..1c35afa399 100644 --- a/web/js/util/util.test.js +++ b/web/js/util/util.test.js @@ -69,19 +69,19 @@ describe('parseDateUTC', () => { }); }); -test('toISOStringDate', () => { +test('toISOStringDate with date', () => { const d = new Date(Date.UTC(2013, 0, 15)); expect(util.toISOStringDate(d)).toBe('2013-01-15'); }); -test('toISOStringSeconds', () => { - const d = new Date(Date.UTC(2013, 0, 15, 11, 22, 33)); - expect(util.toISOStringSeconds(d)).toBe('2013-01-15T11:22:33Z'); +test('toISOStringDate with string', () => { + const d = '2013-01-15T11:22:33Z'; + expect(util.toISOStringDate(d)).toBe('2013-01-15'); }); -test('toHourMinutes', () => { +test('toISOStringSeconds', () => { const d = new Date(Date.UTC(2013, 0, 15, 11, 22, 33)); - expect(util.toHourMinutes(d)).toBe('11:22'); + expect(util.toISOStringSeconds(d)).toBe('2013-01-15T11:22:33Z'); }); test('clearTimeUTC', () => { From ccc434416258e95471576e9dc3732763abc0aca9 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 17 Nov 2021 12:44:34 -0700 Subject: [PATCH 10/35] update tests --- e2e/features/layers/layer-picker-test.js | 2 +- e2e/features/smart-handoff/smart-handoff-test.js | 2 +- e2e/features/timeline/timeline-test.js | 2 +- web/js/modules/date/util.js | 10 ++++------ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/e2e/features/layers/layer-picker-test.js b/e2e/features/layers/layer-picker-test.js index 2ec971fd4f..a1cc39770c 100644 --- a/e2e/features/layers/layer-picker-test.js +++ b/e2e/features/layers/layer-picker-test.js @@ -168,7 +168,7 @@ module.exports = { c.click(addLayers); // Confirm available facet still enabled but date changed - c.assert.containsText(availableFilterTextEl, 'Available 2012 Apr 14'); + c.assert.containsText(availableFilterTextEl, 'Available 2012 APR 14'); c.expect.element(availableFilterCheckboxInput).to.be.selected; }, 'Disabling coverage filter updates list': (c) => { diff --git a/e2e/features/smart-handoff/smart-handoff-test.js b/e2e/features/smart-handoff/smart-handoff-test.js index 3957ad0b3b..c3fa583979 100644 --- a/e2e/features/smart-handoff/smart-handoff-test.js +++ b/e2e/features/smart-handoff/smart-handoff-test.js @@ -62,7 +62,7 @@ module.exports = { // Verify granules and date are correct c.expect .element('.granule-count-header') - .to.have.text.equal('Available granules for 2019 Dec 01:'); + .to.have.text.equal('Available granules for 2019 DEC 01:'); c.waitForElementVisible('.granule-count-info', TIME_LIMIT); }, diff --git a/e2e/features/timeline/timeline-test.js b/e2e/features/timeline/timeline-test.js index f20939f190..ff0a38d5c6 100644 --- a/e2e/features/timeline/timeline-test.js +++ b/e2e/features/timeline/timeline-test.js @@ -136,7 +136,7 @@ module.exports = { }, // date subdaily tooltip date present on load - 'Date subdaily tooltip date present load': (c) => { + 'Date subdaily tooltip date present on load': (c) => { c.url(c.globals.url + localQueryStrings.subdailyLayerIntervalTimescale); c.waitForElementVisible('.date-tooltip', TIME_LIMIT) .assert.containsText('.date-tooltip', '2019 OCT 04 09:46Z (DOY 277)'); diff --git a/web/js/modules/date/util.js b/web/js/modules/date/util.js index 7c821da3d2..a48a2fd654 100644 --- a/web/js/modules/date/util.js +++ b/web/js/modules/date/util.js @@ -356,7 +356,7 @@ export const coverageDateFormatter = (dateType, date, period) => { const parsedDate = parseDate(date); switch (period) { case 'subdaily': - dateString = `${moment(parsedDate).format('YYYY MMM DD HH:mm')}Z`.toUpperCase(); + dateString = formatDisplayDate(parsedDate, true); break; case 'yearly': @@ -378,11 +378,9 @@ export const coverageDateFormatter = (dateType, date, period) => { }; export const formatDisplayDate = (date, subdaily) => { - const format = subdaily ? 'YYYY MMM DD HH:mmZ' : 'YYYY MMM DD'; - return moment - .utc(date) - .format(format) - .toUpperCase(); + const format = subdaily ? 'YYYY MMM DD HH:mm' : 'YYYY MMM DD'; + const dateString = moment.utc(date).format(format); + return `${dateString.toUpperCase()}${subdaily ? 'Z' : ''}`; }; export const formatISODate = (date) => moment(date).format('YYYY-MM-DD'); From 499a648db7b81226956420852bde4f4e8de4ec70 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Thu, 4 Nov 2021 09:21:46 -0600 Subject: [PATCH 11/35] data center facet --- tasks/python3/getVisMetadata.py | 14 ++++++++++++-- web/js/modules/product-picker/facet-config.js | 8 ++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tasks/python3/getVisMetadata.py b/tasks/python3/getVisMetadata.py index 803a2f512b..c74b4c64f8 100755 --- a/tasks/python3/getVisMetadata.py +++ b/tasks/python3/getVisMetadata.py @@ -16,7 +16,7 @@ output_file = args[2] # NOTE: Only using these properties at this time -use_keys = ['conceptIds'] +use_keys = ['conceptIds', 'dataCenter'] layer_metadata = {} def get_metadata(layer_id, base_url): @@ -24,7 +24,17 @@ def get_metadata(layer_id, base_url): if (response.status_code != 200): print('%s WARNING: No metadata config found for [%s]' % (prog, layer_id)) return - layer_metadata[layer_id] = response.json() + metadata = response.json() + + if metadata.get('conceptIds', None) is not None: + for collection in metadata.get("conceptIds"): + dataCenter = collection.get("dataCenter", None) + if metadata.get("dataCenter", None) is None: + metadata["dataCenter"] = [dataCenter] + elif dataCenter not in metadata["dataCenter"]: + metadata["dataCenter"].append(dataCenter) + + layer_metadata[layer_id] = metadata # Remove any props we don't expect to use metadata_keys = dict(layer_metadata[layer_id]).keys() diff --git a/web/js/modules/product-picker/facet-config.js b/web/js/modules/product-picker/facet-config.js index 170e57a734..3150334d26 100644 --- a/web/js/modules/product-picker/facet-config.js +++ b/web/js/modules/product-picker/facet-config.js @@ -54,4 +54,12 @@ export default [ tooltip: 'Whether the layer represents daytime or nighttime imagery or data', hideZeroCount: true, }, + { + field: 'dataCenter', + label: 'Data Center', + filterType: 'any', + tooltip: '', + show: 30, + hideZeroCount: true, + }, ]; From c137d59312bc3a0da1947cd611e800f6555c05a9 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Mon, 8 Nov 2021 11:43:10 -0700 Subject: [PATCH 12/35] map daac names --- config/default/common/features.json | 21 ++++++++++++- tasks/python3/getVisMetadata.py | 31 +++++++++++++------ web/js/modules/product-picker/facet-config.js | 4 +-- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/config/default/common/features.json b/config/default/common/features.json index 3388bb43ea..3c6604212e 100644 --- a/config/default/common/features.json +++ b/config/default/common/features.json @@ -9,7 +9,26 @@ "feedback": true, "previewSnapshots": true, "vismetadata": { - "url": "https://gibs.earthdata.nasa.gov/layer-metadata/v1.0/" + "url": "https://gibs.earthdata.nasa.gov/layer-metadata/v1.0/", + "daacMap": { + "GES_DISC": "GES DISC", + "LANCEMODIS": "MODAPS SIPS", + "GHRC_DAAC": "GHRC DAAC", + "NSIDC_ECS": "NSIDC DAAC", + "LAADS": "LAADS DAAC", + "PODAAC": "PO.DAAC", + "LPDAAC_ECS": "LP DAAC", + "SEDAC": "SEDAC", + "LARC_ASDC": "ASDC", + "LARC": "ASDC", + "LANCEAMSR2": "AMSR2 SIPS", + "OB_DAAC": "OB.DAAC", + "OMINRT": "OMI/Ozone SIPS", + "ASIPS": "Atmosphere SIPS", + "LPCLOUD": "LPDAAC", + "ORNL_DAAC": "ORNL DAAC", + "MOPITT": "MOPITT SIPS" + } }, "googleTagManager": true, "notification": { diff --git a/tasks/python3/getVisMetadata.py b/tasks/python3/getVisMetadata.py index c74b4c64f8..4a15152c12 100755 --- a/tasks/python3/getVisMetadata.py +++ b/tasks/python3/getVisMetadata.py @@ -18,6 +18,22 @@ # NOTE: Only using these properties at this time use_keys = ['conceptIds', 'dataCenter'] layer_metadata = {} +daacMap = {} + +def get_daac(metadata): + if metadata.get('conceptIds', None) is None: + return metadata + for collection in metadata.get("conceptIds"): + origDataCenter = collection.get("dataCenter", None) + dataCenter = daacMap.get(origDataCenter, None) + collection.pop('dataCenter', None) + if dataCenter is None: + continue + if metadata.get("dataCenter", None) is None: + metadata["dataCenter"] = [dataCenter] + elif dataCenter not in metadata["dataCenter"]: + metadata["dataCenter"].append(dataCenter) + return metadata def get_metadata(layer_id, base_url): response = requests.get(base_url + layer_id + '.json') @@ -25,16 +41,7 @@ def get_metadata(layer_id, base_url): print('%s WARNING: No metadata config found for [%s]' % (prog, layer_id)) return metadata = response.json() - - if metadata.get('conceptIds', None) is not None: - for collection in metadata.get("conceptIds"): - dataCenter = collection.get("dataCenter", None) - if metadata.get("dataCenter", None) is None: - metadata["dataCenter"] = [dataCenter] - elif dataCenter not in metadata["dataCenter"]: - metadata["dataCenter"].append(dataCenter) - - layer_metadata[layer_id] = metadata + layer_metadata[layer_id] = get_daac(metadata) # Remove any props we don't expect to use metadata_keys = dict(layer_metadata[layer_id]).keys() @@ -58,6 +65,8 @@ def main(url): except Exception as e: print("%s:" % (e)) + print(layer_metadata) + with open(output_file, "w", encoding="utf-8") as fp: # Format of this object will determine how this data is combined into wv.json json.dump({ 'layers': layer_metadata}, fp, indent=2, sort_keys=True) @@ -68,6 +77,8 @@ def main(url): metadata_config = json.load(features).get('features').get('vismetadata') if metadata_config is not None: url = metadata_config.get('url') + daacMap = metadata_config.get('daacMap', {}) + print(daacMap) main(url) else: print('%s: Visualization metadata not configured. Exiting.' % (prog)) diff --git a/web/js/modules/product-picker/facet-config.js b/web/js/modules/product-picker/facet-config.js index 3150334d26..8d16280102 100644 --- a/web/js/modules/product-picker/facet-config.js +++ b/web/js/modules/product-picker/facet-config.js @@ -56,9 +56,9 @@ export default [ }, { field: 'dataCenter', - label: 'Data Center', + label: 'DAAC/SIPS', filterType: 'any', - tooltip: '', + tooltip: ' Date: Wed, 17 Nov 2021 12:27:04 -0700 Subject: [PATCH 14/35] remove daac facet tooltip link --- web/js/modules/product-picker/facet-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/modules/product-picker/facet-config.js b/web/js/modules/product-picker/facet-config.js index 8d16280102..ae3698975c 100644 --- a/web/js/modules/product-picker/facet-config.js +++ b/web/js/modules/product-picker/facet-config.js @@ -58,7 +58,7 @@ export default [ field: 'dataCenter', label: 'DAAC/SIPS', filterType: 'any', - tooltip: ' Date: Mon, 22 Nov 2021 10:30:13 -0700 Subject: [PATCH 20/35] simplify add/remove overlays, fix issue on load --- web/js/map/natural-events/event-track.js | 65 +++++++++++------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/web/js/map/natural-events/event-track.js b/web/js/map/natural-events/event-track.js index 9bffe2b403..44f7eff3b4 100644 --- a/web/js/map/natural-events/event-track.js +++ b/web/js/map/natural-events/event-track.js @@ -28,10 +28,10 @@ class EventTrack extends React.Component { }; this.onMoveEnd = this.onMoveEnd.bind(this); - this.debouncedTrackUpdate = lodashDebounce(this.updateCurrentTrack, 250); + this.debouncedTrackUpdate = lodashDebounce(this.updateCurrentTrack, 50); this.debouncedOnPropertyChange = lodashDebounce( this.onPropertyChange.bind(this), - 500, + 50, { leading: true, trailing: false }, ); } @@ -40,9 +40,9 @@ class EventTrack extends React.Component { this.initialize(); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps) { const { - map, selectedDate, isAnimatingToEvent, eventsData, selectedEvent, + isPlaying, map, selectedDate, isAnimatingToEvent, eventsData, selectedEvent, } = this.props; const selectedDateChange = (selectedDate && selectedDate.valueOf()) !== (prevProps.selectedDate && prevProps.selectedDate.valueOf()); @@ -56,12 +56,12 @@ class EventTrack extends React.Component { if (prevMap) { this.update(null); this.removeTrack(prevMap); - removeOldPoints(prevMap, trackDetails.pointsAndArrows); + removePointOverlays(prevMap, trackDetails.pointsAndArrows); } this.initialize(); } - if (selectedDateChange || finishedAnimating || eventsLoaded) { + if (!isPlaying && (selectedDateChange || finishedAnimating || eventsLoaded)) { this.debouncedTrackUpdate(); } @@ -83,7 +83,10 @@ class EventTrack extends React.Component { // NOTE: Does not cause additional listeners to be registered on subsequent calls map.on('moveend', this.onMoveEnd); map.getView().on('propertychange', this.debouncedOnPropertyChange); - this.debouncedTrackUpdate(); + // Don't render until map position is set + setTimeout(() => { + this.debouncedTrackUpdate(); + }, 500); } updateCurrentTrack() { @@ -130,20 +133,21 @@ class EventTrack extends React.Component { isNewTarget = oldLon !== targetLon || oldLat !== targetLat; } if (isNewTarget) { - removeOldPoints(map, trackDetails.pointsAndArrows); + removePointOverlays(map, trackDetails.pointsAndArrows); } } } - /** - * @param {Object} map Openlayers map object - * @return {Object} Empty object - */ + addTrack = (map, { track, pointsAndArrows }) => { + map.addOverlay(track); + addPointOverlays(map, pointsAndArrows); + } + removeTrack = function(map) { const { trackDetails } = this.state; const { track, pointsAndArrows } = trackDetails; map.removeOverlay(track); - removeOldPoints(map, pointsAndArrows); + removePointOverlays(map, pointsAndArrows); return {}; }; @@ -151,9 +155,7 @@ class EventTrack extends React.Component { * Update track * * @param {Object} event EONET event object - * @param {Object} map Ol map object * @param {String} selectedDate - * @return {[type]} */ update = function(event, date) { const { @@ -177,10 +179,7 @@ class EventTrack extends React.Component { pointsAndArrows, hidden: false, }; - - map.addOverlay(track); - track.changed(); - console.log('adding track'); + this.addTrack(map, newTrackDetails); }; if (!event || event.geometry.length < 2) { @@ -216,7 +215,7 @@ class EventTrack extends React.Component { } } -const removeOldPoints = function(map, pointsAndArrows) { +const removePointOverlays = function(map, pointsAndArrows) { lodashEach(pointsAndArrows, (pointOverlay) => { if (map.getOverlayById(pointOverlay.getId())) { map.removeOverlay(pointOverlay); @@ -270,27 +269,21 @@ const getTracksAndPoints = function(eventObj, proj, map, selectedDate, callback) if (index !== 0) { let prevCoordinates = clusters[index - 1].geometry.coordinates; let nextCoordinates = clusterPoint.geometry.coordinates; - // polar projections require transform of coordinates to crs if (proj.selected.id !== 'geographic') { const { crs } = proj.selected; prevCoordinates = olProj.transform(prevCoordinates, 'EPSG:4326', crs); nextCoordinates = olProj.transform(nextCoordinates, 'EPSG:4326', crs); } - const lineSegmentArray = [prevCoordinates, nextCoordinates]; const arrowOverlay = getArrows(lineSegmentArray, map); pointsAndArrows.push(arrowOverlay); trackSegments.push(lineSegmentArray); - addOverlayIfIsVisible(map, arrowOverlay); } - const point = clusterPoint.properties.cluster ? getClusterPointEl(proj, clusterPoint, map, pointClusterObj, callback) : getTrackPoint(proj, clusterPoint, isSelected, callback); pointsAndArrows.push(point); - - addOverlayIfIsVisible(map, point); }); return { track: getTrackLines(map, trackSegments), @@ -299,28 +292,27 @@ const getTracksAndPoints = function(eventObj, proj, map, selectedDate, callback) }; function addOverlayIfIsVisible(map, overlay) { - if ( - olExtent.containsCoordinate( - map.getView().calculateExtent(), - overlay.getPosition(), - ) - ) { + const extent = map.getView().calculateExtent(); + const position = overlay.getPosition(); + if (olExtent.containsCoordinate(extent, position)) { map.addOverlay(overlay); } } const mapStateToProps = (state) => { const { - map, proj, events, date, + map, proj, events, date, animation, } = state; const { isAnimatingToEvent } = events; + const { isPlaying } = animation; return { + eventsData: getFilteredEvents(state), + isAnimatingToEvent, + isPlaying, map: map.ui.selected, proj, selectedDate: date.selected, selectedEvent: events.selected, - eventsData: getFilteredEvents(state), - isAnimatingToEvent, }; }; @@ -332,8 +324,9 @@ const mapDispatchToProps = (dispatch) => ({ EventTrack.propTypes = { eventsData: PropTypes.array, - map: PropTypes.object, isAnimatingToEvent: PropTypes.bool, + isPlaying: PropTypes.bool, + map: PropTypes.object, proj: PropTypes.object, selectEvent: PropTypes.func, selectedEvent: PropTypes.object, From 4659d14b51b43e1d548d6161f34329cc8305afa9 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Mon, 22 Nov 2021 10:33:12 -0700 Subject: [PATCH 21/35] fix style lint issue --- web/css/events.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/css/events.css b/web/css/events.css index 62dd38c034..579afac956 100644 --- a/web/css/events.css +++ b/web/css/events.css @@ -255,7 +255,7 @@ .event-track-line { z-index: 0; } -.event-track-point, .event-track-cluster-point{ +.event-track-point, .event-track-cluster-point { z-index: 1; } From 9ee01f68b9ec088c94451f73d10e1f67286fd81c Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Fri, 3 Dec 2021 10:30:11 -0700 Subject: [PATCH 22/35] fix some track rendering issues --- web/js/map/natural-events/event-track.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/web/js/map/natural-events/event-track.js b/web/js/map/natural-events/event-track.js index 44f7eff3b4..1fc6fc0916 100644 --- a/web/js/map/natural-events/event-track.js +++ b/web/js/map/natural-events/event-track.js @@ -28,10 +28,10 @@ class EventTrack extends React.Component { }; this.onMoveEnd = this.onMoveEnd.bind(this); - this.debouncedTrackUpdate = lodashDebounce(this.updateCurrentTrack, 50); + this.debouncedTrackUpdate = lodashDebounce(this.updateCurrentTrack, 200); this.debouncedOnPropertyChange = lodashDebounce( this.onPropertyChange.bind(this), - 50, + 20, { leading: true, trailing: false }, ); } @@ -42,7 +42,7 @@ class EventTrack extends React.Component { componentDidUpdate(prevProps) { const { - isPlaying, map, selectedDate, isAnimatingToEvent, eventsData, selectedEvent, + isPlaying, map, extent, selectedDate, isAnimatingToEvent, eventsData, selectedEvent, } = this.props; const selectedDateChange = (selectedDate && selectedDate.valueOf()) !== (prevProps.selectedDate && prevProps.selectedDate.valueOf()); @@ -50,6 +50,8 @@ class EventTrack extends React.Component { const finishedAnimating = !isAnimatingToEvent && (isAnimatingToEvent !== prevProps.isAnimatingToEvent); const eventsLoaded = eventsData && eventsData.length && (eventsData !== prevProps.eventsData); const prevMap = prevProps.map; + const prevExtent = prevProps.extent; + const extentChange = prevExtent && (extent[0] !== prevExtent[0] || extent[1] !== prevExtent[1]); const { trackDetails } = this.state; if (map !== prevMap) { @@ -61,7 +63,7 @@ class EventTrack extends React.Component { this.initialize(); } - if (!isPlaying && (selectedDateChange || finishedAnimating || eventsLoaded)) { + if (!isPlaying && (selectedDateChange || finishedAnimating || eventsLoaded || extentChange)) { this.debouncedTrackUpdate(); } @@ -83,10 +85,6 @@ class EventTrack extends React.Component { // NOTE: Does not cause additional listeners to be registered on subsequent calls map.on('moveend', this.onMoveEnd); map.getView().on('propertychange', this.debouncedOnPropertyChange); - // Don't render until map position is set - setTimeout(() => { - this.debouncedTrackUpdate(); - }, 500); } updateCurrentTrack() { @@ -101,7 +99,6 @@ class EventTrack extends React.Component { onMoveEnd = function(e) { const { map } = this.props; const { trackDetails } = this.state; - if (trackDetails.id) { addPointOverlays(map, trackDetails.pointsAndArrows); } else { @@ -112,7 +109,6 @@ class EventTrack extends React.Component { onPropertyChange = (e) => { const { map } = this.props; const { trackDetails } = this.state; - if (e.key === 'resolution' || e.key === 'rotation') { const newTrackDetails = trackDetails.id ? this.removeTrack(map) : {}; this.setState({ trackDetails: newTrackDetails }); @@ -310,6 +306,7 @@ const mapStateToProps = (state) => { isAnimatingToEvent, isPlaying, map: map.ui.selected, + extent: map.extent, proj, selectedDate: date.selected, selectedEvent: events.selected, @@ -327,6 +324,7 @@ EventTrack.propTypes = { isAnimatingToEvent: PropTypes.bool, isPlaying: PropTypes.bool, map: PropTypes.object, + extent: PropTypes.array, proj: PropTypes.object, selectEvent: PropTypes.func, selectedEvent: PropTypes.object, From 24028f51c8c68b80ed96712dd9c2487b8780426d Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Mon, 6 Dec 2021 11:47:13 -0700 Subject: [PATCH 23/35] make sure tracks re-render on tab switch --- web/js/map/natural-events/event-track.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web/js/map/natural-events/event-track.js b/web/js/map/natural-events/event-track.js index 1fc6fc0916..1fa404b98b 100644 --- a/web/js/map/natural-events/event-track.js +++ b/web/js/map/natural-events/event-track.js @@ -85,6 +85,7 @@ class EventTrack extends React.Component { // NOTE: Does not cause additional listeners to be registered on subsequent calls map.on('moveend', this.onMoveEnd); map.getView().on('propertychange', this.debouncedOnPropertyChange); + map.once('postrender', () => { this.debouncedTrackUpdate(); }); } updateCurrentTrack() { From 57f22c05fc589d249ce5977352a66503520390d4 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Mon, 6 Dec 2021 14:28:32 -0700 Subject: [PATCH 24/35] fix laggy track re-render --- web/js/map/natural-events/event-track.js | 40 +++--------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/web/js/map/natural-events/event-track.js b/web/js/map/natural-events/event-track.js index 1fa404b98b..a9351282f2 100644 --- a/web/js/map/natural-events/event-track.js +++ b/web/js/map/natural-events/event-track.js @@ -27,12 +27,11 @@ class EventTrack extends React.Component { trackDetails: {}, }; - this.onMoveEnd = this.onMoveEnd.bind(this); - this.debouncedTrackUpdate = lodashDebounce(this.updateCurrentTrack, 200); + this.debouncedTrackUpdate = lodashDebounce(this.updateCurrentTrack, 50); this.debouncedOnPropertyChange = lodashDebounce( this.onPropertyChange.bind(this), - 20, - { leading: true, trailing: false }, + 100, + { leading: true, trailing: true }, ); } @@ -75,15 +74,12 @@ class EventTrack extends React.Component { componentWillUnmount() { const { map } = this.props; this.update(null); - map.un('moveend', this.onMoveEnd); map.getView().un('propertychange', this.debouncedOnPropertyChange); } initialize() { const { map } = this.props; if (!map) return; - // NOTE: Does not cause additional listeners to be registered on subsequent calls - map.on('moveend', this.onMoveEnd); map.getView().on('propertychange', this.debouncedOnPropertyChange); map.once('postrender', () => { this.debouncedTrackUpdate(); }); } @@ -97,41 +93,13 @@ class EventTrack extends React.Component { this.update(event, date); } - onMoveEnd = function(e) { - const { map } = this.props; - const { trackDetails } = this.state; - if (trackDetails.id) { - addPointOverlays(map, trackDetails.pointsAndArrows); - } else { - this.debouncedTrackUpdate(); - } - } - onPropertyChange = (e) => { const { map } = this.props; const { trackDetails } = this.state; + if (!trackDetails.id) return; if (e.key === 'resolution' || e.key === 'rotation') { const newTrackDetails = trackDetails.id ? this.removeTrack(map) : {}; this.setState({ trackDetails: newTrackDetails }); - } else if (e.key === 'center') { - // if old values equal target, map is not moving - // restricts track/cluster points from disappearing on min/max zoom - let isNewTarget = true; - if (e.target) { - const valueCheck = (val) => (typeof val === 'number' ? val.toFixed(6) : 0); - const oldValues = e.oldValue.map((val) => valueCheck(val)); - const targetValues = e.target.values_.center.map((val) => valueCheck(val)); - - const oldLon = oldValues[0]; - const oldLat = oldValues[1]; - const targetLon = targetValues[0]; - const targetLat = targetValues[1]; - - isNewTarget = oldLon !== targetLon || oldLat !== targetLat; - } - if (isNewTarget) { - removePointOverlays(map, trackDetails.pointsAndArrows); - } } } From 3e4fe07608bab90649042ebb97b2c0bd05cbd2e4 Mon Sep 17 00:00:00 2001 From: minniewong Date: Mon, 6 Dec 2021 15:56:07 -0500 Subject: [PATCH 25/35] change to available the last 20 days --- .../metadata/layers/modis/terra/MODIS_Terra_EVI_8Day.md | 2 +- .../metadata/layers/modis/terra/MODIS_Terra_NDVI_8Day.md | 2 +- .../wv.json/layers/modis/terra/MODIS_Terra_EVI_8Day.json | 5 ++++- .../wv.json/layers/modis/terra/MODIS_Terra_NDVI_8Day.json | 8 +++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_EVI_8Day.md b/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_EVI_8Day.md index e29b428628..10d45d84e3 100644 --- a/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_EVI_8Day.md +++ b/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_EVI_8Day.md @@ -1,5 +1,5 @@ The MODIS Enhanced Vegetation Index (EVI) is also used as a measure of the greenness and health of vegetation. It is calculated in a similar fashion as NDVI but it corrects for distortions caused by ground cover beneath the canopy vegetation and distortions in reflected light caused by particles in the air by using the blue band to remove residual atmosphere contamination caused by smoke and sub-pixel thin cirrus clouds. The EVI is more effective in areas with large amounts of chlorophyll such as rainforests. The index values range from -0.2 to 1 where higher values (0.3 to 1) indicate areas covered by green, leafy vegetation and lower values (0 to 0.3) indicate areas where there is little or no vegetation. -The MODIS rolling 8-day EVI layer is available as a near real-time, rolling 8-day product (MOD13Q4N) from from the Terra satellite. It is created from a rolling 8-day land surface reflectance product, MOD09Q1N. The sensor resolution is 250 m, imagery resolution is 250 m, and the temporal resolution is an 8-day product which is updated daily. +The MODIS rolling 8-day EVI layer is available as a near real-time, rolling 8-day product (MOD13Q4N) from from the Terra satellite. It is created from a rolling 8-day land surface reflectance product, MOD09Q1N. The sensor resolution is 250 m, imagery resolution is 250 m, and the temporal resolution is an 8-day product which is updated daily, and available for the last 20 days. References: MOD13Q4N [doi:10.5067/MODIS/MOD13Q4N.NRT.061](https://doi.org/10.5067/MODIS/MOD13Q4N.NRT.061) \ No newline at end of file diff --git a/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_NDVI_8Day.md b/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_NDVI_8Day.md index 4aa83c470a..a9b1040f3e 100644 --- a/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_NDVI_8Day.md +++ b/config/default/common/config/metadata/layers/modis/terra/MODIS_Terra_NDVI_8Day.md @@ -1,5 +1,5 @@ The MODIS Normalized Difference Vegetation Index (NDVI) layer is a measure of the greenness and health of vegetation. The index is calculated based on how much red and near-infrared light is reflected by plant leaves. The index values range from -0.2 to 1 where higher values (0.3 to 1) indicate areas covered by green, leafy vegetation and lower values (0 to 0.3) indicate areas where there is little or no vegetation. Areas with a lot of green leaf growth, indicates the presence of chlorophyll which reflects more infrared light and less visible light, are depicted in dark green colors, areas with some green leaf growth are in light greens, and areas with little to no vegetation growth are depicted in tan colors. -The MODIS rolling 8-day NDVI layer is available as a near real-time, rolling 8-day product (MOD13Q4N) from from the Terra satellite. It is created from a rolling 8-day land surface reflectance product, MOD09Q1N. The sensor resolution is 250 m, imagery resolution is 250 m, and the temporal resolution is an 8-day product which is updated daily. +The MODIS rolling 8-day NDVI layer is available as a near real-time, rolling 8-day product (MOD13Q4N) from from the Terra satellite. It is created from a rolling 8-day land surface reflectance product, MOD09Q1N. The sensor resolution is 250 m, imagery resolution is 250 m, and the temporal resolution is an 8-day product which is updated daily, and available for the last 20 days. References: MOD13Q4N [doi:10.5067/MODIS/MOD13Q4N.NRT.061](https://doi.org/10.5067/MODIS/MOD13Q4N.NRT.061) diff --git a/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_EVI_8Day.json b/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_EVI_8Day.json index 786b55546b..e83a50340e 100644 --- a/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_EVI_8Day.json +++ b/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_EVI_8Day.json @@ -11,7 +11,10 @@ "wrapadjacentdays": true, "daynight": [ "day" - ] + ], + "availability": { + "rollingWindow": 20 + } } } } \ No newline at end of file diff --git a/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_NDVI_8Day.json b/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_NDVI_8Day.json index ef2a9ba0c8..793402e476 100644 --- a/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_NDVI_8Day.json +++ b/config/default/common/config/wv.json/layers/modis/terra/MODIS_Terra_NDVI_8Day.json @@ -9,7 +9,13 @@ "tags": "ndvi normalized difference vegetation index", "layergroup": "Vegetation Indices", "product": "MOD13Q4N", - "wrapadjacentdays": true + "wrapadjacentdays": true, + "daynight": [ + "day" + ], + "availability": { + "rollingWindow": 20 + } } } } \ No newline at end of file From daa5adeae907e2f2d4d554beefd1bb917d5f5608 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 8 Dec 2021 09:27:25 -0700 Subject: [PATCH 26/35] v3.14.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0404577d60..2a457623a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "worldview", - "version": "3.13.3", + "version": "3.14.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3594c704fc..093b0883d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "worldview", - "version": "3.13.3", + "version": "3.14.0", "description": "Interactive interface for browsing full-resolution, global satellite imagery", "keywords": [ "NASA", From 6050dd4ca2b20f0ec54def47dbb28e3e5c3ed286 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Tue, 7 Dec 2021 11:07:53 -0700 Subject: [PATCH 27/35] allow cross-origin requests for configs --- rpm/httpd.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rpm/httpd.conf b/rpm/httpd.conf index b5eea25de1..68d1f47887 100644 --- a/rpm/httpd.conf +++ b/rpm/httpd.conf @@ -11,3 +11,7 @@ AddHandler cgi-script .cgi Options ExecCGI + + + Header set Access-Control-Allow-Origin "*" + From 8d516c59e439e81878c1116428d290b2249afc09 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Tue, 23 Nov 2021 11:04:50 -0700 Subject: [PATCH 28/35] disabled animation controls while playing --- web/css/anim.widget.css | 6 + web/css/dateselector.css | 94 +++++++---- web/css/rc-slider-overrides.css | 14 ++ web/css/timeline.css | 5 + .../date-selector/date-input-column.js | 10 +- .../date-selector/date-range-selector.js | 11 +- .../components/date-selector/date-selector.js | 44 +---- .../timescale-interval-change.js | 158 +++++++++--------- web/js/containers/animation-widget.js | 3 + 9 files changed, 191 insertions(+), 154 deletions(-) diff --git a/web/css/anim.widget.css b/web/css/anim.widget.css index 5495ea792c..ce93ee79fb 100644 --- a/web/css/anim.widget.css +++ b/web/css/anim.widget.css @@ -7,6 +7,9 @@ left: 10px; } } +.wv-animation-widget .input-range.rc-slider-disabled { + cursor: default; +} .wv-animation-widget { width: 334px; border-radius: 5px; @@ -30,6 +33,9 @@ & .interval-btn.interval-btn-active { color: #ce4c21; display: inline; + &.disabled { + color: #fff; + } } & .wv-tooltip { bottom: 128px; diff --git a/web/css/dateselector.css b/web/css/dateselector.css index b68e033262..7a717de913 100644 --- a/web/css/dateselector.css +++ b/web/css/dateselector.css @@ -17,44 +17,66 @@ .wv-date-selector-widget .input-wrapper:nth-child(4) { max-width: 35px; } - -.wv-date-selector-widget input { - color: #fff; - background: transparent; - border: 0; - font-family: "Roboto Mono", monospace; - vertical-align: middle; - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - padding: 0; - height: inherit; - text-align: center; - width: 100%; -} - -.wv-date-selector-widget .input-wrapper:hover { - border-color: #666; -} - -.wv-date-selector-widget .input-wrapper:hover .date-arrows { - visibility: visible; -} - -.wv-date-selector-widget .input-wrapper:hover .date-arrows svg path { - fill: #888; -} - -.wv-date-selector-widget .input-wrapper.selected { - border-color: #fff; -} - -.wv-date-selector-widget .input-wrapper.selected .date-arrows svg path { - fill: #fff; +.disabled .wv-date-selector-widget .input-wrapper { + &:hover { + border: 1px solid transparent; + } + input { + cursor: default; + } + .date-arrows { + &:hover { + background-color: transparent; + cursor: default; + } + svg path { + visibility: hidden; + } + } } -.wv-date-selector-widget div { - display: inline-block; +.wv-date-selector-widget { + div { + display: inline-block; + } + input { + color: #fff; + background: transparent; + border: 0; + font-family: "Roboto Mono", monospace; + vertical-align: middle; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + padding: 0; + height: inherit; + text-align: center; + width: 100%; + } + .input-wrapper:hover { + border-color: #666; + &.disabled { + border-color: transparent; + } + .date-arrows { + visibility: visible; + &.disabled { + visibility: hidden; + } + } + .date-arrows svg path { + fill: #888; + &.disabled { + fill: trasparent + } + } + } + .input-wrapper.selected { + border-color: #fff; + .date-arrows svg path { + fill: #fff; + } + } } .button-action-group { diff --git a/web/css/rc-slider-overrides.css b/web/css/rc-slider-overrides.css index d736a96f09..f2633ce3a3 100644 --- a/web/css/rc-slider-overrides.css +++ b/web/css/rc-slider-overrides.css @@ -18,3 +18,17 @@ .rc-slider-tooltip-inner { min-width: 40px; } + +.rc-slider-disabled { + background: transparent; + cursor: default; + .rc-slider-handle { + background: #999; + } + .rc-slider-rail { + background: #666; + } + .rc-slider-track { + background: #666; + } +} diff --git a/web/css/timeline.css b/web/css/timeline.css index 0705d50c0e..3a8592c7d6 100644 --- a/web/css/timeline.css +++ b/web/css/timeline.css @@ -507,6 +507,11 @@ button:focus { .interval-btn-active:hover { background-color: #fff; color: #1a1a1a; + &.disabled { + background-color: transparent; + color: #ccc; + cursor: default; + } } .zoom-btn, .interval-btn { diff --git a/web/js/components/date-selector/date-input-column.js b/web/js/components/date-selector/date-input-column.js index 57706613f1..d4f27e0d3f 100644 --- a/web/js/components/date-selector/date-input-column.js +++ b/web/js/components/date-selector/date-input-column.js @@ -219,8 +219,9 @@ class DateInputColumn extends Component { rollDate = (amt) => { const { - date, minDate, maxDate, type, updateDate, + date, minDate, maxDate, type, updateDate, isDisabled, } = this.props; + if (isDisabled) return; const newDate = util.rollDate( date, type, @@ -278,6 +279,7 @@ class DateInputColumn extends Component { isValid, isStartDate, isEndDate, + isDisabled, type, } = this.props; const { @@ -304,7 +306,7 @@ class DateInputColumn extends Component { > this.rollDate(1)} + onClick={isDisabled ? () => {} : () => this.rollDate(1)} type={type} /> this.rollDate(-1)} + onClick={isDisabled ? () => {} : () => this.rollDate(-1)} type={type} /> @@ -340,6 +343,7 @@ DateInputColumn.propTypes = { isValid: PropTypes.bool, isStartDate: PropTypes.bool, isEndDate: PropTypes.bool, + isDisabled: PropTypes.bool, maxDate: PropTypes.object, minDate: PropTypes.object, onFocus: PropTypes.func, diff --git a/web/js/components/date-selector/date-range-selector.js b/web/js/components/date-selector/date-range-selector.js index be222a99ef..91b3164dd5 100644 --- a/web/js/components/date-selector/date-range-selector.js +++ b/web/js/components/date-selector/date-range-selector.js @@ -4,7 +4,7 @@ import DateSelector from './date-selector'; export default function DateRangeSelector (props) { const { - startDate, endDate, setDateRange, minDate, maxDate, subDailyMode, idSuffix, + startDate, endDate, setDateRange, minDate, maxDate, subDailyMode, idSuffix, isDisabled, } = props; const setStartDate = (newStart) => { @@ -13,8 +13,12 @@ export default function DateRangeSelector (props) { const setEndDate = (newEnd) => { setDateRange([startDate, newEnd]); }; + const className = isDisabled + ? 'wv-date-range-selector disabled' + : 'wv-date-range-selector'; + return ( -
    +
    to
    @@ -32,6 +37,7 @@ export default function DateRangeSelector (props) { maxDate={maxDate} minDate={startDate} subDailyMode={subDailyMode} + isDisabled={isDisabled} isEndDate />
    @@ -40,6 +46,7 @@ export default function DateRangeSelector (props) { DateRangeSelector.propTypes = { idSuffix: PropTypes.string, + isDisabled: PropTypes.bool, startDate: PropTypes.object, endDate: PropTypes.object, setDateRange: PropTypes.func, diff --git a/web/js/components/date-selector/date-selector.js b/web/js/components/date-selector/date-selector.js index f84de8a717..4befe35635 100644 --- a/web/js/components/date-selector/date-selector.js +++ b/web/js/components/date-selector/date-selector.js @@ -28,43 +28,6 @@ class DateSelector extends Component { }; } - shouldComponentUpdate(nextProps, nextState) { - const { - date, - subDailyMode, - maxDate, - minDate, - } = this.props; - const { - year, - month, - day, - hour, - minute, - yearValid, - monthValid, - dayValid, - hourValid, - minuteValid, - } = this.state; - - const updateCheck = year === nextState.year - && month === nextState.month - && day === nextState.day - && hour === nextState.hour - && minute === nextState.minute - && yearValid === nextState.yearValid - && monthValid === nextState.monthValid - && dayValid === nextState.dayValid - && hourValid === nextState.hourValid - && minuteValid === nextState.minuteValid - && date.getTime() === nextProps.date.getTime() - && subDailyMode === nextProps.subDailyMode - && maxDate.getTime() === nextProps.maxDate.getTime() - && minDate.getTime() === nextProps.minDate.getTime(); - return !updateCheck; - } - componentDidUpdate(prevProps) { const { date, @@ -342,6 +305,7 @@ class DateSelector extends Component { subDailyMode, isStartDate, isEndDate, + isDisabled, } = this.props; const { year, @@ -382,18 +346,21 @@ class DateSelector extends Component { type="year" value={yearValue} isValid={yearValid} + isDisabled={isDisabled} /> { subDailyMode && ( <> @@ -402,6 +369,7 @@ class DateSelector extends Component { type="hour" value={hourValue} isValid={hourValid} + isDisabled={isDisabled} />
    :
    Z
    @@ -426,6 +395,7 @@ DateSelector.propTypes = { idSuffix: PropTypes.string, isStartDate: PropTypes.bool, isEndDate: PropTypes.bool, + isDisabled: PropTypes.bool, maxDate: PropTypes.object, minDate: PropTypes.object, onDateChange: PropTypes.func, diff --git a/web/js/components/timeline/timeline-controls/timescale-interval-change.js b/web/js/components/timeline/timeline-controls/timescale-interval-change.js index 280bb718f8..e46febaae6 100644 --- a/web/js/components/timeline/timeline-controls/timescale-interval-change.js +++ b/web/js/components/timeline/timeline-controls/timescale-interval-change.js @@ -57,15 +57,11 @@ class TimeScaleIntervalChange extends PureComponent { } } - toolTipHoverOn = () => { + setTooltipState = (hovered) => { + const { isDisabled } = this.props; + if (isDisabled) return; this.setState({ - toolTipHovered: true, - }); - } - - toolTipHoverOff = () => { - this.setState({ - toolTipHovered: false, + toolTipHovered: hovered, }); } @@ -126,95 +122,104 @@ class TimeScaleIntervalChange extends PureComponent { }); } + renderTooltip = () => { + const { toolTipHovered, customIntervalText } = this.state; + const { hasSubdailyLayers } = this.props; + return ( +
    +
    + this.handleClickInterval('year')} + > + Year + + this.handleClickInterval('month')} + > + Month + + this.handleClickInterval('day')} + > + Day + + {hasSubdailyLayers ? ( + <> + this.handleClickInterval('hour')} + > + Hour + + this.handleClickInterval('minute')} + > + Minute + + + ) : null} + this.handleClickInterval('custom')} + > + {customIntervalText} + + this.handleClickInterval('custom', true)} + > + Custom + +
    +
    + ); + } + render() { const { customIntervalText, - toolTipHovered, } = this.state; const { customSelected, - hasSubdailyLayers, interval, + isDisabled, } = this.props; + + const className = `no-drag interval-btn interval-btn-active${customSelected ? ' custom-interval-text' : ''} ${isDisabled ? ' disabled' : ''}`; return ( <>
    this.setTooltipState(true)} + onMouseLeave={() => this.setTooltipState(false)} onClick={this.onClick} > - {/* timeScale display */} + {customSelected ? customIntervalText : `${1} ${TIME_SCALE_FROM_NUMBER[interval]}`} - {/* hover timeScale unit dialog / entry point to Custom selector */} -
    -
    - this.handleClickInterval('year')} - > - Year - - this.handleClickInterval('month')} - > - Month - - this.handleClickInterval('day')} - > - Day - - {hasSubdailyLayers ? ( - <> - this.handleClickInterval('hour')} - > - Hour - - this.handleClickInterval('minute')} - > - Minute - - - ) : null} - this.handleClickInterval('custom')} - > - {customIntervalText} - - this.handleClickInterval('custom', true)} - > - Custom - -
    -
    + {!isDisabled ? this.renderTooltip() : null} +
    ); @@ -249,6 +254,7 @@ TimeScaleIntervalChange.propTypes = { customSelected: PropTypes.bool, hasSubdailyLayers: PropTypes.bool, interval: PropTypes.number, + isDisabled: PropTypes.bool, selectInterval: PropTypes.func, timeScaleChangeUnit: PropTypes.string, toggleCustomModal: PropTypes.func, diff --git a/web/js/containers/animation-widget.js b/web/js/containers/animation-widget.js index f1de33bcea..a4e1a84751 100644 --- a/web/js/containers/animation-widget.js +++ b/web/js/containers/animation-widget.js @@ -493,6 +493,7 @@ class AnimationWidget extends React.Component { timeScaleChangeUnit={interval} hasSubdailyLayers={hasSubdailyLayers} modalType={customModalType.ANIMATION} + isDisabled={isPlaying} /> {' Increments'}
    @@ -521,6 +522,7 @@ class AnimationWidget extends React.Component { onChange={(num) => this.setState({ speed: num })} handle={RangeHandle} onAfterChange={() => { onSlide(speed); }} + disabled={isPlaying} /> {sliderLabel} @@ -537,6 +539,7 @@ class AnimationWidget extends React.Component { minDate={minDate} maxDate={maxDate} subDailyMode={subDailyMode} + isDisabled={isPlaying} /> From ce9cfc274e53707e6d97b8828127ac0a3b7753cf Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 24 Nov 2021 09:04:25 -0700 Subject: [PATCH 29/35] move date specific util fns to date util file --- .../date-selector/date-input-column.js | 14 +- web/js/modules/date/util.js | 136 ++++++++++++++++++ web/js/util/util.js | 133 ----------------- 3 files changed, 143 insertions(+), 140 deletions(-) diff --git a/web/js/components/date-selector/date-input-column.js b/web/js/components/date-selector/date-input-column.js index d4f27e0d3f..73ec1405da 100644 --- a/web/js/components/date-selector/date-input-column.js +++ b/web/js/components/date-selector/date-input-column.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Arrow from '../util/arrow'; -import util from '../../util/util'; +import { rollDate } from '../../modules/date/util'; import { yearValidation, monthValidation, @@ -153,13 +153,13 @@ class DateInputColumn extends Component { if (keyCode === 38) { // up e.preventDefault(); - this.rollDate(1); + this.changeDate(1); return; } if (keyCode === 40) { // down e.preventDefault(); - this.rollDate(-1); + this.changeDate(-1); return; } if (e.type === 'focusout' || entered || tabbed) { @@ -217,12 +217,12 @@ class DateInputColumn extends Component { return newDate; } - rollDate = (amt) => { + changeDate = (amt) => { const { date, minDate, maxDate, type, updateDate, isDisabled, } = this.props; if (isDisabled) return; - const newDate = util.rollDate( + const newDate = rollDate( date, type, amt, @@ -306,7 +306,7 @@ class DateInputColumn extends Component { > {} : () => this.rollDate(1)} + onClick={isDisabled ? () => {} : () => this.changeDate(1)} type={type} /> {} : () => this.rollDate(-1)} + onClick={isDisabled ? () => {} : () => this.changeDate(-1)} type={type} /> diff --git a/web/js/modules/date/util.js b/web/js/modules/date/util.js index a48a2fd654..a68ef8dbf6 100644 --- a/web/js/modules/date/util.js +++ b/web/js/modules/date/util.js @@ -384,3 +384,139 @@ export const formatDisplayDate = (date, subdaily) => { }; export const formatISODate = (date) => moment(date).format('YYYY-MM-DD'); + +const getMinDate = function() { + return new Date(Date.UTC(1000, 0, 1, 0, 0)); +}; + +const getMaxDate = function() { + return new Date(Date.UTC(3000, 11, 30, 23, 59)); +}; + +const roll = function(val, min, max) { + if (val < min) { + return max - (min - val) + 1; + } + if (val > max) { + return min + (val - max) - 1; + } + return val; +}; + +const getDaysInMonth = function(d) { + let year; + let month; + if (d.getUTCFullYear) { + year = d.getUTCFullYear(); + month = d.getUTCMonth(); + } else { + year = d.year; + month = d.month; + } + const lastDay = new Date(Date.UTC(year, month + 1, 0)); + return lastDay.getUTCDate(); +}; + +const rollRange = function(date, interval, minDate, maxDate) { + const year = date.getUTCFullYear(); + const month = date.getUTCMonth(); + let first; + let last; + switch (interval) { + case 'minute': { + const firstMinute = new Date(Date.UTC(year, month, 1, 0, 0)); + const lastMinute = new Date(Date.UTC(year, month, getDaysInMonth(date), 23, 59)); + first = new Date(Math.max(firstMinute, minDate)) + .getUTCMinutes(); + last = new Date(Math.min(lastMinute, maxDate)) + .getUTCMinutes(); + break; + } + case 'hour': { + const firstHour = new Date(Date.UTC(year, month, 1, 0)); + const lastHour = new Date(Date.UTC(year, month, getDaysInMonth(date), 23)); + first = new Date(Math.max(firstHour, minDate)) + .getUTCHours(); + last = new Date(Math.min(lastHour, maxDate)) + .getUTCHours(); + break; + } + case 'day': { + const firstDay = new Date(Date.UTC(year, month, 1)); + const lastDay = new Date(Date.UTC(year, month, getDaysInMonth(date))); + first = new Date(Math.max(firstDay, minDate)) + .getUTCDate(); + last = new Date(Math.min(lastDay, maxDate)) + .getUTCDate(); + break; + } + case 'month': { + const firstMonth = new Date(Date.UTC(year, 0, 1)); + const lastMonth = new Date(Date.UTC(year, 11, 31)); + first = new Date(Math.max(firstMonth, minDate)) + .getUTCMonth(); + last = new Date(Math.min(lastMonth, maxDate)) + .getUTCMonth(); + break; + } + case 'year': { + const firstYear = getMinDate(); + const lastYear = getMaxDate(); + first = new Date(Math.max(firstYear, minDate)) + .getUTCFullYear(); + last = new Date(Math.min(lastYear, maxDate)) + .getUTCFullYear(); + break; + } + default: + break; + } + return { + first, + last, + }; +}; + +export const rollDate = function(date, interval, amount, minDate, maxDate) { + const newMinDate = minDate || getMinDate(); + const newMaxDate = maxDate || getMaxDate(); + const range = rollRange(date, interval, newMinDate, newMaxDate); + const min = range.first; + const max = range.last; + const second = date.getUTCSeconds(); + let minute = date.getUTCMinutes(); + let hour = date.getUTCHours(); + let day = date.getUTCDate(); + let month = date.getUTCMonth(); + let year = date.getUTCFullYear(); + switch (interval) { + // TODO: change minute and hour hard-coded min & max to be dynamic + case 'minute': + minute = roll(minute + amount, 0, 59); + break; + case 'hour': + hour = roll(hour + amount, 0, 23); + break; + case 'day': + day = roll(day + amount, min, max); + break; + case 'month': + month = roll(month + amount, min, max); + break; + case 'year': + year = roll(year + amount, min, max); + break; + default: + throw new Error(`[rollDate] Invalid interval: ${interval}`); + } + const daysInMonth = getDaysInMonth({ + year, + month, + }); + if (day > daysInMonth) { + day = daysInMonth; + } + let newDate = new Date(Date.UTC(year, month, day, hour, minute, second)); + newDate = new Date(util.clamp(newDate, newMinDate, newMaxDate)); + return newDate; +}; diff --git a/web/js/util/util.js b/web/js/util/util.js index 7025046b95..7a3a50260e 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -338,20 +338,6 @@ export default (function(self) { return i; }; - self.daysInMonth = function(d) { - let year; - let month; - if (d.getUTCFullYear) { - year = d.getUTCFullYear(); - month = d.getUTCMonth(); - } else { - year = d.year; - month = d.month; - } - const lastDay = new Date(Date.UTC(year, month + 1, 0)); - return lastDay.getUTCDate(); - }; - self.daysInYear = function(date) { const jStart = self.parseDateUTC(`${date.getUTCFullYear()}-01-01`); const jDate = `00${Math.ceil((date.getTime() - jStart) / 86400000) + 1}`; @@ -422,125 +408,6 @@ export default (function(self) { return val; }; - self.roll = function(val, min, max) { - if (val < min) { - return max - (min - val) + 1; - } - if (val > max) { - return min + (val - max) - 1; - } - return val; - }; - - self.minDate = function() { - return new Date(Date.UTC(1000, 0, 1, 0, 0)); - }; - - self.maxDate = function() { - return new Date(Date.UTC(3000, 11, 30, 23, 59)); - }; - - self.rollRange = function(date, interval, minDate, maxDate) { - const year = date.getUTCFullYear(); - const month = date.getUTCMonth(); - let first; - let last; - switch (interval) { - case 'minute': { - const firstMinute = new Date(Date.UTC(year, month, 1, 0, 0)); - const lastMinute = new Date(Date.UTC(year, month, self.daysInMonth(date), 23, 59)); - first = new Date(Math.max(firstMinute, minDate)) - .getUTCMinutes(); - last = new Date(Math.min(lastMinute, maxDate)) - .getUTCMinutes(); - break; - } - case 'hour': { - const firstHour = new Date(Date.UTC(year, month, 1, 0)); - const lastHour = new Date(Date.UTC(year, month, self.daysInMonth(date), 23)); - first = new Date(Math.max(firstHour, minDate)) - .getUTCHours(); - last = new Date(Math.min(lastHour, maxDate)) - .getUTCHours(); - break; - } - case 'day': { - const firstDay = new Date(Date.UTC(year, month, 1)); - const lastDay = new Date(Date.UTC(year, month, self.daysInMonth(date))); - first = new Date(Math.max(firstDay, minDate)) - .getUTCDate(); - last = new Date(Math.min(lastDay, maxDate)) - .getUTCDate(); - break; - } - case 'month': { - const firstMonth = new Date(Date.UTC(year, 0, 1)); - const lastMonth = new Date(Date.UTC(year, 11, 31)); - first = new Date(Math.max(firstMonth, minDate)) - .getUTCMonth(); - last = new Date(Math.min(lastMonth, maxDate)) - .getUTCMonth(); - break; - } - case 'year': { - const firstYear = self.minDate(); - const lastYear = self.maxDate(); - first = new Date(Math.max(firstYear, minDate)) - .getUTCFullYear(); - last = new Date(Math.min(lastYear, maxDate)) - .getUTCFullYear(); - break; - } - default: - break; - } - return { - first, - last, - }; - }; - - self.rollDate = function(date, interval, amount, minDate = self.minDate(), maxDate = self.maxDate()) { - const range = self.rollRange(date, interval, minDate, maxDate); - const min = range.first; - const max = range.last; - const second = date.getUTCSeconds(); - let minute = date.getUTCMinutes(); - let hour = date.getUTCHours(); - let day = date.getUTCDate(); - let month = date.getUTCMonth(); - let year = date.getUTCFullYear(); - switch (interval) { - // TODO: change minute and hour hard-coded min & max to be dynamic - case 'minute': - minute = self.roll(minute + amount, 0, 59); - break; - case 'hour': - hour = self.roll(hour + amount, 0, 23); - break; - case 'day': - day = self.roll(day + amount, min, max); - break; - case 'month': - month = self.roll(month + amount, min, max); - break; - case 'year': - year = self.roll(year + amount, min, max); - break; - default: - throw new Error(`[rollDate] Invalid interval: ${interval}`); - } - const daysInMonth = self.daysInMonth({ - year, - month, - }); - if (day > daysInMonth) { - day = daysInMonth; - } - let newDate = new Date(Date.UTC(year, month, day, hour, minute, second)); - newDate = new Date(self.clamp(newDate, minDate, maxDate)); - return newDate; - }; /** * Gets the current time minus minutesOffset for geostationary layers. From c63664f5b48c01616011be34271bd0d043fc385f Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 24 Nov 2021 14:24:37 -0700 Subject: [PATCH 30/35] prevent playback with >300 frames - also don't snap date when max frames exceeded --- web/css/anim.widget.css | 4 ++ .../animation-widget/play-button.js | 17 +++++--- web/js/containers/animation-widget.js | 42 ++++++++++++------- web/js/containers/gif.js | 2 +- web/js/util/util.js | 7 ++-- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/web/css/anim.widget.css b/web/css/anim.widget.css index ce93ee79fb..e0c3590b32 100644 --- a/web/css/anim.widget.css +++ b/web/css/anim.widget.css @@ -10,6 +10,7 @@ .wv-animation-widget .input-range.rc-slider-disabled { cursor: default; } + .wv-animation-widget { width: 334px; border-radius: 5px; @@ -64,6 +65,9 @@ border-radius: 5px; &.disabled { cursor: not-allowed; + svg.svg-inline--fa:hover { + cursor: not-allowed; + } & .wv-animation-widget-icon { color: rgba(255, 255, 255, 0.3); opacity: 0.5; diff --git a/web/js/components/animation-widget/play-button.js b/web/js/components/animation-widget/play-button.js index 137093bcc0..f825150c0a 100644 --- a/web/js/components/animation-widget/play-button.js +++ b/web/js/components/animation-widget/play-button.js @@ -7,16 +7,22 @@ import { UncontrolledTooltip } from 'reactstrap'; * @class PlayButton * @extends React.Component */ -const PlayButton = (props) => { - const { playing, pause, play } = props; +const PlayButton = ({ + playing, pause, play, isDisabled, +}) => { const buttonId = 'play-button'; - const labelText = playing ? 'Pause animation' : 'Play animation'; + const labelText = isDisabled + ? 'Too many animation frames. Reduce time range or increase increment size.' + : playing + ? 'Pause animation' : 'Play animation'; + const onClick = isDisabled ? () => {} : playing ? pause : play; + return (
    { const widgetWidth = 334; const subdailyWidgetWidth = 460; -const maxFrames = 40; +const maxFrames = 300; +const maxGifFrames = 40; /* * A react component, Builds a rather specific @@ -349,6 +350,7 @@ class AnimationWidget extends React.Component { isPlaying, onPushPause, hasSubdailyLayers, + numberOfFrames, } = this.props; const { collapsedWidgetPosition } = this.state; const cancelSelector = '.no-drag, svg'; @@ -371,6 +373,7 @@ class AnimationWidget extends React.Component { playing={isPlaying} play={this.onPushPlay} pause={onPushPause} + isDisabled={numberOfFrames >= maxFrames} /> @@ -395,9 +398,9 @@ class AnimationWidget extends React.Component { visibleLayersForProj, proj, } = this.props; - const gifDisabled = numberOfFrames >= maxFrames; + const gifDisabled = numberOfFrames >= maxGifFrames; const elemExists = document.querySelector('#create-gif-button'); - const showWarning = elemExists && numberOfFrames >= maxFrames; + const showWarning = elemExists && numberOfFrames >= maxGifFrames; const warningMessage = ( Too many frames were selected. @@ -409,7 +412,7 @@ class AnimationWidget extends React.Component { const openGif = async () => { const { startDate, endDate } = this.zeroDates(); - if (numberOfFrames >= maxFrames) { + if (numberOfFrames >= maxGifFrames) { return; } const nonDownloadableLayers = hasNonDownloadableLayer ? getNonDownloadableLayers(visibleLayersForProj) : null; @@ -469,6 +472,7 @@ class AnimationWidget extends React.Component { interval, animationCustomModalOpen, hasSubdailyLayers, + numberOfFrames, } = this.props; const { speed, widgetPosition } = this.state; const cancelSelector = '.no-drag, .date-arrows'; @@ -508,6 +512,7 @@ class AnimationWidget extends React.Component { playing={isPlaying} play={this.onPushPlay} pause={onPushPause} + isDisabled={numberOfFrames >= maxFrames} /> @@ -563,7 +568,7 @@ class AnimationWidget extends React.Component { isDistractionFreeModeActive, promiseImageryForTime, selectDate, - currentDate, + snappedCurrentDate, isGifActive, delta, interval, @@ -578,13 +583,6 @@ class AnimationWidget extends React.Component { delta, ); - const snappedCurrentDate = snapToIntervalDelta( - currentDate, - startDate, - endDate, - interval, - delta, - ); if (!isActive) { return null; @@ -687,7 +685,7 @@ function mapStateToProps(state) { const useInterval = customSelected ? customInterval || 3 : interval; const subDailyInterval = useInterval > 3; const subDailyMode = subDailyInterval && hasSubdailyLayers; - const numberOfFrames = util.getNumberOfDays( + const numberOfFrames = util.getNumberOfSteps( startDate, endDate, TIME_SCALE_FROM_NUMBER[useInterval], @@ -696,6 +694,20 @@ function mapStateToProps(state) { ); const { rotation } = map; const visibleLayersForProj = lodashFilter(activeLayersForProj, 'visible'); + const currentDate = getSelectedDate(state); + let snappedCurrentDate; + if (numberOfFrames <= maxFrames) { + snappedCurrentDate = snapToIntervalDelta( + currentDate, + startDate, + endDate, + TIME_SCALE_FROM_NUMBER[useInterval], + delta, + ); + } else { + snappedCurrentDate = currentDate; + } + return { appNow, screenWidth: browser.screenWidth, @@ -704,7 +716,7 @@ function mapStateToProps(state) { startDate, endDate, activePalettes, - currentDate: getSelectedDate(state), + snappedCurrentDate, minDate, maxDate, isActive: animationIsActive, @@ -831,7 +843,7 @@ AnimationWidget.propTypes = { activePalettes: PropTypes.object, animationCustomModalOpen: PropTypes.bool, visibleLayersForProj: PropTypes.array, - currentDate: PropTypes.object, + snappedCurrentDate: PropTypes.object, customDelta: PropTypes.number, customInterval: PropTypes.number, delta: PropTypes.number, diff --git a/web/js/containers/gif.js b/web/js/containers/gif.js index d1398a9c1f..768210300d 100644 --- a/web/js/containers/gif.js +++ b/web/js/containers/gif.js @@ -410,7 +410,7 @@ function mapStateToProps(state) { speed, map, url, - numberOfFrames: util.getNumberOfDays( + numberOfFrames: util.getNumberOfSteps( startDate, endDate, customSelected diff --git a/web/js/util/util.js b/web/js/util/util.js index 7a3a50260e..669ed9cfdd 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -308,8 +308,7 @@ export default (function(self) { case 'month': year = newDate.getUTCFullYear(); month = newDate.getUTCMonth(); - maxDay = new Date(year, month + amount + 1, 0) - .getUTCDate(); + maxDay = new Date(year, month + amount + 1, 0).getUTCDate(); if (maxDay <= date.getUTCDate()) { newDate.setUTCDate(maxDay); } @@ -324,12 +323,12 @@ export default (function(self) { return newDate; }; - self.getNumberOfDays = function(start, end, interval, increment = 1, maxToCheck) { + self.getNumberOfSteps = function(start, end, interval, delta = 1, maxToCheck) { let i = 1; let currentDate = start; while (currentDate < end) { i += 1; - currentDate = self.dateAdd(currentDate, interval, increment); + currentDate = self.dateAdd(currentDate, interval, delta); // if checking for a max number limit, break out after reaching it if (maxToCheck && i >= maxToCheck) { return i; From d8b620353d069b8554ac7cbfde817b2bbd2c77c7 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Wed, 24 Nov 2021 14:38:07 -0700 Subject: [PATCH 31/35] fix unit tests/linting --- web/css/dateselector.css | 18 ++-- web/js/modules/date/util.js | 4 +- web/js/modules/date/util.test.js | 177 ++++++++++++++++++++++++++++++- web/js/util/util.test.js | 171 ----------------------------- 4 files changed, 187 insertions(+), 183 deletions(-) diff --git a/web/css/dateselector.css b/web/css/dateselector.css index 7a717de913..d91590274a 100644 --- a/web/css/dateselector.css +++ b/web/css/dateselector.css @@ -24,14 +24,14 @@ input { cursor: default; } - .date-arrows { - &:hover { - background-color: transparent; - cursor: default; - } - svg path { - visibility: hidden; - } +} +.disabled .wv-date-selector-widget { + .date-arrows &:hover { + background-color: transparent; + cursor: default; + } + .date-arrows svg path { + visibility: hidden; } } @@ -67,7 +67,7 @@ .date-arrows svg path { fill: #888; &.disabled { - fill: trasparent + fill: trasparent; } } } diff --git a/web/js/modules/date/util.js b/web/js/modules/date/util.js index a68ef8dbf6..ffd40d49c2 100644 --- a/web/js/modules/date/util.js +++ b/web/js/modules/date/util.js @@ -393,7 +393,7 @@ const getMaxDate = function() { return new Date(Date.UTC(3000, 11, 30, 23, 59)); }; -const roll = function(val, min, max) { +export const roll = function(val, min, max) { if (val < min) { return max - (min - val) + 1; } @@ -403,7 +403,7 @@ const roll = function(val, min, max) { return val; }; -const getDaysInMonth = function(d) { +export const getDaysInMonth = function(d) { let year; let month; if (d.getUTCFullYear) { diff --git a/web/js/modules/date/util.test.js b/web/js/modules/date/util.test.js index d4ff993339..a67783eeba 100644 --- a/web/js/modules/date/util.test.js +++ b/web/js/modules/date/util.test.js @@ -1,4 +1,6 @@ -import { mapLocationToDateState, tryCatchDate } from './util'; +import { + mapLocationToDateState, tryCatchDate, rollDate, roll, getDaysInMonth, +} from './util'; import fixtures from '../../fixtures'; const state = fixtures.getState(); @@ -34,3 +36,176 @@ test('If date is invalid, uses Initial Time', () => { expect(stateFromLocation.date.selected).toBe(state.date.selected); }); + +describe('rollDate', () => { + const tests = [{ + name: 'day up', + d: new Date(Date.UTC(2014, 1, 15)), + period: { value: 1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 1, 16)), + }, { + name: 'day up, roll', + d: new Date(Date.UTC(2014, 1, 28)), + period: { value: 1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 1, 1)), + }, { + name: 'day down', + d: new Date(Date.UTC(2014, 1, 15)), + period: { value: -1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 1, 14)), + }, { + name: 'day down, roll', + d: new Date(Date.UTC(2014, 1, 1)), + period: { value: -1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 1, 28)), + }, { + name: 'day up, roll over max', + maxDate: new Date(Date.UTC(2014, 11, 2)), + d: new Date(Date.UTC(2014, 11, 2)), + period: { value: 1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 11, 1)), + }, { + name: 'day down, roll over max', + maxDate: new Date(Date.UTC(2014, 11, 2)), + d: new Date(Date.UTC(2014, 11, 1)), + period: { value: -1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 11, 2)), + }, { + name: 'day up, roll over min', + minDate: new Date(Date.UTC(2014, 11, 2)), + d: new Date(Date.UTC(2014, 11, 31)), + period: { value: 1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 11, 2)), + }, { + name: 'day down, roll over min', + minDate: new Date(Date.UTC(2014, 11, 2)), + d: new Date(Date.UTC(2014, 11, 2)), + period: { value: -1, unit: 'day' }, + answer: new Date(Date.UTC(2014, 11, 31)), + }, { + name: 'month up', + d: new Date(Date.UTC(2014, 5, 15)), + period: { value: 1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 6, 15)), + }, { + name: 'month up, roll', + d: new Date(Date.UTC(2014, 11, 15)), + period: { value: 1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 0, 15)), + }, { + name: 'month down', + d: new Date(Date.UTC(2014, 5, 15)), + period: { value: -1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 4, 15)), + }, { + name: 'month down, roll', + d: new Date(Date.UTC(2014, 0, 15)), + period: { value: -1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 11, 15)), + }, { + name: 'month up, roll over max', + maxDate: new Date(Date.UTC(2014, 10, 2)), + d: new Date(Date.UTC(2014, 9, 20)), + period: { value: 1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 10, 2)), + }, { + name: 'month up, roll past max', + maxDate: new Date(Date.UTC(2014, 10, 2)), + d: new Date(Date.UTC(2014, 10, 1)), + period: { value: 1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 0, 1)), + }, { + name: 'month down, roll over max', + maxDate: new Date(Date.UTC(2014, 10, 2)), + d: new Date(Date.UTC(2014, 0, 20)), + period: { value: -1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 10, 2)), + }, { + name: 'month up, truncate day', + d: new Date(Date.UTC(2014, 0, 31)), + period: { value: 1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 1, 28)), + }, { + name: 'month down, roll over min', + minDate: new Date(Date.UTC(2014, 2, 25)), + d: new Date(Date.UTC(2014, 3, 1)), + period: { value: -1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 2, 25)), + }, { + name: 'month up, roll over min', + minDate: new Date(Date.UTC(2014, 2, 25)), + d: new Date(Date.UTC(2014, 11, 1)), + period: { value: 1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 2, 25)), + }, { + name: 'month down, roll past min', + minDate: new Date(Date.UTC(2014, 2, 25)), + d: new Date(Date.UTC(2014, 2, 27)), + period: { value: -1, unit: 'month' }, + answer: new Date(Date.UTC(2014, 11, 27)), + }, { + name: 'year up', + d: new Date(Date.UTC(2014, 5, 15)), + period: { value: 1, unit: 'year' }, + answer: new Date(Date.UTC(2015, 5, 15)), + }, { + name: 'year down', + d: new Date(Date.UTC(2014, 5, 15)), + period: { value: -1, unit: 'year' }, + answer: new Date(Date.UTC(2013, 5, 15)), + }, { + name: 'year up, roll over max', + minDate: new Date(Date.UTC(2000, 3, 12)), + maxDate: new Date(Date.UTC(2015, 6, 16)), + d: new Date(Date.UTC(2015, 0, 1)), + period: { value: 1, unit: 'year' }, + answer: new Date(Date.UTC(2000, 3, 12)), + }, { + name: 'year down, roll over min', + minDate: new Date(Date.UTC(2000, 3, 12)), + maxDate: new Date(Date.UTC(2015, 6, 16)), + d: new Date(Date.UTC(2000, 8, 1)), + period: { value: -1, unit: 'year' }, + answer: new Date(Date.UTC(2015, 6, 16)), + }]; + + tests.forEach((t) => { + test(t.name, () => { + const result = rollDate(t.d, t.period.unit, t.period.value, t.minDate, t.maxDate); + expect(result).toEqual(t.answer); + }); + }); +}); + +describe('roll', () => { + const tests = [ + { + v: 15, min: 10, max: 20, answer: 15, name: 'middle', + }, + { + v: 8, min: 10, max: 20, answer: 19, name: 'min', + }, + { + v: 22, min: 10, max: 20, answer: 11, name: 'max', + }, + ]; + + tests.forEach((t) => { + test(t.name, () => { + expect(roll(t.v, t.min, t.max)).toBe(t.answer); + }); + }); +}); + +describe('daysInMonth', () => { + test('feb, non-leap', () => { + const d = new Date(Date.UTC(2015, 1, 15)); + expect(getDaysInMonth(d)).toBe(28); + }); + + test('feb, leap', () => { + const d = new Date(Date.UTC(2016, 1, 15)); + expect(getDaysInMonth(d)).toBe(29); + }); +}); + diff --git a/web/js/util/util.test.js b/web/js/util/util.test.js index 1c35afa399..6aa023f7a6 100644 --- a/web/js/util/util.test.js +++ b/web/js/util/util.test.js @@ -116,17 +116,6 @@ describe('dateAdd', () => { }); }); -describe('daysInMonth', () => { - test('feb, non-leap', () => { - const d = new Date(Date.UTC(2015, 1, 15)); - expect(util.daysInMonth(d)).toBe(28); - }); - - test('feb, leap', () => { - const d = new Date(Date.UTC(2016, 1, 15)); - expect(util.daysInMonth(d)).toBe(29); - }); -}); describe('daysInYear', () => { const tests = [ @@ -163,166 +152,6 @@ describe('clamp', () => { }); }); -describe('roll', () => { - const tests = [ - { - v: 15, min: 10, max: 20, answer: 15, name: 'middle', - }, - { - v: 8, min: 10, max: 20, answer: 19, name: 'min', - }, - { - v: 22, min: 10, max: 20, answer: 11, name: 'max', - }, - ]; - - tests.forEach((t) => { - test(t.name, () => { - expect(util.roll(t.v, t.min, t.max)).toBe(t.answer); - }); - }); -}); - -describe('rollDate', () => { - const tests = [{ - name: 'day up', - d: new Date(Date.UTC(2014, 1, 15)), - period: { value: 1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 1, 16)), - }, { - name: 'day up, roll', - d: new Date(Date.UTC(2014, 1, 28)), - period: { value: 1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 1, 1)), - }, { - name: 'day down', - d: new Date(Date.UTC(2014, 1, 15)), - period: { value: -1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 1, 14)), - }, { - name: 'day down, roll', - d: new Date(Date.UTC(2014, 1, 1)), - period: { value: -1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 1, 28)), - }, { - name: 'day up, roll over max', - maxDate: new Date(Date.UTC(2014, 11, 2)), - d: new Date(Date.UTC(2014, 11, 2)), - period: { value: 1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 11, 1)), - }, { - name: 'day down, roll over max', - maxDate: new Date(Date.UTC(2014, 11, 2)), - d: new Date(Date.UTC(2014, 11, 1)), - period: { value: -1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 11, 2)), - }, { - name: 'day up, roll over min', - minDate: new Date(Date.UTC(2014, 11, 2)), - d: new Date(Date.UTC(2014, 11, 31)), - period: { value: 1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 11, 2)), - }, { - name: 'day down, roll over min', - minDate: new Date(Date.UTC(2014, 11, 2)), - d: new Date(Date.UTC(2014, 11, 2)), - period: { value: -1, unit: 'day' }, - answer: new Date(Date.UTC(2014, 11, 31)), - }, { - name: 'month up', - d: new Date(Date.UTC(2014, 5, 15)), - period: { value: 1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 6, 15)), - }, { - name: 'month up, roll', - d: new Date(Date.UTC(2014, 11, 15)), - period: { value: 1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 0, 15)), - }, { - name: 'month down', - d: new Date(Date.UTC(2014, 5, 15)), - period: { value: -1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 4, 15)), - }, { - name: 'month down, roll', - d: new Date(Date.UTC(2014, 0, 15)), - period: { value: -1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 11, 15)), - }, { - name: 'month up, roll over max', - maxDate: new Date(Date.UTC(2014, 10, 2)), - d: new Date(Date.UTC(2014, 9, 20)), - period: { value: 1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 10, 2)), - }, { - name: 'month up, roll past max', - maxDate: new Date(Date.UTC(2014, 10, 2)), - d: new Date(Date.UTC(2014, 10, 1)), - period: { value: 1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 0, 1)), - }, { - name: 'month down, roll over max', - maxDate: new Date(Date.UTC(2014, 10, 2)), - d: new Date(Date.UTC(2014, 0, 20)), - period: { value: -1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 10, 2)), - }, { - name: 'month up, truncate day', - d: new Date(Date.UTC(2014, 0, 31)), - period: { value: 1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 1, 28)), - }, { - name: 'month down, roll over min', - minDate: new Date(Date.UTC(2014, 2, 25)), - d: new Date(Date.UTC(2014, 3, 1)), - period: { value: -1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 2, 25)), - }, { - name: 'month up, roll over min', - minDate: new Date(Date.UTC(2014, 2, 25)), - d: new Date(Date.UTC(2014, 11, 1)), - period: { value: 1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 2, 25)), - }, { - name: 'month down, roll past min', - minDate: new Date(Date.UTC(2014, 2, 25)), - d: new Date(Date.UTC(2014, 2, 27)), - period: { value: -1, unit: 'month' }, - answer: new Date(Date.UTC(2014, 11, 27)), - }, { - name: 'year up', - d: new Date(Date.UTC(2014, 5, 15)), - period: { value: 1, unit: 'year' }, - answer: new Date(Date.UTC(2015, 5, 15)), - }, { - name: 'year down', - d: new Date(Date.UTC(2014, 5, 15)), - period: { value: -1, unit: 'year' }, - answer: new Date(Date.UTC(2013, 5, 15)), - }, { - name: 'year up, roll over max', - minDate: new Date(Date.UTC(2000, 3, 12)), - maxDate: new Date(Date.UTC(2015, 6, 16)), - d: new Date(Date.UTC(2015, 0, 1)), - period: { value: 1, unit: 'year' }, - answer: new Date(Date.UTC(2000, 3, 12)), - }, { - name: 'year down, roll over min', - minDate: new Date(Date.UTC(2000, 3, 12)), - maxDate: new Date(Date.UTC(2015, 6, 16)), - d: new Date(Date.UTC(2000, 8, 1)), - period: { value: -1, unit: 'year' }, - answer: new Date(Date.UTC(2015, 6, 16)), - }]; - - tests.forEach((t) => { - test(t.name, () => { - const result = util.rollDate(t.d, t.period.unit, t.period.value, t.minDate, t.maxDate); - expect(result).toEqual(t.answer); - }); - }); -}); - describe('formatDMS', () => { const tests = [{ name: 'zero', From 6f18245af6bf908d09fce8b750b38b6ab2e492ad Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Fri, 3 Dec 2021 11:01:56 -0700 Subject: [PATCH 32/35] don't calculate all dates when max frames exceeded --- web/js/containers/animation-widget.js | 5 +++-- web/js/containers/gif.js | 5 ++--- web/js/modules/animation/util.js | 14 ++++++++++++++ web/js/util/util.js | 14 -------------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/web/js/containers/animation-widget.js b/web/js/containers/animation-widget.js index cc17378928..a707425c76 100644 --- a/web/js/containers/animation-widget.js +++ b/web/js/containers/animation-widget.js @@ -37,6 +37,7 @@ import { getQueueLength, getMaxQueueLength, snapToIntervalDelta, + getNumberOfSteps, } from '../modules/animation/util'; import { subdailyLayersActive, @@ -685,7 +686,7 @@ function mapStateToProps(state) { const useInterval = customSelected ? customInterval || 3 : interval; const subDailyInterval = useInterval > 3; const subDailyMode = subDailyInterval && hasSubdailyLayers; - const numberOfFrames = util.getNumberOfSteps( + const numberOfFrames = getNumberOfSteps( startDate, endDate, TIME_SCALE_FROM_NUMBER[useInterval], @@ -696,7 +697,7 @@ function mapStateToProps(state) { const visibleLayersForProj = lodashFilter(activeLayersForProj, 'visible'); const currentDate = getSelectedDate(state); let snappedCurrentDate; - if (numberOfFrames <= maxFrames) { + if (numberOfFrames < maxFrames) { snappedCurrentDate = snapToIntervalDelta( currentDate, startDate, diff --git a/web/js/containers/gif.js b/web/js/containers/gif.js index 768210300d..5ece2fc298 100644 --- a/web/js/containers/gif.js +++ b/web/js/containers/gif.js @@ -10,7 +10,6 @@ import { import GifStream from '../modules/animation/gifstream'; import GifPanel from '../components/animation-widget/gif-panel'; import util from '../util/util'; - import Crop from '../components/util/image-crop'; import { resolutionsGeo, @@ -23,7 +22,7 @@ import { import { TIME_SCALE_FROM_NUMBER } from '../modules/date/constants'; import GifResults from '../components/animation-widget/gif-post-creation'; import getImageArray from '../modules/animation/selectors'; -import { getStampProps, svgToPng } from '../modules/animation/util'; +import { getStampProps, svgToPng, getNumberOfSteps } from '../modules/animation/util'; import { changeCropBounds } from '../modules/animation/actions'; import { subdailyLayersActive } from '../modules/layers/selectors'; import { formatDisplayDate } from '../modules/date/util'; @@ -410,7 +409,7 @@ function mapStateToProps(state) { speed, map, url, - numberOfFrames: util.getNumberOfSteps( + numberOfFrames: getNumberOfSteps( startDate, endDate, customSelected diff --git a/web/js/modules/animation/util.js b/web/js/modules/animation/util.js index 09f01c6684..b54d34fd8d 100644 --- a/web/js/modules/animation/util.js +++ b/web/js/modules/animation/util.js @@ -36,6 +36,20 @@ export function snapToIntervalDelta(currDate, startDate, endDate, interval, delt return currentDate || startDate; } +export function getNumberOfSteps(start, end, interval, delta = 1, maxToCheck) { + let i = 1; + let currentDate = start; + while (currentDate < end) { + i += 1; + currentDate = util.dateAdd(currentDate, interval, delta); + // if checking for a max number limit, break out after reaching it + if (maxToCheck && i >= maxToCheck) { + return i; + } + } + return i; +} + export function getStampProps( stampWidthRatio, breakPoint, diff --git a/web/js/util/util.js b/web/js/util/util.js index 669ed9cfdd..374bfed5c4 100644 --- a/web/js/util/util.js +++ b/web/js/util/util.js @@ -323,20 +323,6 @@ export default (function(self) { return newDate; }; - self.getNumberOfSteps = function(start, end, interval, delta = 1, maxToCheck) { - let i = 1; - let currentDate = start; - while (currentDate < end) { - i += 1; - currentDate = self.dateAdd(currentDate, interval, delta); - // if checking for a max number limit, break out after reaching it - if (maxToCheck && i >= maxToCheck) { - return i; - } - } - return i; - }; - self.daysInYear = function(date) { const jStart = self.parseDateUTC(`${date.getUTCFullYear()}-01-01`); const jDate = `00${Math.ceil((date.getTime() - jStart) / 86400000) + 1}`; From c381256c26e7d298a0a9eff1cc8f8f87bde90837 Mon Sep 17 00:00:00 2001 From: Jason Kent Date: Fri, 3 Dec 2021 12:20:23 -0700 Subject: [PATCH 33/35] add animation tests, fix disabled hover style --- e2e/features/animation/animation-test.js | 142 +++++++++++++------- e2e/features/animation/gif-test.js | 4 +- e2e/features/compare/compare-mobile-test.js | 4 +- e2e/reuseables/querystrings.js | 6 +- e2e/reuseables/selectors.js | 15 +++ web/css/dateselector.css | 2 +- 6 files changed, 120 insertions(+), 53 deletions(-) diff --git a/e2e/features/animation/animation-test.js b/e2e/features/animation/animation-test.js index ef5896275d..3fea217360 100644 --- a/e2e/features/animation/animation-test.js +++ b/e2e/features/animation/animation-test.js @@ -1,56 +1,70 @@ const reuseables = require('../../reuseables/skip-tour.js'); -const localSelectors = require('../../reuseables/selectors.js'); const localQueryStrings = require('../../reuseables/querystrings.js'); +const { + dragger, + animationWidget, + animationButton, + playButton, + animateYearUp, + animateYearDown, + yearStartInput, + monthStartInput, + dayStartInput, + hourStartInput, + minuteStartInput, + yearEndInput, + monthEndInput, + dayEndInput, + hourEndInput, + minuteEndInput, + animationIntervalSelector, + animationFrameSlider, +} = require('../../reuseables/selectors.js'); const TIME_LIMIT = 10000; module.exports = { '@tags': ['localStorageDisabled'], - beforeEach(client) { - reuseables.loadAndSkipTour(client, TIME_LIMIT); + beforeEach(c) { + reuseables.loadAndSkipTour(c, TIME_LIMIT); }, - /** - * Clicking the animation widget button - * Opens the widget - */ - 'Toggling Animation Mode': (client) => { - client.waitForElementVisible(localSelectors.dragger, TIME_LIMIT, (el) => { - client.expect.element(localSelectors.animationWidget).to.not.be.present; - client.useCss().click(localSelectors.animationButton); - client.waitForElementVisible(localSelectors.animationWidget, TIME_LIMIT); + + 'Clicking the animation widget button opens the widget': (c) => { + c.waitForElementVisible(dragger, TIME_LIMIT, (el) => { + c.expect.element(animationWidget).to.not.be.present; + c.useCss().click(animationButton); + c.waitForElementVisible(animationWidget, TIME_LIMIT); }); }, - 'Opening custom interval widget': (client) => { - client.url(client.globals.url + localQueryStrings.activeAnimationWidget); - client.waitForElementVisible( - localSelectors.animationButton, + + 'Opening custom interval widget': (c) => { + c.url(c.globals.url + localQueryStrings.activeAnimationWidget); + c.waitForElementVisible( + animationButton, TIME_LIMIT, (el) => { - client + c .useCss() .moveToElement('.wv-animation-widget-header #timeline-interval-btn-container #current-interval', 1, 1) .waitForElementVisible('.wv-animation-widget-header .timeline-interval .interval-years', 2000) .click('.wv-animation-widget-header .timeline-interval #interval-custom-static'); - client.pause(1000); - client.useCss().assert.elementPresent('#wv-animation-widget .custom-interval-widget'); - client.useCss().assert.containsText('.wv-animation-widget-header #current-interval', '1 DAY'); - client.useCss().assert.containsText('#timeline #current-interval', '1 DAY'); + c.pause(1000); + c.useCss().assert.elementPresent('#wv-animation-widget .custom-interval-widget'); + c.useCss().assert.containsText('.wv-animation-widget-header #current-interval', '1 DAY'); + c.useCss().assert.containsText('#timeline #current-interval', '1 DAY'); }, ); }, - /** - * Moving the range selector updates the selected range - * in the animation widget date selector - */ - 'Changing date range of animation': (client) => { - client.url(client.globals.url + localQueryStrings.activeAnimationWidget); + + 'Changing date range of animation via timeline dragger': (c) => { + c.url(c.globals.url + localQueryStrings.activeAnimationWidget); // Test Permalink opens widget - client.waitForElementVisible( + c.waitForElementVisible( '#day-animation-widget-start', TIME_LIMIT, (el) => { - client.getValue('#day-animation-widget-start', (result) => { + c.getValue('#day-animation-widget-start', (result) => { const startDay = result.value; - client + c .useCss() .moveToElement( '#wv-timeline-range-selector > g:nth-child(2) > rect', @@ -61,41 +75,77 @@ module.exports = { .moveToElement('.timeline-dragger', 0, 0) .mouseButtonUp(0) .pause(2000); - client.getValue('#day-animation-widget-start', (result) => { + c.getValue('#day-animation-widget-start', (result) => { const newDay = result.value; - client.assert.notEqual(startDay, newDay); + c.assert.notEqual(startDay, newDay); }); }); }, ); }, - /** - * Changing animation time interval - */ - 'Changing animation time interval': (client) => { + 'Changing animation time interval': (c) => { // Can't use moveToElement twice with same elements // because of selenium catching. // Loading a different Url fixed the problem // https://github.com/SeleniumHQ/selenium/issues/4724#issuecomment-330862710 - reuseables.loadAndSkipTour(client, TIME_LIMIT); - client.useCss().click(localSelectors.animationButton); - client.waitForElementVisible( - localSelectors.animationButton, + reuseables.loadAndSkipTour(c, TIME_LIMIT); + c.useCss().click(animationButton); + c.waitForElementVisible( + animationButton, TIME_LIMIT, (el) => { - client + c .useCss() .moveToElement('.wv-animation-widget-header #timeline-interval-btn-container #current-interval', 1, 1) .waitForElementVisible('.wv-animation-widget-header .timeline-interval .interval-years', 2000) .click('.wv-animation-widget-header #timeline-interval #interval-years'); - client.pause(1000); - client.useCss().assert.containsText('.wv-animation-widget-header #current-interval', '1 YEAR'); - client.useCss().assert.containsText('#timeline #current-interval', '1 YEAR'); + c.pause(1000); + c.useCss().assert.containsText('.wv-animation-widget-header #current-interval', '1 YEAR'); + c.useCss().assert.containsText('#timeline #current-interval', '1 YEAR'); }, ); }, - after(client) { - client.end(); + + 'Disable when playing': (c) => { + c.url(c.globals.url + localQueryStrings.animationGeostationary); + c.waitForElementVisible(animationButton, TIME_LIMIT); + c.click(playButton); + c.pause(1000); + c.assert.attributeEquals(yearStartInput, 'disabled', 'true'); + c.assert.attributeEquals(yearEndInput, 'disabled', 'true'); + c.assert.attributeEquals(monthStartInput, 'disabled', 'true'); + c.assert.attributeEquals(monthEndInput, 'disabled', 'true'); + c.assert.attributeEquals(dayStartInput, 'disabled', 'true'); + c.assert.attributeEquals(dayEndInput, 'disabled', 'true'); + c.assert.attributeEquals(hourStartInput, 'disabled', 'true'); + c.assert.attributeEquals(hourEndInput, 'disabled', 'true'); + c.assert.attributeEquals(minuteStartInput, 'disabled', 'true'); + c.assert.attributeEquals(minuteEndInput, 'disabled', 'true'); + c.assert.cssClassPresent(animationFrameSlider, 'rc-slider-disabled'); + c.assert.cssClassPresent(animationIntervalSelector, 'disabled'); + c.click(playButton); + }, + + 'Disable playback when max frames exceeded': (c) => { + c.url(c.globals.url + localQueryStrings.animationGeostationary); + c.click(animateYearDown); + c.pause(500); + c.assert.cssClassPresent(playButton, 'disabled'); + + // Playback re-enabled when frames within the max + c.click(animateYearUp); + c.pause(500); + c.assert.not.cssClassPresent(playButton, 'disabled'); + + // App should not freeze when dates roll over + c.click(animateYearUp); + c.pause(500); + c.assert.value(yearStartInput, '1948'); + c.assert.cssClassPresent(playButton, 'disabled'); + }, + + after(c) { + c.end(); }, }; diff --git a/e2e/features/animation/gif-test.js b/e2e/features/animation/gif-test.js index 01cd7426b9..78fc899a71 100644 --- a/e2e/features/animation/gif-test.js +++ b/e2e/features/animation/gif-test.js @@ -112,7 +112,7 @@ module.exports = { 'GIF download is disabled when too many frames would be requested with standard interval': function( client, ) { - client.url(client.globals.url + localQueryStrings.animationTooManyFrames); + client.url(client.globals.url + localQueryStrings.animationTooManyFramesGif); client.waitForElementVisible( localSelectors.animationWidget, TIME_LIMIT, @@ -124,7 +124,7 @@ module.exports = { 'GIF download is disabled when too many frames would be requested with custom interval': function( client, ) { - client.url(client.globals.url + localQueryStrings.animationTooManyFramesCustomInterval); + client.url(client.globals.url + localQueryStrings.animationTooManyFramesGifCustomInterval); client.waitForElementVisible( localSelectors.animationWidget, TIME_LIMIT, diff --git a/e2e/features/compare/compare-mobile-test.js b/e2e/features/compare/compare-mobile-test.js index 47d6627d13..f523d7a3cb 100644 --- a/e2e/features/compare/compare-mobile-test.js +++ b/e2e/features/compare/compare-mobile-test.js @@ -34,7 +34,7 @@ module.exports = { 'Mobile comparison A|B toggle buttons are visible and only A is selected by default': (c) => { c.waitForElementVisible(compareMobileSelectToggle, TIME_LIMIT); c.assert.cssClassPresent(aMobileCompareButton, 'compare-btn-selected'); - c.assert.cssClassNotPresent(bMobileCompareButton, 'compare-btn-selected'); + c.assert.not.cssClassPresent(bMobileCompareButton, 'compare-btn-selected'); }, // toggle select B change compare mode date to B @@ -72,7 +72,7 @@ module.exports = { 'B compare button toggle is only selected on B permalink load': (c) => { c.url(c.globals.url + localQueryStrings.spyAndBIsActive); c.waitForElementVisible(swipeDragger, TIME_LIMIT); - c.assert.cssClassNotPresent(aMobileCompareButton, 'compare-btn-selected'); + c.assert.not.cssClassPresent(aMobileCompareButton, 'compare-btn-selected'); c.assert.cssClassPresent(bMobileCompareButton, 'compare-btn-selected'); }, diff --git a/e2e/reuseables/querystrings.js b/e2e/reuseables/querystrings.js index 94f3147f15..c66f79bdd7 100644 --- a/e2e/reuseables/querystrings.js +++ b/e2e/reuseables/querystrings.js @@ -3,8 +3,10 @@ module.exports = { activeAnimationWidget: '?p=geographic&l=VIIRS_SNPP_CorrectedReflectance_TrueColor(hidden),MODIS_Aqua_CorrectedReflectance_TrueColor(hidden),MODIS_Terra_CorrectedReflectance_TrueColor,Reference_Labels_15m(hidden),Reference_Features_15m(hidden),Coastlines_15m&t=2018-04-04&z=3&v=-177.94712426144758,-46.546875,-5.14662573855243,53.015625&ab=on&as=2018-03-28&ae=2018-04-04&av=3&al=false', activeCustomColormap: '?p=geographic&l=AIRS_L2_Carbon_Monoxide_500hPa_Volume_Mixing_Ratio_Day(palette=red_1)&t=2016-04-08&z=3&v=-223.875,-91.828125,162.84375,98.296875&ab=on&as=2016-03-25&ae=2016-04-08&av=3&al=false', animationProjectionRotated: '?p=arctic&l=MODIS_Terra_CorrectedReflectance_TrueColor,Coastlines_15m&t=2016-12-09&z=3&v=-2764195.2298414493,-88762.12734933128,2589496.903095221,3893331.478195751&r=-18.0000&ab=on&as=2016-12-02&ae=2016-12-09&av=3&al=true', - animationTooManyFrames: '?p=geographic&v=-52.43799794510461,-36.052394508901145,-33.34815419510461,-12.532863258901145&t=2019-06-24-T10%3A00%3A00Z&as=2018-06-24-T10%3A00%3A00Z&ae=2019-07-01-T10%3A00%3A00Z&ab=on', - animationTooManyFramesCustomInterval: '?p=geographic&v=-52.43799794510461,-36.052394508901145,-33.34815419510461,-12.532863258901145&t=2019-06-13-T08%3A00%3A00Z&ics=true&ici=2&icd=3&as=2009-06-23-T10%3A00%3A00Z&ae=2019-07-01-T10%3A00%3A00Z&ab=on', + animationTooManyFramesGif: '?p=geographic&v=-52.43799794510461,-36.052394508901145,-33.34815419510461,-12.532863258901145&t=2019-06-24-T10%3A00%3A00Z&as=2018-06-24-T10%3A00%3A00Z&ae=2019-07-01-T10%3A00%3A00Z&ab=on', + animationTooManyFramesGifCustomInterval: '?p=geographic&v=-52.43799794510461,-36.052394508901145,-33.34815419510461,-12.532863258901145&t=2019-06-13-T08%3A00%3A00Z&ics=true&ici=2&icd=3&as=2009-06-23-T10%3A00%3A00Z&ae=2019-07-01-T10%3A00%3A00Z&ab=on', + animationGeostationary: '?v=-127.54084611130202,-31.196051270164425,-36.29880238885806,62.96630766900102&z=4&ics=true&ici=5&icd=10&as=2021-12-03-T16%3A00%3A00Z&ae=2021-12-03-T17%3A10%3A00Z&l=GOES-East_ABI_GeoColor,Coastlines_15m&lg=true&al=true&ab=on&t=2021-12-01-T20%3A10%3A00Z', + // compare swipeAndAIsActive: diff --git a/e2e/reuseables/selectors.js b/e2e/reuseables/selectors.js index e319a50e43..d5d7663fbf 100644 --- a/e2e/reuseables/selectors.js +++ b/e2e/reuseables/selectors.js @@ -14,6 +14,21 @@ module.exports = { animationWidget: '#wv-animation-widget', animationButtonCase: '#timeline-header .animate-button', animationButton: '.animate-button', + playButton: '#play-button', + animateYearUp: '#wv-animation-widget .input-wrapper-year .date-arrow-up', + animateYearDown: '#wv-animation-widget .input-wrapper-year .date-arrow-down', + yearStartInput: '#year-animation-widget-start', + monthStartInput: '#month-animation-widget-start', + dayStartInput: '#day-animation-widget-start', + hourStartInput: '#hour-animation-widget-start', + minuteStartInput: '#minute-animation-widget-start', + yearEndInput: '#year-animation-widget-end', + monthEndInput: '#month-animation-widget-end', + dayEndInput: '#day-animation-widget-end', + hourEndInput: '#hour-animation-widget-end', + minuteEndInput: '#minute-animation-widget-end', + animationIntervalSelector: '#wv-animation-widget #current-interval', + animationFrameSlider: '#wv-animation-widget .rc-slider', // sidebar, layers sidebarContainer: '#productsHolder', diff --git a/web/css/dateselector.css b/web/css/dateselector.css index d91590274a..ad0eea1790 100644 --- a/web/css/dateselector.css +++ b/web/css/dateselector.css @@ -26,7 +26,7 @@ } } .disabled .wv-date-selector-widget { - .date-arrows &:hover { + .date-arrows:hover { background-color: transparent; cursor: default; } From 451d7250741fcc3d86a4a83aa325d29d2eed8b5d Mon Sep 17 00:00:00 2001 From: Jason Hurley Date: Fri, 10 Dec 2021 14:35:29 -0500 Subject: [PATCH 34/35] Updated README.md so the build status button is pointing to Github instead of Travis-CI. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1998e8ad86..4d4f1fcdd1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Worldview Screenshot](/web/images/readme-preview.jpg)](https://worldview.earthdata.nasa.gov) -[![Build Status](https://api.travis-ci.org/nasa-gibs/worldview.svg?branch=main)](https://travis-ci.org/nasa-gibs/worldview) +[![Build Status](https://github.com/nasa-gibs/worldview/actions/workflows/build-test-app.yml/badge.svg?branch=main)](https://github.com/nasa-gibs/worldview) Interactive interface for browsing full-resolution, global satellite imagery. From b25eb6f1d38e83c26c549e5c0732ad0323c807aa Mon Sep 17 00:00:00 2001 From: Jason Hurley Date: Fri, 10 Dec 2021 16:31:20 -0500 Subject: [PATCH 35/35] Updated Build Status clickthrough URL to build-test-app.yml --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d4f1fcdd1..ff13db39ab 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Worldview Screenshot](/web/images/readme-preview.jpg)](https://worldview.earthdata.nasa.gov) -[![Build Status](https://github.com/nasa-gibs/worldview/actions/workflows/build-test-app.yml/badge.svg?branch=main)](https://github.com/nasa-gibs/worldview) +[![Build Status](https://github.com/nasa-gibs/worldview/actions/workflows/build-test-app.yml/badge.svg?branch=main)](https://github.com/nasa-gibs/worldview/actions/workflows/build-test-app.yml) Interactive interface for browsing full-resolution, global satellite imagery.