Code Monkey home page Code Monkey logo

tripkit's Introduction

TripKit

TripKit is a Swift-port of https://github.com/schildbach/public-transport-enabler with some additional enhancements. This library allows you to get data from public transport providers. You can get an overview of all supported transit providers here: https://navigatorapp.net/coverage. Look into NetworkProvider.swift for an overview of the API.

TripKit is built using Swift 5.0 and requires iOS 12.0/watchOS 5.0/tvOS 12.0/macOS 10.13.

This library is currently used by the ÖPNV Navigator app in the iOS App Store.

Static tests Provider tests

Integration

Use the Swift Package Manager to install TripKit into your project. If you have a Package.swift file, add the following entry to your dependencies:

import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    dependencies: [
        // Insert the following line into your Swift package dependencies.
        .package(url: "https://github.com/alexander-albers/tripkit.git", .branch("main")),
    ],
)

If you are using a regular Xcode project, you can select "File" -> "Add Packages…" from the menu bar and paste the url of this git repository.

The tagged commits of this repository correspond to the released versions of the ÖPNV Navigator app and have no other meaning. I try to avoid code-breaking changes between releases as far as possible, but since this project is still under active development there is not guarantee that some minor things might break.

Example Usage

Create a new instance of a network provider:

let provider: NetworkProvider = KvvProvider() // Karlsruher Verkehrsverbund

Find locations for a given keyword:

let (request, result) = await provider.suggestLocations(constraint: "Marktplatz")
switch result {
case .success(let locations):
    for suggestedLocation in locations {
        print(suggestedLocation.location.getUniqueShortName())
    }
case .failure(let error):
    print(error)
}

Find locations near a coordinate (Marktplatz):

let (request, result) = await provider.queryNearbyLocations(location: Location(lat: 49009656, lon: 8402383))
switch result {
case .success(let locations):
    for location in locations {
        print(location.getUniqueShortName())
    }
case .failure(let error):
    print(error)
}

Query departures from Marktplatz (id=7001003):

let (request, result) = await provider.queryDepartures(stationId: "7001003")
switch result {
case .success(let departures):
    for departure in departures.flatMap { $0.departures } {
        let label = departure.line.label ?? "?"
        let destination = departure.destination?.getUniqueShortName() ?? "?"
        let time = departure.getTime()
        print("\(time): \(line) --> \(destination)")
    }
case .invalidStation:
    print("invalid station id")
case .failure(let error):
    print(error)
}

Query trips between Marktplatz (7001003) and Kronenplatz (7001002):

let (request, result) = await provider.queryTrips(from: Location(id: "7001003"), via: nil, to: Location(id: "7001002"))
switch result {
case .success(let context, let from, let via, let to, let trips, let messages):
    for trip in trips {
        print(trip.id)
    }
default:
    print("no trips could be found")
}

Query all intermediate stops of a line:

// journeyContext can be obtained from a PublicLeg instance.
let (request, result) = await provider.queryJourneyDetail(context: journeyContext)
switch result {
case .success(let trip, let leg):
    print(leg.intermediateStops)
case .invalidId:
    print("invalid context")
case .failure(let error):
    print(error)
}

More api methods can be found in NetworkProvider.swift and NetworkProvider+Async.swift.

Using providers that require secrets

For some providers a secret like an API key is required to use their API. You need to request the secrets directly from the provider or use the same ones that are used by the official apps.

For unit testing, you need to specify all required secrets in a secrets.json file. A template can be found here.

Contributing and future plans

Feel free to add further transit providers to the project, as long as they don't overlap with already existing ones and don't require too much maintenance or a server to be used. Since this project is based on the public-transport-enabler, my intention is to have this project as close to it as possible. For now, I'd like to stick to transit providers in German-speaking countries, but a further expansion to other countries is imaginable for the future.

Related Projects

tripkit's People

Contributors

alexander-albers avatar epirat avatar robot8a avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

tripkit's Issues

Tests: allow testing multiple different trips/departures/etc.

