timetable
Version:
Using GTFS and other sources to produce beautiful and usable timetables for public transport.
229 lines (212 loc) • 10.6 kB
text/coffeescript
############################################################################################################
# njs_util = require 'util'
# njs_path = require 'path'
# njs_fs = require 'fs'
# njs_crypto = require 'crypto'
#...........................................................................................................
# BAP = require 'coffeenode-bitsnpieces'
# TYPES = require 'coffeenode-types'
# TEXT = require 'coffeenode-text'
TRM = require 'coffeenode-trm'
rpr = TRM.rpr.bind TRM
badge = 'TIMETABLE/READER'
log = TRM.get_logger 'plain', badge
info = TRM.get_logger 'info', badge
whisper = TRM.get_logger 'whisper', badge
alert = TRM.get_logger 'alert', badge
debug = TRM.get_logger 'debug', badge
warn = TRM.get_logger 'warn', badge
help = TRM.get_logger 'help', badge
urge = TRM.get_logger 'urge', badge
echo = TRM.echo.bind TRM
rainbow = TRM.rainbow.bind TRM
#...........................................................................................................
ASYNC = require 'async'
#...........................................................................................................
REGISTRY = require './REGISTRY'
COURSES = require './COURSES'
P = require 'pipedreams'
$ = P.$.bind P
#...........................................................................................................
options = require '../options'
DEV = options[ 'mode' ] is 'dev'
#-----------------------------------------------------------------------------------------------------------
@_distance_from_latlongs = ( latlong_a, latlong_b ) ->
### http://hashbang.co.nz/blog/2013/2/25/d3_js_geo_fun ###
# calcDist: (p1,p2) ->
# #Haversine formula
# dLatRad = Math.abs(p1[1] - p2[1]) * Math.PI/180;
# dLonRad = Math.abs(p1[0] - p2[0]) * Math.PI/180;
# # Calculate origin in Radians
# lat1Rad = p1[1] * Math.PI/180;
# lon1Rad = p1[0] * Math.PI/180;
# # Calculate new point in Radians
# lat2Rad = p2[1] * Math.PI/180;
# lon2Rad = p2[0] * Math.PI/180;
# # Earth's Radius
# eR = 6371;
# d1 = Math.sin(dLatRad/2) * Math.sin(dLatRad/2) +
# Math.sin(dLonRad/2) * Math.sin(dLonRad/2) * Math.cos(lat1Rad) * Math.cos(lat2Rad);
# d2 = 2 * Math.atan2(Math.sqrt(d1), Math.sqrt(1-d1));
# return(eR * d2);
### http://www.geodatasource.com/developers/javascript ###
[ lat1, lon1, ] = latlong_a
[ lat2, lon2, ] = latlong_b
# [ lon1, lat1, ] = latlong_a
# [ lon2, lat2, ] = latlong_b
radlat1 = Math.PI * lat1 / 180
radlat2 = Math.PI * lat2 / 180
radlon1 = Math.PI * lon1 / 180
radlon2 = Math.PI * lon2 / 180
theta = ( lon1 - lon2 ) * Math.PI / 180
R = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(theta)
R = Math.acos R
R = R * 180 / Math.PI
R = R * 60 * 1.1515
R = R * 1.609344
R = R * 1000
return R
#-----------------------------------------------------------------------------------------------------------
@create_indexes = ( handler ) ->
#.........................................................................................................
tasks = []
constraints = [
"CREATE CONSTRAINT ON (n:trip) ASSERT n.id IS UNIQUE;"
"CREATE CONSTRAINT ON (n:stop) ASSERT n.id IS UNIQUE;"
"CREATE CONSTRAINT ON (n:route) ASSERT n.id IS UNIQUE;"
"CREATE CONSTRAINT ON (n:stoptime) ASSERT n.id IS UNIQUE;" ]
#.........................................................................................................
identifiers = [
# ':trip(`id`)'
# ':stop(`id`)'
# ':route(`id`)'
# ':stoptime(`id`)'
':trip(`route-id`)'
':stop(`name`)'
':stoptime(`trip-id`)'
':stoptime(`stop-id`)'
':route(`name`)' ]
#.........................................................................................................
for constraint in constraints
do ( constraint ) =>
query = query: constraint
tasks.push ( async_handler ) =>
N4J._request query, ( error ) =>
return async_handler error if error?
whisper "created constraint and index with #{constraint}"
async_handler null
#.........................................................................................................
for identifier in identifiers
do ( identifier ) =>
query = query: """CREATE INDEX ON #{identifier};"""
tasks.push ( async_handler ) =>
N4J._request query, ( error ) =>
return async_handler error if error?
whisper "created index on #{identifier}"
async_handler null
#.........................................................................................................
on_finish = ( error ) =>
return handler error if error?
handler null
#.........................................................................................................
ASYNC.series tasks, on_finish
#-----------------------------------------------------------------------------------------------------------
@update_distances = ( handler ) ->
query = query: """MATCH (stop1:stop)-[r:distance]->(stop2:stop) RETURN DISTINCT stop1,stop2"""
#---------------------------------------------------------------------------------------------------------
N4J._request query, ( error, rows ) =>
return handler error if error?
tasks = []
for [ stop1, stop2 ] in rows
id1 = stop1[ 'id' ]
id2 = stop2[ 'id' ]
distance_m = Math.floor 0.5 + @_distance_from_latlongs stop1[ 'latlong' ], stop2[ 'latlong' ]
query = query: """
MATCH (stop1:stop {id: #{N4J._escape id1}})-[r:distance]->(stop2:stop {id: #{N4J._escape id2}})
SET r.value = #{distance_m}"""
do ( query ) =>
tasks.push ( async_handler ) =>
N4J._request query, ( error ) =>
return async_handler error if error?
# whisper query[ 'query' ]
async_handler null
#-------------------------------------------------------------------------------------------------------
on_finish = ( error ) =>
return handler error if error?
handler null
#-------------------------------------------------------------------------------------------------------
ASYNC.series tasks, on_finish
#---------------------------------------------------------------------------------------------------------
return null
#-----------------------------------------------------------------------------------------------------------
@create_edges = ( handler ) ->
tasks = []
### TAINT rewrite for halts, courses, reltrips ###
# """
# MATCH (stop1:stop)--(a:stoptime)-[:linked]-(b:stoptime)--(stop2:stop)
# CREATE (stop1)-[:distance {`~label`: 'distance', value: 0}]->(stop2)
# """
sources = [
"""
MATCH (a:halt)
MATCH (b:halt {`course-id`: a.`course-id`, `idx`: a.idx + 1})
CREATE (a)-[:linked {`~label`: 'linked'}]->(b);"""
]
#.....................................................................................................
for source in sources
do ( source ) =>
query = query: source
tasks.push ( async_handler ) =>
N4J._request query, ( error ) =>
return async_handler error if error?
whisper "created edges with\n#{source}"
async_handler null
#.....................................................................................................
on_finish = ( error ) =>
return handler error if error?
handler null
#.......................................................................................................
ASYNC.series tasks, on_finish
return null
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
@read_station_nodes = ( registry, handler ) ->
# ratio = if DEV then 1 / 100 else 1
input = P.$read_values registry[ '%gtfs' ][ 'stops' ]
#.........................................................................................................
input
# .pipe P.$sample ratio, headers: true, seed: 5 # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
.pipe @$normalize_station_name 'name'
.pipe @$add_n4j_system_properties 'station'
.pipe @$add_id()
.pipe @$remove_gtfs_fields()
.pipe @$register registry
.pipe P.$collect_sample input, 1, ( _, sample ) -> whisper 'station', sample
.on 'end', =>
return handler null
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
@main = ( registry, handler ) ->
t0 = 1 * new Date() # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
tasks = []
#.......................................................................................................
for node_type in options[ 'data' ][ 'node-types' ]
collection = registry[ node_type ]
# for
# #.......................................................................................................
# for messages in [ no_source, no_method, ]
# for message in messages
# warn message
# #.......................................................................................................
# info "reading data for #{ok_types.length} type(s)"
# info " (#{ok_types.join ', '})"
#.........................................................................................................
ASYNC.series tasks, ( error ) =>
throw error if error?
t1 = 1 * new Date() # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
urge 'dt:', ( t1 - t0 ) / 1000 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
handler null, registry
#.........................................................................................................
return null