tidwall 6257ddba78 Faster point in polygon / GeoJSON updates
The big change is that the GeoJSON package has been completely
rewritten to fix a few of geometry calculation bugs, increase
performance, and to better follow the GeoJSON spec RFC 7946.

GeoJSON updates

- A LineString now requires at least two points.
- All json members, even foreign, now persist with the object.
- The bbox member persists too but is no longer used for geometry
  calculations. This is change in behavior. Previously Tile38 would
  treat the bbox as the object's physical rectangle.
- Corrections to geometry intersects and within calculations.

Faster spatial queries

- The performance of Point-in-polygon and object intersect operations
  are greatly improved for complex polygons and line strings. It went
  from O(n) to roughly O(log n).
- The same for all collection types with many children, including
  FeatureCollection, GeometryCollection, MultiPoint, MultiLineString,
  and MultiPolygon.

Codebase changes

- The pkg directory has been renamed to internal
- The GeoJSON internal package has been moved to a seperate repo at
  https://github.com/tidwall/geojson. It's now vendored.

Please look out for higher memory usage for datasets using complex
shapes. A complex shape is one that has 64 or more points. For these
shapes it's expected that there will be increase of least 54 bytes per
point.
2018-10-13 04:30:48 -07:00

123 lines
2.9 KiB
Go

package geohash
import (
"math"
"testing"
)
// TestCase objects are generated from independent code to verify we get the
// same results. See testcases_test.go.
type TestCase struct {
hashInt uint64
hash string
lat, lng float64
}
// Test we get the same string geohashes.
func TestEncode(t *testing.T) {
for _, c := range testcases {
hash := Encode(c.lat, c.lng)
if c.hash != hash {
t.Errorf("incorrect encode string result for (%v,%v): %s != %s",
c.lat, c.lng, c.hash, hash)
}
}
}
// Test we get the same integer geohashes.
func TestEncodeInt(t *testing.T) {
for _, c := range testcases {
hashInt := EncodeInt(c.lat, c.lng)
if c.hashInt != hashInt {
t.Errorf("incorrect encode integer result for (%v,%v): %016x != %016x xor %016x",
c.lat, c.lng, c.hashInt, hashInt, c.hashInt^hashInt)
}
}
}
// Verify the prefix property.
func TestPrefixProperty(t *testing.T) {
for _, c := range testcases {
for chars := uint(1); chars <= 12; chars++ {
hash := EncodeWithPrecision(c.lat, c.lng, chars)
pre := c.hash[:chars]
if pre != hash {
t.Errorf("incorrect encode string result for (%v,%v) at precision %d: %s != %s",
c.lat, c.lng, chars, pre, hash)
}
}
}
}
// Test bounding boxes for string geohashes.
func TestBoundingBox(t *testing.T) {
for _, c := range testcases {
box := BoundingBox(c.hash)
if !box.Contains(c.lat, c.lng) {
t.Errorf("incorrect bounding box for %s", c.hash)
}
}
}
// Test bounding boxes for integer geohashes.
func TestBoundingBoxInt(t *testing.T) {
for _, c := range testcases {
box := BoundingBoxInt(c.hashInt)
if !box.Contains(c.lat, c.lng) {
t.Errorf("incorrect bounding box for 0x%x", c.hashInt)
}
}
}
// Crude test of integer decoding.
func TestDecodeInt(t *testing.T) {
for _, c := range testcases {
lat, lng := DecodeInt(c.hashInt)
if math.Abs(lat-c.lat) > 0.0000001 {
t.Errorf("large error in decoded latitude for 0x%x", c.hashInt)
}
if math.Abs(lng-c.lng) > 0.0000001 {
t.Errorf("large error in decoded longitude for 0x%x", c.hashInt)
}
}
}
type DecodeTestCase struct {
hash string
box Box
}
// Test decoding at various precisions.
func TestDecode(t *testing.T) {
for _, c := range decodecases {
lat, lng := Decode(c.hash)
if !c.box.Contains(lat, lng) {
t.Errorf("hash %s decoded to %f,%f should lie in %+v",
c.hash, lat, lng, c.box)
}
}
}
// Test decoding at various precisions.
func TestDecodeCenter(t *testing.T) {
for _, c := range decodecases {
lat, lng := DecodeCenter(c.hash)
if !c.box.Contains(lat, lng) {
t.Errorf("hash %s decoded to %f,%f should lie in %+v",
c.hash, lat, lng, c.box)
}
}
}
// Test roundtrip decoding then encoding again.
func TestDecodeThenEncode(t *testing.T) {
for _, c := range decodecases {
precision := uint(len(c.hash))
lat, lng := Decode(c.hash)
rehashed := EncodeWithPrecision(lat, lng, precision)
if c.hash != rehashed {
t.Errorf("hash %s decoded and re-encoded to %s", c.hash, rehashed)
}
}
}