Currently, all tests only support one specific trip or departure stop that will be tested. Ideally, we should be able to specify all locations in some kind of array to be able to test several specific trips.
For example, for the BVG provider it would be nice to additionally test a journey where the Berlkönig flag is required (see #26), while still keeping the current test trip.
We could for example specify all trips that should be tested like the following in a provider test class:

var trips: [
  ["900013103", "900056102"], // from Prinzenstraße to Nollendorfplatz
  ["1", "2", "3"], // from 1 to 3 via 2
  //...
]
var departures: [
  "900013103", // test departures for Prinzenstraße
  "900056102", // test departures for Nollendorfplatz
  //...
]

Url encoding/decoding deprecation warning for unsupported encoding

Some providers require url encoding or decoding using non-utf8 charsets. This behavior has been deprecated by Apple for some years now.

extension String {
public func decodeUrl(using encoding: String.Encoding) -> String? {
return (self as NSString).replacingPercentEscapes(using: encoding.rawValue)
}
public func encodeUrl(using encoding: String.Encoding) -> String? {
return (self as NSString).addingPercentEscapes(using: encoding.rawValue)
}
}

Those methods already have been removed from the Swift String class and are only available for NSString:
https://github.com/apple/swift-corelibs-foundation/blob/eec4b26deee34edb7664ddd9c1222492a399d122/Sources/Foundation/NSStringAPI.swift#L2165

Deprecation warning for NSString: Use -stringByRemovingPercentEncoding instead, which always uses the recommended UTF-8 encoding.

decodeUrl

  • used by Hafas Legacy with isoLatin1 encoding

encodeUrl

  • used by UrlBuilder
  • VBB, Linz and NVBW use isoLatin1 encoding

Currently, everything still works fine, but this url encoding/decoding may need to be reimplemented at some point in the future if Apple decides to completely remove the respective methods.

Efa (VVO, VRN, ...): trip request illegal location

Sending a trip request may fail with invalid location. It seems like the wrong location type is supplied for pois.

Example: suburbID%3A14628130%3A50%3AGlash%C3%BCtte (Sachs)%3A1533682%3A5405107%3AMRCV will not be identified, because TripKit uses type=address instead of type=any.

May be because location is supplied from ambiguous results.

HCI: filter remarks and HIM-messages

Currently, every unhandled remark or message is appended to the message text of a leg. The intention behind this was to not withhold any important information from the user. However at the moment way too many unnecessary information is being parsed, like the operator of the mot or mask mandates. On the other side, delay reasons like "Reparatur am Zug" might not be parsed at all.

The approach of for example the VBB app is to only parse around 10-20 remarks and show messages like the mask mandate only once per trip. The problem with this approach is that there are way too many remark types that could be parsed, so you have to make a selection without withholding key information (db, for instance, displays some text of the remarks-array below the trip leg).

Related issues are public-transport/hafas-client#5 and marudor/bahn.expert#139.

Todos for now:

  • find out the most common remark types that should not be displayed
  • figure out why some messages are currently not being parsed
  • same as with the VBB app, look at DB Navigator and find out how they handle this

Initialization of NSAttributedString in a background thread.

Under some circumstances, initializing a NSAttributedString from a HTML string in a background thread may cause a crash. See discussion in Apple Docs: https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-init

Relevant code in project:

extension String {
init?(htmlEncodedString: String?) {
guard let htmlEncodedString = htmlEncodedString else {
return nil
}
guard let data = htmlEncodedString.data(using: .utf8) else {
self.init(htmlEncodedString)
return
}
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else {
self.init(htmlEncodedString)
return
}
self.init(attributedString.string)
}
}

Since this is used to strip out any html tags and parse things such as ordered and unordered lists, this method could potentially be rewritten without any use of NSAttributedString.

Hci: Support more journey types

There are still journey types which are not being parsed in AbstractHciProvider and result in a parse error:

  • BIKE: Bike
  • KISS: Car
  • PARK: Park & Ride
  • TAXI: Taxi
  • CHKI: Check-In
  • CHKO: Check-Out

I haven't encountered any of the above mentioned types in the wild, except for one case: BVG requires that the product type "BERLKOENIG" is included, or otherwise some trips may fail, even if those don't include any Berlkönig products (see #26).

Fix NSKeyedUnarchiver allowed classes

It seems like since Xcode 13 NSKeyedUnarchiver complains about certain data types when decoding a saved object.

Example:

xctest[16883:503835] [general] *** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSNumber' (0x7ff84cc674e8) [/System/Library/Frameworks/Foundation.framework]' for key 'priority', even though it was not explicitly included in the client allowed classes set: '{(
"'NSArray' (0x7ff84cc3d970) [/System/Library/Frameworks/CoreFoundation.framework]",
"'TripKit.SuggestedLocation' (0x10dd72a00) [/Users/alex/Library/Developer/Xcode/DerivedData/TripKit-dsfwcedxwupmylaklfxshubrnyjl/Build/Products/Debug/TripKit.framework]"
)}'. This will be disallowed in the future.

