go-staticmaps
go-staticmaps copied to clipboard
Rendering at/beyond the edges of the latitude/longitude coordinate system
Hi!
We played around with go-staticmaps today and tried to render maps for all countries of the world. By doing so we ran into some problems.
It seems that for some bounding boxes the implementation is too naive to produce a map. Especially we need a way to tell the API which way round the world the map should be (e.g. a bounding box is always upper left first and then lower right).
In addition we had some issue with the copyright notice in the images, when generating small images. There was simply not enough space to properly display them. It might be a good idea to scale the font size in such a case. It would also be great if it would be possible to disable the copyright notice completely. If there is a central copyright notice on our website than it's a redundant information anyway.
Here is the code we use to reproduce the problems including 3 artificial 'countries' that show the problems as extreme cases:
package main
import (
"image/color"
"log"
sm "github.com/flopp/go-staticmaps"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
type country struct {
id string
lat1 float64
lng1 float64
lat2 float64
lng2 float64
}
var countries = []country{
country{"AQ", -60.5155, -180, -89.9999, 180}, // A lot of grey (and almost the whole world)
country{"CA", 83.1106, -141, 41.676, -52.6363}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"FJ", -12.4801, 177.129, -20.676, -178.424}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"GL", 83.6274, -73.042, 59.7774, -11.3123}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"KI", 4.71957, 169.556, -11.437, -150.215}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"NZ", -34.3897, 166.715, -47.286, -180}, // Only partially rendered
country{"RU", 81.8574, 19.25, 41.1889, -169.05}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"TV", -5.64197, 176.065, -10.8012, 179.863}, // Only partially rendered
country{"UM", 28.2198, -177.392, -0.389006, 166.655}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"Test1", -90.0, 25.0, 90.0, 28.0}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
country{"Test2", 25.0, -180.0, 28.0, 180.0}, // Half the image is grey and only 1 Marker is shown
country{"Test3", 25.0, 170.0, 28.0, -170.0}, // panic: runtime error: index out of range (at tile_fetcher.go:45)
}
func main() {
for _, c := range countries {
log.Printf("INFO: Rendering country '%s'.\n", c.id)
ctx := sm.NewContext()
ctx.SetSize(400, 300)
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(c.lat1, c.lng1), color.RGBA{0xff, 0, 0, 0xff}, 16.0))
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(c.lat2, c.lng2), color.RGBA{0, 0xff, 0, 0xff}, 16.0))
bbox := s2.RectFromLatLng(s2.LatLngFromDegrees(c.lat1, c.lng1))
bbox = bbox.AddPoint(s2.LatLngFromDegrees(c.lat2, c.lng2))
ctx.SetBoundingBox(bbox)
ctx.SetTileProvider(sm.GetTileProviders()["cycle"])
img, err := ctx.Render()
if err != nil {
log.Printf("ERROR: Unable to render country '%s': %s\n", c.id, err)
}
if err := gg.SavePNG(c.id+".png", img); err != nil {
log.Printf("ERROR: Unable to save image for country '%s': %s\n", c.id, err)
}
}
}
Best regards,
Ole
Thanks for the bug report.
I'm actually not surprised that there are issues with wrapping coordinates and extreme bboxes. I will look into this in the following days.
I just pushed 79a46b5d2d7718ffdf117a38bb6ea68b74c56ad9, which hopefully fixes the issues with wrapping bounding boxes.
For this to work properly, you have to use a bounding box with left longitude > right longitude; I added a convenience function CreateBBox(nwlat float64, nwlng float64, selat float64, selng float64)
to create such a bounding box.
A side note: go-staticmaps uses the [https://en.wikipedia.org/wiki/Web_Mercator](Web Mercator) projection (Google Maps and other tile based web mapping services use this projection, too). A disadvantage of this projection is, that it cuts off at 85.051129° north and south, so we cannot display anything north of 85° and south of -85°.
Can you please run your tests again, to see if there are stil some issues?
Hi,
we started using staticmaps for our OSM-Explorer website today and discovered that there are still some issues.
One Example: This map still has a transparent bar at the top which makes it look odd when shown with other maps that don't have this problem:
I went over the tests, and updated the comments regarding the remaining issues.
package main
import (
"image/color"
"log"
sm "github.com/flopp/go-staticmaps"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
type country struct {
id string
lat1 float64
lng1 float64
lat2 float64
lng2 float64
}
var countries = []country{
country{"AQ", -60.5155, -180, -89.9999, 180}, // A lot of grey/transparent (and almost the whole world)
country{"CA", 83.1106, -141, 41.676, -52.6363}, // some grey/transparent at the top
country{"FJ", -12.4801, 177.129, -20.676, -178.424}, // ok
country{"GL", 83.6274, -73.042, 59.7774, -11.3123}, // some grey/transparent at the top
country{"KI", 4.71957, 169.556, -11.437, -150.215}, // only one marker shown
country{"NZ", -34.3897, 166.715, -47.286, -180}, // ok
country{"RU", 81.8574, 19.25, 41.1889, -169.05}, // some grey/transparent at the top, only one marker shown
country{"TV", -5.64197, 176.065, -10.8012, 179.863}, // ok
country{"UM", 28.2198, -177.392, -0.389006, 166.655}, // ok
country{"Test1", -90.0, 25.0, 90.0, 28.0}, // skips out of bounds tile but does not end
country{"Test2", 25.0, -180.0, 28.0, 180.0}, // ok
country{"Test3", 25.0, 170.0, 28.0, -170.0}, // ok
}
func main() {
for _, c := range countries {
log.Printf("INFO: Rendering country '%s'.\n", c.id)
ctx := sm.NewContext()
ctx.SetSize(400, 300)
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(c.lat1, c.lng1), color.RGBA{0xff, 0, 0, 0xff}, 16.0))
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(c.lat2, c.lng2), color.RGBA{0, 0xff, 0, 0xff}, 16.0))
bbox := s2.RectFromLatLng(s2.LatLngFromDegrees(c.lat1, c.lng1))
bbox = bbox.AddPoint(s2.LatLngFromDegrees(c.lat2, c.lng2))
ctx.SetBoundingBox(bbox)
ctx.SetTileProvider(sm.GetTileProviders()["opentopomap"])
img, err := ctx.Render()
if err != nil {
log.Printf("ERROR: Unable to render country '%s': %s\n", c.id, err)
}
if err := gg.SavePNG(c.id+".png", img); err != nil {
log.Printf("ERROR: Unable to save image for country '%s': %s\n", c.id, err)
}
}
}
Best regards,
Tobias
Thanks for updating and commenting the test cases.
Do you have any recommendations what to do about transparent areas near +/-90° where there are no tiles? I can only think of repeating the top pixels of the north-most tile row/bottom pixels of the south-most tile row to fill the empty areas.
Hi!
I would not repeat pixels to compensate scaling for the y-axis. I think in general it is ok to have a unfilled / transparent area. It's the same with the Google Static Maps API. What is important is that the alignment gets a bit smarter.
For CA, GL and RU there are enough additional tiles in the south to draw a full map. I would expect them to look like these two:
If you use multiple map images in a website it is usually better to have the transparent / white / unfilled part at the bottom of the image. That way multiple images in one row do not look odd. So the AQ Image is fine for me, however it might be a good idea to allow specifying a background color in addition.
Best regards Tobias
The last commits fix some of the issues:
- b0138b7c96b22c8973663e1220aab6deba0ec133 fixes the latlng to pixel transformation (solves test cases KI and RU) and avoids to draw markers north of 85°/south of -85° (these are the latitude bounds of the used projection) (fixes test case Test1)
- f69176185f70ebcbea1deb00707debc500776e92 adds a background color
Very good!
This leaves us basically with the alignment issue. I updated the tests:
package main
import (
"image/color"
"log"
sm "github.com/flopp/go-staticmaps"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
type country struct {
id string
lat1 float64
lng1 float64
lat2 float64
lng2 float64
width int
height int
}
var countries = []country{
// name, lat1, lng1, lat2, lng2, image-width, image-height
country{"AQ", -60.5155, -180, -89.9999, 180, 400, 300}, // alignment issues (should show more tiles in the north)
country{"CA", 83.1106, -141, 41.676, -52.6363, 400, 300}, // alignment issues (map should cover whole image)
country{"FJ", -12.4801, 177.129, -20.676, -178.424, 400, 300}, // ok
country{"GL", 83.6274, -73.042, 59.7774, -11.3123, 400, 300}, // alignment issues (map should cover whole image)
country{"KI", 4.71957, 169.556, -11.437, -150.215, 400, 300}, // ok
country{"NZ", -34.3897, 166.715, -47.286, -180, 400, 300}, // ok
country{"RU", 81.8574, 19.25, 41.1889, -169.05, 400, 300}, // alignment issues (map should cover whole image)
country{"TR", 35.8076803335914, 25.6212890260128, 42.2969999781262, 44.8176637355973, 500, 312}, // ok
country{"TV", -5.64197, 176.065, -10.8012, 179.863, 400, 300}, // ok
country{"UM", 28.2198, -177.392, -0.389006, 166.655, 400, 300}, // ok
country{"Test1", -90.0, 25.0, 90.0, 28.0, 400, 300}, // alignment issues (map should start in upper left corner / and or cover the whole image)
country{"Test2", 25.0, -180.0, 28.0, 180.0, 400, 300}, // ok
country{"Test3", 25.0, 170.0, 28.0, -170.0, 400, 300}, // ok
}
func main() {
for _, c := range countries {
log.Printf("INFO: Rendering country '%s'.\n", c.id)
ctx := sm.NewContext()
ctx.SetSize(c.width, c.height)
ctx.SetBackground(color.RGBA{0xff, 0, 0, 0xff})
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(c.lat1, c.lng1), color.RGBA{0, 0, 0xff, 0xff}, 16.0))
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(c.lat2, c.lng2), color.RGBA{0, 0xff, 0, 0xff}, 16.0))
bbox := s2.RectFromLatLng(s2.LatLngFromDegrees(c.lat1, c.lng1))
bbox = bbox.AddPoint(s2.LatLngFromDegrees(c.lat2, c.lng2))
ctx.SetBoundingBox(bbox)
ctx.SetTileProvider(sm.GetTileProviders()["osm"])
img, err := ctx.Render()
if err != nil {
log.Printf("ERROR: Unable to render country '%s': %s\n", c.id, err)
}
if err := gg.SavePNG(c.id+".png", img); err != nil {
log.Printf("ERROR: Unable to save image for country '%s': %s\n", c.id, err)
}
}
}
Best regards Tobias