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
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

## Unreleased

- Add `NewEnvelopeXY` constructor for building an `Envelope` from variadic x/y
coordinate pairs, following the existing `New*XY` constructor pattern.
- Add `NewEnvelopeXY` constructor, which builds an `Envelope` from variadic x
and y coordinates (x1, y1, x2, y2, ..., xn, yn), where `NewEnvelope` takes
`XY` values. It follows the same convention as the other `XY` constructors,
such as `NewPointXY` and `NewLineStringXY`. The result is the smallest
`Envelope` containing all of the coordinates, so no arguments gives the empty
envelope and a single pair gives a point envelope. An odd number of arguments
panics.

## v0.59.0

Expand Down
32 changes: 18 additions & 14 deletions geom/ctor_from_coords.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,20 +227,6 @@ func NewPolygonXYZM(xyzms ...[]float64) Polygon {
return polygonFromCoords(xyzms, DimXYZM)
}

// NewEnvelopeXY builds a new [Envelope] from x and y coordinates, x1, y1, x2,
// y2, ..., xn, yn. If the number of coordinates is not a multiple of 2 the
// function will panic.
func NewEnvelopeXY(xys ...float64) Envelope {
if len(xys)%2 != 0 {
panic("geom: coordinate arguments to NewEnvelopeXY must have a length that is a multiple of 2")
}
var env Envelope
for i := 0; i < len(xys); i += 2 {
env = env.ExpandToIncludeXY(XY{xys[i], xys[i+1]})
}
return env
}

// NewSingleRingPolygonXY builds a new XY [Polygon] from the x and y coordinates
// of its exterior ring, in the form x1, y1, x2, y2, ..., xn, yn, x1, y1 (the
// first and last coordinates of the ring should be the same). If the number of
Expand Down Expand Up @@ -343,6 +329,24 @@ func NewMultiPolygonXYZM(xyzms ...[][]float64) MultiPolygon {
return multiPolygonFromCoords(xyzms, DimXYZM)
}

// NewEnvelopeXY builds a new [Envelope] from x and y coordinates, x1, y1, x2,
// y2, ..., xn, yn. The result is the smallest [Envelope] containing all of
// those coordinates. If the number of coordinates is not a multiple of 2 the
// function will panic.
//
// It doesn't perform any validation on the result. The [Envelope.Validate] method can be
// used to check the validity of the result if needed.
func NewEnvelopeXY(xys ...float64) Envelope {
if len(xys)%2 != 0 {
panic("geom: coordinate arguments to NewEnvelopeXY must have a length that is a multiple of 2")
}
var env Envelope
for i := 0; i < len(xys); i += 2 {
env = env.ExpandToIncludeXY(XY{xys[i], xys[i+1]})
}
return env
}

