diff --git a/context.go b/context.go index ca96a42..40958d0 100644 --- a/context.go +++ b/context.go @@ -38,10 +38,10 @@ type Context struct { paths []*Path areas []*Area circles []*Circle - overlays []*TileProvider + overlays []TileProvider userAgent string - tileProvider *TileProvider + tileProvider TileProvider overrideAttribution *string } @@ -61,7 +61,7 @@ func NewContext() *Context { } // SetTileProvider sets the TileProvider to be used -func (m *Context) SetTileProvider(t *TileProvider) { +func (m *Context) SetTileProvider(t TileProvider) { m.tileProvider = t } @@ -140,7 +140,7 @@ func (m *Context) ClearCircles() { } // AddOverlay adds an overlay to the Context -func (m *Context) AddOverlay(overlay *TileProvider) { +func (m *Context) AddOverlay(overlay TileProvider) { m.overlays = append(m.overlays, overlay) } @@ -205,7 +205,7 @@ func (m *Context) determineZoom(bounds s2.Rect, center s2.LatLng) int { return 15 } - tileSize := m.tileProvider.TileSize + tileSize := m.tileProvider.TileSize() margin := 4.0 + m.determineExtraMarginPixels() w := (float64(m.width) - 2.0*margin) / float64(tileSize) h := (float64(m.height) - 2.0*margin) / float64(tileSize) @@ -350,7 +350,7 @@ func (m *Context) Render() (image.Image, error) { return nil, err } - tileSize := m.tileProvider.TileSize + tileSize := m.tileProvider.TileSize() trans := newTransformer(m.width, m.height, zoom, center, tileSize) img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight)) gc := gg.NewContextForRGBA(img) @@ -359,7 +359,7 @@ func (m *Context) Render() (image.Image, error) { } // fetch and draw tiles to img - layers := []*TileProvider{m.tileProvider} + layers := []TileProvider{m.tileProvider} if m.overlays != nil { layers = append(layers, m.overlays...) } @@ -390,7 +390,7 @@ func (m *Context) Render() (image.Image, error) { img, image.Point{trans.pCenterX - int(m.width)/2, trans.pCenterY - int(m.height)/2}, draw.Src) - attribution := m.tileProvider.Attribution + attribution := m.tileProvider.Attribution() if m.overrideAttribution != nil { attribution = *m.overrideAttribution } @@ -422,7 +422,7 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) { return nil, s2.Rect{}, err } - tileSize := m.tileProvider.TileSize + tileSize := m.tileProvider.TileSize() trans := newTransformer(m.width, m.height, zoom, center, tileSize) img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight)) gc := gg.NewContextForRGBA(img) @@ -431,7 +431,7 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) { } // fetch and draw tiles to img - layers := []*TileProvider{m.tileProvider} + layers := []TileProvider{m.tileProvider} if m.overlays != nil { layers = append(layers, m.overlays...) } @@ -457,21 +457,21 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) { } // draw attribution - if m.tileProvider.Attribution == "" { + if m.tileProvider.Attribution() == "" { return img, trans.Rect(), nil } - _, textHeight := gc.MeasureString(m.tileProvider.Attribution) + _, textHeight := gc.MeasureString(m.tileProvider.Attribution()) boxHeight := textHeight + 4.0 gc.SetRGBA(0.0, 0.0, 0.0, 0.5) gc.DrawRectangle(0.0, float64(trans.pHeight)-boxHeight, float64(trans.pWidth), boxHeight) gc.Fill() gc.SetRGBA(1.0, 1.0, 1.0, 0.75) - gc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0) + gc.DrawString(m.tileProvider.Attribution(), 4.0, float64(m.height)-4.0) return img, trans.Rect(), nil } -func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *transformer, tileSize int, provider *TileProvider) error { +func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *transformer, tileSize int, provider TileProvider) error { t := NewTileFetcher(provider) if m.userAgent != "" { t.SetUserAgent(m.userAgent) diff --git a/tile_fetcher.go b/tile_fetcher.go index b4c018d..8c8679b 100644 --- a/tile_fetcher.go +++ b/tile_fetcher.go @@ -12,9 +12,9 @@ import ( _ "image/jpeg" // to be able to decode jpegs _ "image/png" // to be able to decode pngs "io" - "io/ioutil" + //"io/ioutil" "log" - "net/http" + //"net/http" "os" "path/filepath" @@ -23,14 +23,14 @@ import ( // TileFetcher downloads map tile images from a TileProvider type TileFetcher struct { - tileProvider *TileProvider + tileProvider TileProvider cacheDir string useCaching bool userAgent string } // NewTileFetcher creates a new Tilefetcher struct -func NewTileFetcher(tileProvider *TileProvider) *TileFetcher { +func NewTileFetcher(tileProvider TileProvider) *TileFetcher { t := new(TileFetcher) t.tileProvider = tileProvider app := appdirs.New("go-staticmaps", "flopp.net", "0.1") @@ -45,15 +45,6 @@ func (t *TileFetcher) SetUserAgent(a string) { t.userAgent = a } -func (t *TileFetcher) url(zoom, x, y int) string { - shard := "" - ss := len(t.tileProvider.Shards) - if len(t.tileProvider.Shards) > 0 { - shard = t.tileProvider.Shards[(x+y)%ss] - } - return t.tileProvider.getURL(shard, zoom, x, y) -} - func (t *TileFetcher) cacheFileName(zoom int, x, y int) string { return fmt.Sprintf("%s/%d/%d/%d", t.cacheDir, zoom, x, y) } @@ -65,6 +56,7 @@ func (t *TileFetcher) ToggleCaching(enabled bool) { // Fetch download (or retrieves from the cache) a tile image for the specified zoom level and tile coordinates func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) { + if t.useCaching { fileName := t.cacheFileName(zoom, x, y) cachedImg, err := t.loadCache(fileName) @@ -73,11 +65,15 @@ func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) { } } - url := t.url(zoom, x, y) - data, err := t.download(url) - if err != nil { - return nil, err - } + data, err := t.tileProvider.FetchTile(zoom, x, y) + + /* + url := t.url(zoom, x, y) + data, err := t.download(url) + if err != nil { + return nil, err + } + */ img, _, err := image.Decode(bytes.NewBuffer(data)) if err != nil { @@ -94,6 +90,17 @@ func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) { return img, nil } +/* +func (t *TileFetcher) url(zoom, x, y int) string { + possible_shards := t.tileProvider.Shards() + shard := "" + ss := len(possible_shards) + if len(possible_shards) > 0 { + shard = possible_shards[(x+y)%ss] + } + return t.tileProvider.URL(shard, zoom, x, y) +} + func (t *TileFetcher) download(url string) ([]byte, error) { req, _ := http.NewRequest("GET", url, nil) req.Header.Set("User-Agent", t.userAgent) @@ -116,6 +123,7 @@ func (t *TileFetcher) download(url string) ([]byte, error) { return contents, nil } +*/ func (t *TileFetcher) loadCache(fileName string) (image.Image, error) { file, err := os.Open(fileName) diff --git a/tile_provider.go b/tile_provider.go index 028a4a1..7a3b04e 100644 --- a/tile_provider.go +++ b/tile_provider.go @@ -5,137 +5,223 @@ package sm -import "fmt" +import ( + "fmt" + "io/ioutil" + "net/http" +) + +func fetchURL(url string) ([]byte, error) { + + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + return nil, err + } + + // req.Header.Set("User-Agent", t.userAgent) + + resp, err := http.DefaultClient.Do(req) + + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("GET %s: %s", url, resp.Status) + } + + defer resp.Body.Close() + + return ioutil.ReadAll(resp.Body) +} // TileProvider encapsulates all infos about a map tile provider service (name, url scheme, attribution, etc.) -type TileProvider struct { - Name string - Attribution string - TileSize int - URLPattern string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y - Shards []string + +type TileProvider interface { + Name() string + Attribution() string + TileSize() int + URLPattern() string + TileURL(int, int, int) string + Shards() []string + FetchTile(int, int, int) ([]byte, error) +} + +type DefaultTileProvider struct { + TileProvider + name string + attribution string + tileSize int + urlPattern string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y + shards []string +} + +func (t *DefaultTileProvider) Name() string { + return t.name +} + +func (t *DefaultTileProvider) Attribution() string { + return t.attribution +} + +func (t *DefaultTileProvider) TileSize() int { + return t.tileSize } -func (t *TileProvider) getURL(shard string, zoom, x, y int) string { - return fmt.Sprintf(t.URLPattern, shard, zoom, x, y) +func (t *DefaultTileProvider) URLPattern() string { + return t.urlPattern +} + +func (t *DefaultTileProvider) Shards() []string { + return t.shards +} + +func (t *DefaultTileProvider) TileURL(zoom int, x int, y int) string { + + possible_shards := t.Shards() + shard := "" + + ss := len(possible_shards) + + if len(possible_shards) > 0 { + shard = possible_shards[(x+y)%ss] + } + + return fmt.Sprintf(t.URLPattern(), shard, zoom, x, y) +} + +func (t *DefaultTileProvider) FetchTile(z int, x int, y int) ([]byte, error) { + + url := t.TileURL(z, x, y) + return fetchURL(url) } // NewTileProviderOpenStreetMaps creates a TileProvider struct for OSM's tile service -func NewTileProviderOpenStreetMaps() *TileProvider { - t := new(TileProvider) - t.Name = "osm" - t.Attribution = "Maps and Data (c) openstreetmap.org and contributors, ODbL" - t.TileSize = 256 - t.URLPattern = "http://%[1]s.tile.openstreetmap.org/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b", "c"} + +func NewTileProviderOpenStreetMaps() TileProvider { + t := &DefaultTileProvider{ + name: "osm", + attribution: "Maps and Data (c) openstreetmap.org and contributors, ODbL", + tileSize: 256, + urlPattern: "http://%[1]s.tile.openstreetmap.org/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b", "c"}, + } return t } -func newTileProviderThunderforest(name string) *TileProvider { - t := new(TileProvider) - t.Name = fmt.Sprintf("thunderforest-%s", name) - t.Attribution = "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL" - t.TileSize = 256 - t.URLPattern = "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b", "c"} +func newTileProviderThunderforest(name string) TileProvider { + t := &DefaultTileProvider{ + name: fmt.Sprintf("thunderforest-%s", name), + attribution: "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL", + tileSize: 256, + urlPattern: "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b", "c"}, + } return t } // NewTileProviderThunderforestLandscape creates a TileProvider struct for thundeforests's 'landscape' tile service -func NewTileProviderThunderforestLandscape() *TileProvider { +func NewTileProviderThunderforestLandscape() TileProvider { return newTileProviderThunderforest("landscape") } // NewTileProviderThunderforestOutdoors creates a TileProvider struct for thundeforests's 'outdoors' tile service -func NewTileProviderThunderforestOutdoors() *TileProvider { +func NewTileProviderThunderforestOutdoors() TileProvider { return newTileProviderThunderforest("outdoors") } // NewTileProviderThunderforestTransport creates a TileProvider struct for thundeforests's 'transport' tile service -func NewTileProviderThunderforestTransport() *TileProvider { +func NewTileProviderThunderforestTransport() TileProvider { return newTileProviderThunderforest("transport") } // NewTileProviderStamenToner creates a TileProvider struct for stamens' 'toner' tile service -func NewTileProviderStamenToner() *TileProvider { - t := new(TileProvider) - t.Name = "stamen-toner" - t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL" - t.TileSize = 256 - t.URLPattern = "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b", "c", "d"} +func NewTileProviderStamenToner() TileProvider { + t := &DefaultTileProvider{ + name: "stamen-toner", + attribution: "Maps (c) Stamen; Data (c) OSM and contributors, ODbL", + tileSize: 256, + urlPattern: "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b", "c", "d"}, + } return t } // NewTileProviderStamenTerrain creates a TileProvider struct for stamens' 'terrain' tile service -func NewTileProviderStamenTerrain() *TileProvider { - t := new(TileProvider) - t.Name = "stamen-terrain" - t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL" - t.TileSize = 256 - t.URLPattern = "http://%[1]s.tile.stamen.com/terrain/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b", "c", "d"} +func NewTileProviderStamenTerrain() TileProvider { + t := &DefaultTileProvider{ + name: "stamen-terrain", + attribution: "Maps (c) Stamen; Data (c) OSM and contributors, ODbL", + tileSize: 256, + urlPattern: "http://%[1]s.tile.stamen.com/terrain/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b", "c", "d"}, + } return t } // NewTileProviderOpenTopoMap creates a TileProvider struct for opentopomap's tile service -func NewTileProviderOpenTopoMap() *TileProvider { - t := new(TileProvider) - t.Name = "opentopomap" - t.Attribution = "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM" - t.TileSize = 256 - t.URLPattern = "http://%[1]s.tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b", "c"} +func NewTileProviderOpenTopoMap() TileProvider { + t := &DefaultTileProvider{ + name: "opentopomap", + attribution: "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM", + tileSize: 256, + urlPattern: "http://%[1]s.tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b", "c"}, + } return t } // NewTileProviderWikimedia creates a TileProvider struct for Wikimedia's tile service -func NewTileProviderWikimedia() *TileProvider { - t := new(TileProvider) - t.Name = "wikimedia" - t.Attribution = "Map (c) Wikimedia; Data (c) OSM and contributors, ODbL." - t.TileSize = 256 - t.URLPattern = "https://maps.wikimedia.org/osm-intl/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{} +func NewTileProviderWikimedia() TileProvider { + t := &DefaultTileProvider{ + name: "wikimedia", + attribution: "Map (c) Wikimedia; Data (c) OSM and contributors, ODbL.", + tileSize: 256, + urlPattern: "https://maps.wikimedia.org/osm-intl/%[2]d/%[3]d/%[4]d.png", + shards: []string{}, + } return t } // NewTileProviderOpenCycleMap creates a TileProvider struct for OpenCycleMap's tile service -func NewTileProviderOpenCycleMap() *TileProvider { - t := new(TileProvider) - t.Name = "cycle" - t.Attribution = "Maps and Data (c) openstreetmaps.org and contributors, ODbL" - t.TileSize = 256 - t.URLPattern = "http://%[1]s.tile.opencyclemap.org/cycle/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b"} +func NewTileProviderOpenCycleMap() TileProvider { + t := &DefaultTileProvider{ + name: "cycle", + attribution: "Maps and Data (c) openstreetmaps.org and contributors, ODbL", + tileSize: 256, + urlPattern: "http://%[1]s.tile.opencyclemap.org/cycle/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b"}, + } return t } -func newTileProviderCarto(name string) *TileProvider { - t := new(TileProvider) - t.Name = fmt.Sprintf("carto-%s", name) - t.Attribution = "Map (c) Carto [CC BY 3.0] Data (c) OSM and contributors, ODbL." - t.TileSize = 256 - t.URLPattern = "https://cartodb-basemaps-%[1]s.global.ssl.fastly.net/" + name + "_all/%[2]d/%[3]d/%[4]d.png" - t.Shards = []string{"a", "b", "c", "d"} +func newTileProviderCarto(name string) TileProvider { + t := &DefaultTileProvider{ + name: fmt.Sprintf("carto-%s", name), + attribution: "Map (c) Carto [CC BY 3.0] Data (c) OSM and contributors, ODbL.", + tileSize: 256, + urlPattern: "https://cartodb-basemaps-%[1]s.global.ssl.fastly.net/" + name + "_all/%[2]d/%[3]d/%[4]d.png", + shards: []string{"a", "b", "c", "d"}, + } return t } // NewTileProviderCartoLight creates a TileProvider struct for Carto's tile service (light variant) -func NewTileProviderCartoLight() *TileProvider { +func NewTileProviderCartoLight() TileProvider { return newTileProviderCarto("light") } // NewTileProviderCartoDark creates a TileProvider struct for Carto's tile service (dark variant) -func NewTileProviderCartoDark() *TileProvider { +func NewTileProviderCartoDark() TileProvider { return newTileProviderCarto("dark") } // GetTileProviders returns a map of all available TileProviders -func GetTileProviders() map[string]*TileProvider { - m := make(map[string]*TileProvider) +func GetTileProviders() map[string]TileProvider { + m := make(map[string]TileProvider) - list := []*TileProvider{ + list := []TileProvider{ NewTileProviderOpenStreetMaps(), NewTileProviderOpenCycleMap(), NewTileProviderThunderforestLandscape(), @@ -151,7 +237,7 @@ func GetTileProviders() map[string]*TileProvider { } for _, tp := range list { - m[tp.Name] = tp + m[tp.Name()] = tp } return m