From 1ee7ee41e62cf5476d8db656763a55138ef42b36 Mon Sep 17 00:00:00 2001 From: Giuseppe Broccolo Date: Sun, 21 Feb 2021 19:28:49 +0000 Subject: [PATCH] Add support for the Polygon extended types --- geom.go | 47 +++++++++++++++++ line.go | 17 ++++++- polygon_test.go | 26 ++++++++++ polygonm.go | 67 +++++++++++++++++++++++++ polygonm_test.go | 108 +++++++++++++++++++++++++++++++++++++++ polygonms.go | 74 +++++++++++++++++++++++++++ polygonms_test.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ polygons.go | 74 +++++++++++++++++++++++++++ polygons_test.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ polygonz.go | 67 +++++++++++++++++++++++++ polygonz_test.go | 107 +++++++++++++++++++++++++++++++++++++++ polygonzm.go | 67 +++++++++++++++++++++++++ polygonzm_test.go | 106 +++++++++++++++++++++++++++++++++++++++ polygonzms.go | 74 +++++++++++++++++++++++++++ polygonzms_test.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ polygonzs.go | 74 +++++++++++++++++++++++++++ polygonzs_test.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ set_geom.go | 43 ++++++++++++++++ 18 files changed, 1438 insertions(+), 1 deletion(-) create mode 100644 polygonm.go create mode 100644 polygonm_test.go create mode 100644 polygonms.go create mode 100644 polygonms_test.go create mode 100644 polygons.go create mode 100644 polygons_test.go create mode 100644 polygonz.go create mode 100644 polygonz_test.go create mode 100644 polygonzm.go create mode 100644 polygonzm_test.go create mode 100644 polygonzms.go create mode 100644 polygonzms_test.go create mode 100644 polygonzs.go create mode 100644 polygonzs_test.go diff --git a/geom.go b/geom.go index d80d0598..be8ef1b3 100644 --- a/geom.go +++ b/geom.go @@ -259,6 +259,53 @@ type Polygoner interface { LinearRings() [][][2]float64 } +type PolygonZer interface { + Geometry + LinearRings() [][][3]float64 +} + +type PolygonMer interface { + Geometry + LinearRings() [][][3]float64 +} + +type PolygonZMer interface { + Geometry + LinearRings() [][][4]float64 +} + +type PolygonSer interface { + Geometry + LinearRings() struct { + Srid uint32 + Pol Polygon + } +} + +type PolygonZSer interface { + Geometry + LinearRings() struct { + Srid uint32 + Polz PolygonZ + } +} + +type PolygonMSer interface { + Geometry + LinearRings() struct { + Srid uint32 + Polm PolygonM + } +} + +type PolygonZMSer interface { + Geometry + LinearRings() struct { + Srid uint32 + Polzm PolygonZM + } +} + // MultiPolygoner is a geometry of multiple polygons. type MultiPolygoner interface { Geometry diff --git a/line.go b/line.go index 76e39e85..395edf06 100644 --- a/line.go +++ b/line.go @@ -10,8 +10,12 @@ const ( PrecisionLevelBigFloat = 20 ) -// Line has exactly two points +// Line has exactly two points - we define here also the extended Z, M and ZM versions +// which refer respectively to PointZ, PointM and PointZM geometries type Line [2][2]float64 +type LineZ [2][3]float64 +type LineM [2][3]float64 +type LineZM [2][4]float64 // IsVertical returns true if the `y` elements of the points that make up the line (l) are equal. func (l Line) IsVertical() bool { return l[0][0] == l[1][0] } @@ -79,3 +83,14 @@ func (l Line) LengthSquared() float64 { deltax, deltay := l[1][0]-l[0][0], l[1][1]-l[0][1] return (deltax * deltax) + (deltay * deltay) } + +// for the extended geometries, just extract the related pair of points - the user will then +// use the XY coordinates to eventually build the Line object if needed and use the related methods +func (lz LineZ) PointZ1() *PointZ { return (*PointZ)(&lz[0]) } +func (lz LineZ) PointZ2() *PointZ { return (*PointZ)(&lz[1]) } + +func (lm LineM) PointM1() *PointM { return (*PointM)(&lm[0]) } +func (lm LineM) PointM2() *PointM { return (*PointM)(&lm[1]) } + +func (lzm LineZM) PointZM1() *PointZM { return (*PointZM)(&lzm[0]) } +func (lzm LineZM) PointZM2() *PointZM { return (*PointZM)(&lzm[1]) } diff --git a/polygon_test.go b/polygon_test.go index 1f2c32aa..4bf53b2d 100644 --- a/polygon_test.go +++ b/polygon_test.go @@ -11,6 +11,7 @@ import ( func TestPolygonSetter(t *testing.T) { type tcase struct { points [][][2]float64 + lines [][]geom.Line setter geom.PolygonSetter expected geom.PolygonSetter err error @@ -38,6 +39,15 @@ func TestPolygonSetter(t *testing.T) { glr := tc.setter.LinearRings() if !reflect.DeepEqual(tc.points, glr) { t.Errorf("linear rings, expected %v got %v", tc.points, glr) + return + } + + // compare the extracted segments + segs, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + } } } tests := []tcase{ @@ -50,6 +60,22 @@ func TestPolygonSetter(t *testing.T) { {10, 20}, }, }, + lines: [][]geom.Line{ + { + { + {10, 20}, + {30, 40}, + }, + { + {30, 40}, + {-10, -5}, + }, + { + {-10, -5}, + {10, 20}, + }, + }, + }, setter: &geom.Polygon{ { {15, 20}, diff --git a/polygonm.go b/polygonm.go new file mode 100644 index 00000000..4f94d040 --- /dev/null +++ b/polygonm.go @@ -0,0 +1,67 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonM is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonM = errors.New("geom: nil PolygonM") + +// ErrInvalidLinearRingM is thrown when a LinearRingM is malformed +var ErrInvalidLinearRingM = errors.New("geom: invalid LinearRingM") + +// ErrInvalidPolygonM is thrown when a Polygon is malformed +var ErrInvalidPolygonM = errors.New("geom: invalid PolygonM") + +// PolygonM is a geometry consisting of multiple closed LineStringMs. +// There must be only one exterior LineStringM with a clockwise winding order. +// There may be one or more interior LineStringMs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonM [][][3]float64 + +// LinearRings returns the coordinates of the linear rings +func (p PolygonM) LinearRings() [][][3]float64 { + return p +} + +// SetLinearRingZs modifies the array of 2D+1 coordinates +func (p *PolygonM) SetLinearRings(input [][][3]float64) (err error) { + if p == nil { + return ErrNilPolygonM + } + + *p = append((*p)[:0], input...) + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonM) AsSegments() (segs [][]LineM, err error) { + + if len(p) == 0 { + return nil, nil + } + + segs = make([][]LineM, 0, len(p)) + for i := range p { + switch len(p[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p[i]) + subr := make([]LineM, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = LineM{p[i][pj], p[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, nil +} diff --git a/polygonm_test.go b/polygonm_test.go new file mode 100644 index 00000000..275203cb --- /dev/null +++ b/polygonm_test.go @@ -0,0 +1,108 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonMSetter(t *testing.T) { + type tcase struct { + pointms [][][3]float64 + lines [][]geom.LineM + setter geom.PolygonMSetter + expected geom.PolygonMSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.pointms) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(tc.pointms, glr) { + t.Errorf("linear rings, expected %v got %v", tc.pointms, glr) + return + } + + // compare the extracted segments + segs, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + } + } + + } + } + tests := []tcase{ + { + pointms: [][][3]float64{ + { + {10, 20, 3}, + {30, 40, 5}, + {-10, -5, 0.5}, + {10, 20, 3}, + }, + }, + lines: [][]geom.LineM{ + { + { + {10, 20, 3}, + {30, 40, 5}, + }, + { + {30, 40, 5}, + {-10, -5, 0.5}, + }, + { + {-10, -5, 0.5}, + {10, 20, 3}, + }, + }, + }, + setter: &geom.PolygonM{ + { + {15, 20, 3}, + {35, 40, 5}, + {-15, -5, 0.5}, + {25, 20, 3}, + }, + }, + expected: &geom.PolygonM{ + { + {10, 20, 3}, + {30, 40, 5}, + {-10, -5, 0.5}, + {10, 20, 3}, + }, + }, + }, + { + setter: (*geom.PolygonM)(nil), + err: geom.ErrNilPolygonM, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/polygonms.go b/polygonms.go new file mode 100644 index 00000000..0ec9db2a --- /dev/null +++ b/polygonms.go @@ -0,0 +1,74 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonMS is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonMS = errors.New("geom: nil PolygonMS") + +// ErrInvalidLinearRingMS is thrown when a LinearRingMS is malformed +var ErrInvalidLinearRingMS = errors.New("geom: invalid LinearRingMS") + +// ErrInvalidPolygonMS is thrown when a Polygon is malformed +var ErrInvalidPolygonMS = errors.New("geom: invalid PolygonMS") + +// PolygonMS is a geometry consisting of multiple closed LineStringMSs. +// There must be only one exterior LineStringMS with a clockwise winding order. +// There may be one or more interior LineStringMSs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonMS struct { + Srid uint32 + Polm PolygonM +} + +// LinearRings returns the coordinates of the linear rings +func (p PolygonMS) LinearRings() struct { + Srid uint32 + Polm PolygonM +} { + return p +} + +// SetLinearRingMs modifies the array of 3D coordinates +func (p *PolygonMS) SetLinearRings(srid uint32, polm PolygonM) (err error) { + if p == nil { + return ErrNilPolygonMS + } + + p.Srid = srid + p.Polm = polm + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonMS) AsSegments() (segs [][]LineM, srid uint32, err error) { + + if len(p.Polm) == 0 { + return nil, 0, nil + } + + segs = make([][]LineM, 0, len(p.Polm)) + for i := range p.Polm { + switch len(p.Polm[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p.Polm[i]) + subr := make([]LineM, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = LineM{p.Polm[i][pj], p.Polm[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, p.Srid, nil +} diff --git a/polygonms_test.go b/polygonms_test.go new file mode 100644 index 00000000..71a13509 --- /dev/null +++ b/polygonms_test.go @@ -0,0 +1,122 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonMSSetter(t *testing.T) { + type tcase struct { + srid uint32 + polygonm geom.PolygonM + lines [][]geom.LineM + setter geom.PolygonMSSetter + expected geom.PolygonMSSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.srid, tc.polygonm) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + polyms := struct { + Srid uint32 + Polm geom.PolygonM + }{tc.srid, tc.polygonm} + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(polyms, glr) { + t.Errorf("linear rings, expected %v got %v", polyms, glr) + } + + // compare the extracted segments + segs, srid, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + return + } + if srid != tc.srid { + t.Errorf("srid of segments, expected %v got %v", tc.srid, srid) + } + } + } + } + tests := []tcase{ + { + srid: 4326, + polygonm: geom.PolygonM{ + { + {10, 20, 3}, + {30, 40, 5}, + {-10, -5, 0.5}, + {10, 20, 3}, + }, + }, + lines: [][]geom.LineM{ + { + { + {10, 20, 3}, + {30, 40, 5}, + }, + { + {30, 40, 5}, + {-10, -5, 0.5}, + }, + { + {-10, -5, 0.5}, + {10, 20, 3}, + }, + }, + }, + setter: &geom.PolygonMS{ + Srid: 4326, + Polm: geom.PolygonM{ + { + {15, 20, 3}, + {35, 40, 5}, + {-15, -5, 0.5}, + {25, 20, 3}, + }, + }, + }, + expected: &geom.PolygonMS{ + Srid: 4326, + Polm: geom.PolygonM{ + { + {10, 20, 3}, + {30, 40, 5}, + {-10, -5, 0.5}, + {10, 20, 3}, + }, + }, + }, + }, + { + setter: (*geom.PolygonMS)(nil), + err: geom.ErrNilPolygonMS, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/polygons.go b/polygons.go new file mode 100644 index 00000000..783d7c9c --- /dev/null +++ b/polygons.go @@ -0,0 +1,74 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonS is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonS = errors.New("geom: nil PolygonS") + +// ErrInvalidLinearRingS is thrown when a LinearRingS is malformed +var ErrInvalidLinearRingS = errors.New("geom: invalid LinearRingS") + +// ErrInvalidPolygonS is thrown when a Polygon is malformed +var ErrInvalidPolygonS = errors.New("geom: invalid PolygonS") + +// PolygonS is a geometry consisting of multiple closed LineStringSs. +// There must be only one exterior LineStringS with a clockwise winding order. +// There may be one or more interior LineStringSs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonS struct { + Srid uint32 + Pol Polygon +} + +// LinearRings returns the coordinates of the linear rings +func (p PolygonS) LinearRings() struct { + Srid uint32 + Pol Polygon +} { + return p +} + +// SetLinearRingZs modifies the array of 3D coordinates +func (p *PolygonS) SetLinearRings(srid uint32, pol Polygon) (err error) { + if p == nil { + return ErrNilPolygonS + } + + p.Srid = srid + p.Pol = pol + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonS) AsSegments() (segs [][]Line, srid uint32, err error) { + + if len(p.Pol) == 0 { + return nil, 0, nil + } + + segs = make([][]Line, 0, len(p.Pol)) + for i := range p.Pol { + switch len(p.Pol[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p.Pol[i]) + subr := make([]Line, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = Line{p.Pol[i][pj], p.Pol[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, p.Srid, nil +} diff --git a/polygons_test.go b/polygons_test.go new file mode 100644 index 00000000..101ecd5a --- /dev/null +++ b/polygons_test.go @@ -0,0 +1,122 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonSSetter(t *testing.T) { + type tcase struct { + srid uint32 + lines [][]geom.Line + polygon geom.Polygon + setter geom.PolygonSSetter + expected geom.PolygonSSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.srid, tc.polygon) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + polys := struct { + Srid uint32 + Pol geom.Polygon + }{tc.srid, tc.polygon} + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(polys, glr) { + t.Errorf("linear rings, expected %v got %v", polys, glr) + } + + // compare the extracted segments + segs, srid, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + return + } + if srid != tc.srid { + t.Errorf("srid of segments, expected %v got %v", tc.srid, srid) + } + } + } + } + tests := []tcase{ + { + srid: 4326, + polygon: geom.Polygon{ + { + {10, 20}, + {30, 40}, + {-10, -5}, + {10, 20}, + }, + }, + lines: [][]geom.Line{ + { + { + {10, 20}, + {30, 40}, + }, + { + {30, 40}, + {-10, -5}, + }, + { + {-10, -5}, + {10, 20}, + }, + }, + }, + setter: &geom.PolygonS{ + Srid: 4326, + Pol: geom.Polygon{ + { + {15, 20}, + {35, 40}, + {-15, -5}, + {25, 20}, + }, + }, + }, + expected: &geom.PolygonS{ + Srid: 4326, + Pol: geom.Polygon{ + { + {10, 20}, + {30, 40}, + {-10, -5}, + {10, 20}, + }, + }, + }, + }, + { + setter: (*geom.PolygonS)(nil), + err: geom.ErrNilPolygonS, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/polygonz.go b/polygonz.go new file mode 100644 index 00000000..97439e3d --- /dev/null +++ b/polygonz.go @@ -0,0 +1,67 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonZ is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonZ = errors.New("geom: nil PolygonZ") + +// ErrInvalidLinearRingZ is thrown when a LinearRingZ is malformed +var ErrInvalidLinearRingZ = errors.New("geom: invalid LinearRingZ") + +// ErrInvalidPolygonZ is thrown when a Polygon is malformed +var ErrInvalidPolygonZ = errors.New("geom: invalid PolygonZ") + +// PolygonZ is a geometry consisting of multiple closed LineStringZs. +// There must be only one exterior LineStringZ with a clockwise winding order. +// There may be one or more interior LineStringZs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonZ [][][3]float64 + +// LinearRings returns the coordinates of the linear rings +func (p PolygonZ) LinearRings() [][][3]float64 { + return p +} + +// SetLinearRingZs modifies the array of 3D coordinates +func (p *PolygonZ) SetLinearRings(input [][][3]float64) (err error) { + if p == nil { + return ErrNilPolygonZ + } + + *p = append((*p)[:0], input...) + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonZ) AsSegments() (segs [][]LineZ, err error) { + + if len(p) == 0 { + return nil, nil + } + + segs = make([][]LineZ, 0, len(p)) + for i := range p { + switch len(p[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p[i]) + subr := make([]LineZ, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = LineZ{p[i][pj], p[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, nil +} diff --git a/polygonz_test.go b/polygonz_test.go new file mode 100644 index 00000000..0933d7c2 --- /dev/null +++ b/polygonz_test.go @@ -0,0 +1,107 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonZSetter(t *testing.T) { + type tcase struct { + pointzs [][][3]float64 + lines [][]geom.LineZ + setter geom.PolygonZSetter + expected geom.PolygonZSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.pointzs) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(tc.pointzs, glr) { + t.Errorf("linear rings, expected %v got %v", tc.pointzs, glr) + return + } + + // compare the extracted segments + segs, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + } + } + } + } + tests := []tcase{ + { + pointzs: [][][3]float64{ + { + {10, 20, 30}, + {30, 40, 50}, + {-10, -5, 0}, + {10, 20, 30}, + }, + }, + lines: [][]geom.LineZ{ + { + { + {10, 20, 30}, + {30, 40, 50}, + }, + { + {30, 40, 50}, + {-10, -5, 0}, + }, + { + {-10, -5, 0}, + {10, 20, 30}, + }, + }, + }, + setter: &geom.PolygonZ{ + { + {15, 20, 30}, + {35, 40, 50}, + {-15, -5, 0}, + {25, 20, 30}, + }, + }, + expected: &geom.PolygonZ{ + { + {10, 20, 30}, + {30, 40, 50}, + {-10, -5, 0}, + {10, 20, 30}, + }, + }, + }, + { + setter: (*geom.PolygonZ)(nil), + err: geom.ErrNilPolygonZ, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/polygonzm.go b/polygonzm.go new file mode 100644 index 00000000..b2b2e351 --- /dev/null +++ b/polygonzm.go @@ -0,0 +1,67 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonZM is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonZM = errors.New("geom: nil PolygonZM") + +// ErrInvalidLinearRingZM is thrown when a LinearRingZM is malformed +var ErrInvalidLinearRingZM = errors.New("geom: invalid LinearRingZM") + +// ErrInvalidPolygonZM is thrown when a Polygon is malformed +var ErrInvalidPolygonZM = errors.New("geom: invalid PolygonZM") + +// PolygonZM is a geometry consisting of multiple closed LineStringZMs. +// There must be only one exterior LineStringZM with a clockwise winding order. +// There may be one or more interior LineStringZMs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonZM [][][4]float64 + +// LinearRings returns the coordinates of the linear rings +func (p PolygonZM) LinearRings() [][][4]float64 { + return p +} + +// SetLinearRingZs modifies the array of 3D coordinates +func (p *PolygonZM) SetLinearRings(input [][][4]float64) (err error) { + if p == nil { + return ErrNilPolygonZM + } + + *p = append((*p)[:0], input...) + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonZM) AsSegments() (segs [][]LineZM, err error) { + + if len(p) == 0 { + return nil, nil + } + + segs = make([][]LineZM, 0, len(p)) + for i := range p { + switch len(p[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p[i]) + subr := make([]LineZM, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = LineZM{p[i][pj], p[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, nil +} diff --git a/polygonzm_test.go b/polygonzm_test.go new file mode 100644 index 00000000..c432dfd2 --- /dev/null +++ b/polygonzm_test.go @@ -0,0 +1,106 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonZMSetter(t *testing.T) { + type tcase struct { + pointzms [][][4]float64 + lines [][]geom.LineZM + setter geom.PolygonZMSetter + expected geom.PolygonZMSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.pointzms) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(tc.pointzms, glr) { + t.Errorf("linear rings, expected %v got %v", tc.pointzms, glr) + } + + // compare the extracted segments + segs, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + } + } + } + } + tests := []tcase{ + { + pointzms: [][][4]float64{ + { + {10, 20, 30, 3}, + {30, 40, 50, 5}, + {-10, -5, 0, 0.5}, + {10, 20, 30, 3}, + }, + }, + lines: [][]geom.LineZM{ + { + { + {10, 20, 30, 3}, + {30, 40, 50, 5}, + }, + { + {30, 40, 50, 5}, + {-10, -5, 0, 0.5}, + }, + { + {-10, -5, 0, 0.5}, + {10, 20, 30, 3}, + }, + }, + }, + setter: &geom.PolygonZM{ + { + {15, 20, 30, 3}, + {35, 40, 50, 5}, + {-15, -5, 0, 0.5}, + {25, 20, 30, 3}, + }, + }, + expected: &geom.PolygonZM{ + { + {10, 20, 30, 3}, + {30, 40, 50, 5}, + {-10, -5, 0, 0.5}, + {10, 20, 30, 3}, + }, + }, + }, + { + setter: (*geom.PolygonZM)(nil), + err: geom.ErrNilPolygonZM, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/polygonzms.go b/polygonzms.go new file mode 100644 index 00000000..3cc1cfa4 --- /dev/null +++ b/polygonzms.go @@ -0,0 +1,74 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonZMS is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonZMS = errors.New("geom: nil PolygonZMS") + +// ErrInvalidLinearRingZMS is thrown when a LinearRingZMS is malformed +var ErrInvalidLinearRingZMS = errors.New("geom: invalid LinearRingZMS") + +// ErrInvalidPolygonZMS is thrown when a Polygon is malformed +var ErrInvalidPolygonZMS = errors.New("geom: invalid PolygonZMS") + +// PolygonZMS is a geometry consisting of multiple closed LineStringZMSs. +// There must be only one exterior LineStringZMS with a clockwise winding order. +// There may be one or more interior LineStringZMSs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonZMS struct { + Srid uint32 + Polzm PolygonZM +} + +// LinearRings returns the coordinates of the linear rings +func (p PolygonZMS) LinearRings() struct { + Srid uint32 + Polzm PolygonZM +} { + return p +} + +// SetLinearRingMs modifies the array of 3D coordinates +func (p *PolygonZMS) SetLinearRings(srid uint32, polzm PolygonZM) (err error) { + if p == nil { + return ErrNilPolygonZMS + } + + p.Srid = srid + p.Polzm = polzm + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonZMS) AsSegments() (segs [][]LineZM, srid uint32, err error) { + + if len(p.Polzm) == 0 { + return nil, 0, nil + } + + segs = make([][]LineZM, 0, len(p.Polzm)) + for i := range p.Polzm { + switch len(p.Polzm[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p.Polzm[i]) + subr := make([]LineZM, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = LineZM{p.Polzm[i][pj], p.Polzm[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, p.Srid, nil +} diff --git a/polygonzms_test.go b/polygonzms_test.go new file mode 100644 index 00000000..4ae61483 --- /dev/null +++ b/polygonzms_test.go @@ -0,0 +1,122 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonZMSSetter(t *testing.T) { + type tcase struct { + srid uint32 + polygonzm geom.PolygonZM + lines [][]geom.LineZM + setter geom.PolygonZMSSetter + expected geom.PolygonZMSSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.srid, tc.polygonzm) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + polyzms := struct { + Srid uint32 + Polzm geom.PolygonZM + }{tc.srid, tc.polygonzm} + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(polyzms, glr) { + t.Errorf("linear rings, expected %v got %v", polyzms, glr) + } + + // compare the extracted segments + segs, srid, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + return + } + if srid != tc.srid { + t.Errorf("srid of segments, expected %v got %v", tc.srid, srid) + } + } + } + } + tests := []tcase{ + { + srid: 4326, + polygonzm: geom.PolygonZM{ + { + {10, 20, 30, 3}, + {30, 40, 50, 5}, + {-10, -5, 0, 0.5}, + {10, 20, 30, 3}, + }, + }, + lines: [][]geom.LineZM{ + { + { + {10, 20, 30, 3}, + {30, 40, 50, 5}, + }, + { + {30, 40, 50, 5}, + {-10, -5, 0, 0.5}, + }, + { + {-10, -5, 0, 0.5}, + {10, 20, 30, 3}, + }, + }, + }, + setter: &geom.PolygonZMS{ + Srid: 4326, + Polzm: geom.PolygonZM{ + { + {15, 20, 30, 3}, + {35, 40, 50, 5}, + {-15, -5, 0, 0.5}, + {25, 20, 30, 3}, + }, + }, + }, + expected: &geom.PolygonZMS{ + Srid: 4326, + Polzm: geom.PolygonZM{ + { + {10, 20, 30, 3}, + {30, 40, 50, 5}, + {-10, -5, 0, 0.5}, + {10, 20, 30, 3}, + }, + }, + }, + }, + { + setter: (*geom.PolygonZMS)(nil), + err: geom.ErrNilPolygonZMS, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/polygonzs.go b/polygonzs.go new file mode 100644 index 00000000..b039f126 --- /dev/null +++ b/polygonzs.go @@ -0,0 +1,74 @@ +package geom + +import ( + "errors" +) + +// ErrNilPolygonZS is thrown when a polygonz is nil but shouldn't be +var ErrNilPolygonZS = errors.New("geom: nil PolygonZS") + +// ErrInvalidLinearRingZS is thrown when a LinearRingZS is malformed +var ErrInvalidLinearRingZS = errors.New("geom: invalid LinearRingZS") + +// ErrInvalidPolygonZS is thrown when a Polygon is malformed +var ErrInvalidPolygonZS = errors.New("geom: invalid PolygonZS") + +// PolygonZS is a geometry consisting of multiple closed LineStringZSs. +// There must be only one exterior LineStringZS with a clockwise winding order. +// There may be one or more interior LineStringZSs with a counterclockwise winding orders. +// The last point in the linear ring will not match the first point. +type PolygonZS struct { + Srid uint32 + Polz PolygonZ +} + +// LinearRings returns the coordinates of the linear rings +func (p PolygonZS) LinearRings() struct { + Srid uint32 + Polz PolygonZ +} { + return p +} + +// SetLinearRingZs modifies the array of 3D coordinates +func (p *PolygonZS) SetLinearRings(srid uint32, polz PolygonZ) (err error) { + if p == nil { + return ErrNilPolygonZS + } + + p.Srid = srid + p.Polz = polz + return +} + +// AsSegments returns the polygon as a slice of lines. This will make no attempt to only add unique segments. +func (p PolygonZS) AsSegments() (segs [][]LineZ, srid uint32, err error) { + + if len(p.Polz) == 0 { + return nil, 0, nil + } + + segs = make([][]LineZ, 0, len(p.Polz)) + for i := range p.Polz { + switch len(p.Polz[i]) { + case 0, 1, 2: + continue + // TODO(gdey) : why are we getting invalid points. + /* + case 1, 2: + return nil, ErrInvalidLinearRing + */ + + default: + pilen := len(p.Polz[i]) + subr := make([]LineZ, pilen) + pj := pilen - 1 + for j := 0; j < pilen; j++ { + subr[j] = LineZ{p.Polz[i][pj], p.Polz[i][j]} + pj = j + } + segs = append(segs, subr) + } + } + return segs, p.Srid, nil +} diff --git a/polygonzs_test.go b/polygonzs_test.go new file mode 100644 index 00000000..698c2ed0 --- /dev/null +++ b/polygonzs_test.go @@ -0,0 +1,122 @@ +package geom_test + +import ( + "reflect" + "strconv" + "testing" + + "github.com/go-spatial/geom" +) + +func TestPolygonZSSetter(t *testing.T) { + type tcase struct { + srid uint32 + polygonz geom.PolygonZ + lines [][]geom.LineZ + setter geom.PolygonZSSetter + expected geom.PolygonZSSetter + err error + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + err := tc.setter.SetLinearRings(tc.srid, tc.polygonz) + if tc.err == nil && err != nil { + t.Errorf("error, expected nil got %v", err) + return + } + if tc.err != nil { + if err.Error() != tc.err.Error() { + t.Errorf("error, expected %v got %v", tc.err, err) + } + return + } + + // compare the results + if !reflect.DeepEqual(tc.expected, tc.setter) { + t.Errorf("Polygon Setter, expected %v got %v", tc.expected, tc.setter) + return + } + + // compare the results of the Rings + polyzs := struct { + Srid uint32 + Polz geom.PolygonZ + }{tc.srid, tc.polygonz} + glr := tc.setter.LinearRings() + if !reflect.DeepEqual(polyzs, glr) { + t.Errorf("linear rings, expected %v got %v", polyzs, glr) + } + + // compare the extracted segments + segs, srid, err := tc.setter.AsSegments() + if err != nil { + if !reflect.DeepEqual(tc.lines, segs) { + t.Errorf("segments, expected %v got %v", tc.lines, segs) + return + } + if srid != tc.srid { + t.Errorf("srid of segments, expected %v got %v", tc.srid, srid) + } + } + } + } + tests := []tcase{ + { + srid: 4326, + polygonz: geom.PolygonZ{ + { + {10, 20, 30}, + {30, 40, 50}, + {-10, -5, 0}, + {10, 20, 30}, + }, + }, + lines: [][]geom.LineZ{ + { + { + {10, 20, 30}, + {30, 40, 50}, + }, + { + {30, 40, 50}, + {-10, -5, 0}, + }, + { + {-10, -5, 0}, + {10, 20, 30}, + }, + }, + }, + setter: &geom.PolygonZS{ + Srid: 4326, + Polz: geom.PolygonZ{ + { + {15, 20, 30}, + {35, 40, 50}, + {-15, -5, 0}, + {25, 20, 30}, + }, + }, + }, + expected: &geom.PolygonZS{ + Srid: 4326, + Polz: geom.PolygonZ{ + { + {10, 20, 30}, + {30, 40, 50}, + {-10, -5, 0}, + {10, 20, 30}, + }, + }, + }, + }, + { + setter: (*geom.PolygonZS)(nil), + err: geom.ErrNilPolygonZS, + }, + } + + for i := range tests { + t.Run(strconv.FormatInt(int64(i), 10), fn(tests[i])) + } +} diff --git a/set_geom.go b/set_geom.go index 629db766..d9f30a73 100644 --- a/set_geom.go +++ b/set_geom.go @@ -200,6 +200,49 @@ type MultiLineStringZMSSetter interface { type PolygonSetter interface { Polygoner SetLinearRings([][][2]float64) error + AsSegments() ([][]Line, error) +} + +type PolygonZSetter interface { + PolygonZer + SetLinearRings([][][3]float64) error + AsSegments() ([][]LineZ, error) +} + +type PolygonMSetter interface { + PolygonMer + SetLinearRings([][][3]float64) error + AsSegments() ([][]LineM, error) +} + +type PolygonZMSetter interface { + PolygonZMer + SetLinearRings([][][4]float64) error + AsSegments() ([][]LineZM, error) +} + +type PolygonSSetter interface { + PolygonSer + SetLinearRings(srid uint32, pol Polygon) error + AsSegments() ([][]Line, uint32, error) +} + +type PolygonZSSetter interface { + PolygonZSer + SetLinearRings(srid uint32, polz PolygonZ) error + AsSegments() ([][]LineZ, uint32, error) +} + +type PolygonMSSetter interface { + PolygonMSer + SetLinearRings(srid uint32, polm PolygonM) error + AsSegments() ([][]LineM, uint32, error) +} + +type PolygonZMSSetter interface { + PolygonZMSer + SetLinearRings(srid uint32, polzm PolygonZM) error + AsSegments() ([][]LineZM, uint32, error) } // MultiPolygonSetter is a mutable MultiPolygoner.