diff --git a/src/constants/src/layers.ts b/src/constants/src/layers.ts index e803b92bde..4365fc9d2c 100644 --- a/src/constants/src/layers.ts +++ b/src/constants/src/layers.ts @@ -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 ]; diff --git a/src/layers/package.json b/src/layers/package.json index adc2ae8958..85b41381c7 100644 --- a/src/layers/package.json +++ b/src/layers/package.json @@ -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", diff --git a/src/layers/src/geojson-layer/geojson-layer.ts b/src/layers/src/geojson-layer/geojson-layer.ts index f38dee3981..e3757a72a9 100644 --- a/src/layers/src/geojson-layer/geojson-layer.ts +++ b/src/layers/src/geojson-layer/geojson-layer.ts @@ -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, { @@ -209,6 +211,7 @@ export default class GeoJsonLayer extends Layer { dataContainer: DataContainerInterface | null = null; filteredIndex: Uint8ClampedArray | null = null; filteredIndexTrigger: number[] | null = null; + centroids: Array = []; constructor(props) { super(props); @@ -417,6 +420,23 @@ export default class GeoJsonLayer extends Layer { }; } + isInPolygon(data: DataContainerInterface, index: number, polygon: Feature): 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; @@ -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}); } diff --git a/src/layers/src/geojson-layer/geojson-utils.ts b/src/layers/src/geojson-layer/geojson-utils.ts index 9e46176918..7eb58fd87a 100644 --- a/src/layers/src/geojson-layer/geojson-utils.ts +++ b/src/layers/src/geojson-layer/geojson-utils.ts @@ -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'; @@ -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 = []; + 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 }; } diff --git a/src/layers/src/layer-utils.ts b/src/layers/src/layer-utils.ts index 6e787633da..34292199c6 100644 --- a/src/layers/src/layer-utils.ts +++ b/src/layers/src/layer-utils.ts @@ -50,6 +50,7 @@ export type GeojsonLayerMetaProps = { featureTypes: DeckGlGeoTypes; bounds: BBox | null; fixedRadius: boolean; + centroids?: Array; }; export function getGeojsonLayerMetaFromArrow({ @@ -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 @@ -90,7 +92,8 @@ export function getGeojsonLayerMetaFromArrow({ dataToFeature: binaryGeometries, featureTypes, bounds, - fixedRadius + fixedRadius, + centroids: meanCenters }; } diff --git a/src/reducers/package.json b/src/reducers/package.json index d87373fb26..afe37c72dd 100644 --- a/src/reducers/package.json +++ b/src/reducers/package.json @@ -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", diff --git a/src/reducers/src/vis-state-updaters.ts b/src/reducers/src/vis-state-updaters.ts index 5951b2cbfb..952e9ba46c 100644 --- a/src/reducers/src/vis-state-updaters.ts +++ b/src/reducers/src/vis-state-updaters.ts @@ -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'; @@ -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 @@ -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: { diff --git a/src/utils/src/filter-utils.ts b/src/utils/src/filter-utils.ts index 5d10341a8f..33e0602ac7 100644 --- a/src/utils/src/filter-utils.ts +++ b/src/utils/src/filter-utils.ts @@ -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; } diff --git a/yarn.lock b/yarn.lock index 607b15f0cc..6d37d66946 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==