Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions geom.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion line.go
Original file line number Diff line number Diff line change
Expand Up @@ -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] }
Expand Down Expand Up @@ -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]) }
26 changes: 26 additions & 0 deletions polygon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand All @@ -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},
Expand Down
67 changes: 67 additions & 0 deletions polygonm.go
Original file line number Diff line number Diff line change
@@ -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
}
108 changes: 108 additions & 0 deletions polygonm_test.go
Original file line number Diff line number Diff line change
@@ -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]))
}
}
74 changes: 74 additions & 0 deletions polygonms.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading