From 537d5e6e01b3b4507592c59abaa6966fd3bac5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Sandvik?= Date: Sat, 21 Sep 2024 23:40:58 +0200 Subject: [PATCH 1/3] Air quality data --- i18n/en.pot | 16 ++++- src/components/Routes.js | 12 ++++ src/components/explore/Tabs.js | 1 + .../explore/airQuality/AirQuality.js | 50 +++++++++++++++ .../airQuality/charts/carbonMonoxide.js | 63 +++++++++++++++++++ .../airQuality/charts/nitrogenDioxide.js | 63 +++++++++++++++++++ .../airQuality/charts/particulateMatter.js | 63 +++++++++++++++++++ .../airQuality/charts/sulfurDioxide.js | 63 +++++++++++++++++++ src/data/datasets.js | 12 ++++ src/hooks/useExploreUri.js | 3 +- src/utils/ee-utils.js | 4 +- 11 files changed, 344 insertions(+), 6 deletions(-) create mode 100644 src/components/explore/airQuality/AirQuality.js create mode 100644 src/components/explore/airQuality/charts/carbonMonoxide.js create mode 100644 src/components/explore/airQuality/charts/nitrogenDioxide.js create mode 100644 src/components/explore/airQuality/charts/particulateMatter.js create mode 100644 src/components/explore/airQuality/charts/sulfurDioxide.js diff --git a/i18n/en.pot b/i18n/en.pot index ff081276..c0303774 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-09-21T10:56:27.165Z\n" -"PO-Revision-Date: 2024-09-21T10:56:27.165Z\n" +"POT-Creation-Date: 2024-09-21T21:20:37.273Z\n" +"PO-Revision-Date: 2024-09-21T21:20:37.273Z\n" msgid "About this app" msgstr "About this app" @@ -197,6 +197,15 @@ msgstr "Heat" msgid "Climate change" msgstr "Climate change" +msgid "Air quality" +msgstr "Air quality" + +msgid "{{name}}: Particle pollution {{period}}" +msgstr "{{name}}: Particle pollution {{period}}" + +msgid "Particulate matter d < 2.5 um (PM2.5)" +msgstr "Particulate matter d < 2.5 um (PM2.5)" + msgid "" "Temperature anomaly is the difference of a temperature from a reference " "value, calculated as the average temperature over a period of 30 years. " @@ -793,6 +802,9 @@ msgstr "Approximately 31 km (0.25°)" msgid "Approximately 9 km (0.1°)" msgstr "Approximately 9 km (0.1°)" +msgid "Approximately 40 km" +msgstr "Approximately 40 km" + msgid "Air temperature (ERA5-Land)" msgstr "Air temperature (ERA5-Land)" diff --git a/src/components/Routes.js b/src/components/Routes.js index dc80c7ce..4a8e7391 100644 --- a/src/components/Routes.js +++ b/src/components/Routes.js @@ -15,6 +15,7 @@ import HumidityDaily from "./explore/humidity/HumidityDaily"; import HeatMonthly from "./explore/heat/HeatMonthly"; import HeatDaily from "./explore/heat/HeatDaily"; import ClimateChange from "./explore/climateChange/ClimateChange"; +import AirQuality from "./explore/airQuality/AirQuality"; import ImportPage from "./import/ImportPage"; import SetupPage from "./setup/SetupPage"; import SettingsPage from "./settings/SettingsPage"; @@ -104,6 +105,17 @@ const tabRoutes = [ }, ], }, + { + path: "airquality", + element: , + children: [ + { + // path: ":month/:referencePeriodId", + index: true, + element: , + }, + ], + }, ]; const Routes = () => { diff --git a/src/components/explore/Tabs.js b/src/components/explore/Tabs.js index 05fa36f3..61ea197b 100644 --- a/src/components/explore/Tabs.js +++ b/src/components/explore/Tabs.js @@ -13,6 +13,7 @@ const tabs = [ { id: "humidity", label: i18n.t("Humidity") }, { id: "heat", label: i18n.t("Heat") }, { id: "climatechange", label: i18n.t("Climate change") }, + { id: "airquality", label: i18n.t("Air quality") }, ]; const Tabs = () => { diff --git a/src/components/explore/airQuality/AirQuality.js b/src/components/explore/airQuality/AirQuality.js new file mode 100644 index 00000000..19d7b377 --- /dev/null +++ b/src/components/explore/airQuality/AirQuality.js @@ -0,0 +1,50 @@ +import Chart from "../Chart"; +import DailyPeriodSelect from "../DailyPeriodSelect"; +import DataLoader from "../../shared/DataLoader"; +import Resolution from "../../shared/Resolution"; +import getParticulateMatterConfig from "./charts/particulateMatter"; +import getCarbonMonoxideConfig from "./charts/carbonMonoxide"; +import getNitrogetDioxideConfig from "./charts/nitrogenDioxide"; +import getSulfurDioxideConfig from "./charts/sulfurDioxide"; +import exploreStore from "../../../utils/exploreStore"; +import useEarthEngineTimeSeries from "../../../hooks/useEarthEngineTimeSeries"; +import { camsDaily } from "../../../data/datasets"; + +const filter = [ + { + type: "eq", + arguments: ["model_forecast_hour", 0], + }, + { + type: "eq", + arguments: ["model_initialization_hour", 0], + }, +]; + +const AirQuality = () => { + const orgUnit = exploreStore((state) => state.orgUnit); + const period = exploreStore((state) => state.dailyPeriod); + + const data = useEarthEngineTimeSeries(camsDaily, period, orgUnit, filter); + + const { name } = orgUnit.properties; + + return ( + <> + {data ? ( + <> + + + + + + ) : ( + + )} + + + + ); +}; + +export default AirQuality; diff --git a/src/components/explore/airQuality/charts/carbonMonoxide.js b/src/components/explore/airQuality/charts/carbonMonoxide.js new file mode 100644 index 00000000..897c90ef --- /dev/null +++ b/src/components/explore/airQuality/charts/carbonMonoxide.js @@ -0,0 +1,63 @@ +import i18n from "@dhis2/d2-i18n"; +import { colors } from "@dhis2/ui"; +import { animation, credits, getDailyPeriod } from "../../../../utils/chart"; + +const getChart = (name, data) => { + const series = data.map((d) => ({ + x: new Date(d.id).getTime(), + y: d["total_column_carbon_monoxide_surface"] * 1000000, + })); + + return { + title: { + text: i18n.t("{{name}}: Carbon monoxide {{period}}", { + name, + period: getDailyPeriod(data), + nsSeparator: ";", + }), + }, + credits, + tooltip: { + valueSuffix: " mg/m^2", + }, + chart: { + type: "column", + height: 480, + zoomType: "x", + marginBottom: 75, + }, + plotOptions: { + series: { + pointPadding: 0, + groupPadding: 0, + borderWidth: 0, + animation, + }, + }, + xAxis: { + type: "datetime", + tickInterval: 2592000000, + labels: { + format: "{value: %b}", + }, + }, + yAxis: { + min: 0, + // max: 0.1, + title: false, + labels: { + format: "{value} mg/m^2", + }, + }, + series: [ + { + data: series, + name: i18n.t("Total column carbon monoxide"), + color: colors.yellow800, + zIndex: 1, + }, + ], + }; +}; + +export default getChart; diff --git a/src/components/explore/airQuality/charts/nitrogenDioxide.js b/src/components/explore/airQuality/charts/nitrogenDioxide.js new file mode 100644 index 00000000..abf7ce31 --- /dev/null +++ b/src/components/explore/airQuality/charts/nitrogenDioxide.js @@ -0,0 +1,63 @@ +import i18n from "@dhis2/d2-i18n"; +import { colors } from "@dhis2/ui"; +import { animation, credits, getDailyPeriod } from "../../../../utils/chart"; + +const getChart = (name, data) => { + const series = data.map((d) => ({ + x: new Date(d.id).getTime(), + y: d["total_column_nitrogen_dioxide_surface"] * 1000000, + })); + + return { + title: { + text: i18n.t("{{name}}: Nitrogen dioxide {{period}}", { + name, + period: getDailyPeriod(data), + nsSeparator: ";", + }), + }, + credits, + tooltip: { + valueSuffix: " mg/m^2", + }, + chart: { + type: "column", + height: 480, + zoomType: "x", + marginBottom: 75, + }, + plotOptions: { + series: { + pointPadding: 0, + groupPadding: 0, + borderWidth: 0, + animation, + }, + }, + xAxis: { + type: "datetime", + tickInterval: 2592000000, + labels: { + format: "{value: %b}", + }, + }, + yAxis: { + min: 0, + // max: 0.1, + title: false, + labels: { + format: "{value} mg/m^2", + }, + }, + series: [ + { + data: series, + name: i18n.t("Total column nitrogen dioxide"), + color: colors.yellow800, + zIndex: 1, + }, + ], + }; +}; + +export default getChart; diff --git a/src/components/explore/airQuality/charts/particulateMatter.js b/src/components/explore/airQuality/charts/particulateMatter.js new file mode 100644 index 00000000..d0273922 --- /dev/null +++ b/src/components/explore/airQuality/charts/particulateMatter.js @@ -0,0 +1,63 @@ +import i18n from "@dhis2/d2-i18n"; +import { colors } from "@dhis2/ui"; +import { animation, credits, getDailyPeriod } from "../../../../utils/chart"; + +const getChart = (name, data) => { + const series = data.map((d) => ({ + x: new Date(d.id).getTime(), + y: d["particulate_matter_d_less_than_25_um_surface"] * 1000000, + })); + + return { + title: { + text: i18n.t("{{name}}: Particle pollution {{period}}", { + name, + period: getDailyPeriod(data), + nsSeparator: ";", + }), + }, + credits, + tooltip: { + valueSuffix: " mg/m^3", + }, + chart: { + type: "column", + height: 480, + zoomType: "x", + marginBottom: 75, + }, + plotOptions: { + series: { + pointPadding: 0, + groupPadding: 0, + borderWidth: 0, + animation, + }, + }, + xAxis: { + type: "datetime", + tickInterval: 2592000000, + labels: { + format: "{value: %b}", + }, + }, + yAxis: { + min: 0, + max: 0.1, + title: false, + labels: { + format: "{value} mg/m^3", + }, + }, + series: [ + { + data: series, + name: i18n.t("Particulate matter d < 2.5 um (PM2.5)"), + color: colors.yellow800, + zIndex: 1, + }, + ], + }; +}; + +export default getChart; diff --git a/src/components/explore/airQuality/charts/sulfurDioxide.js b/src/components/explore/airQuality/charts/sulfurDioxide.js new file mode 100644 index 00000000..53157ecc --- /dev/null +++ b/src/components/explore/airQuality/charts/sulfurDioxide.js @@ -0,0 +1,63 @@ +import i18n from "@dhis2/d2-i18n"; +import { colors } from "@dhis2/ui"; +import { animation, credits, getDailyPeriod } from "../../../../utils/chart"; + +const getChart = (name, data) => { + const series = data.map((d) => ({ + x: new Date(d.id).getTime(), + y: d["total_column_sulphur_dioxide_surface"] * 1000000, + })); + + return { + title: { + text: i18n.t("{{name}}: Sulphur dioxide {{period}}", { + name, + period: getDailyPeriod(data), + nsSeparator: ";", + }), + }, + credits, + tooltip: { + valueSuffix: " mg/m^2", + }, + chart: { + type: "column", + height: 480, + zoomType: "x", + marginBottom: 75, + }, + plotOptions: { + series: { + pointPadding: 0, + groupPadding: 0, + borderWidth: 0, + animation, + }, + }, + xAxis: { + type: "datetime", + tickInterval: 2592000000, + labels: { + format: "{value: %b}", + }, + }, + yAxis: { + min: 0, + // max: 0.1, + title: false, + labels: { + format: "{value} mg/m^2", + }, + }, + series: [ + { + data: series, + name: i18n.t("Total column sulphur dioxide"), + color: colors.yellow800, + zIndex: 1, + }, + ], + }; +}; + +export default getChart; diff --git a/src/data/datasets.js b/src/data/datasets.js index 6bf8fcfa..3bd77add 100644 --- a/src/data/datasets.js +++ b/src/data/datasets.js @@ -22,6 +22,7 @@ const relativeHumidityParser = ([dewData, tempData]) => export const era5Resolution = i18n.t("Approximately 31 km (0.25°)"); export const era5LandResolution = i18n.t("Approximately 9 km (0.1°)"); +export const camsResolution = i18n.t("Approximately 40 km"); export default [ { @@ -252,3 +253,14 @@ export const era5HeatMonthly = { ...era5HeatDaily, aggregationPeriod: MONTHLY, }; + +export const camsDaily = { + datasetId: "ECMWF/CAMS/NRT", + band: [ + "particulate_matter_d_less_than_25_um_surface", + "total_column_nitrogen_dioxide_surface", + "total_column_sulphur_dioxide_surface", + "total_column_carbon_monoxide_surface", + ], + resolution: camsResolution, +}; diff --git a/src/hooks/useExploreUri.js b/src/hooks/useExploreUri.js index 96ebc313..1bf0c889 100644 --- a/src/hooks/useExploreUri.js +++ b/src/hooks/useExploreUri.js @@ -42,7 +42,8 @@ const useExploreUri = () => { const baseUri = `/${section}/${orgUnit.id}/${tab}`; let uri; - if (tab === "forecast10days") { + // if (tab === "forecast10days") { + if (tab === "forecast10days" || tab === "airquality") { uri = baseUri; } else if (tab === "climatechange" && month && referencePeriod) { uri = `${baseUri}/${month}/${referencePeriod.id}`; diff --git a/src/utils/ee-utils.js b/src/utils/ee-utils.js index d15c4c97..a5eaa610 100644 --- a/src/utils/ee-utils.js +++ b/src/utils/ee-utils.js @@ -344,7 +344,5 @@ export const getCacheKey = (dataset, period, feature, filter) => { const bandkey = Array.isArray(band) ? band.join("-") : band; const filterKey = getKeyFromFilter(filter); - return `${id}-${datasetId}-${bandkey}-${startTime}-${endTime}${getKeyFromFilter( - filter - )}`; + return `${id}-${datasetId}-${bandkey}-${startTime}-${endTime}${filterKey}`; }; From 1403b92b6f88769f9950ad3bf6789cff6aaa6775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Sandvik?= Date: Tue, 24 Sep 2024 10:46:59 +0200 Subject: [PATCH 2/3] Translation strings --- i18n/en.pot | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index c0303774..326d59ad 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-09-21T21:20:37.273Z\n" -"PO-Revision-Date: 2024-09-21T21:20:37.273Z\n" +"POT-Creation-Date: 2024-09-24T08:18:30.056Z\n" +"PO-Revision-Date: 2024-09-24T08:18:30.056Z\n" msgid "About this app" msgstr "About this app" @@ -200,12 +200,30 @@ msgstr "Climate change" msgid "Air quality" msgstr "Air quality" +msgid "{{name}}: Carbon monoxide {{period}}" +msgstr "{{name}}: Carbon monoxide {{period}}" + +msgid "Total column carbon monoxide" +msgstr "Total column carbon monoxide" + +msgid "{{name}}: Nitrogen dioxide {{period}}" +msgstr "{{name}}: Nitrogen dioxide {{period}}" + +msgid "Total column nitrogen dioxide" +msgstr "Total column nitrogen dioxide" + msgid "{{name}}: Particle pollution {{period}}" msgstr "{{name}}: Particle pollution {{period}}" msgid "Particulate matter d < 2.5 um (PM2.5)" msgstr "Particulate matter d < 2.5 um (PM2.5)" +msgid "{{name}}: Sulphur dioxide {{period}}" +msgstr "{{name}}: Sulphur dioxide {{period}}" + +msgid "Total column sulphur dioxide" +msgstr "Total column sulphur dioxide" + msgid "" "Temperature anomaly is the difference of a temperature from a reference " "value, calculated as the average temperature over a period of 30 years. " From db6fc9172490a1d2f438bef51a707da227734582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Sandvik?= Date: Sun, 8 Dec 2024 13:10:30 +0100 Subject: [PATCH 3/3] Merge fix --- src/components/explore/airQuality/AirQuality.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/explore/airQuality/AirQuality.js b/src/components/explore/airQuality/AirQuality.js index 19d7b377..e8a3dd9d 100644 --- a/src/components/explore/airQuality/AirQuality.js +++ b/src/components/explore/airQuality/AirQuality.js @@ -6,7 +6,7 @@ import getParticulateMatterConfig from "./charts/particulateMatter"; import getCarbonMonoxideConfig from "./charts/carbonMonoxide"; import getNitrogetDioxideConfig from "./charts/nitrogenDioxide"; import getSulfurDioxideConfig from "./charts/sulfurDioxide"; -import exploreStore from "../../../utils/exploreStore"; +import exploreStore from "../../../store/exploreStore"; import useEarthEngineTimeSeries from "../../../hooks/useEarthEngineTimeSeries"; import { camsDaily } from "../../../data/datasets";