go-spatial / geom

Geometry interfaces to help drive interoperability within the Go geospatial community
MIT License
168 stars 37 forks source link

Grid: Unpredictable interactions between ToNative and FromNative #127

Open jchamberlain opened 1 year ago

jchamberlain commented 1 year ago

Expected

Grid.ToNative() should return the top-left point of the given tile:

grid, _ := slippy.NewGrid(3857)
tile := slippy.NewTile(5, 0, 0)
point, _ := grid.ToNative(tile)
fmt.Println(point) // [-2.003750834e+07 2.003750834e+07]

Grid.FromNative() should return the tile corresponding to a top-left point:

grid, _ := slippy.NewGrid(3857)
tile, _ := grid.FromNative(5, geom.Point{-2.003750834e7, 2.003750834e7})
fmt.Println(tile) // &{5 0 0}

FromNative() and ToNative() should be able to reverse each other predictably. That is, FromNative(ToNative(x)) == x.

Problem

The above does not hold for all coordinates. In the following example, 5,3,5 becomes 5,3,4:

grid, _ := NewGrid(3857)
tile1 := NewTile(5, 3, 5)
point, _ := grid.ToNative(tile1)
tile2, _ := grid.FromNative(5, point)
fmt.Println(tile2) // &{5 3 4}

x=3 and y=5 seems to be buggy at multiple zooms (try 6,3,5, 7,3,5, and 8,3,5)

Impact

Whatever is causing this problem seems to make slippy.RangeFamilyAt() unpredictable as well, as it relies on ToNative() and FromNative(). When updating Tegola to use the latest geom (https://github.com/go-spatial/tegola/pull/952), I couldn't get RangeFamilyAt() to work and had to copy in the old version which does not make use of these functions.

Note: it's entirely possible this is related to me using an M1 Mac. I see slightly smaller floats generated on a regular basis, so maybe someone could try this on amd64?

gdey commented 1 year ago

Converting to/from an SRID is a lossy action; it is not expected to be a perfect transformation. Usually, you want to reduce the number of transforms you do.

jchamberlain commented 1 year ago

That makes sense. In which case, RangeFamilyAt() should be completely reverted as it relies on converting back and forth between zoom ranges, and is thus unpredictable.

I'm also not 100% clear if converting to/from an SRID is what's happening here. My understanding was that it's converting between slippy tile names and Web Mercator units. Shouldn't you be able to go back and forth between those predictably? Put another way, I assume this would only happen if ToNative() is returning a point that FromNative() considers to be outside of the tile. That feels intuitively like something which should be able to be reconciled.