Skip to content

Commit 0266bc3

Browse files
authored
20 integrate address search results (#33)
* working API that makes calls to local Flask App blocked by CORS on client side due to calling on a localhost - but Flask shows the calls and returns 200 status on the server side so without CORS this would return exactly what we would need * Successful zoom in for any of top 30 results - from any given keystroke * usePlaces hook added to Search.js * got rid of old code * quick npx standard fix * added utils function for projection * Delete launch.json * added proj4 to package.json * fixed warnings * fixed eslint hasOwnProperty issue * fix loading issue * remove launch * fix gitignore to account for launch.json * fix launch json
1 parent ddd3775 commit 0266bc3

File tree

10 files changed

+196
-15
lines changed

10 files changed

+196
-15
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/build
1313

1414
# misc
15+
.env
1516
.DS_Store
1617
.env.local
1718
.env.development.local
@@ -21,3 +22,6 @@
2122
npm-debug.log*
2223
yarn-debug.log*
2324
yarn-error.log*
25+
26+
#launch
27+
/.vscode/launch.json

package-lock.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"axios": "^0.27.2",
1616
"mapbox-gl": "npm:empty-npm-package@^1.0.0",
1717
"maplibre-gl": "^2.4.0",
18+
"proj4": "^2.8.0",
1819
"react": "^18.2.0",
1920
"react-dom": "^18.2.0",
2021
"react-map-gl": "^7.0.19",
@@ -63,4 +64,4 @@
6364
"gh-pages": "^4.0.0",
6465
"standard": "^17.0.0"
6566
}
66-
}
67+
}

