Skip to content

Commit

Permalink
MetroGTFS Package: MetroRail GTFS Static data in a new wrapper (#57)
Browse files Browse the repository at this point in the history
Create a new package, MetroGTFS, for handling GTFS Static data
  • Loading branch information
emma-k-alexandra committed Mar 15, 2024
1 parent 883b7e8 commit 74dcbb0
Show file tree
Hide file tree
Showing 22 changed files with 968 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Set up Swift
uses: fwal/setup-swift@v1
with:
swift-version: '5.8'
swift-version: '5.9'
- name: Generate Docs
uses: fwcd/swift-docc-action@v1
with:
Expand Down
69 changes: 38 additions & 31 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
{
"object": {
"pins": [
{
"package": "DVR",
"repositoryURL": "https://github.com/venmo/DVR.git",
"state": {
"branch": null,
"revision": "d13f7135d1993053580efe13c9ecc43200852d09",
"version": "2.1.0"
}
},
{
"package": "SwiftDocCPlugin",
"repositoryURL": "https://github.com/apple/swift-docc-plugin",
"state": {
"branch": null,
"revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6",
"version": "1.0.0"
}
},
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
"version": "1.18.0"
}
"pins" : [
{
"identity" : "dvr",
"kind" : "remoteSourceControl",
"location" : "https://github.com/venmo/DVR.git",
"state" : {
"revision" : "d13f7135d1993053580efe13c9ecc43200852d09",
"version" : "2.1.0"
}
]
},
"version": 1
},
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
"version" : "0.14.1"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6",
"version" : "1.0.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
"version" : "1.18.0"
}
}
],
"version" : 2
}
36 changes: 30 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
// swift-tools-version:5.6
// swift-tools-version:5.9
import PackageDescription

let package = Package(
name: "WMATA",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
.macOS(.v12),
.iOS(.v15),
.tvOS(.v15),
.watchOS(.v8)
],
products: [
.library(
name: "WMATA",
targets: ["WMATA"]
),
.library(
name: "MetroGTFS",
targets: ["MetroGTFS"]
)
],
dependencies: [
.package(
Expand All @@ -26,7 +30,11 @@ let package = Package(
),
.package(
url: "https://github.com/apple/swift-docc-plugin",
from: "1.0.0"
.upToNextMajor(from: .init(1, 0, 0))
),
.package(
url: "https://github.com/stephencelis/SQLite.swift.git",
.upToNextMinor(from: .init(0, 14, 1))
)
],
targets: [
Expand All @@ -41,5 +49,21 @@ let package = Package(
dependencies: ["WMATA", "DVR"],
resources: [.process("Fixtures")]
),
.target(
name: "MetroGTFS",
dependencies: [
.product(name: "SQLite", package: "SQLite.swift"),
"WMATA"
],
resources: [
.copy("MetroGTFS.sqlite3")
]
),
.testTarget(
name: "MetroGTFSTests",
dependencies: [
"MetroGTFS"
]
)
]
)
62 changes: 45 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,12 @@

