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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,6 +45,11 @@ func (g *Feature) Empty() bool {
return g.base.Empty()
}
// Valid ...
func (g *Feature) Valid() bool {
return g.base.Valid()
}
// Rect ...
func (g *Feature) Rect() geometry.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.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 {
Rect() Rect
Empty() bool
Valid() bool
ContainsPoint(point Point) bool
IntersectsPoint(point Point) bool
ContainsRect(rect Rect) bool
@ -20,3 +21,8 @@ type Geometry interface {
// require conformance
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
}
// Valid ...
func (line *Line) Valid() bool {
if !WorldPolygon.ContainsLine(line) {
return false
}
return true
}
// NewLine creates a new Line
func NewLine(points []Point, opts *IndexOptions) *Line {
line := new(Line)

View File

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

View File

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

View File

@ -234,10 +234,21 @@ func Test369(t *testing.T) {
{-122.44, 37.7341129},
{-122.4408378, 37.7341129},
}, 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(b))
expect(t, !polyHoles.IntersectsPoly(c))
expect(t, a.IntersectsPoly(polyHoles))
expect(t, !b.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
}
// Valid ...
func (rect Rect) Valid() bool {
if !WorldPolygon.ContainsRect(rect) {
return false
}
return true
}
// Rect ...
func (rect Rect) Rect() Rect {
return rect

View File

@ -81,6 +81,10 @@ func TestRectEmpty(t *testing.T) {
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) {
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
}
// 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
func (series *baseSeries) Rect() 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.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()
}
// Valid ...
func (g *LineString) Valid() bool {
return g.base.Valid()
}
// Rect ...
func (g *LineString) Rect() geometry.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 {
return nil, err
}
if opts.RequireValid {
if !g.Valid() {
return nil, errDataInvalid
}
}
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)
}
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) {
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]]}`)
@ -30,6 +36,16 @@ func TestLineStringVarious(t *testing.T) {
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) {
// ls := expectJSON(t, `{"type":"LineString","coordinates":[
// [10,10],[20,20],[20,10]

View File

@ -42,6 +42,17 @@ func (g *MultiLineString) String() string {
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 ...
func (g *MultiLineString) JSON() string {
return string(g.AppendJSON(nil))
@ -80,6 +91,11 @@ func parseJSONMultiLineString(
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err
}
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
g.parseInitRectIndex(opts)
return &g, nil
}

View File

@ -10,6 +10,15 @@ func TestMultiLineString(t *testing.T) {
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) {
p := expectJSON(t, `{"type":"MultiLineString","coordinates":[
[[10,10],[20,20]],

View File

@ -41,6 +41,17 @@ func (g *MultiPolygon) String() string {
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 ...
func (g *MultiPolygon) JSON() string {
return string(g.AppendJSON(nil))
@ -90,6 +101,11 @@ func parseJSONMultiPolygon(
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
return nil, err
}
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
g.parseInitRectIndex(opts)
return &g, nil
}

View File

@ -25,6 +25,19 @@ func TestMultiPolygon(t *testing.T) {
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) {
p := expectJSON(t, `{"type":"MultiPolygon","coordinates":[
[

View File

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

View File

@ -34,6 +34,11 @@ func (g *Point) Empty() bool {
return g.base.Empty()
}
// Valid ...
func (g *Point) Valid() bool {
return g.base.Valid()
}
// Rect ...
func (g *Point) Rect() geometry.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 {
return nil, err
}
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
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,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) {
var g Object = PO(10, 20)
@ -22,6 +27,34 @@ func TestPointVarious(t *testing.T) {
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) {
// p := expectJSON(t, `{"type":"Point","coordinates":[15,15,0]}`, nil)
// expect(t, p.Within(PO(15, 15)))

View File

@ -21,6 +21,11 @@ func (g *Polygon) Empty() bool {
return g.base.Empty()
}
// Valid ...
func (g *Polygon) Valid() bool {
return g.base.Valid()
}
// Rect ...
func (g *Polygon) Rect() geometry.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 {
return nil, err
}
if opts.RequireValid {
if !g.Valid() {
return nil, errCoordinatesInvalid
}
}
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,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) {
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()
}
// Valid ...
func (g *Rect) Valid() bool {
return g.base.Valid()
}
// Rect ...
func (g *Rect) Rect() geometry.Rect {
return g.base

View File

@ -41,3 +41,9 @@ func TestRect(t *testing.T) {
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})
}