├── README.md ├── RouteFinder1.0 ├── .ipynb_checkpoints │ └── how_to_use_RouteFinder1.0-checkpoint.ipynb ├── MakeCluster.py ├── RouteFinder.py ├── get_data.py ├── how_to_use_RouteFinder1.0.ipynb └── plotting_functions.py ├── RouteFinder2.0 ├── .ipynb_checkpoints │ ├── GetData_RouteFinder2.0-checkpoint.ipynb │ └── Routing-checkpoint.ipynb ├── AoA.py ├── CurrentSystem.py ├── GetData_RouteFinder2.0.ipynb ├── GetPopMetric.py ├── Intersections.py ├── MakeStops.py ├── Reduce.py ├── RouteFinder2.py ├── Routing.ipynb ├── __pycache__ │ ├── AoA.cpython-36.pyc │ ├── CurrentSystem.cpython-36.pyc │ ├── GetPopMetric.cpython-36.pyc │ ├── Intersections.cpython-36.pyc │ ├── MakeStops.cpython-36.pyc │ ├── Reduce.cpython-36.pyc │ ├── downloadGEOdata.cpython-36.pyc │ ├── getPop_df.cpython-36.pyc │ ├── joinBlockPopGEOs.cpython-36.pyc │ └── myAPIs.cpython-36.pyc ├── downloadGEOdata.py ├── getPop_df.py └── joinBlockPopGEOs.py └── __pycache__ ├── MakeCluster.cpython-36.pyc ├── RouteFinder.cpython-36.pyc ├── clustering.cpython-36.pyc ├── get_data.cpython-36.pyc └── plotting_functions.cpython-36.pyc /README.md: -------------------------------------------------------------------------------- 1 | # Bus Routing with Genetic Algorithms for the City of Austin 2 | 3 | ## Objective 4 | Build a __Genetic Algorithm__ modeling class that can be used to optimize bus transit routes for Austin's public transportation system with the purpose of attracting more riders and relieving road congestion. 5 | 6 | ## Background 7 | City governments and researchers have been studying different ways to design efficient and useful transportation networks for over a century. Industry calls it the __Transit Network Design Problem__ and it aims to optimize across many objectives with a large potential solution space. Non-linear problems like this one have seen success using a Genetic Algorithm approach. 8 | 9 | In the City of Austin, Capital Metro ridership rates have seen steady declines in their fixed route services. At this moment, the City of Austin is in the final stages of approving a new strategic mobility plan: [2025 Austin Metropolitan Area Transportation Plan](http://austintexas.gov/asmp "2025 AMATP"). In addition, Capital Metro has their own [Connections 2025](http://connections2025.org/ "Connections 2025") which is their answer to building a better connected transit system over the next 5-10 years. The current proposal by the City of Austin and Capital Metro appears well thought out and is excellently presented for public adoption. However, I have not been able to ascertain the scientific or data-driven techniques they used in creating their plan. 10 | 11 | * [Connections 2025 Fact Sheet](http://connections2025.org/wp-content/uploads/2016/01/Connections2025-factSheet-final-eng.pdf "Connections 2025 Facts") 12 | * [Market & Service Fact Sheet](http://connections2025.org/wp-content/uploads/2016/05/Connections2025_factsheet.pdf "Fact Sheet") 13 | 14 | The [Downtown Alliance](http://www.downtownaustin.com/daa/transportation) also has a good web page for referencing all the major organizations involved in transportation and mobility for the City of Austin. 15 | 16 | In researching this problem I found several scientific studies. Below is an example of one methodology used in India. 17 | 18 | * [Optimal Route Network Design For Transit Systems Using Genetic Algorithms](http://home.iitk.ac.in/~partha/eng-opt02): In this 2002 paper, the author uses a three step iterative process. 19 | 1. Initial Route Set Generation (IRSG) with a pre-specified number of routes. Each route is determined by first selecting a starting node, then selecting all other nodes sequentially until either (i) the number of nodes reaches a maximum number, or (ii) the route length reaches a maximum. 20 | 2. Evaluation of Route Set: A set of 'route goodness' metrics are determined and a single 'route goodness' score is calculated. 21 | 3. Route Modification: utilizes a genetic algorithm to iterate over the routes in search for a better 'route goodness' score. 22 | 23 | This [Stanford study](http://cs229.stanford.edu/proj2013/HuangLing-OptimizingPublicTransit.pdf "Optimizing Public Transit") uses a linear Regression model, and data from 25 U.S. cities to predict optimal station locations based on estimated ridership. 24 | 25 | I also found this [Master Thesis](https://oatd.org/oatd/record?record=handle%5C%3A11427%5C%2F13368) from the University of Cape Town that looked into different heuristic algorithms and settled on using a Genetic Algorithm. 26 | 27 | 28 | ## Approach 29 | My thesis is that the current bus route network is not convenient or useful enough for a large portion of the population. My goal is to minimize travel time and maximize network reach. The first problem to solve is to answer the question "where do bus stops need to be located?" I will initialize a new set of bus stops using block-level [population](http://connections2025.org/wp-content/uploads/2016/02/CapMetro_2010PopEmp.pdf "Population & Employment Density") data from the 2010 census. I will use this to build a __weighted k-means algorithm__. Then, I will define the regions and choose a good location for each regions transfer terminal. Then, I will implement a RouteFinder class, that uses a genetic algorithm to search for optimal routing within a given region using distance between nodes for a fitness function. 30 | 31 | I believe this approach will be successful because other cities have seen success with this method. I used the example from [Curitiba, Brazil](https://www.slideshare.net/TheMissionGroup/a-market-focused-paradigm-for-public-transit-pt-3-designing-effective-transit-networks) in designing this strategy, since they are known in the transportation industry as a model city on this topic. My theory is that making a fast and well interconnected network will encourage daily commuters to choose public transit over driving. 32 | 33 | 34 | ## Format 35 | Python class stored in a python file and demonstrated in a JupyerNotebook. 36 | 37 | ## Data Sources 38 | Austin Texas 2010 Census TIGER/Line Shapefiles downloaded from the [Census Bureau](https://www.census.gov/geo/maps-data/data/tiger-line.html) 39 | 40 | ## Next Steps 41 | * Build the weighted-K-Means Algorithm 42 | * Use an a-star graph search and weigh the edges (or paths) by average traffic flow speed. 43 | * evaluate each region 44 | * Further enrich population data with jobs data 45 | -------------------------------------------------------------------------------- /RouteFinder1.0/MakeCluster.py: -------------------------------------------------------------------------------- 1 | #------------------------------# 2 | # IMPORT LIBRARIES 3 | #------------------------------# 4 | 5 | import geopandas as gpd 6 | from geopandas import GeoSeries 7 | import pandas as pd 8 | import warnings 9 | warnings.filterwarnings("ignore") 10 | from sklearn.cluster import KMeans 11 | from shapely.geometry import Point, Polygon 12 | from shapely.geometry import LineString, shape 13 | from shapely.prepared import prep 14 | 15 | 16 | 17 | class MakeClusters: 18 | def __init__(self,centroids_geo_df, streets_df): 19 | ''' 20 | INPUT: centroids_geo_df = centroids GeoDatFrame returned from clean_census_data() function 21 | streets_df = 22 | ''' 23 | self.centroids_geo_df = centroids_geo_df 24 | self.streets_df = streets_df 25 | self.n_clusters = None 26 | self.random_state = 0 27 | self.clusters_df = None 28 | self.model = None 29 | 30 | 31 | def fit(self, n_clusters): 32 | self.n_clusters = n_clusters 33 | #extracy coordinates from df 34 | self.centroids_geo_df.set_geometry('centroids', inplace=True) 35 | x = self.centroids_geo_df.geometry.x.values 36 | y = self.centroids_geo_df.geometry.y.values 37 | lat_long = [[x[i],y[i]] for i in range(x.shape[0])] 38 | 39 | 40 | # Perform KMeans Clustering 41 | self.model = KMeans(n_clusters=self.n_clusters, random_state=self.random_state) 42 | self.model.fit(lat_long) 43 | 44 | # Make Clusters df 45 | clusters = [Point(x[0],x[1]) for x in self.model.cluster_centers_] 46 | clusters_df = pd.DataFrame(clusters) 47 | clusters_df.columns = ['cluster_pts'] 48 | self.clusters_df = gpd.GeoDataFrame(clusters_df, crs = {'init': 'epsg:4326'}, geometry='cluster_pts') 49 | 50 | 51 | def snap_one(self, point, rad=0): 52 | ''' 53 | This function searches a radius around a centroid to find nearest 54 | roads. Then finds the road with the minimum distance to the centroid and returns 55 | the coordinates of that location on the road. 56 | 57 | INPUT: point = Point Object returned from make_clusters() function 58 | rad = initial search radius 59 | 60 | OUTPUT: new_point 61 | ''' 62 | # 1. Start with an empty list of roads 63 | line_list = [] 64 | # 2. Search for closest roads to the point 65 | while line_list == []: 66 | #Define search radius 67 | rad = rad + .01 68 | search_radius = point.buffer(rad) 69 | #Prepare my search radius 70 | prep_radius = prep(search_radius) 71 | #Make a list of near-by objects 72 | hit_list = list(filter(prep_radius.contains, self.streets_df.geometry.values)) 73 | #Filter the list of objects to only include roads (not other point objects) 74 | line_list = [item for item in hit_list if shape(item).geom_type == 'LineString'] 75 | 76 | 77 | # 3. Select nearest road to point 78 | road = None 79 | value = None 80 | for rd in line_list: 81 | val = rd.distance(point) 82 | if road == None: 83 | road = rd 84 | value = val 85 | if val < value: 86 | road = rd 87 | value = val 88 | else: 89 | continue 90 | 91 | # 4. Use linear referencing method to return a new point object 92 | dist = road.project(point) 93 | new_pt = road.interpolate(dist) 94 | 95 | return new_pt 96 | 97 | def snap(self): 98 | #modifiy clusters_df with new point coordinates snapped to road network 99 | streets = self.streets_df.geometry.values 100 | clusters = self.clusters_df.geometry.values 101 | self.clusters_df['new_geometry'] = [self.snap_one(pt) for pt in clusters] 102 | self.clusters_df.set_geometry('new_geometry', inplace=True) 103 | -------------------------------------------------------------------------------- /RouteFinder1.0/RouteFinder.py: -------------------------------------------------------------------------------- 1 | #------------------------------# 2 | # IMPORT LIBRARIES 3 | #------------------------------# 4 | 5 | import geopandas as gpd 6 | from geopandas import GeoSeries 7 | import pandas as pd 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import warnings 11 | warnings.filterwarnings("ignore") 12 | from sklearn.cluster import KMeans 13 | from shapely.geometry import Point, Polygon, box 14 | from shapely.geometry import LineString, shape 15 | from shapely.prepared import prep 16 | import random 17 | from get_data import local_roads 18 | 19 | #----------------------------------------# 20 | # ROUTE DISCOVERY GENETIC ALGORITHM CLASS 21 | #----------------------------------------# 22 | 23 | 24 | class RouteFinder: 25 | def __init__(self): 26 | ''' 27 | INPUT: bus_stops = list of shapely point objects 28 | ''' 29 | self.bus_stops = None 30 | self.bus_stops_geo = None 31 | self.minx = None 32 | self.maxx = None 33 | self.miny = None 34 | self.maxy = None 35 | self.roads = None 36 | self.region = None 37 | self.N = None 38 | self.station = None 39 | self.routes_dict = None 40 | self.end_a_key = None 41 | self.end_b_key = None 42 | self.route_list = None 43 | 44 | def evaluation_area(self, tract_list, zone_name, tracts_df, stops_df, file_path): 45 | ''' 46 | INPUT: zone_name = str; string to name the zone 47 | tract_list = list of strings; list of census tracts to evaluate 48 | tracts_df = GeoDataFrame returned from make_tract_geos() function 49 | stops_df = GeoDataFrame returned from snap_all() function 50 | file_path = str; path to roads shapefile 51 | ''' 52 | # Create boundary shape of evaluation zone 53 | zone_df = tracts_df[tracts_df.TRACT.isin(tract_list)] 54 | for row in range(zone_df.shape[0]-1): 55 | if row == 0: 56 | current = zone_df.geometry.values[row] 57 | else: 58 | current = boundary_poly 59 | next_ = zone_df.geometry.values[row+1] 60 | boundary_poly = current.union(next_) 61 | boundary_poly = GeoSeries(boundary_poly) 62 | boundary_poly.crs = {'init': 'epsg:4326'} 63 | self.region = boundary_poly 64 | 65 | # Identify stops inside the evaluation zone 66 | stops_df['in_zone'] = stops_df.geometry.intersects(self.region[0]) 67 | self.bus_stops = stops_df[stops_df.in_zone == True].geometry.values 68 | self.bus_stops_geo = self.transform_to_GeoDataFrame(self.bus_stops) 69 | 70 | # Define Boundary Box 71 | self.minx = self.region.bounds.minx.values[0] 72 | self.maxx = self.region.bounds.maxx.values[0] 73 | self.miny = self.region.bounds.miny.values[0] 74 | self.maxy = self.region.bounds.maxy.values[0] 75 | 76 | # Identify roads inside the boundary box 77 | road_box = box(self.minx, self.miny, self.maxx, self.maxy) 78 | self.roads = local_roads(file_path, road_box)[0] 79 | 80 | 81 | 82 | 83 | def fit(self, start_pt, end_a, end_b, epochs, N=4): 84 | ''' 85 | INPUT: start_pt = shapely point object (the area's main transfer station) 86 | end_a = shapely point object 87 | end_b = shapely point object 88 | epochs = integer 89 | ''' 90 | #-------------------------------------------------# 91 | # Make a Dictionary of Stop ID's and Stop Locations 92 | #-------------------------------------------------# 93 | e = enumerate(self.bus_stops) 94 | stops_dict = {'start':start_pt} 95 | for idx, stop in e: 96 | if stop.x == end_a.x and stop.y == end_a.y: 97 | stops_dict[idx] = stop 98 | self.end_a_key = idx 99 | if stop.x == end_b.x and stop.y == end_b.y: 100 | stops_dict[idx] = stop 101 | self.end_b_key = idx 102 | else: 103 | stops_dict[idx] = stop 104 | self.routes_dict = stops_dict 105 | self.station = start_pt 106 | self.N = N 107 | 108 | #-------------------------------------------------# 109 | # Create List of Epoch Trials 110 | #-------------------------------------------------# 111 | route_list = [] 112 | for run in range(epochs): 113 | route_a , route_b = self.route_maker() 114 | total_fitness = self.fitness(route_a) + self.fitness(route_b) 115 | if len(route_list) < N: 116 | route_list.append((total_fitness, route_a, route_b)) 117 | route_list = sorted(route_list, key=lambda x: x[0]) 118 | if total_fitness < route_list[-1][0]: 119 | route_list.pop() 120 | route_list.append((total_fitness, route_a, route_b)) 121 | route_list = sorted(route_list, key=lambda x: x[0]) 122 | else: 123 | continue 124 | self.route_list = route_list 125 | 126 | 127 | def route_maker(self): 128 | 129 | #-------------------------------------------------# 130 | # randomly assign stops to routes 131 | #-------------------------------------------------# 132 | exclude_stops = ['start', self.end_a_key, self.end_b_key] 133 | stop_ids = [stop for stop in self.routes_dict.keys() if stop not in exclude_stops] 134 | random.shuffle(stop_ids) 135 | route_a = ['start'] 136 | route_b = ['start'] 137 | while stop_ids: 138 | choose_route = np.random.choice(['a','b']) 139 | pop_pt = stop_ids.pop() 140 | if choose_route == 'a': 141 | route_a.append(pop_pt) 142 | else: 143 | route_b.append(pop_pt) 144 | #-------------------------------------------------# 145 | # append the end nodes 146 | #-------------------------------------------------# 147 | route_a.append(self.end_a_key) 148 | route_b.append(self.end_b_key) 149 | return route_a, route_b 150 | 151 | 152 | def fitness(self,route): 153 | travel_dist = 0 154 | current_node = None 155 | current_pt = None 156 | next_pt = None 157 | for node in route: 158 | if current_node == None: 159 | current_pt = node 160 | current_node = self.routes_dict[node] 161 | continue 162 | next_node = self.routes_dict[node] 163 | next_pt = node 164 | dist = next_node.distance(current_node) 165 | travel_dist += dist 166 | current_node = next_node 167 | current_pt = next_pt 168 | 169 | return travel_dist 170 | 171 | def score(self): 172 | size = self.bus_stops.shape[0] 173 | print('number of total stops = {}'.format(size)) 174 | print() 175 | for num in range(self.N): 176 | score = self.route_list[num][0] 177 | a = self.route_list[num][1] 178 | b = self.route_list[num][2] 179 | print('Route Option {}: \nscore = {}'.format(num+1, score)) 180 | print() 181 | 182 | 183 | def evolve(self, epochs): 184 | for i in range(epochs): 185 | for parent in self.route_list: 186 | indx = self.route_list.index(parent) 187 | parent_fitness = parent[0] 188 | parent_a = parent[1] 189 | parent_b = parent[2] 190 | 191 | # Make Random Slices 192 | rand_a = sorted(list(np.random.randint(1,high=len(parent_a)-1,size=2))) 193 | rand_b = sorted(list(np.random.randint(1,high=len(parent_b)-1,size=2))) 194 | 195 | # Slice the Routes 196 | a_start = [j for j in parent_a if parent_a.index(j) < rand_a[0]] 197 | a_end = [j for j in parent_a if parent_a.index(j) > rand_a[1]] 198 | 199 | b_start = [j for j in parent_b if parent_b.index(j) < rand_b[0]] 200 | b_end = [j for j in parent_b if parent_b.index(j) > rand_b[1]] 201 | 202 | #Shuffle the Bag 203 | a_bag = [j for j in parent_a if j not in a_start and j not in a_end] 204 | b_bag = [j for j in parent_b if j not in b_start and j not in b_end] 205 | mixed_bag = a_bag + b_bag 206 | random.shuffle(mixed_bag) 207 | 208 | # Assign Nodes to Routes 209 | while mixed_bag: 210 | choose_route = np.random.choice(['a','b']) 211 | pop_pt = mixed_bag.pop() 212 | if choose_route == 'a': 213 | a_start.append(pop_pt) 214 | else: 215 | b_start.append(pop_pt) 216 | 217 | child_a = a_start + a_end 218 | child_b = b_start + b_end 219 | 220 | # Evaluate Fitness of Child & Replace if Imporvement 221 | child_fitness = self.fitness(child_a) + self.fitness(child_b) 222 | if child_fitness < parent_fitness: 223 | self.route_list.pop(indx) 224 | self.route_list.insert(indx,(child_fitness, child_a, child_b)) 225 | else: 226 | continue 227 | 228 | #Sort the Winning Routes 229 | self.route_list = sorted(self.route_list) 230 | 231 | 232 | 233 | def plot(self): 234 | ''' 235 | OUTPUT: plot of routes 236 | ''' 237 | 238 | fig = plt.figure(figsize=(18,12)) 239 | for n in range(self.N): 240 | # Make LineString Objects 241 | route_a_line = self.route_line(self.route_list[n][1]) 242 | route_b_line = self.route_line(self.route_list[n][2]) 243 | 244 | # Make Geo DataFrames 245 | route_a_geo = self.transform_to_GeoDataFrame(route_a_line) 246 | route_b_geo = self.transform_to_GeoDataFrame(route_b_line) 247 | 248 | ax = fig.add_subplot(2,2,n+1) 249 | 250 | ax.set_aspect('equal') 251 | self.roads.plot(ax=ax,color='black', linewidth=.5 ) 252 | self.region.plot(ax=ax, color='orange', alpha=.2) 253 | self.bus_stops_geo.plot(ax=ax, color='red',markersize=8) 254 | 255 | route_a_geo.plot(ax=ax, color='green', linewidth=1) 256 | route_b_geo.plot(ax=ax, color='blue', linewidth=1) 257 | 258 | ax.set_title('East Side Route Map {}\nFitness = {}'.format(n+1,self.route_list[n][0])) 259 | ax.set_ylim((self.miny, self.maxy)) 260 | ax.set_xlim((self.minx, self.maxx)) 261 | ax.set_xticks([]) 262 | ax.set_yticks([]) 263 | 264 | plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=.1, hspace=.1) 265 | plt.show() 266 | 267 | 268 | def route_line(self, route): 269 | ''' 270 | INPUT: list of root id's 271 | OUTPUT: LineString object 272 | ''' 273 | tuples = [(self.routes_dict[node].x, self.routes_dict[node].y) for node in route] 274 | return LineString(tuples) 275 | 276 | def transform_to_GeoDataFrame(self, geo_list): 277 | ''' 278 | INPUT: list of geometry objects 279 | OUTPUT: GeoDataFrame 280 | ''' 281 | geo_df = gpd.GeoDataFrame(GeoSeries(geo_list)) 282 | geo_df.columns = ['geometry'] 283 | geo_df.crs = {'init': 'epsg:4326'} 284 | geo_df.set_geometry('geometry',inplace=True) 285 | 286 | return geo_df 287 | -------------------------------------------------------------------------------- /RouteFinder1.0/get_data.py: -------------------------------------------------------------------------------- 1 | #------------------------------# 2 | # IMPORT LIBRARIES 3 | #------------------------------# 4 | 5 | import geopandas as gpd 6 | import pandas as pd 7 | import warnings 8 | warnings.filterwarnings("ignore") 9 | from shapely.geometry import Point, Polygon 10 | 11 | #------------------------------# 12 | # FUNCTIONS 13 | #------------------------------# 14 | 15 | def define_city_geo(file_path, city_name): 16 | ''' 17 | INPUT: file_path = string; path to state level census boundary shapefile (i.e. 'gz_2010_48_160_00_500k.shp') 18 | city_name = string; name of census place of interest (i.e. 'Austin') 19 | 20 | OUTPUT: 1) shapely.geometry.multipolygon.MultiPolygon Object 21 | 2) geopandas.geodataframe.GeoDataFrame 22 | ''' 23 | # Read in file to GeoPandas 24 | geo_df = gpd.read_file(file_path) 25 | # Filter NAME 26 | geo_df = geo_df[geo_df.NAME == city_name] 27 | # Convert crs to EPSG: 4326 (GPS) and define geometry 28 | geo_df.to_crs({'init': 'epsg:4326'}, inplace=True) 29 | 30 | geo_shape = geo_df.geometry.values[0] 31 | 32 | return geo_shape , geo_df 33 | 34 | def clean_census_data(file_path, county_number, shape_obj): 35 | ''' 36 | INPUT: file_path = string; path to state level census block shapefile data (i.e. 'tabblock2010_48_pophu.shp') 37 | county_number = str; census designated number for county of interest (i.e. '453' for Austin) 38 | shape_obj = shapely.geometry.Polygon Object 39 | 40 | OUTPUT: GeoDataFrame of city level census blocks 41 | ''' 42 | 43 | # Read in file to GeoPandas 44 | state_df = gpd.read_file(file_path) 45 | # Filter to county level 46 | county_df = state_df[state_df.COUNTYFP10 == county_number] 47 | # Filter for city level 48 | county_df['in_city'] = county_df.geometry.within(shape_obj) 49 | city_df = county_df[county_df.in_city == True] 50 | 51 | # Convert crs to EPSG: 4326 (GPS) and define geometry 52 | city_df.to_crs({'init': 'epsg:4326'}, inplace=True) 53 | # Define block-level centroids 54 | city_df['centroids'] = city_df.geometry.centroid 55 | 56 | # Make a geometry column with geometry in UTM13N (EPSG:26913) units 57 | city_df['UTM13N'] = city_df.geometry.to_crs(epsg=26913) 58 | city_df.set_geometry('UTM13N', inplace=True) 59 | # Calculate block Area in square miles 60 | sq_meter_sq_miles_multiplier = 0.000000386102 61 | city_df['area_sq_miles'] = city_df['UTM13N'].area*sq_meter_sq_miles_multiplier 62 | # Calculate population density per census block 63 | city_df['pop_per_sq_mi'] = city_df.POP10/city_df.area_sq_miles 64 | # Calculate Housing Density per census plock 65 | city_df['housing_density_sq_mile'] = city_df.HOUSING10/city_df.area_sq_miles 66 | 67 | # Convert crs back to EPSG: 4326 (GPS) and define geometry 68 | city_df.to_crs({'init': 'epsg:4326'}, inplace=True) 69 | city_df.set_geometry('geometry') 70 | city_df.drop(columns=['in_city', 'UTM13N','STATEFP10','COUNTYFP10'], inplace=True) 71 | 72 | centroids_df = city_df.drop(columns=['geometry']) 73 | centroids_df.set_geometry('centroids', inplace=True) 74 | centroids_df.to_crs({'init': 'epsg:4326'}, inplace=True) 75 | 76 | blocks_df = city_df.drop(columns=['centroids']) 77 | blocks_df.set_geometry('geometry', inplace=True) 78 | blocks_df.to_crs({'init': 'epsg:4326'}, inplace=True) 79 | 80 | return centroids_df, blocks_df 81 | 82 | def make_tract_geos(file_path, county_code, shape_obj): 83 | ''' 84 | INPUT: file_path: str: path to tracts shapefile data 85 | county_code: str; census designated county code (i.e. '453' for Austin) 86 | shape_obj: shape_obj = shapely.geometry.Polygon Object 87 | OUTPUT: GeoDataFrame of census tract shape objects 88 | ''' 89 | tracts_df = gpd.read_file(file_path) 90 | county_tracts = tracts_df[tracts_df.COUNTY == county_code] 91 | tracts_df.to_crs({'init': 'epsg:4326'}, inplace=True) 92 | county_tracts['in_city'] = county_tracts.geometry.intersects(shape_obj) 93 | city_tracts = county_tracts[county_tracts.in_city == True] 94 | city_tracts['geometry'] = city_tracts.geometry.intersection(shape_obj) 95 | 96 | return city_tracts 97 | 98 | def current_bus_system(file_path, shape_obj): 99 | ''' 100 | INPUT: file_path: str; path to current city bus stop shapefile data 101 | shape_obj: shapely.geometry.Polygon Object 102 | OUTPUT: bus_stop GeoDataFrame 103 | ''' 104 | stops_df = gpd.read_file(file_path) 105 | stops_df.to_crs({'init': 'epsg:4326'}, inplace=True) 106 | stops_df = stops_df[stops_df.geometry.within(shape_obj)] 107 | return stops_df 108 | 109 | def local_roads(file_path, shape_obj): 110 | ''' 111 | INPUT: file_path = string; path to county level census roads shapefile (i.e. 'tl_2017_48453_roads.shp') 112 | shape_obj = shapely.geometry.Polygon Object 113 | 114 | OUTPUT: 1) street level GeoDataFrame of local roads 115 | 2) secondary level GeoDataFrame of local roads 116 | ''' 117 | # Read in file to GeoPandas 118 | roads_df = gpd.read_file(file_path) 119 | # Filter Relavent Roads 120 | # S1400 = Local Neighborhood Road, Rural Road, City Street 121 | # S1200 = Secondary Road (U.S. Highway, State Highway or County Highway system) 122 | roads_df = roads_df[roads_df.MTFCC.isin(['S1400','S1200'])] 123 | # Convert crs to EPSG: 4326 (GPS) and define geometry 124 | roads_df.to_crs({'init': 'epsg:4326'}, inplace=True) 125 | # Filter for roads inside city limits 126 | roads_df['in_boundary'] = roads_df.geometry.intersects(shape_obj) 127 | local_roads = roads_df[roads_df['in_boundary'] == True] 128 | # Clip roads 129 | local_roads['geometry'] = local_roads.geometry.intersection(shape_obj) 130 | # Get rid of Point Objects 131 | local_roads['length'] = local_roads.geometry.length 132 | local_roads = local_roads[local_roads.length != 0] 133 | # Divide dataFrame into streets and secondary roads 134 | streets_df = local_roads[local_roads.MTFCC == 'S1400'] 135 | secondary_df = local_roads[local_roads.MTFCC == 'S1200'] 136 | 137 | return streets_df, secondary_df 138 | 139 | if __name__ == '__main__': 140 | pass 141 | 142 | #boundary_path = 'data/boundary/gz_2010_48_160_00_500k.shp' 143 | #city_shape , city_df = define_city_geo(boundary_path, 'Austin') 144 | 145 | #census_block_path = '../population/tabblock2010_48_pophu.shp' 146 | #austin_centroids, austin_blocks = clean_census_data(census_block_path, '453', city_shape) 147 | 148 | # Save shapefiles 149 | #austin_centroids.to_file('data/austin_centroids_df/austin_centroids_df.shp', driver='ESRI Shapefile') 150 | #austin_blocks.to_file('data/austin_blocks_df/austin_blocks_df.shp', driver='ESRI Shapefile') 151 | -------------------------------------------------------------------------------- /RouteFinder1.0/plotting_functions.py: -------------------------------------------------------------------------------- 1 | #------------------------------# 2 | # IMPORT LIBRARIES 3 | #------------------------------# 4 | 5 | import geopandas as gpd 6 | from geopandas import GeoSeries 7 | import pandas as pd 8 | import matplotlib.pyplot as plt 9 | import warnings 10 | warnings.filterwarnings("ignore") 11 | from shapely.geometry import Point, Polygon 12 | from shapely.geometry import LineString, shape 13 | 14 | #------------------------------# 15 | # PLOTTING FUNCTIONS 16 | #------------------------------# 17 | 18 | 19 | def plot_roads(streets_df, secondary_df): 20 | ''' 21 | INPUT: streets_df = GeoDataFrame of local roads made with local_roads() function 22 | secondary_df = GeoDatFrame of secondary roads made with local_roads() function 23 | 24 | OUTPUT: plot of road network 25 | ''' 26 | fig, ax = plt.subplots(figsize=(10,10)) 27 | streets_df.plot(ax=ax, color='grey', linewidth=.2, markersize=.2, label='local roads') 28 | secondary_df.plot(ax=ax, color='green', linewidth=1, markersize=.2, label='secondary roads' ) 29 | ax.set_title('Local Road Netork') 30 | ax.set_xticks([]) 31 | ax.set_yticks([]) 32 | ax.legend(fontsize=12, markerscale=10) 33 | ax.set_aspect('equal') 34 | 35 | plt.show() 36 | 37 | 38 | 39 | def block_data_viz(cleaned_geo_df): 40 | ''' 41 | INPUT: GeoDatFrame cleaned with clean_census_data() function 42 | 43 | OUTPUT: census data visualizations 44 | ''' 45 | 46 | fig, (ax1, ax2) = plt.subplots(2,1,figsize=(10,10)) 47 | 48 | pop_density = cleaned_geo_df.pop_per_sq_mi 49 | pop_density.hist(ax=ax1, bins=250, range=(1,80000)) 50 | ax1.set_title('Population Density per Census Block') 51 | ax1.set_xlabel('People per Square Mile') 52 | ax1.set_ylabel('Frequency') 53 | ax1.set_yscale('log') 54 | 55 | block_areas = cleaned_geo_df.area_sq_miles 56 | block_areas.hist(ax=ax2, bins=250, range=(0,.5)) 57 | ax2.set_title('Block Area Distribution') 58 | ax2.set_xlabel('Square Miles per Census Block') 59 | ax2.set_ylabel('Frequency') 60 | ax2.set_yscale('log') 61 | 62 | plt.show 63 | 64 | 65 | def pop_heatmap(cleaned_geo_df): 66 | ''' 67 | INPUT: GeoDatFrame cleaned with clean_census_data() function 68 | 69 | OUTPUT: population density heat map by city census block 70 | ''' 71 | fig, ax = plt.subplots(1,1,figsize=(10,10)) 72 | cleaned_geo_df.plot(ax=ax, column='pop_per_sq_mi', cmap='Oranges', edgecolor='black', scheme='Quantiles', linewidth=.1) 73 | ax.set_title('Austin Population Density Heat Map') 74 | ax.set_xticks([]) 75 | ax.set_yticks([]) 76 | 77 | plt.show() 78 | 79 | 80 | 81 | def plot_stops(stops_df, clusters_df, roads_df): 82 | ''' 83 | INPUT: stops_df: GeoDataFrame of current bus stops cleaned with current_bus_system() function 84 | clusters_df: GeoDataFrame of population clusters generated with make_clusters() function 85 | OUTPUT: plot of curent bus stop vs. population cluster centers 86 | ''' 87 | n = stops_df.shape[0] 88 | k = clusters_df.shape[0] 89 | 90 | fig, (ax1, ax2) = plt.subplots(1,2,figsize=(20,10), sharey=True) 91 | 92 | roads_df.plot(ax=ax1, color='black', linewidth=.15) 93 | stops_df.plot(ax=ax1, marker='.', color='red', markersize=1) 94 | ax1.set_title('{} Curent Bus Stops'.format(n)) 95 | ax1.set_xticks([]) 96 | ax1.set_yticks([]) 97 | ax1.set_aspect('equal') 98 | 99 | roads_df.plot(ax=ax2, color='black', linewidth=.15) 100 | clusters_df.plot(ax=ax2, marker='.', color='red', markersize=1) 101 | ax2.set_title('{} Population Cluster Centers'.format(k)) 102 | ax2.set_xticks([]) 103 | ax2.set_yticks([]) 104 | ax2.set_aspect('equal') 105 | 106 | plt.subplots_adjust(wspace=.05) 107 | plt.show() 108 | 109 | 110 | def plot_tracts(city_tracts_df): 111 | ''' 112 | INPUT: city_tracts_df; GeoDataFrame cleaned with make_tract_geos() function 113 | OUTPUT: GeoDataFrame of census tract shape objects 114 | ''' 115 | fig, ax = plt.subplots(figsize=(10,10)) 116 | ax.set_aspect('equal') 117 | city_tracts_df.geometry.plot(ax=ax, alpha=.1, edgecolor='blue') 118 | ax.set_xticks([]) 119 | ax.set_yticks([]) 120 | plt.show() 121 | -------------------------------------------------------------------------------- /RouteFinder2.0/.ipynb_checkpoints/Routing-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /RouteFinder2.0/AoA.py: -------------------------------------------------------------------------------- 1 | from geopandas import GeoSeries 2 | from shapely.ops import cascaded_union 3 | import matplotlib.pyplot as plt 4 | 5 | def makeBoundary(df, crs): 6 | # create a list of geometries 7 | polygons = [] 8 | for geo in df[['geometry']].values: 9 | polygons.append(geo[0]) 10 | 11 | boundary = GeoSeries(cascaded_union(polygons)) 12 | #boundary.plot(color='white', edgecolor='k') 13 | #plt.show() 14 | boundary.crs = crs 15 | 16 | return boundary 17 | -------------------------------------------------------------------------------- /RouteFinder2.0/CurrentSystem.py: -------------------------------------------------------------------------------- 1 | import geopandas 2 | 3 | 4 | def CurrentStops(file_path, shape_obj, crs): 5 | ''' 6 | INPUT: file_path: str; path to current city bus stop shapefile data 7 | shape_obj: shapely.geometry.Polygon Object 8 | OUTPUT: bus_stop GeoDataFrame 9 | ''' 10 | stops_df = geopandas.read_file(file_path) 11 | stops_df.to_crs(crs, inplace=True) 12 | stops_df = stops_df[stops_df.geometry.within(shape_obj)] 13 | 14 | return stops_df 15 | -------------------------------------------------------------------------------- /RouteFinder2.0/GetPopMetric.py: -------------------------------------------------------------------------------- 1 | import geopandas 2 | 3 | def Population(stops_df, AoA_df): 4 | ''' 5 | INPUT: 6 | 7 | OUTPUT: 8 | ''' 9 | population = 0 10 | # iterate through stops 11 | while stops_df.shape[0] != 0: 12 | stop = stops_df.geometry.values[0] 13 | # Define are within 400 meters of stop 14 | buffer400 = stop.buffer(400) 15 | # filter blocks in buffer range 16 | blocks_df = AoA_df[AoA_df.geometry.intersects(buffer400) == True] 17 | # Find fractional area of buffer over block 18 | blocks_df['fractionalArea'] = blocks_df.geometry.intersection(buffer400).area / blocks_df.geometry.area 19 | # Find fractional population 20 | blocks_df['fractionalPop'] = blocks_df.population * blocks_df.fractionalArea 21 | # Add population 22 | population += blocks_df.fractionalPop.sum() 23 | # Filter CurrentStops_df to reduce population duplicates 24 | buffer200 = stop.buffer(200) 25 | stops_df = stops_df[stops_df.geometry.intersects(buffer200) == False] 26 | 27 | return int(population) 28 | 29 | 30 | -------------------------------------------------------------------------------- /RouteFinder2.0/Intersections.py: -------------------------------------------------------------------------------- 1 | import osmnx as ox 2 | import matplotlib.pyplot as plt 3 | 4 | def getIntersections(boundary, projection, tolerance=100): 5 | ''' 6 | INPUT: boundary shap 7 | projection in UTM 8 | int (in meters) representing tolerance of clean_intersections() function for osmnx package 9 | 10 | OUTPUT: GeoSeries of Intersections 11 | ''' 12 | 13 | # Create Graph 14 | G = ox.graph_from_polygon(boundary, network_type='drive') 15 | ox.plot_graph(G) 16 | plt.show() 17 | 18 | # Clean Intersections 19 | G_proj = ox.project_graph(G, to_crs=projection) 20 | intersections = ox.clean_intersections(G_proj, tolerance=tolerance, dead_ends=False) 21 | 22 | return intersections 23 | 24 | 25 | def IntersectionList(blockGeo, intersections, buffer=400): 26 | ''' 27 | INPUT: blockGeo: Shapely Geometry Object 28 | intersections: Shapely Geometry Series from getIntersections() function 29 | buffer: integer (in meters) of distance to search (400 = 1/4 mile -- approx) 30 | OUTPUT: List; List of Intersections within buffer distance of block 31 | ''' 32 | block = blockGeo.buffer(400) # 400 meters is about 1/4 mile 33 | L = [] 34 | for i in intersections: 35 | if i.intersects(block) == True: 36 | L.append(i) 37 | return L 38 | 39 | 40 | def BlockList(IntersectionGeo, AoA_df, buffer=200): 41 | ''' 42 | INPUT: IntersectionGeo: Shapely Geometry Object 43 | blocks: Shapely Geometry Series of all blocks 44 | buffer: integer (in meters) of distance to search (200 = .024 mile -- approx) 45 | OUTPUT: List; List of Blocks within buffer distance of intersection 46 | ''' 47 | blocks_df = AoA_df[['geometry', 'population']] 48 | intersection = IntersectionGeo.buffer(200) #search for all blocks within 200 meters (or about 0.124 miles) 49 | blockList = [] 50 | iPop = 0 51 | for row in range(blocks_df.shape[0]): 52 | geo = blocks_df.iloc[row]['geometry'] 53 | pop = blocks_df.iloc[row]['population'] 54 | if geo.intersects(intersection) == True: 55 | blockList.append(geo) 56 | iPop += pop 57 | 58 | return blockList , iPop 59 | 60 | 61 | def IntersectionsPerBlock(AoA_df, intersections): 62 | ''' 63 | INPUT: AoA_df: GeoDataFrame of Area of Interest 64 | intersections: GeoSeries of intersections from getIntersections() 65 | 66 | OUTPUT: AoA_df: GeoDataFrame with new columns added ('iList', 'iCounts') 67 | ** iList is a list of intersections near each block 68 | ** iCount is a count of intersections near each block 69 | ''' 70 | iArray = [] 71 | iCounts = [] 72 | for block in AoA_df.geometry: 73 | iList = IntersectionList(block, intersections) 74 | iArray.append(iList) 75 | iCounts.append(len(iList)) 76 | 77 | AoA_df['iList'] = iArray 78 | AoA_df['iCounts'] = iCounts 79 | 80 | AoA_underserved = AoA_df[(AoA_df.iCounts == 0) & (AoA_df.population != 0)] 81 | 82 | underserved = sum(AoA_underserved.population) 83 | print('{} people do not live near an intersection'.format(underserved)) 84 | 85 | return AoA_df 86 | 87 | def BlocksPerIntersection(intersections_df, AoA_df): 88 | ''' 89 | INPUT: AoA_df: GeoDataFrame of Area of Interest 90 | intersections_df: GeoDataFrame of intersections 91 | 92 | OUTPUT: intersections_df: GeoDataFrame with new columns added ('bList', 'iPop') 93 | ** bList is a list of blocks near each intersection 94 | ** iPop is a population count assigned to each intersection 95 | ''' 96 | blockArray = [] 97 | popArray = [] 98 | for intersection in intersections_df.geometry: 99 | blockList, intersectionPop = BlockList(intersection, AoA_df) 100 | blockArray.append(blockList) 101 | popArray.append(intersectionPop) 102 | 103 | intersections_df['bList'] = blockArray 104 | intersections_df['iPop'] = popArray 105 | 106 | return intersections_df 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /RouteFinder2.0/MakeStops.py: -------------------------------------------------------------------------------- 1 | import pandas 2 | import geopandas 3 | 4 | 5 | def MakeStops(intersections_df, meters=800): 6 | ''' 7 | INPUT: DataFrame returned from BlocksPerIntersection() function 8 | 9 | OUTPUT: DataFrame 10 | ''' 11 | 12 | # Sort the DataFrme in order of descending population 13 | iPop_df = intersections_df[intersections_df.iPop > 0].sort_values('iPop', ascending=False) 14 | 15 | indexList = [] 16 | served = [] # population served at each bus stop identified 17 | while iPop_df.shape[0] != 0: 18 | stop = iPop_df.geometry.values[0] 19 | idx = iPop_df.index.values[0] 20 | indexList.append(idx) 21 | # identify the exclusion zone 22 | zone = stop.buffer(meters) # 400 meters = approx 1/4 mile 23 | # reach is the sum of all populations in the exclusion zone 24 | reach = iPop_df[iPop_df.geometry.intersects(zone) == True].iPop.sum() 25 | served.append([idx, reach]) 26 | #filter out any intersections within exclusion zone 27 | iPop_df = iPop_df[iPop_df.geometry.intersects(zone) == False] 28 | 29 | stops_df = intersections_df[intersections_df.index.isin(indexList)] 30 | 31 | # convert the 'served' array into a DataFrame 32 | served_df = pandas.DataFrame(data=served, columns=['idx','mergedPop'], dtype='int') 33 | served_df.set_index('idx', inplace=True) 34 | 35 | # Merge DataFrames 36 | stops_df = stops_df.merge(served_df, left_index=True, right_index=True) 37 | 38 | return stops_df 39 | -------------------------------------------------------------------------------- /RouteFinder2.0/Reduce.py: -------------------------------------------------------------------------------- 1 | import geopandas 2 | 3 | def filterStops(CurrentStops_df, stops_df): 4 | goalNum = CurrentStops_df.shape[0]/2 5 | minPop = stops_df.mergedPop.min() 6 | maxPop = stops_df.mergedPop.max() 7 | for bench in range(minPop, maxPop): 8 | filterStops = stops_df[stops_df.mergedPop > bench] 9 | if filterStops.shape[0] <= goalNum: 10 | return filterStops, bench -------------------------------------------------------------------------------- /RouteFinder2.0/RouteFinder2.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class RouteMaker2: 4 | def __init__(self, stops_df, AoA_df): 5 | self.stops_df = stops_df 6 | self.AoA_df = AoA_df 7 | self.LatLong = {'init', 'EPSG:4326'} 8 | 9 | # Populated with .define_system() method 10 | self.GPS = None 11 | self.nRoutes = None 12 | self.station = None 13 | 14 | 15 | def define_system(self, station, nRoutes): 16 | self.station = station 17 | self.nRoutes = nRoutes 18 | input_msg = input('Input NAD83 EPSG Code\n(Example: \'2277\' for Texas North Central, USA)\nEPSG:') 19 | self.GPS = {'init', 'EPSG:{}'.format(input_msg)} 20 | -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/AoA.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/AoA.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/CurrentSystem.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/CurrentSystem.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/GetPopMetric.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/GetPopMetric.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/Intersections.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/Intersections.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/MakeStops.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/MakeStops.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/Reduce.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/Reduce.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/downloadGEOdata.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/downloadGEOdata.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/getPop_df.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/getPop_df.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/joinBlockPopGEOs.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/joinBlockPopGEOs.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/__pycache__/myAPIs.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/RouteFinder2.0/__pycache__/myAPIs.cpython-36.pyc -------------------------------------------------------------------------------- /RouteFinder2.0/downloadGEOdata.py: -------------------------------------------------------------------------------- 1 | import requests, zipfile, io, os 2 | 3 | 4 | def Download_Unzip_GEOs(state): 5 | ''' 6 | INPUT: (1) state FIPS code 7 | 8 | OUTPUT: String; path to block level GEO data 9 | 10 | ** This function downloads and extracts data to the './data' directory 11 | ''' 12 | 13 | # download zip file and extract to a local 'data' folder 14 | url = 'https://www2.census.gov/geo/tiger/TIGER2017/TABBLOCK/' + 'tl_2017_{}_tabblock10.zip'.format(state) 15 | r = requests.get(url) 16 | z = zipfile.ZipFile(io.BytesIO(r.content)) 17 | new_path = 'LOCAL/blockGEOs_{}'.format(state) 18 | if not os.path.exists(new_path): 19 | os.makedirs(new_path) 20 | z.extractall(new_path) 21 | 22 | return new_path 23 | 24 | if __name__ == "__main__": 25 | state = str(input('enter state FIPS code: ')) 26 | Download_Unzip_GEOs(state) 27 | -------------------------------------------------------------------------------- /RouteFinder2.0/getPop_df.py: -------------------------------------------------------------------------------- 1 | from LOCAL.myAPIs import census_API_key as key 2 | import requests 3 | import json 4 | import pandas as pd 5 | 6 | 7 | def makeAPI_PopByBlock2010(state, county, key): 8 | ''' 9 | INPUT: (1) state FIPS code 10 | (2) FIPS county code 11 | (3) API key to US Census 12 | 13 | OUTPUT: pandas DataFrame 14 | ''' 15 | 16 | # Use API to get population data by census block 17 | base_url = 'https://api.census.gov/data/2010/sf1' 18 | query_call = '?get=P0010001' 19 | geo_call = '&for=block:*&in=state:{}&in=county:{}&'.format(state,county) 20 | key_call = 'key={}'.format(key) 21 | URL = base_url + query_call + geo_call + key_call 22 | response = requests.get(URL).json() 23 | data = response[1:] 24 | labels = response[0] 25 | 26 | # Create pandas DataFrame 27 | df = pd.DataFrame(data=data, columns=labels) 28 | # Rename population column 29 | df.rename(columns={'P0010001': 'population'}, inplace=True) 30 | # Create 'ID' column and set index 31 | df['ID'] = df.state + df.county + df.tract + df.block 32 | df.set_index('ID', inplace=True) 33 | 34 | return df 35 | 36 | 37 | 38 | if __name__ == "__main__": 39 | key = key() 40 | state = str(input('enter state FIPS code (i.e. 48 for TX): ')) 41 | county = str(input('enter county FIPS code (i.e. 453 for Travis): ')) 42 | -------------------------------------------------------------------------------- /RouteFinder2.0/joinBlockPopGEOs.py: -------------------------------------------------------------------------------- 1 | import geopandas 2 | 3 | def joinDFs(path_blockGEOs, df): 4 | ''' 5 | INPUT: (1) string; path to blockGEOs 6 | (2) DataFrame returned by 'makeAPI_PopByBlock2010' 7 | 8 | OUTPUT: DataFrame 9 | ''' 10 | 11 | # create a geo-pandas DataFrame and filter the columns for only ID and 'geometry' 12 | geo_df = geopandas.read_file(path_blockGEOs) 13 | geo_df = geo_df[['GEOID10','geometry']] 14 | geo_df.set_index('GEOID10', inplace=True) 15 | 16 | 17 | # join the tiger_df and the df tables 18 | joined_df = geo_df.join(df, how='right') 19 | 20 | 21 | print('GeoDataFrame with projection = {}'.format(joined_df.crs)) 22 | 23 | return joined_df 24 | 25 | -------------------------------------------------------------------------------- /__pycache__/MakeCluster.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/__pycache__/MakeCluster.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/RouteFinder.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/__pycache__/RouteFinder.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/clustering.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/__pycache__/clustering.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/get_data.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/__pycache__/get_data.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/plotting_functions.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Schmidtbit/Bus-Route-Optimization/102dc44dabd72c1911e6f867c78e15e71cef8f2a/__pycache__/plotting_functions.cpython-36.pyc --------------------------------------------------------------------------------