BVG doesn't work

Cannot get any connections with BVG.
Other providers work, but BVG doesn't.

Hafas: edge-case when parsing location names

When querying trips using non-latin characters, there exists a problem where the response locations cannot be parsed anymore by the library.

Example:
Provider: RMV
From: Schöfferstraße 2, 达姆施塔特 (4-49867186:8640047)
To: Wormser Straße 32, 64295 达姆施塔特, 德国 (3-49856719:8637645)

Response location is Schöfferstraße 2, , where the second location part is empty and reversed - usually, the place is first.
This results in a parse error, as we try to create a location with a place name but no location name.

BVG: include Berlkönig

BVG requires, for whatever reason, that the product type "BERLKOENIG" is included, or otherwise some trips may fail, even if those don't include any Berlkönig products.

See for example this request:

{
  "auth": {
    "aid": <secret>,
    "type": "AID"
  },
  "svcReqL": [
    {
      "meth": "TripSearch",
      "cfg": {},
      "req": {
        "arrLocL": [
          {
            "type": "S",
            "lid": "A=1@O=U Elsterwerdaer Platz (Berlin)@X=13560758@Y=52504644@U=86@L=900171006@B=1@p=1617247685@"
          }
        ],
        "getPolyline": true,
        "gisFltrL": [
          {
            "type": "M",
            "meta": "foot_speed_normal",
            "profile": {
              "linDistRouting": false,
              "maxdist": 2000,
              "type": "F"
            },
            "mode": "FB"
          }
        ],
        "depLocL": [
          {
            "lid": "A=1@O=Pilgramer Str./Rahnsdorfer Str. (Berlin)@X=13619395@Y=52496554@U=86@L=900176540@B=1@p=1617247685@",
            "type": "S"
          }
        ],
        "outFrwd": true,
        "outTime": "080000",
        "extChgTime": -1,
        "getPasslist": true,
        "trfReq": {
          "jnyCl": 2,
          "tvlrProf": [
            {
              "type": "E"
            }
          ],
          "cType": "PK"
        },
        "jnyFltrL": [
          {
            "value": "1111111111",
            "mode": "BIT",
            "type": "PROD"
          },
          {
      -->   "value": "BERLKOENIG",
            "mode": "INC",
            "type": "GROUP"
          }
        ],
        "outDate": "20210409"
      }
    }
  ],
  "ext": "BVG.1",
  "formatted": true,
  "lang": "de",
  "ver": "1.24",
  "client": {
    "id": "BVG",
    "type": "IPH",
  }
}

However, enabling this option leads to a new JNY-type named "KISS", which is currently not handled and results in a parse error.

--> Revert this commit after KISS is handled: b04f52c

VRS: illegal delay for trips after midnight

When querying for trips around midnight time, sometimes trips get shown with a delay of 1440 minutes. This seems to be caused by some day rollover bug, probably only for trips in Bonn.
Example trip: Arndstr. -> Bonn Hbf

VMV: fix line number parsing

Line numbers from VMV provider currently contain lots of unnecessary information, like the full product name (eg. Regional Express) or the provider name (eg. Ostdeutsche Eisenbahn GmbH).

Example: Schwerin, Hbf.

Issue was likely introduced with: bb31487

Expand usage of SwiftyJson

Even though I'm not a fan of using many third party frameworks, SwiftyJson does a great job of keeping the code clean and readable when dealing with Json responses, while still keeping type-safety. That's why I'd like to expand the usage of SwiftyJson to more network provider implementations.

VVM ambiguouity

Previously, the VVM Mittelfranken provider has been used in VvmProvider.swift. The old endpoint was no longer reachable, and when I migrated the url to the current one, I confused VVM Mittelfranken with VVM Mainfranken and added the endpoint for the latter. Both providers use https://www.bayern-fahrplan.de on their website and are probably connected to DEFAS, but the VVM Mittelfranken app does not use the Efa or Hafas API. The question is how to move forward from now on.

