├── __init__.py ├── lib ├── __init__.py └── transit │ ├── __init__.py │ ├── TransitSettings.py │ └── TransitModel.py ├── src ├── img │ ├── map.png │ ├── pin.png │ └── logo.png ├── css │ ├── fullscreen.png │ ├── fullscreen@2x.png │ ├── gtfs.css │ ├── sidebar.css │ ├── app.css │ ├── leaflet.fullscreen.css │ ├── modal.css │ ├── user.css │ ├── enmodal.css │ └── balloon.css ├── view.html ├── unauthorized.html ├── js │ ├── enmodal │ │ ├── sharing.js │ │ ├── bezier.js │ │ ├── utils.js │ │ ├── user.js │ │ ├── settings.js │ │ ├── image.js │ │ ├── navbar-handlers.js │ │ ├── draw.js │ │ ├── markers.js │ │ └── session.js │ └── lib │ │ ├── spline.min.js │ │ ├── filesaver.min.js │ │ ├── leaflet.polylineoffset.min.js │ │ ├── rgbcolor.min.js │ │ ├── lz-string.min.js │ │ ├── leaflet.curve.min.js │ │ ├── spline.js │ │ ├── leaflet.pip.min.js │ │ ├── filesaver.js │ │ ├── stackblur.min.js │ │ ├── leaflet.image.min.js │ │ └── leaflet.polylineoffset.js ├── registration_confirm.html ├── registration_fail.html ├── user.html └── graphviz.html ├── .gitmodules ├── .gitignore ├── tools ├── batch_uploader.py ├── screenshot.js ├── set_up_db.py ├── print_users.py ├── print_registrations.py ├── print_sessions.py ├── cleanser.js ├── export_session_as_json.py ├── interactive_session.py ├── dggrid_to_pts.js ├── census_tract_uploader.js ├── set_dggrid_employment.js ├── import_zip_codes.js └── nationwide_latent_demand_2.js ├── settings.cfg.example ├── package.json ├── requirements.txt ├── server.py ├── Gruntfile.js ├── EnmodalRedis.py ├── EnmodalSessions.py ├── valhalla └── valhalla.json ├── README.md └── EnmodalMap.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/transit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpwright/enmodal/HEAD/src/img/map.png -------------------------------------------------------------------------------- /src/img/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpwright/enmodal/HEAD/src/img/pin.png -------------------------------------------------------------------------------- /src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpwright/enmodal/HEAD/src/img/logo.png -------------------------------------------------------------------------------- /src/css/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpwright/enmodal/HEAD/src/css/fullscreen.png -------------------------------------------------------------------------------- /src/css/fullscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpwright/enmodal/HEAD/src/css/fullscreen@2x.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/pyroutelib2"] 2 | path = lib/pyroutelib2 3 | url = https://github.com/gaulinmp/pyroutelib2.git 4 | -------------------------------------------------------------------------------- /src/css/gtfs.css: -------------------------------------------------------------------------------- 1 | .enm #gtfs-agency-list { 2 | max-height: 50vh; 3 | overflow-y: scroll; 4 | } 5 | 6 | .enm .gtfs-agency { 7 | margin: 10px; 8 | } 9 | 10 | .enm .gtfs-route { 11 | margin-left: 45px; 12 | } -------------------------------------------------------------------------------- /src/view.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 |
5 | 6 | 9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /src/unauthorized.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 |
5 |
you must be logged in to view this page. please log in to continue.
6 |
7 | 10 | 11 | {% endblock %} -------------------------------------------------------------------------------- /src/js/enmodal/sharing.js: -------------------------------------------------------------------------------- 1 | class Sharing { 2 | constructor() { 3 | } 4 | 5 | update(public_key, private_key) { 6 | console.log(location.origin); 7 | $("#share-link-public input").val(location.origin+"/?id="+public_key); 8 | $("#share-link-private input").val(location.origin+"/?id="+private_key); 9 | } 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.out 3 | node_modules/ 4 | .DS_Store 5 | ftp.py 6 | static/geojson/* 7 | transit.gz 8 | venv/* 9 | *.cfg 10 | *.kate-swp 11 | *.pyc 12 | vm_setup.sh 13 | *.csv 14 | data/* 15 | *.zip 16 | *.gz 17 | *.tar 18 | *.osm 19 | *.bin 20 | *.wpr 21 | *.dat 22 | .idea/ 23 | valhalla/*.bin 24 | valhalla/*.txt 25 | valhalla/demos 26 | valhalla/tiles 27 | valhalla/valhalla_tiles 28 | tools/cache/ 29 | uploads/* 30 | -------------------------------------------------------------------------------- /src/css/sidebar.css: -------------------------------------------------------------------------------- 1 | #sidebar { 2 | position: absolute; 3 | padding: 46px 12px 12px 12px; 4 | right: 0px; 5 | top: 0px; 6 | bottom: 0px; 7 | background: rgba(0, 0, 0, 0.75); 8 | color: white; 9 | z-index: 5000; 10 | max-height: 100%; 11 | width: 25%; 12 | min-width: 310px; 13 | display: flex; 14 | display: -webkit-flex; 15 | flex-direction: column; 16 | height: 100vh; 17 | } -------------------------------------------------------------------------------- /src/registration_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 | 10 |
11 |
thanks for confirming your registration! you are now able to login.
12 |
let's get started
13 |
14 | 15 | {% endblock %} -------------------------------------------------------------------------------- /src/registration_fail.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 | 10 | 11 |
12 |
sadly this registration link is expired or otherwise invalid.
13 |
to generate a new registration link, log in using this email address, then click "Resend confirmation email".
14 |
15 | 16 | {% endblock %} -------------------------------------------------------------------------------- /tools/batch_uploader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from os import listdir, system 4 | from os.path import isfile, join, splitext 5 | 6 | DGGRID_FOLDER = "../../dggrid/output/ca-7/" 7 | 8 | files = [f for f in listdir(DGGRID_FOLDER) if isfile(join(DGGRID_FOLDER, f))] 9 | 10 | for f in sorted(files): 11 | filename, file_extension = splitext(f) 12 | if file_extension == '.geojson': 13 | print 'uploading '+filename 14 | system('node nationwide_latent_demand_2.js -c "../static/geojson/tabblock2010_06_7_simplified.geojson" -d "'+DGGRID_FOLDER+f+'"') 15 | -------------------------------------------------------------------------------- /tools/screenshot.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const commandLineArgs = require('command-line-args') 3 | 4 | function sleep(ms) { 5 | return new Promise(resolve => setTimeout(resolve, ms)); 6 | } 7 | 8 | const optionDefinitions = [ 9 | { name: 'url', type: String}, 10 | { name: 'output', type: String} 11 | ]; 12 | 13 | const options = commandLineArgs(optionDefinitions); 14 | 15 | (async () => { 16 | const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']}); 17 | const page = await browser.newPage(); 18 | await page.goto(options.url); 19 | await sleep(5000); 20 | await page.screenshot({path: options.output}); 21 | 22 | await browser.close(); 23 | })(); 24 | -------------------------------------------------------------------------------- /settings.cfg.example: -------------------------------------------------------------------------------- 1 | [flask] 2 | port_http: 5050 3 | domain: http://localhost 4 | secret_key: F12Yr58j4zX T~Y%C!efJ]Dxe/,?KD 5 | use_logins: False 6 | 7 | gtfs_enabled: False 8 | # Folder to store uploads, relative to server.py 9 | upload_folder = uploads 10 | screenshot_folder = dist/img/map-screenshots 11 | 12 | [geocode] 13 | reverse_geocode_provider: mapbox 14 | mapzen_key: MAPZEN_KEY 15 | mapbox_key: MAPBOX_KEY 16 | google_key: GOOGLE_KEY 17 | 18 | [sessions] 19 | host: localhost 20 | port: 5432 21 | dbname: sessions 22 | user: postgres 23 | password: DB_PASSWORD 24 | secret_key_public: aaaaaaaaaaaaaaaa 25 | secret_key_private: bbbbbbbbbbbbbbbb 26 | 27 | # Session expiration time, in seconds. 28 | expiration_time: 1800 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enmodal", 3 | "version": "1.0.0", 4 | "description": "enmodal frontend", 5 | "main": "Gruntfile.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Jason Wright", 13 | "license": "ISC", 14 | "dependencies": { 15 | "command-line-args": "^5.1.1", 16 | "grunt-cli": "^1.3.2", 17 | "grunt-contrib-uglify-es": "^3.3.0", 18 | "puppeteer": "^3.0.4" 19 | }, 20 | "devDependencies": { 21 | "grunt": "^1.3.0", 22 | "grunt-contrib-concat": "^1.0.1", 23 | "grunt-contrib-copy": "^1.0.0", 24 | "grunt-contrib-jshint": "^2.1.0", 25 | "grunt-contrib-uglify": "^4.0.1", 26 | "grunt-contrib-watch": "^1.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bcrypt==3.1.7 2 | certifi==2020.4.5.1 3 | cffi==1.14.1 4 | chardet==3.0.4 5 | click==7.1.2 6 | cycler==0.10.0 7 | dnspython==1.16.0 8 | email-validator==1.1.0 9 | Flask==1.1.2 10 | Flask-Login==0.5.0 11 | Flask-SSLify==0.1.5 12 | geobuf==1.1.1 13 | geographiclib==1.50 14 | geopy==1.22.0 15 | graphviz==0.14 16 | idna==2.9 17 | itsdangerous==1.1.0 18 | Jinja2==2.11.2 19 | lzstring==1.0.4 20 | MailGun-V3==0.3.2 21 | MarkupSafe==1.1.1 22 | memory-profiler==0.57.0 23 | more-itertools==8.2.0 24 | numpy==1.26.0 25 | objgraph==3.4.1 26 | Paste==3.4.0 27 | portend==2.6 28 | protobuf==3.11.3 29 | psutil==5.7.0 30 | psycopg2==2.8.5 31 | pycparser==2.20 32 | pyparsing==2.4.7 33 | python-dateutil==2.8.1 34 | python-http-client==3.2.7 35 | pytz==2020.1 36 | requests==2.23.0 37 | six==1.14.0 38 | street-address==0.4.0 39 | tempora==3.0.0 40 | unicodecsv==0.14.1 41 | urllib3==1.25.9 42 | Werkzeug==1.0.1 43 | zc.lockfile==2.0 44 | -------------------------------------------------------------------------------- /src/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | height: 100%; 4 | width: 100%; 5 | } 6 | 7 | input, 8 | textarea { 9 | border: 1px dashed rgba(255, 255, 255, 0.1); 10 | } 11 | 12 | input:focus, 13 | textarea:focus { 14 | outline: none; 15 | outline-width: 0; 16 | } 17 | 18 | #map-title { 19 | z-index: 4999; 20 | color: white; 21 | position: absolute; 22 | top: 0px; 23 | left: 0px; 24 | width: 75%; 25 | height: 44px; 26 | text-align: center; 27 | } 28 | 29 | #map-title-inner { 30 | background-color: rgba(20, 20, 20, 1.0); 31 | margin-top: 0px; 32 | display: inline-block; 33 | padding: 40px 40px 10px 40px; 34 | text-align: center; 35 | border-radius: 40px; 36 | line-height: 11px; 37 | } 38 | 39 | input.enm-editable { 40 | background-color: rgba(20, 20, 20, 1.0); 41 | color: white; 42 | font-size: 1em; 43 | height: 2em; 44 | width: 200px; 45 | margin: 0px; 46 | padding: 5px; 47 | } -------------------------------------------------------------------------------- /tools/set_up_db.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import psycopg2 3 | import psycopg2.extras 4 | import os 5 | 6 | config = configparser.RawConfigParser() 7 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'settings.cfg'))) 8 | 9 | SESSIONS_HOST = config.get('sessions', 'host') 10 | SESSIONS_PORT = config.get('sessions', 'port') 11 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 12 | SESSIONS_USER = config.get('sessions', 'user') 13 | SESSIONS_PASSWORD = config.get('sessions', 'password') 14 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 15 | # print the connection string we will use to connect 16 | print("Connecting to database\n ->%s" % (SESSIONS_CONN_STRING)) 17 | 18 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 19 | cursor = conn.cursor() 20 | 21 | query = "CREATE TABLE IF NOT EXISTS sessions (id BIGSERIAL PRIMARY KEY, data jsonb, updated timestamp without time zone, owner_id int, title text);" 22 | #print query 23 | cursor.execute(query) 24 | 25 | 26 | conn.commit() 27 | 28 | cursor.close() 29 | conn.close() -------------------------------------------------------------------------------- /src/css/leaflet.fullscreen.css: -------------------------------------------------------------------------------- 1 | .leaflet-control-fullscreen a { 2 | background:#fff url(fullscreen.png) no-repeat 0 0; 3 | background-size:26px 52px; 4 | } 5 | .leaflet-touch .leaflet-control-fullscreen a { 6 | background-position: 2px 2px; 7 | } 8 | .leaflet-fullscreen-on .leaflet-control-fullscreen a { 9 | background-position:0 -26px; 10 | } 11 | .leaflet-touch.leaflet-fullscreen-on .leaflet-control-fullscreen a { 12 | background-position: 2px -24px; 13 | } 14 | 15 | /* Do not combine these two rules; IE will break. */ 16 | .leaflet-container:-webkit-full-screen { 17 | width:100%!important; 18 | height:100%!important; 19 | } 20 | .leaflet-container.leaflet-fullscreen-on { 21 | width:100%!important; 22 | height:100%!important; 23 | } 24 | 25 | .leaflet-pseudo-fullscreen { 26 | position:fixed!important; 27 | width:100%!important; 28 | height:100%!important; 29 | top:0!important; 30 | left:0!important; 31 | z-index:99999; 32 | } 33 | 34 | @media 35 | (-webkit-min-device-pixel-ratio:2), 36 | (min-resolution:192dpi) { 37 | .leaflet-control-fullscreen a { 38 | background-image:url(fullscreen@2x.png); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/js/enmodal/bezier.js: -------------------------------------------------------------------------------- 1 | class Pin { 2 | constructor(location) { 3 | this.location = location; 4 | this.sid = _id_factory.id(); 5 | this.marker = null; 6 | } 7 | 8 | draw() { 9 | enmodal.transit_interface.layers.active.line_paths.addLayer(this.marker); 10 | } 11 | 12 | undraw() { 13 | enmodal.transit_interface.layers.active.line_paths.removeLayer(this.marker); 14 | } 15 | 16 | toJSON() { 17 | return {"sid": this.sid, "location": this.location}; 18 | } 19 | } 20 | 21 | class SplineSegment { 22 | constructor(controls, centers) { 23 | this.controls = controls; 24 | this.centers = centers; 25 | } 26 | } 27 | 28 | class LineSplineSegment { 29 | constructor(line, spline_segments, reverse) { 30 | this.line = line; 31 | this.spline_segments = spline_segments; 32 | this.reverse = reverse; 33 | } 34 | } 35 | 36 | class BezierControlPoint { 37 | constructor(lat, lng) { 38 | this.lat = lat; 39 | this.lng = lng; 40 | } 41 | } 42 | 43 | class BezierCenter { 44 | constructor(lat, lng) { 45 | this.lat = lat; 46 | this.lng = lng; 47 | } 48 | } -------------------------------------------------------------------------------- /tools/print_users.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import psycopg2 3 | import psycopg2.extras 4 | import os 5 | 6 | config = ConfigParser.RawConfigParser() 7 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'settings.cfg'))) 8 | 9 | SESSIONS_HOST = config.get('sessions', 'host') 10 | SESSIONS_PORT = config.get('sessions', 'port') 11 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 12 | SESSIONS_USER = config.get('sessions', 'user') 13 | SESSIONS_PASSWORD = config.get('sessions', 'password') 14 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 15 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 16 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 17 | 18 | # print the connection string we will use to connect 19 | print "Connecting to database\n ->%s" % (SESSIONS_CONN_STRING) 20 | 21 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 22 | cursor = conn.cursor() 23 | 24 | query = "SELECT id, first_name, last_name, email, created, last_login from users ORDER BY last_login ASC;" 25 | print query 26 | cursor.execute(query) 27 | 28 | rows = cursor.fetchall() 29 | for row in rows: 30 | print "id: %d, first_name: %s, last_name: %s, email: %s, created: %s, last_login: %s" % (row[0], row[1], row[2], row[3], row[4], row[5]) 31 | 32 | conn.commit() 33 | 34 | cursor.close() 35 | conn.close() 36 | -------------------------------------------------------------------------------- /tools/print_registrations.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import psycopg2 3 | import psycopg2.extras 4 | import os 5 | 6 | config = ConfigParser.RawConfigParser() 7 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'settings.cfg'))) 8 | 9 | SESSIONS_HOST = config.get('sessions', 'host') 10 | SESSIONS_PORT = config.get('sessions', 'port') 11 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 12 | SESSIONS_USER = config.get('sessions', 'user') 13 | SESSIONS_PASSWORD = config.get('sessions', 'password') 14 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 15 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 16 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 17 | 18 | # print the connection string we will use to connect 19 | print "Connecting to database\n ->%s" % (SESSIONS_CONN_STRING) 20 | 21 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 22 | cursor = conn.cursor() 23 | 24 | query = "SELECT id, first_name, last_name, email, created, email_sent from pending_registrations ORDER BY created ASC;" 25 | print query 26 | cursor.execute(query) 27 | 28 | rows = cursor.fetchall() 29 | for row in rows: 30 | print "id: %d, first_name: %s, last_name: %s, email: %s, created: %s, email_sent: %s" % (row[0], row[1], row[2], row[3], row[4], row[5]) 31 | 32 | conn.commit() 33 | 34 | cursor.close() 35 | conn.close() 36 | -------------------------------------------------------------------------------- /tools/print_sessions.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import psycopg2 3 | import psycopg2.extras 4 | import os 5 | 6 | config = ConfigParser.RawConfigParser() 7 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'settings.cfg'))) 8 | 9 | SESSIONS_HOST = config.get('sessions', 'host') 10 | SESSIONS_PORT = config.get('sessions', 'port') 11 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 12 | SESSIONS_USER = config.get('sessions', 'user') 13 | SESSIONS_PASSWORD = config.get('sessions', 'password') 14 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 15 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 16 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 17 | 18 | # print the connection string we will use to connect 19 | print "Connecting to database\n ->%s" % (SESSIONS_CONN_STRING) 20 | 21 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 22 | cursor = conn.cursor() 23 | 24 | query = "SELECT id, updated, title, owner_id from sessions ORDER BY updated ASC;" 25 | print query 26 | cursor.execute(query) 27 | 28 | rows = cursor.fetchall() 29 | for row in rows: 30 | print "id: %d, updated: %s, public: %s, private: %s, title: %s, owner_id: %s" % (row[0], row[1], str("%x" % (row[0] ^ SESSIONS_SECRET_KEY_PUBLIC)), str("%x" % (row[0] ^ SESSIONS_SECRET_KEY_PRIVATE)), row[2], row[3]) 31 | 32 | conn.commit() 33 | 34 | cursor.close() 35 | conn.close() 36 | -------------------------------------------------------------------------------- /src/css/modal.css: -------------------------------------------------------------------------------- 1 | .enm.modal { 2 | width: 75%; 3 | max-width: -webkit-calc(100% - 310px); 4 | max-width: -moz-calc(100% - 310px); 5 | max-width: calc(100% - 310px); 6 | height: 100%; 7 | color: white; 8 | display: table; 9 | z-index: 900; 10 | position: absolute; 11 | top: 0; 12 | } 13 | 14 | .enm.modal-middle { 15 | display: table-cell; 16 | vertical-align: middle; 17 | } 18 | 19 | .enm.modal-inner { 20 | width: 80%; 21 | background: rgba(0, 0, 0, 0.75); 22 | border: 1px solid #777; 23 | display: flex; 24 | margin: auto; 25 | } 26 | 27 | .enm.modal-row { 28 | display: flex; 29 | flex-direction: row; 30 | width: 100%; 31 | padding: 10px; 32 | } 33 | 34 | .enm.modal-col { 35 | display: flex; 36 | flex-direction: column; 37 | width: 100%; 38 | } 39 | 40 | .enm.modal .header { 41 | font-size: 1.5em; 42 | width: 100%; 43 | background: rgba(255, 255, 255, 0.1); 44 | padding: 10px; 45 | } 46 | 47 | .enm.modal .header a.button { 48 | float: right; 49 | font-size: 14px; 50 | } 51 | 52 | .enm.modal .body { 53 | padding: 10px; 54 | } 55 | 56 | .enm.modal input.large { 57 | color: #222; 58 | font-size: 3em; 59 | height: 2em; 60 | } 61 | 62 | .enm.modal input.large.picker { 63 | padding-left: 0.4em; 64 | width: 78%; 65 | } 66 | 67 | .enm.modal a.button.large { 68 | font-size: 2em; 69 | line-height: 2.5em; 70 | } 71 | 72 | .enm.modal a.button.large.picker { 73 | width: 20%; 74 | margin-left: 2%; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/css/user.css: -------------------------------------------------------------------------------- 1 | #user-tab-content { 2 | margin-top: 12px; 3 | } 4 | 5 | #user-tab-content input { 6 | background-color: rgba(255, 255, 255, 0.2); 7 | border: 0; 8 | color: white; 9 | padding: 5px; 10 | max-width: 300px; 11 | } 12 | 13 | #user-tab-content .has-error input { 14 | background-color: rgba(50, 0, 0, 0.5); 15 | } 16 | 17 | #user-tab-content button { 18 | border: 1px solid white; 19 | border-radius: 5px; 20 | cursor: pointer; 21 | display: inline-block; 22 | color: #fff; 23 | text-decoration: none; 24 | padding: 5px; 25 | min-width: 50px; 26 | text-align: center; 27 | background-color: rgba(255, 255, 255, 0.10); 28 | } 29 | 30 | #change-password-done { 31 | width: 300px; 32 | max-width: 100%; 33 | } 34 | 35 | #change-password-problem { 36 | width: 300px; 37 | max-width: 100%; 38 | } 39 | 40 | .user-map { 41 | float: left; 42 | margin-top: 12px; 43 | margin-right: 12px; 44 | padding: 12px; 45 | border: 1px solid white; 46 | border-radius: 5px; 47 | cursor: pointer; 48 | display: inline-block; 49 | color: #fff; 50 | text-decoration: none; 51 | text-align: center; 52 | background-color: rgba(255, 255, 255, 0.10); 53 | } 54 | 55 | .map-title { 56 | margin: 10px; 57 | color: #fff; 58 | } 59 | 60 | .map-title:hover { 61 | text-decoration: none; 62 | } 63 | 64 | .map-actions { 65 | text-align: right; 66 | } 67 | 68 | .user-map img { 69 | width: 200px; 70 | } 71 | 72 | .user-map:hover { 73 | background: rgba(255, 255, 255, 0.20); 74 | } 75 | 76 | .user-map a:hover { 77 | text-decoration: none; 78 | } 79 | } -------------------------------------------------------------------------------- /tools/cleanser.js: -------------------------------------------------------------------------------- 1 | var jsonfile = require('jsonfile'); 2 | var turf = require('turf'); 3 | 4 | var input_file = 'geojson/everything.geojson'; 5 | 6 | var turf_polygons = {}; 7 | var tract_centroids = {}; 8 | var tract_nearby = {}; 9 | 10 | jsonfile.readFile(input_file, function(err, data) { 11 | console.log('Loaded tracts'); 12 | 13 | var manhattan_center = { 14 | "type": "Feature", 15 | "properties": {}, 16 | "geometry": { 17 | "type": "Point", 18 | "coordinates": [-74.005416, 40.721926] 19 | } 20 | }; 21 | 22 | var new_data = JSON.parse(JSON.stringify(data)); 23 | 24 | for (tract_index in data.features) { 25 | var feature = data.features[tract_index] 26 | var properties = feature.properties; 27 | var properties_new = {"S":properties["STATEFP10"], "L":properties["LOCALNAME"],"C":properties["TRACTCE10"],"H":properties["HH_COUNT"]}; 28 | new_data.features[tract_index].properties = properties_new; 29 | } 30 | var rem_data = JSON.parse(JSON.stringify(new_data)); 31 | rem_data.features = []; 32 | for (tract_index in new_data.features) { 33 | var feature = new_data.features[tract_index] 34 | var centroid = turf.centroid(feature); 35 | //console.log(feature); 36 | console.log(centroid.geometry.coordinates); 37 | var distance = turf.distance(centroid, manhattan_center, "miles"); 38 | //console.log(distance); 39 | 40 | if (distance < 20.0) { 41 | rem_data.features.push(feature); 42 | } 43 | 44 | } 45 | 46 | jsonfile.writeFile('everything_node.geojson', rem_data, {spaces: 2}, function(err) { 47 | console.error(err); 48 | }); 49 | }); -------------------------------------------------------------------------------- /src/js/enmodal/utils.js: -------------------------------------------------------------------------------- 1 | function get_url_parameter(param) { 2 | var vars = {}; 3 | window.location.href.replace( location.hash, '' ).replace( 4 | /[?&]+([^=&]+)=?([^&]*)?/gi, // regexp 5 | function( m, key, value ) { // callback 6 | vars[key] = value !== undefined ? value : ''; 7 | } 8 | ); 9 | 10 | if (param) { 11 | return vars[param] ? vars[param].replace(/\#$/, '') : null; 12 | } 13 | return vars; 14 | } 15 | 16 | function num_unique_colors(lines) { 17 | var ret = 0; 18 | var used_colors = []; 19 | for (var i = 0; i < lines.length; i++) { 20 | if (used_colors.indexOf(lines[i].color_bg) == -1) { 21 | used_colors.push(lines[i].color_bg); 22 | ret += 1; 23 | } 24 | } 25 | return ret; 26 | } 27 | 28 | function station_distance(station_1, station_2) { 29 | var s1 = { 30 | "type": "Feature", 31 | "properties": {}, 32 | "geometry": { 33 | "type": "Point", 34 | "coordinates": [station_1.location[1], station_1.location[0]] 35 | } 36 | }; 37 | var s2 = { 38 | "type": "Feature", 39 | "properties": {}, 40 | "geometry": { 41 | "type": "Point", 42 | "coordinates": [station_2.location[1], station_2.location[0]] 43 | } 44 | }; 45 | 46 | return turf.distance(s1, s2, "miles"); 47 | } 48 | 49 | function is_latlng(s) { 50 | return /^(\-)?[0-9]{0,3}.[0-9]*,(\ )?(\-)?[0-9]{0,3}.[0-9]*$/.test(s); 51 | } 52 | 53 | function get_latlng(s) { 54 | var c = s.split(","); 55 | return [parseFloat(c[0]), parseFloat(c[1])]; 56 | } 57 | 58 | function save_json() { 59 | var json = session_json(); 60 | var blob = new Blob([json], {type: "text/plain;charset=utf-8"}); 61 | saveAs(blob, "enmodal.json"); 62 | } -------------------------------------------------------------------------------- /tools/export_session_as_json.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import psycopg2 3 | import psycopg2.extras 4 | import os 5 | import argparse 6 | import json 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser(description='Export a session as JSON.') 10 | parser.add_argument('id', help='session id') 11 | args = parser.parse_args() 12 | 13 | config = ConfigParser.RawConfigParser() 14 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'settings.cfg'))) 15 | 16 | SESSIONS_HOST = config.get('sessions', 'host') 17 | SESSIONS_PORT = config.get('sessions', 'port') 18 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 19 | SESSIONS_USER = config.get('sessions', 'user') 20 | SESSIONS_PASSWORD = config.get('sessions', 'password') 21 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 22 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 23 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 24 | 25 | # print the connection string we will use to connect 26 | print "Connecting to database\n ->%s" % (SESSIONS_CONN_STRING) 27 | 28 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 29 | cursor = conn.cursor() 30 | 31 | public_id = int(args.id, 16) ^ SESSIONS_SECRET_KEY_PUBLIC 32 | print public_id 33 | query = "SELECT updated, data from sessions WHERE id=%d;" % (public_id) 34 | print query 35 | cursor.execute(query) 36 | 37 | row = cursor.fetchone() 38 | print "id: %d, updated: %s, data: %s" % (public_id, row[0], row[1]) 39 | outfile = open(args.id+".json", "w") 40 | outfile.write(json.dumps(row[1])) 41 | outfile.close() 42 | 43 | conn.commit() 44 | 45 | cursor.close() 46 | conn.close() 47 | 48 | if __name__ == "__main__": 49 | main() -------------------------------------------------------------------------------- /tools/interactive_session.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import psycopg2 3 | import psycopg2.extras 4 | import os 5 | import argparse 6 | import json 7 | import sys 8 | 9 | sys.path.insert(0, '../lib/transit') 10 | import Transit 11 | from TransitGIS import * 12 | 13 | def main(): 14 | parser = argparse.ArgumentParser(description='Load a session by ID and run interactive session') 15 | parser.add_argument('id', help='session id') 16 | args = parser.parse_args() 17 | 18 | config = ConfigParser.RawConfigParser() 19 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'settings.cfg'))) 20 | 21 | SESSIONS_HOST = config.get('sessions', 'host') 22 | SESSIONS_PORT = config.get('sessions', 'port') 23 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 24 | SESSIONS_USER = config.get('sessions', 'user') 25 | SESSIONS_PASSWORD = config.get('sessions', 'password') 26 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 27 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 28 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 29 | 30 | # print the connection string we will use to connect 31 | print "Connecting to database\n ->%s" % (SESSIONS_CONN_STRING) 32 | 33 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 34 | cursor = conn.cursor() 35 | 36 | public_id = int(args.id, 16) ^ SESSIONS_SECRET_KEY_PUBLIC 37 | #print public_id 38 | query = "SELECT updated, data from sessions WHERE id=%d;" % (public_id) 39 | #print query 40 | cursor.execute(query) 41 | 42 | row = cursor.fetchone() 43 | #print "id: %d, updated: %s, data: %s" % (public_id, row[0], row[1]) 44 | 45 | conn.commit() 46 | 47 | cursor.close() 48 | conn.close() 49 | 50 | m = Transit.Map(0) 51 | m.from_json(row[1]) 52 | return m 53 | 54 | if __name__ == "__main__": 55 | map = main() 56 | service = map.services[0] -------------------------------------------------------------------------------- /src/js/enmodal/user.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $.ajax({ url: "user-maps", 3 | async: true, 4 | dataType: 'json', 5 | success: function(data, status) { 6 | console.log(data); 7 | $("#user-maps").html(""); 8 | for (var i = 0; i < data.maps.length; i++) { 9 | var map = data.maps[i]; 10 | var cache_string = (Math.random()+1).toString(36).slice(2, 5); 11 | $("#user-maps").append('
'+map.title+'
Delete
'); 12 | } 13 | } 14 | }); 15 | 16 | $("#change-password-submit").click(function(e) { 17 | $.ajax({ url: "change-password", 18 | async: true, 19 | method: "POST", 20 | data: $("#ajax-change-password-form").serialize(), 21 | dataType: 'json', 22 | success: function(data, status) { 23 | console.log("register"); 24 | if (data.result == "OK") { 25 | $(".form-group").removeClass("has-error"); 26 | $("#change-password-submit").hide(); 27 | $("#change-password-problem").fadeOut(); 28 | $("#change-password-done").fadeIn(); 29 | } else { 30 | $(".form-group").removeClass("has-error"); 31 | for (var i = 0; i < data.fields.length; i++) { 32 | $("#"+data.fields[i]).addClass("has-error"); 33 | } 34 | $("#change-password-problem").text(data.message); 35 | $("#change-password-problem").fadeIn(); 36 | } 37 | } 38 | }); 39 | }); 40 | }); -------------------------------------------------------------------------------- /lib/transit/TransitSettings.py: -------------------------------------------------------------------------------- 1 | import json 2 | import Transit 3 | 4 | class Pin(object): 5 | 6 | def __init__(self): 7 | self.location = [] 8 | 9 | def to_json(self): 10 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True) 11 | 12 | def from_json(self, j): 13 | self.location = [float(j["location"][0]), float(j["location"][1])] 14 | 15 | class StationPair(object): 16 | 17 | def __init__(self, station_1, station_2): 18 | self.station_ids = [station_1, station_2] 19 | self.pins = [] 20 | 21 | def has_stations(self, s1, s2): 22 | if (self.station_ids[0] == s1 and self.station_ids[1] == s2) or (self.station_ids[1] == s1 and self.station_ids[0] == s2): 23 | return True 24 | else: 25 | return False 26 | 27 | def add_pin(self, pin): 28 | self.pins.append(pin) 29 | 30 | def set_pins(self, pins): 31 | self.pins = pins 32 | 33 | def to_json(self): 34 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True) 35 | 36 | def from_json(self, j): 37 | self.station_ids = j["station_ids"] 38 | for pin in j["pins"]: 39 | p = Pin() 40 | p.from_json(pin) 41 | self.add_pin(p) 42 | 43 | 44 | class Settings(object): 45 | """A Map contains a collection of Services, everything needed for a single Transit session. 46 | 47 | Attributes: 48 | services: An array of Services. 49 | """ 50 | 51 | def __init__(self): 52 | self.station_pairs = [] 53 | 54 | def config_station_pair(self, s1, s2, pins): 55 | station_pair_found = False 56 | for station_pair in self.station_pairs: 57 | if station_pair.has_stations(s1, s2): 58 | station_pair_found = True 59 | station_pair.set_pins(pins) 60 | if not station_pair_found: 61 | sp = StationPair(s1, s2) 62 | sp.set_pins(pins) 63 | self.station_pairs.append(sp) 64 | 65 | def to_json(self): 66 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True) 67 | 68 | def from_json(self, j): 69 | self.station_pairs = [] 70 | for station_pair in j['station_pairs']: 71 | s = StationPair(station_pair['station_ids'][0], station_pair['station_ids'][1]) 72 | s.from_json(station_pair) 73 | self.station_pairs.append(s) -------------------------------------------------------------------------------- /src/js/lib/spline.min.js: -------------------------------------------------------------------------------- 1 | var BezierSpline=function(options){this.points=options.points||[];this.duration=options.duration||1e4;this.sharpness=options.sharpness||.85;this.centers=[];this.controls=[];this.stepLength=options.stepLength||60;this.length=this.points.length;this.delay=0;for(var i=0;imindist){steps.push(t);laststep=step}}return steps};BezierSpline.prototype.vector=function(t){var p1=this.pos(t+10);var p2=this.pos(t-10);return{angle:180*Math.atan2(p1.y-p2.y,p1.x-p2.x)/3.14,speed:Math.sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)+(p2.z-p1.z)*(p2.z-p1.z))}};BezierSpline.prototype.pos=function(time){function bezier(t,p1,c1,c2,p2){var B=function(t){var t2=t*t,t3=t2*t;return[t3,3*t2*(1-t),3*t*(1-t)*(1-t),(1-t)*(1-t)*(1-t)]};var b=B(t);var pos={x:p2.x*b[0]+c2.x*b[1]+c1.x*b[2]+p1.x*b[3],y:p2.y*b[0]+c2.y*b[1]+c1.y*b[2]+p1.y*b[3],z:p2.z*b[0]+c2.z*b[1]+c1.z*b[2]+p1.z*b[3]};return pos}var t=time-this.delay;if(t<0)t=0;if(t>this.duration)t=this.duration-1;var t2=t/this.duration;if(t2>=1)return this.points[this.length-1];var n=Math.floor((this.points.length-1)*t2);var t1=(this.length-1)*t2-n;return bezier(t1,this.points[n],this.controls[n][1],this.controls[n+1][0],this.points[n+1])}; 2 | -------------------------------------------------------------------------------- /src/js/enmodal/settings.js: -------------------------------------------------------------------------------- 1 | // Game version 2 | var GAME_VERSION = 0.13; 3 | 4 | // Send incremental updates to server? 5 | var INC_UPDATES = true; 6 | 7 | // Used to set async parameter for all server requests. 8 | var ASYNC_REQUIRED = true; 9 | var ASYNC_OPTIONAL = false; 10 | 11 | // Drawing parameters 12 | var CURVE_THRESHOLD = 0.005; // Max overshoot from curve momentum. 13 | var MARKER_RADIUS_DEFAULT = 4.0; 14 | var MARKER_RADIUS_LARGE = 8.0; 15 | var MARKER_RADIUS_HUGE = 12.0; 16 | var MARKER_MERGE_DELTA = 4.0; 17 | var STATION_MARKER_LARGE_THRESHOLD = 3; // Number of groups needed to force a large station marker 18 | var STATION_MARKER_HUGE_THRESHOLD = 4; 19 | var STATION_MARKER_SCALE_THRESHOLD = 6; 20 | var TRACK_WIDTH = 4.0; 21 | var TRACK_OFFSET = 4.0; 22 | var TRANSFER_WIDTH = 3.0; 23 | var TRANSFER_PREVIEW_OPACITY = 0.75; 24 | var MAX_TRANSFER_DISTANCE_MILES = 0.25; 25 | 26 | var USE_CURVED_TRACKS = true; 27 | var CURVE_OVERSHOOT = 0.5; 28 | var BEZIER_SHARPNESS = 0.4; 29 | 30 | var DGGRID_AREA = 0.0733633; 31 | var MAX_ZOOM = 16; 32 | var MIN_ZOOM = 6; 33 | var START_ZOOM = 13; 34 | 35 | var MIN_ZOOM_FOR_HEXAGONS = 13; 36 | 37 | var DEBUG_MODE = false; 38 | 39 | // Map rendering parameters 40 | var SHARED_STRETCH_THRESHOLD = 8; // Max number of "local" stations in a shared stretch. 41 | 42 | var TRANSFER_BUTTON_DEFAULT = "Start Transfer"; 43 | var TRANSFER_BUTTON_START = "Click a station"; 44 | var TRANSFER_BUTTON_END = "Click another station"; 45 | 46 | // Instructions for calculate_ridership function 47 | var RIDERSHIP_ADD = 0; 48 | var RIDERSHIP_NOCHANGE = 1; 49 | var RIDERSHIP_DELETE = 2; 50 | 51 | // Custom lines 52 | var CUSTOM_LINE_FIRST_INDEX = 97; 53 | 54 | var FOLLOW_STREET_MOVE_THRESH = 500; 55 | 56 | var PIN_DISTANCE_MIN = 16; 57 | var PIN_DISTANCE_FROM_STATION_MIN = 8; 58 | var PIN_DISTANCE_TO_SHOW_PINS = 100; 59 | var PIN_DISTANCE_FROM_EXISTING_PIN_MIN = 40; 60 | 61 | var INACTIVE_OPACITY = 0.25; 62 | 63 | var BEZIER_LUT_STEPS = 100; 64 | 65 | var STATION_MERGE_THRESHOLD = 8; 66 | var ALLOW_STATION_MERGING = true; 67 | var SERVICE_MODES_ENABLED = false; 68 | 69 | var PIN_ICON = L.icon({ 70 | iconUrl: 'static/img/pin.png', 71 | iconSize: [30, 25], 72 | iconAnchor: [15, 25] 73 | }); 74 | 75 | var DEFAULT_LINE_BG = "#808183"; 76 | var DEFAULT_LINE_FG = "#FFF"; 77 | 78 | HEXAGON_SCALES = { 79 | "population": chroma.scale('YlGnBu').domain([1,0]), 80 | "employment": chroma.scale('YlOrRd').domain([1,0]) 81 | }; 82 | HEXAGON_UNITS = { 83 | "population": "persons / mile2", 84 | "employment": "jobs / mile2" 85 | }; 86 | 87 | var DEBUG_BEZIER_CONTROLS = false; 88 | var DEBUG_PIN_PROJECTIONS = false; 89 | 90 | var GTFS_ENABLED = true; 91 | 92 | var UNDO_BUFFER_SIZE = 20; -------------------------------------------------------------------------------- /tools/dggrid_to_pts.js: -------------------------------------------------------------------------------- 1 | var jsonfile = require('jsonfile'); 2 | var turf = require('turf'); 3 | var ProgressBar = require('progress'); 4 | var pg = require('pg'); 5 | var argv = require("minimist")(process.argv.slice(2)); 6 | var parse = require('csv-parse/lib/sync'); 7 | var fs = require('fs'); 8 | var wellknown = require('wellknown'); 9 | 10 | var config = { 11 | user: 'postgres', 12 | database: 'transit', 13 | password: 'nostrand', 14 | host: 'localhost', 15 | port: 5432, 16 | max: 10, 17 | idleTimeoutMillis: 100000000 18 | }; 19 | 20 | var pool = new pg.Pool(config); 21 | var dggrids_processed = 0; 22 | 23 | pool.connect(function(err, client, done) { 24 | if (err) { 25 | return console.error('error fetching client from pool', err); 26 | } else { 27 | var sel_result = client.query("SELECT id, gid, ST_AsText(geo) FROM dggrid WHERE id > 4000000 ORDER BY id ASC LIMIT 500000;", function(sel_err, sel_result) { 28 | if (sel_err) { 29 | console.error('error running query', sel_err); 30 | } 31 | var bar = new ProgressBar(' processing [:bar] :percent :etas', { 32 | complete: '=', 33 | incomplete: ' ', 34 | width: 100, 35 | total: sel_result.rows.length 36 | }); 37 | 38 | console.log(sel_result.rows.length.toString() + " dggrids found."); 39 | for (var i = 0; i < sel_result.rows.length; i++) { 40 | var row = sel_result.rows[i]; 41 | db_sync(client, bar, row); 42 | } 43 | }); 44 | } 45 | }); 46 | 47 | function db_sync(client, bar, row) { 48 | 49 | var dggrid_geo = row.st_astext; 50 | var dggrid_gid = row.gid; 51 | var dggrid_obj = wellknown.parse(dggrid_geo); 52 | var dggrid_centroid = turf.centroid(dggrid_obj); 53 | var wkt = wellknown.stringify(dggrid_centroid); 54 | /* 55 | var wkt = 'POLYGON(('; 56 | for (var i = 0; i < feature["geometry"]["coordinates"][0].length; i++) { 57 | var coordinate = feature["geometry"]["coordinates"][0][i]; 58 | wkt += coordinate[0]; 59 | wkt += ' '; 60 | wkt += coordinate[1]; 61 | if (i < feature["geometry"]["coordinates"][0].length - 1) { 62 | wkt += ', '; 63 | } else { 64 | wkt += '))'; 65 | } 66 | } 67 | */ 68 | 69 | client.query("INSERT INTO dgpt (gid, geo) VALUES("+dggrid_gid+", ST_GeomFromText('"+wkt+"'));", function(psd_err, psd_result) { 70 | if (psd_err) { 71 | console.error('error running query', psd_err); 72 | } 73 | }); 74 | bar.tick(); 75 | } 76 | 77 | pool.on('error', function (err, client) { 78 | console.error('idle client error', err.message, err.stack) 79 | }); -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # Flask and plugins 2 | from flask import Flask, render_template, request, after_this_request 3 | from flask_login import LoginManager, UserMixin, current_user, login_required, login_user, logout_user 4 | 5 | # enmodal libraries 6 | import os 7 | import sys 8 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__)))) 9 | from EnmodalCore import enmodal 10 | from EnmodalMap import enmodal_map 11 | from EnmodalSessions import * 12 | from EnmodalGTFS import enmodal_gtfs 13 | 14 | # psycopg2 15 | import psycopg2 16 | import psycopg2.extras 17 | 18 | # misc 19 | import uuid 20 | import json 21 | 22 | # config 23 | import configparser 24 | config = configparser.RawConfigParser() 25 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), 'settings.cfg'))) 26 | PORT_HTTP = int(config.get('flask', 'port_http')) 27 | SESSIONS_HOST = config.get('sessions', 'host') 28 | SESSIONS_PORT = config.get('sessions', 'port') 29 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 30 | SESSIONS_USER = config.get('sessions', 'user') 31 | SESSIONS_PASSWORD = config.get('sessions', 'password') 32 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 33 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 34 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 35 | SESSION_EXPIRATION_TIME = int(config.get('sessions', 'expiration_time')) 36 | UPLOAD_FOLDER = config.get('flask', 'upload_folder') 37 | SCREENSHOT_FOLDER = config.get('flask', 'screenshot_folder') 38 | 39 | # set up app object 40 | application = Flask(__name__, static_folder='dist', template_folder='dist', static_url_path='/static') 41 | application.register_blueprint(enmodal) 42 | application.secret_key = config.get('flask', 'secret_key') 43 | 44 | login_manager = LoginManager() 45 | login_manager.init_app(application) 46 | 47 | application.register_blueprint(enmodal_map) 48 | application.register_blueprint(enmodal_gtfs) 49 | 50 | @login_manager.user_loader 51 | def load_user(user): 52 | return User.get(user) 53 | 54 | @application.route('/session') 55 | def route_session_status(): 56 | s = EnmodalSession() 57 | session_manager.add(s) 58 | a = session_manager.auth_by_key(s.private_key()) 59 | 60 | return_obj = {"is_private": a.editable, "public_key": '{:16x}'.format(a.session.public_key())} 61 | if a.editable: 62 | return_obj["private_key"] = '{:16x}'.format(a.session.private_key()) 63 | del a 64 | return json.dumps(return_obj) 65 | 66 | def run_server(): 67 | application.run(port = PORT_HTTP) 68 | 69 | if __name__ == "__main__": 70 | 71 | if not os.path.isdir(UPLOAD_FOLDER): 72 | os.mkdir(UPLOAD_FOLDER) 73 | if not os.path.isdir(UPLOAD_FOLDER): 74 | os.mkdir(UPLOAD_FOLDER) 75 | 76 | run_server() 77 | -------------------------------------------------------------------------------- /src/css/enmodal.css: -------------------------------------------------------------------------------- 1 | /* links */ 2 | .enm a { 3 | color: rgb(58, 80, 114); 4 | } 5 | 6 | .enm a:hover { 7 | color: rgb(58, 80, 114); 8 | text-decoration: underline; 9 | } 10 | 11 | /* buttons */ 12 | .enm a.button, .enm label.button { 13 | cursor: pointer; 14 | color: #fff; 15 | border: 1px solid white; 16 | border-radius: 5px; 17 | text-decoration: none; 18 | background-color: rgba(255, 255, 255, 0.10); 19 | padding: 5px; 20 | text-align: center; 21 | display: inline-block; 22 | font-weight: normal; 23 | } 24 | 25 | .enm a.button:hover, .enm label.button:hover { 26 | background: rgba(255, 255, 255, 0.20); 27 | color: #fff; 28 | } 29 | 30 | .enm a.button.active { 31 | background-color: rgba(255, 200, 200, 0.1); 32 | font-weight: bold; 33 | } 34 | 35 | .enm button.collapse-caret { 36 | display: inline-block; 37 | background: none; 38 | border: none; 39 | padding-top: 0em; 40 | width: 20px; 41 | } 42 | 43 | /* form inputs */ 44 | 45 | .enm .dropbox { 46 | outline: 1px dashed #aaa; /* the dash box */ 47 | outline-offset: -10px; 48 | background-color: rgba(255, 255, 255, 0.10); 49 | padding: 40px; 50 | min-height: 200px; /* minimum height */ 51 | position: relative; 52 | cursor: pointer; 53 | } 54 | 55 | .enm .input-file { 56 | opacity: 0; /* invisible but it's there! */ 57 | width: 100%; 58 | height: 200px; 59 | position: absolute; 60 | cursor: pointer; 61 | } 62 | 63 | .enm .dropbox:hover { 64 | background-color: rgba(255, 255, 255, 0.20); 65 | } 66 | 67 | .enm .dropbox p { 68 | font-size: 1.2em; 69 | text-align: center; 70 | padding: 50px 0; 71 | } 72 | 73 | .enm label { 74 | font-weight: normal; 75 | } 76 | 77 | /* scrollbars */ 78 | 79 | ::-webkit-scrollbar-track { 80 | background-color: none; 81 | } 82 | 83 | ::-webkit-scrollbar { 84 | width: 6px; 85 | background-color: none; 86 | } 87 | 88 | ::-webkit-scrollbar-thumb { 89 | border-radius: 6px; 90 | background-color: rgba(255, 255, 255, 0.5); 91 | } 92 | 93 | /* bootstrap modifiers */ 94 | .nav-tabs>li>a { 95 | background: rgba(255, 255, 255, 0.6); 96 | } 97 | 98 | /* bootstrap-vue modifiers */ 99 | .collapsed > .when-opened, 100 | :not(.collapsed) > .when-closed { 101 | display: none; 102 | } 103 | 104 | .enm button.dropdown-button:hover { 105 | border: 1px solid white; 106 | } 107 | 108 | .enm button.enm:hover { 109 | border: none; 110 | background: none; 111 | } 112 | 113 | .enm button.enm:active { 114 | border: none; 115 | background: none; 116 | } 117 | 118 | .enm button.enm:focus { 119 | outline: 0; 120 | } 121 | 122 | /* leaflet/esri modifiers */ 123 | .enm .leaflet-pane.leaflet-stationMarker-pane, 124 | .enm .leaflet-pane.leaflet-inactiveStationMarker-pane, 125 | .enm .leaflet-pane.leaflet-overlay-pane { 126 | z-index: 510; 127 | } -------------------------------------------------------------------------------- /src/js/lib/filesaver.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),c=e.webkitRequestFileSystem,d=e.requestFileSystem||c||e.mozRequestFileSystem,u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",f=0,l=4e4,v=function(e){var t=function(){"string"==typeof e?n().revokeObjectURL(e):e.remove()};setTimeout(t,l)},p=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){u(i)}}},w=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob(["\uFEFF",e],{type:e.type}):e},y=function(t,u,l){l||(t=w(t));var y,m,S,h=this,R=t.type,O=!1,g=function(){p(h,"writestart progress write writeend".split(" "))},b=function(){if(m&&a&&"undefined"!=typeof FileReader){var o=new FileReader;return o.onloadend=function(){var e=o.result;m.location.href="data:attachment/file"+e.slice(e.search(/[,;]/)),h.readyState=h.DONE,g()},o.readAsDataURL(t),void(h.readyState=h.INIT)}if((O||!y)&&(y=n().createObjectURL(t)),m)m.location.href=y;else{var r=e.open(y,"_blank");void 0===r&&a&&(e.location.href=y)}h.readyState=h.DONE,g(),v(y)},E=function(e){return function(){return h.readyState!==h.DONE?e.apply(this,arguments):void 0}},N={create:!0,exclusive:!1};return h.readyState=h.INIT,u||(u="download"),r?(y=n().createObjectURL(t),void setTimeout(function(){o.href=y,o.download=u,i(o),g(),v(y),h.readyState=h.DONE})):(e.chrome&&R&&R!==s&&(S=t.slice||t.webkitSlice,t=S.call(t,0,t.size,s),O=!0),c&&"download"!==u&&(u+=".download"),(R===s||c)&&(m=e),d?(f+=t.size,void d(e.TEMPORARY,f,E(function(e){e.root.getDirectory("saved",N,E(function(e){var n=function(){e.getFile(u,N,E(function(e){e.createWriter(E(function(n){n.onwriteend=function(t){m.location.href=e.toURL(),h.readyState=h.DONE,p(h,"writeend",t),v(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&b()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=h["on"+e]}),n.write(t),h.abort=function(){n.abort(),h.readyState=h.DONE},h.readyState=h.WRITING}),b)}),b)};e.getFile(u,{create:!1},E(function(e){e.remove(),n()}),E(function(e){e.code===e.NOT_FOUND_ERR?n():b()}))}),b)}),b)):void b())},m=y.prototype,S=function(e,t,n){return new y(e,t,n)};return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,n){return n||(e=w(e)),navigator.msSaveOrOpenBlob(e,t||"download")}:(m.abort=function(){var e=this;e.readyState=e.DONE,p(e,"abort")},m.readyState=m.INIT=0,m.WRITING=1,m.DONE=2,m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null,S)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!==define.amd&&define([],function(){return saveAs}); -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | // Metadata. 7 | meta: { 8 | version: '0.1.0' 9 | }, 10 | banner: '/*! enmodal - v<%= meta.version %> - ' + 11 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 12 | '* http://enmodal.co/\n' + 13 | '* Copyright (c) <%= grunt.template.today("yyyy") %> ' + 14 | 'Jason Wright; Licensed MIT */\n', 15 | // Task configuration. 16 | copy: { 17 | main: { 18 | files: [ 19 | {expand: true, flatten: true, src: ['src/js/enmodal/*.js'], dest: 'dist/js/', filter: 'isFile'}, 20 | {expand: true, flatten: true, src: ['src/js/lib/*.min.js'], dest: 'dist/js/', filter: 'isFile'}, 21 | {expand: true, flatten: true, src: ['src/css/*.css'], dest: 'dist/css/', filter: 'isFile'}, 22 | {expand: true, flatten: true, src: ['src/img/*'], dest: 'dist/img/', filter: 'isFile'}, 23 | {expand: true, flatten: true, src: ['src/*.html'], dest: 'dist/', filter: 'isFile'}, 24 | ], 25 | }, 26 | }, 27 | concat: { 28 | options: { 29 | banner: '<%= banner %>', 30 | sourceMap: true, 31 | stripBanners: true 32 | }, 33 | dist: { 34 | src: ['src/js/enmodal/main.js', 35 | 'src/js/enmodal/bezier.js', 36 | 'src/js/enmodal/data-layers.js', 37 | 'src/js/enmodal/components.js', 38 | 'src/js/enmodal/draw.js', 39 | 'src/js/enmodal/image.js', 40 | 'src/js/enmodal/interface.js', 41 | 'src/js/enmodal/markers.js', 42 | 'src/js/enmodal/session.js', 43 | 'src/js/enmodal/settings.js', 44 | 'src/js/enmodal/sharing.js', 45 | 'src/js/enmodal/sidebar.js', 46 | 'src/js/enmodal/station-pair.js', 47 | 'src/js/enmodal/transit.js', 48 | 'src/js/enmodal/utils.js' 49 | ], 50 | dest: 'dist/js/enmodal.js' 51 | } 52 | }, 53 | uglify: { 54 | options: { 55 | banner: '<%= banner %>', 56 | sourceMap: true, 57 | sourceMapIncludeSources: true, 58 | sourceMapIn: 'dist/js/enmodal.js.map' 59 | }, 60 | dist: { 61 | src: '<%= concat.dist.dest %>', 62 | dest: 'dist/js/enmodal.min.js' 63 | } 64 | }, 65 | jshint: { 66 | files: ['Gruntfile.js', 'src/js/enmodal/*.js'], 67 | options: { 68 | reporterOutput: "", 69 | esnext: true, 70 | globals: { 71 | jQuery: true 72 | } 73 | } 74 | }, 75 | 76 | watch: { 77 | files: ['<%= jshint.files %>', 'src/*.html', 'src/css/*.css'], 78 | tasks: ['copy', 'concat', 'uglify'] 79 | }, 80 | }); 81 | 82 | // These plugins provide necessary tasks. 83 | grunt.loadNpmTasks('grunt-contrib-jshint'); 84 | grunt.loadNpmTasks('grunt-contrib-watch'); 85 | grunt.loadNpmTasks('grunt-contrib-copy'); 86 | grunt.loadNpmTasks('grunt-contrib-concat'); 87 | grunt.loadNpmTasks('grunt-contrib-uglify-es'); 88 | grunt.loadNpmTasks('grunt-vue'); 89 | 90 | // Default task. 91 | grunt.registerTask('default', ['copy', 'concat', 'uglify', 'jshint']); 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /src/user.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | 4 | 5 | 6 | 7 |
8 | 16 |
17 | 18 |
19 |
20 | Create a new map 21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 |

