Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions packages/core-data/src/utils/Typesense.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { ObjectJs as ObjectUtils } from '@performant-software/shared-components';
import { Map as MapUtils } from '@performant-software/geospatial';
import { feature, featureCollection, truncate } from '@turf/turf';
import { feature, featureCollection } from '@turf/turf';
import { history } from 'instantsearch.js/es/lib/routers';
import TypesenseInstantsearchAdapter from 'typesense-instantsearch-adapter';
import _ from 'underscore';
Expand Down Expand Up @@ -179,6 +179,16 @@ const getGeometry = (place, path) => {
return _.get(place, path);
};

/**
* Returns the properties object for the passed place/path.
*
* @param place
* @param path
*/
const getProperties = (place, path) => {
return _.get(place, path) || {};
};

/**
* Returns the geometry URL for the passed place.
*
Expand Down Expand Up @@ -230,7 +240,8 @@ const toFeature = (record: any, item: any, geometry: any) => {
type: record.type,
items: [item],
url: record.url,
layerId: record.layerId
layerId: record.layerId,
originalProperties: record.properties
};

const id = parseInt(record.record_id, 10);
Expand Down Expand Up @@ -310,11 +321,20 @@ const toFeatureCollection = (results: Array<any>, path: string, options: Options
*
* @returns {*}
*/
const getFeatures = (features, results, path, options = {}) => {
const getFeatures = (
features,
results,
geometryPath,
propertiesPath,
options = {}
) => {
const newFeatures = [...features];

const objectPath = path.substring(0, path.lastIndexOf(ATTRIBUTE_DELIMITER));
const geometryPath = path.substring(path.lastIndexOf(ATTRIBUTE_DELIMITER) + 1, path.length);
const objectPath = geometryPath.substring(0, geometryPath.lastIndexOf(ATTRIBUTE_DELIMITER));
const geoJsonPath = geometryPath.substring(geometryPath.lastIndexOf(ATTRIBUTE_DELIMITER) + 1, geometryPath.length);
const originalPropertiesPath = propertiesPath
? propertiesPath.substring(propertiesPath.lastIndexOf(ATTRIBUTE_DELIMITER) + 1, propertiesPath.length)
: null;

const placeIds = [];
const recordIds = [];
Expand All @@ -334,9 +354,13 @@ const getFeatures = (features, results, path, options = {}) => {
if (options.geometries) {
geometryUrl = getGeometryUrl(place, options.geometries);
} else {
geometry = getGeometry(place, geometryPath);
geometry = getGeometry(place, geoJsonPath);
}

const properties = originalPropertiesPath
? getProperties(place, originalPropertiesPath)
: null;

const include = geometryUrl || (geometry && (!options.type || geometry.type === options.type));

if (include) {
Expand All @@ -350,7 +374,11 @@ const getFeatures = (features, results, path, options = {}) => {
record.properties?.items.push(trimmedResult);
}
} else {
newFeatures.push(MapUtils.toFeature({ ...place, layerId, url: geometryUrl }, trimmedResult, geometry));
newFeatures.push(MapUtils.toFeature({
...place,
layerId,
url: geometryUrl
}, trimmedResult, geometry, properties));
}
}
});
Expand Down
36 changes: 36 additions & 0 deletions packages/geospatial/src/components/CertaintyLayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @flow

import React from 'react';
import { Layer, Source } from 'react-map-gl/maplibre';
import MapStyles from '../utils/MapStyles';
import MapUtils from '../utils/Map';

type Props = {
geometry?: any,
certaintyRadius: number
};

/**
* Renders circles with the given certainty_radius circumference around all points in a new layer.
*/
const CertaintyLayer = (props: Props) => {
const circles = MapUtils.getCertaintyCircles([props.geometry], () => props.certaintyRadius);

return (
<Source
data={circles}
type='geojson'
>
<Layer
{...MapStyles.fill}
id='certainty-layer'
paint={{
...MapStyles.fill.paint,
}}
/>
</Source>
);
};

export default CertaintyLayer;

1 change: 1 addition & 0 deletions packages/geospatial/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow

// Components
export { default as CertaintyLayer } from './components/CertaintyLayer';
export { default as DrawControl } from './components/DrawControl';
export { default as GeoJsonLayer } from './components/GeoJsonLayer';
export { default as GeocodingControl } from './components/GeocodingControl';
Expand Down
50 changes: 48 additions & 2 deletions packages/geospatial/src/utils/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,55 @@ import {
featureCollection
} from '@turf/turf';
import _ from 'underscore';
import circle from '@turf/circle';

const MIN_LATITUDE = -90;
const MAX_LATITUDE = 90;
const MIN_LONGITUDE = -180;
const MAX_LONGITUDE = 180;

/**
* Returns a GeoJSON circle feature with the given center point and radius.
* @param point - The center point of the circle.
* @param radius - The radius of the circle in kilometers.
* @returns {Feature<Geometry, Properties>} - The GeoJSON circle feature.
*/
const buildCircle = (point, radius) => (
circle(point.coordinates, radius, { units: 'kilometers', steps: 32 })
);

/**
* Returns a GeoJSON feature collection containing circles for each item in the given array.
*/
const getCertaintyCircles = (
items,
getCertaintyRadius: (item: any) => number | undefined
) => {
const features = [];

for (const item of items) {
if (getCertaintyRadius(item)) {
if (item.geometry?.type === 'FeatureCollection') {
for (const childFeature of item.geometry.features) {
if (childFeature.geometry?.type === 'Point') {
features.push(buildCircle(childFeature.geometry, getCertaintyRadius(item)));
}
}
} else if (item.geometry.type === 'GeometryCollection') {
for (const geometry of item.geometry.geometries) {
if (geometry.type === 'Point') {
features.push(buildCircle(geometry, getCertaintyRadius(item)));
}
}
} else if (item.geometry?.type === 'Point') {
features.push(buildCircle(item.geometry, getCertaintyRadius(item)));
}
}
}

return featureCollection(features);
};

/**
* Adds the geo-referenced image layer to the passed map.
*
Expand Down Expand Up @@ -85,6 +128,7 @@ const removeLayer = (map, layerId) => map && map.removeLayer(layerId);
* @param record
* @param item
* @param geometry
* @param originalProperties
*
* @returns {Feature<Geometry, {
* id: *,
Expand All @@ -99,7 +143,7 @@ const removeLayer = (map, layerId) => map && map.removeLayer(layerId);
* url: *
* }>}
*/
const toFeature = (record: any, item: any, geometry: any) => {
const toFeature = (record: any, item: any, geometry: any, originalProperties?: any) => {
const properties = {
id: record.record_id,
ccode: [],
Expand All @@ -110,7 +154,8 @@ const toFeature = (record: any, item: any, geometry: any) => {
names: record.names?.map((toponym: string) => ({ toponym })),
type: record.type,
items: [item],
url: record.url
url: record.url,
originalProperties: originalProperties || {}
};

const id = parseInt(record.record_id, 10);
Expand Down Expand Up @@ -159,6 +204,7 @@ const validateCoordinates = (coordinates) => {

export default {
addGeoreferenceLayer,
getCertaintyCircles,
getBoundingBox,
removeLayer,
toFeature,
Expand Down