As some station ids seem to be interoperable, I tend to just keep the current implementation of the VVM and continue only supporting the VVM Mainfranken for now..

see schildbach/public-transport-enabler#400

HCI: improve individual leg parsing

  1. When there are multiple individual legs in succession, they should get merged similar to how its done for efa providers.
  2. When the preceding leg has a delay, the delay gets added to the duration of the individual leg, which obviously should not be the case.

RMV: first trip does not get shown

When querying trips for the current time, sometimes the first possible trip does not appear in the results (eg. queryTrips for 15:00 would not show a trip departuring at 15:01). May be correlated to the trip having a delay.
Possible workaround: query trips 10-15 minutes earlier.

HCI: parse poly list

In jny:

"polyG": {
  "polyXL": [
    0
  ],
  "layerX": 0,
  "crdSysX": 0
},

In common:

"polyL": [
  {
    "delta": true,
    "dim": 2,
    "crdEncYX": "coords",
    "crdEncS": "NNMKNLNMNMNNLNNNKNNNMNMLNKNNN",
    "crdEncF": "?????????????????????????????",
    "ppLocRefL": [
      {
        "ppIdx": 0,
        "locX": 0
      },
      {
        "ppIdx": 13,
        "locX": 3
      },
      {
        "ppIdx": 28,
        "locX": 2
      }
    ]
  },
...
]

Clean up model classes

It would be nice if all model classes (ie. everything in the model package like Trip, Leg, Departure and so on) would be a struct instead of a class. That way we could remove a lot of boilerplate since structs in Swift automatically support serialization, hashing etc. One caveat is that the new structs must be able to decode data that has been previously encoded using the current classes. Otherwise, previously saved data is lost.

Also, there is a lot of potential for cleaning up all variable and method names. We should:

  • use calculated properties instead of methods for stuff like Trip.getDepartureTime() [--> Trip.departureTime] or Leg.getMaxTime() [--> Trip.maxTime]
  • remove force-unwraps of optionals like in legs.first!.getDepartureTime()
  • make planned-time variables non-optional (every trip/leg/departure should have a non-optional departure time. However, providers like KVV also return intermediate stops without any time associated, if there is a stop where the train some times stops but not always. Maybe use enum case to make this distinction clear in the code? Also, make it clear in the code when to use Stop.departureXXX or Stop.arrivalXXX, possibly with enums, too.)

All points above are medium term goals to make the API a little nicer to use for everyone. :)

HVV: migrate to new API

The HAFAS endpoint hvv-app.hafas.de may be shut down at the end of 2021. Currently, search requests to the API contain a warning that the "App version will be switched off on 1.1.2022".

The new website and app of HVV use Geofox.de as API provider.

Example request: https://gti.geofox.de/gti/restapp/getRoute
{
  "version": 47,
  "language": "de",
  "start": {
    "name": "Hauptbahnhof",
    "city": "",
    "combinedName": "",
    "type": "STATION",
    "id": "Master:9910950"
  },
  "dest": {
    "name": "Berliner Platz",
    "city": "",
    "combinedName": "",
    "type": "STATION",
    "id": "Master:62003"
  },
  "toStartBy": "FOOTPATH",
  "toDestBy": "FOOTPATH",
  "time": {
    "date": "04.12.2021",
    "time": "09:54"
  },
  "timeIsDeparture": true,
  "schedulesBefore": 0,
  "schedulesAfter": 4,
  "intermediateStops": true,
  "tariffInfoSelector": [
    {
      "tariff": "HVV",
      "tariffRegions": true,
      "kinds": [
        "1"
      ]
    },
    {
      "tariff": "SH",
      "tariffRegions": false,
      "kinds": [
        "1"
      ]
    }
  ],
  "penalties": [
    {
      "name": "desiredType",
      "value": "longdistancebus,fasttrain&extrafasttrain:10000"
    }
  ],
  "returnContSearchData": true,
  "realtime": "REALTIME"
}

Authentication:
geofox-auth-user: hbtweb
geofox-auth-signature: base64(HmacSHA1(request_body, pass))

GVH: migrate to hafas.

While the official Website still uses the EFA endpoint, the GVH App now uses a hafas backend (gvh.hafas.de) and seems to return way more accurate data.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.