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

Clicked point to Lonboard map #671

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

Conversation

ATL2001
Copy link
Contributor

@ATL2001 ATL2001 commented Oct 5, 2024

This PR adds a new property to the Lonboard map that holds a tuple for the coordinate where the user last clicked on the map. It is not implemented as an .on_click(), as was discussed, but I think it accomplishes the same functionality (and may actually have more utility this way). hopefully this is an acceptable pattern, but if not, let me know and I'll see what I can do.

@kylebarron
Copy link
Member

I think the main question is whether we want to have this as a property or as an event.

In general, I think the right way to think about the traitlets is as serializing state. The data state should be the same on each side, so it makes sense for all the data properties to be traitlets. But stuff like the clicked point potentially should be an event handler instead.

@ATL2001
Copy link
Contributor Author

ATL2001 commented Oct 6, 2024

yeah, that's fair (and what I was originally going for). I think I got excited to get the information from the javascript side into somewhere I could access it from the python side and ran with it :). I'll poke around some more when I get some more free time to see if I can wire it up as a legit "on_click" event. With me being totally new to this sort of work, if you happen to have any pointers where to read anything about doing something like this, I'm all ears, my random searching the internet hasn't been as productive as I'd like

@kylebarron
Copy link
Member

see if I can wire it up as a legit "on_click" event

You can use custom messages. See https://ipywidgets.readthedocs.io/en/8.1.5/examples/Widget%20Events.html

You can use model.send from the JS side, like so https://github.com/developmentseed/lonboard/pull/413/files#diff-828f90355c3084df59b9704cc4339f114728cf2f2bfc2e9e977d7f96508e7525R48-R52

Then register an on_message handler on the Python side: https://github.com/developmentseed/lonboard/pull/413/files#diff-ab269100f27648760a02f3304913cbb24dbb1b8b2a7a3dc8b514aac5c15ff34dR543

And we'd probably have a wrapper to ensure that only messages with an ID of something like on-click get passed to the function the user passes in:
https://github.com/developmentseed/lonboard/pull/413/files#diff-ab269100f27648760a02f3304913cbb24dbb1b8b2a7a3dc8b514aac5c15ff34dR525

@ATL2001
Copy link
Contributor Author

ATL2001 commented Oct 7, 2024

Thanks Kyle, that helped a lot! I appear to have it all working on my end but need to get to work. I should be able to get it cleaned up and checked in tonight though!

@ATL2001
Copy link
Contributor Author

ATL2001 commented Oct 7, 2024

ok, all checked in, let me know what you think!

src/index.tsx Outdated
Comment on lines 183 to 189
while (x < 180) {
x += 360;
}
while (x > 180) {
x -= 360;
}
info.coordinate[0] = x;
Copy link
Member

Choose a reason for hiding this comment

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

Do you know how often this happens? This kind of coordinate shuffling won't work if we support alternate coordinate systems

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only time this happens is when the user is zoomed out and can see the whole earth wrapped around, and then when they click on the far right side of the map the x coordinate can exceed 180 (and likewise for the left side, you can end up less than -180). if you've got plans to support other coordinate systems, we should certainly exclude this and leave that sort of logic for the to the person writing the callback to implement.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I think for now let's remove this coordinate shuffling but document that in some cases the values can be outside the expected range.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a note to the docstring of on_click function indicating the X/Longitude may be less than or greater than expected, with an example of what will happen if the map is wrapped around and the user clicks on the left or the right part of the earth (hopefully it makes sense, I'm very open to any re-wording/clarification)

Copy link
Member

@kylebarron kylebarron left a comment

Choose a reason for hiding this comment

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

Ideally we would only send back the onClick event if a callback has been registered on the Python side. That means we wouldn't have any performance impact in sending messages for the 99% of the time that a user doesn't have a Python callback registered.

That would mean we'd serialize a trait

So perhaps we should have on_click_handlers as a trait on BaseLayer, which is a tuple holding function callbacks. Then this gets synced to the frontend with custom serialization as a boolean that's just len(traits) > 0. Then if the frontend sees true, it sends the on-click messages back to Python.

And Python would have an observe method that watches the on_click_handlers and updates the global callback registry with those.

What do you think?

lonboard/_map.py Outdated
Comment on lines 107 to 109
if msg.get("kind") != "on-click":
return
self._click_handlers(tuple(msg.get("coordinate")))
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather flip these:

Suggested change
if msg.get("kind") != "on-click":
return
self._click_handlers(tuple(msg.get("coordinate")))
if msg.get("kind") != "on-click":
return self._click_handlers(tuple(msg.get("coordinate")))

That makes it easier in the future to add other kind of message callbacks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, I agree that it would be better to only send the message if there is a callback registered.

I'm not following you on the

on_click_handlers as a trait on BaseLayer

why would we put it on the BaseLayer, not keep it on the map?

Copy link
Member

@kylebarron kylebarron left a comment

Choose a reason for hiding this comment

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

Ideally we would only send back the onClick event if a callback has been registered on the Python side. That means we wouldn't have any performance impact in sending messages for the 99% of the time that a user doesn't have a Python callback registered.

That would mean we'd serialize a trait

So perhaps we should have on_click_handlers as a trait on BaseLayer, which is a tuple holding function callbacks. Then this gets synced to the frontend with custom serialization as a boolean that's just len(traits) > 0. Then if the frontend sees true, it sends the on-click messages back to Python.

And Python would have an observe method that watches the on_click_handlers and updates the global callback registry with those.

What do you think?

…ait for the front end to use so unnecessary messages arent sent if there are no python callbacks registered, and flip floppped the guard clause on the _handle_anywidget_dispatch
@kylebarron
Copy link
Member

I'm not following you on the

on_click_handlers as a trait on BaseLayer

why would we put it on the BaseLayer, not keep it on the map?

Sorry I think I meant to write Map. The main comment was that it should be a dynamic trait, so that we can see from the frontend whether callbacks are registered, and thus whether events should be sent back to Python.

@ATL2001
Copy link
Contributor Author

ATL2001 commented Nov 5, 2024

I'm not following you on the

on_click_handlers as a trait on BaseLayer

why would we put it on the BaseLayer, not keep it on the map?

Sorry I think I meant to write Map. The main comment was that it should be a dynamic trait, so that we can see from the frontend whether callbacks are registered, and thus whether events should be sent back to Python.

excellent, that's what I've got in there now. when a new callback is registered/removed from python in the on_click(), instead of just registering the callback function, it will also populate a new boolean "_has_click_handlers" trait that the javascript side uses to decide if it needs to send the coordinate back to python

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.

2 participants