WMATA.swift is a Swift interface to the [Washington Metropolitan Area Transit Authority API](https://developer.wmata.com).

## Contents

- [Requirements](#requirements)
- [Installation](#installation)
- [Documentation](#documentation)
- [Dependencies](#dependencies)
- [Contact](#contact)
- [Contributing](#contributing)
- [License](#license)

## Installation
## Install

### Requirements

- Swift 5.6
- Xcode 13.2
- Swift 5.9
- Xcode 15

### Swift Package Manager

Expand All @@ -31,11 +21,47 @@ dependencies: [
]
```

## Usage

### Standard API

To work with WMATA's Standard API use the `WMATA` package.

```swift
import WMATA

let nextTrains = Rail.NextRails(
key: YOUR_API_KEY,
station: .waterfront
)

nextTrains.request { result in
switch result {
case let .success(response):
print(response.trains)
case let .failure(error):
print(error)
}
}
```

### GTFS Static

To work with GTFS Static data use the `MetroGTFS` package.

```swift
import MetroGTFS

let ashburn = try GTFSStop("STN_N12")

print(ashburn.name) // "ASHBURN METRORAIL STATION"
```

## OS Support

WMATA.swift commits to supporting current minus 2 OS versions.

Currently, WMATA.swift is compatible with macOS 10.15, iOS 13, tvOS 13, watchOS 6 or higher.
Currently, WMATA.swift is compatible with macOS 12, iOS 15, tvOS 15, watchOS 8 or higher.

## Documentation

Expand All @@ -47,6 +73,7 @@ To view documentation within Xcode, within the menu navigate to `Product > Build

- [swift-protobuf](https://github.com/apple/swift-protobuf), for GTFS-RT feeds.
- [DVR](https://github.com/venmo/DVR), for testing.
- [SQLite.swift](https://github.com/stephencelis/SQLite.swift), for GTFS Static data. Only used in `MetroGTFS` package.

## Contact

Expand All @@ -56,9 +83,8 @@ Feel free to email questions and comments to [emma@emma.sh](mailto:emma@emma.sh)

Todo:

- [ ] Build out more DVR tests.
- [ ] Automated builds.
- [ ] Convert async functions from a `Result` to `return` or `throw` behavior, the dominant async pattern in Swift.
- [ ] Support all GTFS Static data in `MetroGTFS`
- [ ] Convert async functions from a `Result` to `return` or `throw` behavior, the dominant async pattern in Swift

## Developer

Expand All @@ -67,3 +93,5 @@ To generate documentation for deploying to Github Pages, run `./docs.sh`.
## License

WMATA.swift is released under the MIT license. [See LICENSE](https://github.com/emma-k-alexandra/WMATA.swift/blob/main/LICENSE) for details.

This package is not distributed by or affiliated with WMATA.
2 changes: 2 additions & 0 deletions Sources/MetroGTFS/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gtfs-files/*.txt
gtfs-files/swift-files/*.swift
27 changes: 27 additions & 0 deletions Sources/MetroGTFS/Coordinates.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// GTFSCoordinates.swift
//
//
// Created by Emma on 11/25/23.
//

import Foundation

/// Location with latitude and longitude coordinates
public struct GTFSCoordinates: Equatable, Hashable, Codable {
/// Latitude in degrees, for the DMV this value is positive.
public var latitude: Double

/// Longitude in degrees, For the DMV this value is negative.
public var longitude: Double

/// Create a new location
///
/// - Parameters:
/// - latitude: Latitude of location in degrees, positive for the DMV.
/// - longitude: Longitude of location in degrees, negative for the DMV.
public init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
121 changes: 121 additions & 0 deletions Sources/MetroGTFS/Database.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// Database.swift
//
//
// Created by Emma on 11/26/23.
//

import Foundation
import SQLite

/// The GTFS Static Database. Used to perform queries on the GTFS Static Database.
struct GTFSDatabase {
private let connection: Connection

/// Create a new GTFS Database. If there is not currently an open connection to the database, create one.
init() throws {
if let connection = GTFSDatabase.shared {
self.connection = connection

return
}

let connection: Connection

do {
connection = try GTFSDatabase.connection()
} catch {
throw GTFSDatabaseError.unableToConnectToDatabase
}

GTFSDatabase.shared = connection

self.connection = connection
}

/// Run a database query that only returns one row
func run(query: SQLite.Table) throws -> Row? {
do {
return try connection.pluck(query)
} catch {
throw GTFSDatabaseError.unableToPerformQuery(query)
}
}

/// Run a database query that returns multiple rows
func run(query: SQLite.Table) throws -> AnySequence<Row> {
do {
return try connection.prepare(query)
} catch {
throw GTFSDatabaseError.unableToPerformQuery(query)
}
}
}

extension GTFSDatabase {
/// Get all GTFS Structures of the given type from the GTFS Database
func all<Structure: Queryable>(_ structure: Structure.Type) throws -> AnySequence<Row> {
return try run(query: structure.databaseTable.sqlTable)
}

/// Get all GTFS Structures of the given type with the given `id` in the given `column`. Defaults to using the primary key column.
func all<Structure: Queryable>(
_ structure: Structure.Type,
with id: GTFSIdentifier<Structure>,
in column: SQLite.Expression<String> = Structure.databaseTable.primaryKeyColumn
) throws -> AnySequence<Row> {
return try run(query: structure.databaseTable.sqlTable.where(column == id.rawValue))
}

/// Get all GTFS Structures of the given type with the given `id` in the given `column`.
func all<Structure: Queryable>(
_ structure: Structure.Type,
with id: GTFSIdentifier<Structure>,
in column: SQLite.Expression<String?>
) throws -> AnySequence<Row> {
return try run(query: structure.databaseTable.sqlTable.where(column == id.rawValue))
}


/// Get a single structure of the given type with the given `id` in the given `column`. Defaults to using the primary key column.
func one<Structure: Queryable>(
_ structure: Structure.Type,
with id: GTFSIdentifier<Structure>,
in column: SQLite.Expression<String> = Structure.databaseTable.primaryKeyColumn
) throws -> Row? {
return try run(query: structure.databaseTable.sqlTable.where(column == id.rawValue))
}
}

extension GTFSDatabase {
/// The global shares connection to the GTFS database
private static var shared: Connection?

/// Create a new connection to the MetroGTFS SQLite database
private static func connection() throws -> Connection {
let path = Bundle.module.path(forResource: "MetroGTFS", ofType: "sqlite3")

guard let path else {
throw GTFSDatabaseError.failedToLoadDatabase
}

let connection = try Connection(path, readonly: true)

return connection
}
}

extension GTFSDatabase {
/// A SQLite database table and the column it's primary key is in
struct Table {
let sqlTable: SQLite.Table
let primaryKeyColumn: SQLite.Expression<String>
}
}

/// If a data type can be loaded from a SQLite database
protocol Queryable {
/// The actual table in SQLite to pull the data type from
static var databaseTable: GTFSDatabase.Table { get }
}

Loading

0 comments on commit 74dcbb0

Please sign in to comment.