Skip to content
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

WIP: Select by bounding box #417

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open

WIP: Select by bounding box #417

wants to merge 19 commits into from

Conversation

kylebarron
Copy link
Member

Building on top of #412 with help from @batpad

Change list

  • Draw bbox in deck instead of separate layer

Notes

  • Create mode for when picking bbox should be active
  • Send data back to Python
  • Make multiple draw calls, one per set of layer ids, then set on layer state
  • Maybe delegate to sub models directly. Add a pick callback directly onto the models, and then iterate over each model, calling pick which picks and then assigns directly onto that model's state?
  • In testing, we seemed to get only 35k rows back when we got 143k rows checking the same bbox in geopandas with the input gdf. This is possibly related to x,y,width, height passed in to pickObjects? Fixed with latest commit
  • Note about crossing international dateline
  • Ensure we're not creating more than 256 layers.

@batpad
Copy link
Member

batpad commented Mar 14, 2024

@kylebarron I added a "selectionMode" and some ugly UI for a user to start selection mode and clear selection. As a bonus, when the user has a selection, it also displays the number of objects selected.

There's a few things I feel a bit unsure about the logic, and also how the code is structured. We can look tomorrow - but I think this does roughly what we'd want.

@kylebarron
Copy link
Member Author

It probably makes sense to keep the bounding box state on the widget model so that Python has access to the same bounding box. So e.g. connecting to Mosaic you could pass the bounding box for them to do the same query in duckdb

@batpad
Copy link
Member

batpad commented Mar 23, 2024

There would still a few things to be done here to finish, here's what it looks like so far:

BboxScreengrab.mp4

@kylebarron - Although, I'm wondering if this can be restructured to not be so tightly coupled to the main rendering code in index.tsx - it would be almost nice to be able to build something like this as a separate widget, not inside the core lonboard code-base. I think that's probably doable - it might be nice to talk through next week.

cc @hanbyul-here

src/index.tsx Outdated
@@ -91,7 +98,7 @@ function App() {
});

const [mapId] = useState(uuidv4());

let mapRef = useRef<DeckGLRef>(null);

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

Copy link
Member Author

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...

src/index.tsx Outdated
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Show the selection start point (note this does not show the proposed bounding box, but could be done)
// Show the selection start point

Since a hover box is now shown

@batpad
Copy link
Member

batpad commented Mar 26, 2024

@kylebarron for now, this just sets map.selected_bounds after the user selects the bbox on the python side.

As an approach, I think I prefer this than trying to send indexes for all the objects down from JS - can we not implement this as a method in python and just use the bbox sent by the frontend? So a map.get_selected_objects() or so that would read the bounds from selected_bounds but then just do that query in Python to get the selected objects?

I feel like this is almost ready for review -- not sure what else we should do here.

@hanbyul-here would you be able to give this a look and point out anything obvious that you feel should be changed?

There's possibly a few things that could maybe be done nicer?

Also this was my first time really writing TypeScript so if there's anything there that seems like it's not proper..

One thing to note:

  • Right now, "setting" map.selected_bounds in python will not do anything on the JS side - if we want a way for a user to programmatically set the selection in Python, I would rather do this by an explicit method on map like "set_selected_bounds", and treat it similar to how the flyTo operation is being handled currently.

@kylebarron
Copy link
Member Author

As an approach, I think I prefer this than trying to send indexes for all the objects down from JS - can we not implement this as a method in python and just use the bbox sent by the frontend? So a map.get_selected_objects() or so that would read the bounds from selected_bounds but then just do that query in Python to get the selected objects?

I think there are pros and cons to each manner of implementation, but I think that there are some cases where maintaining the indexes on the model itself is valuable. It might also enable more client side use cases where you're syncing the data and the selection with another external widget through JS, avoiding the Python kernel.

And not needing to do another search query on the Python side might be nice for performance as well. And if we're sending up to like 100k uint32s encoded in Parquet to the server, that's a pretty small file. Probably smaller than 50kb?

@kylebarron
Copy link
Member Author

Note that a user's selection on the screen is in pixels, and does not always align with an axis-aligned bounding box. E.g. if the map is tilted, the pixel bounding box will accurately choose items within that view because deck.gl uses the actual scene rendering for picking. But that won't be able to be picked from Python because you'd have to serialized the entire view and camera state and implement 3d picking from scratch.

@batpad
Copy link
Member

batpad commented Mar 30, 2024

@kylebarron based on our conversation, I've made it now so that each layer has a selected_bounds property and when a user selects a bbox, it will set that property on each layer on the map.

lonboard/_layer.py Outdated Show resolved Hide resolved
@kylebarron kylebarron marked this pull request as ready for review April 2, 2024 19:30
@kylebarron
Copy link
Member Author

I think this is getting closer; I would like to see if we can refactor the meat of the selection into a separate function so it doesn't bloat up the App component.

@batpad
Copy link
Member

batpad commented Apr 9, 2024

cc @LanesGood

@LanesGood
Copy link
Member

@batpad in response to your request for a design review of the bbox selection instructions/icon:

  1. is it possible to use the ipyleaflet draw controls? https://ipyleaflet.readthedocs.io/en/latest/controls/draw_control.html
  2. If not and this is more of a need to implement custom icons, I'd suggest going with something very recognizable, like the geojson.io controls (which are also used in ipyleaflet):
    image
    We could implement this as a button simply in the selection div you've added, and I would propose adding a separate "x points selected" text interface element.

@kylebarron
Copy link
Member Author

  1. is it possible to use the ipyleaflet draw controls? ipyleaflet.readthedocs.io/en/latest/controls/draw_control.html

I don't think there's any way to use the ipyleaflet draw controls because we're not using leaflet under the hood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants