Skip to content

Commit

Permalink
ReactWidgets - Search by coordinates support (#731)
Browse files Browse the repository at this point in the history
* ReactWidgets - Search by coordinates support in useGeocoderWidgetController hook

* SearchByCoordinates - Fix for regexp to check lat,lon
  • Loading branch information
eamador authored Jul 5, 2023
1 parent e99bc67 commit f59d85e
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Not released

- Search by coordinates supported in useGeocoderWidgetController hook [#731](https://github.com/CartoDB/carto-react/pull/731)

## 2.1

### 2.1.5 (2023-07-05)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
isCoordinate,
isLatitude,
isLongitude,
validateAndGenerateCoordsResult
} from '../../../src/widgets/utils/validateCoordinates';

// Test isLatitude function
test('isLatitude should return true for valid latitudes', () => {
expect(isLatitude(0)).toBe(true);
expect(isLatitude(45)).toBe(true);
expect(isLatitude(-90)).toBe(true);
});
test('isLatitude should return false for invalid latitudes', () => {
expect(isLatitude(91)).toBe(false);
expect(isLatitude(-91)).toBe(false);
expect(isLatitude('invalid')).toBe(false);
});

// Test isLongitude function
test('isLongitude should return true for valid longitudes', () => {
expect(isLongitude(0)).toBe(true);
expect(isLongitude(120)).toBe(true);
expect(isLongitude(-180)).toBe(true);
});
test('isLongitude should return false for invalid longitudes', () => {
expect(isLongitude(181)).toBe(false);
expect(isLongitude(-181)).toBe(false);
expect(isLongitude('invalid')).toBe(false);
expect(isLongitude('Sevilla,')).toBe(false);
});

// Test isCoordinate function
test('isCoordinate should return true for valid coordinates', () => {
expect(isCoordinate('90,120')).toBe(true);
expect(isCoordinate('0,0')).toBe(true);
expect(isCoordinate('45,-90')).toBe(true);
expect(isCoordinate('-12.345,67.890')).toBe(true);
expect(isCoordinate('0.1234,-45.6789')).toBe(true);
expect(isCoordinate('12.3456789, 0')).toBe(true);
expect(isCoordinate('0 0')).toBe(true);
expect(isCoordinate('45 -90')).toBe(true);
expect(isCoordinate('-12.345 67.890')).toBe(true);
expect(isCoordinate('0.1234 -45.6789')).toBe(true);
expect(isCoordinate('12.3456789 0')).toBe(true);
});

test('isCoordinate should return false for invalid coordinates', () => {
// Invalid latitude
expect(isCoordinate('120,90')).toBe(false);
expect(isCoordinate('-100 180')).toBe(false);
expect(isCoordinate('abc 45')).toBe(false);

// Invalid longitude
expect(isCoordinate('0 181')).toBe(false);
expect(isCoordinate('45 -181')).toBe(false);
expect(isCoordinate('12.345 200')).toBe(false);
expect(isCoordinate('45 def')).toBe(false);

// Extra characters
expect(isCoordinate('0 0 extra')).toBe(false);
expect(isCoordinate('45 -90 extra')).toBe(false);
expect(isCoordinate('-12.345 67.890 extra')).toBe(false);
expect(isCoordinate('0.1234 -45.6789 extra')).toBe(false);
expect(isCoordinate('12.3456789 0 extra')).toBe(false);

// Missing coordinate parts
expect(isCoordinate('0')).toBe(false);
expect(isCoordinate('45 ')).toBe(false);
expect(isCoordinate(' 0')).toBe(false);
expect(isCoordinate('232,49,0')).toBe(false);
expect(isCoordinate('invalid')).toBe(false);
});

// Test validateAndGenerateCoordsResult function
test('validateAndGenerateCoordsResult should return the correct result for valid input', () => {
expect(validateAndGenerateCoordsResult('0,0')).toEqual({ latitude: 0, longitude: 0 });
expect(validateAndGenerateCoordsResult('45,-90')).toEqual({
latitude: 45,
longitude: -90
});
expect(validateAndGenerateCoordsResult('-12.345,67.890')).toEqual({
latitude: -12.345,
longitude: 67.89
});
});
test('validateAndGenerateCoordsResult should return false for invalid input', () => {
expect(validateAndGenerateCoordsResult('91,0')).toBe(false);
expect(validateAndGenerateCoordsResult('-91,180')).toBe(false);
expect(validateAndGenerateCoordsResult('invalid')).toBe(false);
});
20 changes: 16 additions & 4 deletions packages/react-widgets/src/hooks/useGeocoderWidgetController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { geocodeStreetPoint } from '../models/GeocodingModel';
import {
isCoordinate,
validateAndGenerateCoordsResult
} from '../widgets/utils/validateCoordinates';

const DEFAULT_COUNTRY = ''; // 'SPAIN', 'USA'

Expand All @@ -14,13 +18,16 @@ const setGeocoderResult = (payload) => ({
*
* @param {object} props
* @param {Function=} [props.onError] - Function to handle error messages from the widget.
* @param {boolean} [props.allowSearchByCoords]
*/
export default function useGeocoderWidgetController(props = {}) {
const credentials = useSelector((state) => state.carto.credentials);
// Component local state and events handling
const [searchText, setSearchText] = useState('');
const [loading, setIsLoading] = useState(false);

const { allowSearchByCoords } = props;

// Actions dispatched
const dispatch = useDispatch();

Expand Down Expand Up @@ -54,10 +61,15 @@ export default function useGeocoderWidgetController(props = {}) {
}
try {
setIsLoading(true);
const result = await geocodeStreetPoint(credentials, {
searchText,
country: DEFAULT_COUNTRY
});
let result;
if (allowSearchByCoords && isCoordinate(searchText)) {
result = validateAndGenerateCoordsResult(searchText);
} else {
result = await geocodeStreetPoint(credentials, {
searchText,
country: DEFAULT_COUNTRY
});
}
if (result) {
updateMarker(result);
}
Expand Down
25 changes: 25 additions & 0 deletions packages/react-widgets/src/widgets/utils/validateCoordinates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const isLatitude = (lat) => {
return isFinite(lat) && Math.abs(lat) <= 90;
};

export const isLongitude = (lng) => {
return isFinite(lng) && Math.abs(lng) <= 180;
};

export const isCoordinate = (str) => {
const coordinateRegexp =
/^-?(\d{1,2}|[1-8]\d|\d{1,2}\.\d{1,9}|[1-8]\d\.\d{1,9}|90(\.0{1,9})?)(\s|-|,\s?)-?(\d{1,2}|1[0-7]\d|\d{1,2}\.\d{1,9}|1[0-7]\d\.\d{1,9}|180(\.0{1,6})?)$/;
return coordinateRegexp.test(str);
};

export const validateAndGenerateCoordsResult = (searchText) => {
const [latitude, longitude] =
searchText.indexOf(',') !== -1 ? searchText.split(',') : searchText.split(' ');
const hasCoords = latitude && longitude;
const areValidCoords = isLatitude(latitude) && isLongitude(longitude);
if (!hasCoords || !areValidCoords) return false;
return {
longitude: parseFloat(longitude),
latitude: parseFloat(latitude)
};
};

0 comments on commit f59d85e

Please sign in to comment.