Skip to content

Commit

Permalink
[Feat] add polygon filter based on mean centers for GeoJsonLayer (#2476)
Browse files Browse the repository at this point in the history
Signed-off-by: Xun Li <lixun910@gmail.com>
  • Loading branch information
lixun910 committed Dec 19, 2023
1 parent 5092486 commit df87781
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/constants/src/layers.ts
Expand Up @@ -516,5 +516,6 @@ export const EDITOR_AVAILABLE_LAYERS: string[] = [
LAYER_TYPES.hexagon,
LAYER_TYPES.arc,
LAYER_TYPES.line,
LAYER_TYPES.hexagonId
LAYER_TYPES.hexagonId,
LAYER_TYPES.geojson
];
2 changes: 2 additions & 0 deletions src/layers/package.json
Expand Up @@ -53,7 +53,9 @@
"@nebula.gl/edit-modes": "1.0.2-alpha.1",
"@nebula.gl/layers": "1.0.2-alpha.1",
"@turf/bbox": "^6.0.1",
"@turf/center": "^6.0.1",
"@turf/helpers": "^6.1.4",
"@turf/boolean-within": "^6.0.1",
"@types/geojson": "^7946.0.7",
"@types/keymirror": "^0.1.1",
"@types/lodash.memoize": "^4.1.7",
Expand Down
28 changes: 25 additions & 3 deletions src/layers/src/geojson-layer/geojson-layer.ts
Expand Up @@ -19,7 +19,9 @@
// THE SOFTWARE.

import * as arrow from 'apache-arrow';
import {Feature} from 'geojson';
import {point as turfPoint} from '@turf/helpers';
import booleanWithin from '@turf/boolean-within';
import {Feature, Polygon} from 'geojson';
import uniq from 'lodash.uniq';
import {DATA_TYPES} from 'type-analyzer';
import Layer, {
Expand Down Expand Up @@ -209,6 +211,7 @@ export default class GeoJsonLayer extends Layer {
dataContainer: DataContainerInterface | null = null;
filteredIndex: Uint8ClampedArray | null = null;
filteredIndexTrigger: number[] | null = null;
centroids: Array<number[] | null> = [];

constructor(props) {
super(props);
Expand Down Expand Up @@ -417,6 +420,23 @@ export default class GeoJsonLayer extends Layer {
};
}

isInPolygon(data: DataContainerInterface, index: number, polygon: Feature<Polygon>): Boolean {
if (this.centroids.length === 0 || !this.centroids[index]) {
return false;
}
const isReactangleSearchBox = polygon.properties?.shape === 'Rectangle';
const point = this.centroids[index];
// if no valid centroid, return false
if (!point) return false;
// quick check if centroid is within the query rectangle
if (isReactangleSearchBox && polygon.properties?.bbox) {
const [minX, minY, maxX, maxY] = polygon.properties?.bbox;
return point[0] >= minX && point[0] <= maxX && point[1] >= minY && point[1] <= maxY;
}
// use turf.js to check if centroid is within query polygon
return booleanWithin(turfPoint(point), polygon);
}

updateLayerMeta(dataContainer) {
this.dataContainer = dataContainer;

Expand All @@ -429,21 +449,23 @@ export default class GeoJsonLayer extends Layer {
if (this.dataToFeature.length < dataContainer.numChunks()) {
// for incrementally loading data, we only load and render the latest batch; otherwise, we will load and render all batches
const isIncrementalLoad = dataContainer.numChunks() - this.dataToFeature.length === 1;
const {dataToFeature, bounds, fixedRadius, featureTypes} = getGeojsonLayerMetaFromArrow({
const {dataToFeature, bounds, fixedRadius, featureTypes, centroids} = getGeojsonLayerMetaFromArrow({
dataContainer,
getGeoColumn,
getGeoField,
...(isIncrementalLoad ? {chunkIndex: this.dataToFeature.length} : null)
});
if (centroids) this.centroids = this.centroids.concat(centroids);
this.updateMeta({bounds, fixedRadius, featureTypes});
this.dataToFeature = [...this.dataToFeature, ...dataToFeature];
}
} else {
if (this.dataToFeature.length === 0) {
const {dataToFeature, bounds, fixedRadius, featureTypes} = getGeojsonLayerMeta({
const {dataToFeature, bounds, fixedRadius, featureTypes, centroids} = getGeojsonLayerMeta({
dataContainer,
getFeature
});
if (centroids) this.centroids = centroids;
this.dataToFeature = dataToFeature;
this.updateMeta({bounds, fixedRadius, featureTypes});
}
Expand Down
19 changes: 18 additions & 1 deletion src/layers/src/geojson-layer/geojson-utils.ts
Expand Up @@ -21,6 +21,8 @@
import {Feature, BBox} from 'geojson';
import normalize from '@mapbox/geojson-normalize';
import bbox from '@turf/bbox';
import center from '@turf/center';
import {AllGeoJSON} from '@turf/helpers'
import {parseSync} from '@loaders.gl/core';
import {WKBLoader, WKTLoader} from '@loaders.gl/wkt';
import {binaryToGeometry} from '@loaders.gl/gis';
Expand Down Expand Up @@ -90,11 +92,26 @@ export function getGeojsonLayerMeta({
// keep a record of what type of geometry the collection has
const featureTypes = getGeojsonFeatureTypes(dataToFeature);

const meanCenters: Array<number[] | null> = [];
for (let i = 0; i < dataToFeature.length; i++) {
const feature = dataToFeature[i];
if (feature) {
try {
// TODO: use line interpolate to get center of line for LineString
const cent = center(feature as AllGeoJSON);
meanCenters.push(cent.geometry.coordinates);
} catch (e) {
meanCenters.push(null);
}
}
}

return {
dataToFeature,
bounds,
fixedRadius,
featureTypes
featureTypes,
centroids: meanCenters
};
}

Expand Down
9 changes: 6 additions & 3 deletions src/layers/src/layer-utils.ts
Expand Up @@ -50,6 +50,7 @@ export type GeojsonLayerMetaProps = {
featureTypes: DeckGlGeoTypes;
bounds: BBox | null;
fixedRadius: boolean;
centroids?: Array<number[] | null>;
};

export function getGeojsonLayerMetaFromArrow({
Expand All @@ -74,10 +75,11 @@ export function getGeojsonLayerMetaFromArrow({
chunkOffset: geoColumn.data[0].length * chunkIndex
}
: {}),
triangulate: true
triangulate: true,
calculateMeanCenters: true
};
// create binary data from arrow data for GeoJsonLayer
const {binaryGeometries, featureTypes, bounds} = getBinaryGeometriesFromArrow(
const {binaryGeometries, featureTypes, bounds, meanCenters} = getBinaryGeometriesFromArrow(
geoColumn,
encoding,
options
Expand All @@ -90,7 +92,8 @@ export function getGeojsonLayerMetaFromArrow({
dataToFeature: binaryGeometries,
featureTypes,
bounds,
fixedRadius
fixedRadius,
centroids: meanCenters
};
}

Expand Down
1 change: 1 addition & 0 deletions src/reducers/package.json
Expand Up @@ -44,6 +44,7 @@
"@kepler.gl/types": "3.0.0-alpha.1",
"@kepler.gl/utils": "3.0.0-alpha.1",
"@loaders.gl/loader-utils": "^4.1.0-alpha.4",
"@turf/bbox": "^6.0.1",
"@types/lodash.clonedeep": "^4.5.7",
"@types/lodash.flattendeep": "^4.4.7",
"@types/lodash.get": "^4.4.6",
Expand Down
5 changes: 5 additions & 0 deletions src/reducers/src/vis-state-updaters.ts
Expand Up @@ -18,6 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import bbox from '@turf/bbox';
import {console as Console} from 'global/window';
import {disableStackCapturing, withTask} from 'react-palm/tasks';
import cloneDeep from 'lodash.clonedeep';
Expand Down Expand Up @@ -2575,6 +2576,8 @@ export function setFeaturesUpdater(
// if feature is part of a filter
const filterId = feature && getFilterIdInFeature(feature);
if (filterId && feature) {
// add bbox for polygon filter to speed up filtering
if (feature.properties) feature.properties.bbox = bbox(feature);
const featureValue = featureToFilterValue(feature, filterId);
const filterIdx = state.filters.findIndex(fil => fil.id === filterId);
// @ts-ignore
Expand All @@ -2596,6 +2599,8 @@ export const setSelectedFeatureUpdater = (
state: VisState,
{feature, selectionContext}: VisStateActions.SetSelectedFeatureUpdaterAction
): VisState => {
// add bbox for polygon filter to speed up filtering
if (feature && feature.properties) feature.properties.bbox = bbox(feature);
return {
...state,
editor: {
Expand Down
4 changes: 4 additions & 0 deletions src/utils/src/filter-utils.ts
Expand Up @@ -454,6 +454,10 @@ export const getPolygonFilterFunctor = (layer, filter, dataContainer) => {
const pos = getCentroid({id});
return pos.every(Number.isFinite) && isInPolygon(pos, filter.value);
};
case LAYER_TYPES.geojson:
return data => {
return layer.isInPolygon(data, data.index, filter.value);
};
default:
return () => true;
}
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Expand Up @@ -3210,7 +3210,7 @@
d3-geo "1.7.1"
turf-jsts "*"

"@turf/center@>=4.0.0", "@turf/center@^6.5.0":
"@turf/center@>=4.0.0", "@turf/center@^6.0.1", "@turf/center@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@turf/center/-/center-6.5.0.tgz#3bcb6bffcb8ba147430cfea84aabaed5dbdd4f07"
integrity sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==
Expand Down

0 comments on commit df87781

Please sign in to comment.