Skip to content

Commit

Permalink
Implement polygon-smooth and its tests (dartclub#127)
Browse files Browse the repository at this point in the history
* initial implementation, and refactor

* finished test and benchmark impl

* reference in README
  • Loading branch information
lukas-h authored Aug 22, 2022
1 parent 5770e4c commit f503b90
Show file tree
Hide file tree
Showing 19 changed files with 2,497 additions and 19 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This includes a fully [RFC 7946](https://tools.ietf.org/html/rfc7946)-compliant
Most of the implementation is a direct translation from [turf.js](https://github.com/Turfjs/turf).

## Get started

- Get the [Dart tools](https://dart.dev/tools)
- Install the library with `dart pub add turf`
- Import the library in your code and use it. For example:
Expand Down Expand Up @@ -58,12 +59,14 @@ main() {
![polymorphism](https://user-images.githubusercontent.com/10634693/159876354-f9da2f37-02b3-4546-b32a-c0f82c372272.png)

## Notable Design Decisions

- Nested `GeometryCollections` (as described in
[RFC 7946 section 3.1.8](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8))
are _not supported_ which takes a slightly firmer stance than the "should
avoid" language in the specification

## Tests and Benchmarks

Tests are run with `dart test` and benchmarks can be run with
`dart run benchmark`

Expand All @@ -73,6 +76,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
## Components

### Measurement

- [ ] along
- [x] [area](https://github.com/dartclub/turf_dart/blob/main/lib/src/area.dart)
- [x] [bbox](https://github.com/dartclub/turf_dart/blob/main/lib/src/bbox.dart)
Expand All @@ -96,13 +100,15 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] greatCircle

### Coordinate Mutation

- [x] [cleanCoords](https://github.com/dartclub/turf_dart/blob/main/lib/src/clean_coords.dart)
- [ ] flip
- [ ] rewind
- [ ] round
- [x] [truncate](https://github.com/dartclub/turf_dart/blob/main/lib/src/truncate.dart)

### Transformation

- [ ] bboxClip
- [ ] bezierSpline
- [ ] buffer
Expand All @@ -114,17 +120,18 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] dissolve
- [ ] intersect
- [ ] lineOffset
- [ ] polygonSmooth
- [x] [polygonSmooth](ttps://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_smooth.dart)
- [ ] simplify
- [ ] tesselate
- [ ] transformRotate
- [ ] transformTranslate
- [ ] transformScale
- [ ] union
- [ ] voronoi
- [x] [polyLineDecode](https://github.com/Dennis-Mwea/turf_dart/blob/main/lib/src/polyline.dart)
- [x] [polyLineDecode](https://github.com/dartclub/turf_dart/blob/main/lib/src/polyline.dart)

### Feature Conversion

- [ ] combine
- [x] [explode](https://github.com/dartclub/turf_dart/blob/main/lib/src/explode.dart)
- [ ] flatten
Expand All @@ -133,6 +140,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [x] [polygonToLine](https://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_to_line.dart)

### MISC

- [ ] ellipse
- [ ] kinks
- [ ] lineArc
Expand All @@ -150,40 +158,48 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] unkinkPolygon

### Random

- [ ] randomPosition
- [ ] randomPoint
- [ ] randomLineString
- [ ] randomPolygon

### Data

- [ ] sample

### Interpolation

- [ ] interpolate
- [ ] isobands
- [ ] isolines
- [ ] planepoint
- [ ] tin

### Joins

- [ ] pointsWithinPolygon
- [ ] tag

### Grids

- [ ] hexGrid
- [ ] pointGrid
- [ ] squareGrid
- [ ] triangleGrid

### Classification

- [x] [nearestPoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/nearest_point.dart)

### Aggregation

- [ ] collect
- [ ] clustersDbscan
- [ ] clustersKmeans

### META

- [x] [coordAll](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta/coord.dart)
- [x] [coordEach](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta/coord.dart)
- [x] [coordReduce](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta/coord.dart)
Expand All @@ -204,6 +220,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [x] [clusterReduce](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta/cluster.dart)

### Booleans

- [ ] booleanClockwise
- [ ] booleanConcave
- [ ] booleanContains
Expand All @@ -218,6 +235,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] booleanWithin

### Unit Conversion

- [x] [bearingToAzimuth](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart)
- [x] [convertArea](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart)
- [x] [convertLength](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart)
Expand Down
21 changes: 21 additions & 0 deletions benchmark/polygon_smooth_benchmark.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'dart:convert';
import 'dart:io';

import 'package:benchmark/benchmark.dart';
import 'package:turf/polygon_smooth.dart';
import 'package:turf/turf.dart';

main() {
group("turf-polygon-smooth", () {
var inDir = Directory('./test/examples/polygonSmooth/in');
for (var file in inDir.listSync(recursive: true)) {
if (file is File && file.path.endsWith('.geojson')) {
benchmark(file.path, () {
var inSource = file.readAsStringSync();
var inGeom = GeoJSONObject.fromJson(jsonDecode(inSource));
polygonSmooth(inGeom, iterations: 3);
});
}
}
});
}
3 changes: 3 additions & 0 deletions lib/polygon_smooth.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_polygon_smooth;

export 'src/polygon_smooth.dart';
30 changes: 14 additions & 16 deletions lib/src/line_to_polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,26 +143,24 @@ Feature<Polygon> lineStringToPolygon(
List<List<Position>> multiCoords = [];
num largestArea = 0;

line.coordinates.forEach(
(coord) {
if (autoComplete) {
coord = _autoCompleteCoords(coord);
}
for (var coord in line.coordinates) {
if (autoComplete) {
coord = _autoCompleteCoords(coord);
}

// Largest LineString to be placed in the first position of the coordinates array
if (orderCoords) {
var area = _calculateArea(bbox(LineString(coordinates: coord)));
if (area > largestArea) {
multiCoords.insert(0, coord);
largestArea = area;
} else {
multiCoords.add(coord);
}
// Largest LineString to be placed in the first position of the coordinates array
if (orderCoords) {
var area = _calculateArea(bbox(LineString(coordinates: coord)));
if (area > largestArea) {
multiCoords.insert(0, coord);
largestArea = area;
} else {
multiCoords.add(coord);
}
},
);
} else {
multiCoords.add(coord);
}
}
return Feature(
geometry: Polygon(coordinates: multiCoords), properties: properties);
} else {
Expand Down
152 changes: 152 additions & 0 deletions lib/src/polygon_smooth.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import 'package:turf/meta.dart';
import 'package:turf/turf.dart';

///
/// Smooths a [Polygon], [MultiPolygon], also inside [Feature]s, [FeatureCollection]s, or [GeometryCollection]. Based on [Chaikin's algorithm](http://graphics.cs.ucdavis.edu/education/CAGDNotes/Chaikins-Algorithm/Chaikins-Algorithm.html).
/// Warning: may create degenerate polygons.
/// The optional parameter [iterations] is the number of times to smooth the polygon. A higher value means a smoother polygon.
/// The functions returns a [FeatureCollection] of [Polygon]s and [MultiPolygon]s.
///
/// ```dart
/// var polygon = Polygon(coordinates: [
/// [
/// Position(11, 0),
/// Position(22, 4),
/// Position(31, 0),
/// Position(31, 11),
/// Position(21, 15),
/// Position(11, 11),
/// Position(11, 0),
/// ]
/// ]);
///
/// var smoothed = polygonSmooth(polygon, iterations: 3);
/// ```
FeatureCollection polygonSmooth(GeoJSONObject inputPolys,
{int iterations = 1}) {
var outPolys = <Feature>[];

geomEach(inputPolys, (
GeometryType? geom,
int? geomIndex,
Map<String, dynamic>? featureProperties,
BBox? featureBBox,
dynamic featureId,
) {
var outCoords;
var poly;
var tempOutput;

switch (geom?.type) {
case GeoJSONObjectType.polygon:
outCoords = <List<Position>>[[]];
for (var i = 0; i < iterations; i++) {
tempOutput = <List<Position>>[[]];
poly = geom;
if (i > 0) poly = Polygon(coordinates: outCoords);
_processPolygon(poly, tempOutput);
outCoords = List<List<Position>>.of(tempOutput);
}
outPolys.add(Feature(
geometry: Polygon(coordinates: outCoords),
properties: featureProperties));
break;
case GeoJSONObjectType.multiPolygon:
outCoords = [
[<Position>[]]
];
for (var y = 0; y < iterations; y++) {
tempOutput = <List<List<Position>>>[
[[]]
];
poly = geom;
if (y > 0) poly = MultiPolygon(coordinates: outCoords);
_processMultiPolygon(poly, tempOutput);
outCoords = List<List<List<Position>>>.of(tempOutput);
}
outPolys.add(Feature(
geometry: MultiPolygon(coordinates: outCoords),
properties: featureProperties));
break;
default:
throw Exception("geometry is invalid, must be Polygon or MultiPolygon");
}
});
return FeatureCollection(features: outPolys);
}

_processPolygon(Polygon poly, List<List<Position>> tempOutput) {
var prevGeomIndex = 0;
var subtractCoordIndex = 0;

coordEach(poly, (currentCoord, coordIndex, featureIndex, multiFeatureIndex,
geometryIndex) {
if (geometryIndex! > prevGeomIndex) {
prevGeomIndex = geometryIndex;
subtractCoordIndex = coordIndex!;
tempOutput.add([]);
}
var realCoordIndex = coordIndex! - subtractCoordIndex;
var p1 = poly.coordinates[geometryIndex][realCoordIndex + 1];
var p0x = currentCoord!.lng;
var p0y = currentCoord.lat;
var p1x = p1.lng;
var p1y = p1.lat;
tempOutput[geometryIndex].add(Position(
0.75 * p0x + 0.25 * p1x,
0.75 * p0y + 0.25 * p1y,
));
tempOutput[geometryIndex].add(Position(
0.25 * p0x + 0.75 * p1x,
0.25 * p0y + 0.75 * p1y,
));
}, true);
for (var ring in tempOutput) {
ring.add(ring[0]);
}
}

_processMultiPolygon(poly, List<List<List<Position>>> tempOutput) {
var prevGeomIndex = 0;
var subtractCoordIndex = 0;
var prevMultiIndex = 0;

coordEach(poly, (currentCoord, coordIndex, featureIndex, multiFeatureIndex,
geometryIndex) {
if (multiFeatureIndex! > prevMultiIndex) {
prevMultiIndex = multiFeatureIndex;
subtractCoordIndex = coordIndex!;
tempOutput.add([[]]);
}
if (geometryIndex! > prevGeomIndex) {
prevGeomIndex = geometryIndex;
subtractCoordIndex = coordIndex!;
tempOutput[multiFeatureIndex].add([]);
}
var realCoordIndex = coordIndex! - subtractCoordIndex;
if (realCoordIndex + 1 ==
poly.coordinates[multiFeatureIndex][geometryIndex].length) {
return;
}
var p1 =
poly.coordinates[multiFeatureIndex][geometryIndex][realCoordIndex + 1];
var p0x = currentCoord!.lng;
var p0y = currentCoord.lat;
var p1x = p1.lng;
var p1y = p1.lat;
tempOutput[multiFeatureIndex][geometryIndex].add(Position(
0.75 * p0x + 0.25 * p1x,
0.75 * p0y + 0.25 * p1y,
));
tempOutput[multiFeatureIndex][geometryIndex].add(Position(
0.25 * p0x + 0.75 * p1x,
0.25 * p0y + 0.75 * p1y,
));
}, true);

for (var poly in tempOutput) {
for (var ring in poly) {
ring.add(ring[0]);
}
}
}
4 changes: 3 additions & 1 deletion lib/src/polygon_to_line.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ FeatureCollection _multiPolygonToLine(MultiPolygon geom,
properties = properties ?? <String, dynamic>{};

var lines = <Feature>[];
coords.forEach((coord) => {lines.add(_coordsToLine(coord, properties))});
for (var coord in coords) {
lines.add(_coordsToLine(coord, properties));
}
return FeatureCollection(features: lines);
}

Expand Down
Loading

0 comments on commit f503b90

Please sign in to comment.