forked from paulmach/orb
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add geometry simplification algorithms
- Loading branch information
Showing
15 changed files
with
1,439 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
orb/simplify [](https://godoc.org/github.com/paulmach/orb/simplify) | ||
============ | ||
|
||
This package implements several reducing/simplifing function for `orb.Geometry` types. | ||
|
||
Currently implemented: | ||
|
||
* [Douglas-Peucker](#dp) | ||
* [Visvalingam](#vis) | ||
* [Radial](#radial) | ||
|
||
**Note:** The geometry object CAN be modified, use `Clone()` if a copy is required. | ||
|
||
<a name="dp"></a>Douglas-Peucker | ||
-------------------------------- | ||
|
||
Probably the most popular simplification algorithm. For algorithm details, see | ||
[wikipedia](http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm). | ||
|
||
The algorithm is a pass through for 1d geometry, e.g. Point and MultiPoint. | ||
The algorithms can modify the original geometry, use `Clone()` if a copy is required. | ||
|
||
Usage: | ||
|
||
original := orb.LineString{} | ||
reduced := simplify.DouglasPeucker(threshold).Simplify(original.Clone()) | ||
|
||
<a name="vis"></a>Visvalingam | ||
----------------------------- | ||
|
||
See Mike Bostock's explanation for | ||
[algorithm details](http://bost.ocks.org/mike/simplify/). | ||
|
||
The algorithm is a pass through for 1d geometry, e.g. Point and MultiPoint. | ||
The algorithms can modify the original geometry, use `Clone()` if a copy is required. | ||
|
||
Usage: | ||
|
||
original := orb.Ring{} | ||
|
||
// will remove all whose triangle is smaller than `threshold` | ||
reduced := simplify.VisvalingamThreshold(threshold).Simplify(original) | ||
|
||
// will remove points until there are only `toKeep` points left. | ||
reduced := simplify.VisvalingamKeep(toKeep).Simplify(original) | ||
|
||
// One can also combine the parameters. | ||
// This will continue to remove points until: | ||
// - there are no more below the threshold, | ||
// - or the new path is of length `toKeep` | ||
reduced := simplify.Visvalingam(threshold, toKeep).Simplify(original) | ||
|
||
<a name="radial"></a>Radial | ||
--------------------------- | ||
|
||
Radial reduces the path by removing points that are close together. | ||
A full [algorithm description](http://psimpl.sourceforge.net/radial-distance.html). | ||
|
||
The algorithm is a pass through for 1d geometry, like Point and MultiPoint. | ||
The algorithms can modify the original geometry, use `Clone()` if a copy is required. | ||
|
||
Usage: | ||
|
||
original := geo.Polygon{} | ||
|
||
// this method uses a Euclidean distance measure. | ||
reduced := simplify.Radial(planar.Distance, threshold).Simplify(path) | ||
|
||
// if the points are in the lng/lat space Radial Geo will | ||
// compute the geo distance between the coordinates. | ||
reduced:= simplify.Radial(geo.Distance, meters).Simplify(path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package simplify | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"testing" | ||
|
||
"github.com/paulmach/orb" | ||
"github.com/paulmach/orb/planar" | ||
) | ||
|
||
func TestDouglasPeucker_BenchmarkData(t *testing.T) { | ||
cases := []struct { | ||
threshold float64 | ||
length int | ||
}{ | ||
{0.1, 1118}, | ||
{0.5, 257}, | ||
{1.0, 144}, | ||
{1.5, 95}, | ||
{2.0, 71}, | ||
{3.0, 46}, | ||
{4.0, 39}, | ||
{5.0, 33}, | ||
} | ||
|
||
ls := benchmarkData() | ||
|
||
for i, tc := range cases { | ||
r := DouglasPeucker(tc.threshold).LineString(ls.Clone()) | ||
if len(r) != tc.length { | ||
t.Errorf("%d: reduced poorly, %d != %d", i, len(r), tc.length) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkDouglasPeucker(b *testing.B) { | ||
ls := benchmarkData() | ||
|
||
var data []orb.LineString | ||
for i := 0; i < b.N; i++ { | ||
data = append(data, ls.Clone()) | ||
} | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
DouglasPeucker(0.1).LineString(data[i]) | ||
} | ||
} | ||
|
||
func TestRadial_BenchmarkData(t *testing.T) { | ||
cases := []struct { | ||
threshold float64 | ||
length int | ||
}{ | ||
{0.1, 8282}, | ||
{0.5, 2023}, | ||
{1.0, 1043}, | ||
{1.5, 703}, | ||
{2.0, 527}, | ||
{3.0, 350}, | ||
{4.0, 262}, | ||
{5.0, 209}, | ||
} | ||
|
||
ls := benchmarkData() | ||
for i, tc := range cases { | ||
ls := Radial(planar.Distance, tc.threshold).LineString(ls.Clone()) | ||
if len(ls) != tc.length { | ||
t.Errorf("%d: data reduced poorly: %v != %v", i, len(ls), tc.length) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkRadial(b *testing.B) { | ||
ls := benchmarkData() | ||
|
||
var data []orb.LineString | ||
for i := 0; i < b.N; i++ { | ||
data = append(data, ls.Clone()) | ||
} | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
Radial(planar.Distance, 0.1).LineString(data[i]) | ||
} | ||
} | ||
|
||
func BenchmarkRadial_DisanceSquared(b *testing.B) { | ||
ls := benchmarkData() | ||
|
||
var data []orb.LineString | ||
for i := 0; i < b.N; i++ { | ||
data = append(data, ls.Clone()) | ||
} | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
Radial(planar.DistanceSquared, 0.1*0.1).LineString(data[i]) | ||
} | ||
} | ||
|
||
func TestVisvalingam_BenchmarkData(t *testing.T) { | ||
cases := []struct { | ||
threshold float64 | ||
length int | ||
}{ | ||
{0.1, 867}, | ||
{0.5, 410}, | ||
{1.0, 293}, | ||
{1.5, 245}, | ||
{2.0, 208}, | ||
{3.0, 169}, | ||
{4.0, 151}, | ||
{5.0, 135}, | ||
} | ||
|
||
ls := benchmarkData() | ||
for i, tc := range cases { | ||
r := VisvalingamThreshold(tc.threshold).LineString(ls.Clone()) | ||
if len(r) != tc.length { | ||
t.Errorf("%d: data reduced poorly: %v != %v", i, len(ls), tc.length) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkVisvalingam_Threshold(b *testing.B) { | ||
ls := benchmarkData() | ||
|
||
var data []orb.LineString | ||
for i := 0; i < b.N; i++ { | ||
data = append(data, ls.Clone()) | ||
} | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
VisvalingamThreshold(0.1).LineString(data[i]) | ||
} | ||
} | ||
|
||
func BenchmarkVisvalingam_Keep(b *testing.B) { | ||
ls := benchmarkData() | ||
toKeep := int(float64(len(ls)) / 1.616) | ||
|
||
var data []orb.LineString | ||
for i := 0; i < b.N; i++ { | ||
data = append(data, ls.Clone()) | ||
} | ||
|
||
b.ReportAllocs() | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
VisvalingamKeep(toKeep).LineString(data[i]) | ||
} | ||
} | ||
|
||
func benchmarkData() orb.LineString { | ||
// Data taken from the simplify-js example at http://mourner.github.io/simplify-js/ | ||
f, err := os.Open("testdata/lisbon2portugal.json") | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer f.Close() | ||
|
||
var points []float64 | ||
json.NewDecoder(f).Decode(&points) | ||
|
||
var ls orb.LineString | ||
for i := 0; i < len(points); i += 2 { | ||
ls = append(ls, orb.Point{points[i], points[i+1]}) | ||
} | ||
|
||
return ls | ||
} |
Oops, something went wrong.