diff --git a/components/aoi-card/component.jsx b/components/aoi-card/component.jsx
index 45ea86cf0a..1a5a9b66ea 100644
--- a/components/aoi-card/component.jsx
+++ b/components/aoi-card/component.jsx
@@ -52,6 +52,7 @@ const getLatestAlerts = ({ location, params }) =>
class AoICard extends PureComponent {
static propTypes = {
+ admin: PropTypes.object,
name: PropTypes.string,
tags: PropTypes.array,
application: PropTypes.string,
@@ -64,6 +65,7 @@ class AoICard extends PureComponent {
onFetchAlerts: PropTypes.func,
status: PropTypes.string,
setConfirmSubscriptionModalSettings: PropTypes.func,
+ openOutdatedAreaModal: PropTypes.func,
confirmed: PropTypes.bool,
id: PropTypes.string,
};
@@ -131,6 +133,7 @@ class AoICard extends PureComponent {
render() {
const {
+ admin,
tags,
name,
application,
@@ -142,9 +145,13 @@ class AoICard extends PureComponent {
location,
status,
setConfirmSubscriptionModalSettings,
+ openOutdatedAreaModal,
id,
confirmed,
} = this.props;
+
+ const isOutdatedGadmArea =
+ admin?.source?.provider === 'gadm' && admin?.source?.version === '3.6';
const {
loading,
alerts: { glads, fires, error: dataError },
@@ -237,6 +244,20 @@ class AoICard extends PureComponent {
)}
)}
+ {isOutdatedGadmArea && (
+
+
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ openOutdatedAreaModal();
+ }}
+ >
+ Outdated area, notifications paused
+
+
+ )}
{!simple && !isPending && (
diff --git a/components/forms/area-of-interest/actions.js b/components/forms/area-of-interest/actions.js
index 11303eb61c..62b7a7132a 100644
--- a/components/forms/area-of-interest/actions.js
+++ b/components/forms/area-of-interest/actions.js
@@ -67,7 +67,7 @@ export const saveAreaOfInterest = createThunkAction(
admin: {
source: {
provider: 'gadm',
- version: '3.6',
+ version: '4.1',
},
adm0,
adm1,
diff --git a/components/map-menu/actions.js b/components/map-menu/actions.js
index 9bf81a89c0..c26a5d8b10 100644
--- a/components/map-menu/actions.js
+++ b/components/map-menu/actions.js
@@ -1,7 +1,7 @@
import { createAction, createThunkAction } from 'redux/actions';
import { fetchGeocodeLocations } from 'services/geocoding';
-import { setMapSettings, setMapInteractions } from 'components/map/actions';
+import { setMapSettings } from 'components/map/actions';
import { setAnalysisSettings } from 'components/analysis/actions';
export const setLocationsData = createAction('setLocationsData');
@@ -10,58 +10,64 @@ export const setMenuSettings = createAction('setMenuSettings');
export const getLocationFromSearch = createThunkAction(
'getLocationFromSearch',
- ({ search, token, lang }) => (dispatch) => {
- dispatch(setMenuLoading(true));
- if (search) {
- fetchGeocodeLocations(search, lang, token)
- .then((locations) => {
- if (locations?.length) {
- dispatch(setLocationsData(locations));
- } else {
- dispatch(setLocationsData([]));
- }
- dispatch(setMenuLoading(false));
- })
- .catch(() => {
- dispatch(setMenuLoading(false));
- });
+ ({ search, token, lang }) =>
+ (dispatch) => {
+ dispatch(setMenuLoading(true));
+ if (search) {
+ fetchGeocodeLocations(search, lang, token)
+ .then((locations) => {
+ if (locations?.length) {
+ dispatch(setLocationsData(locations));
+ } else {
+ dispatch(setLocationsData([]));
+ }
+ dispatch(setMenuLoading(false));
+ })
+ .catch(() => {
+ dispatch(setMenuLoading(false));
+ });
+ }
}
- }
);
export const handleClickLocation = createThunkAction(
'handleClickLocation',
- ({ center, bbox: featureBbox, ...feature }) => (dispatch) => {
- if (featureBbox) {
- dispatch(setMapSettings({ canBound: true, bbox: featureBbox }));
- } else {
- dispatch(
- setMapSettings({ center: { lat: center[1], lng: center[0] }, zoom: 12 })
- );
+ ({ center, bbox: featureBbox }) =>
+ (dispatch) => {
+ if (featureBbox) {
+ dispatch(setMapSettings({ canBound: true, bbox: featureBbox }));
+ } else {
+ dispatch(
+ setMapSettings({
+ center: { lat: center[1], lng: center[0] },
+ zoom: 12,
+ })
+ );
+ }
+
+ dispatch(setMenuSettings({ menuSection: '' }));
}
- dispatch(setMapInteractions({ features: [feature], lngLat: center }));
- dispatch(setMenuSettings({ menuSection: '' }));
- }
);
export const handleViewOnMap = createThunkAction(
'handleViewOnMap',
- ({ analysis, mapMenu, map }) => (dispatch) => {
- if (map) {
- dispatch(setMapSettings({ ...map, canBound: true }));
- }
+ ({ analysis, mapMenu, map }) =>
+ (dispatch) => {
+ if (map) {
+ dispatch(setMapSettings({ ...map, canBound: true }));
+ }
- dispatch(
- setMenuSettings({
- ...mapMenu,
- menuSection: '',
- })
- );
+ dispatch(
+ setMenuSettings({
+ ...mapMenu,
+ menuSection: '',
+ })
+ );
- if (analysis) {
- dispatch(setAnalysisSettings(analysis));
+ if (analysis) {
+ dispatch(setAnalysisSettings(analysis));
+ }
}
- }
);
export const showAnalysis = createThunkAction(
diff --git a/components/map-menu/components/sections/my-gfw/component.jsx b/components/map-menu/components/sections/my-gfw/component.jsx
index 9c4a4ddf33..8950434c02 100644
--- a/components/map-menu/components/sections/my-gfw/component.jsx
+++ b/components/map-menu/components/sections/my-gfw/component.jsx
@@ -17,6 +17,7 @@ import Pill from 'components/ui/pill';
import Loader from 'components/ui/loader';
import Paginate from 'components/paginate';
import ConfirmSubscriptionModal from 'components/modals/confirm-subscription';
+import OutdatedAreaModal from 'components/modals/outdated-area/OutdatedAreaModal';
import editIcon from 'assets/icons/edit.svg?sprite';
import shareIcon from 'assets/icons/share.svg?sprite';
@@ -51,6 +52,7 @@ class MapMenuMyGFW extends PureComponent {
unselectedTags: [],
pageSize: 6,
pageNum: 0,
+ outdatedAreaModalOpen: false,
};
static getDerivedStateFromProps(prevProps, prevState) {
@@ -294,7 +296,15 @@ class MapMenuMyGFW extends PureComponent {
tabIndex={0}
key={area.id}
>
-
+
+ this.setState({
+ outdatedAreaModalOpen: true,
+ })}
+ />
{active && this.renderAoiActions()}
);
@@ -370,6 +380,13 @@ class MapMenuMyGFW extends PureComponent {
/>
)}
+
+ this.setState({
+ outdatedAreaModalOpen: false,
+ })}
+ />
);
}
diff --git a/components/map-menu/components/sections/search/selectors.js b/components/map-menu/components/sections/search/selectors.js
index ad2372c322..0824913882 100644
--- a/components/map-menu/components/sections/search/selectors.js
+++ b/components/map-menu/components/sections/search/selectors.js
@@ -1,6 +1,6 @@
import { createSelector, createStructuredSelector } from 'reselect';
import { deburrUpper } from 'utils/strings';
-import { getGadm36Id } from 'utils/gadm';
+import { getGadmId } from 'utils/gadm';
import sortBy from 'lodash/sortBy';
import { translateText, selectActiveLang } from 'utils/lang';
@@ -49,7 +49,7 @@ const getLocations = createSelector(
(locations, location) => {
if (!locations) return null;
const { adm0, adm1, adm2 } = location;
- const gadmId = getGadm36Id(adm0, adm1, adm2);
+ const gadmId = getGadmId(adm0, adm1, adm2);
return locations
.map((l) => ({
diff --git a/components/map/components/popup/components/boundary-sentence/selectors.js b/components/map/components/popup/components/boundary-sentence/selectors.js
index c4305db53a..2588091f91 100644
--- a/components/map/components/popup/components/boundary-sentence/selectors.js
+++ b/components/map/components/popup/components/boundary-sentence/selectors.js
@@ -6,7 +6,7 @@ const getInteractionData = (state, { data }) => data;
/**
* Returns an object with the selected location name, its area and a sentence do be displayed.
- * @param {method} createSelector - return a memoized outut selector.
+ * @param {method} createSelector - return a memoized output selector.
* @see https://reselect.js.org/introduction/getting-started/#output-selector for implementation details
* @param {selector} getInteractionData - data from the area clicked by user
* @return {object} sentence, location name and area.
@@ -14,8 +14,8 @@ const getInteractionData = (state, { data }) => data;
export const getSentence = createSelector(
[getInteractionData],
({ data } = {}) => {
- const { adm_level, gid_0, name_1, country } = data;
- let name = adm_level > 0 ? data[`name_${adm_level}`] : country;
+ const { adm_level, gid_0, name_1, name_0, country } = data;
+ let name = adm_level > 0 ? data[`name_${adm_level}`] : country || name_0;
if (!gid_0) {
name = data[Object.keys(data).find((k) => k.includes('name'))];
@@ -30,12 +30,15 @@ export const getSentence = createSelector(
locationNames = [
locationNameTranslated,
translateText(name_1),
- translateText(country),
+ translateText(country || name_0),
];
}
- if (Number(adm_level) === '1') {
- locationNames = [locationNameTranslated, translateText(country)];
+ if (Number(adm_level) === 1) {
+ locationNames = [
+ locationNameTranslated,
+ translateText(country || name_0),
+ ];
}
const locationName = locationNames.join(', ');
diff --git a/components/map/selectors.js b/components/map/selectors.js
index 95e3d336be..0642032118 100644
--- a/components/map/selectors.js
+++ b/components/map/selectors.js
@@ -48,7 +48,9 @@ export const getMapViewport = createSelector([getMapSettings], (settings) => {
pitch,
latitude: center?.lat,
longitude: center?.lng,
- transitionDuration: 500,
+ // The map transition needs to always be 0 otherwise the map becomes sluggish when panned or zoomed. Only set a
+ // different value when flying between locations and only temporarily.
+ transitionDuration: 0,
};
});
diff --git a/components/modals/outdated-area/OutdatedAreaModal.js b/components/modals/outdated-area/OutdatedAreaModal.js
new file mode 100644
index 0000000000..4c0aed9020
--- /dev/null
+++ b/components/modals/outdated-area/OutdatedAreaModal.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Modal from 'components/modal';
+
+const OutdatedAreaModal = ({ isOpen, handleCloseModal }) => {
+ return (
+
+
+ This area uses an outdated version of political boundaries. As a result,
+ alert notification emails can no longer be sent for this area. To
+ continue receiving alerts, please navigate to this area on the map and
+ re-save the area. Instructions on how to save an area and subscribe to
+ alerts are available via in{' '}
+
+ this Help Center article
+
+ . Please read through our{' '}
+
+ blog outlining these changes
+ {' '}
+ and let us know if you have any questions at{' '}
+ gfw@wri.org .
+
+
+ );
+};
+
+OutdatedAreaModal.propTypes = {
+ isOpen: PropTypes.bool,
+ handleCloseModal: PropTypes.func,
+};
+
+export default OutdatedAreaModal;
diff --git a/components/ui/map/component.jsx b/components/ui/map/component.jsx
index 5012fae3cf..df2c8e8747 100644
--- a/components/ui/map/component.jsx
+++ b/components/ui/map/component.jsx
@@ -271,6 +271,9 @@ class Map extends Component {
onResize={this.onResize}
onLoad={this.onLoad}
getCursor={getCursor}
+ // If the `transitionDuration` is not 0, then the map becomes sluggish when panned or zoomed. Nevertheless,
+ // we still want a transition when flying between locations.
+ transitionDuration={flying ? viewport.transitionDuration || 0 : 0}
transitionInterpolator={new FlyToInterpolator()}
transitionEasing={easeCubic}
preventStyleDiffing
diff --git a/data/analysis-datasets-versions.json b/data/analysis-datasets-versions.json
index 140ec84655..d781d9ccb4 100644
--- a/data/analysis-datasets-versions.json
+++ b/data/analysis-datasets-versions.json
@@ -1,13 +1,13 @@
{
- "ANNUAL_ADM0_CHANGE": "v20240815",
- "ANNUAL_ADM0_SUMMARY": "v20240815",
- "ANNUAL_ADM0_WHITELIST": "v20240815",
- "ANNUAL_ADM1_CHANGE": "v20240815",
- "ANNUAL_ADM1_SUMMARY": "v20240815",
- "ANNUAL_ADM1_WHITELIST": "v20240815",
- "ANNUAL_ADM2_CHANGE": "v20240815",
- "ANNUAL_ADM2_SUMMARY": "v20240815",
- "ANNUAL_ADM2_WHITELIST": "v20240815",
+ "ANNUAL_ADM0_CHANGE": "v20250122",
+ "ANNUAL_ADM0_SUMMARY": "v20250122",
+ "ANNUAL_ADM0_WHITELIST": "v20250122",
+ "ANNUAL_ADM1_CHANGE": "v20250122",
+ "ANNUAL_ADM1_SUMMARY": "v20250122",
+ "ANNUAL_ADM1_WHITELIST": "v20250122",
+ "ANNUAL_ADM2_CHANGE": "v20250122",
+ "ANNUAL_ADM2_SUMMARY": "v20250122",
+ "ANNUAL_ADM2_WHITELIST": "v20250122",
"ANNUAL_GEOSTORE_CHANGE": "v20241209",
"ANNUAL_GEOSTORE_SUMMARY": "v20241209",
@@ -16,13 +16,13 @@
"ANNUAL_WDPA_SUMMARY": "v20240813",
"ANNUAL_WDPA_WHITELIST": "v20240813",
- "VIIRS_ADM0_WHITELIST": "v20240815",
- "VIIRS_ADM1_WHITELIST": "v20240815",
- "VIIRS_ADM2_WHITELIST": "v20240815",
- "VIIRS_ADM2_DAILY": "v20240815",
- "VIIRS_ADM0_WEEKLY": "v20240815",
- "VIIRS_ADM1_WEEKLY": "v20240815",
- "VIIRS_ADM2_WEEKLY": "v20240815",
+ "VIIRS_ADM0_WHITELIST": "v20250206",
+ "VIIRS_ADM1_WHITELIST": "v20250206",
+ "VIIRS_ADM2_WHITELIST": "v20250206",
+ "VIIRS_ADM2_DAILY": "v20250206",
+ "VIIRS_ADM0_WEEKLY": "v20250206",
+ "VIIRS_ADM1_WEEKLY": "v20250206",
+ "VIIRS_ADM2_WEEKLY": "v20250206",
"VIIRS_GEOSTORE_DAILY": "v20241209",
"VIIRS_GEOSTORE_WEEKLY": "v20241209",
@@ -31,13 +31,13 @@
"VIIRS_WDPA_WEEKLY": "v20240122",
"VIIRS_WDPA_WHITELIST": "v20240122",
- "MODIS_ADM0_WHITELIST": "v20240815",
- "MODIS_ADM1_WHITELIST": "v20240815",
- "MODIS_ADM2_WHITELIST": "v20240815",
- "MODIS_ADM2_DAILY": "v20240815",
- "MODIS_ADM0_WEEKLY": "v20240815",
- "MODIS_ADM1_WEEKLY": "v20240815",
- "MODIS_ADM2_WEEKLY": "v20240815",
+ "MODIS_ADM0_WHITELIST": "v20250206",
+ "MODIS_ADM1_WHITELIST": "v20250206",
+ "MODIS_ADM2_WHITELIST": "v20250206",
+ "MODIS_ADM2_DAILY": "v20250206",
+ "MODIS_ADM0_WEEKLY": "v20250206",
+ "MODIS_ADM1_WEEKLY": "v20250206",
+ "MODIS_ADM2_WEEKLY": "v20250206",
"MODIS_GEOSTORE_WHITELIST": "v20240122",
"MODIS_GEOSTORE_DAILY": "v20240122",
diff --git a/layouts/dashboards/components/header/selectors.js b/layouts/dashboards/components/header/selectors.js
index 8d16e6d8e4..5e1d59c75a 100644
--- a/layouts/dashboards/components/header/selectors.js
+++ b/layouts/dashboards/components/header/selectors.js
@@ -84,7 +84,14 @@ export const getFirstUserArea = createSelector([getUserAreas], (areas) =>
export const getAdm0Data = createSelector(
[getAdminMetadata],
- (data) => data && data.adm0
+ (data) =>
+ data &&
+ data.adm0.sort((a, b) =>
+ a.label.localeCompare(b.label, 'en', {
+ sensitivity: 'base',
+ ignorePunctuation: true,
+ })
+ )
);
export const getAdm1Data = createSelector(
diff --git a/layouts/my-gfw/components/areas-table/component.jsx b/layouts/my-gfw/components/areas-table/component.jsx
index 37d603e09d..46e6034f1f 100644
--- a/layouts/my-gfw/components/areas-table/component.jsx
+++ b/layouts/my-gfw/components/areas-table/component.jsx
@@ -16,6 +16,7 @@ import Dropdown from 'components/ui/dropdown';
import Search from 'components/ui/search';
import Paginate from 'components/paginate';
import ConfirmSubscriptionModal from 'components/modals/confirm-subscription';
+import OutdatedAreaModal from 'components/modals/outdated-area/OutdatedAreaModal';
import mapIcon from 'assets/icons/view-map.svg?sprite';
import editIcon from 'assets/icons/edit.svg?sprite';
@@ -42,6 +43,7 @@ class AreasTable extends PureComponent {
alerts: {},
pageSize: 6,
pageNum: 0,
+ outdatedAreaModalOpen: false,
};
componentDidUpdate(prevProps) {
@@ -252,6 +254,10 @@ class AreasTable extends PureComponent {
this.setState({
alerts: { ...allAlerts, [area.id]: alertsResponse },
})}
+ openOutdatedAreaModal={() =>
+ this.setState({
+ outdatedAreaModalOpen: true,
+ })}
/>
@@ -320,6 +326,13 @@ class AreasTable extends PureComponent {
/>
)}
+
+ this.setState({
+ outdatedAreaModalOpen: false,
+ })}
+ />
);
}
diff --git a/pages/about.js b/pages/about.js
index c78a162592..7c007d8806 100644
--- a/pages/about.js
+++ b/pages/about.js
@@ -28,7 +28,7 @@ export const getStaticProps = async () => {
props: {
impactProjects,
sgfProjects,
- countries: countries?.data?.rows,
+ countries: countries?.data,
notifications: notifications || [],
},
revalidate: 10,
diff --git a/pages/dashboards/[[...location]].js b/pages/dashboards/[[...location]].js
index fb817a9911..f81b632365 100644
--- a/pages/dashboards/[[...location]].js
+++ b/pages/dashboards/[[...location]].js
@@ -7,13 +7,12 @@ import uniqBy from 'lodash/uniqBy';
import useRouter from 'utils/router';
import { decodeQueryParams } from 'utils/url';
-import { parseGadm36Id } from 'utils/gadm';
+import { parseGadmId } from 'utils/gadm';
import { parseStringWithVars } from 'utils/strings';
import { getLocationData } from 'services/location';
import { getPublishedNotifications } from 'services/notifications';
import {
- // getCountriesProvider,
getRegionsProvider,
getSubRegionsProvider,
getCategorisedCountries,
@@ -183,9 +182,9 @@ export const getServerSideProps = async ({ params, query, req }) => {
const countryLinks = await getCountryLinksSerialized();
countryData = {
...countryData,
- regions: uniqBy(regions.data.rows).map((row) => ({
- id: parseGadm36Id(row.id).adm1,
- value: parseGadm36Id(row.id).adm1,
+ regions: uniqBy(regions.data).map((row) => ({
+ id: parseGadmId(row.id).adm1,
+ value: parseGadmId(row.id).adm1,
label: row.name,
name: row.name,
})),
@@ -194,12 +193,12 @@ export const getServerSideProps = async ({ params, query, req }) => {
}
if (adm1) {
- const subRegions = await getSubRegionsProvider(adm0, adm1);
+ const subRegions = await getSubRegionsProvider({ adm0, adm1 });
countryData = {
...countryData,
- subRegions: uniqBy(subRegions.data.rows).map((row) => ({
- id: parseGadm36Id(row.id).adm2,
- value: parseGadm36Id(row.id).adm2,
+ subRegions: uniqBy(subRegions.data).map((row) => ({
+ id: parseGadmId(row.id).adm2,
+ value: parseGadmId(row.id).adm2,
label: row.name,
name: row.name,
})),
diff --git a/pages/embed/sentence/[[...location]].js b/pages/embed/sentence/[[...location]].js
index 611b2d5f77..02b1c83d92 100644
--- a/pages/embed/sentence/[[...location]].js
+++ b/pages/embed/sentence/[[...location]].js
@@ -2,11 +2,10 @@ import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import uniqBy from 'lodash/uniqBy';
-import { parseGadm36Id } from 'utils/gadm';
+import { parseGadmId } from 'utils/gadm';
import { getLocationData } from 'services/location';
import {
- // getCountriesProvider,
getRegionsProvider,
getSubRegionsProvider,
getCategorisedCountries,
@@ -91,9 +90,9 @@ export const getServerSideProps = async ({ params }) => {
const countryLinks = await getCountryLinksSerialized();
countryData = {
...countryData,
- regions: uniqBy(regions.data.rows).map((row) => ({
- id: parseGadm36Id(row.id).adm1,
- value: parseGadm36Id(row.id).adm1,
+ regions: uniqBy(regions.data).map((row) => ({
+ id: parseGadmId(row.id).adm1,
+ value: parseGadmId(row.id).adm1,
label: row.name,
name: row.name,
})),
@@ -102,12 +101,12 @@ export const getServerSideProps = async ({ params }) => {
}
if (adm1) {
- const subRegions = await getSubRegionsProvider(adm0, adm1);
+ const subRegions = await getSubRegionsProvider({ adm0, adm1 });
countryData = {
...countryData,
- subRegions: uniqBy(subRegions.data.rows).map((row) => ({
- id: parseGadm36Id(row.id).adm2,
- value: parseGadm36Id(row.id).adm2,
+ subRegions: uniqBy(subRegions.data).map((row) => ({
+ id: parseGadmId(row.id).adm2,
+ value: parseGadmId(row.id).adm2,
label: row.name,
name: row.name,
})),
@@ -151,21 +150,6 @@ export const getServerSideProps = async ({ params }) => {
};
}
};
-//
-// export const getStaticPaths = async () => {
-// const countryData = await getCountriesProvider();
-// const { rows: countries } = countryData?.data || {};
-// const countryPaths = countries.map((c) => ({
-// params: {
-// location: ['country', c.iso],
-// },
-// }));
-//
-// return {
-// paths: ['/embed/sentence/', ...countryPaths] || [],
-// fallback: true,
-// };
-// };
const getSentenceClientSide = async (
locationNames = null,
diff --git a/pages/grants-and-fellowships/[section].js b/pages/grants-and-fellowships/[section].js
index 1acd668243..0d34889f42 100644
--- a/pages/grants-and-fellowships/[section].js
+++ b/pages/grants-and-fellowships/[section].js
@@ -58,7 +58,7 @@ export const getServerSideProps = async ({ query }) => {
title: 'Projects | Grants & Fellowships | Global Forest Watch',
section: query?.section,
projects: parsedProjects || [],
- allCountries: allCountries?.data?.rows || [],
+ allCountries: allCountries?.data || [],
projectCountries: uniqCountries || [],
country: query?.country || '',
projectsTexts: pageTexts?.[0]?.acf,
diff --git a/providers/country-data-provider/actions.js b/providers/country-data-provider/actions.js
index 9ec563459e..1beac13ef5 100644
--- a/providers/country-data-provider/actions.js
+++ b/providers/country-data-provider/actions.js
@@ -1,5 +1,5 @@
import { createAction, createThunkAction } from 'redux/actions';
-import { parseGadm36Id } from 'utils/gadm';
+import { parseGadmId } from 'utils/gadm';
import uniqBy from 'lodash/uniqBy';
import {
@@ -47,10 +47,10 @@ export const getRegions = createThunkAction(
getRegionsProvider(country)
.then((response) => {
const parsedResponse = [];
- uniqBy(response.data.rows).forEach((row) => {
+ uniqBy(response.data).forEach((region) => {
parsedResponse.push({
- id: parseGadm36Id(row.id).adm1,
- name: row.name,
+ id: parseGadmId(region.id).adm1,
+ name: region.name,
});
});
dispatch(setRegions(parsedResponse, 'id'));
@@ -67,14 +67,13 @@ export const getSubRegions = createThunkAction(
({ adm0, adm1, token }) =>
(dispatch) => {
dispatch(setSubRegionsLoading(true));
- getSubRegionsProvider(adm0, adm1, token)
+ getSubRegionsProvider({ adm0, adm1, token })
.then((subRegions) => {
- const { rows } = subRegions.data;
const parsedResponse = [];
- uniqBy(rows).forEach((row) => {
+ uniqBy(subRegions?.data).forEach((subRegion) => {
parsedResponse.push({
- id: parseGadm36Id(row.id).adm2,
- name: row.name,
+ id: parseGadmId(subRegion.id).adm2,
+ name: subRegion.name,
});
});
dispatch(setSubRegions(uniqBy(parsedResponse, 'id')));
diff --git a/providers/geostore-provider/actions.js b/providers/geostore-provider/actions.js
index 2dd6c7ab30..97582bd8e4 100644
--- a/providers/geostore-provider/actions.js
+++ b/providers/geostore-provider/actions.js
@@ -19,7 +19,6 @@ export const fetchGeostore = createThunkAction(
'fetchGeostore',
(params) => (dispatch) => {
const { type, adm0, adm1, adm2, token } = params;
-
if (type && adm0) {
dispatch(setGeostoreLoading({ loading: true, error: false }));
getGeostore({ type, adm0, adm1, adm2, token })
diff --git a/services/__tests__/areas.spec.js b/services/__tests__/areas.spec.js
index 4a878f2e9c..304e12646a 100644
--- a/services/__tests__/areas.spec.js
+++ b/services/__tests__/areas.spec.js
@@ -42,7 +42,7 @@ describe('Areas Service', () => {
// assert
expect(apiAuthRequest.get).toHaveBeenCalledWith(
- '/v2/area?source[provider]=gadm&source[version]=3.6'
+ '/v2/area?source[provider]=gadm&source[version]=4.1'
);
});
@@ -60,14 +60,14 @@ describe('Areas Service', () => {
adm0: 'BRA',
source: {
provider: 'gadm',
- version: '3.6',
+ version: '4.1',
},
},
iso: {
country: 'BRA',
source: {
provider: 'gadm',
- version: '3.6',
+ version: '4.1',
},
},
},
@@ -115,16 +115,14 @@ describe('Areas Service', () => {
adm0: 'BRA',
source: {
provider: 'gadm',
- // version: '4.1',
- version: '3.6', // remove it before release gadm 4.1
+ version: '4.1',
},
},
iso: {
country: 'BRA',
source: {
provider: 'gadm',
- // version: '4.1',
- version: '3.6', // remove it before release gadm 4.1
+ version: '4.1',
},
},
use: {},
diff --git a/services/__tests__/country.spec.js b/services/__tests__/country.spec.js
new file mode 100644
index 0000000000..faaa929bf8
--- /dev/null
+++ b/services/__tests__/country.spec.js
@@ -0,0 +1,165 @@
+import { jest } from '@jest/globals';
+
+import { dataRequest } from 'utils/request';
+
+import {
+ getCountriesProvider,
+ getRegionsProvider,
+ getSubRegionsProvider,
+ getFAOCountriesProvider,
+ getCountryLinksProvider,
+ getCategorisedCountries,
+ getCountryLinksSerialized,
+ GADM_DATASET,
+} from 'services/country';
+import countryLinks from 'services/country-links.json';
+
+jest.mock('utils/request', () => ({
+ dataRequest: {
+ get: jest.fn(),
+ },
+}));
+
+jest.mock('services/country-links.json', () => ({
+ rows: [
+ {
+ iso: 'CMR',
+ external_links:
+ '[{ "title": "Interactive Forest Atlas of Cameroon", "url": "http://cmr.forest-atlas.org/"}]',
+ },
+ {
+ iso: 'CAF',
+ external_links:
+ '[{ "title": "Interactive Forest Atlas of Central African Republic", "url": "http://caf.forest-atlas.org/"}]',
+ },
+ ],
+}));
+
+describe('country service', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('getCountriesProvider', () => {
+ it('should fetch countries data', async () => {
+ const mockResponse = { data: [{ iso: 'BRA', name: 'Brazil' }] };
+ dataRequest.get.mockResolvedValue(mockResponse);
+
+ const result = await getCountriesProvider();
+
+ expect(dataRequest.get).toHaveBeenCalledWith(
+ `${GADM_DATASET}?sql=SELECT country AS name, gid_0 AS iso FROM gadm_administrative_boundaries WHERE adm_level = '0' AND gid_0 NOT IN ('Z01', 'Z02', 'Z03', 'Z04', 'Z05', 'Z06', 'Z07', 'Z08', 'Z09', 'TWN', 'XCA') ORDER BY country`
+ );
+ expect(result).toEqual(mockResponse);
+ });
+ });
+
+ describe('getRegionsProvider', () => {
+ it('should fetch regions data based on adm0', async () => {
+ const mockResponse = { data: [{ id: 'BRA.25', name: 'São Paulo' }] };
+ dataRequest.get.mockResolvedValue(mockResponse);
+
+ const result = await getRegionsProvider({ adm0: 'BRA' });
+
+ expect(dataRequest.get).toHaveBeenCalledWith(
+ `${GADM_DATASET}?sql=SELECT name_1 AS name, gid_1 AS id FROM gadm_administrative_boundaries WHERE adm_level='1' AND gid_0 = 'BRA' ORDER BY name`,
+ { cancelToken: undefined }
+ );
+ expect(result).toEqual(mockResponse);
+ });
+ });
+
+ describe('getSubRegionsProvider', () => {
+ it('should fetch sub-regions data based on adm0 and adm1', async () => {
+ const mockResponse = { data: [{ id: 'BRA.25.390', name: 'Osasco' }] };
+ dataRequest.get.mockResolvedValue(mockResponse);
+
+ const result = await getSubRegionsProvider({ adm0: 'BRA', adm1: '25' });
+
+ expect(dataRequest.get).toHaveBeenCalledWith(
+ `${GADM_DATASET}?sql=SELECT gid_2 as id, name_2 as name FROM gadm_administrative_boundaries WHERE gid_0 = 'BRA' AND gid_1 = 'BRA.25_1' AND adm_level='2' AND type_2 NOT IN ('Waterbody', 'Water body', 'Water Body') ORDER BY name`,
+ { cancelToken: undefined }
+ );
+ expect(result).toEqual(mockResponse);
+ });
+ });
+
+ describe('getFAOCountriesProvider', () => {
+ it('should fetch FAO countries data', async () => {
+ const mockResponse = { data: [{ iso: 'BRA', name: 'BRA' }] };
+ dataRequest.get.mockResolvedValue(mockResponse);
+
+ const result = await getFAOCountriesProvider();
+
+ expect(dataRequest.get).toHaveBeenCalledWith(
+ 'dataset/fao_forest_extent/v2020/query/json?sql=SELECT iso, country AS name FROM data WHERE year = 2020'
+ );
+ expect(result).toEqual(mockResponse);
+ });
+ });
+
+ describe('getCountryLinksProvider', () => {
+ it('should return country links', async () => {
+ const result = await getCountryLinksProvider();
+
+ expect(result).toEqual(countryLinks);
+ });
+ });
+
+ describe('getCountryLinksSerialized', () => {
+ it('should return serialized country links', async () => {
+ const result = await getCountryLinksSerialized();
+
+ expect(result).toEqual({
+ CMR: [
+ {
+ title: 'Interactive Forest Atlas of Cameroon',
+ url: 'http://cmr.forest-atlas.org/',
+ },
+ ],
+ CAF: [
+ {
+ title: 'Interactive Forest Atlas of Central African Republic',
+ url: 'http://caf.forest-atlas.org/',
+ },
+ ],
+ });
+ });
+ });
+
+ describe('getCategorisedCountries', () => {
+ it('should fetch categorised countries data', async () => {
+ const mockGadmResponse = { data: [{ iso: 'BRA', name: 'BRA' }] };
+ const mockFaoResponse = { data: [{ iso: 'BRA', name: 'BRA' }] };
+
+ dataRequest.get
+ .mockResolvedValueOnce(mockGadmResponse)
+ .mockResolvedValueOnce(mockFaoResponse);
+
+ const result = await getCategorisedCountries();
+
+ expect(result).toEqual({
+ gadmCountries: mockGadmResponse.data,
+ faoCountries: mockFaoResponse.data,
+ countries: mockGadmResponse.data,
+ });
+ });
+
+ it('should return categorised countries data as options', async () => {
+ const mockGadmResponse = { data: [{ iso: 'BRA', name: 'BRA' }] };
+ const mockFaoResponse = { data: [{ iso: 'BRA', name: 'BRA' }] };
+
+ dataRequest.get
+ .mockResolvedValueOnce(mockGadmResponse)
+ .mockResolvedValueOnce(mockFaoResponse);
+
+ const result = await getCategorisedCountries(true);
+
+ expect(result).toEqual({
+ gadmCountries: [{ label: 'BRA', value: 'BRA' }],
+ faoCountries: [{ label: 'BRA', value: 'BRA' }],
+ countries: [{ label: 'BRA', value: 'BRA' }],
+ });
+ });
+ });
+});
diff --git a/services/__tests__/geostore.spec.js b/services/__tests__/geostore.spec.js
index fe40565d99..6efdaf4cc6 100644
--- a/services/__tests__/geostore.spec.js
+++ b/services/__tests__/geostore.spec.js
@@ -55,7 +55,7 @@ describe('fetchGeostore', () => {
});
expect(dataRequest.get).toHaveBeenCalledWith(
- 'https://data-api.globalforestwatch.org/geostore/admin/BRA?source[provider]=gadm&source[version]=3.6&simplify=0.1',
+ '/geostore/admin/BRA?source[provider]=gadm&source[version]=4.1&simplify=0.1',
expect.any(Object)
);
});
@@ -86,7 +86,7 @@ describe('fetchGeostore', () => {
});
expect(dataRequest.get).toHaveBeenCalledWith(
- 'https://data-api.globalforestwatch.org/geostore/admin/BRA/25?source[provider]=gadm&source[version]=3.6&simplify=0.01',
+ '/geostore/admin/BRA/25?source[provider]=gadm&source[version]=4.1&simplify=0.01',
expect.any(Object)
);
});
diff --git a/services/__tests__/location.spec.js b/services/__tests__/location.spec.js
new file mode 100644
index 0000000000..818ef35192
--- /dev/null
+++ b/services/__tests__/location.spec.js
@@ -0,0 +1,82 @@
+import { jest } from '@jest/globals';
+
+import {
+ getCountriesProvider,
+ getRegionsProvider,
+ getSubRegionsProvider,
+} from 'services/country';
+
+import { countryConfig } from '../location';
+
+jest.mock('services/country', () => ({
+ getCountriesProvider: jest.fn(),
+ getRegionsProvider: jest.fn(),
+ getSubRegionsProvider: jest.fn(),
+}));
+
+describe('countryConfig', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('adm0', () => {
+ it('should return the correct country data', async () => {
+ const mockCountries = [{ iso: 'BRA', name: 'Brazil' }];
+ getCountriesProvider.mockResolvedValue({ data: mockCountries });
+
+ const result = await countryConfig.adm0({ adm0: 'BRA' });
+
+ expect(result).toEqual({
+ locationName: 'Brazil',
+ iso: 'BRA',
+ });
+ expect(getCountriesProvider).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('adm1', () => {
+ it('should return the correct region and country data', async () => {
+ const mockCountries = [{ iso: 'BRA', name: 'Brazil' }];
+ const mockRegions = [{ id: 'BRA.25_', name: 'São Paulo', iso: 'BRA' }];
+
+ getCountriesProvider.mockResolvedValue({ data: mockCountries });
+ getRegionsProvider.mockResolvedValue({ data: mockRegions });
+
+ const result = await countryConfig.adm1({ adm0: 'BRA', adm1: '25' });
+
+ expect(result).toEqual({
+ locationName: 'São Paulo, Brazil',
+ id: 'BRA.25_',
+ iso: 'BRA',
+ });
+ expect(getCountriesProvider).toHaveBeenCalledTimes(1);
+ expect(getRegionsProvider).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('adm2', () => {
+ it('should return the correct sub-region, region, and country data', async () => {
+ const mockCountries = [{ iso: 'BRA', name: 'Brazil' }];
+ const mockRegions = [{ id: 'BRA.25_', name: 'São Paulo' }];
+ const mockSubRegions = [{ id: 'BRA.25.390_', name: 'Osasco' }];
+
+ getCountriesProvider.mockResolvedValue({ data: mockCountries });
+ getRegionsProvider.mockResolvedValue({ data: mockRegions });
+ getSubRegionsProvider.mockResolvedValue({ data: mockSubRegions });
+
+ const result = await countryConfig.adm2({
+ adm0: 'BRA',
+ adm1: '25',
+ adm2: '390',
+ });
+
+ expect(result).toEqual({
+ locationName: 'Osasco, Brazil, São Paulo',
+ id: 'BRA.25.390_',
+ });
+ expect(getCountriesProvider).toHaveBeenCalledTimes(1);
+ expect(getRegionsProvider).toHaveBeenCalledTimes(1);
+ expect(getSubRegionsProvider).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/services/analysis-cached.js b/services/analysis-cached.js
index e686b4f39b..09059bddf5 100644
--- a/services/analysis-cached.js
+++ b/services/analysis-cached.js
@@ -398,7 +398,7 @@ export const getLossNaturalForest = (params) => {
}
const url = encodeURI(
- `${requestUrl}${SQL_QUERIES.lossNaturalForest}`
+ `${requestUrl}${SQL_QUERIES.lossNaturalForest}&ts=${Date.now()}`
.replace(
/{select_location}/g,
getLocationSelect({ ...params, cast: false })
@@ -1226,7 +1226,7 @@ export const getExtentNaturalForest = (params) => {
}
const url = encodeURI(
- `${requestUrl}${SQL_QUERIES.extentNaturalForest}`
+ `${requestUrl}${SQL_QUERIES.extentNaturalForest}&ts=${Date.now()}`
.replace(
/{select_location}/g,
getLocationSelect({ ...params, cast: false })
diff --git a/services/areas.js b/services/areas.js
index 86ae70e8df..5bc7946b63 100644
--- a/services/areas.js
+++ b/services/areas.js
@@ -53,14 +53,10 @@ export const getAreas = () => {
const gadm41 = apiAuthRequest
.get(`${REQUEST_URL}?source[provider]=gadm&source[version]=4.1`)
- .then(() => {
- // Commenting this logic to avoid gadm 4.1 in production
- // .then((areasResponse) => {
- // const { data: areas } = areasResponse.data;
-
- // return areas.map((area) => parseArea(area))
+ .then((areasResponse) => {
+ const { data: areas } = areasResponse.data;
- return [];
+ return areas.map((area) => parseArea(area));
});
return Promise.all([gadm36, gadm41]).then(([areas36, areas41]) => {
diff --git a/services/country.js b/services/country.js
index 6892454f5f..41ad8675c5 100644
--- a/services/country.js
+++ b/services/country.js
@@ -1,27 +1,30 @@
import { all, spread } from 'axios';
-import { cartoRequest, dataRequest } from 'utils/request';
-import { getGadm36Id } from 'utils/gadm';
+import { dataRequest } from 'utils/request';
+import { getGadmId } from 'utils/gadm';
import countryLinks from './country-links.json';
-const SQL_QUERIES = {
- getCountries:
- "SELECT iso, name_engli as name FROM gadm36_countries WHERE iso != 'TWN' AND iso != 'XCA' ORDER BY name",
- getFAOCountries:
- 'SELECT iso, country AS name FROM data WHERE 1 = 1 AND year = 2020',
- getRegions:
- "SELECT gid_1 as id, name_1 as name FROM gadm36_adm1 WHERE iso = '{iso}' ORDER BY name ",
- getSubRegions:
- "SELECT gid_2 as id, name_2 as name FROM gadm36_adm2 WHERE iso = '{iso}' AND gid_1 = '{adm1}' AND type_2 NOT IN ('Waterbody', 'Water body', 'Water Body') ORDER BY name",
+export const GADM_DATASET =
+ '/dataset/gadm_administrative_boundaries/v4.1.75/query';
+
+export const SQL_QUERIES = {
+ getGADMCountries:
+ "SELECT country AS name, gid_0 AS iso FROM gadm_administrative_boundaries WHERE adm_level = '0' AND gid_0 NOT IN ('Z01', 'Z02', 'Z03', 'Z04', 'Z05', 'Z06', 'Z07', 'Z08', 'Z09', 'TWN', 'XCA') ORDER BY country",
+ getGADMRegions:
+ "SELECT name_1 AS name, gid_1 AS id FROM gadm_administrative_boundaries WHERE adm_level='1' AND gid_0 = '{iso}' ORDER BY name",
+ getGADMSubRegions:
+ "SELECT gid_2 as id, name_2 as name FROM gadm_administrative_boundaries WHERE gid_0 = '{iso}' AND gid_1 = '{adm1}' AND adm_level='2' AND type_2 NOT IN ('Waterbody', 'Water body', 'Water Body') ORDER BY name",
+ getFAOCountries: 'SELECT iso, country AS name FROM data WHERE year = 2020',
};
const convertToOptions = (countries) =>
countries.map((c) => ({ label: c.name, value: c.iso }));
export const getCountriesProvider = () => {
- const url = `/sql?q=${SQL_QUERIES.getCountries}`;
- return cartoRequest.get(url);
+ const url = `${GADM_DATASET}?sql=${SQL_QUERIES.getGADMCountries}`;
+
+ return dataRequest.get(url);
};
export const getFAOCountriesProvider = () => {
@@ -30,15 +33,20 @@ export const getFAOCountriesProvider = () => {
};
export const getRegionsProvider = ({ adm0, token }) => {
- const url = `/sql?q=${SQL_QUERIES.getRegions}`.replace('{iso}', adm0);
- return cartoRequest.get(url, { cancelToken: token });
+ const url = `${GADM_DATASET}?sql=${SQL_QUERIES.getGADMRegions}`.replace(
+ '{iso}',
+ adm0
+ );
+
+ return dataRequest.get(url, { cancelToken: token });
};
-export const getSubRegionsProvider = (adm0, adm1, token) => {
- const url = `/sql?q=${SQL_QUERIES.getSubRegions}`
+export const getSubRegionsProvider = ({ adm0, adm1, token }) => {
+ const url = `${GADM_DATASET}?sql=${SQL_QUERIES.getGADMSubRegions}`
.replace('{iso}', adm0)
- .replace('{adm1}', getGadm36Id(adm0, adm1));
- return cartoRequest.get(url, { cancelToken: token });
+ .replace('{adm1}', getGadmId(adm0, adm1));
+
+ return dataRequest.get(url, { cancelToken: token });
};
export const getCountryLinksProvider = () => {
@@ -63,17 +71,32 @@ export const getCountryLinksSerialized = async () => {
export const getCategorisedCountries = (asOptions = false) =>
all([getCountriesProvider(), getFAOCountriesProvider()]).then(
- spread((gadm36Countries, faoCountries) => {
+ spread((gadm41Countries, faoCountries) => {
+ // GADM 4.1 cut short names larger than 32 characters,
+ // We are enforcing the long name for these cases
+ const shortenedCountries = {
+ 'United States Minor Outlying Isl':
+ 'United States Minor Outlying Islands',
+ 'South Georgia and the South Sand':
+ 'South Georgia and the South Sandwich Islands',
+ };
+
+ const gadm41CountriesWithLongNames = gadm41Countries.data.map((country) =>
+ shortenedCountries[country.name]
+ ? { ...country, name: shortenedCountries[country.name] }
+ : country
+ );
+
return {
gadmCountries: asOptions
- ? convertToOptions(gadm36Countries.data.rows)
- : gadm36Countries.data.rows,
+ ? convertToOptions(gadm41CountriesWithLongNames)
+ : gadm41CountriesWithLongNames,
faoCountries: asOptions
? convertToOptions(faoCountries.data)
: faoCountries.data,
countries: asOptions
- ? convertToOptions(gadm36Countries.data.rows)
- : gadm36Countries.data.rows,
+ ? convertToOptions(gadm41CountriesWithLongNames)
+ : gadm41CountriesWithLongNames,
};
})
);
diff --git a/services/geocoding.js b/services/geocoding.js
index 54c1cc81d2..72319fc40a 100644
--- a/services/geocoding.js
+++ b/services/geocoding.js
@@ -1,81 +1,19 @@
-import { mapboxRequest, cartoRequest } from 'utils/request';
-import compact from 'lodash/compact';
-import { all, spread } from 'axios';
-import bbox from 'turf-bbox';
-
-import { POLITICAL_BOUNDARIES } from 'data/layers';
-
-const getSearchSQL = (string, nameString, nameStringSimple) => {
- const words = string && string.split(/,| |, /);
- if (words && words.length) {
- const mappedWords = compact(words.map((w) => (w ? `%25${w}%25` : '')));
- const whereQueries = mappedWords.map(
- (w) =>
- `LOWER(${nameString}) LIKE '${w}' OR LOWER(${nameStringSimple}) LIKE '${w}' OR LOWER(name_1) LIKE '${w}' OR LOWER(simple_name_1) LIKE '${w}' OR LOWER(name_2) LIKE '${w}' OR LOWER(simple_name_2) LIKE '${w}'`
- );
-
- return whereQueries.join(' OR ');
- }
-
- return null;
-};
-
-const getWhereStatement = (search, nameString, nameStringSimple) => {
- const searchLower = search && search.toLowerCase();
-
- return getSearchSQL(searchLower, nameString, nameStringSimple);
-};
-
-const langCodes = {
- en: '0',
- fr: 'fr',
- zh: 'zh',
- id: '0',
- es_MX: 'es_mx',
- pt_BR: '0',
-};
+import { mapboxRequest } from 'utils/request';
export const fetchGeocodeLocations = (
searchQuery = '',
lang = 'en',
cancelToken
) => {
- const nameString = `name_${langCodes[lang]}`;
- let nameStringSimple = 'simple_name_0';
- if (lang !== 'en') nameStringSimple = nameString;
- const whereStatement = getWhereStatement(
- searchQuery,
- nameString,
- nameStringSimple
- );
-
- return all([
- mapboxRequest
- .get(
- `/geocoding/v5/mapbox.places/${searchQuery}.json?language=${lang}&access_token=${process.env.MapboxAccessToken}&types=postcode,district,place,locality,neighborhood,address,poi`,
- {
- cancelToken,
- }
- )
- .catch(() => {}),
- cartoRequest
- .get(
- `/sql?q=SELECT bbox, centroid, cartodb_id, area, size, level, name_0, name_1, name_2, gid_0, gid_1, gid_2, CASE WHEN gid_2 is not null THEN CONCAT(name_2, ', ', name_1, ', ', ${nameString}) WHEN gid_1 is not null THEN CONCAT(name_1, ', ', ${nameString}) WHEN gid_0 is not null THEN ${nameString} END AS place_name FROM gadm36_political_boundaries WHERE ${whereStatement} AND gid_0 != 'TWN' AND gid_0 != 'XCA' ORDER BY level, place_name LIMIT 5`,
- {
- cancelToken,
- }
- )
- .catch(() => {}),
- ]).then(
- spread((mapboxResponse, cartoResponse) => {
- const boundaries = cartoResponse?.data?.rows?.map((c) => ({
- ...c,
- id: POLITICAL_BOUNDARIES,
- bbox: bbox(JSON.parse(c.bbox)),
- center: JSON.parse(c.centroid)?.coordinates,
- }));
-
- return boundaries.concat(mapboxResponse?.data?.features);
+ return mapboxRequest
+ .get(
+ `/geocoding/v5/mapbox.places/${searchQuery}.json?language=${lang}&access_token=${process.env.NEXT_PUBLIC_MAPBOX_TOKEN}`,
+ {
+ cancelToken,
+ }
+ )
+ .then((mapboxResponse) => {
+ return mapboxResponse?.data?.features;
})
- );
+ .catch(() => {});
};
diff --git a/services/geostore.js b/services/geostore.js
index e52a850466..9041b6e8a7 100644
--- a/services/geostore.js
+++ b/services/geostore.js
@@ -52,7 +52,7 @@ const setThreshold = (iso, adm1, adm2) => {
*/
const fetchGeostore = ({ url, token, queryParams = '' }) => {
return dataRequest
- .get(`https://data-api.globalforestwatch.org${url}?${queryParams}`, {
+ .get(`${url}?${queryParams}`, {
cancelToken: token,
})
.then((response) => {
@@ -70,15 +70,16 @@ export const getGeostore = ({ type, adm0, adm1, adm2, token }) => {
if (!type || !adm0) return null;
const sourceProvider = 'source[provider]=gadm';
- const sourceVersion = 'source[version]=3.6';
+ const sourceVersion = 'source[version]=4.1';
const threshold = `simplify=${setThreshold(adm0, adm1, adm2)}`;
const queryParams = `${sourceProvider}&${sourceVersion}`;
switch (type) {
case 'country':
return fetchGeostore({
- url: `/geostore/admin/${adm0}${adm1 ? `/${adm1}` : ''}${adm2 ? `/${adm2}` : ''
- }`,
+ url: `/geostore/admin/${adm0}${adm1 ? `/${adm1}` : ''}${
+ adm2 ? `/${adm2}` : ''
+ }`,
queryParams: `${queryParams}&${threshold}`,
token,
});
diff --git a/services/location.js b/services/location.js
index f0d8834bd8..55df71ae28 100644
--- a/services/location.js
+++ b/services/location.js
@@ -1,44 +1,71 @@
import lowerCase from 'lodash/lowerCase';
import startCase from 'lodash/startCase';
-import { cartoRequest } from 'utils/request';
+
import { getGeodescriberByGeostore } from 'services/geodescriber';
import { getDatasetQuery } from 'services/datasets';
import { getArea } from 'services/areas';
+import {
+ getCountriesProvider,
+ getRegionsProvider,
+ getSubRegionsProvider,
+} from 'services/country';
+
+const findByIso = (list, iso) => list?.find((item) => item?.iso === iso);
+const findById = (list, idPrefix) =>
+ list?.find((item) => item?.id.startsWith(idPrefix));
+
+const getBaseLocationData = async ({ adm0, adm1, adm2 }) => {
+ const [countriesData, regionsData, subRegionsData] = await Promise.all([
+ getCountriesProvider(),
+ adm1 ? getRegionsProvider({ adm0 }) : null,
+ adm2 ? getSubRegionsProvider({ adm0, adm1 }) : null,
+ ]);
+
+ const { data: countries } = countriesData || {};
+ const { data: regions } = regionsData || {};
+ const { data: subRegions } = subRegionsData || {};
+
+ const country = findByIso(countries, adm0);
+ const region = adm1 ? findById(regions, `${adm0}.${adm1}_`) : null;
+ const subRegion = adm2
+ ? findById(subRegions, `${adm0}.${adm1}.${adm2}_`)
+ : null;
+
+ return { country, region, subRegion };
+};
export const countryConfig = {
- adm0: (params) =>
- cartoRequest(
- `/sql?q=SELECT iso, name_engli as name FROM gadm36_countries WHERE iso = '${params.adm0}' AND iso != 'XCA' AND iso != 'TWN'`
- ).then((response) => {
- const { name, ...props } = response?.data?.rows?.[0];
-
- return {
- locationName: name,
- ...props,
- };
- }),
- adm1: (params) =>
- cartoRequest(
- `/sql?q=SELECT iso, gid_1 as id, name_0 as adm0, name_1 as adm1 FROM gadm36_adm1 WHERE gid_1 = '${params.adm0}.${params.adm1}_1' AND iso != 'XCA' AND iso != 'TWN'`
- ).then((response) => {
- const { adm1, adm0, ...props } = response?.data?.rows?.[0];
-
- return {
- locationName: `${adm1}, ${adm0}`,
- ...props,
- };
- }),
- adm2: (params) =>
- cartoRequest(
- `/sql?q=SELECT gid_2, name_0 as adm0, name_1 as adm1, name_2 as adm2 FROM gadm36_adm2 WHERE gid_2 = '${params.adm0}.${params.adm1}.${params.adm2}_1' AND iso != 'XCA' AND iso != 'TWN'`
- ).then((response) => {
- const { adm2, adm1, adm0, ...props } = response?.data?.rows?.[0];
-
- return {
- locationName: `${adm2}, ${adm1}, ${adm0}`,
- ...props,
- };
- }),
+ adm0: async ({ adm0 }) => {
+ const { country } = await getBaseLocationData({ adm0 });
+ const { name, ...props } = country || {};
+
+ return {
+ locationName: name,
+ ...props,
+ };
+ },
+ adm1: async ({ adm0, adm1 }) => {
+ const { country, region } = await getBaseLocationData({ adm0, adm1 });
+ const { name, ...props } = region || {};
+
+ return {
+ locationName: `${name}, ${country?.name}`,
+ ...props,
+ };
+ },
+ adm2: async ({ adm0, adm1, adm2 }) => {
+ const { country, region, subRegion } = await getBaseLocationData({
+ adm0,
+ adm1,
+ adm2,
+ });
+ const { name, ...props } = subRegion || {};
+
+ return {
+ locationName: `${name}, ${country?.name}, ${region?.name}`,
+ ...props,
+ };
+ },
};
export const geostoreConfig = {
diff --git a/services/projects.js b/services/projects.js
index 664adfbddc..012bb57a70 100644
--- a/services/projects.js
+++ b/services/projects.js
@@ -37,7 +37,7 @@ apiFetch.setFetchHandler(async (options) => {
});
const formatProjects = (projectsData) => {
- const countries = projectsData?.[1]?.data?.rows;
+ const countries = projectsData?.[1]?.data;
const projects = projectsData?.[0]?.data;
if (!projects) {
diff --git a/utils/__tests__/gadm.js b/utils/__tests__/gadm.js
new file mode 100644
index 0000000000..d7cfb48eef
--- /dev/null
+++ b/utils/__tests__/gadm.js
@@ -0,0 +1,71 @@
+import { beforeAll } from '@jest/globals';
+import { getGadmId, parseGadmId, getGadmLocationByLevel } from 'utils/gadm';
+
+describe('getGadmId', () => {
+ it('should generate the correct GADM ID for country and region levels', () => {
+ expect(getGadmId('BRA', '25')).toBe('BRA.25_1');
+ });
+
+ it('should generate the correct GADM ID for country, region, and sub-region levels', () => {
+ expect(getGadmId('BRA', '25', '390')).toBe('BRA.25.390_1');
+ });
+});
+
+describe('parseGadmId', () => {
+ it('should parse a full GADM ID correctly', () => {
+ const gid = 'BRA.25.390';
+ expect(parseGadmId(gid)).toEqual({
+ adm0: 'BRA',
+ adm1: 25,
+ adm2: 390,
+ });
+ });
+
+ it('should parse a GADM ID with only country and region correctly', () => {
+ const gid = 'BRA.25_1';
+ expect(parseGadmId(gid)).toEqual({
+ adm0: 'BRA',
+ adm1: 25,
+ adm2: undefined,
+ });
+ });
+});
+
+describe('getGadmLocationByLevel', () => {
+ let location;
+
+ beforeAll(() => {
+ location = {
+ gid_0: 'BRA',
+ gid_1: 'BRA.25_1',
+ gid_2: 'BRA.25.390_1',
+ };
+ });
+
+ it('should return location with parsed GADM ID for adm_level 0', () => {
+ expect(getGadmLocationByLevel({ adm_level: 0, ...location })).toEqual({
+ type: 'country',
+ adm0: 'BRA',
+ adm1: undefined,
+ adm2: undefined,
+ });
+ });
+
+ it('should return location with parsed GADM ID for level 1', () => {
+ expect(getGadmLocationByLevel({ adm_level: 1, ...location })).toEqual({
+ type: 'country',
+ adm0: 'BRA',
+ adm1: 25,
+ adm2: undefined,
+ });
+ });
+
+ it('should return location with parsed GADM ID for level 2', () => {
+ expect(getGadmLocationByLevel({ adm_level: 2, ...location })).toEqual({
+ type: 'country',
+ adm0: 'BRA',
+ adm1: 25,
+ adm2: 390,
+ });
+ });
+});
diff --git a/utils/gadm.js b/utils/gadm.js
index 754a0fd252..708bc7a427 100644
--- a/utils/gadm.js
+++ b/utils/gadm.js
@@ -1,9 +1,9 @@
-export const getGadm36Id = (country, region, subRegion) =>
+export const getGadmId = (country, region, subRegion) =>
`${country}${region ? `.${region}` : ''}${
subRegion ? `.${subRegion}_1` : '_1'
}`;
-export const parseGadm36Id = (gid) => {
+export const parseGadmId = (gid) => {
if (!gid) return null;
const ids = gid.split('.');
@@ -26,6 +26,6 @@ export const parseGadm36Id = (gid) => {
export const getGadmLocationByLevel = ({ adm_level, ...location }) => ({
type: 'country',
...(location?.gid_0 && {
- ...parseGadm36Id(location[`gid_${adm_level || '0'}`]),
+ ...parseGadmId(location[`gid_${adm_level || '0'}`]),
}),
});
diff --git a/utils/request.js b/utils/request.js
index 73b1f2c8af..89b2ba3e9d 100644
--- a/utils/request.js
+++ b/utils/request.js
@@ -53,10 +53,16 @@ export const dataRequest = axios.create({
baseURL: DATA_API_URL,
headers: {
'x-api-key': DATA_API_KEY,
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0',
},
}),
...(!isServer && {
baseURL: PROXIES.DATA_API,
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0',
}),
transformResponse: [(data) => JSON.parse(data)?.data],
});