downtowngr/mantle

View on GitHub
tasks/generate_transit_stops.rb

Summary

Maintainability
A
35 mins
Test Coverage
require "open-uri"
require "zip"
require "sqlite3"
require "sequel"
require "rgeo/geo_json"
require "smarter_csv"
require "aws-sdk"
require "json/minify"

class GenerateTransitStops
  def initialize
    Dir.mkdir "tmp" unless File.exists?("tmp")
    SQLite3::Database.new("tmp/gtfs.db")
    @db    = Sequel.connect('sqlite://tmp/gtfs.db')

    @coder = RGeo::GeoJSON.coder
    @bb = RGeo::Cartesian::BoundingBox.create_from_points(
      @coder.geo_factory.point(-85.68291485309601, 42.97696419731116),
      @coder.geo_factory.point(-85.65665602684021, 42.95390977598836)
    )

    @s3_file = Aws::S3::Object.new(ENV["AWS_BUCKET"], "maps/transit_stops.geojson", {
      access_key_id: ENV["AWS_SECRET_KEY_ID"],
      secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
      region: "us-east-1"
    })

    @gtfs_files = ["stops.txt", "routes.txt", "trips.txt", "stop_times.txt"]
    @layer_path = "tmp/transit_stops.geojson"
  end

  def perform
    fetch_gtfs("tmp/gtfs.zip")
    unzip_gtfs("tmp/gtfs.zip")

    @gtfs_files.each {|f| populate_db(f)}

    File.write(@layer_path, JSON.minify(@coder.encode(feature_collection).to_json))
    @s3_file.upload_file(@layer_path, acl: "public-read")
  end

  def feature_collection
    features = @db[:stops].all.map do |stop|
      stop_point = @coder.geo_factory.point(stop[:stop_lon], stop[:stop_lat])
      next unless @bb.contains?(stop_point)

      trip_ids = @db[:stop_times].where(stop_id: stop[:stop_id]).select(:trip_id).all.map {|s| s[:trip_id]}
      next if trip_ids.empty?

      route_names = trip_ids.uniq.map do |trip_id|
        route_id = @db[:trips].where(trip_id: trip_id).first[:route_id]
        @db[:routes].where(route_id: route_id).first[:route_short_name]
      end

      routes = route_names.uniq

      @coder.entity_factory.feature(
        @coder.geo_factory.point(stop[:stop_lon], stop[:stop_lat]),
        stop[:stop_id],
        {
          stop_number: stop[:stop_code],
          name: stop[:stop_name],
          routes: routes
        }
      )
    end

    @coder.entity_factory.feature_collection(features)
  end

  def populate_db(filename)
    tablename = File.basename(filename).gsub(/\.\w+/,"").to_sym

    options = {strings_as_keys: true, remove_empty_values: false, remove_zero_values: false}
    data = SmarterCSV.process("tmp/#{filename}", options)

    @db.drop_table? tablename

    @db.create_table tablename do
      data.last.each do |column_name, row|
        if row.class == NilClass
          klass = String
        else
          klass = row.class
        end

        column column_name, klass
      end
    end

    data.each do |row|
      @db[tablename].insert(row)
    end
  end

  def unzip_gtfs(gtfs_zip)
    Zip::File.open(gtfs_zip) do |zip_file|
      @gtfs_files.each do |filename|
        File.delete("tmp/#{filename}") if File.exists?("tmp/#{filename}")
        entry = zip_file.glob(filename).first
        entry.extract("tmp/#{filename}")
      end
    end
  end

  def fetch_gtfs(gtfs_zip)
    open(gtfs_zip, 'wb') do |file|
      file << open('http://connect.ridetherapid.org/InfoPoint/gtfs-zip.ashx').read
    end
  end
end