Skip to content

Commit ae3bd0f

Browse files
Merge pull request #5 from flitsmeister/feature/linux
Add Linux support
2 parents 1345908 + 33e85b5 commit ae3bd0f

File tree

8 files changed

+101
-34
lines changed

8 files changed

+101
-34
lines changed

.github/workflows/swift.yml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,30 @@ on:
1111

1212
jobs:
1313
build:
14-
15-
runs-on: macos-latest
14+
name: Build and test on ${{ matrix.name }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
include:
19+
- name: Ubuntu
20+
os: ubuntu-latest
21+
swift-version: "6.0"
22+
- name: macOS
23+
os: macos-latest
24+
swift-version: ""
25+
runs-on: ${{ matrix.os }}
1626

1727
steps:
1828
- uses: actions/checkout@v4
29+
- uses: swift-actions/setup-swift@v2
30+
if: matrix.swift-version != ''
31+
with:
32+
swift-version: ${{ matrix.swift-version }}
33+
- name: Show toolchain
34+
run: swift --version
35+
- name: Show Xcode version
36+
if: runner.os == 'macOS'
37+
run: xcodebuild -version
1938
- name: Build
2039
run: swift build -v
2140
- name: Run tests

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# FlitsGeohash
22

3-
`FlitsGeohash` is a Swift package for working with geohashes on Apple platforms. It wraps a small C implementation with a Swift-friendly API for encoding coordinates, finding adjacent cells, collecting neighbors, and generating the geohashes that cover a region.
3+
`FlitsGeohash` is a Swift package for working with geohashes on Apple platforms and Linux. It wraps a small C implementation with a Swift-friendly API for encoding coordinates, finding adjacent cells, collecting neighbors, and generating the geohashes that cover a region.
44

55
## Features
66

@@ -9,12 +9,15 @@
99
- Fetch all 8 neighboring geohashes
1010
- Generate the geohashes that cover a map region
1111
- Use strongly typed fixed-length geohashes for common lengths
12+
- Use the same coordinate API on Linux without depending on `CoreLocation`
1213

1314
## Requirements
1415

1516
- Swift 6.0+
16-
- `CoreLocation`
1717
- Apple platforms where `CoreLocation` is available
18+
- Linux
19+
20+
When `CoreLocation` is unavailable, `FlitsGeohash` provides compatible `CLLocationCoordinate2D`, `CLLocationDegrees`, and `CLLocationCoordinate2DIsValid` definitions so the public API stays the same across platforms.
1821

1922
## Installation
2023

@@ -44,7 +47,9 @@ targets: [
4447
### Encode a coordinate
4548

4649
```swift
50+
#if canImport(CoreLocation)
4751
import CoreLocation
52+
#endif
4853
import FlitsGeohash
4954

5055
let coordinate = CLLocationCoordinate2D(
@@ -61,6 +66,8 @@ let shortHash = coordinate.geohash(length: 5)
6166

6267
The string-based API accepts geohash lengths from `1...22`.
6368

69+
On Linux, the snippet above works unchanged. `FlitsGeohash` exposes a compatible coordinate type when `CoreLocation` is not available.
70+
6471
### Adjacent cells and neighbors
6572

6673
```swift
@@ -120,7 +127,7 @@ let regionHashes = Geohash3.hashesForRegion(
120127
swift test
121128
```
122129

123-
The test suite includes correctness checks and performance-oriented coverage for encoding, adjacency, neighbors, and region generation.
130+
The test suite includes correctness checks and performance-oriented coverage for encoding, adjacency, neighbors, and region generation, and CI runs on both macOS and Ubuntu.
124131

125132
## Contributing
126133

Sources/FlitsGeohashSwift/CLLocationCoordinate2D+Geohash.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// Created by Maarten Zonneveld on 08/05/2024.
66
//
77

8+
#if canImport(CoreLocation)
89
import CoreLocation
10+
#endif
911

1012
public extension CLLocationCoordinate2D {
1113

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// CoordinateCompatibility.swift
3+
//
4+
//
5+
// Created by Maarten Zonneveld on 09/03/2026.
6+
//
7+
8+
#if canImport(CoreLocation)
9+
import CoreLocation
10+
#else
11+
import Foundation
12+
13+
public typealias CLLocationDegrees = Double
14+
15+
public struct CLLocationCoordinate2D: Sendable {
16+
public var latitude: CLLocationDegrees
17+
public var longitude: CLLocationDegrees
18+
19+
public init(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
20+
self.latitude = latitude
21+
self.longitude = longitude
22+
}
23+
}
24+
25+
@inline(__always)
26+
public func CLLocationCoordinate2DIsValid(_ coordinate: CLLocationCoordinate2D) -> Bool {
27+
(-90...90).contains(coordinate.latitude) && (-180...180).contains(coordinate.longitude)
28+
}
29+
#endif

Sources/FlitsGeohashSwift/GeoHash.swift

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55
// Created by Maarten Zonneveld on 07/05/2024.
66
//
77

8+
#if canImport(CoreLocation)
89
import CoreLocation
10+
#endif
11+
#if os(Linux)
12+
import Glibc
13+
#else
14+
import Darwin
15+
#endif
916

1017
public enum Geohash {
1118

@@ -40,12 +47,8 @@ public enum Geohash {
4047
}
4148

4249
public static func hash(_ coordinate: CLLocationCoordinate2D, length: UInt32) -> String {
43-
if !CLLocationCoordinate2DIsValid(coordinate) {
44-
assertionFailure("coordinate is invalid")
45-
}
46-
if length < 1, length > 22 {
47-
assertionFailure("length must be greater than 0 and less than 23")
48-
}
50+
precondition(CLLocationCoordinate2DIsValid(coordinate), "coordinate is invalid")
51+
precondition(length > 0 && length < 23, "length must be greater than 0 and less than 23")
4952
guard let pointer = GEOHASH_encode(coordinate.latitude, coordinate.longitude, length) else {
5053
fatalError()
5154
}
@@ -55,34 +58,35 @@ public enum Geohash {
5558
}
5659

5760
public static func adjacent(hash: String, direction: Direction) -> String {
58-
guard let pointer = GEOHASH_get_adjacent(
59-
hash.cString(using: .ascii),
60-
direction.cValue
61-
) else {
62-
fatalError()
61+
hash.withCString { hashCString in
62+
guard let pointer = GEOHASH_get_adjacent(hashCString, direction.cValue) else {
63+
fatalError()
64+
}
65+
let adjacent = string(from: pointer)
66+
free(pointer)
67+
return adjacent
6368
}
64-
let adjacent = string(from: pointer)
65-
free(pointer)
66-
return adjacent
6769
}
6870

6971
public static func neighbors(hash: String) -> Neighbors {
70-
let pointer = GEOHASH_get_neighbors(hash.cString(using: .ascii))
71-
guard let cNeighbors = pointer?.pointee else {
72-
fatalError()
72+
hash.withCString { hashCString in
73+
guard let pointer = GEOHASH_get_neighbors(hashCString) else {
74+
fatalError()
75+
}
76+
let cNeighbors = pointer.pointee
77+
let neighbors = Neighbors(
78+
north: string(from: cNeighbors.north),
79+
south: string(from: cNeighbors.south),
80+
west: string(from: cNeighbors.west),
81+
east: string(from: cNeighbors.east),
82+
northWest: string(from: cNeighbors.north_west),
83+
northEast: string(from: cNeighbors.north_east),
84+
southWest: string(from: cNeighbors.south_west),
85+
southEast: string(from: cNeighbors.south_east)
86+
)
87+
GEOHASH_free_neighbors(pointer)
88+
return neighbors
7389
}
74-
let neighbors = Neighbors(
75-
north: string(from: cNeighbors.north),
76-
south: string(from: cNeighbors.south),
77-
west: string(from: cNeighbors.west),
78-
east: string(from: cNeighbors.east),
79-
northWest: string(from: cNeighbors.north_west),
80-
northEast: string(from: cNeighbors.north_east),
81-
southWest: string(from: cNeighbors.south_west),
82-
southEast: string(from: cNeighbors.south_east)
83-
)
84-
GEOHASH_free_neighbors(pointer)
85-
return neighbors
8690
}
8791

8892
public static func hashesForRegion(

Sources/FlitsGeohashSwift/LengthedGeohash.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// Created by Maarten Zonneveld on 21/05/2024.
66
//
77

8+
#if canImport(CoreLocation)
89
import CoreLocation
10+
#endif
911

1012
public protocol GeohashLengthed {
1113
static var length: UInt32 { get }

Tests/FlitsGeohashSwiftTests/LengthTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
import XCTest
3+
#if canImport(CoreLocation)
34
import CoreLocation
5+
#endif
46
import FlitsGeohash
57

68
final class GeohashTests: XCTestCase {

Tests/FlitsGeohashSwiftTests/PerformanceTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
//
77

88
import XCTest
9+
#if canImport(CoreLocation)
910
import CoreLocation
11+
#endif
1012
import FlitsGeohash
1113

1214
final class PerformanceTests: XCTestCase {

0 commit comments

Comments
 (0)