Repository: precision-sustainable-ag/dst-selector
Pull request #856: WCCC map updates
Author: login: akshat22
Created: 2025-04-23T21:08:17Z | Merged: 2025-04-25T15:08:41Z

- Address #803 
- This PR closes the first two points of the issue. The third point will be covered in another PR


Diff:
diff --git a/package-lock.json b/package-lock.json
index e639f78c..8a1cac0c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12709,7 +12709,7 @@
     },
     "node_modules/shared-react-components": {
       "version": "1.0.0",
-      "resolved": "git+ssh://git@github.com/precision-sustainable-ag/shared-react-components.git#16060bcd9e101de1c89af5c31a7b7563b7c0c4b2",
+      "resolved": "git+ssh://git@github.com/precision-sustainable-ag/shared-react-components.git#5c37f8c2c229cd81682e6a6c5823d97ba6c1983b",
       "license": "ISC",
       "dependencies": {
         "@fontsource/ibm-plex-sans": "^5.1.0",
diff --git a/src/pages/Location/Location.jsx b/src/pages/Location/Location.jsx
index 5edc15ac..60c802ea 100644
--- a/src/pages/Location/Location.jsx
+++ b/src/pages/Location/Location.jsx
@@ -21,6 +21,7 @@ import { PSAReduxMap } from 'shared-react-components/src';
 import statesLatLongDict from '../../shared/stateslatlongdict';
 import { abbrRegion, reverseGEO, callCoverCropApi } from '../../shared/constants';
 import PlantHardinessZone from '../CropSidebar/PlantHardinessZone/PlantHardinessZone';
+import StateChangeAlertDialog from './StateChangeAlertDialog/StateChangeAlertDialog';
 import { updateLocation } from '../../reduxStore/addressSlice';
 import { updateRegion } from '../../reduxStore/mapSlice';
 import { setQueryString, snackHandler } from '../../reduxStore/sharedSlice';
@@ -49,6 +50,11 @@ const Location = () => {
   const [mapFeatures, setMapFeatures] = useState(userFieldRedux);
   // eslint-disable-next-line no-nested-ternary
   const [latLon, setLatLon] = useState(markersRedux ? markersRedux[0] : stateLabelRedux ? statesLatLongDict[stateLabelRedux] : [47, -122]);
+  const [stateLabel, setStateLabel] = useState(null);
+  const [isOpen, setIsOpen] = useState(false);
+  const [isFarmable, setIsFarmable] = useState(true);
+
+  const NOT_FARMABLE_HTML = '<div style="color: red; font-weight: bold; margin-top: 8 px;">The selected land is not farmable</div>';
 
   const updateMapFeatures = (newFeatures) => {
     if (JSON.stringify(mapFeatures) === JSON.stringify(newFeatures)) return;
@@ -70,8 +76,13 @@ const Location = () => {
     setSelectedToEditSite(properties?.address);
     setLatLon([properties?.lat, properties?.lon]);
     updateMapFeatures(properties?.features);
+    setStateLabel(properties?.state?.STATE_NAME);
   };
 
+  useEffect(() => {
+    if (stateLabel && stateLabel !== stateLabelRedux) setIsOpen(true);
+  }, [stateLabel, latLon[0], latLon[1]]);
+
   useEffect(() => {
     // analytics
     pirschAnalytics('Visited Page', { meta: { visited: 'Location' } });
@@ -92,6 +103,50 @@ const Location = () => {
     pirschAnalytics('Location', { meta: { mapUpdate: true } });
   };
 
+  // Fetches ssurgo data and checks if the soil composition is farmable
+  const getSSURGOData = (lat, lon) => {
+    // eslint-disable-next-line max-len
+    const soilDataQuery = `SELECT mu.mukey AS MUKEY, mu.muname AS mapUnitName, muag.drclassdcd AS drainageClass, muag.flodfreqdcd AS floodingFrequency, mp.mupolygonkey as MPKEY
+      FROM mapunit AS mu
+      INNER JOIN muaggatt AS muag ON muag.mukey = mu.mukey
+      INNER JOIN mupolygon AS mp ON mp.mukey = mu.mukey
+      WHERE mu.mukey IN (SELECT * from SDA_Get_Mukey_from_intersection_with_WktWgs84('point (${lon} ${lat})'))
+      AND
+      mp.mupolygonkey IN  (SELECT * from SDA_Get_Mupolygonkey_from_intersection_with_WktWgs84('point (${lon} ${lat})'))`;
+
+    const myHeaders = new Headers();
+    myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');
+
+    const urlencoded = new URLSearchParams();
+    urlencoded.append('query', soilDataQuery);
+    urlencoded.append('format', 'json+columnname');
+
+    const requestOptions = {
+      method: 'POST',
+      headers: myHeaders,
+      body: urlencoded,
+      redirect: 'follow',
+    };
+
+    fetch('https://sdmdataaccess.sc.egov.usda.gov/Tabular/post.rest', requestOptions)
+      .then((response) => response.json())
+      .then((result) => {
+        const stringSplit = [];
+
+        result.Table.forEach((el, index) => {
+          if (index !== 0) {
+            if (stringSplit.indexOf(el[1].split(',')[0]) === -1) {
+              stringSplit.push(el[1].split(',')[0]);
+            }
+          }
+        });
+
+        setIsFarmable(!stringSplit.some((el) => el.toLowerCase().includes('urban land')));
+      })
+      // eslint-disable-next-line no-console
+      .catch((error) => console.error('SSURGO FETCH ERROR', error));
+  };
+
   // when map marker changes, set addressRedux, update regionRedux based on zipcode
   useEffect(() => {
     if (Object.keys(selectedToEditSite).length > 0) {
@@ -261,6 +316,9 @@ const Location = () => {
     if (markersRedux) {
       getDetails();
     }
+
+    // If WCCC, check is the land is farmable
+    if (councilShorthandRedux === 'WCCC' && markersRedux) getSSURGOData(markersRedux[0][0], markersRedux[0][1]);
   }, [markersRedux]);
 
   return (
@@ -342,14 +400,17 @@ const Location = () => {
                 hasCoordBar
                 hasDrawing
                 hasFreehand
+                hasSinglePolygon
                 hasGeolocate
                 hasFullScreen
                 hasMarkerPopup
                 hasMarkerMovable
+                popupContent={!isFarmable ? NOT_FARMABLE_HTML : ''}
                 hasHelp
                 mapboxToken={mapboxToken}
               />
             </Container>
+            <StateChangeAlertDialog isOpen={isOpen} setIsOpen={setIsOpen} />
           </Grid>
         )}
       </Grid>
diff --git a/src/pages/Location/StateChangeAlertDialog/StateChangeAlertDialog.jsx b/src/pages/Location/StateChangeAlertDialog/StateChangeAlertDialog.jsx
new file mode 100644
index 00000000..b7dd2f5f
--- /dev/null
+++ b/src/pages/Location/StateChangeAlertDialog/StateChangeAlertDialog.jsx
@@ -0,0 +1,36 @@
+import {
+  Dialog,
+  DialogActions,
+  DialogContent,
+  Typography,
+} from '@mui/material';
+import React from 'react';
+import { PSAButton } from 'shared-react-components/src';
+
+const StateChangeAlertDialog = ({ isOpen, setIsOpen }) => {
+  const handleClose = () => {
+    setIsOpen(false);
+  };
+
+  return (
+    <Dialog disableEscapeKeyDown open={isOpen}>
+      <DialogContent dividers>
+        <Typography variant="body1">
+          This point is in a state other than the one you selected at the start. Move the point into
+          your selected state or hit Back and select a different state.
+        </Typography>
+      </DialogContent>
+      <DialogActions className="resetBox">
+        <PSAButton
+          buttonType=""
+          onClick={handleClose}
+          color="primary"
+          title="Okay"
+          className="okayButton"
+        />
+      </DialogActions>
+    </Dialog>
+  );
+};
+
+export default StateChangeAlertDialog;
diff --git a/src/reduxStore/sharedSlice.jsx b/src/reduxStore/sharedSlice.jsx
index 6e7787f0..e4ca33f4 100644
--- a/src/reduxStore/sharedSlice.jsx
+++ b/src/reduxStore/sharedSlice.jsx
@@ -193,6 +193,7 @@ const sharedReducer = (state = initialState, action = null) => {
           goBack: action.payload.goBack,
         },
       };
+
     case 'SET_SHARED_REDUX':
       return {
         ...state,


Reviews:
user: login: Adamws33
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-24T17:25:38Z
user: login: YazidZidane
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T18:12:53Z
user: login: akshat22
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T19:15:58Z
user: login: YazidZidane
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T14:30:04Z
user: login: YazidZidane
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T14:40:38Z

Review comments:
diff_hunk: @@ -70,8 +72,13 @@ const Location = () => {
     setSelectedToEditSite(properties?.address);
     setLatLon([properties?.lat, properties?.lon]);
     updateMapFeatures(properties?.features);
+    setStateLabel(properties?.state?.STATE_NAME);
   };
 
+  useEffect(() => {
+    if (stateLabel && stateLabel !== stateLabelRedux) dispatchRedux(setOpenStateChangeAlert(true));
+  }, [stateLabel]);
path: src/pages/Location/Location.jsx
user: login: YazidZidane
body: I think we can put the logic in `useEffect` to `updateProperties`, since the `stateLabel` will only changed by `updateProperties`.
created_at: 2025-04-24T18:09:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,7 @@ const initialState = {
     open: false,
     goBack: true,
   },
+  openStateChangeAlert: false,
path: src/reduxStore/sharedSlice.jsx
user: login: YazidZidane
body: I think we don't need a redux state for controlling the dialog since this dialog is only tied to the map component.
created_at: 2025-04-24T18:11:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -70,8 +72,13 @@ const Location = () => {
     setSelectedToEditSite(properties?.address);
     setLatLon([properties?.lat, properties?.lon]);
     updateMapFeatures(properties?.features);
+    setStateLabel(properties?.state?.STATE_NAME);
   };
 
+  useEffect(() => {
+    if (stateLabel && stateLabel !== stateLabelRedux) dispatchRedux(setOpenStateChangeAlert(true));
+  }, [stateLabel]);
path: src/pages/Location/Location.jsx
user: login: akshat22
body: I think the state variable `stateLabel` and `useEffect` is necessary because if we shift the logic to `updateProperties` then it  opens a dialog every time the `updateProperties`  is called (i.e. everytime a map zooms in and out too).
created_at: 2025-04-24T19:15:58Z
author_association: COLLABORATOR
diff_hunk: @@ -92,6 +103,54 @@ const Location = () => {
     pirschAnalytics('Location', { meta: { mapUpdate: true } });
   };
 
+  // Fetches ssurgo data and checks if the soil composition is farmable
+  const getSSURGOData = (lat, lon) => {
+    // eslint-disable-next-line max-len
+    const soilDataQuery = `SELECT mu.mukey AS MUKEY, mu.muname AS mapUnitName, muag.drclassdcd AS drainageClass, muag.flodfreqdcd AS floodingFrequency, mp.mupolygonkey as MPKEY
+      FROM mapunit AS mu
+      INNER JOIN muaggatt AS muag ON muag.mukey = mu.mukey
+      INNER JOIN mupolygon AS mp ON mp.mukey = mu.mukey
+      WHERE mu.mukey IN (SELECT * from SDA_Get_Mukey_from_intersection_with_WktWgs84('point (${lon} ${lat})'))
+      AND
+      mp.mupolygonkey IN  (SELECT * from SDA_Get_Mupolygonkey_from_intersection_with_WktWgs84('point (${lon} ${lat})'))`;
+
+    const myHeaders = new Headers();
+    myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');
+
+    const urlencoded = new URLSearchParams();
+    urlencoded.append('query', soilDataQuery);
+    urlencoded.append('format', 'json+columnname');
+
+    const requestOptions = {
+      method: 'POST',
+      headers: myHeaders,
+      body: urlencoded,
+      redirect: 'follow',
+    };
+
+    fetch('https://sdmdataaccess.sc.egov.usda.gov/Tabular/post.rest', requestOptions)
+      .then((response) => response.json())
+      .then((result) => {
+        const stringSplit = [];
+
+        result.Table.forEach((el, index) => {
+          if (index !== 0) {
+            if (stringSplit.indexOf(el[1].split(',')[0]) === -1) {
+              stringSplit.push(el[1].split(',')[0]);
+            }
+          }
+        });
+
+        setIsFarmable(!stringSplit.some((el) => el.toLowerCase().includes('urban land')));
+      })
+      // eslint-disable-next-line no-console
+      .catch((error) => console.error('SSURGO FETCH ERROR', error));
+  };
+
+  useEffect(() => {
+    if (councilShorthandRedux === 'WCCC' && markersRedux) getSSURGOData(markersRedux[0][0], markersRedux[0][1]);
+  }, [markersRedux]);
path: src/pages/Location/Location.jsx
user: login: YazidZidane
body: Can you put this `useEffect` to the last `useEffect` in this page since they are using the same dependencies and doing similar things?
created_at: 2025-04-25T14:30:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: mikahpinegar
created_at: 2025-04-23T21:21:21Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-23T21:21:33Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-24T20:04:04Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-24T20:04:28Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-24T22:07:52Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-24T22:09:43Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-25T14:53:07Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR
user: login: mikahpinegar
created_at: 2025-04-25T14:58:53Z
body: Your review app has been deployed and is available at 'http://48.216.196.33:80'.<br><br>[Learn More](https://aka.ms/testwithreviewapps) about how to test and provide feedback for the app.
author_association: CONTRIBUTOR