├── TransportSim.gif ├── .gitignore ├── .travis.yml ├── src ├── templates │ ├── index.tpl │ └── simulation.tpl ├── routeModel.py ├── view.py ├── simulation.py ├── peopleModel.py └── models.py ├── LICENSE └── README.md /TransportSim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidbailey/TransportSim/HEAD/TransportSim.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | var/los-angeles_california.osm 3 | var/los-angeles_california.osm.bz2 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.7 4 | notifications: 5 | slack: 6 | secure: vrIQgoWk6vxxlXh42Y8CpMT+I7l0zvqiE08sHWO6aRTDF6imxzbjfUxfXvw1Ge0w8lH6YGywz6vTNRT9IA0eznCCt8pIJvhu4hCbFevdWKzIG5b+rf/lNCbxzugfTbY54ddAaG9aFo1R4ebJD70amJvFYp0yLr3VSYKuMTuxOX70yWPLQBNtxSIP/WIGJJY6vMMpCsygjRQLndrzQgA8MGrW6pKUdg8Y5C4p+V5grDuPjTWL6XLPzHJQZeE/xDsGWT302xZnyqMgC3xKoEiOPoGN2FGem6zcaT9SowyczmNjGxGQO/2LQPAxvhVtE+dsE4CQwCqxJM1bJXPrMTCaeiTi6MPNsR8zPXr8pyXjbn5qyYEvdZ0kUQrX4lUhYv0kJH5LfzUWSIzpQyxuJkt6PYTq+dX5MNeLVFyiDf1vZmsdGVFI/LJfKxI/6LtF4U9C8/C5G4wQZK2fs3QifkL9rqTDzrF1c+7x53Mqzohx9FNe8cU5PWZzpmNpZMwOwmA4mKBzCkxhwiyPGrY2ofHRTw4cRt7uIkdq09BUOHAeKGCUkWwLgvqz+A+vTc+9fr9MV9NYU1xpmMqq+1FFwnEu7+vm1XCrIMa5av9LyV2WkdI7sas2TmYyLFdSOFePUVCcLrt3zm2xkXSh2bzdZR85T71Rm/x3VVjBSBsYgIb/hgQ= 7 | -------------------------------------------------------------------------------- /src/templates/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TransportSim 5 | 6 | 7 | 8 |

Welcome to TransportSim.

