diff --git a/lib/center_of_mass.dart b/lib/center_of_mass.dart new file mode 100644 index 00000000..4774e444 --- /dev/null +++ b/lib/center_of_mass.dart @@ -0,0 +1,4 @@ +library center_of_mass.dart; + +export 'package:geotypes/geotypes.dart'; +export 'center_of_mass.dart'; \ No newline at end of file diff --git a/lib/src/center_of_mass.dart b/lib/src/center_of_mass.dart new file mode 100644 index 00000000..f623549d --- /dev/null +++ b/lib/src/center_of_mass.dart @@ -0,0 +1,65 @@ +import 'package:turf/meta.dart'; +import 'centroid.dart'; + +// Takes a Feature and returns its center of mass using the Centroid of Polygon formula +// Link to formula: https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon +Feature centerOfMass( + GeoJSONObject geoJson, { + Map? properties = const {}, +}) { + List coords = []; + + coordEach( + geoJson, + (Position? currentCoord, int? coordIndex, int? featureIndex, + int? multiFeatureIndex, int? geometryIndex) { + if (currentCoord != null && + currentCoord[0] != null && + currentCoord[1] != null) { + coords.add(currentCoord); + } + }, + ); + + // Find centre of geoJSON object + Feature centre = centroid(geoJson); + Position translation = centre.geometry!.coordinates; + + // Set up areas with pre-defined variables + double sx = 0; + double sy = 0; + double sArea = 0; + + List neutralizedPoints = coords.map((point) { + return Position(point[0]! - translation[0]!, point[1]! - translation[1]!); + }).toList(); + + // Compute signed area and weighted sums + for (int i = 0; i < neutralizedPoints.length - 1; i++) { + Position pi = neutralizedPoints[i]; + Position pj = neutralizedPoints[i + 1]; + + double xi = pi[0]!.toDouble(), yi = pi[1]!.toDouble(); + double xj = pj[0]!.toDouble(), yj = pj[1]!.toDouble(); + + double a = xi * yj - xj * yi; + sArea += a; + sx += (xi + xj) * a; + sy += (yi + yj) * a; + } + + if (sArea == 0) { + return centre; + } + + double areaFactor = 1 / (6 * sArea); + List finalCoordinates = [ + translation[0]! + areaFactor * sx, + translation[1]! + areaFactor * sy, + ]; + // returns Point + return Feature( + geometry: Point( + coordinates: Position(finalCoordinates[0], finalCoordinates[1])), + properties: properties); +} diff --git a/test/components/center_of_mass_test.dart b/test/components/center_of_mass_test.dart new file mode 100644 index 00000000..7ce4775d --- /dev/null +++ b/test/components/center_of_mass_test.dart @@ -0,0 +1,80 @@ +import 'package:turf/center_of_mass.dart'; +import 'package:turf/meta.dart'; +import 'package:test/test.dart'; +import 'package:turf/src/center_of_mass.dart'; + +void main () { + + final polygon = Feature( + geometry: Polygon( + coordinates: [ + [ + Position(1, 1), + Position(1, -1), + Position(-1, -1), + Position(-1, 1), + Position(1, 1) + ], + ], + ), + ); + + final expectedOutput = Position(0.0, 0.0); + test('centerOfMass - simple polygon centered around (0,0):', () { + expect(centerOfMass(polygon).geometry?.coordinates, equals(expectedOutput)); + }); + + final polygon2 = Feature( + geometry: Polygon( + coordinates: [ + [ + Position(2, 3), + Position(3, 3), + Position(3, 2), + Position(2, 2), + Position(2, 3) + ], + ], + ) + ); + + final expectedOutput2 = Position(2.5, 2.5); + + test('center of mass - simple polygon centered around non-zero coord:', () { + expect(centerOfMass(polygon2).geometry?.coordinates, equals(expectedOutput2)); + }); + + final polygon3 = Feature( + geometry: Polygon( + coordinates: [ + [ + Position(43, 21), + Position(27, 13), + Position(21, 41), + Position(43, 21) + ], + ], + ) + ); + final expectedOutput3 = Position(30.333333333333332, 25); + + test('Center of mass - complex polygon', () { + expect(centerOfMass(polygon3).geometry?.coordinates, equals(expectedOutput3)); + }); + + final polygon4 = Feature( + geometry: Polygon( + coordinates: [ + [ + Position(40, 20), + Position(39, 20), + Position(40, 20) + ], + ], + ) + ); + final expectedOutput4 = Position(39.5, 20); + test('Center of mass - line polygon', () { + expect(centerOfMass(polygon4).geometry?.coordinates, equals(expectedOutput4)); + }); +} \ No newline at end of file