Added sources

This commit is contained in:
Peter Ringset 2017-03-17 13:01:31 +01:00
parent a453ded16f
commit 0ed858c769
5 changed files with 414 additions and 0 deletions

View File

@ -14,6 +14,10 @@
454361D81E7BECE300656472 /* UTMConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 454361761E7BE75800656472 /* UTMConversion.h */; settings = {ATTRIBUTES = (Public, ); }; };
454361D91E7BECE700656472 /* UTMConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 454361761E7BE75800656472 /* UTMConversion.h */; settings = {ATTRIBUTES = (Public, ); }; };
454361DA1E7BECE900656472 /* UTMConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 454361761E7BE75800656472 /* UTMConversion.h */; settings = {ATTRIBUTES = (Public, ); }; };
454361EE1E7C077E00656472 /* RadianDegrees.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454361EB1E7C077E00656472 /* RadianDegrees.swift */; };
454361EF1E7C077E00656472 /* TMCoordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454361EC1E7C077E00656472 /* TMCoordinate.swift */; };
454361F01E7C077E00656472 /* UTMConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454361ED1E7C077E00656472 /* UTMConversion.swift */; };
454361F21E7C078D00656472 /* UTMConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454361F11E7C078D00656472 /* UTMConversionTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -53,6 +57,10 @@
454361CA1E7BE7B000656472 /* UTMConversion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UTMConversion.framework; sourceTree = BUILT_PRODUCTS_DIR; };
454361DB1E7BECFA00656472 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
454361DE1E7BED1500656472 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
454361EB1E7C077E00656472 /* RadianDegrees.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadianDegrees.swift; sourceTree = "<group>"; };
454361EC1E7C077E00656472 /* TMCoordinate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TMCoordinate.swift; sourceTree = "<group>"; };
454361ED1E7C077E00656472 /* UTMConversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTMConversion.swift; sourceTree = "<group>"; };
454361F11E7C078D00656472 /* UTMConversionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTMConversionTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -140,6 +148,9 @@
454361761E7BE75800656472 /* UTMConversion.h */,
454361DB1E7BECFA00656472 /* Info-tvOS.plist */,
454361771E7BE75800656472 /* Info.plist */,
454361EB1E7C077E00656472 /* RadianDegrees.swift */,
454361EC1E7C077E00656472 /* TMCoordinate.swift */,
454361ED1E7C077E00656472 /* UTMConversion.swift */,
);
path = UTMConversion;
sourceTree = "<group>";
@ -149,6 +160,7 @@
children = (
454361DE1E7BED1500656472 /* Info-tvOS.plist */,
454361831E7BE75800656472 /* Info.plist */,
454361F11E7C078D00656472 /* UTMConversionTests.swift */,
);
path = UTMConversionTests;
sourceTree = "<group>";
@ -445,6 +457,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
454361EF1E7C077E00656472 /* TMCoordinate.swift in Sources */,
454361F01E7C077E00656472 /* UTMConversion.swift in Sources */,
454361F21E7C078D00656472 /* UTMConversionTests.swift in Sources */,
454361EE1E7C077E00656472 /* RadianDegrees.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -654,6 +670,7 @@
4543618B1E7BE75800656472 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
DEVELOPMENT_TEAM = 7L5H8T8QE3;
INFOPLIST_FILE = UTMConversionTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@ -666,6 +683,7 @@
4543618C1E7BE75800656472 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
DEVELOPMENT_TEAM = 7L5H8T8QE3;
INFOPLIST_FILE = UTMConversionTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";

View File

@ -0,0 +1,17 @@
//
// RadianDegrees.swift
// UTMConversion
//
// Created by Peter Ringset on 16/03/2017.
// Copyright © 2017 WTW. All rights reserved.
//
import Foundation
func toDegrees(radians: Double) -> Double {
return radians * 180 / M_PI
}
func toRadians(degrees: Double) -> Double {
return degrees / 180 * M_PI
}

View File

@ -0,0 +1,257 @@
//
// TMCoordinate.swift
// UTMConversion
//
// Created by Peter Ringset on 16/03/2017.
// Copyright © 2017 WTW. All rights reserved.
//
import CoreLocation
import Foundation
private let utmScaleFactor = 0.9996
struct TMCoordinate {
let northing: Double
let easting: Double
init(northing: Double, easting: Double) {
self.northing = northing
self.easting = easting
}
init(utmCoordinate: UTMCoordinate) {
easting = (utmCoordinate.easting - 500000.0) / utmScaleFactor;
northing = {
/* If in southern hemisphere, adjust y accordingly. */
if case .southern = utmCoordinate.hemisphere {
return (utmCoordinate.northing - 10000000.0) / utmScaleFactor
} else {
return utmCoordinate.northing / utmScaleFactor
}
}()
}
init(coordinate: CLLocationCoordinate2D, centralMeridian lambda0: Double, datum: UTMDatum) {
let phi = toRadians(degrees: coordinate.latitude) // Latitude in radians
let lambda = toRadians(degrees: coordinate.longitude) // Longitude in radians
let equitorialRadus = datum.equitorialRadius
let polarRadius = datum.polarRadius
/* Precalculate ep2 */
let ep2 = (pow(equitorialRadus, 2.0) - pow(polarRadius, 2.0)) / pow(polarRadius, 2.0)
/* Precalculate nu2 */
let nu2 = ep2 * pow(cos(phi), 2.0)
/* Precalculate N */
let N = pow(equitorialRadus, 2.0) / (polarRadius * sqrt(1 + nu2))
/* Precalculate t */
let t = tan(phi)
let t2 = t * t
/* Precalculate l */
let l = lambda - lambda0
/* Precalculate coefficients for l**n in the equations below
so a normal human being can read the expressions for easting
and northing
-- l**1 and l**2 have coefficients of 1.0 */
let l3coef = 1.0 - t2 + nu2
let l4coef = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2)
let l5coef = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2 - 58.0 * t2 * nu2
let l6coef = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2 - 330.0 * t2 * nu2
let l7coef = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2)
let l8coef = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2)
let arcLengthOfMeridian: (Double, UTMDatum) -> Double = { latitudeInRadians, datum in
let equitorialRadus = datum.equitorialRadius
let polarRadius = datum.polarRadius
/* Precalculate n */
let n = (equitorialRadus - polarRadius) / (equitorialRadus + polarRadius)
/* Precalculate alpha */
let alpha = ((equitorialRadus + polarRadius) / 2.0) * (1.0 + (pow(n, 2.0) / 4.0) + (pow(n, 4.0) / 64.0))
/* Precalculate beta */
let beta = (-3.0 * n / 2.0) + (9.0 * pow(n, 3.0) / 16.0) + (-3.0 * pow(n, 5.0) / 32.0)
/* Precalculate gamma */
let gamma = (15.0 * pow(n, 2.0) / 16.0) + (-15.0 * pow(n, 4.0) / 32.0)
/* Precalculate delta */
let delta = (-35.0 * pow(n, 3.0) / 48.0) + (105.0 * pow(n, 5.0) / 256.0)
/* Precalculate epsilon */
let epsilon = (315.0 * pow(n, 4.0) / 512.0)
/* Now calculate the sum of the series and return */
let result = alpha * (latitudeInRadians + (beta * sin(2.0 * latitudeInRadians)) + (gamma * sin(4.0 * latitudeInRadians)) + (delta * sin(6.0 * latitudeInRadians)) + (epsilon * sin(8.0 * latitudeInRadians)))
return result
}
/* Calculate easting (x) */
easting = N * cos(phi) * l + (N / 6.0 * pow(cos(phi), 3.0) * l3coef * pow(l, 3.0)) + (N / 120.0 * pow(cos(phi), 5.0) * l5coef * pow(l, 5.0)) + (N / 5040.0 * pow(cos(phi), 7.0) * l7coef * pow(l, 7.0))
/* Calculate northing (y) */
northing = arcLengthOfMeridian(phi, datum) + (t / 2.0 * N * pow(cos(phi), 2.0) * pow(l, 2.0)) + (t / 24.0 * N * pow(cos(phi), 4.0) * l4coef * pow(l, 4.0)) + (t / 720.0 * N * pow(cos(phi), 6.0) * l6coef * pow(l, 6.0)) + (t / 40320.0 * N * pow(cos(phi), 8.0) * l8coef * pow(l, 8.0))
}
func utmCoordinate(zone: UTMGridZone, hemisphere: UTMHemisphere) -> UTMCoordinate {
let x = easting * utmScaleFactor + 500000.0;
let y: Double = {
let scaled = northing * utmScaleFactor
if scaled < 0.0 {
return scaled + 10000000.0
}
return scaled
}()
return UTMCoordinate(northing: y, easting: x, zone: zone, hemisphere: hemisphere)
}
//
// Converts x and y coordinates in the Transverse Mercator projection to a latitude/longitude pair. Note that Transverse Mercator is not the same as UTM a scale factor is required to convert between them.
// Remarks:
// The local variables Nf, nuf2, tf, and tf2 serve the same purpose as N, nu2, t, and t2 in MapLatLonToXY, but they are computed with respect to the footpoint latitude phif.
// x1frac, x2frac, x2poly, x3poly, etc. are to enhance readability and to optimize computations.
func coordinate(centralMeridian lambda0: Double, datum: UTMDatum) -> CLLocationCoordinate2D {
let x = easting
let y = northing
let equitorialRadus = datum.equitorialRadius
let polarRadius = datum.polarRadius
/* Get the value of phif, the footpoint latitude. */
let phif = footpointLatitude(northingInMeters: y, datum: datum)
/* Precalculate ep2 */
let ep2 = (pow(equitorialRadus, 2.0) - pow(polarRadius, 2.0)) / pow(polarRadius, 2.0)
/* Precalculate cos (phif) */
let cf = cos(phif)
/* Precalculate nuf2 */
let nuf2 = ep2 * pow(cf, 2.0)
/* Precalculate Nf and initialize Nfpow */
let Nf = pow(equitorialRadus, 2.0) / (polarRadius * sqrt(1 + nuf2))
var Nfpow = Nf
/* Precalculate tf */
let tf = tan(phif)
let tf2 = tf * tf
let tf4 = tf2 * tf2
/* Precalculate fractional coefficients for x**n in the equations
below to simplify the expressions for latitude and longitude. */
let x1frac = 1.0 / (Nfpow * cf)
Nfpow *= Nf /* now equals Nf**2) */
let x2frac = tf / (2.0 * Nfpow)
Nfpow *= Nf /* now equals Nf**3) */
let x3frac = 1.0 / (6.0 * Nfpow * cf)
Nfpow *= Nf /* now equals Nf**4) */
let x4frac = tf / (24.0 * Nfpow)
Nfpow *= Nf /* now equals Nf**5) */
let x5frac = 1.0 / (120.0 * Nfpow * cf)
Nfpow *= Nf /* now equals Nf**6) */
let x6frac = tf / (720.0 * Nfpow)
Nfpow *= Nf /* now equals Nf**7) */
let x7frac = 1.0 / (5040.0 * Nfpow * cf)
Nfpow *= Nf /* now equals Nf**8) */
let x8frac = tf / (40320.0 * Nfpow)
/* Precalculate polynomial coefficients for x**n.
-- x**1 does not have a polynomial coefficient. */
let x2poly = -1.0 - nuf2
let x3poly = -1.0 - 2 * tf2 - nuf2
let x4poly = 5.0 + 3.0 * tf2 + 6.0 * nuf2 - 6.0 * tf2 * nuf2 - 3.0 * (nuf2 * nuf2) - 9.0 * tf2 * (nuf2 * nuf2)
let x5poly = 5.0 + 28.0 * tf2 + 24.0 * tf4 + 6.0 * nuf2 + 8.0 * tf2 * nuf2
let x6poly = -61.0 - 90.0 * tf2 - 45.0 * tf4 - 107.0 * nuf2 + 162.0 * tf2 * nuf2
let x7poly = -61.0 - 662.0 * tf2 - 1320.0 * tf4 - 720.0 * (tf4 * tf2)
let x8poly = 1385.0 + 3633.0 * tf2 + 4095.0 * tf4 + 1575 * (tf4 * tf2)
/* Calculate latitude */
let latitudeRadians = phif + x2frac * x2poly * (x * x) + x4frac * x4poly * pow(x, 4.0) + x6frac * x6poly * pow(x, 6.0) + x8frac * x8poly * pow(x, 8.0)
/* Calculate longitude */
let longitudeRadians = lambda0 + x1frac * x + x3frac * x3poly * pow(x, 3.0) + x5frac * x5poly * pow(x, 5.0) + x7frac * x7poly * pow(x, 7.0)
return CLLocationCoordinate2D(latitude: toDegrees(radians: latitudeRadians), longitude: toDegrees(radians: longitudeRadians))
}
//
// Computes the footpoint latitude for use in converting transverse Mercator coordinates to ellipsoidal coordinates.
private func footpointLatitude(northingInMeters: Double, datum: UTMDatum) -> Double {
let equitorialRadus = datum.equitorialRadius
let polarRadius = datum.polarRadius
/* Precalculate n (Eq. 10.18) */
let n = (equitorialRadus - polarRadius) / (equitorialRadus + polarRadius)
/* Precalculate alpha_ (Eq. 10.22) */
/* (Same as alpha in Eq. 10.17) */
let alpha = ((equitorialRadus + polarRadius) / 2.0) * (1 + (pow(n, 2.0) / 4) + (pow(n, 4.0) / 64))
/* Precalculate y (Eq. 10.23) */
let y = northingInMeters / alpha
/* Precalculate beta (Eq. 10.22) */
let beta = (3.0 * n / 2.0) + (-27.0 * pow(n, 3.0) / 32.0) + (269.0 * pow(n, 5.0) / 512.0)
/* Precalculate gamma (Eq. 10.22) */
let gamma = (21.0 * pow(n, 2.0) / 16.0) + (-55.0 * pow(n, 4.0) / 32.0)
/* Precalculate delta (Eq. 10.22) */
let delta = (151.0 * pow(n, 3.0) / 96.0) + (-417.0 * pow(n, 5.0) / 128.0)
/* Precalculate epsilon (Eq. 10.22) */
let epsilon = (1097.0 * pow(n, 4.0) / 512.0)
/* Now calculate the sum of the series (Eq. 10.21) */
let footprintLatitudeInRadians = y + (beta * sin(2.0 * y)) + (gamma * sin(4.0 * y)) + (delta * sin(6.0 * y)) + (epsilon * sin(8.0 * y))
return footprintLatitudeInRadians
}
//
// Computes the ellipsoidal distance from the equator to a point at a given latitude in meters
private func arcLengthOfMeridian(latitudeInRadians: Double, datum: UTMDatum) -> Double {
let equitorialRadus = datum.equitorialRadius
let polarRadius = datum.polarRadius
/* Precalculate n */
let n = (equitorialRadus - polarRadius) / (equitorialRadus + polarRadius)
/* Precalculate alpha */
let alpha = ((equitorialRadus + polarRadius) / 2.0) * (1.0 + (pow(n, 2.0) / 4.0) + (pow(n, 4.0) / 64.0))
/* Precalculate beta */
let beta = (-3.0 * n / 2.0) + (9.0 * pow(n, 3.0) / 16.0) + (-3.0 * pow(n, 5.0) / 32.0)
/* Precalculate gamma */
let gamma = (15.0 * pow(n, 2.0) / 16.0) + (-15.0 * pow(n, 4.0) / 32.0)
/* Precalculate delta */
let delta = (-35.0 * pow(n, 3.0) / 48.0) + (105.0 * pow(n, 5.0) / 256.0)
/* Precalculate epsilon */
let epsilon = (315.0 * pow(n, 4.0) / 512.0)
/* Now calculate the sum of the series and return */
let result = alpha * (latitudeInRadians + (beta * sin(2.0 * latitudeInRadians)) + (gamma * sin(4.0 * latitudeInRadians)) + (delta * sin(6.0 * latitudeInRadians)) + (epsilon * sin(8.0 * latitudeInRadians)))
return result
}
}

