Open simonw opened 3 years ago
I'd rather not add Pillow as a required dependency, but this feature could treat it as optional - you need to install Pillow for this feature to work but without Pillow other functionality remains.
Initial URL design:
/-/tiles-static?center=latitude,longitude
&zoom=zoom
&size=WIDTHxHEIGHT
Google let you pass a string to center=
which they'll geocode for you - maybe provide a mechanism for optionally configuring a geocoder in the future. For the first version supporting just latitude,longitude
makes sense.
Prior art: https://wiki.openstreetmap.org/wiki/Tile_stitching and https://wiki.openstreetmap.org/wiki/Static_map_images
https://github.com/flopp/py-staticmaps is an impressive Python implementation of this idea. It uses pycairo
which may be a tricky dependency (or maybe it's as easy to install as Pillow, I've not tried yet).
Looking at https://github.com/jperelli/osm-static-maps reminded me that this will need to be able to add attribution to the images too, e.g. http://osm-static-maps.herokuapp.com/?height=200
Stitching tiles together in PIL is pretty straight-forward:
from PIL import Image
import httpx
import io
top = "https://datasette-tiles-demo.datasette.io/-/tiles/basemap/1/0/1.png"
bottom = "https://datasette-tiles-demo.datasette.io/-/tiles/basemap/1/0/0.png"
top_png = httpx.get(top).content
bottom_png = httpx.get(bottom).content
# Create a new blank image 256x512
new = image = Image.new('RGB', (256, 512))
new.paste(top_im, (0, 0, 256, 256))
new.paste(bottom_im, (0, 256, 256, 512))
new.save("/tmp/stitched.png")
Image generated by this:
https://pygeotile.readthedocs.io/en/latest/ can be used to convert lat/lon to tile co-ordinates and back again.
For a point within San Francisco:
point = Point.from_latitude_longitude(37.777, -122.426)
tile = Tile.for_latitude_longitude(37.777, -122.426, 6)
# Tile is now Tile(tms_x=10, tms_y=39, zoom=6)
tile_url = "https://datasette-tiles-demo.datasette.io/-/tiles/basemap/{z}/{x}/{y}.png".format(
z=tile.zoom, x=tile.tms_x, y=tile.tms_y
)
This gives me tile_url
of https://datasette-tiles-demo.datasette.io/-/tiles/basemap/6/10/39.png
The point.pixels(zoom)
method then provides:
point.pixels(6)
# Outputs (2620, 6333)
tile.bounds
# Outputs:
# (Point(latitude=36.5978891330702, longitude=-123.75),
# Point(latitude=40.979898069620155, longitude=-118.125))
tile.bounds[0].pixels(6), tile.bounds[1].pixels(6)
# Outputs ((2560, 6400), (2816, 6144))
Because I find diagrams useful, here's how those "pixel" coordinates work:
I love the Google Maps Static API - https://developers.google.com/maps/documentation/maps-static/overview - both for covering non-JavaScript cases and because it's a much more efficient way to drop a simple map on a page without loading a JavaScript library.
It strikes me that a very simple version of that - where you pass a size, bounding box and maybe even a marker or two - could be supported by this plugin. Loading a few tiles from SQLite, stitching them together into a new image using Pillow and serving the result feels like it might not be a big push.
And then people could serve their own version of the static maps API using their own tiles!
Originally posted by @simonw in https://github.com/simonw/datasette-tiles/issues/14#issuecomment-773440881