Skip to content

Commit b604199

Browse files
authored
Implement picking (#47)
* Implement picking * polygon app autohighlight * Correct multi-polygon offsets when picking
1 parent 81e0f58 commit b604199

File tree

9 files changed

+184
-25
lines changed

9 files changed

+184
-25
lines changed

examples/linestring/app.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from "react";
22
import { createRoot } from "react-dom/client";
33
import { StaticMap, MapContext, NavigationControl } from "react-map-gl";
4-
import DeckGL, { Layer } from "deck.gl/typed";
4+
import DeckGL, { Layer, PickingInfo } from "deck.gl/typed";
55
import { GeoArrowPathLayer } from "@geoarrow/deck.gl-layers";
66
import * as arrow from "apache-arrow";
77

@@ -25,12 +25,9 @@ const NAV_CONTROL_STYLE = {
2525
};
2626

2727
function Root() {
28-
const onClick = (info) => {
28+
const onClick = (info: PickingInfo) => {
2929
if (info.object) {
30-
// eslint-disable-next-line
31-
alert(
32-
`${info.object.properties.name} (${info.object.properties.abbrev})`
33-
);
30+
console.log(JSON.stringify(info.object.toJSON()));
3431
}
3532
};
3633

@@ -59,6 +56,7 @@ function Root() {
5956
data: table,
6057
getColor: [255, 0, 0],
6158
widthMinPixels: 1,
59+
pickable: true,
6260
})
6361
);
6462

@@ -68,6 +66,7 @@ function Root() {
6866
controller={true}
6967
layers={layers}
7068
ContextProvider={MapContext.Provider}
69+
onClick={onClick}
7170
glOptions={{
7271
powerPreference: "high-performance",
7372
}}

examples/multipolygon/app.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function Root() {
5959
extruded: true,
6060
getPolygon: table.getChild("geometry")!,
6161
getFillColor: table.getChild("pop_colors")!,
62-
// pickable: true,
63-
// autoHighlight: true,
62+
pickable: true,
63+
autoHighlight: true,
6464
})
6565
);
6666

