-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #266 from performant-software/feature/rc261_locati…
…on_markers RC #261 - Location markers
- Loading branch information
Showing
13 changed files
with
837 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// @flow | ||
|
||
import { LocationMarkers } from '@performant-software/geospatial'; | ||
import { feature, featureCollection } from '@turf/turf'; | ||
import React, { | ||
useCallback, | ||
useEffect, | ||
useMemo, | ||
useState | ||
} from 'react'; | ||
import _ from 'underscore'; | ||
|
||
type Props = { | ||
animate?: boolean, | ||
|
||
/** | ||
* The URL of the Core Data place record. | ||
*/ | ||
urls: Array<string> | ||
}; | ||
|
||
/** | ||
* This component renders a map marker for a given Core Data Place record. | ||
*/ | ||
const PlaceMarkers = (props: Props) => { | ||
const [places, setPlaces] = useState([]); | ||
|
||
/** | ||
* Converts the set of places into a FeatureCollection. | ||
* | ||
* @type {FeatureCollection<Geometry, Properties>} | ||
*/ | ||
const data = useMemo(() => featureCollection(places), [places]); | ||
|
||
/** | ||
* Fetches the passed URLs and converts the response to JSON. | ||
* | ||
* @type {function(): *} | ||
*/ | ||
const onFetch = useCallback(() => ( | ||
_.map(props.urls, (url) => fetch(url).then((res) => res.json())) | ||
), [props.urls]); | ||
|
||
/** | ||
* Converts the passed list of records to Features and sets them on the state. | ||
* | ||
* @type {function(*): *} | ||
*/ | ||
const onLoad = useCallback((records) => ( | ||
_.map(records, (record) => setPlaces((prevPlaces) => [ | ||
...prevPlaces, | ||
feature(record.geometry, record.properties) | ||
])) | ||
), []); | ||
|
||
/** | ||
* Fetch the place record from the passed URL. | ||
*/ | ||
useEffect(() => { | ||
Promise | ||
.all(onFetch()) | ||
.then(onLoad); | ||
}, [onFetch, onLoad]); | ||
|
||
if (_.isEmpty(data?.features)) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<LocationMarkers | ||
animate={props.animate} | ||
data={data} | ||
/> | ||
); | ||
}; | ||
|
||
export default PlaceMarkers; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// @flow | ||
|
||
import { faker } from '@faker-js/faker'; | ||
import _ from 'underscore'; | ||
import StateBoundaries from '../../src/data/StateBoundaries.json'; | ||
|
||
/** | ||
* Returns the min/max latitude/longitude for the passed state. If no state is provided, we'll compute | ||
* the boundary of all states. | ||
* | ||
* @param state | ||
* | ||
* @returns {{max_lat, min_lng, min_lat, max_lng}|*} | ||
*/ | ||
const getBoundary = (state = null) => { | ||
if (state) { | ||
return StateBoundaries[state]; | ||
} | ||
|
||
let minLatitude; | ||
let maxLatitude; | ||
let minLongitude; | ||
let maxLongitude; | ||
|
||
const keys = _.keys(StateBoundaries); | ||
_.each(keys, (key) => { | ||
const data = StateBoundaries[key]; | ||
|
||
if (!minLatitude || data.min_lat < minLatitude) { | ||
minLatitude = data.min_lat; | ||
} | ||
|
||
if (!maxLatitude || data.max_lat > maxLatitude) { | ||
maxLatitude = data.max_lat; | ||
} | ||
|
||
if (!minLongitude || data.min_lng < minLongitude) { | ||
minLongitude = data.min_lng; | ||
} | ||
|
||
if (!maxLongitude || data.max_lng > maxLongitude) { | ||
maxLongitude = data.max_lng; | ||
} | ||
}); | ||
|
||
return { | ||
min_lat: minLatitude, | ||
max_lat: maxLatitude, | ||
min_lng: minLongitude, | ||
max_lng: maxLongitude | ||
}; | ||
}; | ||
|
||
/** | ||
* Creates a sample place record in the Linked Places format. | ||
* | ||
* @returns {{ | ||
* names: [{ | ||
* toponym: string | ||
* }], | ||
* geometry: { | ||
* coordinates: number[], | ||
* type: string | ||
* }, | ||
* '@id': string, | ||
* type: string, | ||
* properties: { | ||
* ccode: *[], | ||
* record_id: string, | ||
* title: string, | ||
* uuid: string | ||
* } | ||
* }} | ||
*/ | ||
const createPlace = () => { | ||
const uuid = faker.string.uuid(); | ||
const title = faker.location.city(); | ||
|
||
const boundary = getBoundary(); | ||
const latitude = faker.location.latitude({ min: boundary.min_lat, max: boundary.max_lat }); | ||
const longitude = faker.location.longitude({ min: boundary.min_lng, max: boundary.max_lng }); | ||
|
||
return { | ||
'@id': `https://example.com/${uuid}`, | ||
type: 'Place', | ||
properties: { | ||
ccode: [], | ||
record_id: faker.string.numeric(7), | ||
title, | ||
uuid | ||
}, | ||
geometry: { | ||
type: 'Point', | ||
coordinates: [longitude, latitude] | ||
}, | ||
names: [{ | ||
toponym: title | ||
}] | ||
}; | ||
}; | ||
|
||
/** | ||
* Adds the `/core_data/places` and `/core_data/places/:id` routes. | ||
* | ||
* @param router | ||
*/ | ||
const addRoutes = (router) => { | ||
router.get('/core_data/places', (request, response) => { | ||
const { query } = request; | ||
|
||
let number = 1; | ||
if (query.number) { | ||
number = query.number; | ||
} | ||
|
||
const places = []; | ||
|
||
_.times(number, () => places.push(createPlace())); | ||
|
||
response.send(places); | ||
response.end(); | ||
}); | ||
|
||
router.get('/core_data/places/:id', (request, response) => { | ||
response.send(createPlace()); | ||
response.end(); | ||
}); | ||
}; | ||
|
||
export default { | ||
addRoutes | ||
}; |
Oops, something went wrong.