Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Outdooractive/gis-tools
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.8.3
Choose a base ref
...
head repository: Outdooractive/gis-tools
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 15 commits
  • 45 files changed
  • 2 contributors

Commits on Sep 9, 2024

  1. Copy the full SHA
    96aeecd View commit details

Commits on Jan 10, 2025

  1. #65: Port turf-rewind (#66)

    trasch authored Jan 10, 2025
    Copy the full SHA
    a84053b View commit details

Commits on Feb 20, 2025

  1. Copy the full SHA
    09127c8 View commit details

Commits on Mar 4, 2025

  1. Copy the full SHA
    c8f4f50 View commit details
  2. Copy the full SHA
    906506e View commit details

Commits on Mar 7, 2025

  1. Be exact. Very exact.

    trasch committed Mar 7, 2025
    Copy the full SHA
    c653afc View commit details

Commits on Mar 10, 2025

  1. Copy the full SHA
    cbbb513 View commit details

Commits on Mar 13, 2025

  1. Copy the full SHA
    5a156d7 View commit details
  2. Copy the full SHA
    ad63c8a View commit details

Commits on Mar 18, 2025

  1. Copy the full SHA
    fc20eea View commit details

Commits on Mar 24, 2025

  1. Copy the full SHA
    b40fd9b View commit details

