Writing to a GTFS file

Similarly reading GTFS file, one can write GTFS files easily using this library. You can either modify an existing file, or create a new one from scratch. The writing pipes, live in the write namespace within the GtfsFile class. This namespace provides a handful of Pipes which give access to standard GTFS files.

import com.mobimeo.gtfs.file._
import com.mobimeo.gtfs.model._

import cats.effect._
import cats.effect.unsafe.implicits.global

import fs2.io.file.{CopyFlag, CopyFlags, Path}

val gtfs = GtfsFile[IO](Path("site/gtfs.zip"))
// gtfs: Resource[IO, GtfsFile[IO]] = Bind(
//   source = Allocate(
//     resource = cats.effect.kernel.Resource$$$Lambda$16478/0x0000000804362840@1554ed9
//   ),
//   fs = cats.effect.kernel.Resource$$Lambda$16480/0x0000000804364040@35cd7257
// )

Modifying an existing file

Oftentimes you already have an existing file in your hands and you want to modify it. To this end, you can pipe the read stream into the corresponding pipe.

gtfs.use { gtfs =>
  gtfs.read
    .rawStops
    .map(s => s.modify("stop_name")(_.toUpperCase))
    .through(gtfs.write.rawStops)
    .compile
    .drain
}

This code modifies the file in place, making all stop names uppercase. However this is usually not recommended as data are overwritten and original data are replaced. One should prefer to work on a copy of the original file. The GtfsFile provides a way to do it conveniently.

val modified = Path("site/modified-gtfs.zip")
// modified: Path = site/modified-gtfs.zip
gtfs.use { src =>
  src.copyTo(modified, CopyFlags(CopyFlag.ReplaceExisting)).use { tgt =>
    src.read
      .rawStops
      .map(s => s.modify("stop_name")(_.toUpperCase))
      .through(tgt.write.rawStops)
      .compile
      .drain
  }
}.unsafeRunSync()

def printStops(gtfs: GtfsFile[IO]) =
  gtfs.read
    .rawStops
    .map(s => s("stop_name"))
    .unNone
    .take(5)
    .intersperse("\n")
    .evalMap(s => IO(print(s)))
    .compile
    .drain

// original file
gtfs.use(printStops(_)).unsafeRunSync()
// S+U Berlin Hauptbahnhof
// S+U Berlin Hauptbahnhof
// Berlin, Friedrich-Olbricht-Damm/Saatwinkler Damm
// Berlin, Stieffring
// Berlin, Lehrter Str./Invalidenstr.

// modified file
GtfsFile[IO](modified).use(printStops(_)).unsafeRunSync()
// S+U BERLIN HAUPTBAHNHOF
// S+U BERLIN HAUPTBAHNHOF
// BERLIN, FRIEDRICH-OLBRICHT-DAMM/SAATWINKLER DAMM
// BERLIN, STIEFFRING
// BERLIN, LEHRTER STR./INVALIDENSTR.

When using copyTo the entirety of the original GTFS file content is copied and only files that are written to are modified. The rest is identical to the original file (including potential non standard files).

Creating a new file

If one wants to create a new file from scratch, one need to tell the file needs to be created when creating the GTFS resource. An empty GTFS file will be created, and files can be added to it by using the associated write pipes.

def makeStop(id: String, name: String) =
  Stop(id, None, Some(name), None, None, None, None, None, None, None, None, None, None, None)

val file = Path("site/gtfs2.zip")
// file: Path = site/gtfs2.zip
GtfsFile[IO](file, create = true).use { gtfs =>
  fs2.Stream.emits(List(makeStop("stop1", "Some Stop"), makeStop("stop2", "Some Other Stop")))
    .covary[IO]
    .through(gtfs.write.stops[Stop])
    .compile
    .drain
}.unsafeRunSync()

GtfsFile[IO](file).use(printStops(_)).unsafeRunSync()
// Some Stop
// Some Other Stop