Mapview: need current extent of the map display
- Dominant language
- Python
- Stars
- 5.4k
- Forks
- 827
- Avg merge
- 15h 39m
- Merged PRs (30d)
- 40
Description
### What is the problem or limitation you are having?
If you are making geospatial queries to annotate a MapView, you need both the current coordinates of the map (easy enough) but also the extent of the map. This information makes it efficient and simple to make queries to Redis, Mongodb, or PostGIS.
By "extent" I mean the coordinates of the top-left and bottom-right of the displayed rectangle.
### Describe the solution you'd like
Have an `.extent` property of `MapView` that returns the extent as a tuple (left, top, right, bottom). Where `left` and `right` are longitudes and `top` and `bottom` are latitudes.
You can optionally represent the same information as a tuple with the linear width and height, I usually use kilometer but you'd probably want to generalize it to `.extent(units)` where `units` might be kilometers, miles, meters, or feet.
Note that you can easily use haversines to convert between the two of these, and either is fine. PyPi has a good Haversine implementation.
You'd also want an event associated with the MapView object that is triggered when the extent changes, e.g. the map is moved, zoomed, or resized.
If the implementation was _consistently_ oversized by a small amount (like less than five percent) that would be fine for most work -- the MapView object would do the work of clipping things for us. Being undersized would skip data you'd want to display and in the worst case might produce obvious dead spots.
It would be fine to return None or some other kind of undefined value for small values of `.zoom`.
### Describe alternatives you've considered
You can kind of do this already, but it isn't simple, hard to get right, and is kind of ugly.
If you have a periodic task which polls your MapView object and grabs the coordinates of the center, grabs the `zoom` value, and looks at the `layout` object to get the actual current size in CSS pixels you can get there. You need to reference [](https://wiki.openstreetmap.org/wiki/Zoom_levels) to translate CSS pixels to linear distance. And you need to take into account that the Latitude impacts the value you'd use for Longitude and this the overall size of the pixels. It isn't simple and is messy code. Better to hide it in one place than have everyone reinvent the wheel incorrectly.
This also is deeply imperfect as a periodic task as you'll always have some lag.
### Additional context
This code fragment _approximately_ does the job. It does not account for latitude and is at best an approximation:
```
def map_extent(mymap: toga.widgets.mapview.MapView):
"""
Calculates the approximate width and height of the visible area of the given map in
kilometers based on its zoom level, layout dimensions, and a predefined mapping of meters
per CSS pixel for each zoom level. If the zoom level is not found in the predefined mapping,
it defaults to a fixed size of 100 by 100 kilometers.
:param mymap: The MapView widget whose visible extent needs to be calculated.
:type mymap: toga.widgets.mapview.MapView
:return: A tuple containing the width and height of the visible map area in kilometers.
:rtype: tuple[float, float]
"""
m_per_pixel = [
156_543, 78_272, 39_136, 19_568,
9784, 4892, 2446, 1223,
611.496, 305.748, 152.874,
76.437, 38.219, 19.109,
9.555, 4.777, 2.389,
1.194, 0.597, 0.299, 0.149
]
zoom = mymap.zoom
w = mymap.layout.width
h = mymap.layout.height
try:
meters = m_per_pixel[zoom]
w *= meters
h *= meters
w /= 1000
h /= 1000
return w,h
except IndexError:
return 100, 100
```
Contributor guide
Research direction
Start with the toga.widgets.mapview.MapView entry point and inspect how its zoom, layout, coordinates, and existing events are exposed. Define the extent representation and update behavior, then verify that extent changes track map movement, zooming, and resizing without undersizing the visible area.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100