View File

@ -0,0 +1,67 @@
//
// UTMConversion.swift
// UTMConversion
//
// Created by Peter Ringset on 16/03/2017.
// Copyright © 2017 WTW. All rights reserved.
//
import Foundation
import CoreLocation
public typealias UTMGridZone = UInt
extension UTMGridZone {
var centralMeridian: Double {
return toRadians(degrees: -183.0 + (Double(self) * 6.0));
}
}
public enum UTMHemisphere {
case northern
case southern
}
public struct UTMDatum {
public let equitorialRadius: Double
public let polarRadius: Double
public static let wgs84 = UTMDatum(equitorialRadius: 6378137, polarRadius: 6356752.3142) // WGS84
}
public struct UTMCoordinate {
public let northing: Double
public let easting: Double
public let zone: UTMGridZone
public let hemisphere: UTMHemisphere
public init(northing: Double, easting: Double, zone: UTMGridZone, hemisphere: UTMHemisphere) {
self.northing = northing
self.easting = easting
self.zone = zone
self.hemisphere = hemisphere
}
public func coordinate(datum: UTMDatum = UTMDatum.wgs84) -> CLLocationCoordinate2D {
return TMCoordinate(utmCoordinate: self).coordinate(centralMeridian: zone.centralMeridian, datum: datum)
}
}
public extension CLLocationCoordinate2D {
public func utmCoordinate(datum: UTMDatum = UTMDatum.wgs84) -> UTMCoordinate {
let zone = self.zone
return TMCoordinate(coordinate: self, centralMeridian: zone.centralMeridian, datum: datum).utmCoordinate(zone: zone, hemisphere: hemisphere)
}
var zone: UTMGridZone {
return UTMGridZone(floor((longitude + 180.0) / 6)) + 1;
}
var hemisphere: UTMHemisphere {
return latitude < 0 ? .southern : .northern
}
}

