diff --git a/atlas/atlas.go b/atlas/atlas.go index 5ea55e0a5..922511c8a 100644 --- a/atlas/atlas.go +++ b/atlas/atlas.go @@ -131,7 +131,7 @@ func (a *Atlas) SeedMapTile(ctx context.Context, m Map, z, x, y uint) error { return ErrMissingCache } - tile := slippy.NewTile(z, x, y) + tile := slippy.Tile{Z: slippy.Zoom(z), X: x, Y: y} // encode the tile b, err := m.Encode(ctx, tile, nil) diff --git a/atlas/map.go b/atlas/map.go index a262cd4b3..190559489 100644 --- a/atlas/map.go +++ b/atlas/map.go @@ -144,11 +144,11 @@ func (m Map) AddDebugLayers() Map { } // FilterLayersByZoom returns a copy of a Map with a subset of layers that match the given zoom -func (m Map) FilterLayersByZoom(zoom uint) Map { +func (m Map) FilterLayersByZoom(zoom slippy.Zoom) Map { var layers []Layer for i := range m.Layers { - if m.Layers[i].MinZoom <= zoom && m.Layers[i].MaxZoom >= zoom { + if slippy.Zoom(m.Layers[i].MinZoom) <= zoom && slippy.Zoom(m.Layers[i].MaxZoom) >= zoom { layers = append(layers, m.Layers[i]) continue } @@ -182,7 +182,7 @@ func (m Map) FilterLayersByName(names ...string) Map { return m } -func (m Map) encodeMVTProviderTile(ctx context.Context, tile *slippy.Tile, params provider.Params) ([]byte, error) { +func (m Map) encodeMVTProviderTile(ctx context.Context, tile slippy.Tile, params provider.Params) ([]byte, error) { // get the list of our layers ptile := provider.NewTile(tile.Z, tile.X, tile.Y, uint(m.TileBuffer), uint(m.SRID)) @@ -199,7 +199,7 @@ func (m Map) encodeMVTProviderTile(ctx context.Context, tile *slippy.Tile, param // encodeMVTTile will encode the given tile into mvt format // TODO (arolek): support for max zoom -func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provider.Params) ([]byte, error) { +func (m Map) encodeMVTTile(ctx context.Context, tile slippy.Tile, params provider.Params) ([]byte, error) { // tile container var mvtTile mvt.Tile @@ -209,7 +209,7 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provid // layer stack mvtLayers := make([]*mvt.Layer, len(m.Layers)) - // set our waitgroup count + // set our WaitGroup count wg.Add(len(m.Layers)) // iterate our layers @@ -237,7 +237,7 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provid geo := f.Geometry - // check if the feature SRID and map SRID are different. If they are then reporject + // check if the feature SRID and map SRID are different. If they are then reprojected if f.SRID != m.SRID { // TODO(arolek): support for additional projections g, err := basic.ToWebMercator(f.SRID, geo) @@ -261,12 +261,12 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provid } // TODO (arolek): change out the tile type for VTile. tegola.Tile will be deprecated - tegolaTile := tegola.NewTile(tile.ZXY()) + tegolaTile := tegola.TileFromSlippyTile(tile) sg := tegolaGeo // multiple ways to turn off simplification. check the atlas init() function // for how the second two conditions are set - if !l.DontSimplify && simplifyGeometries && tile.Z < simplificationMaxZoom { + if !l.DontSimplify && simplifyGeometries && tile.Z < slippy.Zoom(simplificationMaxZoom) { sg = simplify.SimplifyGeometry(tegolaGeo, tegolaTile.ZEpislon()) } @@ -340,10 +340,9 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provid // Do nothing, context was canceled default: - z, x, y := tile.ZXY() // TODO (arolek): should we return an error to the response or just log the error? - // we can't just write to the response as the waitgroup is going to write to the response as well - log.Errorf("err fetching tile (z: %v, x: %v, y: %v) features: %v", z, x, y, err) + // we can't just write to the response as the WaitGroup is going to write to the response as well + log.Errorf("err fetching tile (%v) features: %v", tile, err) } return } @@ -353,12 +352,12 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provid }(i, layer) } - // wait for the waitgroup to finish + // wait for the WaitGroup to finish wg.Wait() // stop processing if the context has an error. this check is necessary // otherwise the server continues processing even if the request was canceled - // as the waitgroup was not notified of the cancel + // as the WaitGroup was not notified of the cancel if ctx.Err() != nil { return nil, ctx.Err() } @@ -380,7 +379,7 @@ func (m Map) encodeMVTTile(ctx context.Context, tile *slippy.Tile, params provid } // Encode will encode the given tile into mvt format -func (m Map) Encode(ctx context.Context, tile *slippy.Tile, params provider.Params) ([]byte, error) { +func (m Map) Encode(ctx context.Context, tile slippy.Tile, params provider.Params) ([]byte, error) { var ( tileBytes []byte err error diff --git a/atlas/map_test.go b/atlas/map_test.go index 745e6f121..94db89ccc 100644 --- a/atlas/map_test.go +++ b/atlas/map_test.go @@ -21,7 +21,7 @@ import ( func TestMapFilterLayersByZoom(t *testing.T) { testcases := []struct { atlasMap atlas.Map - zoom uint + zoom slippy.Zoom expected atlas.Map }{ { @@ -216,7 +216,7 @@ func TestEncode(t *testing.T) { type tcase struct { grid atlas.Map - tile *slippy.Tile + tile slippy.Tile expected vectorTile.Tile } @@ -365,7 +365,7 @@ func TestEncode(t *testing.T) { }, }, }, - tile: slippy.NewTile(2, 3, 3), + tile: slippy.Tile{Z: 2, X: 3, Y: 3}, expected: vectorTile.Tile{ Layers: []*vectorTile.Tile_Layer{ { @@ -423,7 +423,7 @@ func TestEncode(t *testing.T) { }, }, }, - tile: slippy.NewTile(2, 3, 3), + tile: slippy.Tile{Z: 2, X: 3, Y: 3}, expected: vectorTile.Tile{ Layers: []*vectorTile.Tile_Layer{ { diff --git a/cmd/tegola/cmd/cache/cache.go b/cmd/tegola/cmd/cache/cache.go index 8c2d998c9..04d667f28 100644 --- a/cmd/tegola/cmd/cache/cache.go +++ b/cmd/tegola/cmd/cache/cache.go @@ -2,6 +2,7 @@ package cache import ( "context" + "errors" "fmt" "os" "strconv" @@ -16,7 +17,7 @@ import ( "github.com/go-spatial/tegola/internal/log" ) -// the config from the main app +// Config from the main app var Config *config.Config var RequireCache bool @@ -61,14 +62,14 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e } type TileChannel struct { - channel chan *slippy.Tile + channel chan slippy.Tile cl sync.RWMutex isClosed bool l sync.RWMutex err error } -func (tc *TileChannel) Channel() <-chan *slippy.Tile { +func (tc *TileChannel) Channel() <-chan slippy.Tile { if tc == nil { return nil } @@ -111,7 +112,7 @@ func (tc *TileChannel) Close() { type MapTile struct { MapName string - Tile *slippy.Tile + Tile slippy.Tile } func doWork(ctx context.Context, tileChannel *TileChannel, maps []atlas.Map, concurrency int, worker func(context.Context, MapTile) error) (err error) { @@ -150,7 +151,7 @@ func doWork(ctx context.Context, tileChannel *TileChannel, maps []atlas.Map, con } if cleanup { log.Debugf("worker %v waiting on clean up of tiler", i) - for _ = range tiler { + for range tiler { continue } } @@ -205,7 +206,7 @@ TileChannelLoop: close(tiler) if cleanup { // want to soak up any messages - for _ = range tileChannel.Channel() { + for range tileChannel.Channel() { continue } } @@ -226,7 +227,7 @@ TileChannelLoop: if err == nil { err = mapTileErr } - if err == context.Canceled { + if errors.Is(err, context.Canceled) { return nil } return err diff --git a/cmd/tegola/cmd/cache/format.go b/cmd/tegola/cmd/cache/format.go index 2e5d5b205..b0a06e212 100644 --- a/cmd/tegola/cmd/cache/format.go +++ b/cmd/tegola/cmd/cache/format.go @@ -92,11 +92,10 @@ func (f Format) Parse(val string) (z, x, y uint, err error) { return uint(zi), uint(xi), uint(yi), nil } -func (f Format) ParseTile(val string) (tile *slippy.Tile, err error) { +func (f Format) ParseTile(val string) (tile slippy.Tile, err error) { z, x, y, err := format.Parse(val) if err != nil { - return nil, err + return slippy.Tile{}, err } - tile = slippy.NewTile(z, x, y) - return tile, nil + return slippy.Tile{Z: slippy.Zoom(z), X: x, Y: y}, nil } diff --git a/cmd/tegola/cmd/cache/seed_purge.go b/cmd/tegola/cmd/cache/seed_purge.go index 92ba32c88..46d8ee9ea 100644 --- a/cmd/tegola/cmd/cache/seed_purge.go +++ b/cmd/tegola/cmd/cache/seed_purge.go @@ -200,15 +200,10 @@ func seedPurgeCommand(_ *cobra.Command, _ []string) (err error) { func generateTilesForBounds(ctx context.Context, bounds [4]float64, zooms []uint) *TileChannel { tce := &TileChannel{ - channel: make(chan *slippy.Tile), + channel: make(chan slippy.Tile), } - webmercatorGrid, err := slippy.NewGrid(3857) - if err != nil { - tce.setError(fmt.Errorf("Could not create Web Mercator grid (3857): %s", err)) - tce.Close() - return tce - } + webmercatorGrid := slippy.NewGrid(3857, 0) go func() { defer tce.Close() @@ -216,11 +211,16 @@ func generateTilesForBounds(ctx context.Context, bounds [4]float64, zooms []uint var extent geom.Extent = bounds for _, z := range zooms { - tiles := slippy.FromBounds(webmercatorGrid, &extent, z) + tiles, err := slippy.FromBounds(webmercatorGrid, &extent, slippy.Zoom(z)) + if err != nil { + tce.setError(fmt.Errorf("got error trying to get tiles: %w", err)) + tce.Close() + return + } for _, tile := range tiles { t := tile select { - case tce.channel <- &t: + case tce.channel <- t: case <-ctx.Done(): // we have been cancelled return diff --git a/cmd/tegola/cmd/cache/seed_purge_generator_test.go b/cmd/tegola/cmd/cache/seed_purge_generator_test.go index a0d762150..6973769fd 100644 --- a/cmd/tegola/cmd/cache/seed_purge_generator_test.go +++ b/cmd/tegola/cmd/cache/seed_purge_generator_test.go @@ -10,22 +10,11 @@ import ( "github.com/go-spatial/geom/slippy" ) -type sTiles []*slippy.Tile - -func (st sTiles) Len() int { return len(st) } -func (st sTiles) Swap(i, j int) { st[i], st[j] = st[j], st[i] } -func (st sTiles) Less(i, j int) bool { - zi, xi, yi := st[i].ZXY() - zj, xj, yj := st[j].ZXY() - switch { - case zi != zj: - return zi < zj - case xi != xj: - return xi < xj - default: - return yi < yj - } -} +type sTiles []slippy.Tile + +func (st sTiles) Len() int { return len(st) } +func (st sTiles) Swap(i, j int) { st[i], st[j] = st[j], st[i] } +func (st sTiles) Less(i, j int) bool { return st[i].Less(st[j]) } // IsEqual report true only if both the size and the elements are the same. Where a tile is equal only if the z,x,y elements match. func (st sTiles) IsEqual(ost sTiles) bool { @@ -118,24 +107,24 @@ func TestGenerateTilesForBounds(t *testing.T) { "max_zoom=0": { zooms: []uint{0}, bounds: worldBounds, - tiles: sTiles{slippy.NewTile(0, 0, 0)}, + tiles: sTiles{slippy.Tile{}}, }, "min_zoom=1 max_zoom=1": { zooms: []uint{1}, bounds: worldBounds, tiles: sTiles{ - slippy.NewTile(1, 0, 0), - slippy.NewTile(1, 0, 1), - slippy.NewTile(1, 1, 0), - slippy.NewTile(1, 1, 1), + slippy.Tile{Z: 1}, + slippy.Tile{Z: 1, Y: 1}, + slippy.Tile{Z: 1, X: 1}, + slippy.Tile{Z: 1, X: 1, Y: 1}, }, }, "min_zoom=1 max_zoom=1 bounds=180,90,0,0": { zooms: []uint{1}, bounds: [4]float64{180.0, 90.0, 0.0, 0.0}, tiles: sTiles{ - slippy.NewTile(1, 1, 0), - slippy.NewTile(1, 1, 1), + slippy.Tile{Z: 1, X: 1}, + slippy.Tile{Z: 1, X: 1, Y: 1}, }, }, } diff --git a/cmd/tegola/cmd/cache/slippy.go b/cmd/tegola/cmd/cache/slippy.go deleted file mode 100644 index ffb6ed3c2..000000000 --- a/cmd/tegola/cmd/cache/slippy.go +++ /dev/null @@ -1,43 +0,0 @@ -package cache - -import ( - "math" - - "github.com/go-spatial/geom/slippy" -) - -// rangeFamilyAt runs the given callback on every related tile at the given zoom. This could include -// the provided tile itself (if the same zoom is provided), the parent (overlapping tile at a lower zoom -// level), or children (overlapping tiles at a higher zoom level). -// -// Copied from go-spatial/geom (dc1d50720ee77122d0) since the function by this name doesn't do -// the same thing anymore. (In geom, it no longer returns ancestors or self, only descendants. -// It's also buggy because grid.ToNative/FromNative are buggy.) -// -// This function should be removed once the one in geom is updated to work as expected. -func rangeFamilyAt(t *slippy.Tile, zoom uint, f func(*slippy.Tile) error) error { - // handle ancestors and self - if zoom <= t.Z { - mag := t.Z - zoom - arg := slippy.NewTile(zoom, t.X>>mag, t.Y>>mag) - return f(arg) - } - - // handle descendants - mag := zoom - t.Z - delta := uint(math.Exp2(float64(mag))) - - leastX := t.X << mag - leastY := t.Y << mag - - for x := leastX; x < leastX+delta; x++ { - for y := leastY; y < leastY+delta; y++ { - err := f(slippy.NewTile(zoom, x, y)) - if err != nil { - return err - } - } - } - - return nil -} diff --git a/cmd/tegola/cmd/cache/slippy_test.go b/cmd/tegola/cmd/cache/slippy_test.go index 38d21380e..a4a9b5f22 100644 --- a/cmd/tegola/cmd/cache/slippy_test.go +++ b/cmd/tegola/cmd/cache/slippy_test.go @@ -8,12 +8,13 @@ import ( func TestRangeFamilyAt(t *testing.T) { type coord struct { - z, x, y uint + z slippy.Zoom + x, y uint } type tcase struct { - tile *slippy.Tile - zoomAt uint + tile slippy.Tile + zoomAt slippy.Zoom expected []coord } @@ -31,13 +32,15 @@ func TestRangeFamilyAt(t *testing.T) { return func(t *testing.T) { coordList := make([]coord, 0, len(tc.expected)) - rangeFamilyAt(tc.tile, tc.zoomAt, func(tile *slippy.Tile) error { + //for tile := range tc.tile.FamilyAt(tc.zoomAt) + + slippy.RangeFamilyAt(tc.tile, tc.zoomAt, func(tile slippy.Tile) bool { z, x, y := tile.ZXY() c := coord{z, x, y} coordList = append(coordList, c) - return nil + return true }) if len(coordList) != len(tc.expected) { @@ -56,7 +59,7 @@ func TestRangeFamilyAt(t *testing.T) { testcases := map[string]tcase{ "children 1": { - tile: slippy.NewTile(0, 0, 0), + tile: slippy.Tile{}, zoomAt: 1, expected: []coord{ {1, 0, 0}, @@ -66,7 +69,7 @@ func TestRangeFamilyAt(t *testing.T) { }, }, "children 2": { - tile: slippy.NewTile(8, 3, 5), + tile: slippy.Tile{Z: 8, X: 3, Y: 5}, zoomAt: 10, expected: []coord{ {10, 12, 20}, @@ -91,14 +94,14 @@ func TestRangeFamilyAt(t *testing.T) { }, }, "parent 1": { - tile: slippy.NewTile(1, 0, 0), + tile: slippy.Tile{Z: 1}, zoomAt: 0, expected: []coord{ {0, 0, 0}, }, }, "parent 2": { - tile: slippy.NewTile(3, 3, 5), + tile: slippy.Tile{Z: 3, X: 3, Y: 5}, zoomAt: 1, expected: []coord{ {1, 0, 1}, diff --git a/cmd/tegola/cmd/cache/testdata/.gitignore b/cmd/tegola/cmd/cache/testdata/.gitignore new file mode 100644 index 000000000..ca87835dd --- /dev/null +++ b/cmd/tegola/cmd/cache/testdata/.gitignore @@ -0,0 +1 @@ +*.generated.debug.stilelist \ No newline at end of file diff --git a/cmd/tegola/cmd/cache/tile_list.go b/cmd/tegola/cmd/cache/tile_list.go index 3f7825fb7..efb0e91c2 100644 --- a/cmd/tegola/cmd/cache/tile_list.go +++ b/cmd/tegola/cmd/cache/tile_list.go @@ -100,7 +100,7 @@ func tileListCommand(cmd *cobra.Command, args []string) (err error) { // if explicit is false and zooms is not empty, it will include the tiles above and below with in the provided zooms func generateTilesForTileList(ctx context.Context, tilelist io.Reader, explicit bool, zooms []uint, format Format) *TileChannel { tce := &TileChannel{ - channel: make(chan *slippy.Tile), + channel: make(chan slippy.Tile), } go func() { @@ -109,7 +109,7 @@ func generateTilesForTileList(ctx context.Context, tilelist io.Reader, explicit var ( err error lineNumber int - tile *slippy.Tile + tile slippy.Tile ) scanner := bufio.NewScanner(tilelist) @@ -135,17 +135,18 @@ func generateTilesForTileList(ctx context.Context, tilelist io.Reader, explicit for _, zoom := range zooms { // range will include the original tile. - err = rangeFamilyAt(tile, zoom, func(tile *slippy.Tile) error { + slippy.RangeFamilyAt(tile, slippy.Zoom(zoom), func(tile slippy.Tile) bool { select { case tce.channel <- tile: case <-ctx.Done(): // we have been cancelled - return context.Canceled + return false } - return nil + return true + }) // gracefully stop if cancelled - if err != nil { + if ctx.Err() != nil { return } } diff --git a/cmd/tegola/cmd/cache/tile_list_generator_test.go b/cmd/tegola/cmd/cache/tile_list_generator_test.go index f84aa76fc..b3566f097 100644 --- a/cmd/tegola/cmd/cache/tile_list_generator_test.go +++ b/cmd/tegola/cmd/cache/tile_list_generator_test.go @@ -81,12 +81,12 @@ func TestGenerateTilesForTileList(t *testing.T) { format: defaultTileNameFormat, zooms: []uint{13, 14, 15}, tiles: sTiles{ - slippy.NewTile(13, 150, 390), - slippy.NewTile(14, 300, 781), - slippy.NewTile(15, 600, 1562), - slippy.NewTile(15, 600, 1563), - slippy.NewTile(15, 601, 1562), - slippy.NewTile(15, 601, 1563), + slippy.Tile{Z: 13, X: 150, Y: 390}, + slippy.Tile{Z: 14, X: 300, Y: 781}, + slippy.Tile{Z: 15, X: 600, Y: 1562}, + slippy.Tile{Z: 15, X: 600, Y: 1563}, + slippy.Tile{Z: 15, X: 601, Y: 1562}, + slippy.Tile{Z: 15, X: 601, Y: 1563}, }, }, { @@ -94,7 +94,7 @@ func TestGenerateTilesForTileList(t *testing.T) { format: defaultTileNameFormat, explicit: true, tiles: sTiles{ - slippy.NewTile(14, 300, 781), + slippy.Tile{Z: 14, X: 300, Y: 781}, }, }, } diff --git a/cmd/tegola/cmd/cache/tile_name.go b/cmd/tegola/cmd/cache/tile_name.go index 250ee61b2..a3aa9e9a5 100644 --- a/cmd/tegola/cmd/cache/tile_name.go +++ b/cmd/tegola/cmd/cache/tile_name.go @@ -12,7 +12,7 @@ import ( "github.com/go-spatial/tegola/provider" ) -var tileNameTile *slippy.Tile +var tileNameTile slippy.Tile var TileNameCmd = &cobra.Command{ Use: "tile-name z/x/y", @@ -78,16 +78,13 @@ func tileNameCommand(cmd *cobra.Command, args []string) (err error) { } -func generateTilesForTileName(ctx context.Context, tile *slippy.Tile, explicit bool, zooms []uint) *TileChannel { +func generateTilesForTileName(ctx context.Context, tile slippy.Tile, explicit bool, zooms []uint) *TileChannel { tce := &TileChannel{ - channel: make(chan *slippy.Tile), + channel: make(chan slippy.Tile), } go func() { defer tce.Close() - if tile == nil { - return - } if explicit || len(zooms) == 0 { select { case tce.channel <- tile: @@ -99,17 +96,16 @@ func generateTilesForTileName(ctx context.Context, tile *slippy.Tile, explicit b } for _, zoom := range zooms { // range will include the original tile. - err := rangeFamilyAt(tile, zoom, func(tile *slippy.Tile) error { + slippy.RangeFamilyAt(tile, slippy.Zoom(zoom), func(tile slippy.Tile) bool { select { case tce.channel <- tile: case <-ctx.Done(): - // we have been cancelled - return context.Canceled + return false } - return nil + return true }) // gracefully stop if cancelled - if err != nil { + if ctx.Err() != nil { return } } diff --git a/cmd/tegola/cmd/cache/tile_name_generator_test.go b/cmd/tegola/cmd/cache/tile_name_generator_test.go index 158789c49..6219d98f9 100644 --- a/cmd/tegola/cmd/cache/tile_name_generator_test.go +++ b/cmd/tegola/cmd/cache/tile_name_generator_test.go @@ -11,7 +11,7 @@ import ( func TestGenerateTilesForTileName(t *testing.T) { type tcase struct { - tile *slippy.Tile + tile slippy.Tile zooms []uint explicit bool tiles sTiles @@ -51,29 +51,29 @@ func TestGenerateTilesForTileName(t *testing.T) { tests := map[string]tcase{ "max_zoom=0 tile-name=0/0/0": { - tile: slippy.NewTile(0, 0, 0), + tile: slippy.Tile{}, explicit: true, tiles: sTiles{ - slippy.NewTile(0, 0, 0), + slippy.Tile{}, }, }, "max_zoom=0 tile-name=14/300/781": { - tile: slippy.NewTile(14, 300, 781), + tile: slippy.Tile{Z: 14, X: 300, Y: 781}, explicit: true, tiles: sTiles{ - slippy.NewTile(14, 300, 781), + slippy.Tile{Z: 14, X: 300, Y: 781}, }, }, "min_zoom= 13 max_zoom=15 tile-name=14/300/781": { - tile: slippy.NewTile(14, 300, 781), + tile: slippy.Tile{Z: 14, X: 300, Y: 781}, zooms: []uint{13, 14, 15}, tiles: sTiles{ - slippy.NewTile(13, 150, 390), - slippy.NewTile(14, 300, 781), - slippy.NewTile(15, 600, 1562), - slippy.NewTile(15, 600, 1563), - slippy.NewTile(15, 601, 1562), - slippy.NewTile(15, 601, 1563), + slippy.Tile{Z: 13, X: 150, Y: 390}, + slippy.Tile{Z: 14, X: 300, Y: 781}, + slippy.Tile{Z: 15, X: 600, Y: 1562}, + slippy.Tile{Z: 15, X: 600, Y: 1563}, + slippy.Tile{Z: 15, X: 601, Y: 1562}, + slippy.Tile{Z: 15, X: 601, Y: 1563}, }, }, } diff --git a/cmd/tegola/cmd/cache/worker.go b/cmd/tegola/cmd/cache/worker.go index 80469a127..a7a185344 100644 --- a/cmd/tegola/cmd/cache/worker.go +++ b/cmd/tegola/cmd/cache/worker.go @@ -2,6 +2,7 @@ package cache import ( "context" + "errors" "fmt" "runtime" "time" @@ -36,7 +37,7 @@ func seedWorker(overwrite bool, logThresholdMs int64) func(ctx context.Context, m, err := atlas.GetMap(mt.MapName) if err != nil { return seedPurgeWorkerTileError{ - Tile: *mt.Tile, + Tile: mt.Tile, Err: err, } } @@ -57,7 +58,7 @@ func seedWorker(overwrite bool, logThresholdMs int64) func(ctx context.Context, // cache key key := cache.Key{ MapName: mt.MapName, - Z: z, + Z: uint(z), X: x, Y: y, } @@ -75,12 +76,12 @@ func seedWorker(overwrite bool, logThresholdMs int64) func(ctx context.Context, } // seed the tile - if err = atlas.SeedMapTile(ctx, m, z, x, y); err != nil { - if err == context.Canceled { + if err = atlas.SeedMapTile(ctx, m, uint(z), x, y); err != nil { + if errors.Is(err, context.Canceled) { return err } return seedPurgeWorkerTileError{ - Tile: *mt.Tile, + Tile: mt.Tile, Err: err, } } @@ -89,7 +90,7 @@ func seedWorker(overwrite bool, logThresholdMs int64) func(ctx context.Context, // https://github.com/golang/go/issues/14045 - should be addressed in Go 1.11 runtime.GC() - durationMs := time.Now().Sub(t).Nanoseconds()/1000000 + durationMs := time.Now().Sub(t).Nanoseconds() / 1000000 if durationMs >= logThresholdMs { log.Infof("seeding map (%v) tile (%v/%v/%v) took: %dms", mt.MapName, z, x, y, durationMs) } @@ -110,18 +111,18 @@ func purgeWorker(_ context.Context, mt MapTile) error { if err != nil { return seedPurgeWorkerTileError{ Purge: true, - Tile: *mt.Tile, + Tile: mt.Tile, Err: err, } } // purge the tile - ttile := tegola.NewTile(mt.Tile.ZXY()) + ttile := tegola.TileFromSlippyTile(mt.Tile) if err = atlas.PurgeMapTile(m, ttile); err != nil { return seedPurgeWorkerTileError{ Purge: true, - Tile: *mt.Tile, + Tile: mt.Tile, Err: err, } } diff --git a/provider/gpkg/gpkg.go b/provider/gpkg/gpkg.go index 65649ed07..c35b6a9eb 100644 --- a/provider/gpkg/gpkg.go +++ b/provider/gpkg/gpkg.go @@ -117,11 +117,11 @@ func (p *Provider) TileFeatures(ctx context.Context, layer string, tile provider qtext = fmt.Sprintf("%v FROM `%v` l JOIN `%v` si ON l.`%v` = si.id WHERE l.`%v` IS NOT NULL AND !BBOX! ORDER BY l.`%v`", selectClause, pLayer.tablename, rtreeTablename, pLayer.idFieldname, pLayer.geomFieldname, pLayer.idFieldname) z, _, _ := tile.ZXY() - qtext = replaceTokens(qtext, z, tileBBox) + qtext = replaceTokens(qtext, uint(z), tileBBox) } else { // If layer was specified via "sql" in config, collect it z, _, _ := tile.ZXY() - qtext = replaceTokens(pLayer.sql, z, tileBBox) + qtext = replaceTokens(pLayer.sql, uint(z), tileBBox) qtext = queryParams.ReplaceParams(qtext, &args) } diff --git a/provider/gpkg/gpkg_test.go b/provider/gpkg/gpkg_test.go index b3142dd13..3b1aaba45 100644 --- a/provider/gpkg/gpkg_test.go +++ b/provider/gpkg/gpkg_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/go-spatial/geom" + "github.com/go-spatial/geom/slippy" "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/dict" "github.com/go-spatial/tegola/provider" @@ -239,7 +240,8 @@ func TestNewTileProvider(t *testing.T) { type MockTile struct { extent *geom.Extent bufferedExtent *geom.Extent - Z, X, Y uint + Z slippy.Zoom + X, Y uint srid uint64 } @@ -249,7 +251,7 @@ func (t *MockTile) Extent() (*geom.Extent, uint64) { return t.extent, t.srid } // TODO(arolek): BufferedExtent needs to return a geom.Extent func (t *MockTile) BufferedExtent() (*geom.Extent, uint64) { return t.bufferedExtent, t.srid } -func (t *MockTile) ZXY() (uint, uint, uint) { return t.Z, t.X, t.Y } +func (t *MockTile) ZXY() (slippy.Zoom, uint, uint) { return t.Z, t.X, t.Y } func TestTileFeatures(t *testing.T) { type tcase struct { @@ -608,7 +610,7 @@ func TestOpenNonExistantFile(t *testing.T) { err: gpkg.ErrInvalidFilePath{FilePath: ""}, }, "nonexistance": { - // should not exists + // should not exist config: dict.Dict{ gpkg.ConfigKeyFilePath: NONEXISTANTFILE, }, diff --git a/provider/hana/util_internal_test.go b/provider/hana/util_internal_test.go index 94654b7f8..a32e6d838 100644 --- a/provider/hana/util_internal_test.go +++ b/provider/hana/util_internal_test.go @@ -79,7 +79,7 @@ func TestReplaceTokens(t *testing.T) { sql: "SELECT id, !pixel_width! as width, !pixel_height! as height, !scale_denominator! as scale_denom FROM foo WHERE !BBOX!", layer: Layer{srid: tegola.WebMercator, geomField: "geom"}, tile: provider.NewTile(11, 1070, 676, 64, tegola.WebMercator), - expected: `SELECT id, 76.43702827 as width, 76.43702827 as height, 272989.38669477 as scale_denom FROM foo WHERE "geom".ST_IntersectsRectPlanar(NEW ST_POINT($1, $3), NEW ST_POINT($2, $3)) = 1`, + expected: `SELECT id, 76.43702829 as width, 76.43702829 as height, 272989.38673277 as scale_denom FROM foo WHERE "geom".ST_IntersectsRectPlanar(NEW ST_POINT($1, $3), NEW ST_POINT($2, $3)) = 1`, }, } diff --git a/provider/postgis/util_internal_test.go b/provider/postgis/util_internal_test.go index ee4d24964..83824f836 100644 --- a/provider/postgis/util_internal_test.go +++ b/provider/postgis/util_internal_test.go @@ -39,31 +39,31 @@ func TestReplaceTokens(t *testing.T) { sql: "SELECT * FROM foo WHERE geom && !BBOX!", layer: Layer{srid: tegola.WebMercator}, tile: provider.NewTile(2, 1, 1, 64, tegola.WebMercator), - expected: "SELECT * FROM foo WHERE geom && ST_MakeEnvelope(-10175297.20390625,-156543.03390625,156543.03390625,10175297.20390625,3857)", + expected: "SELECT * FROM foo WHERE geom && ST_MakeEnvelope(-10175297.20532266,-156543.03392804,156543.03392804,10175297.20532266,3857)", }, "replace BBOX with != in query": { sql: "SELECT * FROM foo WHERE geom && !BBOX! AND bar != 42", layer: Layer{srid: tegola.WebMercator}, tile: provider.NewTile(2, 1, 1, 64, tegola.WebMercator), - expected: "SELECT * FROM foo WHERE geom && ST_MakeEnvelope(-10175297.20390625,-156543.03390625,156543.03390625,10175297.20390625,3857) AND bar != 42", + expected: "SELECT * FROM foo WHERE geom && ST_MakeEnvelope(-10175297.20532266,-156543.03392804,156543.03392804,10175297.20532266,3857) AND bar != 42", }, "replace BBOX and ZOOM 1": { sql: "SELECT id, scalerank=!ZOOM! FROM foo WHERE geom && !BBOX!", layer: Layer{srid: tegola.WebMercator}, tile: provider.NewTile(2, 1, 1, 64, tegola.WebMercator), - expected: "SELECT id, scalerank=2 FROM foo WHERE geom && ST_MakeEnvelope(-10175297.20390625,-156543.03390625,156543.03390625,10175297.20390625,3857)", + expected: "SELECT id, scalerank=2 FROM foo WHERE geom && ST_MakeEnvelope(-10175297.20532266,-156543.03392804,156543.03392804,10175297.20532266,3857)", }, "replace BBOX and ZOOM 2": { sql: "SELECT id, scalerank=!ZOOM! FROM foo WHERE geom && !BBOX!", layer: Layer{srid: tegola.WebMercator}, tile: provider.NewTile(16, 11241, 26168, 64, tegola.WebMercator), - expected: "SELECT id, scalerank=16 FROM foo WHERE geom && ST_MakeEnvelope(-13163688.81595605,4035254.04204078,-13163058.21047278,4035884.64752404,3857)", + expected: "SELECT id, scalerank=16 FROM foo WHERE geom && ST_MakeEnvelope(-13163688.81778845,4035254.04260249,-13163058.21230510,4035884.64808584,3857)", }, "replace pixel_width/height and scale_denominator": { sql: "SELECT id, !pixel_width! as width, !pixel_height! as height, !scale_denominator! as scale_denom FROM foo WHERE geom && !BBOX!", layer: Layer{srid: tegola.WebMercator}, tile: provider.NewTile(11, 1070, 676, 64, tegola.WebMercator), - expected: "SELECT id, 76.43702827 as width, 76.43702827 as height, 272989.38669477 as scale_denom FROM foo WHERE geom && ST_MakeEnvelope(899816.69684784,6789748.34757049,919996.07231232,6809927.72303497,3857)", + expected: "SELECT id, 76.43702829 as width, 76.43702829 as height, 272989.38673277 as scale_denom FROM foo WHERE geom && ST_MakeEnvelope(899816.69697309,6789748.34851564,919996.07244038,6809927.72398292,3857)", }, } diff --git a/provider/provider.go b/provider/provider.go index 4cd7b9187..d66d7c53a 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -14,7 +14,7 @@ import ( // providerType defines the type of providers we have in the system. // Standard providers allow layers to be co-mingled from different data sources // because Tegola takes care of the geometry processing and mvt generation. -// MVT providers do not allow layers to be co-mingled and bypasses tegola's geometry +// MVT providers do not allow layers to be co-mingled and bypasses Tegola's geometry // processing and mvt generation. type providerType uint8 @@ -30,17 +30,9 @@ const ( ) var ( - webmercatorGrid slippy.Grid + webmercatorGrid = slippy.NewGrid(3857, 0) ) -func init() { - var err error - webmercatorGrid, err = slippy.NewGrid(3857) - if err != nil { - log.Fatal("Could not create Web Mercator grid (3857): ", err) - } -} - func (pt providerType) Prefix() string { if pt == TypeMvt { return "mvt_" @@ -88,7 +80,7 @@ type tile_t struct { } // NewTile creates a new slippy tile with a Buffer -func NewTile(z, x, y, buf, srid uint) Tile { +func NewTile(z slippy.Zoom, x uint, y uint, buf, srid uint) Tile { return &tile_t{ Tile: slippy.Tile{ Z: z, @@ -101,29 +93,27 @@ func NewTile(z, x, y, buf, srid uint) Tile { // Extent returns the extent of the tile func (tile *tile_t) Extent() (ext *geom.Extent, srid uint64) { - if ext, ok := slippy.Extent(webmercatorGrid, &tile.Tile); ok { - return ext, 3857 + var err error + ext, err = slippy.Extent(webmercatorGrid, tile.Tile) + if err != nil { + log.Error("Could not generate valid extent for tile.", tile, err) + return &geom.Extent{}, 3857 } + return ext, 3857 - log.Error("Could not generate valid extent for tile.", tile) - return &geom.Extent{}, 3857 } -// BufferedExtent returns a the extent of the tile, with the define buffer +// BufferedExtent returns an extent of the tile, with the define buffer func (tile *tile_t) BufferedExtent() (ext *geom.Extent, srid uint64) { ext, _ = tile.Extent() - // WARNING: slippy.PixelsToNative() is misnamed. It doesn't convert the given number of pixels into - // anything--the pixels arguments isn't even used (hence set to 0 here). Instead, slippy.PixelsToNative() - // return the ratio of pixels to projected units at the given zoom. We multiply its return value by - // the pixel count in tile.buffer to get the expected conversion. - return ext.ExpandBy(slippy.PixelsToNative(webmercatorGrid, tile.Z, 0) * float64(tile.buffer)), 3857 + return ext.ExpandBy(slippy.MvtPixelRationForZoom(webmercatorGrid, tile.Z) * float64(tile.buffer)), 3857 } // Tile is an interface used by Tiler, it is an unnecessary abstraction and is -// due to be removed. The tiler interface will, instead take a, *geom.Extent. +// due to be removed. The tiler interface will, instead take a *geom.Extent. type Tile interface { // ZXY returns the z, x and y values of the tile - ZXY() (uint, uint, uint) + ZXY() (slippy.Zoom, uint, uint) // Extent returns the extent of the tile excluding any buffer Extent() (extent *geom.Extent, srid uint64) // BufferedExtent returns the extent of the tile including any buffer @@ -137,7 +127,7 @@ var ParameterTokenRegexp = regexp.MustCompile("![a-zA-Z0-9_-]+!") type Tiler interface { Layerer - // TileFeature will stream decoded features to the callback function fn + // TileFeatures will stream decoded features to the callback function fn // if fn returns ErrCanceled, the TileFeatures method should stop processing TileFeatures(ctx context.Context, layer string, t Tile, params Params, fn func(f *Feature) error) error } @@ -163,7 +153,7 @@ func (tu TilerUnion) Layers() ([]LayerInfo, error) { // InitFunc initialize a provider given a config map. The init function should validate the config map, and report any errors. This is called by the For function. type InitFunc func(dicter dict.Dicter, maps []Map) (Tiler, error) -// CleanupFunc is called to when the system is shutting down, this allows the provider to cleanup. +// CleanupFunc is called to when the system is shutting down, this allows the provider to clean up. type CleanupFunc func() type pfns struct { @@ -179,7 +169,7 @@ var providers map[string]pfns // Register the provider with the system. This call is generally made in the init functions of the provider. // -// the clean up function will be called during shutdown of the provider to allow the provider to do any cleanup. +// the cleanup function will be called during shutdown of the provider to allow the provider to do any cleanup. // // The init function can not be nil, the cleanup function may be nil func Register(name string, init InitFunc, cleanup CleanupFunc) error { @@ -204,7 +194,7 @@ func Register(name string, init InitFunc, cleanup CleanupFunc) error { // MVTRegister the provider with the system. This call is generally made in the init functions of the provider. // -// the clean up function will be called during shutdown of the provider to allow the provider to do any cleanup. +// the cleanup function will be called during shutdown of the provider to allow the provider to do any cleanup. // // The init function can not be nil, the cleanup function may be nil func MVTRegister(name string, init MVTInitFunc, cleanup CleanupFunc) error { @@ -284,7 +274,7 @@ func For(name string, config dict.Dicter, maps []Map) (val TilerUnion, err error return val, ErrInvalidRegisteredProvider{Name: name} } -// Cleanup is called at the end of the run to allow providers to cleanup +// Cleanup is called at the end of the run to allow providers to clean up func Cleanup() { log.Info("cleaning up providers") for _, p := range providers { diff --git a/server/handle_map_layer_zxy.go b/server/handle_map_layer_zxy.go index 652116396..24649602b 100644 --- a/server/handle_map_layer_zxy.go +++ b/server/handle_map_layer_zxy.go @@ -23,17 +23,9 @@ import ( ) var ( - webmercatorGrid slippy.Grid + webmercatorGrid = slippy.NewGrid(3857, 0) ) -func init() { - var err error - webmercatorGrid, err = slippy.NewGrid(3857) - if err != nil { - log.Fatal("Could not create Web Mercator grid (3857): ", err) - } -} - type HandleMapLayerZXY struct { // required mapName string @@ -137,7 +129,7 @@ func (req HandleMapLayerZXY) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // filter down the layers we need for this zoom - m = m.FilterLayersByZoom(req.z) + m = m.FilterLayersByZoom(slippy.Zoom(req.z)) if len(m.Layers) == 0 { msg := fmt.Sprintf("map (%v) has no layers, at zoom %v", req.mapName, req.z) log.Debug(msg) @@ -156,16 +148,16 @@ func (req HandleMapLayerZXY) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - tile := slippy.NewTile(req.z, req.x, req.y) + tile := slippy.Tile{Z: slippy.Zoom(req.z), X: req.x, Y: req.y} { // Check to see that the zxy is within the bounds of the map. // TODO(@ear7h): use a more efficient version of Intersect that doesn't // make a new extent - ext3857, ok := slippy.Extent(webmercatorGrid, tile) - if !ok { + ext3857, err := slippy.Extent(webmercatorGrid, tile) + if err != nil { msg := fmt.Sprintf("map (%v -- %v) does not contains tile at %v/%v/%v. Unable to generate extent.", req.mapName, m.Bounds, req.z, req.x, req.y) - log.Debug(msg) + log.Debug(msg, err) http.Error(w, msg, http.StatusNotFound) return } @@ -180,7 +172,7 @@ func (req HandleMapLayerZXY) ServeHTTP(w http.ResponseWriter, r *http.Request) { ext4326 := &geom.Extent{} copy(ext4326[:], points4326) - if _, intersect := m.Bounds.Intersect(ext4326); !intersect || !ok { + if _, intersect := m.Bounds.Intersect(ext4326); !intersect { msg := fmt.Sprintf("map (%v -- %v) does not contains tile at %v/%v/%v -- %v", req.mapName, m.Bounds, req.z, req.x, req.y, ext4326) log.Debug(msg) http.Error(w, msg, http.StatusNotFound) diff --git a/tile.go b/tile.go index 38ecd7bc5..335e28dcb 100644 --- a/tile.go +++ b/tile.go @@ -5,6 +5,7 @@ import ( "math" "github.com/go-spatial/geom" + "github.com/go-spatial/geom/slippy" "github.com/go-spatial/tegola/maths/webmercator" ) @@ -54,6 +55,8 @@ func NewTile(z, x, y uint) (t *Tile) { return t } +func TileFromSlippyTile(t slippy.Tile) *Tile { return NewTile(uint(t.Z), t.X, t.Y) } + // NewTileLatLong will return a non-nil tile object. func NewTileLatLong(z uint, lat, lon float64) (t *Tile) { t = &Tile{