From dea5af248ab8f6a904b66e52c52c00c3c55b8a77 Mon Sep 17 00:00:00 2001 From: Caroline Denis Date: Wed, 29 Jul 2026 10:32:24 +0200 Subject: [PATCH 1/4] Fix: Pull existing locality values from WB dataset --- .../lib/components/WbToolkit/GeoLocate.tsx | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx b/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx index dd4a7a09823..27c9fb34944 100644 --- a/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx +++ b/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx @@ -10,8 +10,8 @@ import type { IR, RA } from '../../utils/types'; import { filterArray } from '../../utils/types'; import { sortFunction } from '../../utils/utils'; import { Button } from '../Atoms/Button'; +import { formatCoordinate, getLocalityField } from '../Leaflet/helpers'; import { - getLocalityCoordinate, getSelectedLocalityColumns, } from '../Leaflet/wbLocalityDataExtractor'; import type { GeoLocatePayload } from '../Molecules/GeoLocate'; @@ -255,24 +255,32 @@ function getGeoLocateData( readonly visualRow: number; } ): IR { - const visualHeaders = getVisualHeaders(hot, columns); + return buildGeoLocateData( + hot.getDataAtRow(visualRow), + getVisualHeaders(hot, columns), + localityColumns + ); +} + +export function buildGeoLocateData( + row: RA, + headers: RA, + localityColumns: IR +): IR { + const getValue = (fieldName: string): string => + getLocalityField(row, headers, localityColumns, fieldName); - const localityData = - getLocalityCoordinate( - hot.getDataAtRow(visualRow), - visualHeaders, - localityColumns - ) || {}; + const latitude = getValue('locality.latitude1'); + const longitude = getValue('locality.longitude1'); const rawData = { - country: localityData['locality.geography.$country.name']?.value, - state: localityData['locality.geography.$state.name']?.value, - county: localityData['locality.geography.$county.name']?.value, - locality: localityData['locality.localityname']?.value, + country: getValue('locality.geography.$country.name') || undefined, + state: getValue('locality.geography.$state.name') || undefined, + county: getValue('locality.geography.$county.name') || undefined, + locality: getValue('locality.localityname') || undefined, points: - typeof localityData['locality.latitude1'] === 'object' && - typeof localityData['locality.longitude1'] === 'object' - ? `${localityData['locality.latitude1'].value}|${localityData['locality.longitude1'].value}` + latitude !== '' && longitude !== '' + ? `${formatCoordinate(latitude)}|${formatCoordinate(longitude)}` : undefined, }; From dc44dd0070b6612ac6cbdd09fe381c0727bd771a Mon Sep 17 00:00:00 2001 From: Caroline Denis Date: Wed, 29 Jul 2026 10:32:57 +0200 Subject: [PATCH 2/4] Test: Add unit test for geoLocate tool in WB --- .../WbToolkit/__tests__/GeoLocate.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts diff --git a/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts b/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts new file mode 100644 index 00000000000..de03398fc95 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts @@ -0,0 +1,48 @@ +import { buildGeoLocateData } from '../GeoLocate'; + +test('keeps geography and locality when coordinates are blank', () => { + const headers = ['Country', 'State', 'County', 'Locality', 'Lat', 'Lon']; + const localityColumns = { + 'locality.geography.$country.name': 'Country', + 'locality.geography.$state.name': 'State', + 'locality.geography.$county.name': 'County', + 'locality.localityname': 'Locality', + 'locality.latitude1': 'Lat', + 'locality.longitude1': 'Lon', + }; + + expect( + buildGeoLocateData( + ['USA', 'Kansas', 'Douglas', 'Prairie Park', '', ''], + headers, + localityColumns + ) + ).toEqual({ + country: 'USA', + state: 'Kansas', + county: 'Douglas', + locality: 'Prairie Park', + }); +}); + +test('includes formatted point data when coordinates are present', () => { + const headers = ['Country', 'Locality', 'Lat', 'Lon']; + const localityColumns = { + 'locality.geography.$country.name': 'Country', + 'locality.localityname': 'Locality', + 'locality.latitude1': 'Lat', + 'locality.longitude1': 'Lon', + }; + + expect( + buildGeoLocateData( + ['USA', 'Prairie Park', '38:58:48 N', '95:14:24 W'], + headers, + localityColumns + ) + ).toEqual({ + country: 'USA', + locality: 'Prairie Park', + points: '38.98|-95.24', + }); +}); \ No newline at end of file From 3afee39fdd85afd64963f7154710417f8fd826bd Mon Sep 17 00:00:00 2001 From: Caroline Denis Date: Wed, 29 Jul 2026 15:00:14 +0200 Subject: [PATCH 3/4] Fix: Parse Coordinate in geolocate --- .../lib/components/WbToolkit/GeoLocate.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx b/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx index 27c9fb34944..34fab2eb37e 100644 --- a/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx +++ b/specifyweb/frontend/js_src/lib/components/WbToolkit/GeoLocate.tsx @@ -5,12 +5,13 @@ import { useBooleanState } from '../../hooks/useBooleanState'; import { commonText } from '../../localization/common'; import { localityText } from '../../localization/locality'; import { wbText } from '../../localization/workbench'; +import { Coord } from '../../utils/latLong'; import { f } from '../../utils/functools'; import type { IR, RA } from '../../utils/types'; import { filterArray } from '../../utils/types'; import { sortFunction } from '../../utils/utils'; import { Button } from '../Atoms/Button'; -import { formatCoordinate, getLocalityField } from '../Leaflet/helpers'; +import { getLocalityField } from '../Leaflet/helpers'; import { getSelectedLocalityColumns, } from '../Leaflet/wbLocalityDataExtractor'; @@ -270,8 +271,21 @@ export function buildGeoLocateData( const getValue = (fieldName: string): string => getLocalityField(row, headers, localityColumns, fieldName); + const parseCoordinate = (value: string): number | undefined => { + const trimmedValue = value.trim(); + if (trimmedValue === '') return undefined; + if (trimmedValue === '0') return 0; + + const parsedCoordinate = Coord.parse(trimmedValue)?.toDegs(); + return parsedCoordinate === undefined + ? undefined + : parsedCoordinate.components[0] * parsedCoordinate.sign; + }; + const latitude = getValue('locality.latitude1'); const longitude = getValue('locality.longitude1'); + const parsedLatitude = parseCoordinate(latitude); + const parsedLongitude = parseCoordinate(longitude); const rawData = { country: getValue('locality.geography.$country.name') || undefined, @@ -279,8 +293,8 @@ export function buildGeoLocateData( county: getValue('locality.geography.$county.name') || undefined, locality: getValue('locality.localityname') || undefined, points: - latitude !== '' && longitude !== '' - ? `${formatCoordinate(latitude)}|${formatCoordinate(longitude)}` + parsedLatitude !== undefined && parsedLongitude !== undefined + ? `${parsedLatitude}|${parsedLongitude}` : undefined, }; From cd9d17b7aeb6bedca7bc4a162bf4d69c1d794ade Mon Sep 17 00:00:00 2001 From: Caroline Denis Date: Wed, 29 Jul 2026 13:04:05 +0000 Subject: [PATCH 4/4] Lint code with ESLint and Prettier Triggered by 3afee39fdd85afd64963f7154710417f8fd826bd on branch refs/heads/issue-8322 --- .../js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts b/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts index de03398fc95..7e0e56b9adb 100644 --- a/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbToolkit/__tests__/GeoLocate.test.ts @@ -45,4 +45,4 @@ test('includes formatted point data when coordinates are present', () => { locality: 'Prairie Park', points: '38.98|-95.24', }); -}); \ No newline at end of file +});