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}`;
};