-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Select by bounding box #417
Merged
Merged
Changes from 9 commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
e8e4da4
prototype showing selection drawing on top of the map
isaacbrodsky 7db62b2
cleanup
isaacbrodsky 48d4d33
restyle
isaacbrodsky 22f79f7
[wip] select bbox on map
batpad add1a76
add pickobjects to pick selected objects
batpad 3244bcd
fix width calculation bug so that it picks objects correctly
batpad d726e75
add some (ugly) UI for user to trigger starting a selection and clear…
batpad 745e989
merge commit + slightly ugly code to get hover interaction working fo…
batpad 82ce22b
lint
batpad 0515769
send selected_bounds back to python after selection
batpad 813abc5
lintfix
batpad 0459aaa
convert lets that should be consts to consts
batpad 5310e8e
remove point layer when initially clicking since we now show box on h…
batpad 1ab3413
cleanups: remove unneeded point layer, move getPolygon to util, remov…
batpad 39dda64
minor, remove unused var
batpad 2245241
add selected bbox to each layer as well as the map
batpad 5b3c8b6
Merge branch 'main' into bbox-map-select
kylebarron b13c8d1
Use tuple on Python side
kylebarron aa0b556
Rename getPolygon to makePolygon
kylebarron c4080fa
Fix bbox map select feature (#567)
vgeorge f6ba0fc
Merge branch 'main' into bbox-map-select
vgeorge 011fd34
Replace reducer with XState
vgeorge 17ea88f
Fix type definitions for mouse events
vgeorge 1a22f5a
Apply selected_bounds to layers
vgeorge 0139e83
Replace rate limit with throttle
vgeorge 560868b
Define default value for environment variable
vgeorge 0ea37d8
Clear unused elements
vgeorge 91ed1ba
Add property selected_bounds to BaseArrowLayer
vgeorge b709c99
Setup NextUI component library and use icon on toolbar
vgeorge f17c416
Rename files to avoid naming conflict with existing state.ts file
vgeorge b83a91d
Allow cancel/clear bbox
vgeorge a24be14
Allow cancelling after first point was selected
vgeorge 90e4cfa
Merge branch 'main' into bbox-map-select
vgeorge cc67f65
lint fixes
vgeorge 0a4306a
Remove selected_bounds from BaseArrowLayer as it is already available…
vgeorge 127ac5e
Remove unnecessary state
vgeorge fd7dbc3
Update bbox bounds also when it is null
vgeorge 9724d6a
Clean up unused action
vgeorge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 | ||||
---|---|---|---|---|---|---|
@@ -1,9 +1,11 @@ | ||||||
import * as React from "react"; | ||||||
import { useState, useEffect } from "react"; | ||||||
import { useState, useEffect, useMemo, useRef } from "react"; | ||||||
import { createRender, useModelState, useModel } from "@anywidget/react"; | ||||||
import type { Initialize, Render } from "@anywidget/types"; | ||||||
import Map from "react-map-gl/maplibre"; | ||||||
import DeckGL from "@deck.gl/react/typed"; | ||||||
import DeckGL, { DeckGLRef } from "@deck.gl/react/typed"; | ||||||
import { PolygonLayer, ScatterplotLayer } from "@deck.gl/layers/typed"; | ||||||
import type { PickingInfo } from "@deck.gl/core/typed"; | ||||||
import { MapViewState, type Layer } from "@deck.gl/core/typed"; | ||||||
import { BaseLayerModel, initializeLayer } from "./model/index.js"; | ||||||
import type { WidgetModel } from "@jupyter-widgets/base"; | ||||||
|
@@ -71,6 +73,11 @@ function App() { | |||||
let [mapHeight] = useModelState<number>("_height"); | ||||||
let [showTooltip] = useModelState<boolean>("show_tooltip"); | ||||||
let [pickingRadius] = useModelState<number>("picking_radius"); | ||||||
let [selectionMode, setSelectionMode] = useState<boolean | string>(false); | ||||||
let [selectionObjectCount, setSelectionObjectCount] = useState< | ||||||
boolean | number | ||||||
>(false); | ||||||
let [hoverBBoxLayer, setHoverBBoxLayer] = useState<any>(false); | ||||||
let [useDevicePixels] = useModelState<number | boolean>("use_device_pixels"); | ||||||
let [parameters] = useModelState<object>("parameters"); | ||||||
|
||||||
|
@@ -91,7 +98,7 @@ function App() { | |||||
}); | ||||||
|
||||||
const [mapId] = useState(uuidv4()); | ||||||
|
||||||
let mapRef = useRef<DeckGLRef>(null); | ||||||
let [subModelState, setSubModelState] = useState< | ||||||
Record<string, BaseLayerModel> | ||||||
>({}); | ||||||
|
@@ -144,8 +151,153 @@ function App() { | |||||
} | ||||||
}, []); | ||||||
|
||||||
// State is an array of: [screen coordinates, geographic coordinates] | ||||||
const [selectionStart, setSelectionStart] = useState< | ||||||
undefined | [[number, number], number[] | undefined] | ||||||
>(); | ||||||
const [selectionEnd, setSelectionEnd] = useState< | ||||||
undefined | [[number, number], number[] | undefined] | ||||||
>(); | ||||||
|
||||||
function onSelectClick(e: React.SyntheticEvent) { | ||||||
e.stopPropagation(); | ||||||
if (!selectionMode) { | ||||||
setSelectionMode("selecting"); | ||||||
} | ||||||
if (selectionMode === "selected") { | ||||||
setSelectionMode(false); | ||||||
setSelectionStart(undefined); | ||||||
setSelectionEnd(undefined); | ||||||
} | ||||||
} | ||||||
|
||||||
function onMapClick(info: PickingInfo) { | ||||||
if (!selectionMode || selectionMode === "selected") return; | ||||||
console.log("onclick info", info); | ||||||
if (selectionEnd !== undefined) { | ||||||
setSelectionStart(undefined); | ||||||
setSelectionEnd(undefined); | ||||||
} else if (selectionStart !== undefined && selectionEnd === undefined) { | ||||||
setSelectionEnd([[info.x, info.y], info.coordinate]); | ||||||
const width = Math.abs(info.x - selectionStart[0][0]); | ||||||
const height = Math.abs(info.y - selectionStart[0][1]); | ||||||
const left = Math.min(selectionStart[0][0], info.x); | ||||||
const top = Math.min(selectionStart[0][1], info.y); | ||||||
const selectedObjects = mapRef.current?.pickObjects({ | ||||||
x: left, | ||||||
y: top, | ||||||
width, | ||||||
height, | ||||||
}); | ||||||
setSelectionMode("selected"); | ||||||
setHoverBBoxLayer(false); | ||||||
console.log("selected on map", selectedObjects); | ||||||
setSelectionObjectCount(selectedObjects ? selectedObjects.length : 0); | ||||||
} else { | ||||||
setSelectionStart([[info.x, info.y], info.coordinate]); | ||||||
setSelectionEnd(undefined); | ||||||
} | ||||||
} | ||||||
|
||||||
function onMapHover(hoverInfo: PickingInfo) { | ||||||
if (selectionMode !== "selecting") return; | ||||||
const hoverCoords = hoverInfo.coordinate; | ||||||
if (selectionStart && hoverCoords) { | ||||||
const pt1 = selectionStart[1]; | ||||||
const pt2 = hoverCoords; | ||||||
if (!pt1 || !pt2) return; | ||||||
const data = [ | ||||||
{ | ||||||
polygon: [pt1, [pt1[0], pt2[1]], pt2, [pt2[0], pt1[1]], pt1], | ||||||
}, | ||||||
]; | ||||||
const bboxLayer = new PolygonLayer({ | ||||||
id: "selection-layer", | ||||||
data, | ||||||
filled: true, | ||||||
getFillColor: [0, 0, 0, 50], | ||||||
stroked: true, | ||||||
getLineWidth: 2, | ||||||
lineWidthUnits: "pixels", | ||||||
getPolygon: (d) => d.polygon, | ||||||
}); | ||||||
console.log(bboxLayer); | ||||||
setHoverBBoxLayer(bboxLayer); | ||||||
} | ||||||
return; | ||||||
} | ||||||
|
||||||
const selectionIndicator = useMemo(() => { | ||||||
if (!selectionMode) return undefined; | ||||||
if (selectionStart && selectionEnd) { | ||||||
console.log(`Map coords: ${selectionStart[1]} ${selectionEnd[1]}`); | ||||||
const pt1 = selectionStart[1]; | ||||||
const pt2 = selectionEnd[1]; | ||||||
if (!pt1 || !pt2) return undefined; | ||||||
const data = [ | ||||||
{ | ||||||
polygon: [pt1, [pt1[0], pt2[1]], pt2, [pt2[0], pt1[1]], pt1], | ||||||
}, | ||||||
]; | ||||||
console.log("selection data", data); | ||||||
return new PolygonLayer({ | ||||||
id: "selection-layer", | ||||||
data, | ||||||
filled: true, | ||||||
getFillColor: [0, 0, 0, 30], | ||||||
stroked: true, | ||||||
getLineWidth: 2, | ||||||
lineWidthUnits: "pixels", | ||||||
getPolygon: (d) => d.polygon, | ||||||
}); | ||||||
} else if (selectionStart) { | ||||||
// Show the selection start point (note this does not show the proposed bounding box, but could be done) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since a hover box is now shown |
||||||
return new ScatterplotLayer({ | ||||||
id: "select-point-layer", | ||||||
data: [ | ||||||
{ | ||||||
coordinates: selectionStart[1], | ||||||
}, | ||||||
], | ||||||
getRadius: 2, | ||||||
radiusUnits: "pixels", | ||||||
getPosition: (d) => d.coordinates, | ||||||
}); | ||||||
} else { | ||||||
return undefined; | ||||||
} | ||||||
}, [selectionStart, selectionEnd, selectionMode]); | ||||||
|
||||||
if (selectionIndicator) { | ||||||
layers.push(selectionIndicator); | ||||||
} | ||||||
|
||||||
if (hoverBBoxLayer) { | ||||||
layers.push(hoverBBoxLayer); | ||||||
} | ||||||
|
||||||
return ( | ||||||
<div id={`map-${mapId}`} style={{ height: mapHeight || "100%" }}> | ||||||
<div id={`map-${mapId}`} style={{ height: "100%" }}> | ||||||
<div | ||||||
style={{ | ||||||
position: "absolute", | ||||||
top: "2px", | ||||||
right: "2px", | ||||||
backgroundColor: "#fff", | ||||||
padding: "2px", | ||||||
zIndex: "1000", | ||||||
height: "12px", | ||||||
}} | ||||||
onClick={onSelectClick} | ||||||
> | ||||||
{!selectionMode ? "Click to start selecting" : ""} | ||||||
{selectionMode === "selecting" | ||||||
? "Click two points on map to draw bounding box" | ||||||
: ""} | ||||||
{selectionMode === "selected" | ||||||
? `${selectionObjectCount} objects selected. Click to Unselect.` | ||||||
: ""} | ||||||
</div> | ||||||
<DeckGL | ||||||
initialViewState={ | ||||||
["longitude", "latitude", "zoom"].every((key) => | ||||||
|
@@ -159,6 +311,9 @@ function App() { | |||||
// @ts-expect-error | ||||||
getTooltip={showTooltip && getTooltip} | ||||||
pickingRadius={pickingRadius} | ||||||
onClick={onMapClick} | ||||||
onHover={onMapHover} | ||||||
ref={mapRef} | ||||||
useDevicePixels={isDefined(useDevicePixels) ? useDevicePixels : true} | ||||||
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops | ||||||
_typedArrayManagerProps={{ | ||||||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this and the useState above can all be const
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is me writing too much Rust and always using
let
...