func clone1DFloat64s(src []float64) []float64 {
// TODO: Use slices.Clone once on Go 1.21.
if len(src) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion geom/perf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func BenchmarkPolygonMultipleRingsValidation(b *testing.B) {
func BenchmarkPolygonZigZagRingsValidation(b *testing.B) {
for _, sz := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("n=%d", sz), func(b *testing.B) {
outerRingEnv := geom.NewEnvelope(geom.XY{}, geom.XY{7, float64(sz + 1)})
outerRingEnv := geom.NewEnvelopeXY(0, 0, 7, float64(sz+1))
outerRing := outerRingEnv.AsGeometry().MustAsPolygon().ExteriorRing()
var leftFloats, rightFloats []float64
for i := 0; i < sz; i++ {
Expand Down
8 changes: 4 additions & 4 deletions geom/twkb_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ func (p *twkbParser) parseBBoxHeader() (ExtendedEnvelope, error) {
maxM := float64(p.bbox[6]+p.bbox[7]) / p.scalings[3]

return ExtendedEnvelope{
XYEnvelope: NewEnvelope(XY{minX, minY}, XY{maxX, maxY}),
XYEnvelope: NewEnvelopeXY(minX, minY, maxX, maxY),
ZRange: NewInterval(minZ, maxZ),
MRange: NewInterval(minM, maxM),
}, nil
Expand All @@ -360,7 +360,7 @@ func (p *twkbParser) parseBBoxHeader() (ExtendedEnvelope, error) {
maxZ := float64(p.bbox[4]+p.bbox[5]) / p.scalings[2]

return ExtendedEnvelope{
XYEnvelope: NewEnvelope(XY{minX, minY}, XY{maxX, maxY}),
XYEnvelope: NewEnvelopeXY(minX, minY, maxX, maxY),
ZRange: NewInterval(minZ, maxZ),
}, nil
case p.hasM:
Expand All @@ -373,7 +373,7 @@ func (p *twkbParser) parseBBoxHeader() (ExtendedEnvelope, error) {
maxM := float64(p.bbox[4]+p.bbox[5]) / p.scalings[2]

return ExtendedEnvelope{
XYEnvelope: NewEnvelope(XY{minX, minY}, XY{maxX, maxY}),
XYEnvelope: NewEnvelopeXY(minX, minY, maxX, maxY),
MRange: NewInterval(minM, maxM),
}, nil
default:
Expand All @@ -384,7 +384,7 @@ func (p *twkbParser) parseBBoxHeader() (ExtendedEnvelope, error) {
maxY := float64(p.bbox[2]+p.bbox[3]) / p.scalings[1]

return ExtendedEnvelope{
XYEnvelope: NewEnvelope(XY{minX, minY}, XY{maxX, maxY}),
XYEnvelope: NewEnvelopeXY(minX, minY, maxX, maxY),
}, nil
}
}
Expand Down
20 changes: 10 additions & 10 deletions geos/entrypoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -983,61 +983,61 @@ func TestClipByRect(t *testing.T) {
{
name: "polygon fully inside rect",
input: "POLYGON((1 1,1 2,2 2,2 1,1 1))",
rect: geom.NewEnvelope(geom.XY{X: 0, Y: 0}, geom.XY{X: 3, Y: 3}),
rect: geom.NewEnvelopeXY(0, 0, 3, 3),
want: "POLYGON((1 1,1 2,2 2,2 1,1 1))",
},
{
name: "polygon partially overlapping rect",
input: "POLYGON((0 0,0 4,4 4,4 0,0 0))",
rect: geom.NewEnvelope(geom.XY{X: 1, Y: 1}, geom.XY{X: 3, Y: 3}),
rect: geom.NewEnvelopeXY(1, 1, 3, 3),
want: "POLYGON((1 1,1 3,3 3,3 1,1 1))",
},
{
name: "polygon fully outside rect",
input: "POLYGON((0 0,0 1,1 1,1 0,0 0))",
rect: geom.NewEnvelope(geom.XY{X: 5, Y: 5}, geom.XY{X: 6, Y: 6}),
rect: geom.NewEnvelopeXY(5, 5, 6, 6),
want: "GEOMETRYCOLLECTION EMPTY",
},
{
name: "linestring clipped by rect",
input: "LINESTRING(0 0,4 4)",
rect: geom.NewEnvelope(geom.XY{X: 1, Y: 1}, geom.XY{X: 3, Y: 3}),
rect: geom.NewEnvelopeXY(1, 1, 3, 3),
want: "LINESTRING(1 1,3 3)",
},
{
name: "point inside rect",
input: "POINT(2 2)",
rect: geom.NewEnvelope(geom.XY{X: 1, Y: 1}, geom.XY{X: 3, Y: 3}),
rect: geom.NewEnvelopeXY(1, 1, 3, 3),
want: "POINT(2 2)",
},
{
name: "point outside rect",
input: "POINT(0 0)",
rect: geom.NewEnvelope(geom.XY{X: 1, Y: 1}, geom.XY{X: 3, Y: 3}),
rect: geom.NewEnvelopeXY(1, 1, 3, 3),
want: "GEOMETRYCOLLECTION EMPTY",
},
{
name: "empty input geometry",
input: "GEOMETRYCOLLECTION EMPTY",
rect: geom.NewEnvelope(geom.XY{X: 0, Y: 0}, geom.XY{X: 1, Y: 1}),
rect: geom.NewEnvelopeXY(0, 0, 1, 1),
want: "GEOMETRYCOLLECTION EMPTY",
},
{
name: "u-shaped polygon clipped through both arms produces multipolygon",
input: "POLYGON((0 0,4 0,4 3,3 3,3 1,1 1,1 3,0 3,0 0))",
rect: geom.NewEnvelope(geom.XY{X: 0, Y: 2}, geom.XY{X: 4, Y: 4}),
rect: geom.NewEnvelopeXY(0, 2, 4, 4),
want: "MULTIPOLYGON(((0 2,0 3,1 3,1 2,0 2)),((3 2,3 3,4 3,4 2,3 2)))",
},
{
name: "polygon with hole inside rect",
input: "POLYGON((0 0,0 6,6 6,6 0,0 0),(2 2,4 2,4 4,2 4,2 2))",
rect: geom.NewEnvelope(geom.XY{X: 1, Y: 1}, geom.XY{X: 5, Y: 5}),
rect: geom.NewEnvelopeXY(1, 1, 5, 5),
want: "POLYGON((1 1,1 5,5 5,5 1,1 1),(2 2,4 2,4 4,2 4,2 2))",
},
{
name: "polygon with hole partially outside rect removes hole",
input: "POLYGON((0 0,0 6,6 6,6 0,0 0),(1 1,3 1,3 3,1 3,1 1))",
rect: geom.NewEnvelope(geom.XY{X: 2, Y: 2}, geom.XY{X: 5, Y: 5}),
rect: geom.NewEnvelopeXY(2, 2, 5, 5),
want: "POLYGON((2 3,2 5,5 5,5 2,3 2,3 3,2 3))",
},
{
Expand Down
Loading