Commits on Apr 9, 2025

  1. Conversions, renamings and chunks (#70)

    Renamed:
    
    - `GISTool.convertToCoordinate(...)` to `GISTool.coordinate(...)`
    - `GISTool.convertToDegrees(...)` to `GISTool.degrees(...)`
    
    Added:
    
    - `Coordinate3D.degrees(fromMeters:)`
    - `Coordinate3D.equals(other:includingAltitude:equalityDelta:altitudeDelta:)`
    
    Removed:
    
    - `MapTile.pixelCoordinate(...)` (is now `GISTool.coordinate(...)`)
    
    Other:
    
    - Addad an index to `LineSegment`
    trasch authored Apr 9, 2025
    Copy the full SHA
    5ce6e84 View commit details

Commits on Apr 17, 2025

  1. Added overlappingSegments(tolerance:) and estimatedOverlap(tolerance:…

    …) for self-overlapping GeoJSONs (#71)
    trasch authored Apr 17, 2025
    Copy the full SHA
    6201957 View commit details

Commits on Apr 22, 2025

  1. Copy the full SHA
    fdc9456 View commit details

Commits on Apr 23, 2025

  1. Copy the full SHA
    c1321ed View commit details
Showing with 1,541 additions and 228 deletions.
  1. +1 −0 Package.swift
  2. +9 −5 Sources/GISTools/Algorithms/BooleanPointInPolygon.swift
  3. +83 −0 Sources/GISTools/Algorithms/BoundingBoxPosition.swift
  4. +5 −1 Sources/GISTools/Algorithms/Center.swift
  5. +52 −13 Sources/GISTools/Algorithms/Conversions.swift
  6. +1 −1 Sources/GISTools/Algorithms/FrechetDistance.swift
  7. +59 −19 Sources/GISTools/Algorithms/LineChunk.swift
  8. +1 −1 Sources/GISTools/Algorithms/LineIntersect.swift
  9. +154 −1 Sources/GISTools/Algorithms/LineOverlap.swift
  10. +8 −8 Sources/GISTools/Algorithms/LineSegments.swift
  11. +4 −1 Sources/GISTools/Algorithms/Reverse.swift
  12. +110 −0 Sources/GISTools/Algorithms/Rewind.swift
  13. +1 −1 Sources/GISTools/Algorithms/TransformScale.swift
  14. +10 −3 Sources/GISTools/Extensions/ArrayExtensions.swift
  15. +49 −0 Sources/GISTools/Extensions/DoubleExtensions.swift
  16. +19 −0 Sources/GISTools/Extensions/EquatableExtensions.swift
  17. +52 −0 Sources/GISTools/Extensions/IntExtensions.swift
  18. +5 −0 Sources/GISTools/Extensions/SetExtensions.swift
  19. +1 −1 Sources/GISTools/GeoJson/BoundingBox.swift
  20. +1 −1 Sources/GISTools/GeoJson/BoundingBoxRepresentable.swift
  21. +68 −14 Sources/GISTools/GeoJson/Coordinate3D.swift
  22. +21 −11 Sources/GISTools/GeoJson/Feature.swift
  23. +29 −13 Sources/GISTools/GeoJson/FeatureCollection.swift
  24. +2 −2 Sources/GISTools/GeoJson/GeoJson.swift
  25. +3 −3 Sources/GISTools/GeoJson/GeoJsonConvertible.swift
  26. +88 −7 Sources/GISTools/GeoJson/GeometryCollection.swift
  27. +21 −7 Sources/GISTools/GeoJson/LineSegment.swift
  28. +15 −13 Sources/GISTools/GeoJson/LineString.swift
  29. +101 −16 Sources/GISTools/GeoJson/MultiLineString.swift
  30. +99 −14 Sources/GISTools/GeoJson/MultiPoint.swift
  31. +99 −14 Sources/GISTools/GeoJson/MultiPolygon.swift
  32. +9 −9 Sources/GISTools/GeoJson/Point.swift
  33. +9 −9 Sources/GISTools/GeoJson/Polygon.swift
  34. +50 −17 Sources/GISTools/Other/MapTile.swift
  35. +1 −1 Tests/GISToolsTests/Algorithms/ConversionTests.swift
  36. +5 −1 Tests/GISToolsTests/Algorithms/LengthTests.swift
  37. +15 −7 Tests/GISToolsTests/Algorithms/LineChunkTests.swift
  38. +12 −6 Tests/GISToolsTests/Algorithms/LineIntersectionTests.swift
  39. +9 −4 Tests/GISToolsTests/Algorithms/LineSegmentsTests.swift
  40. +106 −0 Tests/GISToolsTests/Algorithms/RewindTests.swift
  41. +35 −0 Tests/GISToolsTests/Extensions/DoubleExtensionsTests.swift
  42. +41 −0 Tests/GISToolsTests/Extensions/IntExtensionsTests.swift
  43. +65 −1 Tests/GISToolsTests/GeoJson/CoordinateTests.swift
  44. +6 −3 Tests/GISToolsTests/GeoJson/LineStringTests.swift
  45. +7 −0 Tests/GISToolsTests/Other/MapTileTests.swift
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import PackageDescription
let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("StrictConcurrency"), // 5.10
.enableUpcomingFeature("StrictConcurrency"), // 6.0
.enableUpcomingFeature("InferSendableFromCaptures"), // Silences a Sendable warning
]

let package = Package(
14 changes: 9 additions & 5 deletions Sources/GISTools/Algorithms/BooleanPointInPolygon.swift
Original file line number Diff line number Diff line change
@@ -88,15 +88,15 @@ extension Polygon {
ignoreBoundary: Bool = false)
-> Bool
{
if let boundingBox = boundingBox, !boundingBox.contains(coordinate) {
if let boundingBox, !boundingBox.contains(coordinate) {
return false
}

guard let outerRing = outerRing, outerRing.contains(coordinate, ignoreBoundary: ignoreBoundary) else {
return false
}
guard let outerRing = outerRing,
outerRing.contains(coordinate, ignoreBoundary: ignoreBoundary)
else { return false }

if let innerRings = innerRings {
if let innerRings {
for ring in innerRings {
if ring.contains(coordinate, ignoreBoundary: ignoreBoundary) {
return false
@@ -140,6 +140,10 @@ extension MultiPolygon {
ignoreBoundary: Bool = false)
-> Bool
{
if let boundingBox, !boundingBox.contains(coordinate) {
return false
}

for polygon in polygons {
if polygon.contains(coordinate, ignoreBoundary: ignoreBoundary) {
return true
83 changes: 83 additions & 0 deletions Sources/GISTools/Algorithms/BoundingBoxPosition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#if !os(Linux)
import CoreLocation
#endif
import Foundation

extension BoundingBox {

/// Position of a coordinate in relation to a bounding box.
public struct CoordinatePosition: OptionSet, Sendable {
public let rawValue: Int

public init(rawValue: Int) {
self.rawValue = rawValue
}

public static let center = CoordinatePosition(rawValue: 1 << 0)
public static let top = CoordinatePosition(rawValue: 1 << 1)
public static let right = CoordinatePosition(rawValue: 1 << 2)
public static let bottom = CoordinatePosition(rawValue: 1 << 3)
public static let left = CoordinatePosition(rawValue: 1 << 4)
public static let outside = CoordinatePosition(rawValue: 1 << 5)
}

/// Returns the relative position of a coordinate with regards to the bounding box.
public func position(of coordinate: Coordinate3D) -> CoordinatePosition {
var position: CoordinatePosition = []

if !contains(coordinate) {
position.insert(.outside)
}

let latitudeSpan = northEast.latitude - southWest.latitude
let longitudeSpan = northEast.longitude - southWest.longitude

let topCutoff = southWest.latitude + (latitudeSpan * 0.65)
if coordinate.latitude > topCutoff {
position.insert(.top)
}

let rightCutoff = southWest.longitude + (longitudeSpan * 0.65)
if coordinate.longitude > rightCutoff {
position.insert(.right)
}

let bottomCutoff = southWest.latitude + (latitudeSpan * 0.35)
if coordinate.latitude < bottomCutoff {
position.insert(.bottom)
}

let leftCutoff = southWest.longitude + (longitudeSpan * 0.35)
if coordinate.longitude < leftCutoff {
position.insert(.left)
}

if position.isEmpty {
position.insert(.center)
}

return position
}

/// Returns the relative position of a point with regards to the bounding box.
public func postion(of point: Point) -> CoordinatePosition {
position(of: point.coordinate)
}

// MARK: - CoreLocation compatibility

#if !os(Linux)

/// Returns the relative position of a coordinate with regards to the bounding box.
public func postion(of coordinate: CLLocationCoordinate2D) -> CoordinatePosition {
position(of: Coordinate3D(coordinate))
}

/// Returns the relative position of a location with regards to the bounding box.
public func postion(of coordinate: CLLocation) -> CoordinatePosition {
position(of: Coordinate3D(coordinate))
}

#endif

}
6 changes: 5 additions & 1 deletion Sources/GISTools/Algorithms/Center.swift
Original file line number Diff line number Diff line change
@@ -24,7 +24,11 @@ extension GeoJson {
public var centroid: Point? {
let allCoordinates = self.allCoordinates

guard !allCoordinates.isEmpty else { return nil }
guard allCoordinates.isNotEmpty else { return nil }

if allCoordinates.count == 1 {
return Point(allCoordinates[0])
}

var sumLongitude: Double = 0.0
var sumLatitude: Double = 0.0
65 changes: 52 additions & 13 deletions Sources/GISTools/Algorithms/Conversions.swift
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import Foundation
extension GISTool {

/// Unit of measurement.
public enum Unit {
public enum Unit: Sendable {
case acres
case centimeters
case centimetres
@@ -43,7 +43,7 @@ extension GISTool {
case .millimeters, .millimetres: return earthRadius * 1000.0
case .nauticalmiles: return earthRadius / 1852.0
case .radians: return 1.0
case .yards: return earthRadius / 1.0936
case .yards: return earthRadius / (1.0 / 1.0936)
default: return nil
}
}
@@ -70,28 +70,36 @@ extension GISTool {
public static func areaFactor(for unit: Unit) -> Double? {
switch unit {
case .acres: return 0.000247105
case .centimeters, .centimetres: return 10000.0
case .centimeters, .centimetres: return 10_000.0
case .feet: return 10.763910417
case .inches: return 1550.003100006
case .kilometers, .kilometres: return 0.000001
case .meters, .metres: return 1.0
case .miles: return 3.86e-7
case .millimeters, .millimetres: return 1_000_000
case .millimeters, .millimetres: return 1_000_000.0
case .yards: return 1.195990046
default: return nil
}
}

/// Converts a length to the requested unit.
/// Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
public static func convert(length: Double, from originalUnit: Unit, to finalUnit: Unit) -> Double? {
public static func convert(
length: Double,
from originalUnit: Unit,
to finalUnit: Unit
) -> Double? {
guard length >= 0 else { return nil }
return length.lengthToRadians(unit: originalUnit)?.radiansToLength(unit: finalUnit)
}

/// Converts a area to the requested unit.
/// Valid units: kilometers, kilometres, meters, metres, centimetres, millimeters, acres, miles, yards, feet, inches
public static func convert(area: Double, from originalUnit: Unit, to finalUnit: Unit) -> Double? {
public static func convert(
area: Double,
from originalUnit: Unit,
to finalUnit: Unit
) -> Double? {
guard area >= 0,
let startFactor = areaFactor(for: originalUnit),
let finalFactor = areaFactor(for: finalUnit)
@@ -106,15 +114,29 @@ extension GISTool {

extension GISTool {

/// Converts pixel coordinates in a given zoom level to a coordinate.
@available(*, deprecated, renamed: "coordinate(fromPixelX:pixelY:zoom:tileSideLength:projection:)")
public static func convertToCoordinate(
fromPixelX pixelX: Double,
pixelY: Double,
atZoom zoom: Int,
tileSideLength: Double = GISTool.tileSideLength,
projection: Projection = .epsg4326)
-> Coordinate3D
{
projection: Projection = .epsg4326
) -> Coordinate3D {
coordinate(fromPixelX: pixelX,
pixelY: pixelY,
zoom: zoom,
tileSideLength: tileSideLength,
projection: projection)
}

/// Converts pixel coordinates in a given zoom level to a coordinate.
public static func coordinate(
fromPixelX pixelX: Double,
pixelY: Double,
zoom: Int,
tileSideLength: Double = GISTool.tileSideLength,
projection: Projection = .epsg4326
) -> Coordinate3D {
let resolution = metersPerPixel(atZoom: zoom, tileSideLength: tileSideLength)

let coordinateXY = Coordinate3D(
@@ -144,11 +166,18 @@ extension GISTool {

extension GISTool {

@available(*, deprecated, renamed: "degrees(fromMeters:atLatitude:)")
public static func convertToDegrees(
fromMeters meters: Double,
atLatitude latitude: CLLocationDegrees)
-> (latitudeDegrees: CLLocationDegrees, longitudeDegrees: CLLocationDegrees)
{
atLatitude latitude: CLLocationDegrees
) -> (latitudeDegrees: CLLocationDegrees, longitudeDegrees: CLLocationDegrees) {
degrees(fromMeters: meters, atLatitude: latitude)
}

public static func degrees(
fromMeters meters: CLLocationDistance,
atLatitude latitude: CLLocationDegrees
) -> (latitudeDegrees: CLLocationDegrees, longitudeDegrees: CLLocationDegrees) {
// Length of one minute at this latitude
let oneDegreeLatitudeDistance: CLLocationDistance = GISTool.earthCircumference / 360.0 // ~111 km
let oneDegreeLongitudeDistance: CLLocationDistance = cos(latitude * Double.pi / 180.0) * oneDegreeLatitudeDistance
@@ -160,3 +189,13 @@ extension GISTool {
}

}

extension Coordinate3D {

public func degrees(
fromMeters meters: CLLocationDistance
) -> (latitudeDegrees: CLLocationDegrees, longitudeDegrees: CLLocationDegrees) {
GISTool.degrees(fromMeters: meters, atLatitude: latitude)
}

}
2 changes: 1 addition & 1 deletion Sources/GISTools/Algorithms/FrechetDistance.swift
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ extension LineString {
/// - Parameters:
/// - from: The other geometry of equal type.
/// - distanceFunction: The algorithm to use for distance calculations.
/// - segmentLength: Adds coordinates to the lines for improved matching (in meters).
/// - segmentLength: This value adds intermediate points to the geometry for improved matching, in meters.
///
/// - Returns: The frechet distance between the two geometries.
public func frechetDistance(
Loading