diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/GeodeticDisplayTests.xcbaseline/9AAF5982-4101-4C27-8140-52ECB503EFD7.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/GeodeticDisplayTests.xcbaseline/9AAF5982-4101-4C27-8140-52ECB503EFD7.plist
new file mode 100644
index 0000000..b233fc7
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcbaselines/GeodeticDisplayTests.xcbaseline/9AAF5982-4101-4C27-8140-52ECB503EFD7.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ classNames
+
+ GeodeticDisplayTests
+
+ testNumberFormatterStringForPerformance()
+
+ com.apple.dt.XCTMetric_Clock.time.monotonic
+
+ baselineAverage
+ 0.000011
+ baselineIntegrationDisplayName
+ Local Baseline
+
+
+ testNumberFormatterStringFromPerformance()
+
+ com.apple.dt.XCTMetric_Clock.time.monotonic
+
+ baselineAverage
+ 0.000013
+ baselineIntegrationDisplayName
+ Local Baseline
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/GeodeticDisplayTests.xcbaseline/Info.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/GeodeticDisplayTests.xcbaseline/Info.plist
new file mode 100644
index 0000000..2cba2a9
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcbaselines/GeodeticDisplayTests.xcbaseline/Info.plist
@@ -0,0 +1,33 @@
+
+
+
+
+ runDestinationsByUUID
+
+ 9AAF5982-4101-4C27-8140-52ECB503EFD7
+
+ localComputer
+
+ busSpeedInMHz
+ 400
+ cpuCount
+ 1
+ cpuKind
+ 6-Core Intel Core i7
+ cpuSpeedInMHz
+ 2600
+ logicalCPUCoresPerPackage
+ 12
+ modelCode
+ MacBookPro16,1
+ physicalCPUCoresPerPackage
+ 6
+ platformIdentifier
+ com.apple.platform.macosx
+
+ targetArchitecture
+ x86_64
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/GeoModels.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/GeoModels.xcscheme
deleted file mode 100644
index 82b7eea..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/GeoModels.xcscheme
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-geo-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-geo-Package.xcscheme
index 48aa5bb..f17f7c6 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-geo-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-geo-Package.xcscheme
@@ -118,6 +118,48 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Self {
- return Self.random(in: -halfRotation...halfRotation)
- }
-
-}
diff --git a/Sources/GeoCoordinates/Longitude.swift b/Sources/GeoCoordinates/Longitude.swift
deleted file mode 100644
index 7561e52..0000000
--- a/Sources/GeoCoordinates/Longitude.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// Longitude.swift
-// GeoSwift
-//
-// Created by Rémi Bardon on 04/02/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-/// The longitude component of a geographical coordinate.
-public struct Longitude: AngularCoordinate {
-
- public static let positiveDirectionChar: Character = "E"
- public static let negativeDirectionChar: Character = "W"
-
- public static var fullRotation: Longitude = 360
- public static var halfRotation: Longitude = 180
-
- public var decimalDegrees: Double
-
- public var positive: Longitude {
- if decimalDegrees < .zero {
- // `degrees` is negative, so we end up with `360 - |degrees|`
- return self + Self.fullRotation
- } else {
- return self
- }
- }
-
- public init(decimalDegrees: Double) {
- self.decimalDegrees = decimalDegrees
- }
-
- public static func random() -> Self {
- return Self.random(in: -halfRotation...halfRotation)
- }
-
-}
diff --git a/Sources/GeoCoordinates/Protocols/AngularCoordinate.swift b/Sources/GeoCoordinates/Protocols/AngularCoordinate.swift
deleted file mode 100644
index 998da49..0000000
--- a/Sources/GeoCoordinates/Protocols/AngularCoordinate.swift
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// AngularCoordinate.swift
-// GeoSwift
-//
-// Created by Rémi Bardon on 04/02/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-/// - Todo: Create custom struct that handles cycling in an interval.
-public protocol AngularCoordinate: ValidatableCoordinate, GeographicNotation {
-
- static var fullRotation: Self { get }
- static var halfRotation: Self { get }
-
- /// "N" or "E" depending on axis
- static var positiveDirectionChar: Character { get }
-
- /// "S" or "W" depending on axis
- static var negativeDirectionChar: Character { get }
-
- var decimalDegrees: Double { get set }
-
- init(decimalDegrees: Double)
-
-}
-
-extension AngularCoordinate {
-
- public var value: Double {
- get { decimalDegrees }
- set { decimalDegrees = newValue }
- }
-
- public init(value: Double) {
- self.init(decimalDegrees: value)
- }
-
-}
-
-extension AngularCoordinate {
-
- public static var min: Self { -halfRotation }
- public static var max: Self { halfRotation }
-
- public var valid: Self {
- if isValid {
- return self
- } else {
- let remainder = self.truncatingRemainder(dividingBy: Self.fullRotation)
-
- if abs(remainder) > .halfRotation {
- let base = self < .zero ? Self.fullRotation : -Self.fullRotation
- return base + remainder
- } else {
- return remainder
- }
- }
- }
-
-}
diff --git a/Sources/GeoCoordinates/Protocols/Coordinate.swift b/Sources/GeoCoordinates/Protocols/Coordinate.swift
deleted file mode 100644
index 1d2d540..0000000
--- a/Sources/GeoCoordinates/Protocols/Coordinate.swift
+++ /dev/null
@@ -1,177 +0,0 @@
-//
-// Coordinate.swift
-// SwiftGeo
-//
-// Created by Rémi Bardon on 03/02/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-import Foundation
-
-public protocol Coordinate: BinaryFloatingPoint, CustomStringConvertible, CustomDebugStringConvertible, Zeroable {
-
- var value: Double { get set }
-
- init(value: Double)
-
-}
-
-extension Coordinate {
-
- // MARK: ExpressibleByFloatLiteral
-
- public init(floatLiteral value: Double) {
- self.init(value: value)
- }
-
- // MARK: ExpressibleByIntegerLiteral
-
- public init(integerLiteral value: Int) {
- self.init(value: Double(value))
- }
-
- // MARK: FloatingPoint
-
- public static var nan: Self { Self.init(value: Double.nan) }
- public static var signalingNaN: Self { Self.init(value: Double.signalingNaN) }
- public static var infinity: Self { Self.init(value: Double.infinity) }
- public static var greatestFiniteMagnitude: Self { Self.init(value: Double.greatestFiniteMagnitude) }
- public static var pi: Self { Self.init(value: Double.pi) }
-
- public static var leastNormalMagnitude: Self { Self.init(value: Double.leastNormalMagnitude) }
- public static var leastNonzeroMagnitude: Self { Self.init(value: Double.leastNonzeroMagnitude) }
-
- public var exponent: Double.Exponent { self.value.exponent }
- public var sign: FloatingPointSign { self.value.sign }
- public var significand: Self { Self.init(value: self.value.significand) }
- public var ulp: Self { Self.init(value: self.value.ulp) }
-
- public var nextUp: Self { Self.init(value: self.value.nextUp) }
-
- public var isNormal: Bool { self.value.isNormal }
- public var isFinite: Bool { self.value.isFinite }
- public var isZero: Bool { self.value.isZero }
- public var isSubnormal: Bool { self.value.isSubnormal }
- public var isInfinite: Bool { self.value.isInfinite }
- public var isNaN: Bool { self.value.isNaN }
- public var isSignalingNaN: Bool { self.value.isSignalingNaN }
- public var isCanonical: Bool { self.value.isCanonical }
-
- public init(sign: FloatingPointSign, exponent: Int, significand: Self) {
- self.init(value: Double(sign: sign, exponent: exponent, significand: significand.value))
- }
-
- public static func / (lhs: Self, rhs: Self) -> Self {
- Self.init(value: lhs.value / rhs.value)
- }
- public static func /= (lhs: inout Self, rhs: Self) {
- lhs.value /= rhs.value
- }
-
- public func isEqual(to other: Self) -> Bool { self.value == other.value }
- public func isLess(than other: Self) -> Bool { self.value < other.value }
- public func isLessThanOrEqualTo(_ other: Self) -> Bool { self.value <= other.value }
-
- public mutating func addProduct(_ lhs: Self, _ rhs: Self) {
- self.value += lhs.value * rhs.value
- }
- public mutating func formRemainder(dividingBy other: Self) {
- self.value.formRemainder(dividingBy: other.value)
- }
- public mutating func formTruncatingRemainder(dividingBy other: Self) {
- self.value.formTruncatingRemainder(dividingBy: other.value)
- }
- public mutating func formSquareRoot() {
- self.value.formSquareRoot()
- }
- public mutating func round(_ rule: FloatingPointRoundingRule) {
- self.value.round(rule)
- }
-
- // MARK: Numeric
-
- public static func * (lhs: Self, rhs: Self) -> Self {
- return Self.init(value: lhs.value * rhs.value)
- }
- public static func *= (lhs: inout Self, rhs: Self) {
- lhs.value *= rhs.value
- }
-
- public var magnitude: Self { Self.init(value: self.value.magnitude) }
-
- public init?(exactly source: T) where T: BinaryInteger {
- guard let value = Double(exactly: source) else { return nil }
- self.init(value: value)
- }
-
- // MARK: AdditiveArithmetic
-
- public static var zero: Self { Self.init(value: .zero) }
-
- // MARK: Strideable
-
- public static func + (lhs: Self, rhs: Self) -> Self {
- return Self.init(value: lhs.value + rhs.value)
- }
-
- public static func - (lhs: Self, rhs: Self) -> Self {
- return Self.init(value: lhs.value - rhs.value)
- }
-
- public func advanced(by n: Double.Stride) -> Self {
- return Self.init(value: self.value.advanced(by: n))
- }
-
- public func distance(to other: Self) -> Double.Stride {
- return self.value.distance(to: other.value)
- }
-
- // MARK: BinaryFloatingPoint
-
- public static var exponentBitCount: Int { Double.exponentBitCount }
- public static var significandBitCount: Int { Double.significandBitCount }
-
- public var binade: Self { Self.init(value: self.value.binade) }
- public var exponentBitPattern: Double.RawExponent { self.value.exponentBitPattern }
- public var significandBitPattern: Double.RawSignificand { self.value.significandBitPattern }
- public var significandWidth: Int { self.value.significandWidth }
-
- public init(
- sign: FloatingPointSign,
- exponentBitPattern: Double.RawExponent,
- significandBitPattern: Double.RawSignificand
- ) {
- self.init(value: Double(
- sign: sign,
- exponentBitPattern: exponentBitPattern,
- significandBitPattern: significandBitPattern
- ))
- }
-
- public init(_ value: Source) where Source: BinaryFloatingPoint {
- self.init(value: Double(value))
- }
-
- public init(_ value: Source) where Source: BinaryInteger {
- self.init(value: Double(value))
- }
-
- // MARK: CustomStringConvertible
-
- public var description: String {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- return formatter.string(from: NSNumber(value: self.value)) ?? "\(self.value)"
- }
-
- // MARK: CustomDebugStringConvertible
-
- public var debugDescription: String {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- formatter.maximumFractionDigits = 3
- formatter.locale = .en
- return formatter.string(from: NSNumber(value: self.value)) ?? "\(self.value)"
- }
-
-}
diff --git a/Sources/GeoCoordinates/Protocols/ValidatableCoordinate.swift b/Sources/GeoCoordinates/Protocols/ValidatableCoordinate.swift
deleted file mode 100644
index 0b98752..0000000
--- a/Sources/GeoCoordinates/Protocols/ValidatableCoordinate.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// ValidatableCoordinate.swift
-// GeoSwift
-//
-// Created by Rémi Bardon on 04/02/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-import Foundation
-
-public protocol ValidatableCoordinate: Coordinate {
-
- static var min: Self { get }
- static var max: Self { get }
-
- /// Tells if this value is in valid bounds or not.
- var isValid: Bool { get }
-
- /// A copy of this value, in valid bounds.
- ///
- /// In angular coordinate system, it means rotating the value
- /// to put it in the valid range.
- ///
- /// In linear coordinate system, it can mean making the value positive.
- var valid: Self { get }
-
-}
-
-extension ValidatableCoordinate {
-
- public static var validRange: ClosedRange { min...max }
-
- public var isValid: Bool {
- Self.validRange.contains(self)
- }
-
-}
diff --git a/Sources/GeoModels/Interoperability/GeoModels+MapKit.swift b/Sources/GeoModels/Interoperability/GeoModels+MapKit.swift
deleted file mode 100644
index 173845a..0000000
--- a/Sources/GeoModels/Interoperability/GeoModels+MapKit.swift
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// GeoModels+MapKit.swift
-// SwiftGeo
-//
-// Created by Rémi Bardon on 02/02/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-#if canImport(MapKit)
-import MapKit
-
-extension WGS842D.BoundingBox {
-
- public var mkCoordinateSpan: MKCoordinateSpan {
- MKCoordinateSpan(latitudeDelta: height.decimalDegrees, longitudeDelta: width.decimalDegrees)
- }
- public var mkCoordinateRegion: MKCoordinateRegion {
- MKCoordinateRegion(center: center.clLocationCoordinate2D, span: mkCoordinateSpan)
- }
-
- public var mkMapWidth: Double {
- mkMapWidthAtLatitude(center.latitude)
- }
- public var mkMapHeight: Double {
- mkMapHeightAtLongitude(center.longitude)
- }
-
- public var mkMapRect: MKMapRect {
- // Avoid center recalculation
- let center = self.center
-
- return MKMapRect(
- origin: northWest.mkMapPoint,
- size: MKMapSize(
- width: mkMapWidthAtLatitude(center.latitude),
- height: mkMapHeightAtLongitude(center.longitude)
- )
- )
- }
-
- public func mkMapWidthAtLatitude(_ latitude: Latitude) -> Double {
- let east = eastAtLatitude(latitude).mkMapPoint
- let west = westAtLatitude(latitude).mkMapPoint
-
- // `east.x > west.x`
- return east.x - west.x
- }
- public func mkMapHeightAtLongitude(_ longitude: Longitude) -> Double {
- let south = southAtLongitude(longitude).mkMapPoint
- let north = northAtLongitude(longitude).mkMapPoint
-
- // `south.y > north.y`
- return south.y - north.y
- }
-
-}
-
-extension WGS842D.LineString {
-
- public var mkPolyline: MKPolyline {
- var points: [CLLocationCoordinate2D] = self.points.map(\.clLocationCoordinate2D)
- return MKPolyline(coordinates: &points, count: points.count)
- }
-
-}
-#endif
diff --git a/Sources/GeoModels/Protocols/CoordinateSystem.swift b/Sources/GeoModels/Protocols/CoordinateSystem.swift
deleted file mode 100644
index 3fc1b62..0000000
--- a/Sources/GeoModels/Protocols/CoordinateSystem.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// CoordinateSystem.swift
-// SwiftGeo
-//
-// Created by Rémi Bardon on 20/03/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-import GeoCoordinates
-
-public protocol CoordinateSystem {
-
- associatedtype Point: GeoCoordinates.Coordinates//: GeoModels.Point
- associatedtype Size: GeoModels.Size
-// associatedtype MultiPoint: GeoModels.MultiPoint
- associatedtype Line: GeoModels.Line
-// associatedtype MultiLine: GeoModels.MultiLine
- associatedtype LineString: GeoModels.LineString
-// associatedtype LinearRing: GeoModels.LinearRing
-
- associatedtype BoundingBox: GeoModels.BoundingBox
-
-}
diff --git a/Sources/GeoModels/WGS843D.swift b/Sources/GeoModels/WGS843D.swift
deleted file mode 100644
index 242b449..0000000
--- a/Sources/GeoModels/WGS843D.swift
+++ /dev/null
@@ -1,246 +0,0 @@
-//
-// WGS843D.swift
-// SwiftGeo
-//
-// Created by Rémi Bardon on 20/03/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-import GeoCoordinates
-import NonEmpty
-
-public enum WGS843D: GeoModels.CoordinateSystem {
-
- public typealias Point = WGS84Coordinate3D
-
- public struct Size: GeoModels.Size {
-
- public typealias CoordinateSystem = WGS843D
- public typealias RawValue = WGS843D.Point
-
- public let rawValue: Self.RawValue
-
- public var width: Self.RawValue.X { self.rawValue.x }
- public var height: Self.RawValue.Y { self.rawValue.y }
- public var zHeight: Self.RawValue.Z { self.rawValue.z }
-
- public init(rawValue: Self.RawValue) {
- self.rawValue = rawValue
- }
-
- public init(width: Self.RawValue.X, height: Self.RawValue.Y, zHeight: Self.RawValue.Z) {
- self.init(rawValue: Self.RawValue.init(x: width, y: height, z: zHeight))
- }
-
- }
-
-// public struct MultiPoint
-
- public struct Line: GeoModels.Line, Hashable {
-
- public typealias CoordinateSystem = WGS843D
- public typealias Point = WGS843D.Point
-
- public let start: Point
- public let end: Point
-
- public var altitudeDelta: Altitude {
- end.altitude - start.altitude
- }
-
- public init(start: Point, end: Point) {
- self.start = start
- self.end = end
- }
-
- }
-
-// public struct MultiLine
-
- public struct LineString: GeoModels.LineString, Hashable {
-
- public typealias CoordinateSystem = WGS843D
- public typealias Points = AtLeast2<[CoordinateSystem.Point]>
- public typealias Lines = NonEmpty<[CoordinateSystem.Line]>
-
- public internal(set) var points: Points
-
- public init(points: Points) {
- self.points = points
- }
-
- public mutating func append(_ point: Self.Point) {
- self.points.append(point)
- }
-
- }
-
-// public struct LinearRing
-
- public struct BoundingBox: Hashable {
-
- public var twoDimensions: LowerDimension
- public var lowAltitude: Altitude
- public var zHeight: Altitude
-
- public var highAltitude: Altitude {
- lowAltitude + zHeight
- }
- public var centerAltitude: Altitude {
- lowAltitude + (zHeight / 2.0)
- }
-
- public var southWestLow: Self.Point {
- Self.Point.init(twoDimensions.southWest, altitude: lowAltitude)
- }
- public var northEastHigh: Self.Point {
- Self.Point.init(twoDimensions.northEast, altitude: highAltitude)
- }
- public var center: Self.Point {
- Self.Point.init(twoDimensions.center, altitude: centerAltitude)
- }
-
- public var crosses180thMeridian: Bool {
- twoDimensions.crosses180thMeridian
- }
-
- public init(_ boundingBox2d: LowerDimension, lowAltitude: Altitude, zHeight: Altitude) {
- self.twoDimensions = boundingBox2d
- self.lowAltitude = lowAltitude
- self.zHeight = zHeight
- }
-
- public init(
- southWestLow: Self.Point,
- width: Longitude,
- height: Latitude,
- zHeight: Altitude
- ) {
- self.init(
- Self.LowerDimension(
- southWest: southWestLow.lowerDimension,
- width: width,
- height: height
- ),
- lowAltitude: southWestLow.altitude,
- zHeight: zHeight
- )
- }
-
- public init(
- southWestLow: Self.Point,
- northEastHigh: Self.Point
- ) {
- self.init(
- southWestLow: southWestLow,
- width: northEastHigh.longitude - southWestLow.longitude,
- height: northEastHigh.latitude - southWestLow.latitude,
- zHeight: northEastHigh.altitude - southWestLow.altitude
- )
- }
-
- public func offsetBy(
- dLat: Latitude = .zero,
- dLong: Longitude = .zero,
- dAlt: Altitude = .zero
- ) -> Self {
- Self.init(
- twoDimensions.offsetBy(dLat: dLat, dLong: dLong),
- lowAltitude: lowAltitude + dAlt,
- zHeight: zHeight
- )
- }
- public func offsetBy(
- dx: Self.Point.X = .zero,
- dy: Self.Point.Y = .zero,
- dz: Self.Point.Z = .zero
- ) -> Self {
- Self.init(
- twoDimensions.offsetBy(dx: dx, dy: dy),
- lowAltitude: lowAltitude + dz,
- zHeight: zHeight
- )
- }
-
- }
-
-}
-
-extension WGS843D.Size: GeoCoordinates.CompoundDimension {
-
- public typealias LowerDimension = WGS842D.Size
-
- public var lowerDimension: Self.LowerDimension {
- LowerDimension(rawValue: self.rawValue.lowerDimension)
- }
-
- public init(_ twoDimensions: Self.LowerDimension, zHeight: Self.RawValue.Z) {
- self.init(rawValue: Self.RawValue.init(
- x: twoDimensions.width,
- y: twoDimensions.height,
- z: zHeight
- ))
- }
-
-}
-
-extension WGS843D.Line: GeoCoordinates.CompoundDimension {
-
- public typealias LowerDimension = WGS842D.Line
-
- public var lowerDimension: LowerDimension {
- Self.LowerDimension(start: self.start.lowerDimension, end: self.end.lowerDimension)
- }
-
-}
-
-extension WGS843D.BoundingBox: GeoModels.BoundingBox {
-
- public typealias CoordinateSystem = WGS843D
-
- public var origin: Self.Point { self.southWestLow }
- public var size: Self.Size { Self.Size(self.twoDimensions.size, zHeight: self.zHeight) }
-
- public init(origin: Self.Point, size: Self.Size) {
- self.init(
- southWestLow: origin,
- width: size.width,
- height: size.height,
- zHeight: size.zHeight
- )
- }
-
- /// The union of bounding boxes gives a new bounding box that encloses the given two.
- public func union(_ other: Self) -> Self {
- // FIXME: Use width, height and zHeight, because `eastLongitude` can cross the antimeridian
- Self.init(
- southWestLow: Self.Point(
- self.twoDimensions.union(other.twoDimensions).southWest,
- altitude: min(self.lowAltitude, other.lowAltitude)
- ),
- northEastHigh: Self.Point(
- self.twoDimensions.union(other.twoDimensions).northEast,
- altitude: max(self.highAltitude, other.highAltitude)
- )
- )
- }
-
-}
-
-extension WGS843D.BoundingBox: GeoCoordinates.CompoundDimension {
-
- public typealias LowerDimension = WGS842D.BoundingBox
-
- public var lowerDimension: LowerDimension {
- self.twoDimensions
- }
-
-}
-
-extension WGS843D.BoundingBox: CustomDebugStringConvertible {
-
- public var debugDescription: String {
- "BBox3D(southWestLow: \(String(reflecting: self.southWestLow)), northEastHigh: \(String(reflecting: self.northEastHigh)))"
- }
-
-}
diff --git a/Sources/Geodesy/Conversions.swift b/Sources/Geodesy/Conversions.swift
index d8b0b92..0dd9106 100644
--- a/Sources/Geodesy/Conversions.swift
+++ b/Sources/Geodesy/Conversions.swift
@@ -15,11 +15,11 @@ public protocol Conversion {
// static var name: String { get }
// static var code: Int { get }
- static func apply, C2: Coordinate>(on coordinate: C1) -> C2
+ static func apply, C2: Coordinates>(on coordinate: C1) -> C2
}
public protocol ReversibleConversion: Conversion {
- static func unapply, C2: Coordinate>(from coordinate: C2) -> C1
+ static func unapply, C2: Coordinates>(from coordinate: C2) -> C1
}
// MARK: EPSG 9659 (Geographic 3D to 2D conversions)
@@ -36,14 +36,14 @@ where OldCRS: GeographicCRS & ThreeDimensionsCRS,
/// Geographic 3D to 2D conversion.
///
/// section 4.1.4
- public static func apply, C2: Coordinate>(on coordinate: C1) -> C2 {
+ public static func apply, C2: Coordinates>(on coordinate: C1) -> C2 {
return C2.init(x: .init(coordinate.x), y: .init(coordinate.y))
}
/// Geographic 2D to 3D conversion.
///
/// section 4.1.1
- public static func unapply, C2: Coordinate>(from coordinate: C2) -> C1 {
+ public static func unapply, C2: Coordinates>(from coordinate: C2) -> C1 {
return C1.init(x: .init(Double(coordinate.x)), y: .init(coordinate.y), z: .init(0.0))
}
}
@@ -83,7 +83,7 @@ where OldCRS: GeographicCRS & ThreeDimensionsCRS,
///
///
/// page 101
- public static func apply, C2: Coordinate>(on coordinate: C1) -> C2 {
+ public static func apply, C2: Coordinates>(on coordinate: C1) -> C2 {
// φ and λ are respectively the latitude and longitude (related to Greenwich) of the point
let φ = Double(coordinate.x).toRadians
let λ = Double(coordinate.y).toRadians
@@ -107,7 +107,7 @@ where OldCRS: GeographicCRS & ThreeDimensionsCRS,
///
///
/// page 101
- public static func unapply, C2: Coordinate>(from coordinate: C2) -> C1 {
+ public static func unapply, C2: Coordinates>(from coordinate: C2) -> C1 {
let x = Double(coordinate.x)
let y = Double(coordinate.y)
let z = Double(coordinate.z)
diff --git a/Sources/Geodesy/CoordinateComponent.swift b/Sources/Geodesy/CoordinateComponent.swift
new file mode 100644
index 0000000..52ab745
--- /dev/null
+++ b/Sources/Geodesy/CoordinateComponent.swift
@@ -0,0 +1,279 @@
+//
+// CoordinateComponent.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 03/02/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import Foundation
+import ValueWithUnit
+
+// MARK: - CoordinateComponent
+
+public protocol CoordinateComponent:
+ RawRepresentable,
+ BinaryFloatingPoint,
+ CustomStringConvertible,
+ CustomDebugStringConvertible,
+ Zeroable
+where RawValue: Value
+{
+ init(rawValue: RawValue)
+}
+
+extension CoordinateComponent {
+
+ internal var _rawValue: RawValue.RawValue { self.rawValue.rawValue }
+
+ // MARK: ExpressibleByFloatLiteral
+
+ public init(floatLiteral value: Double) {
+ self.init(value)
+ }
+
+ // MARK: ExpressibleByIntegerLiteral
+
+ public init(integerLiteral value: Int) {
+ self.init(Double(value))
+ }
+
+ // MARK: FloatingPoint
+
+ public static var nan: Self { Self(Double.nan) }
+ public static var signalingNaN: Self { Self(Double.signalingNaN) }
+ public static var infinity: Self { Self(Double.infinity) }
+ public static var greatestFiniteMagnitude: Self { Self(Double.greatestFiniteMagnitude) }
+ public static var pi: Self { Self(Double.pi) }
+
+ public static var leastNormalMagnitude: Self { Self(Double.leastNormalMagnitude) }
+ public static var leastNonzeroMagnitude: Self { Self(Double.leastNonzeroMagnitude) }
+
+ public var exponent: Double.Exponent { Double(self._rawValue).exponent }
+ public var sign: FloatingPointSign { Double(self._rawValue).sign }
+ public var significand: Self { Self(Double(self._rawValue).significand) }
+ public var ulp: Self { Self(Double(self._rawValue).ulp) }
+
+ public var nextUp: Self { Self(Double(self._rawValue).nextUp) }
+
+ public var isNormal: Bool { Double(self._rawValue).isNormal }
+ public var isFinite: Bool { Double(self._rawValue).isFinite }
+ public var isZero: Bool { Double(self._rawValue).isZero }
+ public var isSubnormal: Bool { Double(self._rawValue).isSubnormal }
+ public var isInfinite: Bool { Double(self._rawValue).isInfinite }
+ public var isNaN: Bool { Double(self._rawValue).isNaN }
+ public var isSignalingNaN: Bool { Double(self._rawValue).isSignalingNaN }
+ public var isCanonical: Bool { Double(self._rawValue).isCanonical }
+
+ public init(sign: FloatingPointSign, exponent: Int, significand: Self) {
+ self.init(Double(sign: sign, exponent: exponent, significand: Double(significand._rawValue)))
+ }
+
+ public static func / (lhs: Self, rhs: Self) -> Self {
+ Self(Double(lhs._rawValue) / Double(rhs._rawValue))
+ }
+ public static func /= (lhs: inout Self, rhs: Self) {
+ lhs = lhs / rhs
+ }
+
+ public func isEqual(to other: Self) -> Bool {
+ Double(self._rawValue) == Double(other._rawValue)
+ }
+ public func isLess(than other: Self) -> Bool {
+ Double(self._rawValue) < Double(other._rawValue)
+ }
+ public func isLessThanOrEqualTo(_ other: Self) -> Bool {
+ Double(self._rawValue) <= Double(other._rawValue)
+ }
+
+ public mutating func addProduct(_ lhs: Self, _ rhs: Self) {
+ self += Self(Double(lhs._rawValue) * Double(rhs._rawValue))
+ }
+ public mutating func formRemainder(dividingBy other: Self) {
+ self = Self(Double(self._rawValue).remainder(dividingBy: Double(other._rawValue)))
+ }
+ public mutating func formTruncatingRemainder(dividingBy other: Self) {
+ self = Self(Double(self._rawValue).truncatingRemainder(dividingBy: Double(other._rawValue)))
+ }
+ public mutating func formSquareRoot() {
+ self = Self(Double(self._rawValue).squareRoot())
+ }
+ public mutating func round(_ rule: FloatingPointRoundingRule) {
+ self = Self(Double(self._rawValue).rounded(rule))
+ }
+
+ // MARK: Numeric
+
+ public static func * (lhs: Self, rhs: Self) -> Self {
+ Self(Double(lhs._rawValue) * Double(rhs._rawValue))
+ }
+ public static func *= (lhs: inout Self, rhs: Self) {
+ lhs = lhs * rhs
+ }
+
+ public var magnitude: Self { Self(Double(self._rawValue).magnitude) }
+
+ public init?(exactly source: T) where T: BinaryInteger {
+ guard let value = Double(exactly: source) else { return nil }
+ self.init(value)
+ }
+
+ // MARK: AdditiveArithmetic
+
+ public static var zero: Self { Self(Double.zero) }
+
+ // MARK: Strideable
+
+ public static func + (lhs: Self, rhs: Self) -> Self {
+ return Self(Double(lhs._rawValue) + Double(rhs._rawValue))
+ }
+
+ public static func - (lhs: Self, rhs: Self) -> Self {
+ return Self(Double(lhs._rawValue) - Double(rhs._rawValue))
+ }
+
+ public func advanced(by n: Double.Stride) -> Self {
+ return Self(Double(self._rawValue).advanced(by: n))
+ }
+
+ public func distance(to other: Self) -> Double.Stride {
+ return Double(self._rawValue).distance(to: Double(other._rawValue))
+ }
+
+ // MARK: BinaryFloatingPoint
+
+ public static var exponentBitCount: Int { Double.exponentBitCount }
+ public static var significandBitCount: Int { Double.significandBitCount }
+
+ public var binade: Self { Self(Double(self._rawValue).binade) }
+ public var exponentBitPattern: Double.RawExponent { Double(self._rawValue).exponentBitPattern }
+ public var significandBitPattern: Double.RawSignificand {
+ Double(self._rawValue).significandBitPattern
+ }
+ public var significandWidth: Int { Double(self._rawValue).significandWidth }
+
+ public init(
+ sign: FloatingPointSign,
+ exponentBitPattern: Double.RawExponent,
+ significandBitPattern: Double.RawSignificand
+ ) {
+ self.init(Double(
+ sign: sign,
+ exponentBitPattern: exponentBitPattern,
+ significandBitPattern: significandBitPattern
+ ))
+ }
+
+ public init(_ value: Source) where Source: BinaryFloatingPoint {
+ self.init(rawValue: .init(rawValue: .init(value)))
+ }
+
+ public init(_ value: Source) where Source: BinaryInteger {
+ self.init(rawValue: .init(rawValue: .init(value)))
+ }
+
+ // MARK: CustomStringConvertible
+
+ public var description: String {
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .decimal
+ return formatter.string(for: self._rawValue) ?? "\(self._rawValue)"
+ }
+
+ // MARK: CustomDebugStringConvertible
+
+ public var debugDescription: String {
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .decimal
+ formatter.maximumFractionDigits = 99
+ formatter.locale = .en
+ return formatter.string(for: self._rawValue) ?? "\(self._rawValue)"
+ }
+
+}
+
+// MARK: - ValidatableCoordinateComponent
+
+public protocol ValidatableCoordinateComponent: CoordinateComponent {
+
+ static var min: Self { get }
+ static var max: Self { get }
+ static var validRange: ClosedRange { get }
+
+ /// Tells if this value is in valid bounds or not.
+ var isValid: Bool { get }
+
+ /// A copy of this value, in valid bounds.
+ ///
+ /// In angular coordinate system, it means rotating the value
+ /// to put it in the valid range.
+ ///
+ /// In linear coordinate system, it can mean making the value positive.
+ var valid: Self { get }
+
+ static func random() -> Self
+
+}
+
+extension ValidatableCoordinateComponent {
+ public static var validRange: ClosedRange { min...max }
+ public var isValid: Bool { Self.validRange.contains(self) }
+}
+
+extension ValidatableCoordinateComponent where Self.RawSignificand: FixedWidthInteger {
+ public static func random() -> Self {
+ return Self.random(in: validRange)
+ }
+}
+
+// MARK: - AngularCoordinateComponent
+
+/// - Todo: Create custom struct that handles cycling in an interval.
+public protocol AngularCoordinateComponent: ValidatableCoordinateComponent {
+
+ static var fullRotation: Self { get }
+ static var halfRotation: Self { get }
+
+ /// "N" or "E" depending on axis
+ static var positiveDirectionChar: Character { get }
+
+ /// "S" or "W" depending on axis
+ static var negativeDirectionChar: Character { get }
+
+ var decimalDegrees: Double { get set }
+ var radians: Double { get set }
+
+ init(decimalDegrees: Double)
+ init(radians: Double)
+
+}
+
+public extension AngularCoordinateComponent {
+
+ static var min: Self { -halfRotation }
+ static var max: Self { halfRotation }
+
+ var valid: Self {
+ if isValid {
+ return self
+ } else {
+ let remainder = self.truncatingRemainder(dividingBy: Self.fullRotation)
+
+ if abs(remainder) > .halfRotation {
+ let base = self < .zero ? Self.fullRotation : -Self.fullRotation
+ return base + remainder
+ } else {
+ return remainder
+ }
+ }
+ }
+ var radians: Double {
+ get { self.decimalDegrees * .pi / 180.0 }
+ set { self.decimalDegrees = newValue * 180.0 / .pi }
+ }
+
+ init(radians: Double) {
+ self.init(decimalDegrees: radians * 180.0 / .pi)
+ }
+
+}
diff --git a/Sources/Geodesy/Geodesy.swift b/Sources/Geodesy/Geodesy.swift
index 599e5d0..2c77e74 100644
--- a/Sources/Geodesy/Geodesy.swift
+++ b/Sources/Geodesy/Geodesy.swift
@@ -7,11 +7,18 @@
//
import Foundation
-import Tagged
+import ValueWithUnit
+
+// MARK: - General protocols
+
+public protocol EPSGItem {
+ static var epsgName: String { get }
+ static var epsgCode: Int { get }
+}
// MARK: - Coordinates
-public protocol Coordinate: Hashable, CustomStringConvertible {
+public protocol Coordinates: Hashable, AdditiveArithmetic, Zeroable, CustomStringConvertible, CustomDebugStringConvertible {
associatedtype CRS: CoordinateReferenceSystem
typealias Components = CRS.CoordinateSystem.Values
@@ -20,13 +27,14 @@ public protocol Coordinate: Hashable, CustomStringConvertible {
init(components: Components)
}
-public extension Coordinate {
- var description: String { "[\(CRS.name)]\(String(describing: self.components))" }
+public extension Coordinates {
+ var description: String { String(describing: self.components) }
+ var debugDescription: String { "[\(CRS.epsgName)]\(String(reflecting: self.components))" }
}
// MARK: 2D Coordinates
-public extension Coordinate where CRS.CoordinateSystem: TwoDimensionsCS {
+public extension Coordinates where CRS.CoordinateSystem: TwoDimensionsCS {
var x: CRS.CoordinateSystem.Axis1.Value { self.components.0 }
var y: CRS.CoordinateSystem.Axis2.Value { self.components.1 }
@@ -38,7 +46,7 @@ public extension Coordinate where CRS.CoordinateSystem: TwoDimensionsCS {
}
}
-public protocol TwoDimensionsCoordinate: Coordinate where CRS: TwoDimensionsCRS {
+public protocol TwoDimensionsCoordinate: Coordinates where CRS: TwoDimensionsCRS {
typealias X = CRS.CoordinateSystem.Axis1.Value
typealias Y = CRS.CoordinateSystem.Axis2.Value
@@ -54,9 +62,27 @@ public extension TwoDimensionsCoordinate {
init(components: Components) {
self.init(x: components.0, y: components.1)
}
+
+ static func + (lhs: Self, rhs: Self) -> Self {
+ Self(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
+ }
+ static func - (lhs: Self, rhs: Self) -> Self {
+ Self(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
+ }
+
+ func offsetBy(dx: X, dy: Y) -> Self {
+ Self(x: self.x + dx, y: self.y + dy)
+ }
+}
+
+public extension TwoDimensionsCoordinate where CRS: GeographicCRS {
+ var latitude: X { self.x }
+ var longitude: Y { self.y }
}
public struct Coordinate2DOf: TwoDimensionsCoordinate where CRS: TwoDimensionsCRS {
+ public static var zero: Self { Self(x: .zero, y: .zero) }
+
public let x: X
public let y: Y
@@ -73,7 +99,7 @@ public struct Coordinate2DOf: TwoDimensionsCoordinate where CRS: TwoDimensi
// MARK: 3D Coordinates
-public extension Coordinate where CRS.CoordinateSystem: ThreeDimensionsCS {
+public extension Coordinates where CRS.CoordinateSystem: ThreeDimensionsCS {
var x: CRS.CoordinateSystem.Axis1.Value { self.components.0 }
var y: CRS.CoordinateSystem.Axis2.Value { self.components.1 }
var z: CRS.CoordinateSystem.Axis3.Value { self.components.2 }
@@ -87,7 +113,7 @@ public extension Coordinate where CRS.CoordinateSystem: ThreeDimensionsCS {
}
}
-public protocol ThreeDimensionsCoordinate: Coordinate where CRS: ThreeDimensionsCRS {
+public protocol ThreeDimensionsCoordinate: Coordinates where CRS: ThreeDimensionsCRS {
typealias X = CRS.CoordinateSystem.Axis1.Value
typealias Y = CRS.CoordinateSystem.Axis2.Value
typealias Z = CRS.CoordinateSystem.Axis3.Value
@@ -105,9 +131,28 @@ public extension ThreeDimensionsCoordinate {
init(components: Components) {
self.init(x: components.0, y: components.1, z: components.2)
}
+
+ static func + (lhs: Self, rhs: Self) -> Self {
+ Self(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z)
+ }
+ static func - (lhs: Self, rhs: Self) -> Self {
+ Self(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z)
+ }
+
+ func offsetBy(dx: X, dy: Y, dz: Z) -> Self {
+ Self(x: self.x + dx, y: self.y + dy, z: self.z + dz)
+ }
+}
+
+public extension ThreeDimensionsCoordinate where CRS: GeographicCRS {
+ var latitude: X { self.x }
+ var longitude: Y { self.y }
+ var altitude: Z { self.z }
}
public struct Coordinate3DOf: ThreeDimensionsCoordinate where CRS: ThreeDimensionsCRS {
+ public static var zero: Self { Self(x: .zero, y: .zero, z: .zero) }
+
public let x: X
public let y: Y
public let z: Z
@@ -126,11 +171,9 @@ public struct Coordinate3DOf: ThreeDimensionsCoordinate where CRS: ThreeDim
// MARK: - Coordinate Reference System (CRS/SRS)
-public protocol CoordinateReferenceSystem {
+public protocol CoordinateReferenceSystem: EPSGItem {
associatedtype Datum: DatumProtocol
associatedtype CoordinateSystem: Geodesy.CoordinateSystem
- static var name: String { get }
- static var code: Int { get }
}
public protocol TwoDimensionsCRS: CoordinateReferenceSystem
@@ -148,24 +191,17 @@ public protocol DatumProtocol {
associatedtype PrimeMeridian: MeridianProtocol
}
-public protocol DatumEnsemble: DatumProtocol {
+public protocol DatumEnsemble: DatumProtocol, EPSGItem {
associatedtype PrimaryMember: DatumEnsembleMember
where PrimaryMember.Ellipsoid == Self.Ellipsoid,
PrimaryMember.PrimeMeridian == Self.PrimeMeridian
- static var name: String { get }
- static var code: Int { get }
-}
-public protocol DatumEnsembleMember: DatumProtocol {
- static var name: String { get }
- static var code: Int { get }
}
+public protocol DatumEnsembleMember: DatumProtocol, EPSGItem {}
public protocol DynamicGeodeticDatum: DatumEnsembleMember {}
// MARK: Reference Ellipsoid
-public protocol ReferenceEllipsoid {
- static var name: String { get }
- static var code: Int { get }
+public protocol ReferenceEllipsoid: EPSGItem {
static var semiMajorAxis: Double { get }
static var semiMinorAxis: Double { get }
static var inverseFlattening: Double { get }
@@ -190,25 +226,21 @@ public extension ReferenceEllipsoid {
// MARK: Meridian
-public protocol MeridianProtocol {
- static var name: String { get }
- static var code: Int { get }
+public protocol MeridianProtocol: EPSGItem {
static var greenwichLongitude: Double { get }
}
public enum Greenwich: MeridianProtocol {
- public static let name: String = "Greenwich"
- public static let code: Int = 8901
+ public static let epsgName: String = "Greenwich"
+ public static let epsgCode: Int = 8901
public static let greenwichLongitude: Double = 0
}
// MARK: - Coordinate System (CS)
-public protocol CoordinateSystem {
+public protocol CoordinateSystem: EPSGItem {
associatedtype Axes
associatedtype Values
- static var name: String { get }
- static var code: Int { get }
}
public protocol TwoDimensionsCS: CoordinateSystem
@@ -217,8 +249,6 @@ where Axes == (Axis1, Axis2),
{
associatedtype Axis1: Axis
associatedtype Axis2: Axis
- // associatedtype Axes = (Axis1, Axis2)
- // associatedtype Values = (Axis1.Value, Axis2.Value)
}
public protocol ThreeDimensionsCS: CoordinateSystem
@@ -228,99 +258,191 @@ where Axes == (Axis1, Axis2, Axis3),
associatedtype Axis1: Axis
associatedtype Axis2: Axis
associatedtype Axis3: Axis
- // associatedtype Axes = (Axis1, Axis2, Axis3)
- // associatedtype Values = (Axis1.Value, Axis2.Value, Axis3.Value)
}
public enum GeocentricCartesian3DCS: ThreeDimensionsCS {
public typealias Axis1 = GeocentricX
public typealias Axis2 = GeocentricY
public typealias Axis3 = GeocentricZ
- public static let name: String = "Cartesian 3D CS (geocentric)"
- public static let code: Int = 6500
+ public static let epsgName: String = "Cartesian 3D CS (geocentric)"
+ public static let epsgCode: Int = 6500
}
public enum Ellipsoidal3DCS: ThreeDimensionsCS {
public typealias Axis1 = GeodeticLatitude
public typealias Axis2 = GeodeticLongitude
public typealias Axis3 = EllipsoidalHeight
- public static let name: String = "Ellipsoidal 3D CS"
- public static let code: Int = 6423
+ public static let epsgName: String = "Ellipsoidal 3D CS"
+ public static let epsgCode: Int = 6423
}
public enum Ellipsoidal2DCS: TwoDimensionsCS {
public typealias Axis1 = GeodeticLatitude
public typealias Axis2 = GeodeticLongitude
- public static let name: String = "Ellipsoidal 2D CS"
- public static let code: Int = 6422
+ public static let epsgName: String = "Ellipsoidal 2D CS"
+ public static let epsgCode: Int = 6422
}
// MARK: Axes
public protocol Axis {
- associatedtype UnitOfMeasurement: UnitOfMeasurementProtocol
- associatedtype Value: BinaryFloatingPoint
+ associatedtype UnitOfMeasurement: Geodesy.UnitOfMeasurement
+ associatedtype Value: CoordinateComponent
static var name: String { get }
static var abbreviation: String { get }
}
-public extension Axis {
- typealias Value = Tagged
-}
-
public enum GeocentricX: Axis {
- public typealias UnitOfMeasurement = Metre
+ public typealias UnitOfMeasurement = Meter
+ public struct Value: CoordinateComponent {
+ public var rawValue: DoubleOf
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+ }
public static let name: String = "Geocentric X"
public static let abbreviation: String = "X"
}
public enum GeocentricY: Axis {
- public typealias UnitOfMeasurement = Metre
+ public typealias UnitOfMeasurement = Meter
+ public struct Value: CoordinateComponent {
+ public var rawValue: DoubleOf
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+ }
public static let name: String = "Geocentric Y"
public static let abbreviation: String = "Y"
}
public enum GeocentricZ: Axis {
- public typealias UnitOfMeasurement = Metre
+ public typealias UnitOfMeasurement = Meter
+ public struct Value: CoordinateComponent {
+ public var rawValue: DoubleOf
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+ }
public static let name: String = "Geocentric Z"
public static let abbreviation: String = "Z"
}
+/// The latitude component of a geographical coordinate.
public enum GeodeticLatitude: Axis {
public typealias UnitOfMeasurement = Degree
+
+ public struct Value: AngularCoordinateComponent, RawRepresentable {
+ public typealias RawValue = DoubleOf
+
+ public static let positiveDirectionChar: Character = "N"
+ public static let negativeDirectionChar: Character = "S"
+
+ public static var fullRotation: Self = 180
+ public static var halfRotation: Self = 90
+
+ public var rawValue: RawValue
+
+ public var decimalDegrees: Double {
+ get { self.rawValue.rawValue }
+ set { self.rawValue.rawValue = newValue }
+ }
+ public var positive: Self {
+ if decimalDegrees < .zero {
+ // `degrees` is negative, so we end up with `180 - |degrees|`
+ return self + Self.fullRotation
+ } else {
+ return self
+ }
+ }
+
+ public init(decimalDegrees: Double) {
+ self.init(rawValue: .init(rawValue: decimalDegrees))
+ }
+
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+ }
+
public static let name: String = "Geodetic latitude"
public static let abbreviation: String = "Lat"
}
+public typealias Latitude = GeodeticLatitude.Value
+
+/// The longitude component of a geographical coordinate.
public enum GeodeticLongitude: Axis {
public typealias UnitOfMeasurement = Degree
+
+ public struct Value: AngularCoordinateComponent, RawRepresentable {
+ public typealias RawValue = DoubleOf
+
+ public static let positiveDirectionChar: Character = "E"
+ public static let negativeDirectionChar: Character = "W"
+
+ public static var fullRotation: Self = 360
+ public static var halfRotation: Self = 180
+
+ public var rawValue: DoubleOf
+
+ public var decimalDegrees: Double {
+ get { self.rawValue.rawValue }
+ set { self.rawValue.rawValue = newValue }
+ }
+ public var positive: Self {
+ if decimalDegrees < .zero {
+ // `degrees` is negative, so we end up with `360 - |degrees|`
+ return self + Self.fullRotation
+ } else {
+ return self
+ }
+ }
+
+ public init(decimalDegrees: Double) {
+ self.init(rawValue: .init(rawValue: decimalDegrees))
+ }
+
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+ }
+
public static let name: String = "Geodetic longitude"
public static let abbreviation: String = "Lon"
}
+public typealias Longitude = GeodeticLongitude.Value
+
public enum EllipsoidalHeight: Axis {
- public typealias UnitOfMeasurement = Metre
+ public typealias UnitOfMeasurement = Meter
+
+ public struct Value: CoordinateComponent {
+ public var rawValue: DoubleOf
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+ public static func random() -> Self {
+ return Self.random(in: -100...10_000)
+ }
+ }
+
public static let name: String = "Ellipsoidal height"
public static let abbreviation: String = "h"
}
-// MARK: - Units of Measurement
+public typealias Altitude = EllipsoidalHeight.Value
-public protocol UnitOfMeasurementProtocol: Hashable, Sendable {
- static var name: String { get }
- static var code: Int { get }
-}
-
-public protocol Length: UnitOfMeasurementProtocol {}
+// MARK: - Units of Measurement
-public protocol Angle: UnitOfMeasurementProtocol {}
+public protocol UnitOfMeasurement: EPSGItem, Hashable, Sendable {}
-public enum Metre: Length {
- public static let name: String = "Metre"
- public static let code: Int = 9001
+extension Meter: Geodesy.UnitOfMeasurement {
+ public static let epsgName: String = "Metre"
+ public static let epsgCode: Int = 9001
}
-public enum Degree: Angle {
- public static let name: String = "Degree"
- public static let code: Int = 9122
+extension Degree: Geodesy.UnitOfMeasurement {
+ public static let epsgName: String = "Degree"
+ public static let epsgCode: Int = 9122
}
diff --git a/Sources/GeoCoordinates/Helpers/Locale+Common.swift b/Sources/Geodesy/Toolbox/Locale+Common.swift
similarity index 87%
rename from Sources/GeoCoordinates/Helpers/Locale+Common.swift
rename to Sources/Geodesy/Toolbox/Locale+Common.swift
index a515b27..59622ca 100644
--- a/Sources/GeoCoordinates/Helpers/Locale+Common.swift
+++ b/Sources/Geodesy/Toolbox/Locale+Common.swift
@@ -6,10 +6,8 @@
// Copyright © 2022 Rémi Bardon. All rights reserved.
//
-import Foundation
+import struct Foundation.Locale
extension Locale {
-
static var en: Locale { Locale(identifier: "en_US") }
-
}
diff --git a/Sources/GeoCoordinates/Protocols/Zeroable.swift b/Sources/Geodesy/Toolbox/Zeroable.swift
similarity index 98%
rename from Sources/GeoCoordinates/Protocols/Zeroable.swift
rename to Sources/Geodesy/Toolbox/Zeroable.swift
index ae1fb15..8e4a77b 100644
--- a/Sources/GeoCoordinates/Protocols/Zeroable.swift
+++ b/Sources/Geodesy/Toolbox/Zeroable.swift
@@ -7,7 +7,5 @@
//
public protocol Zeroable {
-
static var zero: Self { get }
-
}
diff --git a/Sources/GeoCoordinates/Display/Coordinate+Display.swift b/Sources/GeodeticDisplay/Coordinate+Display.swift
similarity index 63%
rename from Sources/GeoCoordinates/Display/Coordinate+Display.swift
rename to Sources/GeodeticDisplay/Coordinate+Display.swift
index 2bfb92b..384367b 100644
--- a/Sources/GeoCoordinates/Display/Coordinate+Display.swift
+++ b/Sources/GeodeticDisplay/Coordinate+Display.swift
@@ -6,9 +6,11 @@
// Copyright © 2021 Rémi Bardon. All rights reserved.
//
-import Foundation
+import class Foundation.NumberFormatter
+import class Foundation.NSNumber
+import Geodesy
-extension AngularCoordinate {
+extension AngularCoordinateComponent {
public var directionChar: Character {
return self.decimalDegrees < .zero ? Self.negativeDirectionChar : Self.positiveDirectionChar
@@ -36,14 +38,14 @@ extension AngularCoordinate {
// MARK: - GeographicNotation
-extension AngularCoordinate {
+extension AngularCoordinateComponent {
public func ddNotation(maxDigits: UInt8 = 6) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = Int(maxDigits)
formatter.locale = .en
- return formatter.string(from: NSNumber(value: decimalDegrees)) ?? String(decimalDegrees)
+ return formatter.string(for: decimalDegrees) ?? String(decimalDegrees)
}
public func dmNotation(full: Bool = false, maxDigits: UInt8 = 3) -> String {
@@ -55,7 +57,7 @@ extension AngularCoordinate {
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = Int(maxDigits)
formatter.locale = .en
- if full || minutes > 0, let string = formatter.string(from: NSNumber(value: minutes)) {
+ if full || minutes > 0, let string = formatter.string(for: minutes) {
parts.append("\(string)'")
}
@@ -76,7 +78,7 @@ extension AngularCoordinate {
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = Int(maxDigits)
formatter.locale = .en
- if full || seconds > 0, let string = formatter.string(from: NSNumber(value: seconds)) {
+ if full || seconds > 0, let string = formatter.string(for: seconds) {
parts.append("\(string)\"")
}
@@ -86,25 +88,3 @@ extension AngularCoordinate {
}
}
-
-extension WGS84Coordinate2D: GeographicNotation {
-
- public func ddNotation(maxDigits: UInt8) -> String {
- let lat = self.latitude.ddNotation(maxDigits: maxDigits)
- let long = self.longitude.ddNotation(maxDigits: maxDigits)
- return "\(lat), \(long)"
- }
-
- public func dmNotation(full: Bool = false, maxDigits: UInt8) -> String {
- let lat = self.latitude.dmNotation(full: full, maxDigits: maxDigits)
- let long = self.longitude.dmNotation(full: full, maxDigits: maxDigits)
- return "\(lat), \(long)"
- }
-
- public func dmsNotation(full: Bool = false, maxDigits: UInt8) -> String {
- let lat = self.latitude.dmsNotation(full: full, maxDigits: maxDigits)
- let long = self.longitude.dmsNotation(full: full, maxDigits: maxDigits)
- return "\(lat), \(long)"
- }
-
-}
diff --git a/Sources/GeoCoordinates/Display/GeographicNotation.swift b/Sources/GeodeticDisplay/GeographicNotation.swift
similarity index 100%
rename from Sources/GeoCoordinates/Display/GeographicNotation.swift
rename to Sources/GeodeticDisplay/GeographicNotation.swift
diff --git a/Sources/GeoModels/Helpers/Exports.swift b/Sources/GeodeticDisplay/Toolbox/Exports.swift
similarity index 81%
rename from Sources/GeoModels/Helpers/Exports.swift
rename to Sources/GeodeticDisplay/Toolbox/Exports.swift
index 0802286..a36aaca 100644
--- a/Sources/GeoModels/Helpers/Exports.swift
+++ b/Sources/GeodeticDisplay/Toolbox/Exports.swift
@@ -6,4 +6,4 @@
// Copyright © 2022 Rémi Bardon. All rights reserved.
//
-@_exported import GeoCoordinates
+@_exported import Geodesy
diff --git a/Sources/GeoCoordinates/Protocols/FloatingPoint+Fraction.swift b/Sources/GeodeticDisplay/Toolbox/FloatingPoint+Fraction.swift
similarity index 100%
rename from Sources/GeoCoordinates/Protocols/FloatingPoint+Fraction.swift
rename to Sources/GeodeticDisplay/Toolbox/FloatingPoint+Fraction.swift
diff --git a/Sources/GeodeticDisplay/Toolbox/Locale+Common.swift b/Sources/GeodeticDisplay/Toolbox/Locale+Common.swift
new file mode 100644
index 0000000..59622ca
--- /dev/null
+++ b/Sources/GeodeticDisplay/Toolbox/Locale+Common.swift
@@ -0,0 +1,13 @@
+//
+// Locale+Common.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 03/02/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import struct Foundation.Locale
+
+extension Locale {
+ static var en: Locale { Locale(identifier: "en_US") }
+}
diff --git a/Sources/GeoModels/Protocols/Boundable.swift b/Sources/GeodeticGeometry/Protocols/Boundable.swift
similarity index 96%
rename from Sources/GeoModels/Protocols/Boundable.swift
rename to Sources/GeodeticGeometry/Protocols/Boundable.swift
index 548ed4c..d22031b 100644
--- a/Sources/GeoModels/Protocols/Boundable.swift
+++ b/Sources/GeodeticGeometry/Protocols/Boundable.swift
@@ -10,7 +10,7 @@
public protocol Boundable {
- associatedtype BoundingBox: GeoModels.BoundingBox
+ associatedtype BoundingBox: GeodeticGeometry.BoundingBox
var bbox: BoundingBox { get }
diff --git a/Sources/GeoModels/Protocols/BoundingBox.swift b/Sources/GeodeticGeometry/Protocols/BoundingBox.swift
similarity index 61%
rename from Sources/GeoModels/Protocols/BoundingBox.swift
rename to Sources/GeodeticGeometry/Protocols/BoundingBox.swift
index 38445f7..d0ad981 100644
--- a/Sources/GeoModels/Protocols/BoundingBox.swift
+++ b/Sources/GeodeticGeometry/Protocols/BoundingBox.swift
@@ -6,13 +6,12 @@
// Copyright © 2022 Rémi Bardon. All rights reserved.
//
-import GeoCoordinates
+import Geodesy
-public protocol BoundingBox: Hashable, Zeroable {
+public protocol BoundingBox: Hashable, Zeroable {
- associatedtype CoordinateSystem: GeoModels.CoordinateSystem
- typealias Point = Self.CoordinateSystem.Point
- typealias Size = Self.CoordinateSystem.Size
+ associatedtype Point: GeodeticGeometry.Point
+ associatedtype Size: GeodeticGeometry.Size
var origin: Point { get }
var size: Size { get }
diff --git a/Sources/GeodeticGeometry/Protocols/GeometricSystem.swift b/Sources/GeodeticGeometry/Protocols/GeometricSystem.swift
new file mode 100644
index 0000000..df57e37
--- /dev/null
+++ b/Sources/GeodeticGeometry/Protocols/GeometricSystem.swift
@@ -0,0 +1,25 @@
+//
+// GeometricSystem.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 20/03/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import Geodesy
+
+public protocol GeometricSystem {
+
+ associatedtype CRS: CoordinateReferenceSystem
+
+ associatedtype Point: GeodeticGeometry.Point
+ associatedtype Size: GeodeticGeometry.Size
+// associatedtype MultiPoint: GeodeticGeometry.MultiPoint
+ associatedtype Line: GeodeticGeometry.Line
+// associatedtype MultiLine: GeodeticGeometry.MultiLine
+ associatedtype LineString: GeodeticGeometry.LineString
+// associatedtype LinearRing: GeodeticGeometry.LinearRing
+
+ associatedtype BoundingBox: GeodeticGeometry.BoundingBox
+
+}
diff --git a/Sources/GeoModels/Protocols/Line.swift b/Sources/GeodeticGeometry/Protocols/Line.swift
similarity index 94%
rename from Sources/GeoModels/Protocols/Line.swift
rename to Sources/GeodeticGeometry/Protocols/Line.swift
index 410ddfb..1c4625c 100644
--- a/Sources/GeoModels/Protocols/Line.swift
+++ b/Sources/GeodeticGeometry/Protocols/Line.swift
@@ -8,7 +8,7 @@
import NonEmpty
-public protocol Line: GeoModels.MultiPoint
+public protocol Line: GeodeticGeometry.MultiPoint
where Self.Points == AtLeast2<[Self.Point]>
{
diff --git a/Sources/GeoModels/Protocols/LineString.swift b/Sources/GeodeticGeometry/Protocols/LineString.swift
similarity index 95%
rename from Sources/GeoModels/Protocols/LineString.swift
rename to Sources/GeodeticGeometry/Protocols/LineString.swift
index 35b1c4e..4f9e84a 100644
--- a/Sources/GeoModels/Protocols/LineString.swift
+++ b/Sources/GeodeticGeometry/Protocols/LineString.swift
@@ -7,10 +7,11 @@
//
import Algorithms
+import GeodeticDisplay
import NonEmpty
-public protocol LineString:
- GeoModels.MultiLine,
+public protocol LineString:
+ GeodeticGeometry.MultiLine,
CustomDebugStringConvertible
{
diff --git a/Sources/GeoModels/Protocols/LinearRing.swift b/Sources/GeodeticGeometry/Protocols/LinearRing.swift
similarity index 89%
rename from Sources/GeoModels/Protocols/LinearRing.swift
rename to Sources/GeodeticGeometry/Protocols/LinearRing.swift
index e34f99d..14ea0bc 100644
--- a/Sources/GeoModels/Protocols/LinearRing.swift
+++ b/Sources/GeodeticGeometry/Protocols/LinearRing.swift
@@ -9,7 +9,7 @@
import Algorithms
import NonEmpty
-public protocol LinearRing: GeoModels.LineString {}
+public protocol LinearRing: GeodeticGeometry.LineString {}
extension LinearRing where Self.Lines.Collection: RangeReplaceableCollection {
diff --git a/Sources/GeoModels/Protocols/MultiLine.swift b/Sources/GeodeticGeometry/Protocols/MultiLine.swift
similarity index 82%
rename from Sources/GeoModels/Protocols/MultiLine.swift
rename to Sources/GeodeticGeometry/Protocols/MultiLine.swift
index 375dad2..3d167a9 100644
--- a/Sources/GeoModels/Protocols/MultiLine.swift
+++ b/Sources/GeodeticGeometry/Protocols/MultiLine.swift
@@ -8,11 +8,11 @@
import NonEmpty
-public protocol MultiLine: GeoModels.MultiPoint
+public protocol MultiLine: GeodeticGeometry.MultiPoint
where Points == AtLeast2<[Self.Point]>
{
- typealias Line = Self.CoordinateSystem.Line
+ associatedtype Line: GeodeticGeometry.Line
associatedtype Lines: NonEmptyProtocol
where Self.Lines.Element == Self.Line
diff --git a/Sources/GeoModels/Protocols/MultiPoint.swift b/Sources/GeodeticGeometry/Protocols/MultiPoint.swift
similarity index 65%
rename from Sources/GeoModels/Protocols/MultiPoint.swift
rename to Sources/GeodeticGeometry/Protocols/MultiPoint.swift
index b190b8a..05ba4df 100644
--- a/Sources/GeoModels/Protocols/MultiPoint.swift
+++ b/Sources/GeodeticGeometry/Protocols/MultiPoint.swift
@@ -6,12 +6,12 @@
// Copyright © 2022 Rémi Bardon. All rights reserved.
//
+import Geodesy
import NonEmpty
-public protocol MultiPoint: Hashable {
+public protocol MultiPoint: Hashable {
- associatedtype CoordinateSystem: GeoModels.CoordinateSystem
- typealias Point = Self.CoordinateSystem.Point
+ associatedtype Point: GeodeticGeometry.Point
associatedtype Points: NonEmptyProtocol
where Self.Points.Element == Self.Point
diff --git a/Sources/GeodeticGeometry/Protocols/Point.swift b/Sources/GeodeticGeometry/Protocols/Point.swift
new file mode 100644
index 0000000..82db1ce
--- /dev/null
+++ b/Sources/GeodeticGeometry/Protocols/Point.swift
@@ -0,0 +1,84 @@
+//
+// Point.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 06/03/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import Geodesy
+
+@dynamicMemberLookup
+public protocol Point: Hashable, Zeroable, AdditiveArithmetic {
+
+ associatedtype CRS: CoordinateReferenceSystem
+ associatedtype Coordinates: Geodesy.Coordinates
+
+ var coordinates: Coordinates { get set }
+
+ init(_ coordinates: Coordinates)
+
+}
+
+public extension Point {
+
+ static var zero: Self { Self(.zero) }
+
+ subscript(dynamicMember keyPath: KeyPath) -> T {
+ self.coordinates[keyPath: keyPath]
+ }
+
+ // MARK: AdditiveArithmetic
+
+ static func + (lhs: Self, rhs: Self) -> Self {
+ Self(lhs.coordinates + rhs.coordinates)
+ }
+ static func - (lhs: Self, rhs: Self) -> Self {
+ Self(lhs.coordinates - rhs.coordinates)
+ }
+
+}
+
+public extension Point where Coordinates: TwoDimensionsCoordinate {
+ func offsetBy(
+ dx: Coordinates.X = .zero,
+ dy: Coordinates.Y = .zero
+ ) -> Self {
+ Self(self.coordinates.offsetBy(dx: dx, dy: dy))
+ }
+ func offsetBy(
+ dLat: Coordinates.X = .zero,
+ dLong: Coordinates.Y = .zero
+ ) -> Self where Coordinates.CRS: GeographicCRS {
+ self.offsetBy(dx: dLat, dy: dLong)
+ }
+}
+
+public extension Point where Coordinates: ThreeDimensionsCoordinate {
+ func offsetBy(
+ dx: Coordinates.X = .zero,
+ dy: Coordinates.Y = .zero,
+ dz: Coordinates.Z = .zero
+ ) -> Self {
+ Self(self.coordinates.offsetBy(dx: dx, dy: dy, dz: dz))
+ }
+ func offsetBy(
+ dLat: Coordinates.X = .zero,
+ dLong: Coordinates.Y = .zero,
+ dAlt: Coordinates.Z = .zero
+ ) -> Self where Coordinates.CRS: GeographicCRS {
+ self.offsetBy(dx: dLat, dy: dLong, dz: dAlt)
+ }
+}
+
+public struct PointOf: Point {
+
+ public typealias CRS = Coordinates.CRS
+
+ public var coordinates: Coordinates
+
+ public init(_ coordinates: Coordinates) {
+ self.coordinates = coordinates
+ }
+
+}
diff --git a/Sources/GeoModels/Protocols/Size.swift b/Sources/GeodeticGeometry/Protocols/Size.swift
similarity index 63%
rename from Sources/GeoModels/Protocols/Size.swift
rename to Sources/GeodeticGeometry/Protocols/Size.swift
index 8365aa0..7de919d 100644
--- a/Sources/GeoModels/Protocols/Size.swift
+++ b/Sources/GeodeticGeometry/Protocols/Size.swift
@@ -6,11 +6,11 @@
// Copyright © 2022 Rémi Bardon. All rights reserved.
//
-import GeoCoordinates
+import Geodesy
-public protocol Size: Hashable, RawRepresentable, Zeroable {
+public protocol Size: Hashable, RawRepresentable, Zeroable {
- associatedtype CoordinateSystem: GeoModels.CoordinateSystem
+ associatedtype CRS: Geodesy.CoordinateReferenceSystem
init(rawValue: RawValue)
diff --git a/Sources/GeoModels/Helpers/CoreLocation+Equatable.swift b/Sources/GeodeticGeometry/Toolbox/CoreLocation+Equatable.swift
similarity index 100%
rename from Sources/GeoModels/Helpers/CoreLocation+Equatable.swift
rename to Sources/GeodeticGeometry/Toolbox/CoreLocation+Equatable.swift
diff --git a/Sources/GeodeticGeometry/Toolbox/Exports.swift b/Sources/GeodeticGeometry/Toolbox/Exports.swift
new file mode 100644
index 0000000..a36aaca
--- /dev/null
+++ b/Sources/GeodeticGeometry/Toolbox/Exports.swift
@@ -0,0 +1,9 @@
+//
+// Exports.swift
+// GeoSwift
+//
+// Created by Rémi Bardon on 03/07/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+@_exported import Geodesy
diff --git a/Sources/GeoModels/Helpers/MapKit+Equatable.swift b/Sources/GeodeticGeometry/Toolbox/MapKit+Equatable.swift
similarity index 100%
rename from Sources/GeoModels/Helpers/MapKit+Equatable.swift
rename to Sources/GeodeticGeometry/Toolbox/MapKit+Equatable.swift
diff --git a/Sources/Turf/GeoModels+Turf.swift b/Sources/Turf/GeoModels+Turf.swift
index eb5dd75..ef19945 100644
--- a/Sources/Turf/GeoModels+Turf.swift
+++ b/Sources/Turf/GeoModels+Turf.swift
@@ -172,10 +172,10 @@ public extension GeoModels.LineString where CoordinateSystem: CoordinateSystemAl
func bezier(sharpness: Double, resolution: Double) -> Self {
// NOTE: For some reason, this does not compile without type casts.
- // Cannot convert return expression of type 'Self.CoordinateSystem.LineString' to return type 'Self'
+ // Cannot convert return expression of type 'Self.GeometricSystem.LineString' to return type 'Self'
// Insert ' as! Self'
- // Cannot convert value of type 'Self' to expected argument type 'Self.CoordinateSystem.LineString'
- // Insert ' as! Self.CoordinateSystem.LineString'
+ // Cannot convert value of type 'Self' to expected argument type 'Self.GeometricSystem.LineString'
+ // Insert ' as! Self.GeometricSystem.LineString'
Self.CoordinateSystem.bezier(
forLineString: self as! Self.CoordinateSystem.LineString,
sharpness: sharpness,
diff --git a/Sources/Turf/Turf+GeoModels.swift b/Sources/Turf/Turf+GeoModels.swift
index 29e8506..fee8811 100644
--- a/Sources/Turf/Turf+GeoModels.swift
+++ b/Sources/Turf/Turf+GeoModels.swift
@@ -18,7 +18,7 @@ import NonEmpty
//public func naiveBBox(
// forMultiPoint multiPoint: MultiPoint
-//) -> MultiPoint.Point.CoordinateSystem.BoundingBox {
+//) -> MultiPoint.Point.GeometricSystem.BoundingBox {
// return naiveBBox(forNonEmptyCollection: multiPoint.points)
//}
diff --git a/Sources/ValueWithUnit/Conversions.swift b/Sources/ValueWithUnit/Conversions.swift
new file mode 100644
index 0000000..86536fa
--- /dev/null
+++ b/Sources/ValueWithUnit/Conversions.swift
@@ -0,0 +1,20 @@
+//
+// Conversions.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 13/10/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+public extension Value where Unit: AngleUnit {
+ func to<
+ OtherUnit: AngleUnit,
+ Target: Value
+ >(_ other: OtherUnit) -> Target {
+ if Unit.radians == OtherUnit.radians {
+ return Target(rawValue: self.rawValue)
+ } else {
+ return Target(rawValue: self.rawValue / RawValue(Unit.radians * OtherUnit.radians))
+ }
+ }
+}
diff --git a/Sources/ValueWithUnit/ValueWithUnit.swift b/Sources/ValueWithUnit/ValueWithUnit.swift
new file mode 100644
index 0000000..f1b33be
--- /dev/null
+++ b/Sources/ValueWithUnit/ValueWithUnit.swift
@@ -0,0 +1,44 @@
+//
+// ValueWithUnit.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 13/10/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import Foundation
+
+public protocol Unit: Hashable, Sendable {
+ #warning("TODO: Add name")
+}
+public protocol AngleUnit: Unit {
+ static var radians: Double { get }
+}
+public protocol LengthUnit: Unit {
+ static var meters: Double { get }
+}
+
+public protocol Value: RawRepresentable
+where RawValue: BinaryFloatingPoint
+{
+ associatedtype Unit: ValueWithUnit.Unit
+ init(rawValue: RawValue)
+}
+
+public struct Radian: AngleUnit {
+ public static let radians: Double = 1
+}
+public struct Degree: AngleUnit {
+ public static let radians: Double = 180.0 / .pi
+}
+public struct Meter: LengthUnit {
+ public static let meters: Double = 1
+}
+
+public struct DoubleOf: Value, Hashable {
+ public typealias Unit = U
+ public var rawValue: Double
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+}
diff --git a/Sources/WGS84/Display.swift b/Sources/WGS84/Display.swift
new file mode 100644
index 0000000..9efa037
--- /dev/null
+++ b/Sources/WGS84/Display.swift
@@ -0,0 +1,31 @@
+//
+// Display.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 24/08/2021.
+// Copyright © 2021 Rémi Bardon. All rights reserved.
+//
+
+import GeodeticDisplay
+
+extension Coordinate2D: GeographicNotation {
+
+ public func ddNotation(maxDigits: UInt8) -> String {
+ let lat = self.latitude.ddNotation(maxDigits: maxDigits)
+ let long = self.longitude.ddNotation(maxDigits: maxDigits)
+ return "\(lat), \(long)"
+ }
+
+ public func dmNotation(full: Bool = false, maxDigits: UInt8) -> String {
+ let lat = self.latitude.dmNotation(full: full, maxDigits: maxDigits)
+ let long = self.longitude.dmNotation(full: full, maxDigits: maxDigits)
+ return "\(lat), \(long)"
+ }
+
+ public func dmsNotation(full: Bool = false, maxDigits: UInt8) -> String {
+ let lat = self.latitude.dmsNotation(full: full, maxDigits: maxDigits)
+ let long = self.longitude.dmsNotation(full: full, maxDigits: maxDigits)
+ return "\(lat), \(long)"
+ }
+
+}
diff --git a/Sources/WGS84/Interoperability/GeoModels+MapKit.swift b/Sources/WGS84/Interoperability/GeoModels+MapKit.swift
new file mode 100644
index 0000000..2f09d06
--- /dev/null
+++ b/Sources/WGS84/Interoperability/GeoModels+MapKit.swift
@@ -0,0 +1,74 @@
+//
+// WGS84+MapKit.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 02/02/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+#if canImport(MapKit)
+import MapKit
+
+extension Coordinate2D {
+
+ public var mkMapPoint: MKMapPoint {
+ MKMapPoint(clLocationCoordinate2D)
+ }
+
+}
+
+//extension WGS842D.BoundingBox {
+//
+// public var mkCoordinateSpan: MKCoordinateSpan {
+// MKCoordinateSpan(latitudeDelta: height.decimalDegrees, longitudeDelta: width.decimalDegrees)
+// }
+// public var mkCoordinateRegion: MKCoordinateRegion {
+// MKCoordinateRegion(center: center.clLocationCoordinate2D, span: mkCoordinateSpan)
+// }
+//
+// public var mkMapWidth: Double {
+// mkMapWidthAtLatitude(center.latitude)
+// }
+// public var mkMapHeight: Double {
+// mkMapHeightAtLongitude(center.longitude)
+// }
+//
+// public var mkMapRect: MKMapRect {
+// // Avoid center recalculation
+// let center = self.center
+//
+// return MKMapRect(
+// origin: northWest.mkMapPoint,
+// size: MKMapSize(
+// width: mkMapWidthAtLatitude(center.latitude),
+// height: mkMapHeightAtLongitude(center.longitude)
+// )
+// )
+// }
+//
+// public func mkMapWidthAtLatitude(_ latitude: Latitude) -> Double {
+// let east = eastAtLatitude(latitude).mkMapPoint
+// let west = westAtLatitude(latitude).mkMapPoint
+//
+// // `east.x > west.x`
+// return east.x - west.x
+// }
+// public func mkMapHeightAtLongitude(_ longitude: Longitude) -> Double {
+// let south = southAtLongitude(longitude).mkMapPoint
+// let north = northAtLongitude(longitude).mkMapPoint
+//
+// // `south.y > north.y`
+// return south.y - north.y
+// }
+//
+//}
+//
+//extension WGS842D.LineString {
+//
+// public var mkPolyline: MKPolyline {
+// var points: [CLLocationCoordinate2D] = self.points.map(\.clLocationCoordinate2D)
+// return MKPolyline(coordinates: &points, count: points.count)
+// }
+//
+//}
+#endif
diff --git a/Sources/GeoCoordinates/Interoperability/GeoCoordinates+CoreLocation.swift b/Sources/WGS84/Interoperability/WGS84+CoreLocation.swift
similarity index 75%
rename from Sources/GeoCoordinates/Interoperability/GeoCoordinates+CoreLocation.swift
rename to Sources/WGS84/Interoperability/WGS84+CoreLocation.swift
index 953f9dd..e53175d 100644
--- a/Sources/GeoCoordinates/Interoperability/GeoCoordinates+CoreLocation.swift
+++ b/Sources/WGS84/Interoperability/WGS84+CoreLocation.swift
@@ -1,5 +1,5 @@
//
-// GeoCoordinates+CoreLocation.swift
+// WGS84+CoreLocation.swift
// SwiftGeo
//
// Created by Rémi Bardon on 02/02/2022.
@@ -9,7 +9,7 @@
#if canImport(CoreLocation)
import CoreLocation
-extension WGS84Coordinate2D {
+extension Coordinate2D {
public var clLocation: CLLocation {
CLLocation(latitude: latitude.decimalDegrees, longitude: longitude.decimalDegrees)
@@ -24,7 +24,10 @@ extension WGS84Coordinate2D {
}
public init(_ coordinate: CLLocationCoordinate2D) {
- self.init(latitude: Latitude(decimalDegrees: coordinate.latitude), longitude: Longitude(decimalDegrees: coordinate.longitude))
+ self.init(
+ latitude: .init(decimalDegrees: coordinate.latitude),
+ longitude: .init(decimalDegrees: coordinate.longitude)
+ )
}
}
diff --git a/Sources/WGS84/WGS84.swift b/Sources/WGS84/WGS84.swift
index ca86977..da2485f 100644
--- a/Sources/WGS84/WGS84.swift
+++ b/Sources/WGS84/WGS84.swift
@@ -20,8 +20,8 @@ public enum WGS84Geographic2DCRS: GeographicCRS, TwoDimensionsCRS {
public typealias Datum = WGS84Ensemble
public typealias CoordinateSystem = Ellipsoidal2DCS
- public static let name: String = "WGS 84 (geographic 2D)"
- public static let code: Int = 4326
+ public static let epsgName: String = "WGS 84 (geographic 2D)"
+ public static let epsgCode: Int = 4326
}
public typealias EPSG4326 = WGS84Geographic2DCRS
@@ -31,8 +31,8 @@ public enum WGS84Geographic3DCRS: GeographicCRS, ThreeDimensionsCRS {
public typealias Datum = WGS84Ensemble
public typealias CoordinateSystem = Ellipsoidal3DCS
- public static let name: String = "WGS 84 (geographic 3D)"
- public static let code: Int = 4979
+ public static let epsgName: String = "WGS 84 (geographic 3D)"
+ public static let epsgCode: Int = 4979
}
public typealias EPSG4979 = WGS84Geographic3DCRS
@@ -42,8 +42,8 @@ public enum WGS84GeocentricCRS: GeocentricCRS, ThreeDimensionsCRS {
public typealias Datum = WGS84Ensemble
public typealias CoordinateSystem = GeocentricCartesian3DCS
- public static let name: String = "WGS 84 (geocentric)"
- public static let code: Int = 4978
+ public static let epsgName: String = "WGS 84 (geocentric)"
+ public static let epsgCode: Int = 4978
}
public typealias EPSG4978 = WGS84GeocentricCRS
@@ -54,14 +54,14 @@ public enum WGS84Ensemble: DatumEnsemble {
public typealias Ellipsoid = EPSG7030
public typealias PrimeMeridian = Greenwich
public typealias PrimaryMember = Member7
- public static let name: String = "World Geodetic System 1984 ensemble"
- public static let code: Int = 6326
+ public static let epsgName: String = "World Geodetic System 1984 ensemble"
+ public static let epsgCode: Int = 6326
public enum Member7: DatumEnsembleMember {
public typealias Ellipsoid = EPSG7030
public typealias PrimeMeridian = Greenwich
- public static let name: String = "World Geodetic System 1984 (G2139)"
- public static let code: Int = 1309
+ public static let epsgName: String = "World Geodetic System 1984 (G2139)"
+ public static let epsgCode: Int = 1309
}
}
@@ -76,8 +76,8 @@ public typealias EPSG1309 = WGS84Ensemble.Member7
/// World Geodetic System 1984 ensemble member 7 ellipsoid
///
public enum EPSG7030: ReferenceEllipsoid {
- public static let name: String = "WGS 84"
- public static let code: Int = 7030
+ public static let epsgName: String = "WGS 84"
+ public static let epsgCode: Int = 7030
public static let semiMajorAxis: Double = 6378137
public static let inverseFlattening: Double = 298.257_223_563
}
@@ -88,8 +88,8 @@ public enum GeocentricCartesian3DCS: ThreeDimensionsCS {
public typealias Axis1 = GeocentricX
public typealias Axis2 = GeocentricY
public typealias Axis3 = GeocentricZ
- public static let name: String = "Cartesian 3D CS (geocentric)"
- public static let code: Int = 6500
+ public static let epsgName: String = "Cartesian 3D CS (geocentric)"
+ public static let epsgCode: Int = 6500
}
public typealias EPSG6500 = GeocentricCartesian3DCS
@@ -98,8 +98,8 @@ public enum Ellipsoidal3DCS: ThreeDimensionsCS {
public typealias Axis1 = GeodeticLatitude
public typealias Axis2 = GeodeticLongitude
public typealias Axis3 = EllipsoidalHeight
- public static let name: String = "Ellipsoidal 3D CS"
- public static let code: Int = 6423
+ public static let epsgName: String = "Ellipsoidal 3D CS"
+ public static let epsgCode: Int = 6423
}
public typealias EPSG6423 = Ellipsoidal3DCS
@@ -107,8 +107,8 @@ public typealias EPSG6423 = Ellipsoidal3DCS
public enum Ellipsoidal2DCS: TwoDimensionsCS {
public typealias Axis1 = GeodeticLatitude
public typealias Axis2 = GeodeticLongitude
- public static let name: String = "Ellipsoidal 2D CS"
- public static let code: Int = 6422
+ public static let epsgName: String = "Ellipsoidal 2D CS"
+ public static let epsgCode: Int = 6422
}
public typealias EPSG6422 = Ellipsoidal2DCS
diff --git a/Sources/GeoModels/WGS842D.swift b/Sources/WGS84/WGS842D.swift
similarity index 58%
rename from Sources/GeoModels/WGS842D.swift
rename to Sources/WGS84/WGS842D.swift
index 943693e..a519091 100644
--- a/Sources/GeoModels/WGS842D.swift
+++ b/Sources/WGS84/WGS842D.swift
@@ -6,38 +6,56 @@
// Copyright © 2022 Rémi Bardon. All rights reserved.
//
-import GeoCoordinates
+import GeodeticGeometry
import NonEmpty
-public enum WGS842D: GeoModels.CoordinateSystem {
+public enum WGS842D: GeometricSystem {
+
+ public typealias CRS = WGS84Geographic2DCRS
+
+ public struct Point: GeodeticGeometry.Point {
+
+ public typealias CRS = WGS84Geographic2DCRS
+ public typealias Coordinates = Coordinate2D
+ public typealias X = Coordinates.X
+ public typealias Y = Coordinates.Y
+
+ public var coordinates: Coordinate2D
- public typealias Point = WGS84Coordinate2D
+ public init(_ coordinates: Coordinate2D) {
+ self.coordinates = coordinates
+ }
+
+ }
- public struct Size: GeoModels.Size {
+ public struct Size: GeodeticGeometry.Size {
- public typealias CoordinateSystem = WGS842D
- public typealias RawValue = WGS842D.Point
+ public typealias CRS = WGS84Geographic2DCRS
+ public typealias GeometricSystem = WGS842D
+ public typealias RawValue = GeometricSystem.Point.Coordinates
public let rawValue: Self.RawValue
- public var width: Self.RawValue.X { self.rawValue.x }
- public var height: Self.RawValue.Y { self.rawValue.y }
+ public var dx: Self.RawValue.X { self.rawValue.x }
+ public var dy: Self.RawValue.Y { self.rawValue.y }
public init(rawValue: Self.RawValue) {
self.rawValue = rawValue
}
- public init(width: Self.RawValue.X, height: Self.RawValue.Y) {
- self.init(rawValue: Self.RawValue.init(x: width, y: height))
+ public init(dx: Self.RawValue.X, dy: Self.RawValue.Y) {
+ self.init(rawValue: RawValue(x: dx, y: dy))
}
}
// public struct MultiPoint
- public struct Line: GeoModels.Line, Hashable {
+ public struct Line: GeodeticGeometry.Line, Hashable {
- public typealias CoordinateSystem = WGS842D
+ public typealias CRS = WGS84Geographic2DCRS
+ public typealias GeometricSystem = WGS842D
+ public typealias Point = GeometricSystem.Point
public let start: Self.Point
public let end: Self.Point
@@ -73,11 +91,14 @@ public enum WGS842D: GeoModels.CoordinateSystem {
// public typealias MultiLine = WGS84MultiLine2D
- public struct LineString: GeoModels.LineString, Hashable {
+ public struct LineString: GeodeticGeometry.LineString, Hashable {
- public typealias CoordinateSystem = WGS842D
- public typealias Points = AtLeast2<[CoordinateSystem.Point]>
- public typealias Lines = NonEmpty<[CoordinateSystem.Line]>
+ public typealias CRS = WGS84Geographic2DCRS
+ public typealias GeometricSystem = WGS842D
+ public typealias Point = GeometricSystem.Point
+ public typealias Points = AtLeast2<[Point]>
+ public typealias Line = GeometricSystem.Line
+ public typealias Lines = NonEmpty<[Line]>
public internal(set) var points: Points
@@ -95,26 +116,30 @@ public enum WGS842D: GeoModels.CoordinateSystem {
public struct BoundingBox: Hashable {
+ public typealias GeometricSystem = WGS842D
+ public typealias Point = GeometricSystem.Point
+ public typealias Size = GeometricSystem.Size
+
public var southWest: Self.Point
public var size: Self.Size
- public var width: Longitude { self.size.width }
- public var height: Latitude { self.size.height }
+ public var dLat: Latitude { self.size.dx }
+ public var dLong: Longitude { self.size.dy }
public var southLatitude: Latitude {
southWest.latitude
}
public var northLatitude: Latitude {
- southLatitude + height
+ southLatitude + dLat
}
public var centerLatitude: Latitude {
- southLatitude + (height / 2.0)
+ southLatitude + (dLat / 2.0)
}
public var westLongitude: Longitude {
southWest.longitude
}
public var eastLongitude: Longitude {
- let longitude = westLongitude + width
+ let longitude = westLongitude + dLong
if longitude > .halfRotation {
return longitude - .fullRotation
@@ -123,7 +148,7 @@ public enum WGS842D: GeoModels.CoordinateSystem {
}
}
public var centerLongitude: Longitude {
- let longitude = westLongitude + (width / 2.0)
+ let longitude = westLongitude + (dLong / 2.0)
if longitude > .halfRotation {
return longitude - .fullRotation
@@ -133,16 +158,16 @@ public enum WGS842D: GeoModels.CoordinateSystem {
}
public var northEast: Self.Point {
- Self.Point.init(latitude: northLatitude, longitude: eastLongitude)
+ Self.Point.init(.init(latitude: northLatitude, longitude: eastLongitude))
}
public var northWest: Self.Point {
- Self.Point.init(latitude: northLatitude, longitude: westLongitude)
+ Self.Point.init(.init(latitude: northLatitude, longitude: westLongitude))
}
public var southEast: Self.Point {
- Self.Point.init(latitude: southLatitude, longitude: westLongitude)
+ Self.Point.init(.init(latitude: southLatitude, longitude: westLongitude))
}
public var center: Self.Point {
- Self.Point.init(latitude: centerLatitude, longitude: centerLongitude)
+ Self.Point.init(.init(latitude: centerLatitude, longitude: centerLongitude))
}
public var south: Self.Point {
@@ -169,10 +194,13 @@ public enum WGS842D: GeoModels.CoordinateSystem {
public init(
southWest: Self.Point,
- width: Longitude,
- height: Latitude
+ dLat: Latitude,
+ dLong: Longitude
) {
- self.init(southWest: southWest, size: Self.Size.init(width: width, height: height))
+ self.init(
+ southWest: southWest,
+ size: Self.Size(dx: dLat, dy: dLong)
+ )
}
public init(
@@ -181,36 +209,36 @@ public enum WGS842D: GeoModels.CoordinateSystem {
) {
self.init(
southWest: southWest,
- width: northEast.longitude - southWest.longitude,
- height: northEast.latitude - southWest.latitude
+ dLat: northEast.latitude - southWest.latitude,
+ dLong: northEast.longitude - southWest.longitude
)
}
public func southAtLongitude(_ longitude: Longitude) -> Self.Point {
- Self.Point.init(latitude: northEast.latitude, longitude: longitude)
+ Self.Point.init(.init(latitude: northEast.latitude, longitude: longitude))
}
public func northAtLongitude(_ longitude: Longitude) -> Self.Point {
- Self.Point.init(latitude: southWest.latitude, longitude: longitude)
+ Self.Point.init(.init(latitude: southWest.latitude, longitude: longitude))
}
public func westAtLatitude(_ latitude: Latitude) -> Self.Point {
- Self.Point.init(latitude: latitude, longitude: southWest.longitude)
+ Self.Point.init(.init(latitude: latitude, longitude: southWest.longitude))
}
public func eastAtLatitude(_ latitude: Latitude) -> Self.Point {
- Self.Point.init(latitude: latitude, longitude: northEast.longitude)
+ Self.Point.init(.init(latitude: latitude, longitude: northEast.longitude))
}
public func offsetBy(dLat: Latitude = .zero, dLong: Longitude = .zero) -> Self {
Self.init(
southWest: southWest.offsetBy(dLat: dLat, dLong: dLong),
- width: width,
- height: height
+ dLat: self.dLat,
+ dLong: self.dLong
)
}
- public func offsetBy(dx: Self.Point.X = .zero, dy: Self.Point.Y = .zero) -> Self {
+ public func offsetBy(dx: Point.X = .zero, dy: Point.Y = .zero) -> Self {
Self.init(
southWest: southWest.offsetBy(dx: dx, dy: dy),
- width: self.width,
- height: self.height
+ dLat: self.dLat,
+ dLong: self.dLong
)
}
@@ -218,9 +246,7 @@ public enum WGS842D: GeoModels.CoordinateSystem {
}
-extension WGS842D.BoundingBox: GeoModels.BoundingBox {
-
- public typealias CoordinateSystem = WGS842D
+extension WGS842D.BoundingBox: GeodeticGeometry.BoundingBox {
public var origin: Self.Point { self.southWest }
@@ -232,14 +258,14 @@ extension WGS842D.BoundingBox: GeoModels.BoundingBox {
public func union(_ other: Self) -> Self {
// FIXME: Use width and height, because `eastLongitude` can cross the antimeridian
Self.init(
- southWest: Self.Point(
+ southWest: Self.Point(.init(
latitude: min(self.southLatitude, other.southLatitude),
longitude: min(self.westLongitude, other.westLongitude)
- ),
- northEast: Self.Point(
+ )),
+ northEast: Self.Point(.init(
latitude: max(self.northLatitude, other.northLatitude),
longitude: max(self.eastLongitude, other.eastLongitude)
- )
+ ))
)
}
diff --git a/Sources/WGS84/WGS843D.swift b/Sources/WGS84/WGS843D.swift
new file mode 100644
index 0000000..8c09cc9
--- /dev/null
+++ b/Sources/WGS84/WGS843D.swift
@@ -0,0 +1,246 @@
+////
+//// WGS843D.swift
+//// SwiftGeo
+////
+//// Created by Rémi Bardon on 20/03/2022.
+//// Copyright © 2022 Rémi Bardon. All rights reserved.
+////
+//
+//import GeodeticGeometry
+//import NonEmpty
+//
+//public enum WGS843D: GeometricSystem {
+//
+// public typealias Point = Coordinate3D
+//
+// public struct Size: GeodeticGeometry.Size {
+//
+// public typealias CoordinateSystem = WGS843D
+// public typealias RawValue = WGS843D.Point
+//
+// public let rawValue: Self.RawValue
+//
+// public var width: Self.RawValue.X { self.rawValue.x }
+// public var height: Self.RawValue.Y { self.rawValue.y }
+// public var zHeight: Self.RawValue.Z { self.rawValue.z }
+//
+// public init(rawValue: Self.RawValue) {
+// self.rawValue = rawValue
+// }
+//
+// public init(width: Self.RawValue.X, height: Self.RawValue.Y, zHeight: Self.RawValue.Z) {
+// self.init(rawValue: Self.RawValue.init(x: width, y: height, z: zHeight))
+// }
+//
+// }
+//
+//// public struct MultiPoint
+//
+// public struct Line: GeodeticGeometry.Line, Hashable {
+//
+// public typealias CoordinateSystem = WGS843D
+// public typealias Point = WGS843D.Point
+//
+// public let start: Point
+// public let end: Point
+//
+// public var altitudeDelta: Altitude {
+// end.altitude - start.altitude
+// }
+//
+// public init(start: Point, end: Point) {
+// self.start = start
+// self.end = end
+// }
+//
+// }
+//
+//// public struct MultiLine
+//
+// public struct LineString: GeodeticGeometry.LineString, Hashable {
+//
+// public typealias CoordinateSystem = WGS843D
+// public typealias Points = AtLeast2<[CoordinateSystem.Point]>
+// public typealias Lines = NonEmpty<[CoordinateSystem.Line]>
+//
+// public internal(set) var points: Points
+//
+// public init(points: Points) {
+// self.points = points
+// }
+//
+// public mutating func append(_ point: Self.Point) {
+// self.points.append(point)
+// }
+//
+// }
+//
+//// public struct LinearRing
+//
+// public struct BoundingBox: Hashable {
+//
+// public var twoDimensions: LowerDimension
+// public var lowAltitude: Altitude
+// public var zHeight: Altitude
+//
+// public var highAltitude: Altitude {
+// lowAltitude + zHeight
+// }
+// public var centerAltitude: Altitude {
+// lowAltitude + (zHeight / 2.0)
+// }
+//
+// public var southWestLow: Self.Point {
+// Self.Point.init(twoDimensions.southWest, altitude: lowAltitude)
+// }
+// public var northEastHigh: Self.Point {
+// Self.Point.init(twoDimensions.northEast, altitude: highAltitude)
+// }
+// public var center: Self.Point {
+// Self.Point.init(twoDimensions.center, altitude: centerAltitude)
+// }
+//
+// public var crosses180thMeridian: Bool {
+// twoDimensions.crosses180thMeridian
+// }
+//
+// public init(_ boundingBox2d: LowerDimension, lowAltitude: Altitude, zHeight: Altitude) {
+// self.twoDimensions = boundingBox2d
+// self.lowAltitude = lowAltitude
+// self.zHeight = zHeight
+// }
+//
+// public init(
+// southWestLow: Self.Point,
+// width: Longitude,
+// height: Latitude,
+// zHeight: Altitude
+// ) {
+// self.init(
+// Self.LowerDimension(
+// southWest: southWestLow.lowerDimension,
+// width: width,
+// height: height
+// ),
+// lowAltitude: southWestLow.altitude,
+// zHeight: zHeight
+// )
+// }
+//
+// public init(
+// southWestLow: Self.Point,
+// northEastHigh: Self.Point
+// ) {
+// self.init(
+// southWestLow: southWestLow,
+// width: northEastHigh.longitude - southWestLow.longitude,
+// height: northEastHigh.latitude - southWestLow.latitude,
+// zHeight: northEastHigh.altitude - southWestLow.altitude
+// )
+// }
+//
+// public func offsetBy(
+// dLat: Latitude = .zero,
+// dLong: Longitude = .zero,
+// dAlt: Altitude = .zero
+// ) -> Self {
+// Self.init(
+// twoDimensions.offsetBy(dLat: dLat, dLong: dLong),
+// lowAltitude: lowAltitude + dAlt,
+// zHeight: zHeight
+// )
+// }
+// public func offsetBy(
+// dx: Self.Point.X = .zero,
+// dy: Self.Point.Y = .zero,
+// dz: Self.Point.Z = .zero
+// ) -> Self {
+// Self.init(
+// twoDimensions.offsetBy(dx: dx, dy: dy),
+// lowAltitude: lowAltitude + dz,
+// zHeight: zHeight
+// )
+// }
+//
+// }
+//
+//}
+//
+//extension WGS843D.Size: GeodeticGeometry.CompoundDimension {
+//
+// public typealias LowerDimension = WGS842D.Size
+//
+// public var lowerDimension: Self.LowerDimension {
+// LowerDimension(rawValue: self.rawValue.lowerDimension)
+// }
+//
+// public init(_ twoDimensions: Self.LowerDimension, zHeight: Self.RawValue.Z) {
+// self.init(rawValue: Self.RawValue.init(
+// x: twoDimensions.width,
+// y: twoDimensions.height,
+// z: zHeight
+// ))
+// }
+//
+//}
+//
+//extension WGS843D.Line: GeoCoordinates.CompoundDimension {
+//
+// public typealias LowerDimension = WGS842D.Line
+//
+// public var lowerDimension: LowerDimension {
+// Self.LowerDimension(start: self.start.lowerDimension, end: self.end.lowerDimension)
+// }
+//
+//}
+//
+//extension WGS843D.BoundingBox: GeoModels.BoundingBox {
+//
+// public typealias CoordinateSystem = WGS843D
+//
+// public var origin: Self.Point { self.southWestLow }
+// public var size: Self.Size { Self.Size(self.twoDimensions.size, zHeight: self.zHeight) }
+//
+// public init(origin: Self.Point, size: Self.Size) {
+// self.init(
+// southWestLow: origin,
+// width: size.width,
+// height: size.height,
+// zHeight: size.zHeight
+// )
+// }
+//
+// /// The union of bounding boxes gives a new bounding box that encloses the given two.
+// public func union(_ other: Self) -> Self {
+// // FIXME: Use width, height and zHeight, because `eastLongitude` can cross the antimeridian
+// Self.init(
+// southWestLow: Self.Point(
+// self.twoDimensions.union(other.twoDimensions).southWest,
+// altitude: min(self.lowAltitude, other.lowAltitude)
+// ),
+// northEastHigh: Self.Point(
+// self.twoDimensions.union(other.twoDimensions).northEast,
+// altitude: max(self.highAltitude, other.highAltitude)
+// )
+// )
+// }
+//
+//}
+//
+//extension WGS843D.BoundingBox: GeoCoordinates.CompoundDimension {
+//
+// public typealias LowerDimension = WGS842D.BoundingBox
+//
+// public var lowerDimension: LowerDimension {
+// self.twoDimensions
+// }
+//
+//}
+//
+//extension WGS843D.BoundingBox: CustomDebugStringConvertible {
+//
+// public var debugDescription: String {
+// "BBox3D(southWestLow: \(String(reflecting: self.southWestLow)), northEastHigh: \(String(reflecting: self.northEastHigh)))"
+// }
+//
+//}
diff --git a/Tests/GeoModelsTests/LineTests.swift b/Tests/GeoModelsTests/LineTests.swift
deleted file mode 100644
index c7f8d16..0000000
--- a/Tests/GeoModelsTests/LineTests.swift
+++ /dev/null
@@ -1,114 +0,0 @@
-//
-// LineTests.swift
-// SwiftGeo
-//
-// Created by Rémi Bardon on 01/02/2022.
-// Copyright © 2022 Rémi Bardon. All rights reserved.
-//
-
-import GeoModels
-import XCTest
-
-final class LineTests: XCTestCase {
-
- typealias Coordinate2D = WGS842D.Point
- typealias Line2D = WGS842D.Line
-
- func testLine2DCrosses180thMeridian() throws {
- self.continueAfterFailure = true
-
- func test(from longitude1: Longitude, to longitude2: Longitude, crosses: Bool) {
- let start = Coordinate2D(latitude: 0, longitude: longitude1)
- let end = Coordinate2D(latitude: 0, longitude: longitude2)
- let line = Line2D(start: start, end: end)
-
- XCTAssertEqual(
- line.crosses180thMeridian,
- crosses,
- "From \(longitude1) to \(longitude2): longitudeDelta=\(line.longitudeDelta), minimalLongitudeDelta=\(line.minimalLongitudeDelta)"
- )
- }
-
- // Exactly 0° length
- test(from: -180, to: -180, crosses: false)
- test(from: -90, to: -90, crosses: false)
- test(from: 0, to: 0, crosses: false)
- test(from: 90, to: 90, crosses: false)
- test(from: 180, to: 180, crosses: false)
-
- // Exactly 180° length
- test(from: 0, to: 180, crosses: false)
- test(from: -90, to: 90, crosses: false)
- test(from: -180, to: 0, crosses: false)
-
- throw XCTSkip("FIXME")
-
- // A bit more than 180° length
- test(from: -1, to: 180, crosses: true)
- test(from: -91, to: 90, crosses: true)
- test(from: -180, to: 1, crosses: true)
-
- // Almost 360° length
- test(from: -179, to: 180, crosses: false)
- test(from: -180, to: 179, crosses: false)
- test(from: 179, to: -180, crosses: true)
- test(from: 180, to: -179, crosses: true)
-
- // Exactly 360° length
- test(from: -180, to: 180, crosses: false)
- test(from: 180, to: -180, crosses: true)
- }
-
- func testLine2DMinimalLongitudeDelta() {
- self.continueAfterFailure = true
-
- func test(from longitude1: Longitude, to longitude2: Longitude, equals minimalDelta: Longitude) {
- let start = Coordinate2D(latitude: 0, longitude: longitude1)
- let end = Coordinate2D(latitude: 0, longitude: longitude2)
- let line = Line2D(start: start, end: end)
-
- XCTAssertEqual(
- line.minimalLongitudeDelta,
- minimalDelta,
- "From \(longitude1) to \(longitude2): longitudeDelta=\(line.longitudeDelta), minimalLongitudeDelta=\(line.minimalLongitudeDelta)"
- )
- }
-
- func symetricTest(between longitude1: Longitude, and longitude2: Longitude, equals minimalDelta: Longitude) {
- test(from: longitude1, to: longitude2, equals: minimalDelta)
- test(from: longitude2, to: longitude1, equals: minimalDelta)
- }
-
- // Exactly 0° length
- symetricTest(between: -180, and: -180, equals: 0)
- symetricTest(between: -90, and: -90, equals: 0)
- symetricTest(between: 0, and: 0, equals: 0)
-
- // Exactly 180° length
- symetricTest(between: 0, and: 180, equals: 180)
- symetricTest(between: -90, and: 90, equals: 180)
- symetricTest(between: -180, and: 0, equals: 180)
-
- // A bit more than 180° length
- test(from: -1, to: 180, equals: -179)
- test(from: -91, to: 90, equals: -179)
- test(from: -180, to: 1, equals: -179)
- test(from: 1, to: -180, equals: 179)
- test(from: 91, to: -90, equals: 179)
- test(from: 180, to: -1, equals: 179)
-
- // Almost 360° length
- test(from: -179, to: 180, equals: -1)
- test(from: -180, to: 179, equals: -1)
- test(from: 179, to: -180, equals: 1)
- test(from: 180, to: -179, equals: 1)
-
- // Exactly 360° length
- symetricTest(between: -180, and: 180, equals: 0)
- }
-
- func testLine2DNonEmpty() {
- XCTAssertEqual(Line2D(start: .zero, end: .zero).points.count, 2)
- }
-
-}
diff --git a/Tests/GeodeticDisplayTests/GeodeticDisplayTests.swift b/Tests/GeodeticDisplayTests/GeodeticDisplayTests.swift
new file mode 100644
index 0000000..a3ce941
--- /dev/null
+++ b/Tests/GeodeticDisplayTests/GeodeticDisplayTests.swift
@@ -0,0 +1,26 @@
+//
+// GeodeticDisplayTests.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 17/10/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import WGS84
+import XCTest
+
+final class GeodeticDisplayTests: XCTestCase {
+
+ func testDescriptions() {
+ let coord = Coordinate2D(x: 1.1232456789, y: -1.1232456789)
+ XCTAssertEqual(String(describing: coord), "(1.1232456789, -1.1232456789)")
+ XCTAssertEqual(
+ String(reflecting: coord),
+ "[WGS 84 (geographic 2D)](1.1232456789, -1.1232456789)"
+ )
+ XCTAssertEqual(coord.ddNotation, "1.123246, -1.123246")
+ XCTAssertEqual(coord.dmNotation, "1° 7.395' N, 1° 7.395' W")
+ XCTAssertEqual(coord.dmsNotation, #"1° 7' 23.6844" N, 1° 7' 23.6844" W"#)
+ }
+
+}
diff --git a/Tests/GeodeticGeometryTests/LineTests.swift b/Tests/GeodeticGeometryTests/LineTests.swift
new file mode 100644
index 0000000..2b7bb18
--- /dev/null
+++ b/Tests/GeodeticGeometryTests/LineTests.swift
@@ -0,0 +1,115 @@
+//
+// LineTests.swift
+// SwiftGeo
+//
+// Created by Rémi Bardon on 01/02/2022.
+// Copyright © 2022 Rémi Bardon. All rights reserved.
+//
+
+import GeodeticGeometry
+import WGS84
+import XCTest
+
+//final class LineTests: XCTestCase {
+//
+// typealias Coordinate2D = WGS842D.Point
+// typealias Line2D = WGS842D.Line
+//
+// func testLine2DCrosses180thMeridian() throws {
+// self.continueAfterFailure = true
+//
+// func test(from longitude1: Longitude, to longitude2: Longitude, crosses: Bool) {
+// let start = Coordinate2D(latitude: 0, longitude: longitude1)
+// let end = Coordinate2D(latitude: 0, longitude: longitude2)
+// let line = Line2D(start: start, end: end)
+//
+// XCTAssertEqual(
+// line.crosses180thMeridian,
+// crosses,
+// "From \(longitude1) to \(longitude2): longitudeDelta=\(line.longitudeDelta), minimalLongitudeDelta=\(line.minimalLongitudeDelta)"
+// )
+// }
+//
+// // Exactly 0° length
+// test(from: -180, to: -180, crosses: false)
+// test(from: -90, to: -90, crosses: false)
+// test(from: 0, to: 0, crosses: false)
+// test(from: 90, to: 90, crosses: false)
+// test(from: 180, to: 180, crosses: false)
+//
+// // Exactly 180° length
+// test(from: 0, to: 180, crosses: false)
+// test(from: -90, to: 90, crosses: false)
+// test(from: -180, to: 0, crosses: false)
+//
+// throw XCTSkip("FIXME")
+//
+// // A bit more than 180° length
+// test(from: -1, to: 180, crosses: true)
+// test(from: -91, to: 90, crosses: true)
+// test(from: -180, to: 1, crosses: true)
+//
+// // Almost 360° length
+// test(from: -179, to: 180, crosses: false)
+// test(from: -180, to: 179, crosses: false)
+// test(from: 179, to: -180, crosses: true)
+// test(from: 180, to: -179, crosses: true)
+//
+// // Exactly 360° length
+// test(from: -180, to: 180, crosses: false)
+// test(from: 180, to: -180, crosses: true)
+// }
+//
+// func testLine2DMinimalLongitudeDelta() {
+// self.continueAfterFailure = true
+//
+// func test(from longitude1: Longitude, to longitude2: Longitude, equals minimalDelta: Longitude) {
+// let start = Coordinate2D(latitude: 0, longitude: longitude1)
+// let end = Coordinate2D(latitude: 0, longitude: longitude2)
+// let line = Line2D(start: start, end: end)
+//
+// XCTAssertEqual(
+// line.minimalLongitudeDelta,
+// minimalDelta,
+// "From \(longitude1) to \(longitude2): longitudeDelta=\(line.longitudeDelta), minimalLongitudeDelta=\(line.minimalLongitudeDelta)"
+// )
+// }
+//
+// func symetricTest(between longitude1: Longitude, and longitude2: Longitude, equals minimalDelta: Longitude) {
+// test(from: longitude1, to: longitude2, equals: minimalDelta)
+// test(from: longitude2, to: longitude1, equals: minimalDelta)
+// }
+//
+// // Exactly 0° length
+// symetricTest(between: -180, and: -180, equals: 0)
+// symetricTest(between: -90, and: -90, equals: 0)
+// symetricTest(between: 0, and: 0, equals: 0)
+//
+// // Exactly 180° length
+// symetricTest(between: 0, and: 180, equals: 180)
+// symetricTest(between: -90, and: 90, equals: 180)
+// symetricTest(between: -180, and: 0, equals: 180)
+//
+// // A bit more than 180° length
+// test(from: -1, to: 180, equals: -179)
+// test(from: -91, to: 90, equals: -179)
+// test(from: -180, to: 1, equals: -179)
+// test(from: 1, to: -180, equals: 179)
+// test(from: 91, to: -90, equals: 179)
+// test(from: 180, to: -1, equals: 179)
+//
+// // Almost 360° length
+// test(from: -179, to: 180, equals: -1)
+// test(from: -180, to: 179, equals: -1)
+// test(from: 179, to: -180, equals: 1)
+// test(from: 180, to: -179, equals: 1)
+//
+// // Exactly 360° length
+// symetricTest(between: -180, and: 180, equals: 0)
+// }
+//
+// func testLine2DNonEmpty() {
+// XCTAssertEqual(Line2D(start: .zero, end: .zero).points.count, 2)
+// }
+//
+//}