diff --git a/README.md b/README.md
index 975b18d6..7acdce7b 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,9 @@ Turf.js | Turf-swift
[turf-bearing](https://turfjs.org/docs/#bearing) | `CLLocationCoordinate2D.direction(to:)`
`LocationCoordinate2D.direction(to:)` on Linux
`RadianCoordinate2D.direction(to:)`
[turf-bezier-spline](https://github.com/Turfjs/turf/tree/master/packages/turf-bezier-spline/) | `LineString.bezier(resolution:sharpness:)`
[turf-boolean-point-in-polygon](https://github.com/Turfjs/turf/tree/master/packages/turf-boolean-point-in-polygon) | `Polygon.contains(_:ignoreBoundary:)`
+[turf-center](http://turfjs.org/docs/#center) | `Polygon.center`
+[turf-center-of-mass](http://turfjs.org/docs/#centerOfMass) | `Polygon.centerOfMass`
+[turf-centroid](http://turfjs.org/docs/#centroid) | `Polygon.centroid`
[turf-circle](https://turfjs.org/docs/#circle) | `Polygon(center:radius:vertices:)` |
[turf-destination](https://github.com/Turfjs/turf/tree/master/packages/turf-destination/) | `CLLocationCoordinate2D.coordinate(at:facing:)`
`LocationCoordinate2D.coordinate(at:facing:)` on Linux
`RadianCoordinate2D.coordinate(at:facing:)`
[turf-distance](https://github.com/Turfjs/turf/tree/master/packages/turf-distance/) | `CLLocationCoordinate2D.distance(to:)`
`LocationCoordinate2D.distance(to:)` on Linux
`RadianCoordinate2D.distance(to:)`
diff --git a/Sources/Turf/CoreLocation.swift b/Sources/Turf/CoreLocation.swift
index 601cc841..aabf50a7 100644
--- a/Sources/Turf/CoreLocation.swift
+++ b/Sources/Turf/CoreLocation.swift
@@ -54,12 +54,12 @@ public struct LocationCoordinate2D {
/**
The latitude in degrees.
*/
- public let latitude: LocationDegrees
+ public var latitude: LocationDegrees
/**
The longitude in degrees.
*/
- public let longitude: LocationDegrees
+ public var longitude: LocationDegrees
/**
Creates a degree-based geographic coordinate.
@@ -71,6 +71,18 @@ public struct LocationCoordinate2D {
}
#endif
+extension LocationCoordinate2D {
+ /**
+ Returns a normalized coordinate, wrapped to -180 and 180 degrees latitude
+ */
+ var normalized: LocationCoordinate2D {
+ return .init(
+ latitude: latitude,
+ longitude: longitude.wrap(min: -180, max: 180)
+ )
+ }
+}
+
extension LocationDirection {
/**
Returns a normalized number given min and max bounds.
diff --git a/Sources/Turf/Geometries/Polygon.swift b/Sources/Turf/Geometries/Polygon.swift
index 66ae251a..de6e45cc 100644
--- a/Sources/Turf/Geometries/Polygon.swift
+++ b/Sources/Turf/Geometries/Polygon.swift
@@ -297,4 +297,70 @@ extension Polygon {
ring[2].longitude == ring[0].longitude
)
}
-}
+
+ /// Calculates the absolute centre (of the bounding box).
+ public var center: LocationCoordinate2D? {
+ // This implementation is a port of: https://github.com/Turfjs/turf/blob/master/packages/turf-center/index.ts
+ return BoundingBox(from: outerRing.coordinates)
+ .map { .init(
+ latitude: ($0.southWest.latitude + $0.northEast.latitude) / 2,
+ longitude: ($0.southWest.longitude + $0.northEast.longitude) / 2
+ ) }
+ }
+
+ /// Calculates the centroid using the mean of all vertices.
+ /// This lessens the effect of small islands and artifacts when calculating the centroid of a set of polygons.
+ public var centroid: LocationCoordinate2D? {
+ // This implementation is a port of: https://github.com/Turfjs/turf/blob/master/packages/turf-centroid/index.ts
+
+ let coordinates = outerRing.coordinates.dropLast()
+ guard coordinates.count > 0 else { return nil }
+
+ let summed = coordinates
+ .reduce(into: LocationCoordinate2D(latitude: 0, longitude: 0)) { acc, next in
+ acc.latitude += next.latitude
+ acc.longitude += next.longitude
+ }
+ return LocationCoordinate2D(
+ latitude: summed.latitude / Double(coordinates.count),
+ longitude: summed.longitude / Double(coordinates.count)
+ ).normalized
+ }
+
+ /// Calculates the [center of mass](https://en.wikipedia.org/wiki/Center_of_mass) using this formula: [Centroid of Polygon](https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon).
+ public var centerOfMass: LocationCoordinate2D? {
+ // This implementation is a port of: https://github.com/Turfjs/turf/blob/master/packages/turf-center-of-mass/index.ts
+
+ // First, we neutralize the feature (set it around coordinates [0,0]) to prevent rounding errors
+ // We take any point to translate all the points around 0
+ guard let center = centroid else { return nil }
+ let coordinates = outerRing.coordinates
+ let neutralized = coordinates.map {
+ LocationCoordinate2D(latitude: $0.latitude - center.latitude, longitude: $0.longitude - center.longitude)
+ }
+
+ var signedArea: Double = 0
+ var sum = LocationCoordinate2D(latitude: 0, longitude: 0)
+ let zipped = zip(neutralized.prefix(upTo: neutralized.count - 1), neutralized.suffix(from: 1))
+ for (pi, pj) in zipped {
+ let (xi, yi) = (pi.longitude, pi.latitude)
+ let (xj, yj) = (pj.longitude, pj.latitude)
+
+ // common factor to compute the signed area and the final coordinates
+ let a = xi * yj - xj * yi
+ signedArea += a
+ sum.longitude += (xi + xj) * a
+ sum.latitude += (yi + yj) * a
+ }
+ guard signedArea != 0 else { return center }
+
+ // compute signed area, and factorise 1/6A
+ let area = signedArea / 2
+ let areaFactor = 1 / (6 * area)
+
+ // final coordinates, adding back values that have been neutralized
+ return LocationCoordinate2D(
+ latitude: center.latitude + areaFactor * sum.latitude,
+ longitude: center.longitude + areaFactor * sum.longitude
+ ).normalized
+ }}
diff --git a/Tests/TurfTests/PolygonTests.swift b/Tests/TurfTests/PolygonTests.swift
index 945f6554..16281168 100644
--- a/Tests/TurfTests/PolygonTests.swift
+++ b/Tests/TurfTests/PolygonTests.swift
@@ -142,6 +142,153 @@ class PolygonTests: XCTestCase {
XCTAssertEqual(expectedDiameter, diameter, accuracy: 0.25)
}
+
+ func testPolygonCentre() {
+ // Adopted from https://github.com/Turfjs/turf/blob/3b20c568e5638f680cde39c26b56fbcf034133f2/packages/turf-center/test.js
+ let coordinate = LocationCoordinate2D(latitude: 45.7536760235992, longitude: 4.841880798339844)
+ let polygon = Polygon([
+ [
+ LocationCoordinate2D(latitude: 45.79398056386735, longitude: 4.8250579833984375),
+ LocationCoordinate2D(latitude: 45.79254427435898, longitude: 4.882392883300781),
+ LocationCoordinate2D(latitude: 45.76081677972451, longitude: 4.910373687744141),
+ LocationCoordinate2D(latitude: 45.7271539426975, longitude: 4.894924163818359),
+ LocationCoordinate2D(latitude: 45.71337148333104, longitude: 4.824199676513671),
+ LocationCoordinate2D(latitude: 45.74021417890731, longitude: 4.773387908935547),
+ LocationCoordinate2D(latitude: 45.778418789239055, longitude: 4.778022766113281),
+ LocationCoordinate2D(latitude: 45.79398056386735, longitude: 4.8250579833984375),
+ ],
+ ])
+ let center = polygon.center!
+ XCTAssertLessThan(center.distance(to: coordinate), 1)
+ }
+
+ func testPolygonImbalancedCentre() {
+ // Adopted from https://github.com/Turfjs/turf/blob/3b20c568e5638f680cde39c26b56fbcf034133f2/packages/turf-center/test.js
+ let coordinate = LocationCoordinate2D(latitude: 45.778762648296855, longitude: 4.851944446563721)
+ let polygon = Polygon([
+ [
+ LocationCoordinate2D(latitude: 45.77258200374433, longitude: 4.854240417480469),
+ LocationCoordinate2D(latitude: 45.777431068484894, longitude: 4.8445844650268555),
+ LocationCoordinate2D(latitude: 45.778658234059755, longitude: 4.845442771911621),
+ LocationCoordinate2D(latitude: 45.779376562352425, longitude: 4.845914840698242),
+ LocationCoordinate2D(latitude: 45.78021460033108, longitude: 4.846644401550292),
+ LocationCoordinate2D(latitude: 45.78078326178593, longitude: 4.847245216369629),
+ LocationCoordinate2D(latitude: 45.78138184652523, longitude: 4.848060607910156),
+ LocationCoordinate2D(latitude: 45.78186070968964, longitude: 4.8487043380737305),
+ LocationCoordinate2D(latitude: 45.78248921135124, longitude: 4.849562644958495),
+ LocationCoordinate2D(latitude: 45.78302792142197, longitude: 4.850893020629883),
+ LocationCoordinate2D(latitude: 45.78374619341895, longitude: 4.852008819580077),
+ LocationCoordinate2D(latitude: 45.784075398324866, longitude: 4.852995872497559),
+ LocationCoordinate2D(latitude: 45.78443452873236, longitude: 4.853854179382324),
+ LocationCoordinate2D(latitude: 45.78470387501975, longitude: 4.8549699783325195),
+ LocationCoordinate2D(latitude: 45.784793656826345, longitude: 4.85569953918457),
+ LocationCoordinate2D(latitude: 45.784853511283764, longitude: 4.857330322265624),
+ LocationCoordinate2D(latitude: 45.78494329284938, longitude: 4.858231544494629),
+ LocationCoordinate2D(latitude: 45.784883438488365, longitude: 4.859304428100585),
+ LocationCoordinate2D(latitude: 45.77294120818474, longitude: 4.858360290527344),
+ LocationCoordinate2D(latitude: 45.77258200374433, longitude: 4.854240417480469)
+ ],
+ ])
+ let center = polygon.center!
+ XCTAssertLessThan(center.distance(to: coordinate), 1)
+ }
+
+ func testPolygonCentroid() {
+ // Adopted from https://github.com/Turfjs/turf/blob/3b20c568e5638f680cde39c26b56fbcf034133f2/packages/turf-centroid/test.js
+ let coordinate = LocationCoordinate2D(latitude: 45.75807143030368, longitude: 4.841194152832031)
+ let polygon = Polygon([
+ [
+ LocationCoordinate2D(latitude: 45.79398056386735, longitude: 4.8250579833984375),
+ LocationCoordinate2D(latitude: 45.79254427435898, longitude: 4.882392883300781),
+ LocationCoordinate2D(latitude: 45.76081677972451, longitude: 4.910373687744141),
+ LocationCoordinate2D(latitude: 45.7271539426975, longitude: 4.894924163818359),
+ LocationCoordinate2D(latitude: 45.71337148333104, longitude: 4.824199676513671),
+ LocationCoordinate2D(latitude: 45.74021417890731, longitude: 4.773387908935547),
+ LocationCoordinate2D(latitude: 45.778418789239055, longitude: 4.778022766113281),
+ LocationCoordinate2D(latitude: 45.79398056386735, longitude: 4.8250579833984375),
+ ],
+ ])
+ XCTAssertLessThan(polygon.centroid!.distance(to: coordinate), 1)
+ }
+
+ func testPolygonImbalancedCentroid() {
+ // Adopted from https://github.com/Turfjs/turf/blob/3b20c568e5638f680cde39c26b56fbcf034133f2/packages/turf-centroid/test.js
+ let coordinate = LocationCoordinate2D(latitude: 45.78143055383553, longitude: 4.851791984156558)
+ let polygon = Polygon([
+ [
+ LocationCoordinate2D(latitude: 45.77258200374433, longitude: 4.854240417480469),
+ LocationCoordinate2D(latitude: 45.777431068484894, longitude: 4.8445844650268555),
+ LocationCoordinate2D(latitude: 45.778658234059755, longitude: 4.845442771911621),
+ LocationCoordinate2D(latitude: 45.779376562352425, longitude: 4.845914840698242),
+ LocationCoordinate2D(latitude: 45.78021460033108, longitude: 4.846644401550292),
+ LocationCoordinate2D(latitude: 45.78078326178593, longitude: 4.847245216369629),
+ LocationCoordinate2D(latitude: 45.78138184652523, longitude: 4.848060607910156),
+ LocationCoordinate2D(latitude: 45.78186070968964, longitude: 4.8487043380737305),
+ LocationCoordinate2D(latitude: 45.78248921135124, longitude: 4.849562644958495),
+ LocationCoordinate2D(latitude: 45.78302792142197, longitude: 4.850893020629883),
+ LocationCoordinate2D(latitude: 45.78374619341895, longitude: 4.852008819580077),
+ LocationCoordinate2D(latitude: 45.784075398324866, longitude: 4.852995872497559),
+ LocationCoordinate2D(latitude: 45.78443452873236, longitude: 4.853854179382324),
+ LocationCoordinate2D(latitude: 45.78470387501975, longitude: 4.8549699783325195),
+ LocationCoordinate2D(latitude: 45.784793656826345, longitude: 4.85569953918457),
+ LocationCoordinate2D(latitude: 45.784853511283764, longitude: 4.857330322265624),
+ LocationCoordinate2D(latitude: 45.78494329284938, longitude: 4.858231544494629),
+ LocationCoordinate2D(latitude: 45.784883438488365, longitude: 4.859304428100585),
+ LocationCoordinate2D(latitude: 45.77294120818474, longitude: 4.858360290527344),
+ LocationCoordinate2D(latitude: 45.77258200374433, longitude: 4.854240417480469)
+ ],
+ ])
+ XCTAssertLessThan(polygon.centroid!.distance(to: coordinate), 1)
+ }
+
+ func testPolygonCentreOfMass() {
+ // Adopted from https://github.com/Turfjs/turf/blob/3b20c568e5638f680cde39c26b56fbcf034133f2/packages/turf-center-of-mass/test.js
+ let coordinate = LocationCoordinate2D(latitude: 45.75581209996416, longitude: 4.840728965137111)
+ let polygon = Polygon([
+ [
+ LocationCoordinate2D(latitude: 45.79398056386735, longitude: 4.8250579833984375),
+ LocationCoordinate2D(latitude: 45.79254427435898, longitude: 4.882392883300781),
+ LocationCoordinate2D(latitude: 45.76081677972451, longitude: 4.910373687744141),
+ LocationCoordinate2D(latitude: 45.7271539426975, longitude: 4.894924163818359),
+ LocationCoordinate2D(latitude: 45.71337148333104, longitude: 4.824199676513671),
+ LocationCoordinate2D(latitude: 45.74021417890731, longitude: 4.773387908935547),
+ LocationCoordinate2D(latitude: 45.778418789239055, longitude: 4.778022766113281),
+ LocationCoordinate2D(latitude: 45.79398056386735, longitude: 4.8250579833984375),
+ ],
+ ])
+ XCTAssertLessThan(polygon.centerOfMass!.distance(to: coordinate), 1)
+ }
+
+ func testPolygonImbalancedCentreOfMass() {
+ // Adopted from https://github.com/Turfjs/turf/blob/3b20c568e5638f680cde39c26b56fbcf034133f2/packages/turf-center-of-mass/test.js
+ let coordinate = LocationCoordinate2D(latitude: 45.77877742486245, longitude: 4.853372894819807)
+ let polygon = Polygon([
+ [
+ LocationCoordinate2D(latitude: 45.77258200374433, longitude: 4.854240417480469),
+ LocationCoordinate2D(latitude: 45.777431068484894, longitude: 4.8445844650268555),
+ LocationCoordinate2D(latitude: 45.778658234059755, longitude: 4.845442771911621),
+ LocationCoordinate2D(latitude: 45.779376562352425, longitude: 4.845914840698242),
+ LocationCoordinate2D(latitude: 45.78021460033108, longitude: 4.846644401550292),
+ LocationCoordinate2D(latitude: 45.78078326178593, longitude: 4.847245216369629),
+ LocationCoordinate2D(latitude: 45.78138184652523, longitude: 4.848060607910156),
+ LocationCoordinate2D(latitude: 45.78186070968964, longitude: 4.8487043380737305),
+ LocationCoordinate2D(latitude: 45.78248921135124, longitude: 4.849562644958495),
+ LocationCoordinate2D(latitude: 45.78302792142197, longitude: 4.850893020629883),
+ LocationCoordinate2D(latitude: 45.78374619341895, longitude: 4.852008819580077),
+ LocationCoordinate2D(latitude: 45.784075398324866, longitude: 4.852995872497559),
+ LocationCoordinate2D(latitude: 45.78443452873236, longitude: 4.853854179382324),
+ LocationCoordinate2D(latitude: 45.78470387501975, longitude: 4.8549699783325195),
+ LocationCoordinate2D(latitude: 45.784793656826345, longitude: 4.85569953918457),
+ LocationCoordinate2D(latitude: 45.784853511283764, longitude: 4.857330322265624),
+ LocationCoordinate2D(latitude: 45.78494329284938, longitude: 4.858231544494629),
+ LocationCoordinate2D(latitude: 45.784883438488365, longitude: 4.859304428100585),
+ LocationCoordinate2D(latitude: 45.77294120818474, longitude: 4.858360290527344),
+ LocationCoordinate2D(latitude: 45.77258200374433, longitude: 4.854240417480469)
+ ],
+ ])
+ let center = polygon.centerOfMass!
+ XCTAssertLessThan(center.distance(to: coordinate), 1)
+ }
func testSmoothClose() {
let original = [