From 8a49707e4d2a0763f9dbbfa877e5ab195468f08c Mon Sep 17 00:00:00 2001 From: Gautam Dey Date: Thu, 8 Mar 2018 10:47:49 -0800 Subject: [PATCH] Removed tegola.BoundingBox and updated slippy #270 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Removed BoundingBox for tegola.Tile, and replaced it with geom.BoundingBox. • Added Bounds method to get the {north, east, south, west} values. • Replaced [4]float64 with *geom.BoundingBox for BufferedExtent and Extent on slippy.Tile. --- bounding_box.go | 17 --- cmd/tegola/cmd/cache.go | 22 +--- cmd/xyz2svg/cmd/draw.go | 2 +- geom/bbox.go | 30 ++++- geom/bbox_test.go | 81 +++++++++++-- geom/slippy/tile.go | 108 ++++++++++++++--- geom/slippy/tile_test.go | 160 +++++++++++++++++++++---- mvt/feature.go | 2 +- mvt/feature_test.go | 9 +- mvt/layer_test.go | 10 -- provider/debug/debug.go | 21 ++-- provider/gpkg/gpkg.go | 14 +-- provider/gpkg/gpkg_register.go | 2 +- provider/gpkg/gpkg_test.go | 81 ++++++------- provider/gpkg/util.go | 2 +- provider/gpkg/util_internal_test.go | 6 +- provider/postgis/util.go | 8 +- provider/postgis/util_internal_test.go | 6 +- provider/provider.go | 4 +- provider/test/provider.go | 13 +- tile.go | 138 +++++++-------------- tile_test.go | 39 ------ 22 files changed, 445 insertions(+), 330 deletions(-) delete mode 100644 bounding_box.go diff --git a/bounding_box.go b/bounding_box.go deleted file mode 100644 index 8b428fa41..000000000 --- a/bounding_box.go +++ /dev/null @@ -1,17 +0,0 @@ -package tegola - -//BoundingBox defines the extent of a tile -type BoundingBox struct { - Minx, Miny, Maxx, Maxy float64 - // Epsilon is the tolerance for the simplification function. - Epsilon float64 - // X,Y,Z are just for debug and display purposes. - X, Y, Z int - HasXYZ bool -} - -// Adhear to the MinMaxer interface. -func (bb BoundingBox) MinX() float64 { return bb.Minx } -func (bb BoundingBox) MaxX() float64 { return bb.Maxx } -func (bb BoundingBox) MinY() float64 { return bb.Miny } -func (bb BoundingBox) MaxY() float64 { return bb.Maxy } diff --git a/cmd/tegola/cmd/cache.go b/cmd/tegola/cmd/cache.go index f3bf5e962..24da96714 100644 --- a/cmd/tegola/cmd/cache.go +++ b/cmd/tegola/cmd/cache.go @@ -17,7 +17,6 @@ import ( "github.com/go-spatial/tegola/atlas" "github.com/go-spatial/tegola/cache" "github.com/go-spatial/tegola/internal/log" - "github.com/go-spatial/tegola/maths/webmercator" "github.com/go-spatial/tegola/provider" ) @@ -97,25 +96,8 @@ var cacheCmd = &cobra.Command{ } zooms = append(zooms, t.Z) - // read the tile bounds, which will be in web mercator - tBounds := t.BoundingBox() - // convert the bounds points to lat lon - ul, err := webmercator.PToLonLat(tBounds.Minx, tBounds.Miny) - if err != nil { - log.Fatal(err) - } - lr, err := webmercator.PToLonLat(tBounds.Maxx, tBounds.Maxy) - if err != nil { - log.Fatal(err) - } - // use the tile bounds as the bounds for the job. - // the grid flips between web mercator and WGS84 which is why we use must use lat from one point and lon from the other - // TODO: this smells funny. Investigate why the grid is flipping - arolek - bounds[0] = ul[0] - bounds[1] = lr[1] - - bounds[2] = lr[0] - bounds[3] = ul[1] + // read the tile bounds, which will be in lat, lon, it will be the north, east, south, west. + bounds = t.Bounds() } else { // bounding box caching boundsParts := strings.Split(cacheBounds, ",") diff --git a/cmd/xyz2svg/cmd/draw.go b/cmd/xyz2svg/cmd/draw.go index 1fa1ba654..6f8537be1 100644 --- a/cmd/xyz2svg/cmd/draw.go +++ b/cmd/xyz2svg/cmd/draw.go @@ -164,7 +164,7 @@ func drawFeatures(pname string, tiler provider.Tiler, layers []string, gid int, } // Clip and validate - ext := geom.NewBBox(pbb[0], pbb[1]) + ext := geom.NewBBox([2]float64{pbb[0], pbb[1]}, [2]float64{pbb[2], pbb[3]}) vg, err := validate.CleanGeometry(ctx, sg, ext) // Draw each of the steps. diff --git a/geom/bbox.go b/geom/bbox.go index 372dd9655..ed2c9c57f 100644 --- a/geom/bbox.go +++ b/geom/bbox.go @@ -81,6 +81,20 @@ func (bb *BoundingBox) MinY() float64 { return bb[1] } +// TODO (gdey): look at how to have this function take into account the dpi. +func (bb *BoundingBox) XSpan() float64 { + if bb == nil { + return math.Inf(1) + } + return bb[2] - bb[0] +} +func (bb *BoundingBox) YSpan() float64 { + if bb == nil { + return math.Inf(1) + } + return bb[3] - bb[1] +} + func (bb *BoundingBox) BBox() [4]float64 { return [4]float64{bb.MinX(), bb.MinY(), bb.MaxX(), bb.MaxY()} } @@ -194,7 +208,21 @@ func (bb *BoundingBox) ScaleBy(s float64) *BoundingBox { if bb == nil { return nil } - return &BoundingBox{bb[0] * s, bb[1] * s, bb[2] * s, bb[3] * s} + return NewBBox( + [2]float64{bb[0] * s, bb[1] * s}, + [2]float64{bb[2] * s, bb[3] * s}, + ) +} + +// ExpandBy will expand bounding box by the given factor. +func (bb *BoundingBox) ExpandBy(s float64) *BoundingBox { + if bb == nil { + return nil + } + return NewBBox( + [2]float64{bb[0] - s, bb[1] - s}, + [2]float64{bb[2] + s, bb[3] + s}, + ) } func (bb *BoundingBox) Clone() *BoundingBox { diff --git a/geom/bbox_test.go b/geom/bbox_test.go index 6ceca18b7..aeb7d1e28 100644 --- a/geom/bbox_test.go +++ b/geom/bbox_test.go @@ -243,9 +243,15 @@ func TestBBoxAttributes(t *testing.T) { return pt[0] == x && pt[1] == y } - fn := func(t *testing.T, bb *geom.BoundingBox) { + type tcase struct { + bb *geom.BoundingBox + xspan, yspan float64 + } + + fn := func(t *testing.T, tc tcase) { t.Parallel() + bb := tc.bb { vert := bb.Vertices() @@ -327,13 +333,36 @@ func TestBBoxAttributes(t *testing.T) { } cbb := bb.Clone() if !cmp.BBox(bb, cbb) { - t.Errorf("Clone, equal, expected (%v) true got (%v) false", bb, cbb) + t.Errorf("Clone equal, expected (%v) true got (%v) false", bb, cbb) + } + xspan := bb.XSpan() + if tc.xspan == math.Inf(1) && xspan != tc.xspan { + t.Errorf("xspan, expected ∞ got %v", xspan) + } else { + if !cmp.Float(tc.xspan, xspan) { + t.Errorf("xspan, expected %v got %v", tc.xspan, xspan) + } + } + yspan := bb.YSpan() + if tc.yspan == math.Inf(1) && yspan != tc.yspan { + t.Errorf("yspan, expected ∞ got %v", yspan) + } else { + if !cmp.Float(tc.yspan, yspan) { + t.Errorf("yspan, expected %v got %v", tc.yspan, yspan) + } } - } - tests := map[string]*geom.BoundingBox{ - "std": &geom.BoundingBox{0.0, 0.0, 10.0, 10.0}, - "nil": nil, + tests := map[string]tcase{ + "std": tcase{ + bb: &geom.BoundingBox{0.0, 0.0, 10.0, 10.0}, + xspan: 10.0, + yspan: 10.0, + }, + "nil": tcase{ + bb: nil, + xspan: math.Inf(1), + yspan: math.Inf(1), + }, } for name, tc := range tests { tc := tc @@ -349,7 +378,7 @@ func TestBBoxScaleBy(t *testing.T) { } fn := func(t *testing.T, tc tcase) { sbb := tc.bb.ScaleBy(tc.scale) - if !reflect.DeepEqual(sbb, tc.ebb) { + if !cmp.BBox(tc.ebb, sbb) { t.Errorf("Scale by, expected %v got %v", tc.ebb, sbb) } } @@ -367,6 +396,44 @@ func TestBBoxScaleBy(t *testing.T) { ebb: &geom.BoundingBox{0, 0, 20, 20}, scale: 2.0, }, + "-2.0 scale": tcase{ + bb: &geom.BoundingBox{0, 0, 10, 10}, + ebb: &geom.BoundingBox{-20, -20, 0, 0}, + scale: -2.0, + }, + } + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { fn(t, tc) }) + } +} + +func TestBBoxExpandBy(t *testing.T) { + type tcase struct { + bb *geom.BoundingBox + factor float64 + ebb *geom.BoundingBox + } + fn := func(t *testing.T, tc tcase) { + sbb := tc.bb.ExpandBy(tc.factor) + if !cmp.BBox(tc.ebb, sbb) { + t.Errorf("Expand by, expected %v got %v", tc.ebb, sbb) + } + } + tests := map[string]tcase{ + "nil": tcase{ + factor: 2.0, + }, + "1.0 factor": tcase{ + bb: &geom.BoundingBox{0, 0, 10, 10}, + ebb: &geom.BoundingBox{-1, -1, 11, 11}, + factor: 1.0, + }, + "-20.1 factor": tcase{ + bb: &geom.BoundingBox{0, 0, 10, 10}, + ebb: &geom.BoundingBox{-10.1, -10.1, 20.1, 20.1}, + factor: -20.1, + }, } for name, tc := range tests { tc := tc diff --git a/geom/slippy/tile.go b/geom/slippy/tile.go index 957492a40..b6a923bf4 100644 --- a/geom/slippy/tile.go +++ b/geom/slippy/tile.go @@ -1,6 +1,10 @@ package slippy -import "math" +import ( + "math" + + "github.com/go-spatial/tegola/geom" +) func NewTile(z, x, y uint64, buffer float64, srid uint64) *Tile { return &Tile{ @@ -26,49 +30,115 @@ type Tile struct { SRID uint64 } -func (t *Tile) ZXY() (uint64, uint64, uint64) { - return t.z, t.x, t.y +func (t *Tile) ZXY() (uint64, uint64, uint64) { return t.z, t.x, t.y } + +func Tile2Lon(x, z uint64) float64 { return float64(x)/math.Exp2(float64(z))*360.0 - 180.0 } + +func Tile2Lat(y, z uint64) float64 { + var n float64 = math.Pi + if y != 0 { + n = math.Pi - 2.0*math.Pi*float64(y)/math.Exp2(float64(z)) + } + + return 180.0 / math.Pi * math.Atan(0.5*(math.Exp(n)-math.Exp(-n))) } -// TODO(arolek): return geom.Extent once it has been refactored +// Bounds returns the bounds of the Tile as defined by the East most longitude, North most latitude, West most longitude, South most latitude. +func (t *Tile) Bounds() [4]float64 { + east := Tile2Lon(t.x, t.z) + west := Tile2Lon(t.x+1, t.z) + north := Tile2Lat(t.y, t.z) + south := Tile2Lat(t.y+1, t.z) + + return [4]float64{east, north, west, south} +} + +/* + // This is how we convert from the Bounds, and TileSize to Extent for Webmercator. + bounds := t.Bounds() + east,north,west, south := bounds[0],bounds[1],bounds[2],bounds[3] + + TileSize := 4096.0 + c, err := webmercator.PToXY(east, north, west, south) + log.Println("c", c, "err", err) + + extent := geom.NewBBox( + [2]float64{c[0], c[1]}, + [2]float64{c[2], c[3]}, + ) + + xspan := extent.XSpan() + yspan := extent.YSpan() + + log.Println("Extent", extent, "MinX", extent.MinX(), "MinY", extent.MinY(), "xspan", xspan, "yspan", yspan) + + // To get the Buffered Extent, we just need the extent and the Buffer size. + // Convert to tile coordinates. + nx := float64(int64((c[0] - extent.MinX()) * TileSize / xspan)) + ny := float64(int64((c[1] - extent.MinY()) * TileSize / yspan)) + mx := float64(int64((c[2] - extent.MinX()) * TileSize / xspan)) + my := float64(int64((c[3] - extent.MinY()) * TileSize / yspan)) + + mextent := geom.NewBBox([2]float64{nx, ny}, [2]float64{mx, my}).ExpandBy(64) + log.Println("mxy[", nx, ny, mx, my, "]", "err", err, "mext", mextent) + bext := geom.NewBBox( + [2]float64{ + (mextent.MinX() * xspan / TileSize) + extent.MinX(), + (mextent.MinY() * yspan / TileSize) + extent.MinY(), + }, + [2]float64{ + (mextent.MaxX() * xspan / TileSize) + extent.MinX(), + (mextent.MaxY() * yspan / TileSize) + extent.MinY(), + }, + ) + log.Println("bext", bext) +*/ + // TODO(arolek): support alternative SRIDs. Currently this assumes 3857 // Extent will return the tile extent excluding the tile's buffer and the Extent's SRID -func (t *Tile) Extent() (extent [2][2]float64, srid uint64) { +func (t *Tile) Extent() (extent *geom.BoundingBox, srid uint64) { max := 20037508.34 // resolution res := (max * 2) / math.Exp2(float64(t.z)) // unbuffered extent - return [2][2]float64{ - { + return geom.NewBBox( + [2]float64{ -max + (float64(t.x) * res), // MinX max - (float64(t.y) * res), // Miny }, - { + [2]float64{ -max + (float64(t.x) * res) + res, // MaxX max - (float64(t.y) * res) - res, // MaxY - }, - }, t.SRID + ), t.SRID } // BufferedExtent will return the tile extent including the tile's buffer and the Extent's SRID -func (t *Tile) BufferedExtent() (bufferedExtent [2][2]float64, srid uint64) { +func (t *Tile) BufferedExtent() (bufferedExtent *geom.BoundingBox, srid uint64) { extent, _ := t.Extent() // TODO(arolek): the following value is hard coded for MVT, but this concept needs to be abstracted to support different projections mvtTileWidthHeight := 4096.0 // the bounds / extent - mvtTileExtent := [2][2]float64{{0 - t.Buffer, 0 - t.Buffer}, {mvtTileWidthHeight + t.Buffer, mvtTileWidthHeight + t.Buffer}} - - xspan := extent[1][0] - extent[0][0] - yspan := extent[1][1] - extent[0][1] + mvtTileExtent := [4]float64{ + 0 - t.Buffer, 0 - t.Buffer, + mvtTileWidthHeight + t.Buffer, mvtTileWidthHeight + t.Buffer, + } - bufferedExtent[0][0] = (mvtTileExtent[0][0] * xspan / mvtTileWidthHeight) + extent[0][0] - bufferedExtent[0][1] = (mvtTileExtent[0][1] * yspan / mvtTileWidthHeight) + extent[0][1] - bufferedExtent[1][0] = (mvtTileExtent[1][0] * xspan / mvtTileWidthHeight) + extent[0][0] - bufferedExtent[1][1] = (mvtTileExtent[1][1] * yspan / mvtTileWidthHeight) + extent[0][1] + xspan := extent.MaxX() - extent.MinX() + yspan := extent.MaxY() - extent.MinY() + bufferedExtent = geom.NewBBox( + [2]float64{ + (mvtTileExtent[0] * xspan / mvtTileWidthHeight) + extent.MinX(), + (mvtTileExtent[1] * yspan / mvtTileWidthHeight) + extent.MinY(), + }, + [2]float64{ + (mvtTileExtent[2] * xspan / mvtTileWidthHeight) + extent.MinX(), + (mvtTileExtent[3] * yspan / mvtTileWidthHeight) + extent.MinY(), + }, + ) return bufferedExtent, t.SRID } diff --git a/geom/slippy/tile_test.go b/geom/slippy/tile_test.go index 20a67228d..5f90b8acd 100644 --- a/geom/slippy/tile_test.go +++ b/geom/slippy/tile_test.go @@ -1,25 +1,38 @@ package slippy_test import ( + "strconv" "testing" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/geom" + "github.com/go-spatial/tegola/geom/cmp" "github.com/go-spatial/tegola/geom/slippy" ) -func TestExtent(t *testing.T) { - testcases := []struct { +/* + +func TestTileExtent(t *testing.T) { + type tcase struct { tile *slippy.Tile expectedExtent [2][2]float64 - }{ - { + } + fn := func(t *testing.T, tc tcase) { + extent, _ := tc.tile.Extent() + + if tc.expectedExtent != extent { + t.Errorf("extent, expected %v got %v", tc.expectedExtent, extent) + } + } + tests := []tcase{ + tcase{ tile: slippy.NewTile(2, 1, 1, 64, tegola.WebMercator), expectedExtent: [2][2]float64{ [2]float64{-10018754.17, 10018754.17}, [2]float64{0, 0}, }, }, - { + tcase{ tile: slippy.NewTile(16, 11436, 26461, 64, tegola.WebMercator), expectedExtent: [2][2]float64{ [2]float64{-13044437.497219238996, 3856706.6986199953}, @@ -27,23 +40,26 @@ func TestExtent(t *testing.T) { }, }, } - - for i, tc := range testcases { - extent, _ := tc.tile.Extent() - - if tc.expectedExtent != extent { - t.Errorf("[%v] expected: %v got: %v", i, tc.expectedExtent, extent) - continue - } + for name, tc := range tests { + tc := tc + t.Run(strconv.FormatUint(uint64(name), 10), func(t *testing.T) { fn(t, tc) }) } } -func TestBufferedExtent(t *testing.T) { - testcases := []struct { +func TestTileBufferedExtent(t *testing.T) { + type tcase struct { tile *slippy.Tile expectedExtent [2][2]float64 - }{ - { + } + fn := func(t *testing.T, tc tcase) { + bufferedExtent, _ := tc.tile.BufferedExtent() + + if tc.expectedExtent != bufferedExtent { + t.Errorf("buffered extent, expected %v got %v", tc.expectedExtent, bufferedExtent) + } + } + tests := []tcase{ + tcase{ tile: slippy.NewTile(2, 1, 1, 64, tegola.WebMercator), expectedExtent: [2][2]float64{ [2]float64{-1.017529720390625e+07, 1.017529720390625e+07}, @@ -52,12 +68,112 @@ func TestBufferedExtent(t *testing.T) { }, } - for i, tc := range testcases { - bufferedExtent, _ := tc.tile.BufferedExtent() + for name, tc := range tests { + tc := tc + t.Run(strconv.FormatUint(uint64(name), 10), func(t *testing.T) { fn(t, tc) }) + } +} +*/ - if tc.expectedExtent != bufferedExtent { - t.Errorf("[%v] expected: %v got: %v", i, tc.expectedExtent, bufferedExtent) - continue +func TestNewTile(t *testing.T) { + type tcase struct { + z, x, y uint64 + buffer float64 + srid uint64 + eBounds [4]float64 + eExtent *geom.BoundingBox + eBExtent *geom.BoundingBox + } + fn := func(t *testing.T, tc tcase) { + + // Test the new functions. + tile := slippy.NewTile(tc.z, tc.x, tc.y, tc.buffer, tc.srid) + { + gz, gx, gy := tile.ZXY() + if gz != tc.z { + t.Errorf("z, expected %v got %v", tc.z, gz) + } + if gx != tc.x { + t.Errorf("x, expected %v got %v", tc.x, gx) + } + if gy != tc.y { + t.Errorf("y, expected %v got %v", tc.y, gy) + } + if tile.Buffer != tc.buffer { + t.Errorf("buffer, expected %v got %v", tc.buffer, tile.Buffer) + } + if tile.SRID != tc.srid { + t.Errorf("srid, expected %v got %v", tc.srid, tile.SRID) + } + } + { + bounds := tile.Bounds() + for i := 0; i < 4; i++ { + if !cmp.Float64(bounds[i], tc.eBounds[i], 0.01) { + t.Errorf("bounds[%v] , expected %v got %v", i, tc.eBounds[i], bounds[i]) + + } + } + } + { + bufferedExtent, srid := tile.BufferedExtent() + if srid != tc.srid { + t.Errorf("buffered extent srid, expected %v got %v", tc.srid, srid) + } + + if !cmp.BBox(tc.eBExtent, bufferedExtent) { + t.Errorf("buffered extent, expected %v got %v", tc.eBExtent, bufferedExtent) + } + } + { + extent, srid := tile.Extent() + if srid != tc.srid { + t.Errorf("extent srid, expected %v got %v", tc.srid, srid) + } + + if !cmp.BBox(tc.eExtent, extent) { + t.Errorf("extent, expected %v got %v", tc.eExtent, extent) + } } + + } + tests := [...]tcase{ + tcase{ + z: 2, + x: 1, + y: 1, + buffer: 64, + srid: tegola.WebMercator, + eExtent: geom.NewBBox( + [2]float64{-10018754.17, 10018754.17}, + [2]float64{0, 0}, + ), + eBExtent: geom.NewBBox( + [2]float64{-1.017529720390625e+07, 1.017529720390625e+07}, + [2]float64{156543.03390624933, -156543.03390624933}, + ), + eBounds: [4]float64{-90, 66.51, 0, 0}, + }, + tcase{ + z: 16, + x: 11436, + y: 26461, + buffer: 64, + srid: tegola.WebMercator, + eExtent: geom.NewBBox( + [2]float64{-13044437.497219238996, 3856706.6986199953}, + [2]float64{-13043826.000993041, 3856095.202393799}, + ), + eBExtent: geom.NewBBox( + [2]float64{-1.3044447051847773e+07, 3.8567162532485295e+06}, + [2]float64{-1.3043816446364507e+07, 3.856085647765265e+06}, + ), + eBounds: [4]float64{-117.18, 32.70, -117.17, 32.70}, + }, + } + for i, tc := range tests { + tc := tc + t.Run(strconv.FormatUint(uint64(i), 10), func(t *testing.T) { fn(t, tc) }) } + } diff --git a/mvt/feature.go b/mvt/feature.go index a5a2a64d2..766e42f91 100644 --- a/mvt/feature.go +++ b/mvt/feature.go @@ -592,7 +592,7 @@ func encodeGeometry(ctx context.Context, geometry tegola.Geometry, tile *tegola. if err != nil { return nil, vectorTile.Tile_UNKNOWN, err } - ext := geom.NewBBox(pbb[0], pbb[1]) + ext := geom.NewBBox([2]float64{pbb[0], pbb[1]}, [2]float64{pbb[2], pbb[3]}) geometry, err = validate.CleanGeometry(ctx, sg, ext) if err != nil { diff --git a/mvt/feature_test.go b/mvt/feature_test.go index eb1a72859..627acb937 100644 --- a/mvt/feature_test.go +++ b/mvt/feature_test.go @@ -63,7 +63,6 @@ func TestEncodeGeometry(t *testing.T) { desc string `tbltest:"desc"` geo basic.Geometry typ vectorTile.Tile_GeomType - bbox tegola.BoundingBox egeo []uint32 eerr error } @@ -172,13 +171,7 @@ func TestEncodeGeometry(t *testing.T) { }, }, }, - typ: vectorTile.Tile_POLYGON, - bbox: tegola.BoundingBox{ - Minx: 0, - Miny: 0, - Maxx: 4096, - Maxy: 4096, - }, + typ: vectorTile.Tile_POLYGON, egeo: []uint32{9, 0, 0, 26, 18, 0, 0, 18, 17, 0, 15, 9, 22, 4, 26, 18, 0, 0, 18, 17, 0, 15, 9, 2, 15, 26, 0, 8, 8, 0, 0, 7, 15}, }, ).Run(fn) diff --git a/mvt/layer_test.go b/mvt/layer_test.go index f8cc68132..932732d5f 100644 --- a/mvt/layer_test.go +++ b/mvt/layer_test.go @@ -132,12 +132,6 @@ func TestLayerAddFeatures(t *testing.T) { func TestLayer(t *testing.T) { tile := tegola.NewTile(0, 0, 0) - baseBBox := tegola.BoundingBox{ - Minx: 0, - Miny: 0, - Maxx: 4096, - Maxy: 4096, - } //TODO: gdey — thing of a better way to build out features for a layer. fromPixel := func(x, y float64) *basic.Point { pt, err := tile.FromPixel(tegola.WebMercator, [2]float64{x, y}) @@ -150,7 +144,6 @@ func TestLayer(t *testing.T) { type tc struct { layer *Layer vtlayer *vectorTile.Tile_Layer - bbox tegola.BoundingBox eerr error } fn := func(i int, tcase tc) { @@ -194,7 +187,6 @@ func TestLayer(t *testing.T) { Name: "nofeatures", }, vtlayer: newTileLayer("nofeatures", nil, nil, nil), - bbox: baseBBox, }, tc{ layer: &Layer{ @@ -212,7 +204,6 @@ func TestLayer(t *testing.T) { // features should not be nil, when we start comparing features this will fail. // But for now it's okay. vtlayer: newTileLayer("onefeature", []string{"tag1", "tag2"}, []*vectorTile.Tile_Value{vectorTileValue("tag")}, []*vectorTile.Tile_Feature{nil}), - bbox: baseBBox, }, tc{ layer: &Layer{ @@ -243,7 +234,6 @@ func TestLayer(t *testing.T) { // features should not be nil, when we start comparing features this will fail. // But for now it's okay. vtlayer: newTileLayer("twofeature", []string{"tag1", "tag2"}, []*vectorTile.Tile_Value{vectorTileValue("tag1")}, []*vectorTile.Tile_Feature{nil, nil}), - bbox: baseBBox, }, ).Run(fn) } diff --git a/provider/debug/debug.go b/provider/debug/debug.go index 50656813f..163be9aef 100644 --- a/provider/debug/debug.go +++ b/provider/debug/debug.go @@ -39,16 +39,9 @@ func (p *Provider) TileFeatures(ctx context.Context, layer string, tile provider switch layer { case "debug-tile-outline": debugTileOutline := provider.Feature{ - ID: 0, - Geometry: geom.Polygon{ - [][2]float64{ - [2]float64{ext[0][0], ext[0][1]}, // Minx, Miny - [2]float64{ext[1][0], ext[0][1]}, // Maxx, Miny - [2]float64{ext[1][0], ext[1][1]}, // Maxx, Maxy - [2]float64{ext[0][0], ext[1][1]}, // Minx, Maxy - }, - }, - SRID: srid, + ID: 0, + Geometry: ext.AsPolygon(), + SRID: srid, Tags: map[string]interface{}{ "type": "debug_buffer_outline", }, @@ -59,17 +52,17 @@ func (p *Provider) TileFeatures(ctx context.Context, layer string, tile provider } case "debug-tile-center": - xlen := ext[1][0] - ext[0][0] // Maxx - Minx - ylen := ext[1][1] - ext[0][1] // Maxy - Miny + xlen := ext.XSpan() + ylen := ext.YSpan() z, x, y := tile.ZXY() debugTileCenter := provider.Feature{ ID: 1, Geometry: geom.Point{ // Minx - ext[0][0] + (xlen / 2), + ext.MinX() + (xlen / 2), // Miny - ext[0][1] + (ylen / 2), + ext.MinY() + (ylen / 2), }, SRID: srid, Tags: map[string]interface{}{ diff --git a/provider/gpkg/gpkg.go b/provider/gpkg/gpkg.go index 756dc12db..4eb71ae09 100644 --- a/provider/gpkg/gpkg.go +++ b/provider/gpkg/gpkg.go @@ -81,28 +81,22 @@ func (p *Provider) TileFeatures(ctx context.Context, layer string, tile provider pLayer := p.layers[layer] // read the tile extent - bufferedExtent, tileSRID := tile.BufferedExtent() - - // TODO: leverage minx/y maxx/y methods once the BufferedExtent returns a geom.Extent type - tileBBox := geom.BoundingBox{ - bufferedExtent[0][0], bufferedExtent[0][1], //minx, miny - bufferedExtent[1][0], bufferedExtent[1][1], //maxx, maxy - } + tileBBox, tileSRID := tile.BufferedExtent() // TODO(arolek): reimplement once the geom package has reprojection // check if the SRID of the layer differs from that of the tile. tileSRID is assumed to always be WebMercator if pLayer.srid != tileSRID { - minGeo, err := basic.FromWebMercator(pLayer.srid, basic.Point{bufferedExtent[0][0], bufferedExtent[0][1]}) + minGeo, err := basic.FromWebMercator(pLayer.srid, basic.Point{tileBBox.MinX(), tileBBox.MinY()}) if err != nil { return fmt.Errorf("error converting point: %v ", err) } - maxGeo, err := basic.FromWebMercator(pLayer.srid, basic.Point{bufferedExtent[1][0], bufferedExtent[1][1]}) + maxGeo, err := basic.FromWebMercator(pLayer.srid, basic.Point{tileBBox.MaxX(), tileBBox.MaxY()}) if err != nil { return fmt.Errorf("error converting point: %v ", err) } - tileBBox = geom.BoundingBox{ + tileBBox = &geom.BoundingBox{ minGeo.AsPoint().X(), minGeo.AsPoint().Y(), maxGeo.AsPoint().X(), maxGeo.AsPoint().Y(), } diff --git a/provider/gpkg/gpkg_register.go b/provider/gpkg/gpkg_register.go index a0a263cea..fe62f8439 100644 --- a/provider/gpkg/gpkg_register.go +++ b/provider/gpkg/gpkg_register.go @@ -190,7 +190,7 @@ func NewTileProvider(config map[string]interface{}) (provider.Tiler, error) { // Bounds checks need params: maxx, minx, maxy, miny // TODO(arolek): this assumes WGS84. should be more flexible wgs84BB := geom.NewBBox([2]float64{180.0, 85.0551}, [2]float64{-180.0, -85.0511}) - customSQL = replaceTokens(customSQL, 0, *wgs84BB) + customSQL = replaceTokens(customSQL, 0, wgs84BB) // Get geometry type & srid from geometry of first row. qtext := fmt.Sprintf("SELECT geom FROM (%v) LIMIT 1;", customSQL) diff --git a/provider/gpkg/gpkg_test.go b/provider/gpkg/gpkg_test.go index 9b2b911a5..2824cc113 100644 --- a/provider/gpkg/gpkg_test.go +++ b/provider/gpkg/gpkg_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/go-spatial/tegola" + "github.com/go-spatial/tegola/geom" "github.com/go-spatial/tegola/provider" "github.com/go-spatial/tegola/provider/gpkg" ) @@ -88,25 +89,19 @@ func TestNewTileProvider(t *testing.T) { } type MockTile struct { - extent [2][2]float64 - bufferedExtent [2][2]float64 + extent *geom.BoundingBox + bufferedExtent *geom.BoundingBox Z, X, Y uint64 srid uint64 } // TODO(arolek): Extent needs to return a geom.Extent -func (t *MockTile) Extent() ([2][2]float64, uint64) { - return t.extent, t.srid -} +func (t *MockTile) Extent() (*geom.BoundingBox, uint64) { return t.extent, t.srid } // TODO(arolek): BufferedExtent needs to return a geom.Extent -func (t *MockTile) BufferedExtent() ([2][2]float64, uint64) { - return t.bufferedExtent, t.srid -} +func (t *MockTile) BufferedExtent() (*geom.BoundingBox, uint64) { return t.bufferedExtent, t.srid } -func (t *MockTile) ZXY() (uint64, uint64, uint64) { - return t.Z, t.X, t.Y -} +func (t *MockTile) ZXY() (uint64, uint64, uint64) { return t.Z, t.X, t.Y } func TestTileFeatures(t *testing.T) { type tcase struct { @@ -153,10 +148,10 @@ func TestTileFeatures(t *testing.T) { layerName: "rd_lines", tile: MockTile{ srid: tegola.WGS84, - bufferedExtent: [2][2]float64{ - {20.0, 37.85}, - {23.6, 37.9431}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{20.0, 37.85}, + [2]float64{23.6, 37.9431}, + ), }, expectedFeatureCount: 0, }, @@ -171,14 +166,14 @@ func TestTileFeatures(t *testing.T) { layerName: "rl_lines", tile: MockTile{ srid: tegola.WGS84, - bufferedExtent: [2][2]float64{ + bufferedExtent: geom.NewBBox( /* {23.6, 38.0}, {23.8, 37.8}, */ - {23.6, 37.8}, - {23.8, 38.0}, - }, + [2]float64{23.6, 37.8}, + [2]float64{23.8, 38.0}, + ), }, expectedFeatureCount: 187, }, @@ -202,10 +197,10 @@ func TestTileFeatures(t *testing.T) { tile: MockTile{ Z: 1, srid: tegola.WebMercator, - bufferedExtent: [2][2]float64{ - {-20026376.39, -20048966.10}, - {20026376.39, 20048966.10}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{-20026376.39, -20048966.10}, + [2]float64{20026376.39, 20048966.10}, + ), }, expectedFeatureCount: 101, }, @@ -229,10 +224,10 @@ func TestTileFeatures(t *testing.T) { tile: MockTile{ Z: 0, srid: tegola.WebMercator, - bufferedExtent: [2][2]float64{ - {-20026376.39, -20048966.10}, - {20026376.39, 20048966.10}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{-20026376.39, -20048966.10}, + [2]float64{20026376.39, 20048966.10}, + ), }, expectedFeatureCount: 44, }, @@ -301,10 +296,10 @@ func TestConfigs(t *testing.T) { }, }, tile: MockTile{ - bufferedExtent: [2][2]float64{ - {-20026376.39, -20048966.10}, - {20026376.39, 20048966.10}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{-20026376.39, -20048966.10}, + [2]float64{20026376.39, 20048966.10}, + ), srid: tegola.WebMercator, }, layerName: "a_points", @@ -333,10 +328,10 @@ func TestConfigs(t *testing.T) { }, }, tile: MockTile{ - bufferedExtent: [2][2]float64{ - {-20026376.39, -20048966.10}, - {20026376.39, 20048966.10}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{-20026376.39, -20048966.10}, + [2]float64{20026376.39, 20048966.10}, + ), srid: tegola.WebMercator, }, layerName: "rd_lines", @@ -355,10 +350,10 @@ func TestConfigs(t *testing.T) { }, }, tile: MockTile{ - bufferedExtent: [2][2]float64{ - {-20026376.39, -20048966.10}, - {20026376.39, 20048966.10}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{-20026376.39, -20048966.10}, + [2]float64{20026376.39, 20048966.10}, + ), srid: tegola.WebMercator, }, layerName: "a_points", @@ -400,10 +395,10 @@ func TestConfigs(t *testing.T) { }, }, tile: MockTile{ - bufferedExtent: [2][2]float64{ - {-20026376.39, -20048966.10}, - {20026376.39, 20048966.10}, - }, + bufferedExtent: geom.NewBBox( + [2]float64{-20026376.39, -20048966.10}, + [2]float64{20026376.39, 20048966.10}, + ), srid: tegola.WebMercator, }, layerName: "a_p_points", diff --git a/provider/gpkg/util.go b/provider/gpkg/util.go index 217584b6a..3a81c0287 100644 --- a/provider/gpkg/util.go +++ b/provider/gpkg/util.go @@ -13,7 +13,7 @@ const ( zoomToken = "!ZOOM!" ) -func replaceTokens(qtext string, zoom uint64, extent geom.BoundingBox) string { +func replaceTokens(qtext string, zoom uint64, extent *geom.BoundingBox) string { // --- Convert tokens provided to SQL // The ZOOM token requires two parameters, both filled with the current zoom level. // Until support for named parameters, the ZOOM token must follow the BBOX token. diff --git a/provider/gpkg/util_internal_test.go b/provider/gpkg/util_internal_test.go index 56c34828a..38977b8ab 100644 --- a/provider/gpkg/util_internal_test.go +++ b/provider/gpkg/util_internal_test.go @@ -11,7 +11,7 @@ func TestReplaceTokens(t *testing.T) { qtext string zoom uint64 // TODO: replace with geom.Extent once it's ready - extent geom.BoundingBox + extent *geom.BoundingBox expected string } @@ -50,7 +50,7 @@ func TestReplaceTokens(t *testing.T) { ne_110m_land t JOIN rtree_ne_110m_land_geom si ON t.fid = si.id WHERE !BBOX!`, - extent: geom.BoundingBox{ + extent: &geom.BoundingBox{ -180, -85.0511, 180, 85.0511, }, @@ -70,7 +70,7 @@ func TestReplaceTokens(t *testing.T) { ne_110m_land t JOIN rtree_ne_110m_land_geom si ON t.fid = si.id WHERE !BBOX! AND min_zoom = !ZOOM!`, - extent: geom.BoundingBox{ + extent: &geom.BoundingBox{ -180, -85.0511, 180, 85.0511, }, diff --git a/provider/postgis/util.go b/provider/postgis/util.go index 0c8367941..2ea0fac47 100644 --- a/provider/postgis/util.go +++ b/provider/postgis/util.go @@ -7,9 +7,9 @@ import ( "strconv" "strings" - "github.com/jackc/pgx" "github.com/go-spatial/tegola/basic" "github.com/go-spatial/tegola/provider" + "github.com/jackc/pgx" ) // genSQL will fill in the SQL field of a layer given a pool, and list of fields. @@ -80,19 +80,19 @@ func replaceTokens(sql string, srid uint64, tile provider.Tile) (string, error) // TODO: leverage helper functions for minx / miny to make this easier to follow // TODO: it's currently assumed the tile will always be in WebMercator. Need to support different projections - minGeo, err := basic.FromWebMercator(srid, basic.Point{bufferedExtent[0][0], bufferedExtent[0][1]}) + minGeo, err := basic.FromWebMercator(srid, basic.Point{bufferedExtent.MinX(), bufferedExtent.MinY()}) if err != nil { return "", fmt.Errorf("Error trying to convert tile point: %v ", err) } - maxGeo, err := basic.FromWebMercator(srid, basic.Point{bufferedExtent[1][0], bufferedExtent[1][1]}) + maxGeo, err := basic.FromWebMercator(srid, basic.Point{bufferedExtent.MaxX(), bufferedExtent.MaxY()}) if err != nil { return "", fmt.Errorf("Error trying to convert tile point: %v ", err) } minPt, maxPt := minGeo.AsPoint(), maxGeo.AsPoint() - bbox := fmt.Sprintf("ST_MakeEnvelope(%v,%v,%v,%v,%v)", minPt.X(), minPt.Y(), maxPt.X(), maxPt.Y(), srid) + bbox := fmt.Sprintf("ST_MakeEnvelope(%.4g,%.4g,%.4g,%.4g,%d)", minPt.X(), minPt.Y(), maxPt.X(), maxPt.Y(), srid) // replace query string tokens z, _, _ := tile.ZXY() diff --git a/provider/postgis/util_internal_test.go b/provider/postgis/util_internal_test.go index be190b5a4..7b70807ed 100644 --- a/provider/postgis/util_internal_test.go +++ b/provider/postgis/util_internal_test.go @@ -18,14 +18,14 @@ func TestReplaceTokens(t *testing.T) { sql: "SELECT * FROM foo WHERE geom && !BBOX!", srid: tegola.WebMercator, tile: slippy.NewTile(2, 1, 1, 64, tegola.WebMercator), - expected: "SELECT * FROM foo WHERE geom && ST_MakeEnvelope(-1.017529720390625e+07,1.017529720390625e+07,156543.03390624933,-156543.03390624933,3857)", + expected: "SELECT * FROM foo WHERE geom && ST_MakeEnvelope(-1.018e+07,-1.565e+05,1.565e+05,1.018e+07,3857)", }, { sql: "SELECT id, scalerank=!ZOOM! FROM foo WHERE geom && !BBOX!", srid: tegola.WebMercator, tile: slippy.NewTile(2, 1, 1, 64, tegola.WebMercator), - expected: "SELECT id, scalerank=2 FROM foo WHERE geom && ST_MakeEnvelope(-1.017529720390625e+07,1.017529720390625e+07,156543.03390624933,-156543.03390624933,3857)", + expected: "SELECT id, scalerank=2 FROM foo WHERE geom && ST_MakeEnvelope(-1.018e+07,-1.565e+05,1.565e+05,1.018e+07,3857)", }, } @@ -37,7 +37,7 @@ func TestReplaceTokens(t *testing.T) { } if sql != tc.expected { - t.Errorf("[%v] incorrect sql, Expected (%v) Got (%v)", i, tc.expected, sql) + t.Errorf("[%v] incorrect sql,\n Expected \n \t%v\n Got \n \t%v", i, tc.expected, sql) } } } diff --git a/provider/provider.go b/provider/provider.go index 6c581c70d..55b9f4717 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -13,9 +13,9 @@ type Tile interface { // ZXY returns the z, x and y values of the tile ZXY() (uint64, uint64, uint64) // Extent returns the extent of the tile excluding any buffer - Extent() (extent [2][2]float64, srid uint64) + Extent() (extent *geom.BoundingBox, srid uint64) // BufferedExtent returns the extent of the tile including any buffer - BufferedExtent() (extent [2][2]float64, srid uint64) + BufferedExtent() (extent *geom.BoundingBox, srid uint64) } type Tiler interface { diff --git a/provider/test/provider.go b/provider/test/provider.go index 177d10173..060a996e8 100644 --- a/provider/test/provider.go +++ b/provider/test/provider.go @@ -43,16 +43,9 @@ func (tp *TileProvider) TileFeatures(ctx context.Context, layer string, t provid ext, srid := t.Extent() debugTileOutline := provider.Feature{ - ID: 0, - Geometry: geom.Polygon{ - [][2]float64{ - [2]float64{ext[0][0], ext[0][1]}, // Minx, Miny - [2]float64{ext[1][0], ext[0][1]}, // Maxx, Miny - [2]float64{ext[1][0], ext[1][1]}, // Maxx, Maxy - [2]float64{ext[0][0], ext[1][1]}, // Minx, Maxy - }, - }, - SRID: srid, + ID: 0, + Geometry: ext.AsPolygon(), + SRID: srid, Tags: map[string]interface{}{ "type": "debug_buffer_outline", }, diff --git a/tile.go b/tile.go index 6b95a8afa..c1f63229a 100644 --- a/tile.go +++ b/tile.go @@ -4,6 +4,7 @@ import ( "fmt" "math" + "github.com/go-spatial/tegola/geom" "github.com/go-spatial/tegola/maths/webmercator" ) @@ -34,8 +35,8 @@ type Tile struct { xspan float64 yspan float64 // This is the computed bounding box. - extent [2][2]float64 - bufpext [2][2]float64 + extent *geom.BoundingBox + bufpext *geom.BoundingBox } // NewTile will return a non-nil tile object. @@ -74,22 +75,19 @@ func (t *Tile) Init() { // resolution res := (max * 2) / math.Exp2(float64(t.Z)) t.cached = true - t.extent = [2][2]float64{ - { - -max + (float64(t.X) * res), // MinX - max - (float64(t.Y) * res), // Miny - }, - { - -max + (float64(t.X) * res) + res, // MaxX - max - (float64(t.Y) * res) - res, // MaxY - - }, + t.extent = &geom.BoundingBox{ + -max + (float64(t.X) * res), // MinX + max - (float64(t.Y) * res), // Miny + -max + (float64(t.X) * res) + res, // MaxX + max - (float64(t.Y) * res) - res, // MaxY + } - t.xspan = t.extent[1][0] - t.extent[0][0] - t.yspan = t.extent[1][1] - t.extent[0][1] + t.xspan = t.extent.MaxX() - t.extent.MinX() + t.yspan = t.extent.MaxY() - t.extent.MinY() /* // This is how we can calculate it. But, it will always be a constant. // So, we just return that constant. + // Where PixelBounds is : [4]float64{0.0, 0.0, t.Extent, t.Extent} bounds, err = t.PixelBounds() if err != nil { return bounds, err @@ -99,8 +97,10 @@ func (t *Tile) Init() { bounds[1][0] += t.Buffer bounds[1][1] += t.Buffer */ - t.bufpext = [2][2]float64{{0 - t.Buffer, 0 - t.Buffer}, {t.Extent + t.Buffer, t.Extent + t.Buffer}} - + t.bufpext = &geom.BoundingBox{ + 0 - t.Buffer, 0 - t.Buffer, + t.Extent + t.Buffer, t.Extent + t.Buffer, + } } func (t *Tile) Deg2Num() (x, y int) { @@ -111,12 +111,29 @@ func (t *Tile) Deg2Num() (x, y int) { } func (t *Tile) Num2Deg() (lat, lng float64) { - n := math.Pi - 2.0*math.Pi*float64(t.Y)/math.Exp2(float64(t.Z)) + lat = Tile2Lat(uint64(t.Y), uint64(t.Z)) + lng = Tile2Lon(uint64(t.X), uint64(t.Z)) + return lat, lng +} - lat = 180.0 / math.Pi * math.Atan(0.5*(math.Exp(n)-math.Exp(-n))) - lng = float64(t.X)/math.Exp2(float64(t.Z))*360.0 - 180.0 +func Tile2Lon(x, z uint64) float64 { return float64(x)/math.Exp2(float64(z))*360.0 - 180.0 } - return lat, lng +func Tile2Lat(y, z uint64) float64 { + var n float64 = math.Pi + if y != 0 { + n = math.Pi - 2.0*math.Pi*float64(y)/math.Exp2(float64(z)) + } + + return 180.0 / math.Pi * math.Atan(0.5*(math.Exp(n)-math.Exp(-n))) +} + +// Bounds returns the bounds of the Tile as defined by the North most Longitude, East most Latitude, South most Longitude, West most Latitude. +func (t *Tile) Bounds() [4]float64 { + north := Tile2Lon(uint64(t.X), uint64(t.Z)) + east := Tile2Lat(uint64(t.Y), uint64(t.Z)) + south := Tile2Lon(uint64(t.X+1), uint64(t.Z)) + west := Tile2Lat(uint64(t.Y+1), uint64(t.Z)) + return [4]float64{north, east, south, west} } func toWebMercator(srid int, pt [2]float64) (npt [2]float64, err error) { @@ -155,8 +172,8 @@ func (t *Tile) ToPixel(srid int, pt [2]float64) (npt [2]float64, err error) { return npt, err } - nx := int64((spt[0] - t.extent[0][0]) * t.Extent / t.xspan) - ny := int64((spt[1] - t.extent[0][1]) * t.Extent / t.yspan) + nx := int64((spt[0] - t.extent.MinX()) * t.Extent / t.xspan) + ny := int64((spt[1] - t.extent.MinY()) * t.Extent / t.yspan) return [2]float64{float64(nx), float64(ny)}, nil } @@ -165,76 +182,14 @@ func (t *Tile) FromPixel(srid int, pt [2]float64) (npt [2]float64, err error) { x := float64(int64(pt[0])) y := float64(int64(pt[1])) - wmx := (x * t.xspan / t.Extent) + t.extent[0][0] - wmy := (y * t.yspan / t.Extent) + t.extent[0][1] + wmx := (x * t.xspan / t.Extent) + t.extent.MinX() + wmy := (y * t.yspan / t.Extent) + t.extent.MinY() return fromWebMercator(srid, [2]float64{wmx, wmy}) } -//BoundingBox returns the bound box coordinates for upper left (ulx, uly) and lower right (lrx, lry) -// in web mercator projection -// ported from: https://raw.githubusercontent.com/mapbox/postgis-vt-util/master/postgis-vt-util.sql -func (t *Tile) BoundingBox() BoundingBox { - return BoundingBox{ - Minx: t.extent[0][0], - Miny: t.extent[0][1], - Maxx: t.extent[1][0], - Maxy: t.extent[1][1], - Epsilon: t.ZEpislon(), - HasXYZ: true, - X: t.X, - Y: t.Y, - Z: t.Z, - } -} - -func (t *Tile) BufferedBoundingBox() (BoundingBox, error) { - - pbounds, err := t.PixelBufferedBounds() - if err != nil { - return BoundingBox{}, err - } - - min, err := t.FromPixel(WebMercator, pbounds[0]) - if err != nil { - return BoundingBox{}, err - } - max, err := t.FromPixel(WebMercator, pbounds[1]) - if err != nil { - return BoundingBox{}, err - } - - return BoundingBox{ - Minx: min[0], - Miny: min[1], - Maxx: max[0], - Maxy: max[1], - Epsilon: t.ZEpislon(), - HasXYZ: true, - X: t.X, - Y: t.Y, - Z: t.Z, - }, nil -} - -func (t *Tile) PixelBounds() (bounds [2][2]float64, err error) { - /* - // This is how we can calculate it. But, it will always be a constant. - // So, we just return that constant. - bounds[0], err = t.ToPixel(WebMercator, t.extent[0]) - if err != nil { - return bounds, err - } - bounds[1], err = t.ToPixel(WebMercator, t.extent[1]) - if err != nil { - return bounds, err - } - */ - return [2][2]float64{{0.0, 0.0}, {t.Extent, t.Extent}}, nil -} - -func (t *Tile) PixelBufferedBounds() (bounds [2][2]float64, err error) { - return t.bufpext, nil +func (t *Tile) PixelBufferedBounds() (bounds [4]float64, err error) { + return t.bufpext.BBox(), nil } // Returns web mercator zoom level @@ -243,15 +198,10 @@ func (t *Tile) ZLevel() int { } //ZRes takes a web mercator zoom level and returns the pixel resolution for that -// scale, assuming 256x256 pixel tiles. Non-integer zoom levels are accepted. +// scale, assuming t.Extent x t.Extent pixel tiles. Non-integer zoom levels are accepted. // ported from: https://raw.githubusercontent.com/mapbox/postgis-vt-util/master/postgis-vt-util.sql -// TODO(gdey): I'm pretty sure we should be using the extent instead of 256 here. But I don't know what the magic number 40075016.6855785 is used for. -// 40075016.6855785 is close to 2*webmercator.MaxXExtent or 2*webmercator.MaxYExtent // 40075016.6855785 is the equator in meters for WGS84 at z=0 -// 256 is the tile width and height. func (t *Tile) ZRes() float64 { - - //return 40075016.6855785 / (256 * math.Exp2(float64(t.Z))) return 40075016.6855785 / (t.Extent * math.Exp2(float64(t.Z))) } diff --git a/tile_test.go b/tile_test.go index bffd881f6..2f494ba7d 100644 --- a/tile_test.go +++ b/tile_test.go @@ -57,45 +57,6 @@ func TestTileDeg2Num(t *testing.T) { } } -func TestTileBBox(t *testing.T) { - testcases := []struct { - tile tegola.Tile - minx, miny, maxx, maxy float64 - }{ - { - tile: *tegola.NewTile(2, 1, 1), - minx: -10018754.17, - miny: 10018754.17, - maxx: 0, - maxy: 0, - }, - { - tile: *tegola.NewTile(16, 11436, 26461), - minx: -13044437.497219238996, - miny: 3856706.6986199953, - maxx: -13043826.000993041, - maxy: 3856095.202393799, - }, - } - - for i, test := range testcases { - bbox := test.tile.BoundingBox() - - if bbox.Minx != test.minx { - t.Errorf("Failed test %v. Expected minx (%v), got (%v)", i, test.minx, bbox.Minx) - } - if bbox.Miny != test.miny { - t.Errorf("Failed test %v. Expected miny (%v), got (%v)", i, test.miny, bbox.Miny) - } - if bbox.Maxx != test.maxx { - t.Errorf("Failed test %v. Expected maxx (%v), got (%v)", i, test.maxx, bbox.Maxx) - } - if bbox.Maxy != test.maxy { - t.Errorf("Failed test %v. Expected maxy (%v), got (%v)", i, test.maxy, bbox.Maxy) - } - } -} - func TestTileZRes(t *testing.T) { testcases := []struct { tile tegola.Tile