Source code for fasttrips.TAZ

from __future__ import print_function
from __future__ import division
from builtins import str
from builtins import object

__copyright__ = "Copyright 2015 Contributing Entities"
__license__   = """
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""
import datetime
import os

import numpy as np
import pandas as pd

from .Error      import NetworkInputError
from .Logger     import FastTripsLogger
from .Route      import Route
from .Stop       import Stop
from .Transfer   import Transfer


[docs]class TAZ(object): """ TAZ class. One instance represents all of the Transportation Analysis Zones as well as their access links and egress links. .. todo:: This is really about the access and egress links; perhaps it should be renamed? Stores access link information in :py:attr:`TAZ.walk_access`, and :py:attr:`TAZ.drive_access`, both instances of :py:class:`pandas.DataFrame`. """ #: File with fasttrips walk access information. #: See `walk_access specification <https://github.com/osplanning-data-standards/GTFS-PLUS/blob/master/files/walk_access_ft.md>`_. INPUT_WALK_ACCESS_FILE = "walk_access_ft.txt" #: Walk access links column name: TAZ Identifier. String. WALK_ACCESS_COLUMN_TAZ = 'taz' #: Walk access links column name: Stop Identifier. String. WALK_ACCESS_COLUMN_STOP = 'stop_id' #: Walk access links column name: Direction (access or egress) WALK_ACCESS_COLUMN_DIRECTION = "direction" #: Walk access links column name: Walk Distance WALK_ACCESS_COLUMN_DIST = 'dist' #: fasttrips Walk access links column name: Elevation Gain, feet gained along link. WALK_ACCESS_COLUMN_ELEVATION_GAIN = 'elevation_gain' #: fasttrips Walk access links column name: Population Density, people per square mile. Float. WALK_ACCESS_COLUMN_POPULATION_DENSITY = 'population_density' #: fasttrips Walk access links column name: Employment Density, employees per square mile. Float. WALK_ACCESS_COLUMN_EMPLOYMENT_DENSITY = 'employment_density' #: fasttrips Walk access links column name: Retail Density, employees per square mile. Float. #WALK_ACCESS_COLUMN_RETAIL_DENSITY = 'retail_density' #: fasttrips Walk access links column name: Employment Density, employees per square mile. Float. WALK_ACCESS_COLUMN_EMPLOYMENT_DENSITY = 'employment_density' #: fasttrips Walk access links column name: Auto Capacity, vehicles per hour per mile. Float. WALK_ACCESS_COLUMN_AUTO_CAPACITY = 'auto_capacity' #: fasttrips Walk access links column name: Indirectness, ratio of Manhattan distance to crow-fly distance. Float. WALK_ACCESS_COLUMN_INDIRECTNESS = 'indirectness' # ========== Added by fasttrips ======================================================= #: Walk access links column name: TAZ Numerical Identifier. Int. WALK_ACCESS_COLUMN_TAZ_NUM = 'taz_num' #: Walk access links column name: Stop Numerical Identifier. Int. WALK_ACCESS_COLUMN_STOP_NUM = 'stop_id_num' #: Walk access links column name: Link walk time. This is a TimeDelta WALK_ACCESS_COLUMN_TIME = 'time' #: Walk access links column name: Link walk time in minutes. This is float. WALK_ACCESS_COLUMN_TIME_MIN = 'time_min' #: Walk acess cost column name: Link generic cost for accessing stop from TAZ. Float. WALK_ACCESS_COLUMN_ACC_COST = 'access_cost' #: Walk acess cost column name: Link generic cost for egressing to TAZ from stop. Float. WALK_ACCESS_COLUMN_EGR_COST = 'egress_cost' #: Walk access links column name: Supply mode. String. WALK_ACCESS_COLUMN_SUPPLY_MODE = 'supply_mode' #: Walk access links column name: Supply mode number. Int. WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM = 'supply_mode_num' #: File with fasttrips drive access information. #: See `drive_access specification <https://github.com/osplanning-data-standards/GTFS-PLUS/blob/master/files/drive_access_ft.md>`_. INPUT_DRIVE_ACCESS_FILE = "drive_access_ft.txt" #: Drive access links column name: TAZ Identifier. String. DRIVE_ACCESS_COLUMN_TAZ = WALK_ACCESS_COLUMN_TAZ #: Drive access links column name: Stop Identifier. String. DRIVE_ACCESS_COLUMN_LOT_ID = 'lot_id' #: Drive access links column name: Direction ('access' or 'egress') DRIVE_ACCESS_COLUMN_DIRECTION = 'direction' #: Drive access links column name: Drive distance DRIVE_ACCESS_COLUMN_DISTANCE = 'dist' #: Drive access links column name: Drive cost in cents (integer) DRIVE_ACCESS_COLUMN_COST = 'cost' #: Drive access links column name: Driving time in minutes between TAZ and lot (TimeDelta) DRIVE_ACCESS_COLUMN_TRAVEL_TIME = 'travel_time' #: Drive access links column name: Start time (e.g. time period these attributes apply), minutes after midnight DRIVE_ACCESS_COLUMN_START_TIME_MIN = 'start_time_min' #: Drive access links column name: Start time (e.g. time period these attributes apply). A DateTime instance DRIVE_ACCESS_COLUMN_START_TIME = 'start_time' #: Drive access links column name: End time (e.g. time period these attributes apply), minutes after midnight DRIVE_ACCESS_COLUMN_END_TIME_MIN = 'end_time_min' #: Drive access links column name: End time (e.g. time period these attributes apply). A DateTime instance DRIVE_ACCESS_COLUMN_END_TIME = 'end_time' #: fasttrips Drive access links column name: Elevation Gain, feet gained along link. DRIVE_ACCESS_COLUMN_ELEVATION_GAIN = 'elevation_gain' #: fasttrips Drive access links column name: Population Density, people per square mile. Float. DRIVE_ACCESS_COLUMN_POPULATION_DENSITY = 'population_density' #: fasttrips Drive access links column name: Retail Density, employees per square mile. Float. DRIVE_ACCESS_COLUMN_RETAIL_DENSITY = 'retail_density' #: fasttrips Drive access links column name: Auto Capacity, vehicles per hour per mile. Float. DRIVE_ACCESS_COLUMN_AUTO_CAPACITY = 'auto_capacity' #: fasttrips Drive access links column name: Indirectness, ratio of Manhattan distance to crow-fly distance. Float. DRIVE_ACCESS_COLUMN_INDIRECTNESS = 'indirectness' # ========== Added by fasttrips ======================================================= #: fasttrips These are the original attributes but renamed to be clear they are the drive component (as opposed to the walk) DRIVE_ACCESS_COLUMN_DRIVE_DISTANCE = 'drive_dist' DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME = 'drive_travel_time' #: Drive access links column name: Driving time in minutes between TAZ and lot (float) DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME_MIN= 'drive_time_min' #: fasttrips Drive access links column name: TAZ Numerical Identifier. Int. DRIVE_ACCESS_COLUMN_TAZ_NUM = WALK_ACCESS_COLUMN_TAZ_NUM #: fasttrips Drive access links column name: Stop Numerical Identifier. Int. DRIVE_ACCESS_COLUMN_STOP = WALK_ACCESS_COLUMN_STOP #: fasttrips Drive access links column name: Stop Numerical Identifier. Int. DRIVE_ACCESS_COLUMN_STOP_NUM = WALK_ACCESS_COLUMN_STOP_NUM #: fasttrips Drive access links column name: Walk distance from lot to transit. Miles. Float. DRIVE_ACCESS_COLUMN_WALK_DISTANCE = 'walk_dist' #: fasttrips Drive access links column name: Walk time from lot to transit. TimeDelta. DRIVE_ACCESS_COLUMN_WALK_TIME = 'walk_time' #: fasttrips Drive access links column name: Walk time from lot to transit. Int. DRIVE_ACCESS_COLUMN_WALK_TIME_MIN = 'walk_time_min' #: fasttrips Drive access links column name: Supply mode. String. DRIVE_ACCESS_COLUMN_SUPPLY_MODE = WALK_ACCESS_COLUMN_SUPPLY_MODE #: Drive access links column name: Supply mode number. Int. DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM = WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM #: File with fasttrips drive access points information. #: See `Drive access points specification <https://github.com/osplanning-data-standards/GTFS-PLUS/blob/master/files/drive_access_points_ft.md>`_. INPUT_DAP_FILE = 'drive_access_points_ft.txt' #: fasttrips DAP column name: Lot ID. String. DAP_COLUMN_LOT_ID = DRIVE_ACCESS_COLUMN_LOT_ID #: fasttrips DAP column name: Lot Latitude (WGS 84) DAP_COLUMN_LOT_LATITUDE = 'lot_lat' #: fasttrips DAP column name: Lot Longitude (WGS 84) DAP_COLUMN_LOT_LONGITUDE = 'lot_lon' #: fasttrips DAP column name: Name of the Lot. String. DAP_COLUMN_NAME = 'name' #: fasttrips DAP column name: Drop-Off. Boolean. DAP_COLUMN_DROP_OFF = 'drop_off' #: fasttrips DAP column name: Capacity (number of parking spaces) DAP_COLUMN_CAPACITY = 'capacity' #: fasttrips DAP column name: Hourly Cost in cents. Integer. DAP_COLUMN_HOURLY_COST = 'hourly_cost' #: fasttrips DAP column name: Maximum Daily Cost in cents. Integer. DAP_COLUMN_MAXIMUM_COST = 'max_cost' #: fasttrips DAP column name: Type DAP_COLUMN_TYPE = 'type' #: mode column MODE_COLUMN_MODE = 'mode' #: mode number MODE_COLUMN_MODE_NUM = 'mode_num' #: access and egress modes. First is default. ACCESS_EGRESS_MODES = ["walk","bike_own","bike_share","PNR","KNR"] #: Access mode: Walk MODE_ACCESS_WALK = 101 #: Access mode: Bike (own) MODE_ACCESS_BIKE_OWN = 102 #: Access mode: Bike (share) MODE_ACCESS_BIKE_SHARE = 103 #: Access mode: Drive to PNR MODE_ACCESS_PNR = 104 #: Access mode: Drive to KNR MODE_ACCESS_KNR = 105 #: Egress mode: Walk MODE_EGRESS_WALK = 201 #: Egress mode: Bike (own) MODE_EGRESS_BIKE_OWN = 202 #: Egress mode: Bike (share) MODE_EGRESS_BIKE_SHARE = 203 #: Egress mode: Drive to PNR MODE_EGRESS_PNR = 204 #: Egress mode: Drive to KNR MODE_EGRESS_KNR = 205 #: Access mode number list, in order of ACCESS_EGRESS_MODES ACCESS_MODE_NUMS = [MODE_ACCESS_WALK, MODE_ACCESS_BIKE_OWN, MODE_ACCESS_BIKE_SHARE, MODE_ACCESS_PNR, MODE_ACCESS_KNR] #: Egress mode number list, in order of ACCESS_EGRESS_MODES EGRESS_MODE_NUMS = [MODE_EGRESS_WALK, MODE_EGRESS_BIKE_OWN, MODE_EGRESS_BIKE_SHARE, MODE_EGRESS_PNR, MODE_EGRESS_KNR] #: Walk mode number list WALK_MODE_NUMS = [MODE_ACCESS_WALK, MODE_EGRESS_WALK] #: Bike mode number list BIKE_MODE_NUMS = [MODE_ACCESS_BIKE_OWN, MODE_ACCESS_BIKE_SHARE, MODE_EGRESS_BIKE_OWN, MODE_EGRESS_BIKE_SHARE] #: Drive mode number list DRIVE_MODE_NUMS = [MODE_ACCESS_PNR, MODE_ACCESS_KNR, MODE_EGRESS_PNR, MODE_EGRESS_KNR] #: File with access/egress links for C++ extension #: It's easier to pass it via a file rather than through the #: initialize_fasttrips_extension() because of the strings involved, I think. OUTPUT_ACCESS_EGRESS_FILE = "ft_intermediate_access_egress.txt"
[docs] def __init__(self, output_dir, gtfs, today, stops, transfers, routes): """ Constructor. Reads the TAZ data from the input files in *input_archive*. """ from .Assignment import Assignment self.access_modes_df = pd.DataFrame(data={TAZ.MODE_COLUMN_MODE :TAZ.ACCESS_EGRESS_MODES, TAZ.MODE_COLUMN_MODE_NUM:TAZ.ACCESS_MODE_NUMS }) self.access_modes_df[TAZ.MODE_COLUMN_MODE] = self.access_modes_df[TAZ.MODE_COLUMN_MODE]\ .apply(lambda x:'%s_%s' % (x, Route.MODE_TYPE_ACCESS)) self.egress_modes_df = pd.DataFrame(data={TAZ.MODE_COLUMN_MODE :TAZ.ACCESS_EGRESS_MODES, TAZ.MODE_COLUMN_MODE_NUM:TAZ.EGRESS_MODE_NUMS }) self.egress_modes_df[TAZ.MODE_COLUMN_MODE] = self.egress_modes_df[TAZ.MODE_COLUMN_MODE]\ .apply(lambda x:'%s_%s' % (x, Route.MODE_TYPE_EGRESS)) routes.add_access_egress_modes(self.access_modes_df, self.egress_modes_df) #: Walk access links table. Make sure TAZ ID and stop ID are read as strings. self.walk_access_df = gtfs.get(TAZ.INPUT_WALK_ACCESS_FILE) # verify required columns are present walk_access_cols = list(self.walk_access_df.columns.values) assert(TAZ.WALK_ACCESS_COLUMN_TAZ in walk_access_cols) assert(TAZ.WALK_ACCESS_COLUMN_STOP in walk_access_cols) assert(TAZ.WALK_ACCESS_COLUMN_DIRECTION in walk_access_cols) assert(TAZ.WALK_ACCESS_COLUMN_DIST in walk_access_cols) # printing this before setting index FastTripsLogger.debug("=========== WALK ACCESS ===========\n" + str(self.walk_access_df.head())) FastTripsLogger.debug("As read\n"+str(self.walk_access_df.dtypes)) # Verify direction is valid invalid_direction = self.walk_access_df.loc[ self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_DIRECTION].isin(["access","egress"])==False ] if len(invalid_direction) > 0: error_msg = "Invalid direction in walk access links: \n%s" % str(invalid_direction) FastTripsLogger.fatal(error_msg) raise NetworkInputError(TAZ.INPUT_WALK_ACCESS_FILE, error_msg) # TODO: remove? Or put walk speed some place? self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_TIME_MIN] = self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_DIST]*60.0/2.7; # convert time column from float to timedelta self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_TIME] = \ self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_TIME_MIN].map(lambda x: datetime.timedelta(minutes=x)) # make sure WALK_ACCESS_COLUMN_TAZ/WALK_ACCESS_COLUMN_DIST is unique walk_access_dupes = self.walk_access_df.duplicated(subset=[TAZ.WALK_ACCESS_COLUMN_TAZ, TAZ.WALK_ACCESS_COLUMN_STOP, TAZ.WALK_ACCESS_COLUMN_DIRECTION], keep=False) if walk_access_dupes.sum() > 0: self.walk_access_df["duplicates"] = walk_access_dupes error_msg = "Duplicate taz/stop pairs in walk access links: \n%s" % str(self.walk_access_df.loc[ self.walk_access_df["duplicates"]]) FastTripsLogger.fatal(error_msg) raise NetworkInputError(TAZ.INPUT_WALK_ACCESS_FILE, error_msg) FastTripsLogger.debug("Final\n"+str(self.walk_access_df.dtypes)) FastTripsLogger.info("Read %7d %15s from %25s" % (len(self.walk_access_df), "walk access", TAZ.INPUT_WALK_ACCESS_FILE)) self.dap_df = gtfs.get(TAZ.INPUT_DAP_FILE) if not self.dap_df.empty: # verify required columns are present dap_cols = list(self.dap_df.columns.values) assert(TAZ.DAP_COLUMN_LOT_ID in dap_cols) assert(TAZ.DAP_COLUMN_LOT_LATITUDE in dap_cols) assert(TAZ.DAP_COLUMN_LOT_LONGITUDE in dap_cols) # default capacity = 0 if TAZ.DAP_COLUMN_CAPACITY not in dap_cols: self.dap_df[TAZ.DAP_COLUMN_CAPACITY] = 0 # default drop-off = True if TAZ.DAP_COLUMN_DROP_OFF not in dap_cols: self.dap_df[TAZ.DAP_COLUMN_DROP_OFF] = True else: self.dap_df = pd.DataFrame() FastTripsLogger.debug("=========== DAPS ===========\n" + str(self.dap_df.head())) FastTripsLogger.debug("\n"+str(self.dap_df.dtypes)) FastTripsLogger.info("Read %7d %15s from %25s" % (len(self.dap_df), "DAPs", TAZ.INPUT_DAP_FILE)) #: Drive access links table. Make sure TAZ ID and lot ID are read as strings. self.drive_access_df = gtfs.get(TAZ.INPUT_DRIVE_ACCESS_FILE) if not self.drive_access_df.empty: # verify required columns are present drive_access_cols = list(self.drive_access_df.columns.values) assert(TAZ.DRIVE_ACCESS_COLUMN_TAZ in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_LOT_ID in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_DIRECTION in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_DISTANCE in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_COST in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_TRAVEL_TIME in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_START_TIME in drive_access_cols) assert(TAZ.DRIVE_ACCESS_COLUMN_END_TIME in drive_access_cols) # printing this before setting index FastTripsLogger.debug("=========== DRIVE ACCESS ===========\n" + str(self.drive_access_df.head())) FastTripsLogger.debug("As read\n"+str(self.drive_access_df.dtypes)) # Rename dist to drive_dist # the distance and times here are for DRIVING self.drive_access_df.rename( columns = {TAZ.DRIVE_ACCESS_COLUMN_DISTANCE : TAZ.DRIVE_ACCESS_COLUMN_DRIVE_DISTANCE, TAZ.DRIVE_ACCESS_COLUMN_TRAVEL_TIME : TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME}, inplace=True) self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME_MIN] = \ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME] # if there are any that go past midnight, duplicate sim_day_end = Assignment.NETWORK_BUILD_DATE_START_TIME + datetime.timedelta(days=1) dupes = self.drive_access_df.loc[self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_END_TIME] > sim_day_end, :].copy() if len(dupes) > 0: # e.g. 18:00 - 27:00 # dupe: 00:00 - 3:00 dupes.loc[ dupes[TAZ.DRIVE_ACCESS_COLUMN_END_TIME] > sim_day_end, TAZ.DRIVE_ACCESS_COLUMN_START_TIME] = Assignment.NETWORK_BUILD_DATE_START_TIME dupes.loc[ dupes[TAZ.DRIVE_ACCESS_COLUMN_END_TIME] > sim_day_end, TAZ.DRIVE_ACCESS_COLUMN_END_TIME ] = dupes[TAZ.DRIVE_ACCESS_COLUMN_END_TIME] - datetime.timedelta(days=1) # orig: 18:00 - 24:00 self.drive_access_df.loc[ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_END_TIME] > sim_day_end, TAZ.DRIVE_ACCESS_COLUMN_END_TIME ] = sim_day_end FastTripsLogger.debug("Added %d morning hour drive access links. Head:\n%s" % (len(dupes), dupes.head().to_string())) # combine self.drive_access_df = self.drive_access_df.append(dupes) # drive access period start/end time: float version self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_START_TIME_MIN] = \ (self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_START_TIME] - Assignment.NETWORK_BUILD_DATE_START_TIME)/np.timedelta64(1,'m') self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_END_TIME_MIN] = \ (self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_END_TIME] - Assignment.NETWORK_BUILD_DATE_START_TIME)/np.timedelta64(1,'m') # convert time column from number to timedelta self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME] = \ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME_MIN].map(lambda x: datetime.timedelta(minutes=float(x))) # need PNRs and KNRs - get them from the dap knr_dap_df = self.dap_df.loc[self.dap_df[TAZ.DAP_COLUMN_DROP_OFF]==True].copy() pnr_dap_df = self.dap_df.loc[self.dap_df[TAZ.DAP_COLUMN_CAPACITY] > 0 ].copy() knr_dap_df['dap_type'] = 'KNR' pnr_dap_df['dap_type'] = 'PNR' self.drive_access_df = pd.merge(left=self.drive_access_df, right=pd.concat([knr_dap_df,pnr_dap_df], axis=0), on=TAZ.DRIVE_ACCESS_COLUMN_LOT_ID, how='left') # look for required column being null lots_not_found = self.drive_access_df.loc[pd.isnull(self.drive_access_df[TAZ.DAP_COLUMN_LOT_LATITUDE])] if len(lots_not_found) > 0: error_msg = "Found %d drive access links in %s with lots not specified in %s" % \ (len(lots_not_found), TAZ.INPUT_DRIVE_ACCESS_FILE, TAZ.INPUT_DAP_FILE) FastTripsLogger.fatal(error_msg) FastTripsLogger.fatal("\nFirst five drive access links with lots not found:\n%s" % \ str(lots_not_found.head().to_string())) raise NetworkInputError(TAZ.INPUT_DAP_FILE, error_msg) self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE] = \ self.drive_access_df['dap_type'] + '_' + \ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DIRECTION] # done with this self.drive_access_df.drop(['dap_type'], axis=1, inplace=True) # We're going to join this with stops to get drive-to-stop drive_access = self.drive_access_df.loc[self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DIRECTION] == 'access'] drive_egress = self.drive_access_df.loc[self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DIRECTION] == 'egress'] # join with transfers to go from taz -> lot -> stop drive_access = pd.merge(left=drive_access, right=transfers.transfers_df, left_on=TAZ.DRIVE_ACCESS_COLUMN_LOT_ID, right_on=Transfer.TRANSFERS_COLUMN_FROM_STOP, how='left') drive_access[TAZ.DRIVE_ACCESS_COLUMN_STOP] = drive_access[Transfer.TRANSFERS_COLUMN_TO_STOP] # join with transfers to go from stop -> lot -> taz drive_egress = pd.merge(left=drive_egress, right=transfers.transfers_df, left_on=TAZ.DRIVE_ACCESS_COLUMN_LOT_ID, right_on=Transfer.TRANSFERS_COLUMN_TO_STOP, how='left') drive_egress[TAZ.DRIVE_ACCESS_COLUMN_STOP] = drive_egress[Transfer.TRANSFERS_COLUMN_FROM_STOP] self.drive_access_df = pd.concat([drive_access, drive_egress], axis=0) # drop redundant columns # TODO: assuming min_transfer_type and transfer_type from GTFS aren't relevant here, since # the time and dist are what matter. # Assuming schedule_precedence doesn't make sense in the drive access/egress context self.drive_access_df.drop([Transfer.TRANSFERS_COLUMN_FROM_STOP, Transfer.TRANSFERS_COLUMN_TO_STOP, Transfer.TRANSFERS_COLUMN_TRANSFER_TYPE, Transfer.TRANSFERS_COLUMN_MIN_TRANSFER_TIME, Transfer.TRANSFERS_COLUMN_SCHEDULE_PRECEDENCE, Transfer.TRANSFERS_COLUMN_PENALTY], axis=1, inplace=True) # not relevant for drive access if Transfer.TRANSFERS_COLUMN_FROM_ROUTE in list(self.drive_access_df.columns.values): self.drive_access_df.drop([Transfer.TRANSFERS_COLUMN_FROM_ROUTE], axis=1, inplace=True) if Transfer.TRANSFERS_COLUMN_TO_ROUTE in list(self.drive_access_df.columns.values): self.drive_access_df.drop([Transfer.TRANSFERS_COLUMN_TO_ROUTE], axis=1, inplace=True) if Transfer.TRANSFERS_COLUMN_MIN_TRANSFER_TIME_MIN in list(self.drive_access_df.columns.values): self.drive_access_df.drop([Transfer.TRANSFERS_COLUMN_MIN_TRANSFER_TIME_MIN], axis=1, inplace=True) # some may have no lot to stop connections -- check for null stop ids null_stop_ids = self.drive_access_df.loc[pd.isnull( self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_STOP])] if len(null_stop_ids) > 0: FastTripsLogger.warn("Dropping %d drive links that don't connect to stops:\n%s" % (len(null_stop_ids), str(null_stop_ids))) # drop them self.drive_access_df = self.drive_access_df.loc[ pd.notnull(self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_STOP])] # rename walk attributes to be clear self.drive_access_df.rename( columns={ Transfer.TRANSFERS_COLUMN_DISTANCE:TAZ.DRIVE_ACCESS_COLUMN_WALK_DISTANCE, Transfer.TRANSFERS_COLUMN_TIME :TAZ.DRIVE_ACCESS_COLUMN_WALK_TIME, Transfer.TRANSFERS_COLUMN_TIME_MIN:TAZ.DRIVE_ACCESS_COLUMN_WALK_TIME_MIN}, inplace=True) # add generic distance and time self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DISTANCE] = self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_WALK_DISTANCE] + \ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_DISTANCE] self.drive_access_df["time_min"] = self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_WALK_TIME_MIN] + \ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME_MIN] FastTripsLogger.debug("Final (%d) types:\n%s\nhead:\n%s" % (len(self.drive_access_df), str(self.drive_access_df.dtypes), str(self.drive_access_df.head()))) FastTripsLogger.info("Read %7d %15s from %25s" % (len(self.drive_access_df), "drive access", TAZ.INPUT_DRIVE_ACCESS_FILE)) self.has_drive_access = True else: self.has_drive_access = False self.drive_access_df = pd.DataFrame(columns=[TAZ.DRIVE_ACCESS_COLUMN_TAZ, TAZ.DRIVE_ACCESS_COLUMN_LOT_ID]) FastTripsLogger.debug("=========== NO DRIVE ACCESS ===========\n") # add DAPs IDs and TAZ IDs to stop ID list stops.add_daps_tazs_to_stops(self.drive_access_df[[TAZ.DRIVE_ACCESS_COLUMN_LOT_ID]], TAZ.DRIVE_ACCESS_COLUMN_LOT_ID, pd.concat([self.walk_access_df[[TAZ.WALK_ACCESS_COLUMN_TAZ]], self.drive_access_df[[TAZ.DRIVE_ACCESS_COLUMN_TAZ]]], axis=0), TAZ.WALK_ACCESS_COLUMN_TAZ) # transfers can add stop numeric IDs now that DAPs are available transfers.add_numeric_stop_id(stops) # Add numeric stop ID to walk access links self.walk_access_df = stops.add_numeric_stop_id(self.walk_access_df, id_colname=TAZ.WALK_ACCESS_COLUMN_STOP, numeric_newcolname=TAZ.WALK_ACCESS_COLUMN_STOP_NUM, warn=True, warn_msg="Numeric stop id not found for walk access links") # Add TAZ stop ID to walk and drive access links self.walk_access_df = stops.add_numeric_stop_id(self.walk_access_df, id_colname=TAZ.WALK_ACCESS_COLUMN_TAZ, numeric_newcolname=TAZ.WALK_ACCESS_COLUMN_TAZ_NUM) # These have direction now. Set supply mode string self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE] = "walk_" + self.walk_access_df[TAZ.WALK_ACCESS_COLUMN_DIRECTION] self.walk_access_df = routes.add_numeric_mode_id(self.walk_access_df, id_colname=TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE, numeric_newcolname=TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM) if self.has_drive_access: print(self.drive_access_df.loc[ self.drive_access_df[TAZ.DRIVE_ACCESS_COLUMN_STOP] == "9065"]) self.drive_access_df = stops.add_numeric_stop_id(self.drive_access_df, id_colname=TAZ.DRIVE_ACCESS_COLUMN_STOP, numeric_newcolname=TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, warn=True, warn_msg="Drive access stops missing ids") self.drive_access_df = stops.add_numeric_stop_id(self.drive_access_df, id_colname=TAZ.DRIVE_ACCESS_COLUMN_TAZ, numeric_newcolname=TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM) self.drive_access_df = routes.add_numeric_mode_id(self.drive_access_df, id_colname=TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE, numeric_newcolname=TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM) # warn on stops that have no walk access self.warn_on_stops_without_walk_access(stops) # write this to communicate to extension self.write_access_egress_for_extension(output_dir)
[docs] def add_distance(self, links_df, dist_col): """ Sets distance column value for access and egress links. .. todo:: This neglects the start_time/end_time issue. Don't use without fixing. """ ############## walk ############## walk_dists = self.walk_access_df[[TAZ.WALK_ACCESS_COLUMN_TAZ_NUM, TAZ.WALK_ACCESS_COLUMN_STOP_NUM, TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM, TAZ.WALK_ACCESS_COLUMN_DIST]].copy() walk_dists.rename(columns={TAZ.WALK_ACCESS_COLUMN_DIST:"walk_dist"}, inplace=True) # walk access links_df = pd.merge(left =links_df, left_on =["A_id_num","B_id_num","mode_num"], right =walk_dists, right_on=[TAZ.WALK_ACCESS_COLUMN_TAZ_NUM, TAZ.WALK_ACCESS_COLUMN_STOP_NUM, TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM], how ="left") links_df.loc[ pd.notnull(links_df["walk_dist"]), dist_col ] = links_df["walk_dist"] links_df.drop([TAZ.WALK_ACCESS_COLUMN_TAZ_NUM, TAZ.WALK_ACCESS_COLUMN_STOP_NUM, TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM, "walk_dist"], axis=1, inplace=True) # walk egress links_df = pd.merge(left =links_df, left_on =["A_id_num","B_id_num","mode_num"], right =walk_dists, right_on=[TAZ.WALK_ACCESS_COLUMN_STOP_NUM, TAZ.WALK_ACCESS_COLUMN_TAZ_NUM, TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM], how ="left") links_df.loc[ pd.notnull(links_df["walk_dist"]), dist_col ] = links_df["walk_dist"] links_df.drop([TAZ.WALK_ACCESS_COLUMN_TAZ_NUM, TAZ.WALK_ACCESS_COLUMN_STOP_NUM, TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM, "walk_dist"], axis=1, inplace=True) ############## drive ############## FastTripsLogger.debug("drive_access_df=\n%s" % self.drive_access_df.head()) if len(self.drive_access_df) > 0: drive_dists = self.drive_access_df[[TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM, TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM, TAZ.DRIVE_ACCESS_COLUMN_DRIVE_DISTANCE, TAZ.DRIVE_ACCESS_COLUMN_WALK_DISTANCE, TAZ.DRIVE_ACCESS_COLUMN_START_TIME, TAZ.DRIVE_ACCESS_COLUMN_END_TIME]].copy() drive_dists["drive_total_dist"] = drive_dists[TAZ.DRIVE_ACCESS_COLUMN_DRIVE_DISTANCE] + drive_dists[TAZ.DRIVE_ACCESS_COLUMN_WALK_DISTANCE] drive_dists.drop([TAZ.DRIVE_ACCESS_COLUMN_DRIVE_DISTANCE, TAZ.DRIVE_ACCESS_COLUMN_WALK_DISTANCE], axis=1, inplace=True) # drive access links_df = pd.merge(left =links_df, left_on =["A_id_num","B_id_num","mode_num"], right =drive_dists, right_on=[TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM, TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM], how ="left") # TODO: drop those with drive access links covering different times links_df.loc[ pd.notnull(links_df["drive_total_dist"]), dist_col ] = links_df["drive_total_dist"] links_df.drop([TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM, TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM, "drive_total_dist"], axis=1, inplace=True) # drive egress links_df = pd.merge(left =links_df, left_on =["A_id_num","B_id_num","mode_num"], right =drive_dists, right_on=[TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM, TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM], how ="left") links_df.loc[ pd.notnull(links_df["drive_total_dist"]), dist_col ] = links_df["drive_total_dist"] links_df.drop([TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM, TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM, "drive_total_dist"], axis=1, inplace=True) FastTripsLogger.debug("links_df=\n%s" % links_df.head(30).to_string()) return links_df
[docs] def warn_on_stops_without_walk_access(self, stops): """ Do any stops lack *any* walk access? """ # FastTripsLogger.debug("warn_on_stops_without_walk_access: \n%s", stops.stops_df.head() ) # FastTripsLogger.debug("warn_on_stops_without_walk_access: \n%s", self.walk_access_df.head() ) # join stops to walk access no_access_stops = pd.merge(left = stops.stops_df[[Stop.STOPS_COLUMN_STOP_ID]], right = self.walk_access_df[[TAZ.WALK_ACCESS_COLUMN_STOP, TAZ.WALK_ACCESS_COLUMN_TAZ]], how = "left") no_access_stops = no_access_stops.loc[pd.isnull(no_access_stops[TAZ.WALK_ACCESS_COLUMN_TAZ])] if len(no_access_stops) > 0: FastTripsLogger.warn("The following %d stop ids have no walk access: \n%s" % (len(no_access_stops), no_access_stops.to_string()))
[docs] def write_access_egress_for_extension(self, output_dir): """ Write the access and egress links to a single output file for the C++ extension to read. It's in this form because I'm not sure how to pass the strings to C++ in Assignment.initialize_fasttrips_extension so I know that's inconsistent, but it's a time sink to investigate, so I'll leave this for now .. todo:: clean this up? Rename intermediate files (they're not really output) """ # ========== Walk access/egres ================================================= # print "walk_access columns" # for col in list(self.walk_access_df.columns): print " %s" % col # start with all walk columns self.walk_df = self.walk_access_df.copy() # drop the redundant columns drop_fields = [TAZ.WALK_ACCESS_COLUMN_TAZ, # use numerical version TAZ.WALK_ACCESS_COLUMN_STOP, # use numerical version TAZ.WALK_ACCESS_COLUMN_DIRECTION, # it's in the supply mode num TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE, # use numerical version TAZ.WALK_ACCESS_COLUMN_TIME, # use numerical version ] # we can only drop fields that are in the dataframe walk_fields = list(self.walk_df.columns.values) valid_drop_fields = [] for field in drop_fields: if field in walk_fields: valid_drop_fields.append(field) self.walk_df.drop(valid_drop_fields, axis=1, inplace=True) # make walk access valid all times -- need this for consistency self.walk_df[TAZ.DRIVE_ACCESS_COLUMN_START_TIME_MIN] = 0.0 self.walk_df[TAZ.DRIVE_ACCESS_COLUMN_END_TIME_MIN ] = 60.0*24.0 # the index is TAZ num, supply mode num, and stop num self.walk_df.set_index([TAZ.WALK_ACCESS_COLUMN_TAZ_NUM, TAZ.WALK_ACCESS_COLUMN_SUPPLY_MODE_NUM, TAZ.WALK_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_START_TIME_MIN, TAZ.DRIVE_ACCESS_COLUMN_END_TIME_MIN], inplace=True) # ========== Drive access/egres ================================================= self.drive_df = self.drive_access_df.copy() # print "drive_access columns" # for col in list(self.drive_access_df.columns): print " %s" % col # TEMP drive_fields = list(self.drive_df.columns.values) # drop some of the attributes drop_fields = [TAZ.DRIVE_ACCESS_COLUMN_TAZ, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_STOP, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_DRIVE_TRAVEL_TIME, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_START_TIME, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_END_TIME, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_WALK_TIME, # use numerical version TAZ.DRIVE_ACCESS_COLUMN_DIRECTION, # redundant with supply mode TAZ.DAP_COLUMN_DROP_OFF, # redundant with supply mode TAZ.DAP_COLUMN_LOT_LATITUDE, # probably not useful TAZ.DAP_COLUMN_LOT_LONGITUDE, # probably not useful TAZ.DRIVE_ACCESS_COLUMN_LOT_ID, # probably not useful ] valid_drop_fields = [] for field in drop_fields: if field in drive_fields: valid_drop_fields.append(field) self.drive_df.drop(valid_drop_fields, axis=1, inplace=True) # the index is TAZ num, supply mode num, and stop num if len(self.drive_df) > 0: self.drive_df.set_index([TAZ.DRIVE_ACCESS_COLUMN_TAZ_NUM, TAZ.DRIVE_ACCESS_COLUMN_SUPPLY_MODE_NUM, TAZ.DRIVE_ACCESS_COLUMN_STOP_NUM, TAZ.DRIVE_ACCESS_COLUMN_START_TIME_MIN, TAZ.DRIVE_ACCESS_COLUMN_END_TIME_MIN], inplace=True) # stack() this will make it so beyond taz num, supply mode num, and stop num # the remaining columns collapse to variable name, variable value # put walk and drive together access_df = pd.concat([self.walk_df.stack(), self.drive_df.stack()], axis=0).to_frame() else: access_df = self.walk_df.stack().to_frame() access_df.reset_index(inplace=True) # rename from these default column names access_df.rename(columns={"level_3":"attr_name", 0:"attr_value"}, inplace=True) # make attr_value a float instead of an object access_df["attr_value"] = access_df["attr_value"].astype(float) FastTripsLogger.debug("\n" + str(access_df.head())) FastTripsLogger.debug("\n" + str(access_df.tail())) # Check for null stop ids null_stop_ids = access_df.loc[pd.isnull(access_df["stop_id_num"])] if len(null_stop_ids) > 0: FastTripsLogger.warn("write_access_egress_for_extension null_stop_ids:\n%s" % str(null_stop_ids)) # for now, drop rows with null stop id nums access_df = access_df.loc[ pd.notnull(access_df["stop_id_num"]) ] access_df["stop_id_num"] = access_df["stop_id_num"].astype(int) access_df.to_csv(os.path.join(output_dir, TAZ.OUTPUT_ACCESS_EGRESS_FILE), sep=" ", index=False) FastTripsLogger.debug("Wrote %s" % os.path.join(output_dir, TAZ.OUTPUT_ACCESS_EGRESS_FILE))