Skip to content

Commit

Permalink
Merge branch 'master' into exact_algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
peterstace committed Oct 14, 2021
2 parents 8eb48b1 + e585bab commit e0bd53f
Show file tree
Hide file tree
Showing 72 changed files with 2,895 additions and 1,197 deletions.
4 changes: 4 additions & 0 deletions .ci/run_benchmarks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ trap "rm -f $new $old" EXIT
package="./..."
bench="."

pushd "$HOME"
go get golang.org/x/perf/cmd/benchstat
popd

for (( i = 0; i < 15; i++ )); do
echo
echo "RUN $i"
Expand Down
2 changes: 2 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Have you:

- Add cmprefimpl tests? (if appropriate?)

- Updated release notes? (if appropriate?)

## Related Issue

- Please link to the related issue(s).
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: build
on:
push
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: golangci-lint
on:
push:
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo sudo apt install libgdal-dev libgeos-dev libproj-dev
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v1.42
16 changes: 16 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
run:
timeout: 5m

linter-settings:
gosimple:
go: "1.16"
checks: ["all"]

linters:
fast: false
disable:
- errcheck
enable:
- govet
- gofmt
- goimports
122 changes: 122 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,127 @@
# Changelog

## v0.33.1

__Special thanks to Albert Teoh for contributing to this release.__

- Adds a new method `MinMaxXYs (XY, XY, bool)` to the `Envelope` type. The
first two return values are the minimum and maximum XY values in the
envelope, and the third return value indicates whether or not the first two
are defined (they are only defined for non-empty envelopes).

## v0.33.0

2021-10-11

__Special thanks to Albert Teoh for contributing to this release.__

- **Breaking change**: The `Envelope` type can now be an empty envelope.
Previously, it was only able to represent a rectangle with some area, a
horizontal or vertical line, or a single point. Its `AsGeometry` returns
an empty `GeometryCollection` in the case where it's empty. The result of
`AsGeometry` is unchanged for non-empty envelopes.

- **Breaking change**: The `NewEnvelope` function signature has changed. It now
accepts a slice of `geom.XY` as the sole argument. The behaviour of the
function is the same as before, except that if no XY values are provided then
an empty envelope is returned without error.

- **Breaking change**: The `Envelope` type's `EnvelopeFromGeoms` method has
been removed. To replicate the behaviour of this method, users can construct
a `GeometryCollection` and call its `Envelope` method.

- **Breaking change**: The `Envelope` type's `Min`, `Max`, and `Center` methods
now return `Point`s rather than `XY`s. When the envelope is empty, `Min`,
`Max`, and `Center` return empty points.

- **Breaking change**: The `Envelope` type's `Distance` method now returns
`(float64, bool)` rather than `float64`. The returned boolean is only true if
the distance between the two envelopes is defined (i.e. when they are both
non-empty).

- **Breaking change**: The `Envelope` method on the `Geometry`,
`GeometryCollection`, `Point`, `LineString`, `Polygon`, `MultiPoint`,
`MultiLineString`, and `MultiPolygon` types now return `Envelope` instead of
`(Envelope, bool)`. The empty vs non-empty status is encoded inside the
envelope instead of via an explicit boolean.

- The `Envelope` type now has `IsEmpty`, `IsPoint`, `IsLine`, and
`IsRectanagle` methods. These correspond to the 4 possible envelope
categories.

## v0.32.0

2021-09-08

__Special thanks to Albert Teoh for contributing to this release.__

- **Breaking change**: Consolidates `MultiPoint` constructors and simplifies
`MultiPoint` internal representation. Removes the `BitSet` type, previously
used for `MultiPoint` construction. Removes the `NewMultiPointFromPoints` and
`NewMultiPointWithEmptyMask` functions. Modifies the `NewMultiPoint` function
to accept a slice of `Point`s rather than a `Sequence`.

- **Breaking change**: Consolidates `Point` construction. Removes the
`NewPointFromXY` function. It is replaced by a new `AsPoint` method on the
`XY` type.

- Refactors internal test helpers.

- Adds linting to CI using `golangci-lint`.

- **Breaking change**: Renames geometry constructors for consistency.
`NewPolygonFromRings` is renamed to `NewPolygon`.
`NewMultiLineStringFromLineStrings` is renamed to `NewMultiLineString`.
`NewMultiPolygonFromPolygons` is renamed to `NewMultiPolygon`.

- **Breaking change**: Adds checks for anomalous `float64` values (NaN and +/-
infinity) during geometry construction.

- The `NewPoint` function now returns `(Point, error)` rather than `Point`.
The returned error is non-nil when the inputs contain anomalous values.

