Skip to content

Rendering at/beyond the edges of the latitude/longitude coordinate system #19

Open

Description

@ole108

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

Activity

  1. flopp commented on Jan 27, 2017

    @flopp
    Owner

    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.

  2. self-assigned this
    on Jan 27, 2017
  3. flopp commented on Jan 28, 2017

    @flopp
    Owner

    I just pushed 79a46b5, 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?

  4. Noki commented on Jun 6, 2017

    @Noki

    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:

    Canada Map

    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

  5. flopp commented on Jun 25, 2017

    @flopp
    Owner

    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.

  6. Noki commented on Jun 26, 2017

    @Noki

    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:

    CA
    RU

    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

  7. flopp commented on Jun 27, 2017

    @flopp
    Owner

    The last commits fix some of the issues:

    • b0138b7 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)
    • f691761 adds a background color
  8. Noki commented on Jun 29, 2017

    @Noki

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions