diff --git a/web/modules/weather_blocks/weather_blocks.module b/web/modules/weather_blocks/weather_blocks.module index f9a5f64fc..0b45ef816 100644 --- a/web/modules/weather_blocks/weather_blocks.module +++ b/web/modules/weather_blocks/weather_blocks.module @@ -50,6 +50,27 @@ function weather_blocks_template_preprocess_default_variables_alter( } } catch (Throwable $e) { } + + try { + $wfo = strtolower($weatherMetadata["grid"]->wfo); + $satellite = $weatherData->getSatelliteMetadata($wfo); + + $goes = "GOES16"; + $satellite = $satellite->meta->satellite; + if ($satellite == "GOES-West") { + $goes = "GOES18"; + } + + if ($satellite) { + $weatherMetadata["satellite"] = [ + "gif" => + "https://cdn.star.nesdis.noaa.gov/WFO/$wfo/GEOCOLOR/$goes-" . + strtoupper($wfo) . + "-GEOCOLOR-600x600.gif", + ]; + } + } catch (Throwable $e) { + } } $variables["weather"] = $weatherMetadata; diff --git a/web/modules/weather_data/src/Service/DataLayer.php b/web/modules/weather_data/src/Service/DataLayer.php index 9340102b0..68d5d6c2b 100644 --- a/web/modules/weather_data/src/Service/DataLayer.php +++ b/web/modules/weather_data/src/Service/DataLayer.php @@ -333,6 +333,17 @@ public function getPlaceNearPolygon($points) return self::$i_placeNearPolygon[$wktPoints]; } + public function getSatelliteMetadata($wfo) + { + $wfo = strtolower($wfo); + $url = + "https://cdn.star.nesdis.noaa.gov/WFO/catalogs/WFO_02_" . + $wfo . + "_catalog.json"; + $response = $this->fetch($url)->wait(); + return $response; + } + public function databaseFetch($sql) { return $this->database->query($sql)->fetch(); diff --git a/web/modules/weather_data/src/Service/Test/DataLayer.php.test b/web/modules/weather_data/src/Service/Test/DataLayer.php.test index 54541d7f3..49df96406 100644 --- a/web/modules/weather_data/src/Service/Test/DataLayer.php.test +++ b/web/modules/weather_data/src/Service/Test/DataLayer.php.test @@ -257,4 +257,36 @@ final class DataLayerTest extends TestCase $this->assertEquals($expected, $actual); } + + /** + * @group unit + * @group data-layer + */ + public function testGetSatelliteMetadata(): void + { + $expected = (object) [ + "satellite" => "is got", + ]; + + $dataLayer = $this->getDataLayer(false); + $this->httpClient->append( + new Response( + 200, + ["Content-type", "application/json"], + json_encode($expected), + ), + ); + + $actual = $dataLayer->getSatelliteMetadata("BOB"); + + $this->assertEquals($expected, $actual); + $this->assertEquals( + "GET", + $this->httpHistory[0]["request"]->getMethod(), + ); + $this->assertEquals( + "/WFO/catalogs/WFO_02_bob_catalog.json", + $this->httpHistory[0]["request"]->getURI()->getPath(), + ); + } } diff --git a/web/modules/weather_data/src/Service/Test/Satellite.php.test b/web/modules/weather_data/src/Service/Test/Satellite.php.test new file mode 100644 index 000000000..3abdc7045 --- /dev/null +++ b/web/modules/weather_data/src/Service/Test/Satellite.php.test @@ -0,0 +1,537 @@ +assertEquals(true, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testKnowsNotMarineAlerts(): void + { + $actual = AlertUtility::isMarineAlert("severe thunderstorm WARNING"); + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testKnowsLandAlerts(): void + { + $actual = AlertUtility::isLandAlert("severe THUNDERSTORM warning"); + $this->assertEquals(true, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testKnowsNotLandAlerts(): void + { + $actual = AlertUtility::isLandAlert("storm watch"); + $this->assertEquals(false, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testSortsAlertsCorrectly(): void + { + $now = new \DateTimeImmutable(); + + $alerts = [ + (object) [ + "event" => "flood watch", + "onset" => $now->modify("-1 hour"), + ], + (object) [ + "event" => "air quality alert", + "onset" => $now->modify("+1 hour"), + ], + (object) [ + "event" => "volcano warning", + "onset" => $now->modify("-1 hour"), + ], + (object) [ + "event" => "tornado warning", + "onset" => $now->modify("-1 hour"), + ], + (object) [ + "event" => "dust storm warning", + "onset" => $now->modify("-1 hour"), + ], + (object) [ + "event" => "volcano warning", + "onset" => $now->modify("+2 hour"), + ], + (object) [ + "event" => "air quality alert", + "onset" => $now->modify("+2 hours"), + ], + (object) [ + "event" => "air quality alert", + "onset" => $now->modify("+3 hour"), + ], + ]; + + $expected = [ + $alerts[3], // active tornado warning + $alerts[2], // active volcano warning + $alerts[4], // active dust storm warning + $alerts[0], // active flood watch + $alerts[1], // next hour air quality alert + $alerts[5], // in two hours volcano warning + $alerts[6], // in two hours air quality alert + $alerts[7], // in three hours air quality alert + ]; + + $actual = AlertUtility::sort($alerts); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testGetsEndTime(): void + { + $expected = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $actual = AlertUtility::getEndTime( + (object) [ + "endsRaw" => "2000-01-01T05:00:00Z", + "expiresRaw" => "oh no", + "timezone" => "Etc/UTC", + ], + ); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testGetsExpireTimeIfNoEndTime(): void + { + $expected = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2073-01-01T05:00:00Z", + ); + + $actual = AlertUtility::getEndTime( + (object) [ + "endsRaw" => null, + "expiresRaw" => "2073-01-01T05:00:00Z", + "timezone" => "Etc/UTC", + ], + ); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testGetsExpireTimeIfNothing(): void + { + $expected = false; + + $actual = AlertUtility::getEndTime( + (object) [ + "endsRaw" => null, + "expiresRaw" => null, + "timezone" => "Etc/UTC", + ], + ); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForOngoingEndingToday(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("-1 hour"), + "endsRaw" => "2000-01-01T07:00:00Z", + "timezone" => "Etc/UTC", + ]; + + $expected = "until 7:00 AM today"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForOngoingEndingLater(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("-1 hour"), + "endsRaw" => "2000-01-02T07:00:00Z", + "timezone" => "Etc/UTC", + ]; + + $expected = "until Sunday 01/02 7:00 AM UTC"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForOngoingUnknownEnding(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("-1 hour"), + "endsRaw" => null, + "expiresRaw" => null, + "timezone" => "Etc/UTC", + ]; + + $expected = "is in effect"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForThisMorning(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+1 hour"), + "timezone" => "Etc/UTC", + ]; + + $expected = "this morning"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForThisAfternoon(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+12 hours"), + "timezone" => "Etc/UTC", + ]; + + $expected = "this afternoon"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForTonight(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+16 hour"), + "timezone" => "Etc/UTC", + ]; + + $expected = "tonight"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForTomorrowMorning(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+24 hour"), + "timezone" => "Etc/UTC", + ]; + + $expected = "tomorrow morning"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForTomorrowAfternoon(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+36 hour"), + "timezone" => "Etc/UTC", + ]; + + $expected = "tomorrow afternoon"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForTomorrowNight(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+40 hour"), + "timezone" => "Etc/UTC", + ]; + + $expected = "tomorrow night"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForFurtherFuture(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("+2 days"), + "timezone" => "Etc/UTC", + ]; + + $expected = "Monday"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testDurationTextForPast(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alert = (object) [ + "onset" => $now->modify("-2 hour"), + "endsRaw" => "2000-01-01T04:00:00Z", + "timezone" => "Etc/UTC", + ]; + + $expected = "has concluded"; + + $actual = AlertUtility::getDurationText($alert, $now); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testGetAlertLevel(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alerts = [ + (object) [ + "event" => "winter storm warning", + "onset" => $now->modify("-1 hour"), + ], + (object) [ + "event" => "flood Watch", + "onset" => $now->modify("+1 hour"), + ], + (object) [ + "event" => "air quality alert", + "onset" => $now->modify("+1 hour"), + ], + ]; + + $expected = ["warning", "watch", "other"]; + $actual = array_map(function ($alert) { + return AlertUtility::getAlertLevel($alert->event); + }, $alerts); + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testHighestAlertLevelWarning(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alerts = [ + (object) [ + "event" => "winter storm warning", + "onset" => $now->modify("-1 hour"), + "alertLevel" => "warning", + ], + (object) [ + "event" => "flood Watch", + "onset" => $now->modify("+1 hour"), + "alertLevel" => "watch", + ], + (object) [ + "event" => "air quality alert", + "onset" => $now->modify("+1 hour"), + "alertLevel" => "other", + ], + ]; + + $expected = "warning"; + + $actual = AlertUtility::getHighestAlertLevel($alerts); + + $this->assertEquals($expected, $actual); + } + + /** + * @group unit + * @group alert-utility + */ + public function testHighestAlertLevelWatch(): void + { + $now = \DateTimeImmutable::createFromFormat( + \DateTimeInterface::ISO8601_EXPANDED, + "2000-01-01T05:00:00Z", + ); + + $alerts = [ + (object) [ + "event" => "winter weather advisory", + "onset" => $now->modify("-1 hour"), + "alertLevel" => "other", + ], + (object) [ + "event" => "flood watch", + "onset" => $now->modify("+1 hour"), + "alertLevel" => "watch", + ], + (object) [ + "event" => "air quality alert", + "onset" => $now->modify("+1 hour"), + "alertLevel" => "other", + ], + ]; + + $expected = "watch"; + + $actual = AlertUtility::getHighestAlertLevel($alerts); + + $this->assertEquals($expected, $actual); + } +} diff --git a/web/modules/weather_data/src/Service/WeatherDataService.php b/web/modules/weather_data/src/Service/WeatherDataService.php index 3d549d509..783007f82 100644 --- a/web/modules/weather_data/src/Service/WeatherDataService.php +++ b/web/modules/weather_data/src/Service/WeatherDataService.php @@ -200,4 +200,9 @@ public function getPlaceNearPoint($lat, $lon) { return $this->dataLayer->getPlaceNearPoint($lat, $lon); } + + public function getSatelliteMetadata($wfo) + { + return $this->dataLayer->getSatelliteMetadata($wfo); + } } diff --git a/web/themes/new_weather_theme/templates/layout/page--point.html.twig b/web/themes/new_weather_theme/templates/layout/page--point.html.twig index a71795a9b..88f4866a7 100644 --- a/web/themes/new_weather_theme/templates/layout/page--point.html.twig +++ b/web/themes/new_weather_theme/templates/layout/page--point.html.twig @@ -58,6 +58,7 @@ {% include '@new_weather_theme/partials/radar.html.twig' with { 'point': weather.point } %} + {% if weather.satellite %}

{{ "Satellite" | t }}

@@ -65,10 +66,11 @@
- +
+ {% endif %}