Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: simplify for Feature<LineString>, ported from turf.js #183

Merged
merged 2 commits into from
Jul 16, 2024
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
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a round function in helpers:

num round(num value, [num precision = 0]) {

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]
]
}
}
Loading