This commit is contained in:
tidwall 2019-01-10 12:54:07 -07:00
commit 5e4c1601b1
30 changed files with 258 additions and 3 deletions

4
Gopkg.lock generated
View File

@ -243,7 +243,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:36ee77434dbfa0fafc20f0788cfc0a30f7dda8aea228b2574028be818cfabc31" digest = "1:24cb019229d3a4d0febd3934800b3759df35c722e93d63d9cfe32015d7abfec3"
name = "github.com/tidwall/geojson" name = "github.com/tidwall/geojson"
packages = [ packages = [
".", ".",
@ -251,7 +251,7 @@
"geometry", "geometry",
] ]
pruneopts = "" pruneopts = ""
revision = "0d533c870e85d0cb1909818503e0127f9e543d92" revision = "ade1bcf7b6db0e5ffb0605fea152254d2c002d6a"
[[projects]] [[projects]]
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794" digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"

View File

@ -27,6 +27,11 @@ func (s String) Empty() bool {
return true return true
} }
// Valid ...
func (s String) Valid() bool {
return false
}
// Rect ... // Rect ...
func (s String) Rect() geometry.Rect { func (s String) Rect() geometry.Rect {
return geometry.Rect{} return geometry.Rect{}

View File

@ -166,7 +166,7 @@ func (server *Server) cmdSearchArgs(
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
s.obj, err = geojson.Parse(obj, nil) s.obj, err = geojson.Parse(obj, &server.geomParseOpts)
if err != nil { if err != nil {
return return
} }

View File

@ -183,6 +183,10 @@ func Serve(host string, port int, dir string, http bool) error {
if err == nil { if err == nil {
server.geomParseOpts.IndexChildren = int(n) server.geomParseOpts.IndexChildren = int(n)
} }
requireValid := os.Getenv("REQUIREVALID")
if requireValid != "" {
server.geomParseOpts.RequireValid = true
}
indexKind := os.Getenv("T38IDXGEOMKIND") indexKind := os.Getenv("T38IDXGEOMKIND")
switch indexKind { switch indexKind {
default: default:

View File

@ -178,6 +178,11 @@ func (g *Circle) Empty() bool {
return false return false
} }
// Valid ...
func (g *Circle) Valid() bool {
return g.getObject().Valid()
}
// ForEach ... // ForEach ...
func (g *Circle) ForEach(iter func(geom Object) bool) bool { func (g *Circle) ForEach(iter func(geom Object) bool) bool {
return iter(g) return iter(g)

View File

@ -64,6 +64,11 @@ func (g *collection) Empty() bool {
return g.pempty return g.pempty
} }
// Valid ...
func (g *collection) Valid() bool {
return g.Rect().Valid()
}
// Rect ... // Rect ...
func (g *collection) Rect() geometry.Rect { func (g *collection) Rect() geometry.Rect {
return g.prect return g.prect

View File

@ -45,6 +45,11 @@ func (g *Feature) Empty() bool {
return g.base.Empty() return g.base.Empty()
} }
// Valid ...
func (g *Feature) Valid() bool {
return g.base.Valid()
}
// Rect ... // Rect ...
func (g *Feature) Rect() geometry.Rect { func (g *Feature) Rect() geometry.Rect {
return g.base.Rect() return g.base.Rect()

View File

@ -18,3 +18,9 @@ func TestFeatureCollectionPoly(t *testing.T) {
expect(t, p.Intersects(PO(1, 2))) expect(t, p.Intersects(PO(1, 2)))
expect(t, p.Contains(PO(1, 2))) expect(t, p.Contains(PO(1, 2)))
} }
func TestFeatureCollectionValid(t *testing.T) {
json := `{"type":"FeatureCollection","features":[{"type":"Point","coordinates":[1,200]}]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errCoordinatesInvalid, &ParseOptions{RequireValid: true})
}

View File

@ -8,6 +8,7 @@ package geometry
type Geometry interface { type Geometry interface {
Rect() Rect Rect() Rect
Empty() bool Empty() bool
Valid() bool
ContainsPoint(point Point) bool ContainsPoint(point Point) bool
IntersectsPoint(point Point) bool IntersectsPoint(point Point) bool
ContainsRect(rect Rect) bool ContainsRect(rect Rect) bool
@ -20,3 +21,8 @@ type Geometry interface {
// require conformance // require conformance
var _ = []Geometry{Point{}, Rect{}, &Line{}, &Poly{}} var _ = []Geometry{Point{}, Rect{}, &Line{}, &Poly{}}
// WorldPolygon is the maximum bounds for any GeoPoint
var WorldPolygon = NewPoly([]Point{
{-180, -90}, {-180, 90}, {180, 90}, {180, -90}, {-180, -90},
}, nil, &IndexOptions{})

View File

@ -9,6 +9,14 @@ type Line struct {
baseSeries baseSeries
} }
// Valid ...
func (line *Line) Valid() bool {
if !WorldPolygon.ContainsLine(line) {
return false
}
return true
}
// NewLine creates a new Line // NewLine creates a new Line
func NewLine(points []Point, opts *IndexOptions) *Line { func NewLine(points []Point, opts *IndexOptions) *Line {
line := new(Line) line := new(Line)

View File

@ -19,6 +19,14 @@ func (point Point) Empty() bool {
return false return false
} }
// Valid ...
func (point Point) Valid() bool {
if !WorldPolygon.ContainsPoint(point) {
return false
}
return true
}
// Rect ... // Rect ...
func (point Point) Rect() Rect { func (point Point) Rect() Rect {
return Rect{point, point} return Rect{point, point}

View File

@ -39,6 +39,14 @@ func (poly *Poly) Empty() bool {
return poly.Exterior.Empty() return poly.Exterior.Empty()
} }
// Valid ...
func (poly *Poly) Valid() bool {
if !WorldPolygon.ContainsPoly(poly) {
return false
}
return true
}
// Rect ... // Rect ...
func (poly *Poly) Rect() Rect { func (poly *Poly) Rect() Rect {
if poly == nil || poly.Exterior == nil { if poly == nil || poly.Exterior == nil {

View File

@ -234,10 +234,21 @@ func Test369(t *testing.T) {
{-122.44, 37.7341129}, {-122.44, 37.7341129},
{-122.4408378, 37.7341129}, {-122.4408378, 37.7341129},
}, nil, DefaultIndexOptions) }, nil, DefaultIndexOptions)
d := NewPoly([]Point{
{-182.4408378, 37.7341129},
{-122.4408378, 37.733},
{-122.44, 37.733},
{-122.44, 37.7341129},
{-122.4408378, 137.7341129},
}, nil, DefaultIndexOptions)
expect(t, polyHoles.IntersectsPoly(a)) expect(t, polyHoles.IntersectsPoly(a))
expect(t, !polyHoles.IntersectsPoly(b)) expect(t, !polyHoles.IntersectsPoly(b))
expect(t, !polyHoles.IntersectsPoly(c)) expect(t, !polyHoles.IntersectsPoly(c))
expect(t, a.IntersectsPoly(polyHoles)) expect(t, a.IntersectsPoly(polyHoles))
expect(t, !b.IntersectsPoly(polyHoles)) expect(t, !b.IntersectsPoly(polyHoles))
expect(t, !c.IntersectsPoly(polyHoles)) expect(t, !c.IntersectsPoly(polyHoles))
expect(t, a.Valid())
expect(t, b.Valid())
expect(t, c.Valid())
expect(t, !d.Valid())
} }

View File

@ -113,6 +113,14 @@ func (rect Rect) Empty() bool {
return false return false
} }
// Valid ...
func (rect Rect) Valid() bool {
if !WorldPolygon.ContainsRect(rect) {
return false
}
return true
}
// Rect ... // Rect ...
func (rect Rect) Rect() Rect { func (rect Rect) Rect() Rect {
return rect return rect

View File

@ -81,6 +81,10 @@ func TestRectEmpty(t *testing.T) {
expect(t, !R(0, 0, 10, 10).Empty()) expect(t, !R(0, 0, 10, 10).Empty())
} }
func TestRectValid(t *testing.T) {
expect(t, R(0, 0, 10, 10).Valid())
}
func TestRectRect(t *testing.T) { func TestRectRect(t *testing.T) {
expect(t, R(0, 0, 10, 10).Rect() == R(0, 0, 10, 10)) expect(t, R(0, 0, 10, 10).Rect() == R(0, 0, 10, 10))
} }

View File

@ -136,6 +136,17 @@ func (series *baseSeries) Empty() bool {
return (series.closed && len(series.points) < 3) || len(series.points) < 2 return (series.closed && len(series.points) < 3) || len(series.points) < 2
} }
// Valid ...
func (series *baseSeries) Valid() bool {
valid := true
for _, point := range series.points {
if !point.Valid() {
valid = false
}
}
return valid
}
// Rect returns the series rectangle // Rect returns the series rectangle
func (series *baseSeries) Rect() Rect { func (series *baseSeries) Rect() Rect {
return series.rect return series.rect

View File

@ -16,3 +16,9 @@ func TestGeometryCollectionPoly(t *testing.T) {
expect(t, p.Intersects(PO(1, 2))) expect(t, p.Intersects(PO(1, 2)))
expect(t, p.Contains(PO(1, 2))) expect(t, p.Contains(PO(1, 2)))
} }
func TestGeometryCollectionValid(t *testing.T) {
json := `{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,200]}]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errCoordinatesInvalid, &ParseOptions{RequireValid: true})
}

View File

@ -21,6 +21,11 @@ func (g *LineString) Empty() bool {
return g.base.Empty() return g.base.Empty()
} }
// Valid ...
func (g *LineString) Valid() bool {
return g.base.Valid()
}
// Rect ... // Rect ...
func (g *LineString) Rect() geometry.Rect { func (g *LineString) Rect() geometry.Rect {
return g.base.Rect() return g.base.Rect()
@ -145,6 +150,11 @@ func parseJSONLineString(keys *parseKeys, opts *ParseOptions) (Object, error) {
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err return nil, err
} }
if opts.RequireValid {
if !g.Valid() {
return nil, errDataInvalid
}
}
return &g, nil return &g, nil
} }

View File

@ -18,6 +18,12 @@ func TestLineStringParse(t *testing.T) {
expectJSON(t, `{"type":"LineString","coordinates":[[3,4],[1,2]],"bbox":[1,2,3,4]}`, nil) expectJSON(t, `{"type":"LineString","coordinates":[[3,4],[1,2]],"bbox":[1,2,3,4]}`, nil)
} }
func TestLineStringParseValid(t *testing.T) {
json := `{"type":"LineString","coordinates":[[1,2],[-12,-190]]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errDataInvalid, &ParseOptions{RequireValid: true})
}
func TestLineStringVarious(t *testing.T) { func TestLineStringVarious(t *testing.T) {
var g = expectJSON(t, `{"type":"LineString","coordinates":[[3,4],[1,2]]}`, nil) var g = expectJSON(t, `{"type":"LineString","coordinates":[[3,4],[1,2]]}`, nil)
expect(t, string(g.AppendJSON(nil)) == `{"type":"LineString","coordinates":[[3,4],[1,2]]}`) expect(t, string(g.AppendJSON(nil)) == `{"type":"LineString","coordinates":[[3,4],[1,2]]}`)
@ -30,6 +36,16 @@ func TestLineStringVarious(t *testing.T) {
expect(t, g.Center() == R(1, 2, 3, 4).Center()) expect(t, g.Center() == R(1, 2, 3, 4).Center())
} }
func TestLineStringValid(t *testing.T) {
var g = expectJSON(t, `{"type":"LineString","coordinates":[[3,4],[1,2]]}`, nil)
expect(t, g.Valid())
}
func TestLineStringInvalid(t *testing.T) {
var g = expectJSON(t, `{"type":"LineString","coordinates":[[3,4],[1,2],[0, 190]]}`, nil)
expect(t, !g.Valid())
}
// func TestLineStringPoly(t *testing.T) { // func TestLineStringPoly(t *testing.T) {
// ls := expectJSON(t, `{"type":"LineString","coordinates":[ // ls := expectJSON(t, `{"type":"LineString","coordinates":[
// [10,10],[20,20],[20,10] // [10,10],[20,20],[20,10]

View File

@ -42,6 +42,17 @@ func (g *MultiLineString) String() string {
return string(g.AppendJSON(nil)) return string(g.AppendJSON(nil))
} }
// Valid ...
func (g *MultiLineString) Valid() bool {
valid := true
for _, p := range g.children {
if !p.Valid() {
valid = false
}
}
return valid
}
// JSON ... // JSON ...
func (g *MultiLineString) JSON() string { func (g *MultiLineString) JSON() string {
return string(g.AppendJSON(nil)) return string(g.AppendJSON(nil))
@ -80,6 +91,11 @@ func parseJSONMultiLineString(
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err return nil, err
} }
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
g.parseInitRectIndex(opts) g.parseInitRectIndex(opts)
return &g, nil return &g, nil
} }

View File

@ -10,6 +10,15 @@ func TestMultiLineString(t *testing.T) {
expectJSON(t, `{"type":"MultiLineString","coordinates":[1,null]}`, errCoordinatesInvalid) expectJSON(t, `{"type":"MultiLineString","coordinates":[1,null]}`, errCoordinatesInvalid)
} }
func TestMultiLineStringValid(t *testing.T) {
json := `{"type":"MultiLineString","coordinates":[
[[10,10],[120,190]],
[[50,50],[100,100]]
]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errCoordinatesInvalid, &ParseOptions{RequireValid: true})
}
func TestMultiLineStringPoly(t *testing.T) { func TestMultiLineStringPoly(t *testing.T) {
p := expectJSON(t, `{"type":"MultiLineString","coordinates":[ p := expectJSON(t, `{"type":"MultiLineString","coordinates":[
[[10,10],[20,20]], [[10,10],[20,20]],

View File

@ -41,6 +41,17 @@ func (g *MultiPolygon) String() string {
return string(g.AppendJSON(nil)) return string(g.AppendJSON(nil))
} }
// Valid ...
func (g *MultiPolygon) Valid() bool {
valid := true
for _, p := range g.children {
if !p.Valid() {
valid = false
}
}
return valid
}
// JSON ... // JSON ...
func (g *MultiPolygon) JSON() string { func (g *MultiPolygon) JSON() string {
return string(g.AppendJSON(nil)) return string(g.AppendJSON(nil))
@ -90,6 +101,11 @@ func parseJSONMultiPolygon(
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err return nil, err
} }
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
g.parseInitRectIndex(opts) g.parseInitRectIndex(opts)
return &g, nil return &g, nil
} }

View File

@ -25,6 +25,19 @@ func TestMultiPolygon(t *testing.T) {
expectJSON(t, `{"type":"MultiPolygon","coordinates":[1,null]}`, errCoordinatesInvalid) expectJSON(t, `{"type":"MultiPolygon","coordinates":[1,null]}`, errCoordinatesInvalid)
} }
func TestMultiPolygonParseValid(t *testing.T) {
json := `{"type":"MultiPolygon","coordinates":[
[
[[0,0],[10,0],[10,10],[0,10],[0,0]],
[[2,2],[8,2],[8,8],[2,8],[2,2]]
],[
[[0,0],[10,0],[10,10],[0,10],[0,0]],
[[2,2],[8,2],[8,8],[2,8],[2,2]]
]
]}`
expectJSONOpts(t, json, nil, &ParseOptions{RequireValid: true})
}
func TestMultiPolygonPoly(t *testing.T) { func TestMultiPolygonPoly(t *testing.T) {
p := expectJSON(t, `{"type":"MultiPolygon","coordinates":[ p := expectJSON(t, `{"type":"MultiPolygon","coordinates":[
[ [

View File

@ -30,6 +30,7 @@ var (
// Object is a GeoJSON type // Object is a GeoJSON type
type Object interface { type Object interface {
Empty() bool Empty() bool
Valid() bool
Rect() geometry.Rect Rect() geometry.Rect
Center() geometry.Point Center() geometry.Point
Contains(other Object) bool Contains(other Object) bool
@ -87,6 +88,7 @@ type ParseOptions struct {
// IndexGeometryKind is the kind of index implementation. // IndexGeometryKind is the kind of index implementation.
// Default is QuadTreeCompressed // Default is QuadTreeCompressed
IndexGeometryKind geometry.IndexKind IndexGeometryKind geometry.IndexKind
RequireValid bool
} }
// DefaultParseOptions ... // DefaultParseOptions ...
@ -94,6 +96,7 @@ var DefaultParseOptions = &ParseOptions{
IndexChildren: 64, IndexChildren: 64,
IndexGeometry: 64, IndexGeometry: 64,
IndexGeometryKind: geometry.QuadTree, IndexGeometryKind: geometry.QuadTree,
RequireValid: false,
} }
// Parse a GeoJSON object // Parse a GeoJSON object

View File

@ -34,6 +34,11 @@ func (g *Point) Empty() bool {
return g.base.Empty() return g.base.Empty()
} }
// Valid ...
func (g *Point) Valid() bool {
return g.base.Valid()
}
// Rect ... // Rect ...
func (g *Point) Rect() geometry.Rect { func (g *Point) Rect() geometry.Rect {
return g.base.Rect() return g.base.Rect()
@ -151,6 +156,11 @@ func parseJSONPoint(keys *parseKeys, opts *ParseOptions) (Object, error) {
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err return nil, err
} }
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
return &g, nil return &g, nil
} }

View File

@ -13,6 +13,11 @@ func TestPointParse(t *testing.T) {
expectJSON(t, `{"type":"Point","coordinates":[1]}`, errCoordinatesInvalid) expectJSON(t, `{"type":"Point","coordinates":[1]}`, errCoordinatesInvalid)
expectJSON(t, `{"type":"Point","coordinates":[1,2,3],"bbox":[1,2,3,4]}`, `{"type":"Point","coordinates":[1,2,3],"bbox":[1,2,3,4]}`) expectJSON(t, `{"type":"Point","coordinates":[1,2,3],"bbox":[1,2,3,4]}`, `{"type":"Point","coordinates":[1,2,3],"bbox":[1,2,3,4]}`)
} }
func TestPointParseValid(t *testing.T) {
json := `{"type":"Point","coordinates":[190,90]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errCoordinatesInvalid, &ParseOptions{RequireValid: true})
}
func TestPointVarious(t *testing.T) { func TestPointVarious(t *testing.T) {
var g Object = PO(10, 20) var g Object = PO(10, 20)
@ -22,6 +27,34 @@ func TestPointVarious(t *testing.T) {
expect(t, !g.Empty()) expect(t, !g.Empty())
} }
func TestPointValid(t *testing.T) {
var g Object = PO(0, 20)
expect(t, g.Valid())
var g1 Object = PO(10, 20)
expect(t, g1.Valid())
}
func TestPointInvalidLargeX(t *testing.T) {
var g Object = PO(10, 91)
expect(t, !g.Valid())
}
func TestPointInvalidLargeY(t *testing.T) {
var g Object = PO(181, 20)
expect(t, !g.Valid())
}
func TestPointValidLargeX(t *testing.T) {
var g Object = PO(180, 20)
expect(t, g.Valid())
}
func TestPointValidLargeY(t *testing.T) {
var g Object = PO(180, 90)
expect(t, g.Valid())
}
// func TestPointPoly(t *testing.T) { // func TestPointPoly(t *testing.T) {
// p := expectJSON(t, `{"type":"Point","coordinates":[15,15,0]}`, nil) // p := expectJSON(t, `{"type":"Point","coordinates":[15,15,0]}`, nil)
// expect(t, p.Within(PO(15, 15))) // expect(t, p.Within(PO(15, 15)))

View File

@ -21,6 +21,11 @@ func (g *Polygon) Empty() bool {
return g.base.Empty() return g.base.Empty()
} }
// Valid ...
func (g *Polygon) Valid() bool {
return g.base.Valid()
}
// Rect ... // Rect ...
func (g *Polygon) Rect() geometry.Rect { func (g *Polygon) Rect() geometry.Rect {
return g.base.Rect() return g.base.Rect()
@ -163,6 +168,11 @@ func parseJSONPolygon(keys *parseKeys, opts *ParseOptions) (Object, error) {
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err return nil, err
} }
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
return &g, nil return &g, nil
} }

View File

@ -28,6 +28,14 @@ func TestPolygonParse(t *testing.T) {
`{"type":"Polygon","coordinates":[[[0,0,0],[10,0,4,5],[5,10],[0,0]]]}`, `{"type":"Polygon","coordinates":[[[0,0,0],[10,0,4,5],[5,10],[0,0]]]}`,
`{"type":"Polygon","coordinates":[[[0,0,0],[10,0,4],[5,10,0],[0,0,0]]]}`) `{"type":"Polygon","coordinates":[[[0,0,0],[10,0,4],[5,10,0],[0,0,0]]]}`)
} }
func TestPolygonParseValid(t *testing.T) {
json := `{"type":"Polygon","coordinates":[
[[0,0],[190,0],[10,10],[0,10],[0,0]],
[[2,2],[8,2],[8,8],[2,8],[2,2]]
]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errCoordinatesInvalid, &ParseOptions{RequireValid: true})
}
func TestPolygonVarious(t *testing.T) { func TestPolygonVarious(t *testing.T) {
var g = expectJSON(t, `{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}`, nil) var g = expectJSON(t, `{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}`, nil)

View File

@ -24,6 +24,11 @@ func (g *Rect) Empty() bool {
return g.base.Empty() return g.base.Empty()
} }
// Valid ...
func (g *Rect) Valid() bool {
return g.base.Valid()
}
// Rect ... // Rect ...
func (g *Rect) Rect() geometry.Rect { func (g *Rect) Rect() geometry.Rect {
return g.base return g.base

View File

@ -41,3 +41,9 @@ func TestRect(t *testing.T) {
expect(t, (&Polygon{}).Distance(rect) != 0) expect(t, (&Polygon{}).Distance(rect) != 0)
} }
func TestRectValid(t *testing.T) {
json := `{"type":"Polygon","coordinates":[[[10,200],[30,200],[30,40],[10,40],[10,200]]]}`
expectJSON(t, json, nil)
expectJSONOpts(t, json, errCoordinatesInvalid, &ParseOptions{RequireValid: true})
}