View File

@ -0,0 +1,55 @@
//
// UTMConversionTests.swift
// UTMConversionTests
//
// Created by Peter Ringset on 17/03/2017.
// Copyright © 2017 Peter Ringset. All rights reserved.
//
import CoreLocation
import UTMConversion
import XCTest
class UTMConversionTests: XCTestCase {
func testCLLocationCoordinate2DConvertToUTMCoordinate() {
let oslo = CLLocationCoordinate2D(latitude: 59.912814611065265, longitude: 10.760192985178369)
let utmOslo = oslo.utmCoordinate()
XCTAssertEqualWithAccuracy(utmOslo.northing, 6643010.0, accuracy: 0.00001);
XCTAssertEqualWithAccuracy(utmOslo.easting, 598430.0, accuracy: 0.00001);
XCTAssertEqual(utmOslo.zone, 32)
XCTAssertEqual(utmOslo.hemisphere, .northern)
let trondheim = CLLocationCoordinate2D(latitude: 63.430493678423012, longitude: 10.394966844991798)
let utmTrondheim = trondheim.utmCoordinate()
XCTAssertEqualWithAccuracy(utmTrondheim.northing, 7034313, accuracy: 0.00001)
XCTAssertEqualWithAccuracy(utmTrondheim.easting, 569612, accuracy: 0.00001)
XCTAssertEqual(utmTrondheim.zone, 32)
XCTAssertEqual(utmTrondheim.hemisphere, .northern)
let johannesburg = CLLocationCoordinate2D(latitude: -26.214767103043133, longitude: 28.040197220939884)
let utmJohannesburg = johannesburg.utmCoordinate()
XCTAssertEqualWithAccuracy(utmJohannesburg.northing, 7100115, accuracy: 0.00001)
XCTAssertEqualWithAccuracy(utmJohannesburg.easting, 603914, accuracy: 0.00001)
XCTAssertEqual(utmJohannesburg.zone, 35)
XCTAssertEqual(utmJohannesburg.hemisphere, .southern)
}
func testUTMCoordinateConvertToCLLocationCoordinate2D() {
let utmOslo = UTMCoordinate(northing: 6643010, easting: 598430, zone: 32, hemisphere: .northern)
let oslo = utmOslo.coordinate()
XCTAssertEqual(oslo.latitude, 59.912814611065265)
XCTAssertEqual(oslo.longitude, 10.760192985178369)
let utmTrondheim = UTMCoordinate(northing: 7034313, easting: 569612, zone: 32, hemisphere: .northern)
let trondheim = utmTrondheim.coordinate()
XCTAssertEqual(trondheim.latitude, 63.430493678423012)
XCTAssertEqual(trondheim.longitude, 10.394966844991798)
let utmJohannesburg = UTMCoordinate(northing: 7100115, easting: 603914, zone: 35, hemisphere: .southern)
let johannesburg = utmJohannesburg.coordinate()
XCTAssertEqual(johannesburg.latitude, -26.214767103043133)
XCTAssertEqual(johannesburg.longitude, 28.040197220939884)
}
}