src/MapUpMap.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function MapUpMap (props) {
1010

1111
useEffect(() => {
1212
if (mapRef.current) {
13-
mapRef.current.flyTo({ center: location, zoom: 16, essential: true })
13+
mapRef.current.flyTo({ center: location, zoom: 18, essential: true })
1414
}
1515
}, [location])
1616

@@ -36,5 +36,3 @@ function MapUpMap (props) {
3636
}
3737

3838
export default MapUpMap
39-
40-
// calc function CSS (100%-60px-90px)

src/Search.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { styled, alpha } from '@mui/material/styles'
33
import InputBase from '@mui/material/InputBase'
44
import SearchIcon from '@mui/icons-material/Search'
55
import Autocomplete from '@mui/material/Autocomplete'
6+
import { toLatLng } from './utils/utils'
7+
import usePlaces from './hooks/usePlaces'
68

79
const SearchContainer = styled('div')(({ theme }) => ({
810
position: 'relative',
@@ -44,15 +46,10 @@ const StyledInputBase = styled(InputBase)(({ theme }) => ({
4446
}
4547
}))
4648

47-
const testData = [
48-
{ label: 'Ordnance Survey', location: [-1.471061, 50.9382] },
49-
{ label: 'Bradford on Avon', location: [-2.249391, 51.347659] },
50-
{ label: 'Geovation', location: [-0.099754, 51.52435] },
51-
{ label: 'Berwick-upon-Tweed', location: [-2.00477, 55.768824] }
52-
]
53-
5449
function Search (props) {
5550
const { setLocation } = props
51+
const { loading, places, searchPlaces } = usePlaces([])
52+
5653
return (
5754
<SearchContainer>
5855
<SearchIconWrapper>
@@ -61,14 +58,23 @@ function Search (props) {
6158
<Autocomplete
6259
disablePortal
6360
id='combo-box-demo'
64-
options={testData}
65-
getOptionLabel={option => option.label}
66-
style={{ width: 300 }}
61+
options={places}
62+
filterOptions={(x) => x}
63+
getOptionLabel={option => option.ADDRESS ? option.ADDRESS : ''}
64+
style={{ width: 500 }}
6765
renderInput={params => {
6866
const { InputLabelProps, InputProps, ...rest } = params
6967
return <StyledInputBase {...params.InputProps} {...rest} />
7068
}}
71-
onChange={(event, newValue) => { if (newValue.location) { setLocation(newValue.location) } }}
69+
onChange={(event, newValue) => {
70+
if (newValue.X_COORDINATE && !loading) {
71+
const latlng = toLatLng({ ea: newValue.X_COORDINATE, no: newValue.Y_COORDINATE })
72+
setLocation([latlng.lng, latlng.lat])
73+
}
74+
}}
75+
onInputChange={(event, newValue) => {
76+
searchPlaces(newValue)
77+
}}
7278
/>
7379
</SearchContainer>
7480
)

src/api/configs/axiosConfigs.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import axios from 'axios'
2+
// initializing the axios instance with custom configs
3+
const api = axios.create({
4+
baseURL: 'http://127.0.0.1:5000' // to replace with AWS URL once online
5+
})
6+
7+
export default api

src/api/configs/axiosUtils.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export function defineCancelApiObject (apiObject) {
2+
// an object that will contain a cancellation handler
3+
// associated to each API property name in the apiObject API object
4+
const cancelApiObject = {}
5+
6+
// each property in the apiObject API layer object
7+
// is associated with a function that defines an API call
8+
9+
// this loop iterates over each API property name
10+
Object.getOwnPropertyNames(apiObject).forEach((apiPropertyName) => {
11+
const cancellationControllerObject = {
12+
controller: undefined
13+
}
14+
15+
// associating the request cancellation handler with the API property name
16+
cancelApiObject[apiPropertyName] = {
17+
handleRequestCancellation: () => {
18+
// if the controller already exists,
19+
// canceling the request
20+
if (cancellationControllerObject.controller) {
21+
// canceling the request and returning this custom message
22+
cancellationControllerObject.controller.abort()
23+
}
24+
25+
// generating a new controller
26+
// with the AbortController factory
27+
cancellationControllerObject.controller = new AbortController()
28+
29+
return cancellationControllerObject.controller
30+
}
31+
}
32+
})
33+
34+
return cancelApiObject
35+
}

src/api/placesAPI.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// big thank you to https://semaphoreci.com/blog/api-layer-react
2+
3+
import api from './configs/axiosConfigs'
4+
import { defineCancelApiObject } from './configs/axiosUtils'
5+
6+
export const PlacesAPI = {
7+
autofill: async function (input, cancel = false) {
8+
const response = await api.request({
9+
url: `/places/${input}`,
10+
method: 'GET',
11+
// retrieving the signal value by using the property name
12+
signal: cancel ? cancelApiObject[this.autofill.name].handleRequestCancellation().signal : undefined
13+
})
14+
15+
// returning the data from API
16+
return response.data
17+
}
18+
}
19+
20+
// defining the cancel API object for PlacesAPI
21+
const cancelApiObject = defineCancelApiObject(PlacesAPI)

src/hooks/usePlaces.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { PlacesAPI } from '../api/placesAPI'
2+
import { useState } from 'react'
3+
4+
const usePlaces = () => {
5+
const [loading, setLoading] = useState(false)
6+
const [places, setPlaces] = useState([])
7+
8+
const searchPlaces = (search) => {
9+
PlacesAPI.autofill(search).then((response) => {
10+
setLoading(true)
11+
return response
12+
})
13+
.then((response) => {
14+
setLoading(false)
15+
setPlaces(response)
16+
})
17+
}
18+
19+
return { loading, places, searchPlaces }
20+
}
21+
22+
export default usePlaces

src/utils/utils.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import proj4 from 'proj4'
2+
3+
// All borrowed from the os-transform GitHub repository
4+
5+
const _maxBounds = {
6+
projected: [[0.0, 0.0], [699999.9, 1299999.9]],
7+
geographic: [[-8.74, 49.84], [1.96, 60.9]]
8+
}
9+
10+
/**
11+
* Return latlng from an input easting + northing.
12+
* @param {object} coordinates - The easting + northing to be transformed.
13+
* @param {integer} decimals - [optional] The specified number of decimal places.
14+
*/
15+
export function toLatLng (coordinates, decimals = 7) {
16+
const test = _checkBounds(coordinates)
17+
if (!test.valid) {
18+
console.log(test.message)
19+
return {}
20+
}
21+
22+
proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs')
23+
const point = proj4('EPSG:27700', 'EPSG:4326', [coordinates.ea, coordinates.no])
24+
25+
const lng = Number(point[0].toFixed(decimals))
26+
const lat = Number(point[1].toFixed(decimals))
27+
28+
return { lat, lng }
29+
}
30+
31+
export function _checkBounds (coordinates) {
32+
let isValid = true
33+
if (Object.prototype.hasOwnProperty.call(coordinates, 'ea') && Object.prototype.hasOwnProperty.call(coordinates, 'no')) {
34+
if ((coordinates.ea < _maxBounds.projected[0][0] || coordinates.ea > _maxBounds.projected[1][0]) ||
35+
(coordinates.no < _maxBounds.projected[0][1] || coordinates.no > _maxBounds.projected[1][1])) {
36+
isValid = false
37+
}
38+
} else if (Object.prototype.hasOwnProperty.call(coordinates, 'lat') && Object.prototype.hasOwnProperty.call(coordinates, 'lng')) {
39+
if ((coordinates.lng < _maxBounds.geographic[0][0] || coordinates.lng > _maxBounds.geographic[1][0]) ||
40+
(coordinates.lat < _maxBounds.geographic[0][1] || coordinates.lat > _maxBounds.geographic[1][1])) {
41+
isValid = false
42+
}
43+
}
44+
45+
const message = isValid ? '' : 'Coordinates out of range.'
46+
47+
return { valid: isValid, message }
48+
}

0 commit comments

Comments
 (0)