Change password

29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 46 | 48 |
49 | 50 |
51 |
52 | 53 | 54 | 70 | 71 | {% endblock %} -------------------------------------------------------------------------------- /src/js/enmodal/image.js: -------------------------------------------------------------------------------- 1 | var OUTPUT_WIDTH_PX = 2000; 2 | var OUTPUT_HEIGHT_PX = 2000; 3 | var OUTPUT_DPI = 72; 4 | var OUTPUT_WIDTH_PT = OUTPUT_WIDTH_PX * (0.75); 5 | var OUTPUT_HEIGHT_PT = OUTPUT_HEIGHT_PX * (0.75); 6 | 7 | function save_svg(canvas, callback) { 8 | var draw = SVG('svg-drawing').size(OUTPUT_HEIGHT_PX,OUTPUT_WIDTH_PX); 9 | 10 | var svg_overlay = $("div.leaflet-overlay-pane svg").html(); 11 | var svg_markers = $("div.leaflet-stationMarker-pane svg").html(); 12 | 13 | draw.svg(svg_overlay); 14 | draw.svg(svg_markers); 15 | 16 | var b64 = btoa(draw.svg()); 17 | //var link = $('').appendTo('body'); 18 | //link[0].click(); 19 | 20 | 21 | canvgv2(document.getElementById('canvas'), draw.svg()); 22 | var d = document.getElementById('canvas').toDataURL("image/png"); 23 | $('#svg-drawing').empty(); 24 | 25 | var ctx = canvas.getContext("2d"); 26 | 27 | var image = new Image(); 28 | 29 | var pixel_bounds = _leaflet_map.getPixelBounds(); 30 | var pixel_origin = _leaflet_map.getPixelOrigin(); 31 | var placement_x = pixel_origin.x - pixel_bounds.min.x; 32 | var placement_y = pixel_origin.y - pixel_bounds.min.y; 33 | 34 | image.onload = function() { 35 | ctx.drawImage(image, placement_x, placement_y); 36 | callback(ctx); 37 | }; 38 | image.src = d; 39 | //var link = $('').appendTo('body'); 40 | //link[0].click(); 41 | } 42 | 43 | function create_image(callback) { 44 | 45 | var center = _leaflet_map.getCenter(); 46 | var zoom = _leaflet_map.getZoom(); 47 | 48 | $("#map").css("height", OUTPUT_HEIGHT_PX); 49 | $("#map").css("width", OUTPUT_WIDTH_PX); 50 | _leaflet_map.invalidateSize(); 51 | 52 | enmodal.transit_interface.preview_clear(); 53 | var bounds = enmodal.transit_map.geographic_bounds(); 54 | if (bounds !== null) _leaflet_map.fitBounds(bounds); 55 | 56 | //_leaflet_map.setView(center, zoom); 57 | 58 | $("#map").hide(); 59 | setTimeout(function() { 60 | leafletImage(_leaflet_map, function(err, canvas) { 61 | //var dimensions = _leaflet_map.getSize(); 62 | save_svg(canvas, function(ctx) { 63 | // Add enmodal footer 64 | ctx.fillStyle = 'rgba(0,0,0,0.75)'; 65 | ctx.fillRect(0, 1964, 2000, 36); 66 | ctx.font = '12px sans-serif'; 67 | ctx.fillStyle = 'white'; 68 | if (enmodal.map_name !== null) { 69 | ctx.fillText(enmodal.map_name, 12, 1988); 70 | } 71 | ctx.textAlign = 'right'; 72 | ctx.fillText("created with enmodal -- http://enmodal.io", 1988, 1988); 73 | callback(canvas); 74 | }); 75 | }); 76 | }, 1000); 77 | } 78 | 79 | function save_image() { 80 | create_image(function(canvas) { 81 | var link = $('').appendTo('body'); 82 | link[0].click(); 83 | var ctx = canvas.getContext("2d"); 84 | ctx.clearRect(0, 0, canvas.width, canvas.height); 85 | document.getElementById('canvas').getContext("2d").clearRect(0, 0, canvas.width, canvas.height); 86 | }); 87 | } 88 | 89 | function save_pdf(callback) { 90 | create_image(function(canvas) { 91 | var pdf = new jsPDF({ 92 | orientation: 'landscape', 93 | unit: 'pt', 94 | format: [OUTPUT_WIDTH_PT, OUTPUT_HEIGHT_PT] 95 | }); 96 | pdf.addImage(canvas.toDataURL("image/jpeg", 1.0), 'JPEG', 0, 0); 97 | pdf.save('enmodal-'+enmodal.session_id+'.pdf'); 98 | var ctx = canvas.getContext("2d"); 99 | ctx.clearRect(0, 0, canvas.width, canvas.height); 100 | document.getElementById('canvas').getContext("2d").clearRect(0, 0, canvas.width, canvas.height); 101 | $("#map").css("height", ""); 102 | $("#map").css("width", ""); 103 | $("#map").show(); 104 | _leaflet_map.invalidateSize(); 105 | callback(); 106 | }); 107 | } -------------------------------------------------------------------------------- /src/js/enmodal/navbar-handlers.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $("#register-submit").click(function(e) { 3 | $.ajax({ url: "register", 4 | async: true, 5 | method: "POST", 6 | data: $("#ajax-register-form").serialize(), 7 | dataType: 'json', 8 | success: function(data, status) { 9 | console.log("register"); 10 | if (data.result == "OK") { 11 | $(".form-group").removeClass("has-error"); 12 | $("#register-submit").hide(); 13 | $("#dropdown-register-problem").fadeOut(); 14 | $("#dropdown-register-done").fadeIn(); 15 | } else { 16 | $(".form-group").removeClass("has-error"); 17 | for (var i = 0; i < data.fields.length; i++) { 18 | $("#"+data.fields[i]).addClass("has-error"); 19 | } 20 | $("#dropdown-register-problem").text(data.message); 21 | $("#dropdown-register-problem").fadeIn(); 22 | } 23 | } 24 | }); 25 | }); 26 | 27 | $("#login-submit").click(function(e) { 28 | $.ajax({ url: "login", 29 | async: true, 30 | method: "POST", 31 | data: $("#ajax-login-form").serialize(), 32 | dataType: 'json', 33 | success: function(data, status) { 34 | console.log("login"); 35 | if (data.result == "OK") { 36 | $("#logged-in-user-email").text(data.email); 37 | $("#nav-user-logged-in").show(); 38 | $("#nav-user-logged-out").hide(); 39 | if (UNAUTH_PAGE_ACCESS) { 40 | location.reload(); 41 | } 42 | } else { 43 | $("#login-reset-password").hide(); 44 | $("#login-resend-validation").hide(); 45 | $("#login-problem").hide(); 46 | if (data.message == "pending registration") { 47 | $("#login-resend-validation").fadeIn(); 48 | } else { 49 | $("#login-problem").fadeIn(); 50 | } 51 | } 52 | } 53 | }); 54 | }); 55 | 56 | $(".forgot-password").click(function(e) { 57 | $.ajax({ url: "reset-password", 58 | async: true, 59 | method: "POST", 60 | data: $("#ajax-login-form").serialize(), 61 | dataType: 'json', 62 | success: function(data, status) { 63 | console.log("login"); 64 | if (data.result == "OK") { 65 | $("#login-resend-validation").hide(); 66 | $("#login-problem").hide(); 67 | $("#login-reset-password").fadeIn(); 68 | } else { 69 | $("#login-resend-validation").hide(); 70 | $("#login-problem").text(data.message); 71 | $("#login-problem").fadeIn(); 72 | } 73 | } 74 | }); 75 | }); 76 | 77 | $("#login-resend-validation-button").click(function(e) { 78 | $.ajax({ url: "resend-registration", 79 | async: true, 80 | method: "POST", 81 | data: $("#ajax-login-form").serialize(), 82 | dataType: 'json', 83 | success: function(data, status) { 84 | //console.log("resend-registration"); 85 | if (data.result == "OK") { 86 | $("#login-resend-validation").text("A new confirmation will be sent to you shortly."); 87 | } 88 | } 89 | }); 90 | }); 91 | 92 | $("#logout").click(function(e) { 93 | $.ajax({ url: "logout", 94 | async: true, 95 | dataType: 'json', 96 | success: function(data, status) { 97 | console.log("logout"); 98 | location.reload(); 99 | } 100 | }); 101 | }); 102 | }); -------------------------------------------------------------------------------- /src/js/lib/leaflet.polylineoffset.min.js: -------------------------------------------------------------------------------- 1 | L.PolylineOffset={translatePoint:function(pt,dist,radians){return L.point(pt.x+dist*Math.cos(radians),pt.y+dist*Math.sin(radians))},offsetPointLine:function(points,distance){var l=points.length;if(l<2){throw new Error("Line should be defined by at least 2 points")}var a=points[0],b,xs,ys,sqDist;var offsetAngle,segmentAngle;var offsetSegments=[];var sqDistance=distance*distance;for(var i=1;isqDistance){offsetSegments.push({angle:segmentAngle,offsetAngle:offsetAngle,distance:distance,original:[a,b],offset:[this.translatePoint(a,distance,offsetAngle),this.translatePoint(b,distance,offsetAngle)]});a=b}}return offsetSegments},latLngsToPoints:function(ll,map){var pts=[];for(var i=0,l=ll.length;istartAngle+Math.PI){return[this.intersection(s1.offset[0],s1.offset[1],s2.offset[0],s2.offset[1])]}var step=Math.abs(8/distance);for(var a=startAngle;a0){points.reverse()}return points}};if(L.version.charAt(0)=="0"&&parseInt(L.version.charAt(2))<8){L.Polyline.include({projectLatlngs:function(){this._originalPoints=[];for(var i=0,len=this._latlngs.length;i SESSION_EXPIRATION_TIME 98 | 99 | 100 | class EnmodalSessionAuthentication(object): 101 | def __init__(self, s, editable): 102 | self.session = s 103 | self.editable = editable 104 | 105 | def returnable_key(self): 106 | if self.editable: 107 | return '{:16x}'.format(self.session.private_key()) 108 | else: 109 | return '{:16x}'.format(self.session.public_key()) 110 | 111 | def check_for_session_errors(h): 112 | if session_manager.auth_by_key(h) is None: 113 | print("session auth problem with key "+str(h)) 114 | return json.dumps({"error": "Invalid session"}) 115 | 116 | return 0 117 | 118 | session_manager = EnmodalSessionManager() 119 | -------------------------------------------------------------------------------- /src/js/lib/rgbcolor.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function f(e){this.ok=!1,"#"==e.charAt(0)&&(e=e.substr(1,6)),e=(e=e.replace(/ /g,"")).toLowerCase();var a={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};for(var r in a)e==r&&(e=a[r]);for(var t=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(e){return[parseInt(e[1]),parseInt(e[2]),parseInt(e[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}}],d=0;d255?255:this.r,this.g=this.g<0||isNaN(this.g)?0:this.g>255?255:this.g,this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var e=this.r.toString(16),f=this.g.toString(16),a=this.b.toString(16);return 1==e.length&&(e="0"+e),1==f.length&&(f="0"+f),1==a.length&&(a="0"+a),"#"+e+f+a},this.getHelpXML=function(){for(var e=new Array,r=0;r "+s.toRGB()+" -> "+s.toHex());o.appendChild(c),o.appendChild(b),l.appendChild(o)}catch(e){}return l}}"undefined"!=typeof define&&define.amd?define(function(){return f}):"undefined"!=typeof module&&module.exports&&(module.exports=f),e.RGBColor=f}("undefined"!=typeof window?window:this); -------------------------------------------------------------------------------- /src/js/lib/lz-string.min.js: -------------------------------------------------------------------------------- 1 | var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString); -------------------------------------------------------------------------------- /EnmodalSessions.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import os 3 | import sys 4 | import datetime 5 | import json 6 | 7 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib', 'transit'))) 8 | import Transit 9 | 10 | import configparser 11 | 12 | config = configparser.RawConfigParser() 13 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), 'settings.cfg'))) 14 | 15 | SESSIONS_HOST = config.get('sessions', 'host') 16 | SESSIONS_PORT = config.get('sessions', 'port') 17 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 18 | SESSIONS_USER = config.get('sessions', 'user') 19 | SESSIONS_PASSWORD = config.get('sessions', 'password') 20 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 21 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 22 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 23 | SESSION_EXPIRATION_TIME = int(config.get('sessions', 'expiration_time')) 24 | 25 | class EnmodalSessionManager(object): 26 | def __init__(self): 27 | self.sessions = [] 28 | 29 | def get_by_sid(self, sid): 30 | for s in self.sessions: 31 | if s.sid == sid: 32 | return s 33 | return None 34 | 35 | def get_by_public_key(self, h): 36 | for s in self.sessions: 37 | if s.public_key() == h: 38 | return s 39 | return None 40 | 41 | def get_by_private_key(self, h): 42 | for s in self.sessions: 43 | if s.private_key() == h: 44 | return s 45 | return None 46 | 47 | def get_sid_from_public_key(self, h): 48 | return h ^ SESSIONS_SECRET_KEY_PUBLIC 49 | 50 | def get_sid_from_private_key(self, h): 51 | return h ^ SESSIONS_SECRET_KEY_PRIVATE 52 | 53 | def add(self, s): 54 | self.sessions.append(s) 55 | 56 | # Whenever we add a new session, check for old ones to remove. 57 | purged = self.purge() 58 | print(str(len(self.sessions))+" active sessions, "+str(purged)+" purged.") 59 | 60 | def remove_by_sid(self, sid): 61 | s = self.get_by_sid(sid) 62 | if s is not None: 63 | self.sessions.remove(s) 64 | 65 | def auth_by_key(self, h): 66 | public_session = self.get_by_public_key(h) 67 | if public_session is not None: 68 | public_session.keep_alive() 69 | a = EnmodalSessionAuthentication(public_session, False) 70 | return a 71 | private_session = self.get_by_private_key(h) 72 | if private_session is not None: 73 | private_session.keep_alive() 74 | a = EnmodalSessionAuthentication(private_session, True) 75 | return a 76 | return None 77 | 78 | def purge(self): 79 | num_sessions_start = len(self.sessions) 80 | #for session in self.sessions: 81 | #if session.is_expired(): 82 | #save_session(session, None, False) 83 | self.sessions = [x for x in self.sessions if not x.is_expired()] 84 | return num_sessions_start - len(self.sessions) 85 | 86 | class EnmodalSession(object): 87 | def __init__(self): 88 | self.sid = uuid.uuid4().int & (1<<63)-1 89 | self.map = Transit.Map(0) 90 | self.last_edit_time = datetime.datetime.now() 91 | 92 | def public_key(self): 93 | return self.sid ^ SESSIONS_SECRET_KEY_PUBLIC 94 | 95 | def private_key(self): 96 | return self.sid ^ SESSIONS_SECRET_KEY_PRIVATE 97 | 98 | def keep_alive(self): 99 | self.last_edit_time = datetime.datetime.now() 100 | 101 | def is_expired(self): 102 | return (datetime.datetime.now() - self.last_edit_time).total_seconds() > SESSION_EXPIRATION_TIME 103 | 104 | 105 | class EnmodalSessionAuthentication(object): 106 | def __init__(self, s, editable): 107 | self.session = s 108 | self.editable = editable 109 | 110 | def returnable_key(self): 111 | if self.editable: 112 | return '{:16x}'.format(self.session.private_key()) 113 | else: 114 | return '{:16x}'.format(self.session.public_key()) 115 | 116 | def check_for_session_errors(h): 117 | if session_manager.auth_by_key(h) is None: 118 | print("session auth problem with key "+str(h)) 119 | return json.dumps({"error": "Invalid session"}) 120 | 121 | return 0 122 | 123 | session_manager = EnmodalSessionManager() 124 | -------------------------------------------------------------------------------- /tools/census_tract_uploader.js: -------------------------------------------------------------------------------- 1 | var jsonfile = require('jsonfile'); 2 | var turf = require('turf'); 3 | var ProgressBar = require('progress'); 4 | var pg = require('pg'); 5 | var argv = require("minimist")(process.argv.slice(2)); 6 | var parse = require('csv-parse/lib/sync'); 7 | var fs = require('fs'); 8 | 9 | var settings = { 10 | "c": "../geojson/tl_2010_36_tract10.geojson", 11 | "p": "../../census/census_tracts_list_36.csv", 12 | "w": "../geojson/usa_water_bodies.geojson" 13 | }; 14 | 15 | for (var key in argv) { 16 | if (key in settings) { 17 | settings[key] = argv[key]; 18 | } 19 | } 20 | 21 | var CENSUS_FILE = settings["c"]; 22 | var POPULATION_FILE = settings["p"]; 23 | var WATER_FILE = settings["w"]; 24 | 25 | var config = { 26 | user: 'postgres', 27 | database: 'transit', 28 | password: 'nostrand', 29 | host: 'localhost', 30 | port: 5432, 31 | max: 10, 32 | idleTimeoutMillis: 100000000 33 | }; 34 | 35 | var pool = new pg.Pool(config); 36 | 37 | pool.connect(function(err, client, done) { 38 | if (err) { 39 | return console.error('error fetching client from pool', err); 40 | } 41 | 42 | client.query('CREATE TABLE IF NOT EXISTS census ( \ 43 | tract_id bigint PRIMARY KEY, \ 44 | geo geometry, \ 45 | population int \ 46 | );', function(err, result) { 47 | done(); 48 | 49 | if (err) { 50 | return console.error('error running query', err); 51 | } else { 52 | fs.readFile(POPULATION_FILE, function(pop_err, pop_data) { 53 | 54 | if (pop_err) { 55 | return console.log(pop_err); 56 | } 57 | var population_records = parse(pop_data, {columns: true}); 58 | var populations = {}; 59 | 60 | jsonfile.readFile(CENSUS_FILE, function(census_err, census_data) { 61 | console.log('Loaded census file '+CENSUS_FILE); 62 | console.log(census_data.features.length + ' total features'); 63 | 64 | var bar = new ProgressBar(' processing [:bar] :percent :etas', { 65 | complete: '=', 66 | incomplete: ' ', 67 | width: 100, 68 | total: census_data.features.length 69 | }); 70 | 71 | for (var k = 0; k < population_records.length; k++) { 72 | var record = population_records[k]; 73 | populations[record["GEOID"]] = record["POP10"]; 74 | } 75 | 76 | for (var i = 0; i < census_data.features.length; i++) { 77 | var feature = census_data.features[i]; 78 | 79 | var wkt = 'POLYGON(('; 80 | for (var j = 0; j < feature["geometry"]["coordinates"][0].length; j++) { 81 | var coordinate = feature["geometry"]["coordinates"][0][j]; 82 | wkt += coordinate[0]; 83 | wkt += ' '; 84 | wkt += coordinate[1]; 85 | if (j < feature["geometry"]["coordinates"][0].length - 1) { 86 | wkt += ', '; 87 | } else { 88 | wkt += '))'; 89 | } 90 | } 91 | 92 | var geoid = feature.properties["STATEFP10"] + feature.properties["COUNTYFP10"] + feature.properties["TRACTCE10"]; 93 | 94 | //console.log("geoid: "+geoid+", wkt: ..., population: "+populations[geoid]); 95 | //console.log(wkt); 96 | client.query("INSERT INTO census (tract_id, geo, population) \ 97 | VALUES("+parseInt(geoid)+", ST_GeomFromText('"+wkt+"'), "+populations[geoid]+");", function(psd_err, psd_result) { 98 | if (psd_err) { 99 | console.error('error running query', psd_err); 100 | console.log(wkt); 101 | } 102 | done(); 103 | }); 104 | 105 | 106 | 107 | } 108 | 109 | bar.tick(); 110 | 111 | }); 112 | 113 | }); //fs.readFile 114 | } 115 | }); 116 | 117 | 118 | 119 | }); 120 | 121 | pool.on('error', function (err, client) { 122 | console.error('idle client error', err.message, err.stack) 123 | }) 124 | -------------------------------------------------------------------------------- /src/js/lib/leaflet.curve.min.js: -------------------------------------------------------------------------------- 1 | L.Curve=L.Path.extend({options:{},initialize:function(t,n){L.setOptions(this,n),this._setPath(t)},getPath:function(){return this._coords},setPath:function(t){return this._setPath(t),this.redraw()},getBounds:function(){return this._bounds},_setPath:function(t){this._coords=t,this._bounds=this._computeBounds()},_computeBounds:function(){for(var t,n,e,o=new L.LatLngBounds,i=0;is+Math.PI)return[this.intersection(t.offset[0],t.offset[1],n.offset[0],n.offset[1])];for(var a=Math.abs(8/e),f=s;f0&&i.reverse(),i},_project:function(){var t,n,e,o;this._points=[];var i=this._coords[1],s=this._coords[this._coords.length-1],r=this._map.latLngToLayerPoint([i[0],i[1]]),a=this._map.latLngToLayerPoint([s[0],s[1]]),f=Math.atan2(a.x-r.x,a.y-r.y);f+=Math.PI/2;for(var l=Math.sin(f)*this.options.offset,h=Math.cos(f)*this.options.offset,u=0;u (255*3/2)) { 81 | this.fr = 0; 82 | this.fg = 0; 83 | this.fb = 0; 84 | } 85 | } 86 | 87 | bg_hex() { 88 | var rs = ("0" + this.r.toString(16).toUpperCase()).slice(-2); 89 | var rg = ("0" + this.g.toString(16).toUpperCase()).slice(-2); 90 | var rb = ("0" + this.b.toString(16).toUpperCase()).slice(-2); 91 | return "#"+rs+rg+rb; 92 | } 93 | 94 | fg_hex() { 95 | var rs = ("0" + this.fr.toString(16).toUpperCase()).slice(-2); 96 | var rg = ("0" + this.fg.toString(16).toUpperCase()).slice(-2); 97 | var rb = ("0" + this.fb.toString(16).toUpperCase()).slice(-2); 98 | return "#"+rs+rg+rb; 99 | } 100 | } 101 | 102 | class Hexagon { 103 | 104 | constructor(id, geo, color, opacity) { 105 | this.sid = id; 106 | this.geo = geo; 107 | this.color = color; 108 | this.opacity = opacity; 109 | this.style = this.generate_style(); 110 | this.poly = this.generate_poly(); 111 | } 112 | 113 | generate_poly() { 114 | return L.geoJSON(this.geo, {style: this.style}); 115 | } 116 | 117 | update_poly() { 118 | this.poly = this.generate_poly(); 119 | } 120 | 121 | generate_style() { 122 | return { 123 | color: this.color, 124 | stroke: false, 125 | fillOpacity: this.opacity 126 | }; 127 | } 128 | 129 | update_style() { 130 | this.style = this.generate_style(); 131 | this.poly.setStyle(this.style); 132 | //this.poly.redraw(); 133 | } 134 | 135 | draw() { 136 | NS_interface.data_layer.addLayer(this.poly); 137 | } 138 | } -------------------------------------------------------------------------------- /src/js/lib/spline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * BezierSpline 3 | * https://github.com/leszekr/bezier-spline-js 4 | * 5 | * @copyright 6 | * Copyright (c) 2013 Leszek Rybicki 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | /* 28 | Usage: 29 | 30 | var spline = new Spline({ 31 | points: array_of_control_points, 32 | duration: time_in_miliseconds, 33 | sharpness: how_curvy, 34 | stepLength: distance_between_points_to_cache 35 | }); 36 | 37 | */ 38 | var BezierSpline = function(options){ 39 | this.points = options.points || []; 40 | this.duration = options.duration || 10000; 41 | this.sharpness = options.sharpness || 0.85; 42 | this.centers = []; 43 | this.controls = []; 44 | this.stepLength = options.stepLength || 60; 45 | this.length = this.points.length; 46 | this.delay = 0; 47 | // this is to ensure compatibility with the 2d version 48 | for(var i=0; imindist){ 86 | steps.push(t); 87 | laststep = step; 88 | } 89 | } 90 | return steps; 91 | }; 92 | 93 | /* 94 | returns angle and speed in the given point in the curve 95 | */ 96 | BezierSpline.prototype.vector = function(t){ 97 | var p1 = this.pos(t+10); 98 | var p2 = this.pos(t-10); 99 | return { 100 | angle:180*Math.atan2(p1.y-p2.y, p1.x-p2.x)/3.14, 101 | speed:Math.sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)+(p2.z-p1.z)*(p2.z-p1.z)) 102 | }; 103 | }; 104 | 105 | /* 106 | Gets the position of the point, given time. 107 | 108 | WARNING: The speed is not constant. The time it takes between control points is constant. 109 | 110 | For constant speed, use Spline.steps[i]; 111 | */ 112 | BezierSpline.prototype.pos = function(time){ 113 | 114 | function bezier(t, p1, c1, c2, p2){ 115 | var B = function(t) { 116 | var t2=t*t, t3=t2*t; 117 | return [(t3),(3*t2*(1-t)),(3*t*(1-t)*(1-t)),((1-t)*(1-t)*(1-t))] 118 | } 119 | var b = B(t) 120 | var pos = { 121 | x : p2.x * b[0] + c2.x * b[1] +c1.x * b[2] + p1.x * b[3], 122 | y : p2.y * b[0] + c2.y * b[1] +c1.y * b[2] + p1.y * b[3], 123 | z : p2.z * b[0] + c2.z * b[1] +c1.z * b[2] + p1.z * b[3] 124 | } 125 | return pos; 126 | } 127 | var t = time-this.delay; 128 | if(t<0) t=0; 129 | if(t>this.duration) t=this.duration-1; 130 | //t = t-this.delay; 131 | var t2 = (t)/this.duration; 132 | if(t2>=1) return this.points[this.length-1]; 133 | 134 | var n = Math.floor((this.points.length-1)*t2); 135 | var t1 = (this.length-1)*t2-n; 136 | return bezier(t1,this.points[n],this.controls[n][1],this.controls[n+1][0],this.points[n+1]); 137 | } 138 | 139 | -------------------------------------------------------------------------------- /tools/set_dggrid_employment.js: -------------------------------------------------------------------------------- 1 | var jsonfile = require('jsonfile'); 2 | var turf = require('turf'); 3 | var ProgressBar = require('progress'); 4 | var pg = require('pg'); 5 | var argv = require("minimist")(process.argv.slice(2)); 6 | var parse = require('csv-parse/lib/sync'); 7 | var fs = require('fs'); 8 | var wellknown = require('wellknown'); 9 | 10 | var config = { 11 | user: 'postgres', 12 | database: 'transit', 13 | password: 'nostrand', 14 | host: 'localhost', 15 | port: 5432, 16 | max: 10, 17 | idleTimeoutMillis: 100000000 18 | }; 19 | 20 | var pool = new pg.Pool(config); 21 | var dggrids_processed = 0; 22 | 23 | pool.connect(function(err, client, done) { 24 | if (err) { 25 | return console.error('error fetching client from pool', err); 26 | } 27 | 28 | client.query('CREATE TABLE IF NOT EXISTS dggrid ( \ 29 | id BIGSERIAL PRIMARY KEY, \ 30 | gid bigint UNIQUE, \ 31 | geo geometry, \ 32 | population int, \ 33 | employment int \ 34 | );', function(err, result) { 35 | //done(); 36 | 37 | if (err) { 38 | return console.error('error running query', err); 39 | } else { 40 | 41 | var sel_result = client.query("SELECT id, gid, ST_AsText(geo) FROM dggrid WHERE id > 1700000 ORDER BY id ASC LIMIT 100000;", function(sel_err, sel_result) { 42 | if (sel_err) { 43 | console.error('error running query', sel_err); 44 | } 45 | var bar = new ProgressBar(' processing [:bar] :percent :etas', { 46 | complete: '=', 47 | incomplete: ' ', 48 | width: 100, 49 | total: sel_result.rows.length 50 | }); 51 | 52 | console.log(sel_result.rows.length.toString() + " dggrids found."); 53 | for (var i = 0; i < sel_result.rows.length; i++) { 54 | var row = sel_result.rows[i]; 55 | db_sync(client, bar, row); 56 | } 57 | 58 | /*if (sel_result.rows.length >= 1) { 59 | client.query("UPDATE dggrid SET population = "+s_population+" WHERE gid = "+s_gid+";", function(psd_err, psd_result) { 60 | 61 | if (psd_err) { 62 | console.error('error running query', psd_err); 63 | } 64 | 65 | dggrids_queried += 1; 66 | //console.log("Query done for dggrid "+dggrids_queried+" of "+dggrids_with_overlap); 67 | if (dggrids_queried == dggrids_with_overlap) { 68 | process.exit(); 69 | } 70 | }); 71 | } else { 72 | 73 | client.query("INSERT INTO dggrid (gid, geo, population) VALUES("+s_gid+", ST_GeomFromText('"+s_wkt+"'), "+s_population+");", function(psd_err, psd_result) { 74 | 75 | if (psd_err) { 76 | console.error('error running query', psd_err); 77 | } 78 | 79 | dggrids_queried += 1; 80 | //console.log("Query done for dggrid "+dggrids_queried+" of "+dggrids_with_overlap); 81 | if (dggrids_queried == dggrids_with_overlap) { 82 | process.exit(); 83 | } 84 | }); 85 | 86 | }*/ 87 | }); 88 | } 89 | }); 90 | 91 | 92 | 93 | }); 94 | 95 | function db_sync(client, bar, row) { 96 | var query = "SELECT id, ST_AsText(geo), zip, employment FROM zip_code WHERE ST_Intersects(geo, ST_GeomFromText('"+row.st_astext+"'));"; 97 | //console.log(query); 98 | client.query(query, function(psd_err, psd_result) { 99 | if (psd_err) { 100 | console.error('error running query', psd_err); 101 | } 102 | var dggrid_geo = row.st_astext; 103 | var dggrid_gid = row.gid; 104 | var dggrid_obj = wellknown.parse(dggrid_geo); 105 | var dggrid_employment = 0.0; 106 | for (var j = 0; j < psd_result.rows.length; j++) { 107 | 108 | var zip_obj = wellknown.parse(psd_result.rows[j].st_astext); 109 | var overlap_polygon = turf.intersect(zip_obj, dggrid_obj); 110 | //console.log(zip_obj.coordinates[0][0]) 111 | //console.log(dggrid_obj.coordinates[0][0]); 112 | if (overlap_polygon != undefined) { 113 | //console.log("Overlap between gid "+dggrid_gid.toString()+" and zip code "+ psd_result.rows[j].zip); 114 | var zip_area = turf.area(zip_obj); 115 | //console.log("Zip area is "+zip_area.toString()); 116 | var overlap_area = turf.area(overlap_polygon); 117 | //console.log("Overlap area is "+overlap_area.toString()); 118 | var overlap_percent = overlap_area / zip_area; 119 | //console.log("Overlap percent between gid "+dggrid_gid.toString()+" and zip code "+ psd_result.rows[j].zip+ " is "+overlap_percent.toString()); 120 | dggrid_employment += overlap_percent * psd_result.rows[j].employment; 121 | } 122 | } 123 | //console.log("Setting gid "+dggrid_gid.toString()+" employment to "+dggrid_employment.toString()); 124 | 125 | bar.tick(); 126 | set_employment(client, bar, dggrid_gid, dggrid_employment); 127 | }); 128 | } 129 | 130 | function set_employment(client, bar, gid, emp) { 131 | 132 | client.query("UPDATE dggrid \ 133 | SET employment = "+Math.round(emp).toString()+" WHERE gid = "+gid+";", function(psd_err, psd_result) { 134 | 135 | if (psd_err) { 136 | console.error('error running query', psd_err); 137 | } 138 | }); 139 | } 140 | 141 | 142 | pool.on('error', function (err, client) { 143 | console.error('idle client error', err.message, err.stack) 144 | }) 145 | -------------------------------------------------------------------------------- /src/js/lib/leaflet.pip.min.js: -------------------------------------------------------------------------------- 1 | !function(n){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.leafletPip=n()}}(function(){return function n(t,o,e){function r(i,s){if(!o[i]){if(!t[i]){var u="function"==typeof require&&require;if(!s&&u)return u(i,!0);if(a)return a(i,!0);var c=new Error("Cannot find module '"+i+"'");throw c.code="MODULE_NOT_FOUND",c}var f=o[i]={exports:{}};t[i][0].call(f.exports,function(n){var o=t[i][1][n];return r(o?o:n)},f,f.exports,n,t,o,e)}return o[i].exports}for(var a="function"==typeof require&&require,i=0;it!=e[a][0]>t&&n<(e[a][1]-e[r][1])*(t-e[r][0])/(e[a][0]-e[r][0])+e[r][1]&&(i=!i);return i}var e=this.gju={};"undefined"!=typeof t&&t.exports&&(t.exports=e),e.lineStringsIntersect=function(n,t){for(var o=[],e=0;e<=n.coordinates.length-2;++e)for(var r=0;r<=t.coordinates.length-2;++r){var a={x:n.coordinates[e][1],y:n.coordinates[e][0]},i={x:n.coordinates[e+1][1],y:n.coordinates[e+1][0]},s={x:t.coordinates[r][1],y:t.coordinates[r][0]},u={x:t.coordinates[r+1][1],y:t.coordinates[r+1][0]},c=(u.x-s.x)*(a.y-s.y)-(u.y-s.y)*(a.x-s.x),f=(i.x-a.x)*(a.y-s.y)-(i.y-a.y)*(a.x-s.x),h=(u.y-s.y)*(i.x-a.x)-(u.x-s.x)*(i.y-a.y);if(0!=h){var d=c/h,l=f/h;d>=0&&1>=d&&l>=0&&1>=l&&o.push({type:"Point",coordinates:[a.x+d*(i.x-a.x),a.y+d*(i.y-a.y)]})}}return 0==o.length&&(o=!1),o},e.pointInBoundingBox=function(n,t){return!(n.coordinates[1]t[1][0]||n.coordinates[0]t[1][1])},e.pointInPolygon=function(t,r){for(var a="Polygon"==r.type?[r.coordinates]:r.coordinates,i=!1,s=0;su;u++){var c=2*Math.PI*u/o,f=Math.asin(Math.sin(i[0])*Math.cos(a)+Math.cos(i[0])*Math.sin(a)*Math.cos(c)),h=i[1]+Math.atan2(Math.sin(c)*Math.sin(a)*Math.cos(i[0]),Math.cos(a)-Math.sin(i[0])*Math.sin(f));s[u]=[],s[u][1]=e.numberToDegree(f),s[u][0]=e.numberToDegree(h)}return{type:"Polygon",coordinates:[s]}},e.rectangleCentroid=function(n){var t=n.coordinates[0],o=t[0][0],e=t[0][1],r=t[2][0],a=t[2][1],i=r-o,s=a-e;return{type:"Point",coordinates:[o+i/2,e+s/2]}},e.pointDistance=function(n,t){var o=n.coordinates[0],r=n.coordinates[1],a=t.coordinates[0],i=t.coordinates[1],s=e.numberToRadius(i-r),u=e.numberToRadius(a-o),c=Math.pow(Math.sin(s/2),2)+Math.cos(e.numberToRadius(r))*Math.cos(e.numberToRadius(i))*Math.pow(Math.sin(u/2),2),f=2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c));return 6371*f*1e3},e.geometryWithinRadius=function(n,t,o){if("Point"==n.type)return e.pointDistance(n,t)<=o;if("LineString"==n.type||"Polygon"==n.type){var r,a={};r="Polygon"==n.type?n.coordinates[0]:n.coordinates;for(var i in r)if(a.coordinates=r[i],e.pointDistance(a,t)>o)return!1}return!0},e.area=function(n){for(var t,o,e=0,r=n.coordinates[0],a=r.length-1,i=0;i0;)if(a=I[e-1],i=T[e-1],e--,i-a>1){for(d=n[i].lng()-n[a].lng(),l=n[i].lat()-n[a].lat(),Math.abs(d)>180&&(d=360-Math.abs(d)),d*=Math.cos(b*(n[i].lat()+n[a].lat())),y=d*d+l*l,s=a+1,u=a,f=-1;i>s;s++)g=n[s].lng()-n[a].lng(),M=n[s].lat()-n[a].lat(),Math.abs(g)>180&&(g=360-Math.abs(g)),g*=Math.cos(b*(n[s].lat()+n[a].lat())),p=g*g+M*M,x=n[s].lng()-n[i].lng(),v=n[s].lat()-n[i].lat(),Math.abs(x)>180&&(x=360-Math.abs(x)),x*=Math.cos(b*(n[s].lat()+n[i].lat())),P=x*x+v*v,c=p>=y+P?P:P>=y+p?p:(g*l-M*d)*(g*l-M*d)/y,c>f&&(u=s,f=c);h>f?(m[r]=a,r++):(e++,I[e-1]=u,T[e-1]=i,e++,I[e-1]=a,T[e-1]=u)}else m[r]=a,r++;m[r]=o-1,r++;for(var w=new Array,s=0;r>s;s++)w.push(n[m[s]]);return w.map(function(n){return{type:"Point",coordinates:[n.lng,n.lat]}})},e.destinationPoint=function(n,t,o){o/=6371,t=e.numberToRadius(t);var r=e.numberToRadius(n.coordinates[0]),a=e.numberToRadius(n.coordinates[1]),i=Math.asin(Math.sin(a)*Math.cos(o)+Math.cos(a)*Math.sin(o)*Math.cos(t)),s=r+Math.atan2(Math.sin(t)*Math.sin(o)*Math.cos(a),Math.cos(o)-Math.sin(a)*Math.sin(i));return s=(s+3*Math.PI)%(2*Math.PI)-Math.PI,{type:"Point",coordinates:[e.numberToDegree(s),e.numberToDegree(i)]}}}()},{}]},{},[1])(1)}); 2 | -------------------------------------------------------------------------------- /tools/import_zip_codes.js: -------------------------------------------------------------------------------- 1 | var jsonfile = require('jsonfile'); 2 | var turf = require('turf'); 3 | var ProgressBar = require('progress'); 4 | var pg = require('pg'); 5 | var argv = require("minimist")(process.argv.slice(2)); 6 | var parse = require('csv-parse'); 7 | var fs = require('fs'); 8 | var wellknown = require('wellknown'); 9 | 10 | var settings = { 11 | "d": "/media/jason/e/projects/subway/data/2010-shapefiles/tl_2010_56_zcta510/tl2010_56_zcta510.geojson", 12 | "e": "../data/2012 county business patterns/zbp12totals.txt" 13 | }; 14 | 15 | for (var key in argv) { 16 | if (key in settings) { 17 | settings[key] = argv[key]; 18 | } 19 | } 20 | 21 | var ZIP_FILE = settings["d"]; 22 | var CBP_FILE = settings["e"]; 23 | 24 | var config = { 25 | user: 'postgres', 26 | database: 'transit', 27 | password: 'nostrand', 28 | host: 'localhost', 29 | port: 5432, 30 | max: 10, 31 | idleTimeoutMillis: 100000000 32 | }; 33 | 34 | var pool = new pg.Pool(config); 35 | var zips_queried = 0; 36 | var num_zips = 0; 37 | 38 | pool.connect(function(err, client, done) { 39 | if (err) { 40 | return console.error('error fetching client from pool', err); 41 | } 42 | 43 | client.query('CREATE TABLE IF NOT EXISTS zip_code ( \ 44 | id BIGSERIAL PRIMARY KEY, \ 45 | zip bigint UNIQUE, \ 46 | geo geometry, \ 47 | employment int \ 48 | );', function(err, result) { 49 | //done(); 50 | 51 | if (err) { 52 | return console.error('error running query', err); 53 | } else { 54 | 55 | jsonfile.readFile(ZIP_FILE, function(zip_err, zip_data) { 56 | if (zip_err) { 57 | return console.error('error loading ZIP file', zip_err); 58 | } 59 | console.log('Loaded ZIP file '+ZIP_FILE); 60 | console.log(zip_data.features.length + " total zip codes"); 61 | num_zips = zip_data.features.length; 62 | var bar = new ProgressBar(' processing [:bar] :percent :etas', { 63 | complete: '=', 64 | incomplete: ' ', 65 | width: 100, 66 | total: zip_data.features.length 67 | }); 68 | 69 | var zip_to_population = {}; 70 | fs.readFile(CBP_FILE, function (cbp_err, cbp_data) { 71 | parse(cbp_data, {delimiter: ','}, function(csv_err, rows) { 72 | for (var i = 1; i < rows.length; i++) { 73 | zip_to_population[parseInt(rows[i][0])] = parseInt(rows[i][4]); 74 | } 75 | 76 | for (var j = 0; j < zip_data.features.length; j++) { 77 | 78 | var feature = zip_data.features[j]; 79 | var zip = feature.properties.ZCTA5CE10; 80 | var employment = zip_to_population[parseInt(zip)]; 81 | 82 | 83 | /*var wkt = 'POLYGON(('; 84 | for (var i = 0; i < feature["geometry"]["coordinates"][0].length; i++) { 85 | var coordinate = feature["geometry"]["coordinates"][0][i]; 86 | wkt += coordinate[0]; 87 | wkt += ' '; 88 | wkt += coordinate[1]; 89 | if (i < feature["geometry"]["coordinates"][0].length - 1) { 90 | wkt += ', '; 91 | } else { 92 | wkt += '))'; 93 | } 94 | }*/ 95 | var wkt = wellknown.stringify(feature.geometry); 96 | 97 | var s_zip = JSON.parse(JSON.stringify(zip)); 98 | var s_employment = JSON.parse(JSON.stringify(Math.round(employment))); 99 | var s_wkt = JSON.parse(JSON.stringify(wkt)); 100 | 101 | //console.log(s_zip+": employment "+s_employment+", wkt "+s_wkt); 102 | 103 | db_sync(client, s_zip, s_employment, s_wkt); 104 | 105 | bar.tick(); 106 | 107 | } 108 | }) 109 | }) 110 | 111 | }); // jsonfile (dggrid) 112 | } 113 | }); 114 | 115 | 116 | 117 | }); 118 | 119 | function db_sync(client, s_zip, s_employment, s_wkt) { 120 | var sel_result = client.query("SELECT id FROM zip_code WHERE zip="+s_zip+";", function(sel_err, sel_result) { 121 | 122 | 123 | if (sel_err) { 124 | console.error('error running query', sel_err); 125 | } 126 | 127 | if (sel_result.rows.length >= 1) { 128 | client.query("UPDATE zip_code \ 129 | SET employment = "+s_employment+" WHERE zip = "+s_zip+";", function(psd_err, psd_result) { 130 | 131 | if (psd_err) { 132 | console.error('error running query', psd_err); 133 | } 134 | }); 135 | } else { 136 | 137 | client.query("INSERT INTO zip_code (zip, geo, employment) \ 138 | VALUES("+s_zip+", ST_GeomFromText('"+s_wkt+"'), "+s_employment+");", function(psd_err, psd_result) { 139 | 140 | if (psd_err) { 141 | console.error('error running query', psd_err); 142 | console.log(s_wkt); 143 | } 144 | }); 145 | 146 | } 147 | zips_queried += 1; 148 | if (zips_queried == num_zips) { 149 | //process.exit(); 150 | } 151 | 152 | }); 153 | } 154 | 155 | pool.on('error', function (err, client) { 156 | console.error('idle client error', err.message, err.stack) 157 | }) 158 | -------------------------------------------------------------------------------- /valhalla/valhalla.json: -------------------------------------------------------------------------------- 1 | { 2 | "additional_data": { 3 | "elevation": "/data/valhalla/elevation/" 4 | }, 5 | "httpd": { 6 | "service": { 7 | "interrupt": "ipc:///tmp/interrupt", 8 | "listen": "tcp://*:8002", 9 | "loopback": "ipc:///tmp/loopback" 10 | } 11 | }, 12 | "loki": { 13 | "actions": [ 14 | "locate", 15 | "route", 16 | "one_to_many", 17 | "many_to_one", 18 | "many_to_many", 19 | "sources_to_targets", 20 | "optimized_route", 21 | "isochrone", 22 | "trace_route", 23 | "trace_attributes" 24 | ], 25 | "logging": { 26 | "color": true, 27 | "file_name": "path_to_some_file.log", 28 | "long_request": 100.0, 29 | "type": "std_out" 30 | }, 31 | "service": { 32 | "proxy": "ipc:///tmp/loki" 33 | }, 34 | "service_defaults": { 35 | "minimum_reachability": 50, 36 | "radius": 0 37 | } 38 | }, 39 | "meili": { 40 | "auto": { 41 | "search_radius": 50, 42 | "turn_penalty_factor": 200 43 | }, 44 | "bicycle": { 45 | "turn_penalty_factor": 140 46 | }, 47 | "customizable": [ 48 | "mode", 49 | "search_radius", 50 | "turn_penalty_factor", 51 | "gps_accuracy", 52 | "sigma_z", 53 | "beta", 54 | "max_route_distance_factor", 55 | "max_route_time_factor" 56 | ], 57 | "default": { 58 | "beta": 3, 59 | "breakage_distance": 2000, 60 | "geometry": false, 61 | "gps_accuracy": 5.0, 62 | "interpolation_distance": 10, 63 | "max_route_distance_factor": 5, 64 | "max_route_time_factor": 5, 65 | "max_search_radius": 100, 66 | "route": true, 67 | "search_radius": 50, 68 | "sigma_z": 4.07, 69 | "turn_penalty_factor": 0 70 | }, 71 | "grid": { 72 | "cache_size": 100240, 73 | "size": 500 74 | }, 75 | "logging": { 76 | "color": true, 77 | "file_name": "path_to_some_file.log", 78 | "type": "std_out" 79 | }, 80 | "mode": "auto", 81 | "multimodal": { 82 | "turn_penalty_factor": 70 83 | }, 84 | "pedestrian": { 85 | "search_radius": 50, 86 | "turn_penalty_factor": 100 87 | }, 88 | "service": { 89 | "proxy": "ipc:///tmp/meili" 90 | }, 91 | "verbose": false 92 | }, 93 | "mjolnir": { 94 | "admin": "/media/jason/e/projects/subway/valhalla/valhalla_tiles/admins.sqlite", 95 | "logging": { 96 | "color": true, 97 | "file_name": "path_to_some_file.log", 98 | "type": "std_out" 99 | }, 100 | "max_cache_size": 1000000000, 101 | "tile_dir": "/media/jason/e/projects/subway/valhalla/valhalla_tiles", 102 | "tile_extract": "/media/jason/e/projects/subway/valhalla/valhalla_tiles.tar", 103 | "timezone": "/media/jason/e/projects/subway/valhalla/valhalla_tiles/timezones.sqlite", 104 | "transit_dir": "/data/valhalla/transit" 105 | }, 106 | "odin": { 107 | "logging": { 108 | "color": true, 109 | "file_name": "path_to_some_file.log", 110 | "type": "std_out" 111 | }, 112 | "service": { 113 | "proxy": "ipc:///tmp/odin" 114 | } 115 | }, 116 | "service_limits": { 117 | "auto": { 118 | "max_distance": 5000000.0, 119 | "max_locations": 20, 120 | "max_matrix_distance": 400000.0, 121 | "max_matrix_locations": 50 122 | }, 123 | "auto_shorter": { 124 | "max_distance": 5000000.0, 125 | "max_locations": 20, 126 | "max_matrix_distance": 400000.0, 127 | "max_matrix_locations": 50 128 | }, 129 | "bicycle": { 130 | "max_distance": 500000.0, 131 | "max_locations": 50, 132 | "max_matrix_distance": 200000.0, 133 | "max_matrix_locations": 50 134 | }, 135 | "bus": { 136 | "max_distance": 5000000.0, 137 | "max_locations": 50, 138 | "max_matrix_distance": 400000.0, 139 | "max_matrix_locations": 50 140 | }, 141 | "hov": { 142 | "max_distance": 5000000.0, 143 | "max_locations": 20, 144 | "max_matrix_distance": 400000.0, 145 | "max_matrix_locations": 50 146 | }, 147 | "isochrone": { 148 | "max_contours": 4, 149 | "max_distance": 25000.0, 150 | "max_locations": 1, 151 | "max_time": 120 152 | }, 153 | "max_avoid_locations": 50, 154 | "max_radius": 200, 155 | "max_reachability": 100, 156 | "multimodal": { 157 | "max_distance": 500000.0, 158 | "max_locations": 50, 159 | "max_matrix_distance": 0.0, 160 | "max_matrix_locations": 0 161 | }, 162 | "pedestrian": { 163 | "max_distance": 250000.0, 164 | "max_locations": 50, 165 | "max_matrix_distance": 200000.0, 166 | "max_matrix_locations": 50, 167 | "max_transit_walking_distance": 10000, 168 | "min_transit_walking_distance": 1 169 | }, 170 | "skadi": { 171 | "max_shape": 750000, 172 | "min_resample": 10.0 173 | }, 174 | "trace": { 175 | "max_best_paths": 4, 176 | "max_best_paths_shape": 100, 177 | "max_distance": 200000.0, 178 | "max_gps_accuracy": 100.0, 179 | "max_search_radius": 100.0, 180 | "max_shape": 16000 181 | }, 182 | "transit": { 183 | "max_distance": 500000.0, 184 | "max_locations": 50, 185 | "max_matrix_distance": 200000.0, 186 | "max_matrix_locations": 50 187 | }, 188 | "truck": { 189 | "max_distance": 5000000.0, 190 | "max_locations": 20, 191 | "max_matrix_distance": 400000.0, 192 | "max_matrix_locations": 50 193 | } 194 | }, 195 | "skadi": { 196 | "actions": [ 197 | "height" 198 | ], 199 | "logging": { 200 | "color": true, 201 | "file_name": "path_to_some_file.log", 202 | "long_request": 5.0, 203 | "type": "std_out" 204 | }, 205 | "service": { 206 | "proxy": "ipc:///tmp/skadi" 207 | } 208 | }, 209 | "thor": { 210 | "logging": { 211 | "color": true, 212 | "file_name": "path_to_some_file.log", 213 | "long_request": 110.0, 214 | "type": "std_out" 215 | }, 216 | "service": { 217 | "proxy": "ipc:///tmp/thor" 218 | }, 219 | "source_to_target_algorithm": "select_optimal" 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/js/lib/filesaver.js: -------------------------------------------------------------------------------- 1 | /* FileSaver.js 2 | * A saveAs() FileSaver implementation. 3 | * 1.3.5 4 | * 2018-01-22 15:49:54 5 | * 6 | * By Eli Grey, https://eligrey.com 7 | * License: MIT 8 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md 9 | */ 10 | 11 | /*global self */ 12 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 13 | 14 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */ 15 | 16 | export default var saveAs = saveAs || (function(view) { 17 | "use strict"; 18 | // IE <10 is explicitly unsupported 19 | if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { 20 | return; 21 | } 22 | var 23 | doc = view.document 24 | // only get URL when necessary in case Blob.js hasn't overridden it yet 25 | , get_URL = function() { 26 | return view.URL || view.webkitURL || view; 27 | } 28 | , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") 29 | , can_use_save_link = "download" in save_link 30 | , click = function(node) { 31 | var event = new MouseEvent("click"); 32 | node.dispatchEvent(event); 33 | } 34 | , is_safari = /constructor/i.test(view.HTMLElement) || view.safari 35 | , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) 36 | , throw_outside = function(ex) { 37 | (view.setImmediate || view.setTimeout)(function() { 38 | throw ex; 39 | }, 0); 40 | } 41 | , force_saveable_type = "application/octet-stream" 42 | // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to 43 | , arbitrary_revoke_timeout = 1000 * 40 // in ms 44 | , revoke = function(file) { 45 | var revoker = function() { 46 | if (typeof file === "string") { // file is an object URL 47 | get_URL().revokeObjectURL(file); 48 | } else { // file is a File 49 | file.remove(); 50 | } 51 | }; 52 | setTimeout(revoker, arbitrary_revoke_timeout); 53 | } 54 | , dispatch = function(filesaver, event_types, event) { 55 | event_types = [].concat(event_types); 56 | var i = event_types.length; 57 | while (i--) { 58 | var listener = filesaver["on" + event_types[i]]; 59 | if (typeof listener === "function") { 60 | try { 61 | listener.call(filesaver, event || filesaver); 62 | } catch (ex) { 63 | throw_outside(ex); 64 | } 65 | } 66 | } 67 | } 68 | , auto_bom = function(blob) { 69 | // prepend BOM for UTF-8 XML and text/* types (including HTML) 70 | // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF 71 | if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 72 | return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); 73 | } 74 | return blob; 75 | } 76 | , FileSaver = function(blob, name, no_auto_bom) { 77 | if (!no_auto_bom) { 78 | blob = auto_bom(blob); 79 | } 80 | // First try a.download, then web filesystem, then object URLs 81 | var 82 | filesaver = this 83 | , type = blob.type 84 | , force = type === force_saveable_type 85 | , object_url 86 | , dispatch_all = function() { 87 | dispatch(filesaver, "writestart progress write writeend".split(" ")); 88 | } 89 | // on any filesys errors revert to saving with object URLs 90 | , fs_error = function() { 91 | if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { 92 | // Safari doesn't allow downloading of blob urls 93 | var reader = new FileReader(); 94 | reader.onloadend = function() { 95 | var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); 96 | var popup = view.open(url, '_blank'); 97 | if(!popup) view.location.href = url; 98 | url=undefined; // release reference before dispatching 99 | filesaver.readyState = filesaver.DONE; 100 | dispatch_all(); 101 | }; 102 | reader.readAsDataURL(blob); 103 | filesaver.readyState = filesaver.INIT; 104 | return; 105 | } 106 | // don't create more object URLs than needed 107 | if (!object_url) { 108 | object_url = get_URL().createObjectURL(blob); 109 | } 110 | if (force) { 111 | view.location.href = object_url; 112 | } else { 113 | var opened = view.open(object_url, "_blank"); 114 | if (!opened) { 115 | // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html 116 | view.location.href = object_url; 117 | } 118 | } 119 | filesaver.readyState = filesaver.DONE; 120 | dispatch_all(); 121 | revoke(object_url); 122 | } 123 | ; 124 | filesaver.readyState = filesaver.INIT; 125 | 126 | if (can_use_save_link) { 127 | object_url = get_URL().createObjectURL(blob); 128 | setTimeout(function() { 129 | save_link.href = object_url; 130 | save_link.download = name; 131 | click(save_link); 132 | dispatch_all(); 133 | revoke(object_url); 134 | filesaver.readyState = filesaver.DONE; 135 | }); 136 | return; 137 | } 138 | 139 | fs_error(); 140 | } 141 | , FS_proto = FileSaver.prototype 142 | , saveAs = function(blob, name, no_auto_bom) { 143 | return new FileSaver(blob, name || blob.name || "download", no_auto_bom); 144 | } 145 | ; 146 | // IE 10+ (native saveAs) 147 | if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { 148 | return function(blob, name, no_auto_bom) { 149 | name = name || blob.name || "download"; 150 | 151 | if (!no_auto_bom) { 152 | blob = auto_bom(blob); 153 | } 154 | return navigator.msSaveOrOpenBlob(blob, name); 155 | }; 156 | } 157 | 158 | FS_proto.abort = function(){}; 159 | FS_proto.readyState = FS_proto.INIT = 0; 160 | FS_proto.WRITING = 1; 161 | FS_proto.DONE = 2; 162 | 163 | FS_proto.error = 164 | FS_proto.onwritestart = 165 | FS_proto.onprogress = 166 | FS_proto.onwrite = 167 | FS_proto.onabort = 168 | FS_proto.onerror = 169 | FS_proto.onwriteend = 170 | null; 171 | 172 | return saveAs; 173 | }( 174 | typeof self !== "undefined" && self 175 | || typeof window !== "undefined" && window 176 | || this 177 | )); -------------------------------------------------------------------------------- /src/js/lib/stackblur.min.js: -------------------------------------------------------------------------------- 1 | !function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.StackBlur=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>T,0!=F?(F=255/F,H[o]=(q*S>>T)*F,H[o+1]=(r*S>>T)*F,H[o+2]=(s*S>>T)*F):H[o]=H[o+1]=H[o+2]=0,q-=u,r-=v,s-=w,t-=x,u-=Q.r,v-=Q.g,w-=Q.b,x-=Q.a,m=p+((m=g+f+1)>T,F>0?(F=255/F,H[m]=(q*S>>T)*F,H[m+1]=(r*S>>T)*F,H[m+2]=(s*S>>T)*F):H[m]=H[m+1]=H[m+2]=0,q-=u,r-=v,s-=w,t-=x,u-=Q.r,v-=Q.g,w-=Q.b,x-=Q.a,m=g+((m=h+L)>P,D[o+1]=r*O>>P,D[o+2]=s*O>>P,q-=t,r-=u,s-=v,t-=M.r,u-=M.g,v-=M.b,m=p+((m=g+f+1)>P,D[m+1]=r*O>>P,D[m+2]=s*O>>P,q-=t,r-=u,s-=v,t-=M.r,u-=M.g,v-=M.b,m=g+((m=h+H)'+this.station.name; 78 | content += ' '; 79 | content += ''; 80 | /* 81 | content += '
'+this.station.neighborhood+'
'; 82 | content += ' '; 83 | if (this.station.ridership == -1) { 84 | content += '...'; 85 | } else { 86 | content += Math.round(this.station.ridership).toString(); 87 | } 88 | content += '
'; 89 | */ 90 | content += '
'; 91 | 92 | var lines = enmodal.transit_interface.active_service.station_lines(this.station); 93 | var active_line_is_different = true; 94 | for (var i = 0; i < lines.length; i++) { 95 | var line = lines[i]; 96 | content += '
'+line.name+'
'; 97 | if (line.sid == enmodal.transit_interface.active_line.sid) { 98 | active_line_is_different = false; 99 | } 100 | } 101 | if (enmodal.transit_interface.active_line === null) active_line_is_different = false; 102 | content += '
'; 103 | 104 | if (active_line_is_different) { 105 | content += '
Build
'+enmodal.transit_interface.active_line.name+'
'; 106 | } 107 | 108 | content += '
Delete
'; 109 | content += '
Transfer
'; 110 | content += '
'; 111 | content += '
'; 112 | 113 | this.popup.setContent(content); 114 | this.popup.update(); 115 | this.marker.bindPopup(this.popup); 116 | } 117 | 118 | update_tooltip() { 119 | //this.marker.unbindTooltip(); 120 | //this.marker.bindTooltip(this.station.name, this.tooltip_options); 121 | this.marker.setTooltipContent(this.station.name); 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /lib/transit/TransitModel.py: -------------------------------------------------------------------------------- 1 | import Transit 2 | import TransitGIS 3 | import json 4 | import time 5 | 6 | from geopy.distance import great_circle 7 | 8 | CATCHMENT_DISTANCE = 0.5 9 | 10 | class Model(object): 11 | 12 | def __init__(self, ridership, region): 13 | self.ridership = ridership 14 | self.region = region 15 | 16 | def ridership_json(self): 17 | return json.dumps(self.ridership) 18 | 19 | def to_json(self): 20 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True) 21 | 22 | def map_analysis(m): 23 | 24 | model = m.model 25 | 26 | bb = TransitGIS.BoundingBox() 27 | bb.set_from_map(m) 28 | bb.min_lat -= 0.01; 29 | bb.max_lat += 0.01; 30 | bb.min_lng -= 0.01; 31 | bb.max_lng += 0.01; 32 | 33 | #start = time.time() 34 | #region = TransitGIS.hexagons_bb(bb) 35 | condensed_region = TransitGIS.HexagonRegion() 36 | #end = time.time() 37 | #print "Getting hexagons took "+str(end-start) 38 | 39 | start = time.time() 40 | # For now just use the first service 41 | service = m.services[0] 42 | hexagon_to_station = {} 43 | station_to_hexagon = {} 44 | 45 | for station in service.stations: 46 | if not station.hexagons_known: 47 | station_hexagons = [] 48 | station_bb = TransitGIS.BoundingBox() 49 | station_bb.set_from_station(station) 50 | station_region = TransitGIS.hexagons_bb(station_bb) 51 | #print "Region has "+str(len(station_region.hexagons))+" hexagons" 52 | for hexagon in station_region.hexagons: 53 | if not condensed_region.has_hexagon(hexagon): 54 | condensed_region.add_hexagon(hexagon) 55 | if hexagon not in hexagon_to_station: 56 | hexagon_to_station[hexagon] = [] 57 | center = hexagon.center() 58 | # Look for stations within catchment. 59 | distance = great_circle(center, (station.location[1], station.location[0])).miles 60 | if (distance <= CATCHMENT_DISTANCE): 61 | if hexagon in hexagon_to_station: 62 | hexagon_to_station[hexagon].append(station) 63 | else: 64 | hexagon_to_station[hexagon] = [station] 65 | if station in station_to_hexagon: 66 | station_to_hexagon[station].append(hexagon) 67 | else: 68 | station_to_hexagon[station] = [hexagon] 69 | station_hexagons.append(hexagon) 70 | station.set_hexagons(station_hexagons) 71 | #print "Number used is "+str(len(station_hexagons)) 72 | else: 73 | for hexagon in station.hexagons: 74 | if not condensed_region.has_hexagon(hexagon): 75 | condensed_region.add_hexagon(hexagon) 76 | if hexagon not in hexagon_to_station: 77 | hexagon_to_station[hexagon] = [] 78 | if hexagon in hexagon_to_station: 79 | hexagon_to_station[hexagon].append(station) 80 | else: 81 | hexagon_to_station[hexagon] = [station] 82 | station_to_hexagon[station] = station.hexagons 83 | 84 | end = time.time() 85 | print("Analyzing hexagons took "+str(end-start)) 86 | ridership = {} 87 | print("Using "+str(condensed_region.num_hexagons())+" hexagons") 88 | 89 | start = time.time() 90 | for hexagon in condensed_region.hexagons: 91 | hexagon_stations = hexagon_to_station[hexagon] 92 | num_stations = len(hexagon_stations) 93 | for station in hexagon_stations: 94 | demand = hexagon.population/num_stations 95 | if station.sid not in ridership: 96 | ridership[station.sid] = demand 97 | else: 98 | ridership[station.sid] += demand 99 | 100 | # for hexagon_a in condensed_region.hexagons: 101 | # # Get information about Hexagon A 102 | # hexagon_a_center = hexagon_a.center() 103 | # hexagon_a_stations = hexagon_to_station[hexagon_a] 104 | # 105 | # # Compare to other hexagons 106 | # for hexagon_b in condensed_region.hexagons: 107 | # if (hexagon_a != hexagon_b): 108 | # hexagon_b_center = hexagon_b.center() 109 | # hexagon_b_stations = hexagon_to_station[hexagon_b] 110 | # 111 | # # Compute demand 112 | # distance = great_circle(hexagon_a_center, hexagon_b_center).miles 113 | # demand = hexagon_a.population * max(0, 10/(hexagon_b.population - 10*distance)) 114 | # 115 | # # Compute system transit cost 116 | # best_cost = distance 117 | # will_use_transit = False 118 | # stations = [] 119 | # for station_a in hexagon_a_stations: 120 | # for station_b in hexagon_b_stations: 121 | # transit_cost = system_transit_cost(service, station_a, station_b) 122 | # walk_a_cost = great_circle(hexagon_a_center, (station_a.location[0], station_a.location[1])).miles 123 | # walk_b_cost = great_circle(hexagon_b_center, (station_b.location[0], station_b.location[1])).miles 124 | # cost = transit_cost + walk_a_cost + walk_b_cost 125 | # #print "Transit = "+str(transit_cost)+", WalkA = "+str(walk_a_cost)+", WalkB = "+str(walk_b_cost) 126 | # if (cost < best_cost): 127 | # best_cost = cost 128 | # will_use_transit = True 129 | # stations = [station_a, station_b] 130 | # 131 | # #print "Best cost is "+str(best_cost) 132 | # if (will_use_transit): 133 | # for station in stations: 134 | # if station.sid not in ridership: 135 | # ridership[station.sid] = demand 136 | # else: 137 | # ridership[station.sid] += demand 138 | 139 | end = time.time() 140 | print("Calculating ridership took "+str(end-start)) 141 | return Model(ridership, condensed_region) 142 | 143 | def dfs(service, visited, station): 144 | visited[station] = True 145 | neighbors = service.station_neighbors(station) 146 | for neighbor in neighbors: 147 | if not visited[neighbor]: 148 | dfs(service, visited, neighbor) 149 | 150 | def dijkstra(service, station): 151 | visited = {} 152 | distance = {} 153 | 154 | for s in service.stations: 155 | distance[s] = 0 156 | visited[s] = False 157 | 158 | visited[station] = True 159 | 160 | for s in service.stations: 161 | neighbors = service.station_neighbors(s) 162 | for n in neighbors: 163 | alt = distance[n] + neighbors[n] 164 | if (alt < distance[n]) or not visited[n]: 165 | distance[n] = alt 166 | visited[n] = True 167 | 168 | return distance 169 | 170 | def system_transit_cost(service, station_1, station_2): 171 | distances = dijkstra(service, station_1) 172 | return distances[station_2] 173 | -------------------------------------------------------------------------------- /src/js/enmodal/session.js: -------------------------------------------------------------------------------- 1 | function session_new() { 2 | // Initialize server 3 | $.ajax({ url: "session", 4 | async: false, 5 | dataType: 'json', 6 | success: function(data, status) { 7 | enmodal.public_key = data.public_key; 8 | enmodal.session_id = data.private_key; 9 | window.history.pushState("", "", "?id="+enmodal.session_id); 10 | enmodal.sharing.update(data.public_key, data.private_key); 11 | } 12 | }); 13 | 14 | // Initialize service 15 | var service = new Service("MTA"); 16 | service.mode = "heavy_rail"; 17 | enmodal.transit_map.add_service(service); 18 | enmodal.transit_interface.active_service = service; 19 | enmodal.sidebar.update_service_selector(enmodal.transit_interface.active_service.sid); 20 | enmodal.sidebar.refresh_service_editor(); 21 | var params = $.param({ 22 | i: enmodal.session_id, 23 | service_id: service.sid, 24 | name: service.name 25 | }); 26 | $.ajax({ url: "service_add?"+params, 27 | async: false, 28 | dataType: 'json', 29 | success: function(data, status) { 30 | } 31 | }); 32 | } 33 | 34 | function handle_map_data(jdata) { 35 | console.log(jdata); 36 | 37 | enmodal.transit_interface.station_pairs = []; 38 | 39 | enmodal.transit_map.sid = jdata.sid; 40 | enmodal.transit_map.from_json(jdata); 41 | 42 | if (enmodal.transit_map.services.length === 0) { 43 | session_new(); 44 | } else { 45 | 46 | enmodal.transit_interface.active_service = enmodal.transit_map.primary_service(); 47 | enmodal.transit_interface.active_line = enmodal.transit_map.primary_service().lines[0]; 48 | enmodal.sidebar.update_service_selector(enmodal.transit_interface.active_service.sid, true); 49 | enmodal.sidebar.refresh_service_editor(); 50 | enmodal.sidebar.update_line_selector(enmodal.transit_interface.active_line.sid); 51 | enmodal.sidebar.update_line_editor(); 52 | enmodal.sidebar.refresh_line_editor(); 53 | enmodal.sidebar.update_line_diagram(); 54 | 55 | // Updating service selector is enough to draw the service. 56 | //enmodal.transit_interface.draw_service(enmodal.transit_interface.active_service, enmodal.transit_interface.layers.active, true, true); 57 | 58 | // use user settings where appropriate 59 | var user_settings = jdata.settings; 60 | for (var i = 0; i < user_settings.station_pairs.length; i++) { 61 | var sp = user_settings.station_pairs[i]; 62 | var station_1 = enmodal.transit_map.get_station_by_id(sp.station_ids[0]); 63 | var station_2 = enmodal.transit_map.get_station_by_id(sp.station_ids[1]); 64 | var sp_real = enmodal.transit_interface.get_station_pair(station_1, station_2); 65 | if (sp_real !== null) { 66 | for (var j = 0; j < sp.pins.length; j++) { 67 | sp_real[0].add_pin(sp.pins[j].location[0], sp.pins[j].location[1]); 68 | } 69 | } 70 | } 71 | 72 | enmodal.transit_interface.draw_transfers(); 73 | //enmodal.transit_interface.map.closePopup(); 74 | var bounds = enmodal.transit_map.geographic_bounds(); 75 | if (bounds !== null) enmodal.leaflet_map.fitBounds(bounds); 76 | enmodal.data.get_ridership(); 77 | } 78 | } 79 | 80 | function session_load() { 81 | // Initialize session and map 82 | var params = $.param({ 83 | i: enmodal.session_id 84 | }); 85 | $.ajax({ url: "session_load?"+params, 86 | async: false, 87 | success: function(data, status) { 88 | 89 | //console.log(data); 90 | var j = JSON.parse(data); 91 | //console.log(j); 92 | if (j.error !== undefined) { 93 | session_new(); 94 | } else { 95 | var jdata = JSON.parse(j.data); 96 | handle_map_data(jdata); 97 | 98 | window.history.pushState("", "", "?id="+enmodal.session_id); 99 | enmodal.public_key = j.public_key; 100 | enmodal.session_id = j.private_key; 101 | if (j.title !== null) { 102 | $("#map-title-inner").html(j.title + ' '); 103 | enmodal.map_name = j.title; 104 | } 105 | 106 | $("#starter-city-picker").hide(); 107 | $("#starter").hide(); 108 | $("#options").show(); 109 | } 110 | } 111 | }); 112 | 113 | } 114 | 115 | function session_save() { 116 | var params = $.param({ 117 | i: enmodal.session_id 118 | }); 119 | $("#tool-save").html(''); 120 | $.ajax({ url: "session_push?"+params, 121 | async: true, 122 | type: "POST", 123 | data: LZString.compressToBase64(session_json()), 124 | dataType: 'text', 125 | success: function(data, status) { 126 | var params = $.param({ 127 | i: enmodal.session_id 128 | }); 129 | $.ajax({ url: "session_save?"+params, 130 | async: true, 131 | dataType: 'json', 132 | success: function(data, status) { 133 | if (data.result == "OK") { 134 | $("#tool-save").html('Save'); 135 | $("#tool-save").attr('data-balloon', 'Saved!'); 136 | $("#tool-save").attr('data-balloon-visible',''); 137 | setTimeout(function() { 138 | $("#tool-save").removeAttr('data-balloon'); 139 | $("#tool-save").removeAttr('data-balloon-visible'); 140 | }, 3000); 141 | } else if (data.message == "Anonymous user") { 142 | $("#tool-save").html('Save'); 143 | $("#tool-save").attr('data-balloon', 'You must be logged in to save.'); 144 | $("#tool-save").attr('data-balloon-visible',''); 145 | setTimeout(function() { 146 | $("#tool-save").removeAttr('data-balloon'); 147 | $("#tool-save").removeAttr('data-balloon-visible'); 148 | }, 3000); 149 | } else { 150 | $("#tool-save").html('Save'); 151 | $("#tool-save").attr('data-balloon', 'Error saving. Please try again later.'); 152 | $("#tool-save").attr('data-balloon-visible',''); 153 | setTimeout(function() { 154 | $("#tool-save").removeAttr('data-balloon'); 155 | $("#tool-save").removeAttr('data-balloon-visible'); 156 | }, 3000); 157 | } 158 | } 159 | }); 160 | } 161 | }); 162 | } 163 | 164 | function session_json() { 165 | var ret = { 166 | "map": enmodal.transit_map, 167 | "settings": enmodal.transit_interface.settings() 168 | }; 169 | return JSON.stringify(ret); 170 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enmodal 2 | 3 | enmodal is a browser-based service for transit planning and analysis. users can quickly mockup transit services (or “scenario plan” modifications to real-world systems) in their browser. enmodal runs server-side modeling algorithms to analyze the impact of different service modifications, using population and employment data. 4 | 5 | ## Set up 6 | 7 | Skip to: [Windows](#windows), [Mac](#mac), [Ubuntu](#ubuntu) 8 | 9 | ### Windows 10 | 11 | #### Clone this repo 12 | 13 | Download this repository as a ZIP file (see Download options above) and unzip to a directory called `enmodal`. Alternatively, install [Git](https://git-scm.com/) and clone the repository: 14 | 15 | git clone https://github.com/jpwright/enmodal.git 16 | 17 | #### Set up PostgreSQL 18 | 19 | Install [PostgreSQL](https://www.postgresql.org) with [PostGIS](http://postgis.net/windows_downloads/) functionality. I recommend following [this tutorial](www.bostongis.com/PrinterFriendly.aspx?content_name=postgis_tut01). 20 | 21 | Make note of the password you set for the admin (`postgres`) account. 22 | 23 | #### Create config file 24 | 25 | Copy `settings.cfg.example` to a new file called `settings.cfg`, and open that file for editing. Most fields can be left at their default values, except: 26 | 27 | - Set the `sessions` database password based on whatever you chose in the previous step. 28 | - If you want support for reverse geocoding, you'll need to set up an account with either [Mapbox](https://www.mapbox.com/developers/) or [Google](https://developers.google.com/maps/documentation/javascript/get-api-key) and supply an API key. (The Mapzen API is no longer functional.) 29 | 30 | #### Set up Python 31 | 32 | Install [Python 3.8](https://www.python.org/) using the Windows installer, or other distribution of your choice. 33 | 34 | *Make sure to select "Add Python to system PATH" when installing.* 35 | 36 | #### Install virtualenv and set up Python requirements 37 | 38 | Open Command Prompt and navigate to the `enmodal` directory. (If you are unfamiliar with navigating directories in Command Prompt, an easy way to do this is to open the `enmodal` directory in Explorer, then in the field that shows you the folder path, type `cmd` and hit Enter.) 39 | 40 | Run the following commands to set up the Python environment: 41 | 42 | python -m pip install virtualenv 43 | python -m venv venv 44 | venv\Scripts\activate.bat 45 | python -m pip install -r requirements.txt 46 | 47 | Leave Command Prompt open as you'll need it future steps. 48 | 49 | #### Create database 50 | 51 | In your Command Prompt window: 52 | 53 | "C:\Program Files\PostgreSQL\10\bin\createdb" -U postgres sessions 54 | 55 | Use the password you set during PostgreSQL installation when requested. 56 | 57 | #### Run database setup tool 58 | 59 | In the same Command Prompt window, run: 60 | 61 | python tools\set_up_db.py 62 | 63 | #### Start the server 64 | 65 | python server.py 66 | 67 | #### Open your browser 68 | 69 | Navigate to `http://localhost:5050` in your browser and get started! 70 | 71 | ### Mac 72 | 73 | #### Clone this repo 74 | 75 | Download this repository as a ZIP file (see Download options above) and unzip to a directory called `enmodal`. Alternatively, install [Git](https://git-scm.com/) and clone the repository: 76 | 77 | git clone https://github.com/jpwright/enmodal.git 78 | 79 | #### Set up Python 80 | 81 | Open up a Terminal, navigate to the directory in which you unzipped enmodal (recommend [this tutorial](https://learn.co/lessons/bash-navigation-osx) if navigating through directories in Terminal is unfamiliar to you), and run the following commands: 82 | 83 | sudo easy_install pip 84 | sudo pip install virtualenv 85 | 86 | #### Install virtualenv and set up Python requirements 87 | 88 | python -m venv venv 89 | source venv/bin/activate 90 | pip install -r requirements.txt 91 | 92 | #### Install PostgreSQL and PostGIS 93 | 94 | Recommend using [Postgres.app](http://postgresapp.com/) to accomplish this. 95 | 96 | Install Postgres.app and open the application. Click "Initialize" then "Start" to start the Postgres server. Double click on any of the databases shown in the window. 97 | 98 | A terminal window should appear. Run these commands: 99 | 100 | CREATE DATABASE sessions; 101 | 102 | #### Create config file 103 | 104 | Copy `settings.cfg.example` to a new file called `settings.cfg`. Most fields can be left at their default values, except: 105 | 106 | - Set the `sessions` `user` to your macOS username (this is the default value if you used Postgres.app) 107 | - Set the `sessions` `password` to be blank (this is the default value if you used Postgres.app) 108 | - If you want support for reverse geocoding, you'll need to set up an account with either [Mapbox](https://www.mapbox.com/developers/) or [Google](https://developers.google.com/maps/documentation/javascript/get-api-key) and supply an API key. (The Mapzen API is no longer functional.) 109 | 110 | #### Run database setup tool 111 | 112 | In your original Terminal window: 113 | 114 | python tools/set_up_db.py 115 | 116 | #### Start the server 117 | 118 | python server.py 119 | 120 | #### Open your browser 121 | 122 | Navigate to `http://localhost:5050` in your browser and get started! 123 | 124 | ### Ubuntu 125 | 126 | #### Clone this repo 127 | 128 | git clone https://github.com/jpwright/enmodal.git && cd enmodal 129 | 130 | #### Set up essential tools 131 | 132 | sudo apt-get install python3-setuptools python3-dev python3-pip python3-psycopg2 python3-wheel postgresql-12 postgresql-server-dev-12 build-essential wget nodejs node-grunt-cli npm 133 | 134 | #### Install virtualenv and set up Python requirements 135 | 136 | python3 -m venv venv 137 | source venv/bin/activate 138 | pip3 install -r requirements.txt 139 | 140 | #### Set up PostgreSQL user 141 | 142 | sudo -u postgres psql postgres 143 | 144 | Then within the `psql` command: 145 | 146 | \password postgres 147 | 148 | Set a password and use it in your `settings.cfg` file below. 149 | 150 | Create the database: 151 | 152 | CREATE DATABASE sessions; 153 | 154 | Quit psql with Ctrl+D. 155 | 156 | #### Create config file 157 | 158 | Copy `settings.cfg.example` to a new file called `settings.cfg`. Most fields can be left at their default values, except: 159 | 160 | - Set the `sessions` database password based on whatever you chose in the previous step. 161 | - If you want support for reverse geocoding, you'll need to set up an account with either [Mapbox](https://www.mapbox.com/developers/) or [Google](https://developers.google.com/maps/documentation/javascript/get-api-key) and supply an API key. (The Mapzen API is no longer functional.) 162 | 163 | #### Run database setup tool 164 | 165 | python3 tools/set_up_db.py 166 | 167 | #### Install NPM and grunt 168 | 169 | npm install grunt grunt-contrib-jshint grunt-contrib-watch grunt-contrib-copy grunt-contrib-concat grunt-contrib-uglify --save-dev 170 | sudo npm install -g grunt-cli 171 | sudo npm install 172 | grunt --force 173 | 174 | #### Start the server 175 | 176 | python server.py 177 | 178 | #### Open your browser 179 | 180 | Navigate to `http://localhost:5050` in your browser and get started! 181 | 182 | ## Populating dggrid database 183 | 184 | Generating the dggrid database (which contains the hexagonal bins of population and employment data) is cumbersome and not yet documented. A copy of the database will eventually be made available for download. The scripts to generate the database yourself are in the `tools` directory. -------------------------------------------------------------------------------- /src/graphviz.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Network | Basic usage 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/js/lib/leaflet.image.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.leafletImage=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;olayer.options.maxZoom||zoom=0){if(isCanvasLayer){var tile=layer._tiles[tilePoint.x+":"+tilePoint.y];tileQueue.defer(canvasTile,tile,tilePos,tileSize)}else{var url=addCacheString(layer.getTileUrl(tilePoint));tileQueue.defer(loadTile,url,tilePos,tileSize)}}});tileQueue.awaitAll(tileQueueFinish);function canvasTile(tile,tilePos,tileSize,callback){callback(null,{img:tile,pos:tilePos,size:tileSize})}function loadTile(url,tilePos,tileSize,callback){var im=new Image;im.crossOrigin="";im.onload=function(){callback(null,{img:this,pos:tilePos,size:tileSize})};im.onerror=function(e){if(layer.options.errorTileUrl!=""&&e.target.errorCheck===undefined){e.target.errorCheck=true;e.target.src=layer.options.errorTileUrl}else{callback(null,{img:dummycanvas,pos:tilePos,size:tileSize})}};im.src=url}function tileQueueFinish(err,data){data.forEach(drawTile);callback(null,{canvas:canvas})}function drawTile(d){ctx.drawImage(d.img,Math.floor(d.pos.x),Math.floor(d.pos.y),d.size,d.size)}}function handlePathRoot(root,callback){var bounds=map.getPixelBounds(),origin=map.getPixelOrigin(),canvas=document.createElement("canvas");canvas.width=dimensions.x;canvas.height=dimensions.y;var ctx=canvas.getContext("2d");var pos=L.DomUtil.getPosition(root).subtract(bounds.min).add(origin);try{ctx.drawImage(root,pos.x,pos.y,canvas.width-pos.x*2,canvas.height-pos.y*2);callback(null,{canvas:canvas})}catch(e){console.error("Element could not be drawn on canvas",root)}}function handleMarkerLayer(marker,callback){var canvas=document.createElement("canvas"),ctx=canvas.getContext("2d"),pixelBounds=map.getPixelBounds(),minPoint=new L.Point(pixelBounds.min.x,pixelBounds.min.y),pixelPoint=map.project(marker.getLatLng()),isBase64=/^data\:/.test(marker._icon.src),url=isBase64?marker._icon.src:addCacheString(marker._icon.src),im=new Image,options=marker.options.icon.options,size=options.iconSize,pos=pixelPoint.subtract(minPoint),anchor=L.point(options.iconAnchor||size&&size.divideBy(2,true));if(size instanceof L.Point)size=[size.x,size.y];var x=Math.round(pos.x-size[0]+anchor.x),y=Math.round(pos.y-anchor.y);canvas.width=dimensions.x;canvas.height=dimensions.y;im.crossOrigin="";im.onload=function(){ctx.drawImage(this,x,y,size[0],size[1]);callback(null,{canvas:canvas})};im.src=url;if(isBase64)im.onload()}function handleEsriDymamicLayer(dynamicLayer,callback){var canvas=document.createElement("canvas");canvas.width=dimensions.x;canvas.height=dimensions.y;var ctx=canvas.getContext("2d");var im=new Image;im.crossOrigin="";im.src=addCacheString(dynamicLayer._currentImage._image.src);im.onload=function(){ctx.drawImage(im,0,0);callback(null,{canvas:canvas})}}function addCacheString(url){if(isDataURL(url)||url.indexOf("mapbox.com/styles/v1")!==-1){return url}return url+(url.match(/\?/)?"&":"?")+"cache="+cacheBusterDate}function isDataURL(url){var dataURLRegex=/^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i;return!!url.match(dataURLRegex)}}},{"d3-queue":2}],2:[function(require,module,exports){(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):factory(global.d3_queue=global.d3_queue||{})})(this,function(exports){"use strict";var version="2.0.3";var slice=[].slice;var noabort={};function Queue(size){if(!(size>=1))throw new Error;this._size=size;this._call=this._error=null;this._tasks=[];this._data=[];this._waiting=this._active=this._ended=this._start=0}Queue.prototype=queue.prototype={constructor:Queue,defer:function(callback){if(typeof callback!=="function"||this._call)throw new Error;if(this._error!=null)return this;var t=slice.call(arguments,1);t.push(callback);++this._waiting,this._tasks.push(t);poke(this);return this},abort:function(){if(this._error==null)abort(this,new Error("abort"));return this},await:function(callback){if(typeof callback!=="function"||this._call)throw new Error;this._call=function(error,results){callback.apply(null,[error].concat(results))};maybeNotify(this);return this},awaitAll:function(callback){if(typeof callback!=="function"||this._call)throw new Error;this._call=callback;maybeNotify(this);return this}};function poke(q){if(!q._start)try{start(q)}catch(e){if(q._tasks[q._ended+q._active-1])abort(q,e)}}function start(q){while(q._start=q._waiting&&q._active=0){if(t=q._tasks[i]){q._tasks[i]=null;if(t.abort)try{t.abort()}catch(e){}}}q._active=NaN;maybeNotify(q)}function maybeNotify(q){if(!q._active&&q._call)q._call(q._error,q._data)}function queue(concurrency){return new Queue(arguments.length?+concurrency:Infinity)}exports.version=version;exports.queue=queue})},{}]},{},[1])(1)}); 2 | -------------------------------------------------------------------------------- /EnmodalMap.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, Blueprint, request, url_for, send_from_directory 2 | from werkzeug.utils import secure_filename 3 | import sys 4 | import os 5 | import string 6 | 7 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__)))) 8 | from EnmodalSessions import * 9 | 10 | from lzstring import LZString 11 | import json 12 | 13 | import psycopg2 14 | import psycopg2.extras 15 | 16 | import configparser 17 | config = configparser.RawConfigParser() 18 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), 'settings.cfg'))) 19 | 20 | SESSIONS_HOST = config.get('sessions', 'host') 21 | SESSIONS_PORT = config.get('sessions', 'port') 22 | SESSIONS_DBNAME = config.get('sessions', 'dbname') 23 | SESSIONS_USER = config.get('sessions', 'user') 24 | SESSIONS_PASSWORD = config.get('sessions', 'password') 25 | SESSIONS_CONN_STRING = "host='"+SESSIONS_HOST+"' port='"+SESSIONS_PORT+"' dbname='"+SESSIONS_DBNAME+"' user='"+SESSIONS_USER+"' password='"+SESSIONS_PASSWORD+"'" 26 | SESSIONS_SECRET_KEY_PUBLIC = int(config.get('sessions', 'secret_key_public'), 16) 27 | SESSIONS_SECRET_KEY_PRIVATE = int(config.get('sessions', 'secret_key_private'), 16) 28 | SESSION_EXPIRATION_TIME = int(config.get('sessions', 'expiration_time')) 29 | 30 | UPLOAD_FOLDER = config.get('flask', 'upload_folder') 31 | 32 | enmodal_map = Blueprint('enmodal_map', __name__) 33 | 34 | def allowed_file(filename): 35 | return '.' in filename and filename.rsplit('.', 1)[1].lower() in ['json'] 36 | 37 | @enmodal_map.route('/uploads/') 38 | def uploaded_file(filename): 39 | return send_from_directory(UPLOAD_FOLDER, filename) 40 | 41 | def save_session(s, user_id, take_snapshot): 42 | sid = s.sid 43 | print("saving with sid "+str(sid)) 44 | sdata = str(s.map.to_json()) 45 | sdt = datetime.datetime.now() 46 | 47 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 48 | cursor = conn.cursor() 49 | cursor.execute("SELECT id FROM sessions WHERE id = '%s' LIMIT 1", [sid]) 50 | if (cursor.rowcount > 0): 51 | cursor.execute("UPDATE sessions SET data = %s, updated = %s WHERE id = %s", (sdata, sdt, sid)) 52 | else: 53 | cursor.execute("INSERT INTO sessions (id, data, updated) VALUES (%s, %s, %s)", (sid, sdata, sdt)) 54 | if user_id != None: 55 | cursor.execute("UPDATE sessions SET owner_id = %s WHERE id = %s", (user_id, sid)) 56 | 57 | conn.commit() 58 | 59 | @enmodal_map.route('/session_save') 60 | def route_session_save(): 61 | h = int(request.args.get('i'), 16) 62 | e = check_for_session_errors(h) 63 | if e: 64 | return e 65 | 66 | a = session_manager.auth_by_key(h) 67 | 68 | if a.editable: 69 | save_session(a.session, 0, True) 70 | del a 71 | return json.dumps({"result": "OK"}) 72 | else: 73 | del a 74 | return json.dumps({"result": "FAIL", "message": "Non-editable session"}) 75 | 76 | @enmodal_map.route('/session_load') 77 | def route_session_load(): 78 | h = int(request.args.get('i'), 16) 79 | 80 | conn = psycopg2.connect(SESSIONS_CONN_STRING) 81 | cursor = conn.cursor() 82 | 83 | is_private = False 84 | sid = session_manager.get_sid_from_public_key(h) 85 | #print "public guess: "+str(sid) 86 | cursor.execute("SELECT data, title FROM sessions WHERE id = '%s' LIMIT 1", [sid]) 87 | if (cursor.rowcount == 0): 88 | sid = session_manager.get_sid_from_private_key(h) 89 | #print "private guess: "+str(sid) 90 | cursor.execute("SELECT data, title FROM sessions WHERE id = '%s' LIMIT 1", [sid]) 91 | is_private = True 92 | if (cursor.rowcount == 0): 93 | return json.dumps({"error": "Invalid ID"}) 94 | 95 | print(sid) 96 | row = cursor.fetchone() 97 | sdata = row[0] 98 | title = row[1] 99 | m = Transit.Map(0) 100 | m.from_json(sdata) 101 | m.sidf_state = 0 102 | m.regenerate_all_ids() 103 | 104 | if not is_private: 105 | s = EnmodalSession() 106 | session_manager.add(s) 107 | s.map = m 108 | else: 109 | s = session_manager.get_by_sid(sid) 110 | if s is None: 111 | s = EnmodalSession() 112 | s.sid = sid 113 | s.map = m 114 | session_manager.add(s) 115 | else: 116 | s.map = m 117 | 118 | a = session_manager.auth_by_key(s.private_key()) 119 | return_obj = {"public_key": '{:16x}'.format(a.session.public_key()), "is_private": a.editable, "title": title, "data": m.to_json()} 120 | if a.editable: 121 | return_obj["private_key"] = '{:16x}'.format(a.session.private_key()) 122 | del a 123 | return json.dumps(return_obj) 124 | 125 | @enmodal_map.route('/session_push', methods=['GET', 'POST']) 126 | def route_session_push(): 127 | h = int(request.args.get('i'), 16) 128 | e = check_for_session_errors(h) 129 | if e: 130 | return e 131 | 132 | print('received session push') 133 | data = request.get_data(as_text=True) 134 | jd = LZString().decompressFromBase64(data) 135 | print('decompressed session push') 136 | jdl = json.loads(jd) 137 | d = jdl['map'] 138 | #print d 139 | d['sidf_state'] = 0 140 | m = Transit.Map(0) 141 | m.from_json(d) 142 | m.sidf_state = 0 143 | 144 | # Copy old map 145 | om = session_manager.auth_by_key(h).session.map 146 | 147 | # Save new map 148 | session_manager.auth_by_key(h).session.map = m 149 | 150 | # Save gids 151 | for service in om.services: 152 | for station in service.stations: 153 | if station.hexagons_known: 154 | for service_n in m.services: 155 | for station_n in service_n.stations: 156 | if station.location == station_n.location: 157 | station_n.set_hexagons(station.hexagons) 158 | 159 | # Copy user settings 160 | # TODO clean this up! 161 | settings = jdl['settings'] 162 | m.settings.from_json(settings) 163 | 164 | return json.dumps({"result": "OK"}) 165 | 166 | @enmodal_map.route('/session_import_json', methods=['POST']) 167 | def route_session_import_json(): 168 | h = int(request.args.get('i'), 16) 169 | e = check_for_session_errors(h) 170 | if e: 171 | return e 172 | 173 | print(request.files) 174 | 175 | # check if the post request has the file part 176 | if 'json' not in request.files: 177 | return json.dumps({"result": "error", "message": "No file."}) 178 | 179 | file = request.files['json'] 180 | # if user does not select file, browser also 181 | # submit a empty part without filename 182 | if file.filename == '': 183 | return json.dumps({"result": "error", "message": "No file."}) 184 | 185 | if file and allowed_file(file.filename): 186 | filename = secure_filename(request.args.get('i') + ".json") 187 | if not os.path.isdir(UPLOAD_FOLDER): 188 | os.mkdir(UPLOAD_FOLDER) 189 | file.save(os.path.join(UPLOAD_FOLDER, filename)) 190 | print(os.path.join(UPLOAD_FOLDER, filename)) 191 | with open(os.path.join(UPLOAD_FOLDER, filename), "r") as f: 192 | r = f.read() 193 | print(r) 194 | printable = set(string.printable) 195 | r = filter(lambda x: x in printable, r) 196 | jdl = json.loads(r) 197 | 198 | d = jdl['map'] 199 | d['sidf_state'] = 0 200 | m = Transit.Map(0) 201 | m.from_json(d) 202 | m.sidf_state = 0 203 | 204 | # Copy old map 205 | om = session_manager.auth_by_key(h).session.map 206 | 207 | # Save new map 208 | session_manager.auth_by_key(h).session.map = m 209 | 210 | # Save gids 211 | for service in om.services: 212 | for station in service.stations: 213 | if station.hexagons_known: 214 | for service_n in m.services: 215 | for station_n in service_n.stations: 216 | if station.location == station_n.location: 217 | station_n.set_hexagons(station.hexagons) 218 | 219 | # Copy user settings 220 | # TODO clean this up! 221 | settings = jdl['settings'] 222 | m.settings.from_json(settings) 223 | return json.dumps({"result": "OK", "data": m.to_json()}) 224 | else: 225 | return json.dumps({"result": "error", "message": "Bad file."}) -------------------------------------------------------------------------------- /src/js/lib/leaflet.polylineoffset.js: -------------------------------------------------------------------------------- 1 | L.PolylineOffset = { 2 | translatePoint: function(pt, dist, radians) { 3 | return L.point(pt.x + dist * Math.cos(radians), pt.y + dist * Math.sin(radians)); 4 | }, 5 | 6 | offsetPointLine: function(points, distance) { 7 | var l = points.length; 8 | if (l < 2) { 9 | throw new Error('Line should be defined by at least 2 points'); 10 | } 11 | 12 | var a = points[0], b, xs ,ys, sqDist; 13 | var offsetAngle, segmentAngle; 14 | var offsetSegments = []; 15 | var sqDistance = distance * distance; 16 | 17 | for(var i=1; i < l; i++) { 18 | b = points[i]; 19 | xs = b.x - a.x; 20 | ys = b.y - a.y; 21 | sqDist = xs * xs + ys * ys; 22 | // angle in (-PI, PI] 23 | segmentAngle = Math.atan2(a.y - b.y, a.x - b.x); 24 | // angle in (-1.5 * PI, PI/2] 25 | offsetAngle = segmentAngle - Math.PI/2; 26 | 27 | // store offset point and other information to avoid recomputing it later 28 | if (sqDist > sqDistance) { 29 | offsetSegments.push({ 30 | angle: segmentAngle, 31 | offsetAngle: offsetAngle, 32 | distance: distance, 33 | original: [a, b], 34 | offset: [ 35 | this.translatePoint(a, distance, offsetAngle), 36 | this.translatePoint(b, distance, offsetAngle) 37 | ] 38 | }); 39 | a = b; 40 | } 41 | } 42 | 43 | return offsetSegments; 44 | }, 45 | 46 | latLngsToPoints: function(ll, map) { 47 | var pts = []; 48 | for(var i=0, l=ll.length; i startAngle + Math.PI) { 199 | return [this.intersection(s1.offset[0], s1.offset[1], s2.offset[0], s2.offset[1])]; 200 | } 201 | 202 | // Step is distance dependent. Bigger distance results in more steps to take 203 | var step = Math.abs(8/distance); 204 | for (var a = startAngle; a < endAngle; a += step) { 205 | points.push(this.translatePoint(center, distance, a)); 206 | } 207 | points.push(this.translatePoint(center, distance, endAngle)); 208 | 209 | if (distance > 0) { 210 | // reverse all points again when going right 211 | points.reverse(); 212 | } 213 | 214 | return points; 215 | } 216 | } 217 | 218 | // Modify the L.Polyline class by overwriting the projection function, 219 | // to add offset related code 220 | // Versions < 0.8 221 | if(L.version.charAt(0) == '0' && parseInt(L.version.charAt(2)) < 8) { 222 | L.Polyline.include({ 223 | projectLatlngs: function() { 224 | this._originalPoints = []; 225 | 226 | for (var i = 0, len = this._latlngs.length; i < len; i++) { 227 | this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]); 228 | } 229 | // Offset management hack --- 230 | if(this.options.offset) { 231 | this._originalPoints = L.PolylineOffset.offsetPoints(this._originalPoints, this.options.offset); 232 | } 233 | // Offset management hack END --- 234 | } 235 | }); 236 | } else { 237 | // Versions >= 0.8 238 | L.Polyline.include({ 239 | _projectLatlngs: function (latlngs, result, projectedBounds) { 240 | var flat = latlngs[0] instanceof L.LatLng, 241 | len = latlngs.length, 242 | i, ring; 243 | 244 | if (flat) { 245 | ring = []; 246 | for (i = 0; i < len; i++) { 247 | ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 248 | if (projectedBounds !== undefined) { 249 | projectedBounds.extend(ring[i]); 250 | } 251 | } 252 | // Offset management hack --- 253 | if(this.options.offset) { 254 | ring = L.PolylineOffset.offsetPoints(ring, this.options.offset); 255 | } 256 | // Offset management hack END --- 257 | result.push(ring); 258 | } else { 259 | for (i = 0; i < len; i++) { 260 | if (projectedBounds !== undefined) { 261 | this._projectLatlngs(latlngs[i], result, projectedBounds); 262 | } else { 263 | this._projectLatlngs(latlngs[i], result); 264 | } 265 | } 266 | } 267 | } 268 | }); 269 | } 270 | 271 | L.Polyline.include({ 272 | setOffset: function(offset) { 273 | this.options.offset = offset; 274 | this.redraw(); 275 | return this; 276 | } 277 | }); 278 | -------------------------------------------------------------------------------- /tools/nationwide_latent_demand_2.js: -------------------------------------------------------------------------------- 1 | var jsonfile = require('jsonfile'); 2 | var turf = require('turf'); 3 | var ProgressBar = require('progress'); 4 | var pg = require('pg'); 5 | var argv = require("minimist")(process.argv.slice(2)); 6 | var parse = require('csv-parse/lib/sync'); 7 | var fs = require('fs'); 8 | 9 | var settings = { 10 | "d": "../../dggrid/output/ny_1.geojson", 11 | "c": "../data/2010-census/tabblock2010_simplified.geojson", 12 | "w": "../data/geo/usa_water_bodies.geojson" 13 | }; 14 | 15 | for (var key in argv) { 16 | if (key in settings) { 17 | settings[key] = argv[key]; 18 | } 19 | } 20 | 21 | var DGGRID_FILE = settings["d"]; 22 | var CENSUS_FILE = settings["c"]; 23 | var WATER_FILE = settings["w"]; 24 | 25 | var config = { 26 | user: 'postgres', 27 | database: 'transit', 28 | password: 'nostrand', 29 | host: 'localhost', 30 | port: 5432, 31 | max: 10, 32 | idleTimeoutMillis: 100000000 33 | }; 34 | 35 | var pool = new pg.Pool(config); 36 | 37 | var dggrids_with_overlap = 0; 38 | var dggrids_queried = 0; 39 | 40 | pool.connect(function(err, client, done) { 41 | if (err) { 42 | return console.error('error fetching client from pool', err); 43 | } 44 | 45 | client.query('CREATE TABLE IF NOT EXISTS dggrid ( \ 46 | id BIGSERIAL PRIMARY KEY, \ 47 | gid bigint UNIQUE, \ 48 | geo geometry, \ 49 | population int, \ 50 | employment int 51 | );', function(err, result) { 52 | //done(); 53 | 54 | if (err) { 55 | return console.error('error running query', err); 56 | } else { 57 | 58 | jsonfile.readFile(DGGRID_FILE, function(dggrid_err, dggrid_data) { 59 | console.log('Loaded DGGrid file '+DGGRID_FILE); 60 | console.log(dggrid_data.features.length + " total dggrids"); 61 | var bar = new ProgressBar(' processing [:bar] :percent :etas', { 62 | complete: '=', 63 | incomplete: ' ', 64 | width: 100, 65 | total: dggrid_data.features.length 66 | }); 67 | 68 | jsonfile.readFile(CENSUS_FILE, function(census_err, census_data) { 69 | 70 | if (census_err) { 71 | return console.error('error: '+census_err); 72 | } 73 | 74 | console.log('Loaded census file '+CENSUS_FILE); 75 | console.log(census_data.features.length + ' total features'); 76 | 77 | var populations = []; 78 | var centroids = []; 79 | var block_areas = []; 80 | var geoids = []; 81 | 82 | for (var k = 0; k < census_data.features.length; k++) { 83 | var block = census_data.features[k]; 84 | 85 | var centroid = turf.centroid(block); 86 | centroids[k] = centroid; 87 | 88 | var area = turf.area(block); 89 | block_areas[k] = area; 90 | 91 | } 92 | 93 | for (var j = 0; j < dggrid_data.features.length; j++) { 94 | 95 | var feature = dggrid_data.features[j]; 96 | var gid = feature.properties.global_id; 97 | 98 | var dggrid_polygon = { 99 | "type": "Feature", 100 | "properties": {}, 101 | "geometry": { 102 | "type": "Polygon", 103 | "coordinates": feature.geometry.coordinates 104 | } 105 | }; 106 | 107 | var dggrid_centroid = turf.centroid(dggrid_polygon); 108 | //var dggrid_area = turf.area(dggrid_polygon); 109 | 110 | var wkt = 'POLYGON(('; 111 | for (var i = 0; i < feature["geometry"]["coordinates"][0].length; i++) { 112 | var coordinate = feature["geometry"]["coordinates"][0][i]; 113 | wkt += coordinate[0]; 114 | wkt += ' '; 115 | wkt += coordinate[1]; 116 | if (i < feature["geometry"]["coordinates"][0].length - 1) { 117 | wkt += ', '; 118 | } else { 119 | wkt += '))'; 120 | } 121 | } 122 | 123 | var has_overlap = false; 124 | 125 | var dggrid_population = 0.0; 126 | for (block_index = 0; block_index < census_data.features.length; block_index++) { 127 | var block = census_data.features[block_index]; 128 | var block_centroid = centroids[block_index]; 129 | var geoid = block.properties["BLOCKID10"]; 130 | 131 | if (block.properties["POP10"] > 0) { 132 | 133 | var distance = turf.distance(dggrid_centroid, block_centroid, 'miles'); 134 | if (distance < 5.0) { 135 | var overlap_polygon = turf.intersect(block, dggrid_polygon); 136 | if (overlap_polygon != undefined) { 137 | //console.log("overlap"); 138 | var population = block.properties["POP10"]; 139 | var area = block_areas[block_index]; 140 | 141 | has_overlap = true; 142 | var overlap_area = turf.area(overlap_polygon); 143 | var new_population = population * (overlap_area/area); 144 | dggrid_population += new_population; 145 | } 146 | } 147 | 148 | } 149 | } 150 | 151 | if (has_overlap && (Math.round(dggrid_population) > 5)) { 152 | 153 | dggrids_with_overlap += 1; 154 | 155 | //pool.connect(function(err, new_client, new_done) { 156 | 157 | 158 | var s_gid = JSON.parse(JSON.stringify(gid)); 159 | var s_population = JSON.parse(JSON.stringify(Math.round(dggrid_population))); 160 | var s_wkt = JSON.parse(JSON.stringify(wkt)); 161 | 162 | db_sync(client, s_gid, s_population, s_wkt); 163 | 164 | 165 | //}); 166 | } 167 | 168 | bar.tick(); 169 | 170 | } 171 | console.log("Added/updated "+dggrids_with_overlap+" dggrids out of a possible "+dggrid_data.features.length); 172 | if (dggrids_with_overlap == 0) { 173 | process.exit(); 174 | } 175 | 176 | }); 177 | 178 | }); // jsonfile (dggrid) 179 | } 180 | }); 181 | 182 | 183 | 184 | }); 185 | 186 | function db_sync(client, s_gid, s_population, s_wkt) { 187 | var sel_result = client.query("SELECT id FROM dggrid WHERE gid="+s_gid+";", function(sel_err, sel_result) { 188 | 189 | 190 | if (sel_err) { 191 | console.error('error running query', sel_err); 192 | } 193 | 194 | if (sel_result.rows.length >= 1) { 195 | client.query("UPDATE dggrid \ 196 | SET population = "+s_population+" WHERE gid = "+s_gid+";", function(psd_err, psd_result) { 197 | 198 | if (psd_err) { 199 | console.error('error running query', psd_err); 200 | } 201 | 202 | dggrids_queried += 1; 203 | //console.log("Query done for dggrid "+dggrids_queried+" of "+dggrids_with_overlap); 204 | if (dggrids_queried == dggrids_with_overlap) { 205 | process.exit(); 206 | } 207 | }); 208 | } else { 209 | 210 | client.query("INSERT INTO dggrid (gid, geo, population) \ 211 | VALUES("+s_gid+", ST_GeomFromText('"+s_wkt+"'), "+s_population+");", function(psd_err, psd_result) { 212 | 213 | if (psd_err) { 214 | console.error('error running query', psd_err); 215 | } 216 | 217 | dggrids_queried += 1; 218 | //console.log("Query done for dggrid "+dggrids_queried+" of "+dggrids_with_overlap); 219 | if (dggrids_queried == dggrids_with_overlap) { 220 | process.exit(); 221 | } 222 | }); 223 | 224 | } 225 | 226 | }); 227 | } 228 | 229 | pool.on('error', function (err, client) { 230 | console.error('idle client error', err.message, err.stack) 231 | }) 232 | --------------------------------------------------------------------------------