9 | 10 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/routeModel.py: -------------------------------------------------------------------------------- 1 | # install http://wiki.openstreetmap.org/wiki/Open_Source_Routing_Machine 2 | import pandas 3 | import os 4 | import requests 5 | from tqdm import trange 6 | 7 | trips = pandas.read_csv(os.path.expanduser('~/TransportSim/var/trips.csv')) 8 | routes = [] 9 | 10 | #for i in trange(len(trips) - 1): 11 | for i in trange((len(trips) - 1)/10000): 12 | #r = requests.get('http://localhost:5000/viaroute?loc=' + str(trips.iloc[i].hY) + ',' + str(trips.iloc[i].hX) + '&loc=' + str(trips.iloc[i].wY) + ',' + str(trips.iloc[i].wX)) 13 | r = requests.get('http://localhost:5000/viaroute?loc=' + str(trips.iloc[10000*i].hY) + ',' + str(trips.iloc[10000*i].hX) + '&loc=' + str(trips.iloc[10000*i].wY) + ',' + str(trips.iloc[10000*i].wX)) 14 | try: routes.append(r.json()['route_geometry']) 15 | except: print "AppendError" 16 | 17 | f = open(os.path.expanduser('~/TransportSim/var/routes.polystrings'), 'w') 18 | for route in routes: 19 | f.write(route + '\n') 20 | 21 | f.close() 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/view.py: -------------------------------------------------------------------------------- 1 | from os.path import expanduser 2 | from subprocess import Popen 3 | from bottle import route, run, template 4 | #from kafka import KafkaConsumer 5 | import json 6 | import redis 7 | 8 | r = redis.StrictRedis(host='localhost', port=6379, db=0) 9 | #carsConsumer = KafkaConsumer('cars', group_id='kafka-python-default-group', bootstrap_servers=['localhost:9092']) 10 | #peopleConsumer = KafkaConsumer('people', group_id='kafka-python-default-group', bootstrap_servers=['localhost:9092']) 11 | previousCarView = {} 12 | previousPeopleView = {} 13 | 14 | @route('/') 15 | def index(): 16 | f = open(expanduser('~/TransportSim/src/main/python/templates/index.tpl'), 'r') 17 | return f.read() 18 | f.close() 19 | 20 | @route('/startsimulation') 21 | def startsimulation(): 22 | #Popen(["sbt", "run"]) 23 | return {'started': 'true'} 24 | 25 | @route('/simulation') 26 | def simulation(): 27 | f = open(expanduser('~/TransportSim/src/main/python/templates/simulation.tpl'), 'r') 28 | return f.read() 29 | f.close() 30 | 31 | @route('/api/getCars') 32 | def getCars(): 33 | global previousCarView 34 | # try: 35 | # carView = list(carsConsumer.poll())[-1].value 36 | # previousCarView = carView 37 | # except: 38 | # carView = previousCarView 39 | #print carView 40 | return json.loads(carView) 41 | 42 | @route('/api/getPeople') 43 | def getPeople(): 44 | global previousPeopleView 45 | # try: 46 | # peopleView = list(peopleConsumer.poll())[-1].value 47 | # previousPeopleView = peopleView 48 | # except: 49 | # peopleView = previousPeopleView 50 | #print peopleView 51 | try: 52 | peopleView = r.get('people') 53 | except: 54 | peopleView = previousPeopleView 55 | return json.loads(peopleView) 56 | 57 | run(host='localhost', port=8080) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TransportSim: A transportation simulation. # 2 | 3 | [![Build Status](https://travis-ci.org/davidbailey/TransportSim.svg?branch=master)](https://travis-ci.org/davidbailey/TransportSim) 4 | [![Coverage Status](https://coveralls.io/repos/davidbailey/TransportSim/badge.svg?branch=master&service=github)](https://coveralls.io/github/davidbailey/TransportSim?branch=master) 5 | 6 | ![TransportSim](https://raw.githubusercontent.com/davidbailey/TransportSim/master/TransportSim.gif "TransportSim") 7 | 8 | ## Architecture ## 9 | 10 | 1. python/peopleModel - generates people (origins and destinations) 11 | 2. python/routeModel - generates a route for each person (polylines) 12 | 3. python/simulation - simulates people (polylines) moving; sends views to redis 13 | 4. python/view - view the models and simulations from redis (openlayers + bottle REST API) 14 | 15 | ## Documentation ## 16 | Time is always in seconds. 17 | Distances have their own class and can be feet or meters. 18 | 19 | ## Install ## 20 | 21 | ``` 22 | git clone https://github.com/davidbailey/TransportSim.git 23 | 24 | pip install pandas geopandas shapely requests bottle redis 25 | 26 | # osrm-backend 27 | brew install osrm-backend 28 | wget http://download.geofabrik.de/north-america/us/california-latest.osm.pbf 29 | ./osrm-extract california-latest.osm.pbf 30 | ./osrm-prepare california-latest.osrm 31 | ./osrm-routed california-latest.osrm 32 | 33 | # download osm 34 | cd var 35 | wget https://s3.amazonaws.com/metro-extracts.mapzen.com/los-angeles_california.osm.bz2 36 | bunzip2 los-angeles_california.osm.bz2 37 | 38 | # redis 39 | brew install redis 40 | redis-server 41 | 42 | # TransportSim 43 | cd TransportSim 44 | python src/view.py 45 | python src/simulation.py 46 | Launch a browser and open http://localhost:8080/ 47 | ``` 48 | -------------------------------------------------------------------------------- /src/simulation.py: -------------------------------------------------------------------------------- 1 | import models 2 | from os.path import expanduser 3 | from polyline import decode 4 | from random import randint 5 | #from kafka import KafkaProducer 6 | import json 7 | import redis 8 | 9 | beginDepartures = 21600 10 | endDepartures = 43200 11 | 12 | footRoutes = [] 13 | with open(expanduser('~/TransportSim/var/foot/routes.polystrings-1700'),'r') as footRoutesFile: 14 | for route in footRoutesFile: 15 | footRoutes.append(decode(route.rstrip())) 16 | 17 | bicycleRoutes = [] 18 | with open(expanduser('~/TransportSim/var/bicycle/routes.polystrings-1700'),'r') as bicycleRoutesFile: 19 | for route in bicycleRoutesFile: 20 | bicycleRoutes.append(decode(route.rstrip())) 21 | 22 | carRoutes = [] 23 | with open(expanduser('~/TransportSim/var/car/routes.polystrings-1700'),'r') as carRoutesFile: 24 | for route in carRoutesFile: 25 | carRoutes.append(decode(route.rstrip())) 26 | 27 | People = [] 28 | Bicycles = [] 29 | Cars = [] 30 | 31 | for a in range(len(carRoutes)): 32 | footBicycleCar = randint(1,100) 33 | departureTime = randint(beginDepartures,endDepartures) 34 | if footBicycleCar <= 80: 35 | p = models.Person(carRoutes[a],departureTime) 36 | People.append(p) 37 | c = models.Car() 38 | p.vehicle = c 39 | c.driver = p 40 | Cars.append(c) 41 | elif 80 < footBicycleCar < 83: 42 | p = models.Person(bicycleRoutes[a],departureTime) 43 | People.append(p) 44 | b = models.Bicycle() 45 | p.vehicle = b 46 | b.driver = p 47 | Bicycles.append(c) 48 | elif 83 < footBicycleCar: 49 | p = models.Person(footRoutes[a],departureTime) 50 | People.append(p) 51 | 52 | footRoutes = False 53 | bicycleRoutes = False 54 | carRoutes = False 55 | 56 | #producer = KafkaProducer(bootstrap_servers='localhost:9092', value_serializer=lambda v: json.dumps(v).encode('utf-8')) 57 | r = redis.StrictRedis(host='localhost', port=6379, db=0) 58 | 59 | for a in range(1000): 60 | print "Round: " + str(a) 61 | map(models.Person.transport,filter((lambda x: not x.crashed and not x.arrived),People)) 62 | peopleView = { "people": map(models.Person.view,People) } 63 | #producer.send('people', peopleView) 64 | r.set('people',json.dumps(peopleView)) 65 | -------------------------------------------------------------------------------- /src/peopleModel.py: -------------------------------------------------------------------------------- 1 | # tract files from http://www.census.gov/cgi-bin/geo/shapefiles2010/file-download 2 | # ca_od_2013 files from http://lehd.ces.census.gov/data/lodes/LODES7/ 3 | import os 4 | import geopandas 5 | import pandas 6 | from random import uniform 7 | from shapely.geometry import box, Point 8 | 9 | tractFile = os.path.expanduser('~/TransportSim/var/tl_2010_06_tabblock10.zip') 10 | tracts = geopandas.GeoDataFrame.from_file('/', vfs = 'zip://' + tractFile) 11 | 12 | xmax, xmin, ymax, ymin = (-119.97, -116.80, 34.80, 33.33) 13 | boundingBox = box(xmin, ymin, xmax, ymax) 14 | 15 | tractsInBox = [] 16 | for name, tract in tracts.iterrows(): 17 | if tract['geometry'].within(boundingBox): 18 | tractsInBox.append(tract) 19 | 20 | tracts = geopandas.GeoDataFrame(tractsInBox) 21 | 22 | ca_od_main_2013s = [] 23 | for i in range(1,6): 24 | ca_od_main_i_2013 = pandas.DataFrame.from_csv(os.path.expanduser('~/TransportSim/var/ca_od_2013/ca_od_main_JT0' + str(i) + '_2013.csv'), index_col=False) 25 | ca_od_main_2013s.append(ca_od_main_i_2013) 26 | 27 | ca_od_main_2013 = pandas.concat(ca_od_main_2013s) 28 | 29 | fix = lambda x: '0' + unicode(x) 30 | 31 | ca_od_main_2013['wGEOID'] = ca_od_main_2013['w_geocode'] 32 | ca_od_main_2013['wGEOID'] = ca_od_main_2013['wGEOID'].apply(fix) 33 | ca_od_main_2013['hGEOID'] = ca_od_main_2013['h_geocode'] 34 | ca_od_main_2013['hGEOID'] = ca_od_main_2013['hGEOID'].apply(fix) 35 | 36 | w_coded = pandas.merge(ca_od_main_2013, tracts, how='inner', left_on='wGEOID', right_on='GEOID10') 37 | wh_coded = pandas.merge(w_coded, tracts, how='inner', left_on='hGEOID', right_on='GEOID10', suffixes=['W','H']) 38 | 39 | def randomPointInPolygon(polygon): 40 | (xmin, ymin, xmax, ymax) = polygon.bounds 41 | while True: 42 | x = uniform(xmin,xmax) 43 | y = uniform(ymin,ymax) 44 | point = Point(x,y) 45 | if point.within(polygon): 46 | return point 47 | 48 | trips = [] 49 | for name, row in wh_coded.iterrows(): 50 | for i in range(0,row['S000']): 51 | hPoint = randomPointInPolygon(row.geometryH) 52 | wPoint = randomPointInPolygon(row.geometryW) 53 | trips.append([hPoint.y, hPoint.x, wPoint.y, wPoint.x]) 54 | 55 | pandas.DataFrame(trips, columns = ['hY', 'hX', 'wY', 'wX']).to_csv(os.path.expanduser('~/TransportSim/var/trips.csv')) 56 | -------------------------------------------------------------------------------- /src/templates/simulation.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | TransportSim 13 | 14 | 15 |
16 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/models.py: -------------------------------------------------------------------------------- 1 | from shapely.geometry import Point 2 | from random import randint 3 | from random import random 4 | from sys import maxint 5 | from math import sqrt 6 | from math import atan 7 | from math import sin 8 | from math import cos 9 | 10 | class Person: 11 | def __init__(self, routeIn, departureTimeIn): 12 | self.route = routeIn 13 | self.departureTime = departureTimeIn 14 | self.id = randint(0,maxint) 15 | self.width = 1.5 + random() 16 | self.length = 0.5 + random() 17 | self.arrived = False 18 | self.crashed = False 19 | self.travelTime = 0 20 | self.speed = 5.0 21 | self.currentRouteSegment = 0 22 | self.centroid = self.route[0] 23 | self.vehicle = False 24 | def vehicleType(self): 25 | if self.vehicle: return self.vehicle.subtype 26 | else: return("Person") 27 | def view(self): 28 | return({"type": "Feature", "id": self.id, "geometry": {"type": "Point", "coordinates": [self.centroid[1]/10, self.centroid[0]/10]}, "properties": {"arrived": self.arrived, "crashed": self.crashed, "vehicle": self.vehicleType()}}) 29 | #return("{\"type\": \"Feature\", \"id\": " + str(self.id) + ", \"geometry\": {\"type\": \"Point\", \"coordinates\": [" + str(self.centroid[1]/10) + "," + str(self.centroid[0]/10) + "]}, \"properties\": {\"arrived\": \"" + str(self.arrived) + "\", \"crashed\": \"" + str(self.crashed) + "\", \"vehicle\": \"" + str(self.vehicleType()) + "\"}}") 30 | # Car/Bike/Ped Route - drive/ride/walk until you hit an intersection, maybe change lanes. at intersection stopLight, stopSign, or go: stright, left, right. Repeat. 31 | # Freeway Route - enter, drive until you exit. 32 | def transport(self): 33 | self.travelTime += 1 34 | nextRouteSegment = self.currentRouteSegment + 1 35 | xDelta = self.route[nextRouteSegment][0] - self.route[self.currentRouteSegment][0] 36 | yDelta = self.route[nextRouteSegment][1] - self.route[self.currentRouteSegment][1] 37 | if sqrt(xDelta**2+yDelta**2) > self.speed: # straightaway logic 38 | theta = atan(xDelta / yDelta) 39 | self.centroid = Point(self.centroid[0] + sin(theta) * self.speed, self.centroid[1] + cos(theta) * self.speed) 40 | else: # intersection logic 41 | self.currentRouteSegment = nextRouteSegment 42 | self.centroid = self.route[self.currentRouteSegment] 43 | if self.currentRouteSegment == len(self.route) - 1: 44 | self.arrived = True 45 | 46 | class Vehicle: 47 | def __init__(self): 48 | self.id = randint(0,maxint) 49 | self.driver = False 50 | self.subtype = "Vehicle" 51 | self.passengers = [] 52 | self.maxPassengers = 0 53 | self.width = 0.0 54 | self.length = 0.0 55 | self.crashed = False 56 | self.centroid = Point(0,0) 57 | 58 | class Bicycle(Vehicle): 59 | def __init__(self): 60 | self.subtype = "Bicycle" 61 | self.maxPassengers = 1 62 | self.width = 2.0 + random() 63 | self.length = 6.0 + random() 64 | 65 | class Car(Vehicle): 66 | def __init__(self): 67 | self.subtype = "Car" 68 | self.maxPassengers = 5 69 | self.width = 6.0 + random() 70 | self.length = 12.0 + random() 71 | 72 | class Bus(Vehicle): 73 | def __init__(self): 74 | self.subtype = "Bus" 75 | self.maxPassengers = 84 76 | self.width = 8.0 + random() 77 | self.length = 40.0 + random() 78 | 79 | class LightRail(Vehicle): 80 | def __init__(self): 81 | self.subtype = "LightRail" 82 | self.maxPassengers = 220 83 | 84 | class HeavyRail(Vehicle): 85 | def __init__(self): 86 | self.subtype = "HeavyRail" 87 | self.maxPassengers = 800 88 | 89 | class Track: 90 | def __init__(self): 91 | self.width = 4.708 92 | 93 | class Railway: 94 | def __init__(self): 95 | self.tracks = [] 96 | 97 | class Lane: 98 | def __init__(self): 99 | self.width = 0.0 100 | 101 | class GeneralLane(Lane): 102 | def __init__(self): 103 | self.width = 10.0 104 | self.allowedVehicles = ["Bicycle,Car,Bus"] 105 | 106 | class FreewayLane(Lane): 107 | def __init__(self): 108 | self.width = 13.0 109 | self.allowedVehicles = ["Car,Bus"] 110 | 111 | class ParkingLane(Lane): 112 | def __init__(self): 113 | self.width = 10.0 114 | self.allowedVehicles = ["Bicycle,Car"] 115 | 116 | class BicycleLane(Lane): 117 | def __init__(self): 118 | self.width = 5.0 119 | self.allowedVehicles = ["Bicycle"] 120 | 121 | class BusLane(Lane): 122 | def __init__(self): 123 | self.width = 5.0 124 | self.allowedVehicles = ["Bicycle,Bus"] 125 | 126 | class Sidewalk(Lane): 127 | def __init__(self): 128 | self.width = 4.0 129 | 130 | class Way: # OSM Way 131 | def __init__(self): 132 | pass 133 | 134 | class HalfRoad: 135 | def __init__(self): 136 | self.lanes = [] 137 | self.parkingLane = ParkingLane() 138 | self.bicycleLane = BicycleLane() 139 | self.busLane = BusLane() 140 | self.sidewalk = Sidewalk() 141 | 142 | class TwoWayRoad: 143 | def __init__(self): 144 | self.wayOne = HalfRoad() 145 | self.wayTwo = HalfRoad() 146 | self.centerLine = [] 147 | 148 | class OneWayRoad: 149 | def __init__(self): 150 | self.wayOne = HalfRoad() 151 | self.centerLine = [] 152 | 153 | class HalfFreeway: 154 | def __init__(self): 155 | self.freewayLanes = [] 156 | 157 | class Freeway: 158 | def __init__(self): 159 | self.wayOne = HalfFreeway() 160 | self.wayTwo = HalfFreeway() 161 | self.centerLine = [] 162 | 163 | class ParkingSpace: 164 | def __init__(self): 165 | self.id = randint(0,maxint) 166 | self.width = 8.0 167 | self.length = 16.0 168 | self.centroid = (0.0,0.0) 169 | self.occupant = False 170 | def view(self): 171 | return("{\"type\": \"Feature\", \"id\": " + str(self.id) + ", \"geometry\": {\"type\": \"Point\", \"coordinates\": [" + str(self.centroid[0]) + "," + str(self.centroid[1]) + "]}, \"properties\": {\"vehicle\": \"" + str(self.occupant) + "\"}}") 172 | 173 | --------------------------------------------------------------------------------