Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ole108 opened this issue Jan 27, 2017 · 7 comments
Open
Assignees
Labels

Comments

@ole108
Copy link

ole108 commented Jan 27, 2017

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

@flopp
Copy link
Owner

flopp commented Jan 27, 2017

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.

@flopp flopp self-assigned this Jan 27, 2017
@flopp flopp added the bug label Jan 27, 2017
@flopp
Copy link
Owner

flopp commented Jan 28, 2017

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?

@Noki
Copy link

Noki commented Jun 6, 2017

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

@flopp
Copy link
Owner

flopp commented Jun 25, 2017

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.

@Noki
Copy link

Noki commented Jun 26, 2017

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

@flopp
Copy link
Owner

flopp commented Jun 27, 2017

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

@Noki
Copy link

Noki commented Jun 29, 2017

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
Labels
Projects
None yet
Development

No branches or pull requests

3 participants