Skip to content

Commit f503b90

Browse files
authored
Implement polygon-smooth and its tests (dartclub#127)
* initial implementation, and refactor * finished test and benchmark impl * reference in README
1 parent 5770e4c commit f503b90

19 files changed

+2497
-19
lines changed

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This includes a fully [RFC 7946](https://tools.ietf.org/html/rfc7946)-compliant
1111
Most of the implementation is a direct translation from [turf.js](https://github.com/Turfjs/turf).
1212

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

6061
## Notable Design Decisions
62+
6163
- Nested `GeometryCollections` (as described in
6264
[RFC 7946 section 3.1.8](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8))
6365
are _not supported_ which takes a slightly firmer stance than the "should
6466
avoid" language in the specification
6567

6668
## Tests and Benchmarks
69+
6770
Tests are run with `dart test` and benchmarks can be run with
6871
`dart run benchmark`
6972

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

7578
### Measurement
79+
7680
- [ ] along
7781
- [x] [area](https://github.com/dartclub/turf_dart/blob/main/lib/src/area.dart)
7882
- [x] [bbox](https://github.com/dartclub/turf_dart/blob/main/lib/src/bbox.dart)
@@ -96,13 +100,15 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
96100
- [ ] greatCircle
97101

98102
### Coordinate Mutation
103+
99104
- [x] [cleanCoords](https://github.com/dartclub/turf_dart/blob/main/lib/src/clean_coords.dart)
100105
- [ ] flip
101106
- [ ] rewind
102107
- [ ] round
103108
- [x] [truncate](https://github.com/dartclub/turf_dart/blob/main/lib/src/truncate.dart)
104109

105110
### Transformation
111+
106112
- [ ] bboxClip
107113
- [ ] bezierSpline
108114
- [ ] buffer
@@ -114,17 +120,18 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
114120
- [ ] dissolve
115121
- [ ] intersect
116122
- [ ] lineOffset
117-
- [ ] polygonSmooth
123+
- [x] [polygonSmooth](ttps://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_smooth.dart)
118124
- [ ] simplify
119125
- [ ] tesselate
120126
- [ ] transformRotate
121127
- [ ] transformTranslate
122128
- [ ] transformScale
123129
- [ ] union
124130
- [ ] voronoi
125-
- [x] [polyLineDecode](https://github.com/Dennis-Mwea/turf_dart/blob/main/lib/src/polyline.dart)
131+
- [x] [polyLineDecode](https://github.com/dartclub/turf_dart/blob/main/lib/src/polyline.dart)
126132

127133
### Feature Conversion
134+
128135
- [ ] combine
129136
- [x] [explode](https://github.com/dartclub/turf_dart/blob/main/lib/src/explode.dart)
130137
- [ ] flatten
@@ -133,6 +140,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
133140
- [x] [polygonToLine](https://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_to_line.dart)
134141

135142
### MISC
143+
136144
- [ ] ellipse
137145
- [ ] kinks
138146
- [ ] lineArc
@@ -150,40 +158,48 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
150158
- [ ] unkinkPolygon
151159

152160
### Random
161+
153162
- [ ] randomPosition
154163
- [ ] randomPoint
155164
- [ ] randomLineString
156165
- [ ] randomPolygon
157166

158167
### Data
168+
159169
- [ ] sample
160170

161171
### Interpolation
172+
162173
- [ ] interpolate
163174
- [ ] isobands
164175
- [ ] isolines
165176
- [ ] planepoint
166177
- [ ] tin
167178

168179
### Joins
180+
169181
- [ ] pointsWithinPolygon
170182
- [ ] tag
171183

172184
### Grids
185+
173186
- [ ] hexGrid
174187
- [ ] pointGrid
175188
- [ ] squareGrid
176189
- [ ] triangleGrid
177190

178191
### Classification
192+
179193
- [x] [nearestPoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/nearest_point.dart)
180194

181195
### Aggregation
196+
182197
- [ ] collect
183198
- [ ] clustersDbscan
184199
- [ ] clustersKmeans
185200

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

206222
### Booleans
223+
207224
- [ ] booleanClockwise
208225
- [ ] booleanConcave
209226
- [ ] booleanContains
@@ -218,6 +235,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
218235
- [ ] booleanWithin
219236

220237
### Unit Conversion
238+
221239
- [x] [bearingToAzimuth](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart)
222240
- [x] [convertArea](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart)
223241
- [x] [convertLength](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:benchmark/benchmark.dart';
5+
import 'package:turf/polygon_smooth.dart';
6+
import 'package:turf/turf.dart';
7+
8+
main() {
9+
group("turf-polygon-smooth", () {
10+
var inDir = Directory('./test/examples/polygonSmooth/in');
11+
for (var file in inDir.listSync(recursive: true)) {
12+
if (file is File && file.path.endsWith('.geojson')) {
13+
benchmark(file.path, () {
14+
var inSource = file.readAsStringSync();
15+
var inGeom = GeoJSONObject.fromJson(jsonDecode(inSource));
16+
polygonSmooth(inGeom, iterations: 3);
17+
});
18+
}
19+
}
20+
});
21+
}

lib/polygon_smooth.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
library turf_polygon_smooth;
2+
3+
export 'src/polygon_smooth.dart';

lib/src/line_to_polygon.dart

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,26 +143,24 @@ Feature<Polygon> lineStringToPolygon(
143143
List<List<Position>> multiCoords = [];
144144
num largestArea = 0;
145145

146-
line.coordinates.forEach(
147-
(coord) {
148-
if (autoComplete) {
149-
coord = _autoCompleteCoords(coord);
150-
}
146+
for (var coord in line.coordinates) {
147+
if (autoComplete) {
148+
coord = _autoCompleteCoords(coord);
149+
}
151150

152-
// Largest LineString to be placed in the first position of the coordinates array
153-
if (orderCoords) {
154-
var area = _calculateArea(bbox(LineString(coordinates: coord)));
155-
if (area > largestArea) {
156-
multiCoords.insert(0, coord);
157-
largestArea = area;
158-
} else {
159-
multiCoords.add(coord);
160-
}
151+
// Largest LineString to be placed in the first position of the coordinates array
152+
if (orderCoords) {
153+
var area = _calculateArea(bbox(LineString(coordinates: coord)));
154+
if (area > largestArea) {
155+
multiCoords.insert(0, coord);
156+
largestArea = area;
161157
} else {
162158
multiCoords.add(coord);
163159
}
164-
},
165-
);
160+
} else {
161+
multiCoords.add(coord);
162+
}
163+
}
166164
return Feature(
167165
geometry: Polygon(coordinates: multiCoords), properties: properties);
168166
} else {

lib/src/polygon_smooth.dart

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import 'package:turf/meta.dart';
2+
import 'package:turf/turf.dart';
3+
4+
///
5+
/// 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).
6+
/// Warning: may create degenerate polygons.
7+
/// The optional parameter [iterations] is the number of times to smooth the polygon. A higher value means a smoother polygon.
8+
/// The functions returns a [FeatureCollection] of [Polygon]s and [MultiPolygon]s.
9+
///
10+
/// ```dart
11+
/// var polygon = Polygon(coordinates: [
12+
/// [
13+
/// Position(11, 0),
14+
/// Position(22, 4),
15+
/// Position(31, 0),
16+
/// Position(31, 11),
17+
/// Position(21, 15),
18+
/// Position(11, 11),
19+
/// Position(11, 0),
20+
/// ]
21+
/// ]);
22+
///
23+
/// var smoothed = polygonSmooth(polygon, iterations: 3);
24+
/// ```
25+
FeatureCollection polygonSmooth(GeoJSONObject inputPolys,
26+
{int iterations = 1}) {
27+
var outPolys = <Feature>[];
28+
29+
geomEach(inputPolys, (
30+
GeometryType? geom,
31+
int? geomIndex,
32+
Map<String, dynamic>? featureProperties,
33+
BBox? featureBBox,
34+
dynamic featureId,
35+
) {
36+
var outCoords;
37+
var poly;
38+
var tempOutput;
39+
40+
switch (geom?.type) {
41+
case GeoJSONObjectType.polygon:
42+
outCoords = <List<Position>>[[]];
43+
for (var i = 0; i < iterations; i++) {
44+
tempOutput = <List<Position>>[[]];
45+
poly = geom;
46+
if (i > 0) poly = Polygon(coordinates: outCoords);
47+
_processPolygon(poly, tempOutput);
48+
outCoords = List<List<Position>>.of(tempOutput);
49+
}
50+
outPolys.add(Feature(
51+
geometry: Polygon(coordinates: outCoords),
52+
properties: featureProperties));
53+
break;
54+
case GeoJSONObjectType.multiPolygon:
55+
outCoords = [
56+
[<Position>[]]
57+
];
58+
for (var y = 0; y < iterations; y++) {
59+
tempOutput = <List<List<Position>>>[
60+
[[]]
61+
];
62+
poly = geom;
63+
if (y > 0) poly = MultiPolygon(coordinates: outCoords);
64+
_processMultiPolygon(poly, tempOutput);
65+
outCoords = List<List<List<Position>>>.of(tempOutput);
66+
}
67+
outPolys.add(Feature(
68+
geometry: MultiPolygon(coordinates: outCoords),
69+
properties: featureProperties));
70+
break;
71+
default:
72+
throw Exception("geometry is invalid, must be Polygon or MultiPolygon");
73+
}
74+
});
75+
return FeatureCollection(features: outPolys);
76+
}
77+
78+
_processPolygon(Polygon poly, List<List<Position>> tempOutput) {
79+
var prevGeomIndex = 0;
80+
var subtractCoordIndex = 0;
81+
82+
coordEach(poly, (currentCoord, coordIndex, featureIndex, multiFeatureIndex,
83+
geometryIndex) {
84+
if (geometryIndex! > prevGeomIndex) {
85+
prevGeomIndex = geometryIndex;
86+
subtractCoordIndex = coordIndex!;
87+
tempOutput.add([]);
88+
}
89+
var realCoordIndex = coordIndex! - subtractCoordIndex;
90+
var p1 = poly.coordinates[geometryIndex][realCoordIndex + 1];
91+
var p0x = currentCoord!.lng;
92+
var p0y = currentCoord.lat;
93+
var p1x = p1.lng;
94+
var p1y = p1.lat;
95+
tempOutput[geometryIndex].add(Position(
96+
0.75 * p0x + 0.25 * p1x,
97+
0.75 * p0y + 0.25 * p1y,
98+
));
99+
tempOutput[geometryIndex].add(Position(
100+
0.25 * p0x + 0.75 * p1x,
101+
0.25 * p0y + 0.75 * p1y,
102+
));
103+
}, true);
104+
for (var ring in tempOutput) {
105+
ring.add(ring[0]);
106+
}
107+
}
108+
109+
_processMultiPolygon(poly, List<List<List<Position>>> tempOutput) {
110+
var prevGeomIndex = 0;
111+
var subtractCoordIndex = 0;
112+
var prevMultiIndex = 0;
113+
114+
coordEach(poly, (currentCoord, coordIndex, featureIndex, multiFeatureIndex,
115+
geometryIndex) {
116+
if (multiFeatureIndex! > prevMultiIndex) {
117+
prevMultiIndex = multiFeatureIndex;
118+
subtractCoordIndex = coordIndex!;
119+
tempOutput.add([[]]);
120+
}
121+
if (geometryIndex! > prevGeomIndex) {
122+
prevGeomIndex = geometryIndex;
123+
subtractCoordIndex = coordIndex!;
124+
tempOutput[multiFeatureIndex].add([]);
125+
}
126+
var realCoordIndex = coordIndex! - subtractCoordIndex;
127+
if (realCoordIndex + 1 ==
128+
poly.coordinates[multiFeatureIndex][geometryIndex].length) {
129+
return;
130+
}
131+
var p1 =
132+
poly.coordinates[multiFeatureIndex][geometryIndex][realCoordIndex + 1];
133+
var p0x = currentCoord!.lng;
134+
var p0y = currentCoord.lat;
135+
var p1x = p1.lng;
136+
var p1y = p1.lat;
137+
tempOutput[multiFeatureIndex][geometryIndex].add(Position(
138+
0.75 * p0x + 0.25 * p1x,
139+
0.75 * p0y + 0.25 * p1y,
140+
));
141+
tempOutput[multiFeatureIndex][geometryIndex].add(Position(
142+
0.25 * p0x + 0.75 * p1x,
143+
0.25 * p0y + 0.75 * p1y,
144+
));
145+
}, true);
146+
147+
for (var poly in tempOutput) {
148+
for (var ring in poly) {
149+
ring.add(ring[0]);
150+
}
151+
}
152+
}

lib/src/polygon_to_line.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ FeatureCollection _multiPolygonToLine(MultiPolygon geom,
4848
properties = properties ?? <String, dynamic>{};
4949

5050
var lines = <Feature>[];
51-
coords.forEach((coord) => {lines.add(_coordsToLine(coord, properties))});
51+
for (var coord in coords) {
52+
lines.add(_coordsToLine(coord, properties));
53+
}
5254
return FeatureCollection(features: lines);
5355
}
5456

0 commit comments

Comments
 (0)