@@ -70,7 +70,7 @@ function Root() {
7070
controller={true}
7171
layers={layers}
7272
ContextProvider={MapContext.Provider}
73-
// onClick={onClick}
73+
onClick={onClick}
7474
>
7575
<StaticMap mapStyle={MAP_STYLE} />
7676
<NavigationControl style={NAV_CONTROL_STYLE} />

examples/multipolygon/generate_data.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import geopandas as gpd
22
import numpy as np
3+
import pandas as pd
34
import pyarrow as pa
45
import pyarrow.feather as feather
56
from lonboard.colormap import apply_continuous_cmap
@@ -9,6 +10,16 @@
910

1011
def main():
1112
gdf = gpd.read_file("ne_10m_admin_0_countries.zip", engine="pyogrio")
13+
14+
# Downcast because the NE_ID defaulted to int64, which the default arrow js json
15+
# serializer doesn't know how to handle
16+
for col in gdf.select_dtypes(np.signedinteger).columns:
17+
gdf[col] = pd.to_numeric(gdf[col], downcast="signed")
18+
for col in gdf.select_dtypes(np.unsignedinteger).columns:
19+
gdf[col] = pd.to_numeric(gdf[col], downcast="unsigned")
20+
for col in gdf.select_dtypes(np.floating).columns:
21+
gdf[col] = pd.to_numeric(gdf[col], downcast="float")
22+
1223
table = geopandas_to_geoarrow(gdf)
1324

1425
log_pop_est = np.where(gdf["POP_EST"] == 0, 0, np.log10(gdf["POP_EST"]))

examples/point/app.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from "react";
22
import { createRoot } from "react-dom/client";
33
import { StaticMap, MapContext, NavigationControl } from "react-map-gl";
4-
import DeckGL, { Layer } from "deck.gl/typed";
4+
import DeckGL, { Layer, PickingInfo } from "deck.gl/typed";
55
import { GeoArrowScatterplotLayer } from "@geoarrow/deck.gl-layers";
66
import * as arrow from "apache-arrow";
77

@@ -25,12 +25,9 @@ const NAV_CONTROL_STYLE = {
2525
};
2626

2727
function Root() {
28-
const onClick = (info) => {
28+
const onClick = (info: PickingInfo) => {
2929
if (info.object) {
30-
// eslint-disable-next-line
31-
alert(
32-
`${info.object.properties.name} (${info.object.properties.abbrev})`
33-
);
30+
console.log(JSON.stringify(info.object.toJSON()));
3431
}
3532
};
3633

@@ -57,11 +54,11 @@ function Root() {
5754
new GeoArrowScatterplotLayer({
5855
id: "geoarrow-points",
5956
data: table,
60-
// getFillColor: [255, 0, 0],
61-
getFillColor: table.getChild("colors"),
62-
radiusMinPixels: 1,
57+
getFillColor: table.getChild("colors")!,
58+
radiusMinPixels: 1.5,
6359
getPointRadius: 10,
6460
pointRadiusMinPixels: 0.8,
61+
pickable: true,
6562
})
6663
);
6764

@@ -71,6 +68,7 @@ function Root() {
7168
controller={true}
7269
layers={layers}
7370
ContextProvider={MapContext.Provider}
71+
onClick={onClick}
7472
>
7573
<StaticMap mapStyle={MAP_STYLE} />
7674
<NavigationControl style={NAV_CONTROL_STYLE} />

examples/polygon/app.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from "react";
22
import { createRoot } from "react-dom/client";
33
import { StaticMap, MapContext, NavigationControl } from "react-map-gl";
4-
import DeckGL, { Layer } from "deck.gl/typed";
4+
import DeckGL, { Layer, PickingInfo } from "deck.gl/typed";
55
import { GeoArrowSolidPolygonLayer } from "@geoarrow/deck.gl-layers";
66
import * as arrow from "apache-arrow";
77

@@ -24,12 +24,9 @@ const NAV_CONTROL_STYLE = {
2424
};
2525

2626
function Root() {
27-
const onClick = (info) => {
27+
const onClick = (info: PickingInfo) => {
2828
if (info.object) {
29-
// eslint-disable-next-line
30-
alert(
31-
`${info.object.properties.name} (${info.object.properties.abbrev})`
32-
);
29+
console.log(JSON.stringify(info.object.toJSON()));
3330
}
3431
};
3532

@@ -57,6 +54,8 @@ function Root() {
5754
id: "geoarrow-polygons",
5855
data: table,
5956
getFillColor: [0, 100, 60, 160],
57+
pickable: true,
58+
autoHighlight: true,
6059
})
6160
);
6261

@@ -66,6 +65,7 @@ function Root() {
6665
controller={true}
6766
layers={layers}
6867
ContextProvider={MapContext.Provider}
68+
onClick={onClick}
6969
>
7070
<StaticMap mapStyle={MAP_STYLE} />
7171
<NavigationControl style={NAV_CONTROL_STYLE} />

src/path-layer.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
CompositeLayer,
55
CompositeLayerProps,
66
DefaultProps,
7+
GetPickingInfoParams,
78
Layer,
89
LayersList,
10+
PickingInfo,
911
Unit,
1012
} from "@deck.gl/core/typed";
1113
import { PathLayer } from "@deck.gl/layers/typed";
@@ -18,6 +20,7 @@ import {
1820
getMultiLineStringChild,
1921
getMultiLineStringResolvedOffsets,
2022
getPointChild,
23+
invertOffsets,
2124
isLineStringVector,
2225
isMultiLineStringVector,
2326
validateColorVector,
@@ -138,6 +141,37 @@ export class GeoArrowPathLayer<
138141
static defaultProps = defaultProps;
139142
static layerName = "GeoArrowPathLayer";
140143

144+
getPickingInfo({ info, sourceLayer }: GetPickingInfoParams): PickingInfo {
145+
const { data: table } = this.props;
146+
147+
// Geometry index as rendered
148+
let index = info.index;
149+
150+
// if a MultiLineString dataset, map from the rendered index back to the
151+
// feature index
152+
// @ts-expect-error `invertedGeomOffsets` is manually set on layer props
153+
if (sourceLayer.props.invertedGeomOffsets) {
154+
// @ts-expect-error `invertedGeomOffsets` is manually set on layer props
155+
index = sourceLayer.props.invertedGeomOffsets[index];
156+
}
157+
158+
// @ts-expect-error `recordBatchIdx` is manually set on layer props
159+
const recordBatchIdx: number = sourceLayer.props.recordBatchIdx;
160+
const batch = table.batches[recordBatchIdx];
161+
const row = batch.get(index);
162+
163+
// @ts-expect-error hack: using private method to avoid recomputing via
164+
// batch lengths on each iteration
165+
const offsets: number[] = table._offsets;
166+
const currentBatchOffset = offsets[recordBatchIdx];
167+
168+
info.object = row;
169+
// Update index to be _global_ index, not within the specific record batch
170+
index += currentBatchOffset;
171+
info.index = index;
172+
return info;
173+
}
174+
141175
renderLayers(): Layer<{}> | LayersList | null {
142176
const { data: table } = this.props;
143177

@@ -206,6 +240,9 @@ export class GeoArrowPathLayer<
206240
const flatCoordinateArray = coordData.values;
207241

208242
const props: PathLayerProps = {
243+
// used for picking purposes
244+
recordBatchIdx,
245+
209246
id: `${this.props.id}-geoarrow-path-${recordBatchIdx}`,
210247
widthUnits: this.props.widthUnits,
211248
widthScale: this.props.widthScale,
@@ -282,6 +319,7 @@ export class GeoArrowPathLayer<
282319
const pointData = getLineStringChild(lineStringData);
283320
const coordData = getPointChild(pointData);
284321

322+
const geomOffsets = multiLineStringData.valueOffsets;
285323
const ringOffsets = lineStringData.valueOffsets;
286324

287325
const nDim = pointData.type.listSize;
@@ -290,6 +328,10 @@ export class GeoArrowPathLayer<
290328
getMultiLineStringResolvedOffsets(multiLineStringData);
291329

292330
const props: PathLayerProps = {
331+
// used for picking purposes
332+
recordBatchIdx,
333+
invertedGeomOffsets: invertOffsets(geomOffsets),
334+
293335
id: `${this.props.id}-geoarrow-path-${recordBatchIdx}`,
294336
widthUnits: this.props.widthUnits,
295337
widthScale: this.props.widthScale,

src/scatterplot-layer.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
CompositeLayer,
55
CompositeLayerProps,
66
DefaultProps,
7+
GetPickingInfoParams,
78
Layer,
89
LayersList,
10+
PickingInfo,
911
Unit,
1012
} from "@deck.gl/core/typed";
1113
import { ScatterplotLayer } from "@deck.gl/layers/typed";
@@ -16,6 +18,7 @@ import {
1618
getGeometryVector,
1719
getMultiPointChild,
1820
getPointChild,
21+
invertOffsets,
1922
isMultiPointVector,
2023
isPointVector,
2124
validateColorVector,
@@ -168,6 +171,37 @@ export class GeoArrowScatterplotLayer<
168171
static defaultProps = defaultProps;
169172
static layerName = "GeoArrowScatterplotLayer";
170173

174+
getPickingInfo({ info, sourceLayer }: GetPickingInfoParams): PickingInfo {
175+
const { data: table } = this.props;
176+
177+
// Geometry index as rendered
178+
let index = info.index;
179+
180+
// if a MultiPoint dataset, map from the rendered index back to the feature
181+
// index
182+
// @ts-expect-error `invertedGeomOffsets` is manually set on layer props
183+
if (sourceLayer.props.invertedGeomOffsets) {
184+
// @ts-expect-error `invertedGeomOffsets` is manually set on layer props
185+
index = sourceLayer.props.invertedGeomOffsets[index];
186+
}
187+
188+
// @ts-expect-error `recordBatchIdx` is manually set on layer props
189+
const recordBatchIdx: number = sourceLayer.props.recordBatchIdx;
190+
const batch = table.batches[recordBatchIdx];
191+
const row = batch.get(index);
192+
193+
// @ts-expect-error hack: using private method to avoid recomputing via
194+
// batch lengths on each iteration
195+
const offsets: number[] = table._offsets;
196+
const currentBatchOffset = offsets[recordBatchIdx];
197+
198+
info.object = row;
199+
// Update index to be _global_ index, not within the specific record batch
200+
index += currentBatchOffset;
201+
info.index = index;
202+
return info;
203+
}
204+
171205
renderLayers(): Layer<{}> | LayersList | null {
172206
const { data: table } = this.props;
173207

@@ -235,6 +269,9 @@ export class GeoArrowScatterplotLayer<
235269
const flatCoordinateArray = geometryData.children[0].values;
236270

237271
const props: ScatterplotLayerProps = {
272+
// @ts-expect-error used for picking purposes
273+
recordBatchIdx,
274+
238275
id: `${this.props.id}-geoarrow-scatterplot-${recordBatchIdx}`,
239276
radiusUnits: this.props.radiusUnits,
240277
radiusScale: this.props.radiusScale,
@@ -335,6 +372,10 @@ export class GeoArrowScatterplotLayer<
335372
const flatCoordinateArray = flatCoordsData.values;
336373

337374
const props: ScatterplotLayerProps = {
375+
// @ts-expect-error used for picking purposes
376+
recordBatchIdx,
377+
invertedGeomOffsets: invertOffsets(geomOffsets),
378+
338379
id: `${this.props.id}-geoarrow-scatterplot-${recordBatchIdx}`,
339380
radiusUnits: this.props.radiusUnits,
340381
radiusScale: this.props.radiusScale,

0 commit comments

Comments
 (0)