- The `NewLineString` function's signature doesn't change, but now returns
a non-nil error if the input `Sequence` contains anomalous values.

- The `OmitInvalid` constructor option now has implications when
constructing `Point` and `MultiPoint` types.

- The `NewEnvelope` function now returns `(Envelope, error)` rather than
`Envelope`. The returned error is non-nil when when the input XYs contain
anomalous values.

- The `Envelope` type's `ExtendToIncludePoint` method is renamed to
`ExtendToIncludeXY` (better matching its argument type). It now returns
`(Envelope, erorr)` rather than `Envelope`. The returned error is non-nil
if the inputs contain any anomalous values.

- The `Envelope` type's `ExpandBy` method is removed due to its limited
utility and complex interactions with anomalous values.

## v0.31.0

2021-08-09

__Special thanks to Albert Teoh for contributing to this release.__

- Fixes some minor linting (and other similar) issues identified by Go Report
Card.

- Adds a new `DumpCoordinates` method to geometry types. This method returns a
`Sequence` containing all of the control points that define the geometry.

- Adds a new `Summary` method to all geometry types. This method gives a short
and human readable summary of geometry values. The summary includes the
geometry type, coordinates type, and component cardinalities where
appropriate (e.g. number of rings in a polygon).

- Adds a new `String` method to all geometry types, implementing the
`fmt.Stringer` interface. The method returns the same string as that returned
by the `Summary` method.

- Adds a new `NumRings` method to the `Polygon` type. This method gives the
total number of rings that make the polygon.

## v0.30.0

