Skip to content

Commit

Permalink
Add: simplify for Feature<LineString>, ported from turf.js (#183)
Browse files Browse the repository at this point in the history
* Add: simplify for Feature<LineString>, ported from turf.js

* Doc: update Progress.md
  • Loading branch information
leiflinse-trivector authored Jul 16, 2024
1 parent b44f20c commit 9e50c04
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Dart. This is an on going project and functions are being added once needed. If
- [ ] intersect
- [ ] lineOffset
- [x] [polygonSmooth](https://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_smooth.dart)
- [ ] simplify
- [x] [simplify](https://github.com/dartclub/turf_dart/blob/main/lib/src/simplify.dart)
- [ ] tesselate
- [x] [transformRotate](https://github.com/dartclub/turf_dart/blob/main/lib/src/transform_rotate.dart)
- [ ] transformTranslate
Expand Down
4 changes: 4 additions & 0 deletions lib/simplify.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
library turf_simplify;

export 'package:geotypes/geotypes.dart';
export 'src/simplify.dart';
125 changes: 125 additions & 0 deletions lib/src/simplify.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import 'package:turf/helpers.dart';
import 'package:turf/turf.dart';

/*
(c) 2013, Vladimir Agafonkin
Simplify.js, a high-performance JS polyline simplification library
mourner.github.io/simplify-js
*/

// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)

/// square distance between 2 points
num _getSqDist(Position p1, Position p2) {
var dx = p1.lng - p2.lng, dy = p1.lat - p2.lat;

return dx * dx + dy * dy;
}

/// square distance from a point to a segment
num _getSqSegDist(Position p, Position p1, Position p2) {
var x = p1.lng, y = p1.lat, dx = p2.lng - x, dy = p2.lat - y;

if (dx != 0 || dy != 0) {
var t = ((p.lng - x) * dx + (p.lat - y) * dy) / (dx * dx + dy * dy);

if (t > 1) {
x = p2.lng;
y = p2.lat;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}

dx = p.lng - x;
dy = p.lat - y;

return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format

/// basic distance-based simplification
List<Position> _simplifyRadialDist(List<Position> points, double sqTolerance) {
var prevPoint = points[0], newPoints = [prevPoint];
late Position point;

for (var i = 1, len = points.length; i < len; i++) {
point = points[i];

if (_getSqDist(point, prevPoint) > sqTolerance) {
newPoints.add(point);
prevPoint = point;
}
}

if (prevPoint != point) newPoints.add(point);

return newPoints;
}

List<Position> _simplifyDPStep(List<Position> points, int first, int last,
double sqTolerance, List<Position> simplified) {
num maxSqDist = sqTolerance;
late int index;

for (var i = first + 1; i < last; i++) {
var sqDist = _getSqSegDist(points[i], points[first], points[last]);

if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}

if (maxSqDist > sqTolerance) {
if (index - first > 1) {
simplified =
_simplifyDPStep(points, first, index, sqTolerance, simplified);
}
simplified.add(points[index]);
if (last - index > 1) {
simplified =
_simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}

return simplified;
}

/// simplification using Ramer-Douglas-Peucker algorithm
List<Position> _simplifyDouglasPeucker(List<Position> points, sqTolerance) {
final last = points.length - 1;

var simplified = [points[0]];
simplified = _simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.add(points[last]);

return simplified;
}

/// Simplify a LineString feature using dart port of simplify.js high-performance JS polyline simplification library.
///
/// both algorithms combined for awesome performance
Feature<LineString> simplify(
Feature<LineString> points, {
double tolerance = 1,
bool highestQuality = false,
}) {
var coords = getCoords(points);
if (coords.length <= 2) return points;
if (coords is! List<Position>) return points;

final sqTolerance = tolerance * tolerance;

coords = highestQuality ? coords : _simplifyRadialDist(coords, sqTolerance);
coords = _simplifyDouglasPeucker(coords, sqTolerance);

return Feature<LineString>(
id: points.id,
geometry: LineString(coordinates: coords),
properties: points.properties,
bbox: points.bbox,
);
}
85 changes: 85 additions & 0 deletions test/components/simplify_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import 'dart:convert';
import 'dart:io';

import 'package:test/test.dart';
import 'package:turf/along.dart';
import 'package:turf/area.dart';
import 'package:turf/simplify.dart';

main() {
group(
'simplify in == out',
() {
var inDir = Directory('./test/examples/simplify/in');
for (var file in inDir.listSync(recursive: true)) {
if (file is File && file.path.endsWith('.geojson')) {
test(
file.path,
() {
var inSource = file.readAsStringSync();
var inGeom = Feature<LineString>.fromJson(jsonDecode(inSource));

var inSimplified = simplify(
inGeom,
tolerance: inGeom.properties?['tolerance'] ?? 0.01,
highestQuality: inGeom.properties?['highQuality'] ?? false,
);

// ignore: prefer_interpolation_to_compose_strings
var outPath = './' +
file.uri.pathSegments
.sublist(0, file.uri.pathSegments.length - 2)
.join('/') +
'/out/${file.uri.pathSegments.last}';

var outSource = File(outPath).readAsStringSync();
var outGeom = Feature<LineString>.fromJson(jsonDecode(outSource));

final precision = 0.0001;
expect(inSimplified.id, outGeom.id);
expect(inSimplified.properties, equals(outGeom.properties));
expect(inSimplified.geometry, isNotNull);
expect(
_roundCoords(inSimplified.geometry!.coordinates, precision),
_roundCoords(outGeom.geometry!.coordinates, precision));
},
);
}
}
},
);
test(
'simplify retains id, properties and bbox',
() {
const properties = {"foo": "bar"};
const id = 12345;
final bbox = BBox(0, 0, 2, 2);
final poly = Feature<LineString>(
geometry: LineString(coordinates: [
Position(0, 0),
Position(2, 2),
Position(2, 0),
Position(0, 0),
]),
properties: properties,
bbox: bbox,
id: id,
);
final simple = simplify(poly, tolerance: 0.1);

expect(simple.id, equals(id));
expect(simple.bbox, equals(bbox));
expect(simple.properties, equals(properties));
},
);
}

List<Position> _roundCoords(List<Position> coords, num precision) {
return coords
.map((p) => Position(_round(p.lng, precision), _round(p.lat, precision)))
.toList();
}

num _round(num value, num precision) {
return (value / precision).roundToDouble() * precision;
}
89 changes: 89 additions & 0 deletions test/examples/simplify/in/linestring.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-80.51399230957031, 28.069556808283608],
[-80.51193237304688, 28.057438520876673],
[-80.49819946289062, 28.05622661698537],
[-80.5023193359375, 28.04471284867091],
[-80.48583984375, 28.042288740362853],
[-80.50575256347656, 28.028349057505775],
[-80.50163269042969, 28.02168161433489],
[-80.49476623535156, 28.021075462659883],
[-80.48652648925781, 28.021075462659883],
[-80.47691345214844, 28.021075462659883],
[-80.46936035156249, 28.015619944017807],
[-80.47760009765624, 28.007133032319448],
[-80.49201965332031, 27.998039170620494],
[-80.46730041503906, 27.962262536875905],
[-80.46524047851562, 27.91980029694533],
[-80.40550231933594, 27.930114089618602],
[-80.39657592773438, 27.980455528671527],
[-80.41305541992188, 27.982274659104082],
[-80.42953491210938, 27.990763528690582],
[-80.4144287109375, 28.00955793247135],
[-80.3594970703125, 27.972572275562527],
[-80.36224365234375, 27.948919060105453],
[-80.38215637207031, 27.913732900444284],
[-80.41786193847656, 27.881570017022806],
[-80.40550231933594, 27.860932192608534],
[-80.39382934570312, 27.85425440786446],
[-80.37803649902344, 27.86336037597851],
[-80.38215637207031, 27.880963078302393],
[-80.36842346191405, 27.888246118437756],
[-80.35743713378906, 27.882176952341734],
[-80.35469055175781, 27.86882358965466],
[-80.3594970703125, 27.8421119273228],
[-80.37940979003906, 27.83300417483936],
[-80.39932250976561, 27.82511017099003],
[-80.40069580078125, 27.79352841586229],
[-80.36155700683594, 27.786846483587688],
[-80.35537719726562, 27.794743268514615],
[-80.36705017089844, 27.800209937418252],
[-80.36889553070068, 27.801918215058347],
[-80.3690242767334, 27.803930152059845],
[-80.36713600158691, 27.805942051806845],
[-80.36584854125977, 27.805524490772143],
[-80.36563396453857, 27.80465140342285],
[-80.36619186401367, 27.803095012921272],
[-80.36623477935791, 27.801842292177923],
[-80.36524772644043, 27.80127286888392],
[-80.36224365234375, 27.801158983867033],
[-80.36065578460693, 27.802639479776524],
[-80.36138534545898, 27.803740348273823],
[-80.36220073699951, 27.804803245204976],
[-80.36190032958984, 27.806625330038287],
[-80.3609561920166, 27.80742248254359],
[-80.35932540893555, 27.806853088493792],
[-80.35889625549315, 27.806321651354835],
[-80.35902500152588, 27.805448570411585],
[-80.35863876342773, 27.804461600896783],
[-80.35739421844482, 27.804461600896783],
[-80.35700798034668, 27.805334689771293],
[-80.35696506500244, 27.80673920932572],
[-80.35726547241211, 27.80772615814989],
[-80.35808086395264, 27.808295547623707],
[-80.3585958480835, 27.80928248230861],
[-80.35653591156006, 27.80943431761813],
[-80.35572052001953, 27.808637179875486],
[-80.3555917739868, 27.80772615814989],
[-80.3555917739868, 27.806055931810487],
[-80.35572052001953, 27.803778309057556],
[-80.35537719726562, 27.801804330717825],
[-80.3554630279541, 27.799564581098746],
[-80.35670757293701, 27.799564581098746],
[-80.35499095916748, 27.796831264786892],
[-80.34610748291016, 27.79478123244122],
[-80.34404754638672, 27.802070060660014],
[-80.34748077392578, 27.804955086774896],
[-80.3433609008789, 27.805790211616266],
[-80.34353256225586, 27.8101555324401],
[-80.33499240875244, 27.810079615315917],
[-80.33383369445801, 27.805676331334084],
[-80.33022880554199, 27.801652484744796],
[-80.32872676849365, 27.80848534345178]
]
}
}
33 changes: 33 additions & 0 deletions test/examples/simplify/out/linestring.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-80.513992, 28.069557],
[-80.48584, 28.042289],
[-80.505753, 28.028349],
[-80.476913, 28.021075],
[-80.49202, 27.998039],
[-80.4673, 27.962263],
[-80.46524, 27.9198],
[-80.405502, 27.930114],
[-80.396576, 27.980456],
[-80.429535, 27.990764],
[-80.414429, 28.009558],
[-80.359497, 27.972572],
[-80.382156, 27.913733],
[-80.417862, 27.88157],
[-80.393829, 27.854254],
[-80.368423, 27.888246],
[-80.354691, 27.868824],
[-80.359497, 27.842112],
[-80.399323, 27.82511],
[-80.400696, 27.793528],
[-80.361557, 27.786846],
[-80.359325, 27.806853],
[-80.354991, 27.796831],
[-80.328727, 27.808485]
]
}
}

0 comments on commit 9e50c04

Please sign in to comment.