Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions encoding/mvt/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
// (square) tile in pixels usually 4096, see DefaultExtent.
// This function treats the tile extent elements as left, top, right, bottom. This is fine
// when working with a north-positive projection such as lat/long (epsg:4326)
// and web mercator (epsg:3857), but a south-positive projection (ie. epsg:2054) or west-postive
// projection would then flip the geomtery. To properly render these coordinate systems, simply
// and web mercator (epsg:3857), but a south-positive projection (ie. epsg:2054) or west-positive
// projection would then flip the geometry. To properly render these coordinate systems, simply
// swap the X's or Y's in the tile extent.
func PrepareGeo(geo geom.Geometry, tile *geom.Extent, pixelExtent float64) geom.Geometry {
switch g := geo.(type) {
Expand Down Expand Up @@ -79,9 +79,8 @@ func PrepareGeo(geo geom.Geometry, tile *geom.Extent, pixelExtent float64) geom.
}

func preparept(g geom.Point, tile *geom.Extent, pixelExtent float64) geom.Point {

px := (g.X() - tile.MinX()) / tile.XSpan() * pixelExtent
py := (tile.MaxY() - g.Y()) / tile.YSpan() * pixelExtent
px := int64((g.X() - tile.MinX()) / tile.XSpan() * pixelExtent)
py := int64((tile.MaxY() - g.Y()) / tile.YSpan() * pixelExtent)

return geom.Point{float64(px), float64(py)}
}
Expand Down
172 changes: 172 additions & 0 deletions encoding/mvt/prepare_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,51 @@ import (
"github.com/go-spatial/geom/cmp"
)

func TestPrepareptIntegerTruncation(t *testing.T) {

type tcase struct {
in geom.Point
out geom.Point
tile geom.Extent
}

fn := func(tc tcase) func(t *testing.T) {
return func(t *testing.T) {
got := preparept(tc.in, &tc.tile, float64(DefaultExtent))

if !cmp.PointEqual(tc.out, got) {
t.Errorf("expected %v got %v", tc.out, got)
}

if got[0] != float64(int64(got[0])) || got[1] != float64(int64(got[1])) {
t.Errorf("result should be integer values, got %v", got)
}
}
}

tests := map[string]tcase{
"exact coordinate": {
in: geom.Point{500, 500},
out: geom.Point{2048, 2048},
tile: geom.Extent{0, 0, 1000, 1000},
},
"fractional coordinate should truncate": {
in: geom.Point{500.7, 500.3},
out: geom.Point{2050, 2046},
tile: geom.Extent{0, 0, 1000, 1000},
},
"very small fraction should truncate to same integer": {
in: geom.Point{500.0001, 500.0001},
out: geom.Point{2048, 2047},
tile: geom.Extent{0, 0, 1000, 1000},
},
}

for name, tc := range tests {
t.Run(name, fn(tc))
}
}

func TestPrepareLinestring(t *testing.T) {

type tcase struct {
Expand Down Expand Up @@ -73,3 +118,130 @@ func TestPrepareLinestring(t *testing.T) {
t.Run(name, fn(tc))
}
}

func TestPreparePolygonClosingPoint(t *testing.T) {

type tcase struct {
in geom.Polygon
tile geom.Extent
expectedRings int
expectedPoints int
}

fn := func(tc tcase) func(t *testing.T) {
return func(t *testing.T) {
got := preparePolygon(tc.in, &tc.tile, float64(DefaultExtent))

if len(got) != tc.expectedRings {
t.Errorf("expected %d rings, got %d", tc.expectedRings, len(got))
}

if len(got) > 0 && len(got[0]) != tc.expectedPoints {
t.Errorf("expected %d points in first ring, got %d", tc.expectedPoints, len(got[0]))
}

if len(got) > 0 && len(got[0]) > 0 {
first := got[0][0]
last := got[0][len(got[0])-1]
if first[0] == last[0] && first[1] == last[1] {
t.Errorf("polygon ring should not have duplicate closing point: first=%v, last=%v", first, last)
}
}
}
}

tests := map[string]tcase{
"simple closed polygon with duplicate closing point": {
in: geom.Polygon{
{
{100, 100},
{200, 100},
{200, 200},
{100, 200},
{100, 100},
},
},
tile: geom.Extent{0, 0, 1000, 1000},
expectedRings: 1,
expectedPoints: 4,
},
"polygon with very close but not identical closing point": {
in: geom.Polygon{
{
{100.0, 100.0},
{200.0, 100.0},
{200.0, 200.0},
{100.0, 200.0},
{100.0001, 100.0001},
},
},
tile: geom.Extent{0, 0, 1000, 1000},
expectedRings: 1,
expectedPoints: 4,
},
}

for name, tc := range tests {
t.Run(name, fn(tc))
}
}

func TestPreparePolygonWithHoles(t *testing.T) {

type tcase struct {
in geom.Polygon
tile geom.Extent
expectedRings int
minPointsPerRing int
}

fn := func(tc tcase) func(t *testing.T) {
return func(t *testing.T) {
got := preparePolygon(tc.in, &tc.tile, float64(DefaultExtent))

if len(got) != tc.expectedRings {
t.Errorf("expected %d rings, got %d", tc.expectedRings, len(got))
}

for i, ring := range got {
if len(ring) < tc.minPointsPerRing {
t.Errorf("ring %d has too few points: %d", i, len(ring))
continue
}
first := ring[0]
last := ring[len(ring)-1]
if first[0] == last[0] && first[1] == last[1] {
t.Errorf("ring %d should not have duplicate closing point", i)
}
}
}
}

tests := map[string]tcase{
"polygon with hole": {
in: geom.Polygon{
{
{100, 100},
{900, 100},
{900, 900},
{100, 900},
{100, 100},
},
{
{300, 300},
{700, 300},
{700, 700},
{300, 700},
{300, 300},
},
},
tile: geom.Extent{0, 0, 1000, 1000},
expectedRings: 2,
minPointsPerRing: 3,
},
}

for name, tc := range tests {
t.Run(name, fn(tc))
}
}