Skip to content

Commit 9e50c04

Browse files
Add: simplify for Feature<LineString>, ported from turf.js (#183)
* Add: simplify for Feature<LineString>, ported from turf.js * Doc: update Progress.md
1 parent b44f20c commit 9e50c04

File tree

6 files changed

+337
-1
lines changed

6 files changed

+337
-1
lines changed

Progress.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Dart. This is an on going project and functions are being added once needed. If
4949
- [ ] intersect
5050
- [ ] lineOffset
5151
- [x] [polygonSmooth](https://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_smooth.dart)
52-
- [ ] simplify
52+
- [x] [simplify](https://github.com/dartclub/turf_dart/blob/main/lib/src/simplify.dart)
5353
- [ ] tesselate
5454
- [x] [transformRotate](https://github.com/dartclub/turf_dart/blob/main/lib/src/transform_rotate.dart)
5555
- [ ] transformTranslate

lib/simplify.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
library turf_simplify;
2+
3+
export 'package:geotypes/geotypes.dart';
4+
export 'src/simplify.dart';

lib/src/simplify.dart

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import 'package:turf/helpers.dart';
2+
import 'package:turf/turf.dart';
3+
4+
/*
5+
(c) 2013, Vladimir Agafonkin
6+
Simplify.js, a high-performance JS polyline simplification library
7+
mourner.github.io/simplify-js
8+
*/
9+
10+
// to suit your point format, run search/replace for '.x' and '.y';
11+
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
12+
13+
/// square distance between 2 points
14+
num _getSqDist(Position p1, Position p2) {
15+
var dx = p1.lng - p2.lng, dy = p1.lat - p2.lat;
16+
17+
return dx * dx + dy * dy;
18+
}
19+
20+
/// square distance from a point to a segment
21+
num _getSqSegDist(Position p, Position p1, Position p2) {
22+
var x = p1.lng, y = p1.lat, dx = p2.lng - x, dy = p2.lat - y;
23+
24+
if (dx != 0 || dy != 0) {
25+
var t = ((p.lng - x) * dx + (p.lat - y) * dy) / (dx * dx + dy * dy);
26+
27+
if (t > 1) {
28+
x = p2.lng;
29+
y = p2.lat;
30+
} else if (t > 0) {
31+
x += dx * t;
32+
y += dy * t;
33+
}
34+
}
35+
36+
dx = p.lng - x;
37+
dy = p.lat - y;
38+
39+
return dx * dx + dy * dy;
40+
}
41+
// rest of the code doesn't care about point format
42+
43+
/// basic distance-based simplification
44+
List<Position> _simplifyRadialDist(List<Position> points, double sqTolerance) {
45+
var prevPoint = points[0], newPoints = [prevPoint];
46+
late Position point;
47+
48+
for (var i = 1, len = points.length; i < len; i++) {
49+
point = points[i];
50+
51+
if (_getSqDist(point, prevPoint) > sqTolerance) {
52+
newPoints.add(point);
53+
prevPoint = point;
54+
}
55+
}
56+
57+
if (prevPoint != point) newPoints.add(point);
58+
59+
return newPoints;
60+
}
61+
62+
List<Position> _simplifyDPStep(List<Position> points, int first, int last,
63+
double sqTolerance, List<Position> simplified) {
64+
num maxSqDist = sqTolerance;
65+
late int index;
66+
67+
for (var i = first + 1; i < last; i++) {
68+
var sqDist = _getSqSegDist(points[i], points[first], points[last]);
69+
70+
if (sqDist > maxSqDist) {
71+
index = i;
72+
maxSqDist = sqDist;
73+
}
74+
}
75+
76+
if (maxSqDist > sqTolerance) {
77+
if (index - first > 1) {
78+
simplified =
79+
_simplifyDPStep(points, first, index, sqTolerance, simplified);
80+
}
81+
simplified.add(points[index]);
82+
if (last - index > 1) {
83+
simplified =
84+
_simplifyDPStep(points, index, last, sqTolerance, simplified);
85+
}
86+
}
87+
88+
return simplified;
89+
}
90+
91+
/// simplification using Ramer-Douglas-Peucker algorithm
92+
List<Position> _simplifyDouglasPeucker(List<Position> points, sqTolerance) {
93+
final last = points.length - 1;
94+
95+
var simplified = [points[0]];
96+
simplified = _simplifyDPStep(points, 0, last, sqTolerance, simplified);
97+
simplified.add(points[last]);
98+
99+
return simplified;
100+
}
101+
102+
/// Simplify a LineString feature using dart port of simplify.js high-performance JS polyline simplification library.
103+
///
104+
/// both algorithms combined for awesome performance
105+
Feature<LineString> simplify(
106+
Feature<LineString> points, {
107+
double tolerance = 1,
108+
bool highestQuality = false,
109+
}) {
110+
var coords = getCoords(points);
111+
if (coords.length <= 2) return points;
112+
if (coords is! List<Position>) return points;
113+
114+
final sqTolerance = tolerance * tolerance;
115+
116+
coords = highestQuality ? coords : _simplifyRadialDist(coords, sqTolerance);
117+
coords = _simplifyDouglasPeucker(coords, sqTolerance);
118+
119+
return Feature<LineString>(
120+
id: points.id,
121+
geometry: LineString(coordinates: coords),
122+
properties: points.properties,
123+
bbox: points.bbox,
124+
);
125+
}

test/components/simplify_test.dart

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:test/test.dart';
5+
import 'package:turf/along.dart';
6+
import 'package:turf/area.dart';
7+
import 'package:turf/simplify.dart';
8+
9+
main() {
10+
group(
11+
'simplify in == out',
12+
() {
13+
var inDir = Directory('./test/examples/simplify/in');
14+
for (var file in inDir.listSync(recursive: true)) {
15+
if (file is File && file.path.endsWith('.geojson')) {
16+
test(
17+
file.path,
18+
() {
19+
var inSource = file.readAsStringSync();
20+
var inGeom = Feature<LineString>.fromJson(jsonDecode(inSource));
21+
22+
var inSimplified = simplify(
23+
inGeom,
24+
tolerance: inGeom.properties?['tolerance'] ?? 0.01,
25+
highestQuality: inGeom.properties?['highQuality'] ?? false,
26+
);
27+
28+
// ignore: prefer_interpolation_to_compose_strings
29+
var outPath = './' +
30+
file.uri.pathSegments
31+
.sublist(0, file.uri.pathSegments.length - 2)
32+
.join('/') +
33+
'/out/${file.uri.pathSegments.last}';
34+
35+
var outSource = File(outPath).readAsStringSync();
36+
var outGeom = Feature<LineString>.fromJson(jsonDecode(outSource));
37+
38+
final precision = 0.0001;
39+
expect(inSimplified.id, outGeom.id);
40+
expect(inSimplified.properties, equals(outGeom.properties));
41+
expect(inSimplified.geometry, isNotNull);
42+
expect(
43+
_roundCoords(inSimplified.geometry!.coordinates, precision),
44+
_roundCoords(outGeom.geometry!.coordinates, precision));
45+
},
46+
);
47+
}
48+
}
49+
},
50+
);
51+
test(
52+
'simplify retains id, properties and bbox',
53+
() {
54+
const properties = {"foo": "bar"};
55+
const id = 12345;
56+
final bbox = BBox(0, 0, 2, 2);
57+
final poly = Feature<LineString>(
58+
geometry: LineString(coordinates: [
59+
Position(0, 0),
60+
Position(2, 2),
61+
Position(2, 0),
62+
Position(0, 0),
63+
]),
64+
properties: properties,
65+
bbox: bbox,
66+
id: id,
67+
);
68+
final simple = simplify(poly, tolerance: 0.1);
69+
70+
expect(simple.id, equals(id));
71+
expect(simple.bbox, equals(bbox));
72+
expect(simple.properties, equals(properties));
73+
},
74+
);
75+
}
76+
77+
List<Position> _roundCoords(List<Position> coords, num precision) {
78+
return coords
79+
.map((p) => Position(_round(p.lng, precision), _round(p.lat, precision)))
80+
.toList();
81+
}
82+
83+
num _round(num value, num precision) {
84+
return (value / precision).roundToDouble() * precision;
85+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"type": "Feature",
3+
"properties": {},
4+
"geometry": {
5+
"type": "LineString",
6+
"coordinates": [
7+
[-80.51399230957031, 28.069556808283608],
8+
[-80.51193237304688, 28.057438520876673],
9+
[-80.49819946289062, 28.05622661698537],
10+
[-80.5023193359375, 28.04471284867091],
11+
[-80.48583984375, 28.042288740362853],
12+
[-80.50575256347656, 28.028349057505775],
13+
[-80.50163269042969, 28.02168161433489],
14+
[-80.49476623535156, 28.021075462659883],
15+
[-80.48652648925781, 28.021075462659883],
16+
[-80.47691345214844, 28.021075462659883],
17+
[-80.46936035156249, 28.015619944017807],
18+
[-80.47760009765624, 28.007133032319448],
19+
[-80.49201965332031, 27.998039170620494],
20+
[-80.46730041503906, 27.962262536875905],
21+
[-80.46524047851562, 27.91980029694533],
22+
[-80.40550231933594, 27.930114089618602],
23+
[-80.39657592773438, 27.980455528671527],
24+
[-80.41305541992188, 27.982274659104082],
25+
[-80.42953491210938, 27.990763528690582],
26+
[-80.4144287109375, 28.00955793247135],
27+
[-80.3594970703125, 27.972572275562527],
28+
[-80.36224365234375, 27.948919060105453],
29+
[-80.38215637207031, 27.913732900444284],
30+
[-80.41786193847656, 27.881570017022806],
31+
[-80.40550231933594, 27.860932192608534],
32+
[-80.39382934570312, 27.85425440786446],
33+
[-80.37803649902344, 27.86336037597851],
34+
[-80.38215637207031, 27.880963078302393],
35+
[-80.36842346191405, 27.888246118437756],
36+
[-80.35743713378906, 27.882176952341734],
37+
[-80.35469055175781, 27.86882358965466],
38+
[-80.3594970703125, 27.8421119273228],
39+
[-80.37940979003906, 27.83300417483936],
40+
[-80.39932250976561, 27.82511017099003],
41+
[-80.40069580078125, 27.79352841586229],
42+
[-80.36155700683594, 27.786846483587688],
43+
[-80.35537719726562, 27.794743268514615],
44+
[-80.36705017089844, 27.800209937418252],
45+
[-80.36889553070068, 27.801918215058347],
46+
[-80.3690242767334, 27.803930152059845],
47+
[-80.36713600158691, 27.805942051806845],
48+
[-80.36584854125977, 27.805524490772143],
49+
[-80.36563396453857, 27.80465140342285],
50+
[-80.36619186401367, 27.803095012921272],
51+
[-80.36623477935791, 27.801842292177923],
52+
[-80.36524772644043, 27.80127286888392],
53+
[-80.36224365234375, 27.801158983867033],
54+
[-80.36065578460693, 27.802639479776524],
55+
[-80.36138534545898, 27.803740348273823],
56+
[-80.36220073699951, 27.804803245204976],
57+
[-80.36190032958984, 27.806625330038287],
58+
[-80.3609561920166, 27.80742248254359],
59+
[-80.35932540893555, 27.806853088493792],
60+
[-80.35889625549315, 27.806321651354835],
61+
[-80.35902500152588, 27.805448570411585],
62+
[-80.35863876342773, 27.804461600896783],
63+
[-80.35739421844482, 27.804461600896783],
64+
[-80.35700798034668, 27.805334689771293],
65+
[-80.35696506500244, 27.80673920932572],
66+
[-80.35726547241211, 27.80772615814989],
67+
[-80.35808086395264, 27.808295547623707],
68+
[-80.3585958480835, 27.80928248230861],
69+
[-80.35653591156006, 27.80943431761813],
70+
[-80.35572052001953, 27.808637179875486],
71+
[-80.3555917739868, 27.80772615814989],
72+
[-80.3555917739868, 27.806055931810487],
73+
[-80.35572052001953, 27.803778309057556],
74+
[-80.35537719726562, 27.801804330717825],
75+
[-80.3554630279541, 27.799564581098746],
76+
[-80.35670757293701, 27.799564581098746],
77+
[-80.35499095916748, 27.796831264786892],
78+
[-80.34610748291016, 27.79478123244122],
79+
[-80.34404754638672, 27.802070060660014],
80+
[-80.34748077392578, 27.804955086774896],
81+
[-80.3433609008789, 27.805790211616266],
82+
[-80.34353256225586, 27.8101555324401],
83+
[-80.33499240875244, 27.810079615315917],
84+
[-80.33383369445801, 27.805676331334084],
85+
[-80.33022880554199, 27.801652484744796],
86+
[-80.32872676849365, 27.80848534345178]
87+
]
88+
}
89+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"type": "Feature",
3+
"properties": {},
4+
"geometry": {
5+
"type": "LineString",
6+
"coordinates": [
7+
[-80.513992, 28.069557],
8+
[-80.48584, 28.042289],
9+
[-80.505753, 28.028349],
10+
[-80.476913, 28.021075],
11+
[-80.49202, 27.998039],
12+
[-80.4673, 27.962263],
13+
[-80.46524, 27.9198],
14+
[-80.405502, 27.930114],
15+
[-80.396576, 27.980456],
16+
[-80.429535, 27.990764],
17+
[-80.414429, 28.009558],
18+
[-80.359497, 27.972572],
19+
[-80.382156, 27.913733],
20+
[-80.417862, 27.88157],
21+
[-80.393829, 27.854254],
22+
[-80.368423, 27.888246],
23+
[-80.354691, 27.868824],
24+
[-80.359497, 27.842112],
25+
[-80.399323, 27.82511],
26+
[-80.400696, 27.793528],
27+
[-80.361557, 27.786846],
28+
[-80.359325, 27.806853],
29+
[-80.354991, 27.796831],
30+
[-80.328727, 27.808485]
31+
]
32+
}
33+
}

0 commit comments

Comments
 (0)