flopp / py-staticmaps

A python module to create static map images with markers, geodesic lines, etc.
MIT License
128 stars 17 forks source link

Improve bounds management #22

Open qlereboursBS opened 2 years ago

qlereboursBS commented 2 years ago

Description

Currently, if you generate a SVG based on bounds, it will contain more than just the bounds you gave, because we have to specify output width and height, and also because zoom level are integers. It would be awesome be able to let the program choose the output size based on the given bounds (or have a minWidth, minHeight instead).

Current behavior:

The code:

import staticmaps
import s2sphere
import sys

context = staticmaps.Context()
# add bounds on east Canada
context.add_bounds(s2sphere.LatLngRect(
    s2sphere.LatLng.from_degrees(43.26120612479979, -79.859619140625),
    s2sphere.LatLng.from_degrees(49.475263243037986, -63.017578125)
))
context.set_tile_provider(staticmaps.tile_provider_OSM)

svg_image = context.render_svg(2000, 2000)
with open("output.svg", "w", encoding="utf-8") as f:
    svg_image.write(f, pretty=True)

Output file looks like this:

image

SVG file available here: https://mega.nz/file/wtdARJwJ#4bXTgySf9vcmBBCnliu3blbkRoLW2hTIEcipWPbs4tg

When given bounds are:

image

Expected behavior

I think that we should be able to call the render_svg method with minWidth and minHeight arguments which would let the program choose a better zoom level, to perfectly fit the bounds. Here is how it could work:

  1. User defines bounds
  2. User defines minWidth and minHeight
  3. The program would determine on it's own which size the output file should be to fit the bounds
  4. It renders the SVG with the viewbox matching the size

I would be ok to work on it, I already tried to do a first version but the mathematics used in the context._determine_zoom function are complicated, I don't know the formulas and I'm not sure to understand the variables. I know that I can return return self._clamp_zoom(zoom) instead of return self._clamp_zoom(zoom - 1) but I don't know how to determine the output size.

flopp commented 2 years ago

That's a valid point. I will look into it.

qlereboursBS commented 2 years ago

Thank you very much. As I said, if you think that you won't have the time to do it, maybe you can guide me (with some links on how the math works to get the zoom, and how we could get the new output dimensions for instance)

lowtower commented 2 years ago

I haven't looked into the code in detail, just read through the text of pull request #19, but I suspect that it tried to solve the same issue. You could check that code.

qlereboursBS commented 2 years ago

Thanks for your comment, it seems that indeed, it tries to solve the same issue, however it's done only for non-svg outputs, it's done by cropping the image using the min and max X and Y of the rendered objects, which (I think) won't work with SVGs

lowtower commented 2 years ago

Hello @flopp and @qlereboursBS,

as far as I understand the code:

frankfurt_newyork tiles

lowtower commented 2 years ago

Is it possible (for all providers) to set an arbitrary tile size? Could it be a solution to calculate the optimum tile size? This is the result with tile size = 200px frankfurt_newyork tiles t200

lowtower commented 2 years ago

Hello @flopp and @qlereboursBS,

I have coded a different approach for svg:

You can check it out at my branch https://github.com/lowtower/py-staticmaps/tree/feature/tighten_to_bounds

Drawbacks up to now:

Questions:

I'll have a look at cairo and pillow as well if this is of interest

frankfurt_newyork

netvandal commented 1 year ago

I'm also interested in the "precision" of the bounds for cairo output in particular. Is difficult to at leas add a flag to know ad the end of the process the "real" bound included in the output image? Thankyou

lowtower commented 1 year ago

Hello @netvandal,

I am afraid, I don't get Your point. What exactly is Your question?

In my example above, the map image is "tightened" to the given boundary - plus the "extra" boundaries for the marker object. This is for all objects - they have a "positioning point" and "extra" boundaries top, right, bottom and left which are added to the regular boundary to make sure the object is fully covered with the map image. If You remove the objects, You should get an image with the "pure" boundaries.

BTW: the afore mentioned branch in my fork doesn't exist any more, but the feature is included in the main branch, not "master".

Cheers, LT