-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add polygon area calculation #21
Changes from 9 commits
d77070d
e340401
d89c654
4adfa4f
a56f773
0c4382e
dd6eac8
d3ead9a
4b4ff4e
944e581
047fe58
dea04ac
ead6d97
51f86dc
6e57647
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,10 @@ public typealias LocationRadians = Double | |
public typealias RadianDistance = Double | ||
public typealias RadianDirection = Double | ||
|
||
let metersPerRadian = 6_373_000.0 | ||
struct Constants { | ||
static let metersPerRadian = 6_373_000.0 | ||
static let equatorialRadius:Double = 6378137 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Specify |
||
} | ||
|
||
/** | ||
A `RadianCoordinate2D` is a coordinate represented in radians as opposed to | ||
|
@@ -285,3 +288,67 @@ public struct Polyline { | |
return closestCoordinate | ||
} | ||
} | ||
|
||
public struct Polygon { | ||
var polygonCoordinates: [[CLLocationCoordinate2D]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GeoJSON represents a polygon as a collection of rings (one outer ring and multiple inner rings). Similarly, the Mapbox Maps SDKs for iOS and macOS represent a polygon as an outer polygon containing multiple inner polygons. Either way, it’s nice to be able to work with a formal structure instead of a nested array. Consider replacing this property with: var rings: [Ring] or: var outerRing: Ring
var innerRings: [Ring] where |
||
|
||
var polygonArea: Double { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This property already lives inside a struct named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment with a permalink to the specific commit of the Turf package you’re porting this code from, in case we need to track down a discrepancy in the future. |
||
var area:Double = 0 | ||
|
||
if (!polygonCoordinates.isEmpty && polygonCoordinates.count > 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: drop the parentheses. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The more idiomatic way to express this in Swift is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we use a non-optional |
||
|
||
area += abs(ringArea(polygonCoordinates[0])) | ||
|
||
for coordinate in polygonCoordinates.suffix(from: 1) { | ||
area -= abs(ringArea(coordinate)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that each item in A more functional approach would be: let area = abs(ringArea(polygonCoordinates.first ?? []))
- polygonCoordinates.suffix(from: 1)
.map { abs(ringArea($0)) } // convert the inner rings to their areas
.reduce(0, +) // sum the inner areas There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we use separate let area = abs(outerRing.area)
- innerRings
.map { abs($0.area) } // convert the inner rings to their areas
.reduce(0, +) // sum the inner areas |
||
} | ||
} | ||
return area | ||
} | ||
|
||
/** | ||
* Calculate the approximate area of the polygon were it projected onto the earth. | ||
* Note that this area will be positive if ring is oriented clockwise, otherwise it will be negative. | ||
* | ||
* Reference: | ||
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion | ||
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 | ||
* | ||
*/ | ||
private func ringArea(_ coordinates: [CLLocationCoordinate2D]) -> Double { | ||
var p1: CLLocationCoordinate2D | ||
var p2: CLLocationCoordinate2D | ||
var p3: CLLocationCoordinate2D | ||
var lowerIndex: Int | ||
var middleIndex: Int | ||
var upperIndex: Int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These six variables are always written inside each iteration of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a tuple consisting of three coordinates to ensure that all three are set simultaneously: let controlPoints: (CLLocationCoordinate2D, CLLocationCoordinate2D, CLLocationCoordinate2D)
if index == coordinatesCount - 2 {
controlPoints = (coordinates[coordinatesCount - 2], coordinates[coordinatesCount - 1], coordinates[0])
}
// … |
||
var area: Double = 0 | ||
let coordinatesCount: Int = coordinates.count | ||
|
||
if (coordinatesCount > 2) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: drop the parentheses (×3). |
||
for index in 0...coordinatesCount - 1 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (index == coordinatesCount - 2) { | ||
lowerIndex = coordinatesCount - 2 | ||
middleIndex = coordinatesCount - 1 | ||
upperIndex = 0 | ||
} else if(index == coordinatesCount - 1) { | ||
lowerIndex = coordinatesCount - 1 | ||
middleIndex = 0 | ||
upperIndex = 1 | ||
} else { | ||
lowerIndex = index | ||
middleIndex = index + 1 | ||
upperIndex = index + 2 | ||
} | ||
|
||
p1 = coordinates[lowerIndex] | ||
p2 = coordinates[middleIndex] | ||
p3 = coordinates[upperIndex] | ||
area += (p3.longitude.toRadians() - p1.longitude.toRadians()) * sin(p2.latitude.toRadians()) | ||
} | ||
|
||
area = area * Constants.equatorialRadius * Constants.equatorialRadius / 2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: use the |
||
} | ||
return area | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"type": "Feature", | ||
"properties": {}, | ||
"geometry": { | ||
"type": "Polygon", | ||
"coordinates": [ | ||
[ | ||
[125, -15], | ||
[113, -22], | ||
[117, -37], | ||
[130, -33], | ||
[148, -39], | ||
[154, -27], | ||
[144, -15], | ||
[125, -15] | ||
] | ||
] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not sure a generic Constants struct adds much value to the file, since these constants are internal to the library.