Skip to content

Commit

Permalink
Add flattenEach function and tests (#48)
Browse files Browse the repository at this point in the history
* Add flattenEach function and tests

* Refactor flattenEach slightly

* Refactor meta -- breaking of iterations tests

* Explicitly do not support nested GeometryCollections

* Update documentation links

* Use more specific type checks in flattenEach test

* rm duplicate fcMixed

* remove geometryCollection clause

* warning for nested GeometryCollection

Co-authored-by: Lukas Himsel <lukas@himsel.me>
  • Loading branch information
baparham and lukas-h authored Mar 16, 2022
1 parent f3b6b0a commit 0c4522c
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 62 deletions.
41 changes: 24 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ 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).


## 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 @@ -24,15 +31,15 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] area
- [ ] bbox
- [ ] bboxPolygon
- [x] [bearing](https://github.com/dartclub/turf_dart/blob/master/lib/bearing.dart)
- [x] [bearing](https://github.com/dartclub/turf_dart/blob/main/lib/bearing.dart)
- [ ] center
- [ ] centerOfMass
- [ ] centroid
- [x] [destination](https://github.com/dartclub/turf_dart/blob/master/lib/destination.dart)
- [x] [distance](https://github.com/dartclub/turf_dart/blob/master/lib/distance.dart)
- [x] [destination](https://github.com/dartclub/turf_dart/blob/main/lib/destination.dart)
- [x] [distance](https://github.com/dartclub/turf_dart/blob/main/lib/distance.dart)
- [ ] envelope
- [ ] length
- [x] [midpoint](https://github.com/dartclub/turf_dart/blob/master/lib/midpoint.dart)
- [x] [midpoint](https://github.com/dartclub/turf_dart/blob/main/lib/midpoint.dart)
- [ ] pointOnFeature
- [ ] polygonTangents
- [ ] pointToLineDistance
Expand Down Expand Up @@ -120,7 +127,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] triangleGrid

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

### Aggregation
- [ ] collect
Expand All @@ -131,17 +138,17 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] coordAll
- [x] coordEach
- [ ] coordReduce
- [x] featureEach
- [x] [featureEach](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta.dart#L157)
- [ ] featureReduce
- [ ] flattenEach
- [x] [flattenEach](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta.dart#L181)
- [ ] flattenReduce
- [ ] getCoord
- [ ] getCoords
- [ ] getGeom
- [ ] getType
- [x] geomEach
- [x] [geomEach](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta.dart#L34)
- [ ] geomReduce
- [x] propEach
- [x] [propEach](https://github.com/dartclub/turf_dart/blob/main/lib/src/meta.dart#L124)
- [ ] propReduce
- [ ] segmentEach
- [ ] segmentReduce
Expand All @@ -168,13 +175,13 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] booleanWithin

### Unit Conversion
- [x] [bearingToAzimuth](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L103)
- [x] [convertArea](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L132)
- [x] [convertLength](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L121)
- [x] [degreesToRadians](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L116)
- [x] [lengthToRadians](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L91)
- [x] [lengthToDegrees](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L99)
- [x] [radiansToLength](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L83)
- [x] [radiansToDegrees](https://github.com/dartclub/turf_dart/blob/master/lib/src/helpers.dart#L111)
- [x] [bearingToAzimuth](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L103)
- [x] [convertArea](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L132)
- [x] [convertLength](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L121)
- [x] [degreesToRadians](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L116)
- [x] [lengthToRadians](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L91)
- [x] [lengthToDegrees](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L99)
- [x] [radiansToLength](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L83)
- [x] [radiansToDegrees](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart#L111)
- [ ] toMercator
- [ ] toWgs84
3 changes: 3 additions & 0 deletions lib/src/geojson.dart
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ abstract class GeometryType<T> extends GeometryObject {
return Polygon.fromJson(json);
case GeoJSONObjectType.multiPolygon:
return MultiPolygon.fromJson(json);
case GeoJSONObjectType.geometryCollection:
throw Exception(
'This implementation does not support nested GeometryCollections');
default:
throw Exception('${json['type']} is not a valid GeoJSON type');
}
Expand Down
80 changes: 79 additions & 1 deletion lib/src/meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ void coordEach(GeoJSONObject geoJSON, CoordEachCallback callback,
}

typedef GeomEachCallback = dynamic Function(
GeometryObject? currentGeometry,
GeometryType? currentGeometry,
int? featureIndex,
Map<String, dynamic>? featureProperties,
BBox? featureBBox,
Expand Down Expand Up @@ -309,3 +309,81 @@ void featureEach(GeoJSONObject geoJSON, FeatureEachCallback callback) {
throw Exception('Unknown Feature/FeatureCollection Type');
}
}

/// Callback for flattenEach
typedef FlattenEachCallback = dynamic Function(
Feature currentFeature, int featureIndex, int multiFeatureIndex);

/// Iterate over flattened features in any [geoJSON] object, similar to
/// Array.forEach, calling [callback] on each flattened feature
///
/// flattenEach(featureCollection, (currentFeature, featureIndex, multiFeatureIndex) {
/// someOperationOnEachFeature(currentFeature);
/// });
/// ```
void flattenEach(GeoJSONObject geoJSON, FlattenEachCallback callback) {
try {
geomEach(geoJSON, (GeometryType? currentGeomObject, featureIndex,
featureProperties, featureBBox, featureId) {
if (currentGeomObject == null ||
currentGeomObject is Point ||
currentGeomObject is LineString ||
currentGeomObject is Polygon) {
_callFlattenEachCallback(callback, currentGeomObject as GeometryType,
featureProperties, featureIndex, 0);
} else {
_forEachFeatureOfMultiFeature(
currentGeomObject, callback, featureProperties, featureIndex);
}
});
} on _ShortCircuit {
return;
}
}

void _forEachFeatureOfMultiFeature(
GeoJSONObject currentGeomObject,
FlattenEachCallback callback,
Map<String, dynamic>? featureProperties,
int? featureIndex) {
if (currentGeomObject is GeometryType) {
for (int multiFeatureIndex = 0;
multiFeatureIndex < currentGeomObject.coordinates.length;
multiFeatureIndex++) {
GeometryType geom;
if (currentGeomObject is MultiPoint) {
geom = Point(
coordinates: currentGeomObject.coordinates[multiFeatureIndex]);
} else if (currentGeomObject is MultiLineString) {
geom = LineString(
coordinates: currentGeomObject.coordinates[multiFeatureIndex]);
} else if (currentGeomObject is MultiPolygon) {
geom = Polygon(
coordinates: currentGeomObject.coordinates[multiFeatureIndex]);
} else {
throw Exception('Unsupported Geometry type');
}
_callFlattenEachCallback(
callback, geom, featureProperties, featureIndex, multiFeatureIndex);
}
}
}

void _callFlattenEachCallback(
FlattenEachCallback callback,
GeometryType<dynamic> geom,
Map<String, dynamic>? featureProperties,
int? featureIndex,
int multiFeatureIndex) {
if (callback(
Feature(
geometry: geom,
properties: featureProperties,
),
featureIndex ?? 0,
multiFeatureIndex) ==
false) {
throw _ShortCircuit();
}
}
142 changes: 98 additions & 44 deletions test/components/meta_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Feature<Point> pt = Feature<Point>(
},
);

Feature<Point> pt2 = Feature<Point>(
geometry: Point.fromJson({
'coordinates': [1, 1],
}),
);

Feature<LineString> line = Feature<LineString>(
geometry: LineString.fromJson({
'coordinates': [
Expand Down Expand Up @@ -69,6 +75,14 @@ Feature<MultiLineString> multiline = Feature<MultiLineString>(
}),
);

Feature<MultiPoint> multiPoint = Feature<MultiPoint>(
geometry: MultiPoint.fromJson({
'coordinates': [
[0, 0],
[1, 1],
],
}));

Feature<MultiPolygon> multiPoly = Feature<MultiPolygon>(
geometry: MultiPolygon.fromJson({
'coordinates': [
Expand Down Expand Up @@ -466,15 +480,6 @@ main() {
});
});

test('propEach --breaking of iterations', () {
var count = 0;
propEach(multiline, (prop, i) {
count += 1;
return false;
});
expect(count, 1);
});

test('featureEach --featureCollection', () {
collection(pt).forEach((input) {
featureEach(input, (feature, i) {
Expand All @@ -501,15 +506,6 @@ main() {
});
});

test('featureEach --breaking of iterations', () {
var count = 0;
featureEach(multiline, (feature, i) {
count += 1;
return false;
});
expect(count, 1);
});

test('geomEach -- GeometryCollection', () {
featureAndCollection(geomCollection.geometry!)
.forEach((GeoJSONObject input) {
Expand Down Expand Up @@ -580,7 +576,7 @@ main() {
);
});

test('meta -- breaking of iterations', () {
group('meta -- breaking of iterations', () {
FeatureCollection<LineString> lines = FeatureCollection<LineString>(
features: [
Feature<LineString>(
Expand Down Expand Up @@ -619,37 +615,95 @@ main() {
]
}),
);

int iterationCount = 0;

void runBreakingIterationTest(dynamic func, dynamic callback) {
iterationCount = 0;
func(lines, callback);
expect(iterationCount, 1, reason: func.toString());
iterationCount = 0;
func(multiLine, callback);
expect(iterationCount, 1, reason: func.toString());
}

// Each Iterators
// meta.segmentEach has been purposely excluded from this list
// TODO fill out this list will all 'each' iterators
for (Function func in [geomEach]) {
// Meta Each function should only a value of 1 after returning `false`
// FeatureCollection
var count = 0;
func(lines, (
GeometryObject? currentGeometry,
int? featureIndex,
Map<String, dynamic>? featureProperties,
BBox? featureBBox,
dynamic featureId,
) {
count += 1;
test('geomEach', () {
runBreakingIterationTest(geomEach, (geom, i, props, bbox, id) {
iterationCount += 1;
return false;
});
expect(count, 1, reason: func.toString());
// Multi Geometry
var multiCount = 0;
func(multiLine, (
GeometryObject? currentGeometry,
int? featureIndex,
Map<String, dynamic>? featureProperties,
BBox? featureBBox,
dynamic featureId,
) {
multiCount += 1;
});

test('flattenEach', () {
runBreakingIterationTest(flattenEach, (feature, i, mI) {
iterationCount += 1;
return false;
});
expect(multiCount, 1, reason: func.toString());
}
});

test('propEach', () {
runBreakingIterationTest(propEach, (prop, i) {
iterationCount += 1;
return false;
});
});

test('featureEach', () {
runBreakingIterationTest(featureEach, (feature, i) {
iterationCount += 1;
return false;
});
});
});

test('flattenEach -- MultiPoint', () {
featureAndCollection(multiPoint.geometry!).forEach((input) {
List<GeometryObject?> output = [];
flattenEach(input, (currentFeature, index, multiIndex) {
output.add(currentFeature.geometry);
});
expect(output, [pt.geometry!, pt2.geometry!]);
});
});

test('flattenEach -- Mixed FeatureCollection', () {
List<Feature> features = [];
List<int> featureIndices = [];
List<int> multiFeatureIndicies = [];
flattenEach(fcMixed, (currentFeature, index, multiIndex) {
features.add(currentFeature);
featureIndices.add(index);
multiFeatureIndicies.add(multiIndex);
});
expect(featureIndices, [0, 1, 2, 2]);
expect(multiFeatureIndicies, [0, 0, 0, 1]);
expect(features.length, 4);
expect(features[0].geometry, isA<Point>());
expect(features[1].geometry, isA<LineString>());
expect(features[2].geometry, isA<LineString>());
expect(features[3].geometry, isA<LineString>());
});

test('flattenEach -- Point-properties', () {
collection(pt).forEach((input) {
Map<String, dynamic>? lastProperties;
flattenEach(input, (currentFeature, index, multiIndex) {
lastProperties = currentFeature.properties;
});
expect(lastProperties, pt.properties);
});
});

test('flattenEach -- multiGeometryFeature-properties', () {
collection(geomCollection).forEach((element) {
Map<String, dynamic>? lastProperties;
flattenEach(element, (currentFeature, index, multiIndex) {
lastProperties = currentFeature.properties;
});
expect(lastProperties, geomCollection.properties);
});
});
}

0 comments on commit 0c4522c

Please sign in to comment.