2021-07-18
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![Go Report
Card](https://goreportcard.com/badge/github.com/peterstace/simplefeatures)](https://goreportcard.com/report/github.com/peterstace/simplefeatures)
[![Coverage
Status](https://coveralls.io/repos/github/peterstace/simplefeatures/badge.svg?branch=add_coveralls)](https://coveralls.io/github/peterstace/simplefeatures)
Status](https://coveralls.io/repos/github/peterstace/simplefeatures/badge.svg?branch=master)](https://coveralls.io/github/peterstace/simplefeatures?branch=master)

Simple Features is a 2D geometry library that provides Go types that model
geometries, as well as algorithms that operate on them.
Expand Down Expand Up @@ -177,7 +177,8 @@ Encoding and decoding WKB directly:

```go
// Marshal as WKB
pt := geom.NewPointFromXY(geom.XY{1.5, 2.5})
coords := geom.Coordinates{XY: geom.XY{1.5, 2.5}}
pt := geom.NewPoint(coords)
wkb := pt.AsBinary()
fmt.Println(wkb) // Prints: [1 1 0 0 0 0 0 0 0 0 0 248 63 0 0 0 0 0 0 4 64]

Expand All @@ -199,7 +200,8 @@ db.Exec(`
)

// Insert our geometry and population data into PostGIS via WKB.
nyc := geom.NewPointFromXY(geom.XY{-74.0, 40.7})
coords := geom.Coordinates{XY: geom.XY{-74.0, 40.7}}
nyc := geom.NewPoint(coords)
db.Exec(`
INSERT INTO my_table
(my_geom, population)
Expand Down
8 changes: 6 additions & 2 deletions geom/accessor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ func TestLineStringAccessor(t *testing.T) {
pt56 := xyCoords(5, 6)

t.Run("start", func(t *testing.T) {
expectGeomEq(t, ls.StartPoint().AsGeometry(), NewPoint(pt12).AsGeometry())
want, err := NewPoint(pt12)
expectNoErr(t, err)
expectGeomEq(t, ls.StartPoint().AsGeometry(), want.AsGeometry())
})
t.Run("end", func(t *testing.T) {
expectGeomEq(t, ls.EndPoint().AsGeometry(), NewPoint(pt56).AsGeometry())
want, err := NewPoint(pt56)
expectNoErr(t, err)
expectGeomEq(t, ls.EndPoint().AsGeometry(), want.AsGeometry())
})
t.Run("num points", func(t *testing.T) {
expectIntEq(t, seq.Length(), 3)
Expand Down
4 changes: 2 additions & 2 deletions geom/alg_convex_hull.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func convexHull(g Geometry) Geometry {

// Check for point case:
if !hasAtLeast2DistinctPointsInXYs(pts) {
return NewPointFromXY(pts[0]).AsGeometry()
return pts[0].asUncheckedPoint().AsGeometry()
}

hull := monotoneChain(pts)
Expand All @@ -40,7 +40,7 @@ func convexHull(g Geometry) Geometry {
if err != nil {
panic(fmt.Errorf("bug in monotoneChain routine - didn't produce a valid ring: %v", err))
}
poly, err := NewPolygonFromRings([]LineString{ring})
poly, err := NewPolygon([]LineString{ring})
if err != nil {
panic(fmt.Errorf("bug in monotoneChain routine - didn't produce a valid polygon: %v", err))
}
Expand Down
19 changes: 9 additions & 10 deletions geom/alg_distance.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func Distance(g1, g2 Geometry) (float64, bool) {
if len(xys1)+len(lns1) > len(xys2)+len(lns2) {
xys1, xys2 = xys2, xys1
lns1, lns2 = lns2, lns1
g1, g2 = g2, g1
}

tr := loadTree(xys2, lns2)
Expand All @@ -55,11 +54,11 @@ func Distance(g1, g2 Geometry) (float64, bool) {
// distance so far.
var recordEnv Envelope
if recordID > 0 {
recordEnv = NewEnvelope(xys2[xyIdx])
recordEnv = xys2[xyIdx].uncheckedEnvelope()
} else {
recordEnv = lns2[lnIdx].envelope()
recordEnv = lns2[lnIdx].uncheckedEnvelope()
}
if recordEnv.Distance(env) > minDist {
if d, ok := recordEnv.Distance(env); ok && d > minDist {
return rtree.Stop
}

Expand All @@ -73,8 +72,8 @@ func Distance(g1, g2 Geometry) (float64, bool) {
return nil
}
for _, xy := range xys1 {
xyEnv := NewEnvelope(xy)
tr.PrioritySearch(xyEnv.box(), func(recordID int) error {
xyEnv := xy.uncheckedEnvelope()
tr.PrioritySearch(xy.box(), func(recordID int) error {
return searchBody(
xyEnv,
recordID,
Expand All @@ -84,8 +83,8 @@ func Distance(g1, g2 Geometry) (float64, bool) {
})
}
for _, ln := range lns1 {
lnEnv := ln.envelope()
tr.PrioritySearch(lnEnv.box(), func(recordID int) error {
lnEnv := ln.uncheckedEnvelope()
tr.PrioritySearch(ln.box(), func(recordID int) error {
return searchBody(
lnEnv,
recordID,
Expand Down Expand Up @@ -137,13 +136,13 @@ func loadTree(xys []XY, lns []line) *rtree.RTree {
items := make([]rtree.BulkItem, len(xys)+len(lns))
for i, xy := range xys {
items[i] = rtree.BulkItem{
Box: NewEnvelope(xy).box(),
Box: xy.box(),
RecordID: i + 1,
}
}
for i, ln := range lns {
items[i+len(xys)] = rtree.BulkItem{
Box: ln.envelope().box(),
Box: ln.box(),
RecordID: -(i + 1),
}
}
Expand Down
23 changes: 12 additions & 11 deletions geom/alg_intersection.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ func intersectionOfIndexedLines(
) {
// TODO: Investigate potential speed up of swapping lines.
var lss []LineString
var ptFloats []float64
var pts []Point
seen := make(map[XY]bool)
for i := range lines1.lines {
lines2.tree.RangeSearch(lines1.lines[i].envelope().box(), func(j int) error {
lines2.tree.RangeSearch(lines1.lines[i].box(), func(j int) error {
inter := lines1.lines[i].intersectLine(lines2.lines[j])
if inter.empty {
return nil
}
if inter.ptA == inter.ptB {
if pt := inter.ptA; !seen[pt] {
ptFloats = append(ptFloats, pt.X, pt.Y)
seen[pt] = true
if xy := inter.ptA; !seen[xy] {
pt := xy.asUncheckedPoint()
pts = append(pts, pt)
seen[xy] = true
}
} else {
lss = append(lss, line{inter.ptA, inter.ptB}.asLineString())
}
return nil
})
}
return NewMultiPoint(NewSequence(ptFloats, DimXY)),
NewMultiLineStringFromLineStrings(lss)
return NewMultiPoint(pts), NewMultiLineString(lss)
}

func intersectionOfMultiPointAndMultiPoint(mp1, mp2 MultiPoint) MultiPoint {
Expand All @@ -38,12 +38,13 @@ func intersectionOfMultiPointAndMultiPoint(mp1, mp2 MultiPoint) MultiPoint {
inMP1[xy] = true
}
}
var floats []float64
var pts []Point
for i := 0; i < mp2.NumPoints(); i++ {
xy, ok := mp2.PointN(i).XY()
pt := mp2.PointN(i)
xy, ok := pt.XY()
if ok && inMP1[xy] {
floats = append(floats, xy.X, xy.Y)
pts = append(pts, pt)
}
}
return NewMultiPoint(NewSequence(floats, DimXY))
return NewMultiPoint(pts)
}

0 comments on commit e0bd53f

Please sign in to comment.