diff --git a/i18n/en.pot b/i18n/en.pot index 5fcbbaa9..ad1b5ed9 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -228,6 +228,33 @@ msgstr "Heat" msgid "Climate change" 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. " @@ -866,6 +893,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 "Approximately 5 km (0.05°)" msgstr "Approximately 5 km (0.05°)" 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 2dff6621..8f583f8a 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..e8a3dd9d --- /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 "../../../store/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 d0ea91b3..12a572c9 100644 --- a/src/data/datasets.js +++ b/src/data/datasets.js @@ -29,6 +29,7 @@ const precipitationParser = (v) => 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 const chirpsResolution = i18n.t("Approximately 5 km (0.05°)"); export default [ @@ -273,3 +274,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 4871c2a5..90fb7954 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}`; };