UNPKG

timetable

Version:

Using GTFS and other sources to produce beautiful and usable timetables for public transport.

391 lines (341 loc) 16.8 kB
// Generated by CoffeeScript 1.7.1 (function() { "\n\n\n\ng/sttm^828812\ng/trip^32565\ng/route^811\n\n\n%|g/route|name:foobar|^811\n\n\n%|gtfs/stoptime|gtfs/trip^123|^556|gtfs/service^33421\n\n %|gtfs/stoptime|gtfs/trip^123|^556\n+ %|gtfs/trip|gtfs/route^8877|^123\n------------------------------------------------------\n= %|gtfs/stoptime|gtfs/route^8877\n\n\n= %|gtfs/stoptime|gtfs/trip^123|^556|gtfs/route^8877\n\n\n############################################################################################################\n\ngtfs:\n stoptime:\n id: gtfs/stoptime/^876\n stop-id: gtfs/stop/^123\n trip-id: gtfs/trip/^456\n ...\n\n stop:\n id: gtfs/stop/^123\n name: Bayerischer+Platz\n ...\n\n trip:\n id: gtfs/trip/^456\n route-id: gtfs/route/^777\n service-id: gtfs/service/^888\n\n\ngtfs/stoptime/^876\ngtfs/stoptime/^876/stop-id:gtfs/stop/^123\ngtfs/stoptime/^876/trip-id:gtfs/trip/^456\ngtfs/stop/^123\ngtfs/stop/^123/name:Bayerischer+Platz\ngtfs/trip/^456\n\n\n############################################################################################################\n\ngtfs:\n stoptime:\n ^: gtfs/stoptime/^876\n gtfs/stop/^: gtfs/stop/^123\n gtfs/trip/^: gtfs/trip/^456\n ...\n arr: 15:38\n dep: 15:38\n\n\n stop:\n ^: gtfs/stop/^123\n name: Bayerischer+Platz\n ...\n\n trip:\n ^: gtfs/trip/^456\n gtfs/route/^: gtfs/route/^777\n gtfs/service/^: gtfs/service/^888\n\n route:\n ^: gtfs/route/^777\n name: U4\n\n# REALM/TYPE/^IDN\nREALM/TYPE/^IDN|NAME:VALUE\nREALM₀/TYPE₀/^IDN₀|>REALM₁/TYPE₁/^IDN₁\n\ngtfs/route/^777\ngtfs/route/^777|name:U4\ngtfs/stop/^123\ngtfs/stop/^123|=name:Bayerischer+Platz\ngtfs/stoptime/^876\ngtfs/stoptime/^876|=arr:15%2538\ngtfs/stoptime/^876|=dep:15%2538\ngtfs/stoptime/^876|>gtfs/stop/^123\ngtfs/stoptime/^876|>gtfs/trip/^456\ngtfs/trip/^456\ngtfs/trip/^456|>gtfs/route/^777\ngtfs/trip/^456|>gtfs/service/^888\n\n\n gtfs/stoptime/^876|>gtfs/trip/^456\n+ gtfs/trip/^456|>gtfs/route/^777\n----------------------------------------------------------------\n= gtfs/stoptime/^876|>>gtfs/route/^777\n\n\n gtfs/stoptime/^876|>gtfs/trip/^456\n gtfs/trip/^456|>gtfs/service/^888\n----------------------------------------------------------------\n= gtfs/stoptime/^876|>>gtfs/service/^888\n\n============================================================================================================\n\n%|REALM/TYPE|=NAME:VALUE|^IDN\n%|REALM₀/TYPE₀|>N>=REALM₁/TYPE₁/^IDN₁|^IDN₀\n\n%|gtfs/route|=name:U4|^777\n%|gtfs/stoptime|=arr:15%2538|^876\n%|gtfs/stoptime|=dep:15%2538|^876\n%|gtfs/stoptime|>1>gtfs/stop/^123|^876\n%|gtfs/stoptime|>1>gtfs/trip/^456|^876\n%|gtfs/stoptime|>2>gtfs/route/^777|^876\n%|gtfs/stoptime|>2>gtfs/service/^888|^876\n%|gtfs/stop|=name:Bayerischer+Platz|^123\n%|gtfs/trip|>gtfs/route/^777|^456\n%|gtfs/trip|>gtfs/service/^888|^456\n"; var $, ASYNC, DEV, P, REGISTRY, TEXT, TRM, TYPES, alert, badge, datasource_infos, debug, echo, global_data_limit, help, info, log, njs_fs, options, rainbow, registry, rpr, urge, warn, whisper, _ref, _ref1, __slice = [].slice; njs_fs = require('fs'); TYPES = require('coffeenode-types'); TEXT = require('coffeenode-text'); TRM = require('coffeenode-trm'); rpr = TRM.rpr.bind(TRM); badge = 'TIMETABLE/GTFS-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); options = require('../options'); global_data_limit = (_ref = (_ref1 = options['data']) != null ? _ref1['limit'] : void 0) != null ? _ref : Infinity; datasource_infos = (require('./get-datasource-infos'))(); REGISTRY = require('./REGISTRY'); ASYNC = require('async'); /* https://github.com/loveencounterflow/pipedreams */ P = require('pipedreams'); $ = P.$.bind(P); DEV = options['mode'] === 'dev'; this.$set_id_from_gtfs_id = function() { return $((function(_this) { return function(record, handler) { var gtfs_id, gtfs_type; gtfs_id = record['gtfs-id']; gtfs_type = record['gtfs-type']; if (gtfs_id == null) { return handler(new Error("unable to register record without GTFS ID: " + (rpr(record)))); } if (gtfs_type == null) { return handler(new Error("unable to register record without GTFS type: " + (rpr(record)))); } record['id'] = "gtfs/" + gtfs_type + "/" + gtfs_id; return handler(null, record); }; })(this)); }; this.$convert_latlon = function() { return $((function(_this) { return function(record, handler) { record['lat'] = parseFloat(record['lat']); record['lon'] = parseFloat(record['lon']); return handler(null, record); }; })(this)); }; this.$_XXXX_add_id_indexes = function() { var on_data; on_data = function(record) { var id, index_record, match, name, realm, ref_id, ref_realm, ref_type, type, _; realm = 'gtfs'; type = record['gtfs-type']; id = record['gtfs-id']; for (name in record) { ref_id = record[name]; match = name.match(/^gtfs-(.+?)-id$/); if (match == null) { continue; } _ = match[0], ref_type = match[1]; ref_realm = 'gtfs'; index_record = { 'id': "%|" + realm + "/" + type + "|" + ref_realm + "/" + ref_type + "/" + ref_id + "|" + id }; this.emit('data', index_record); } return this.emit('data', record); }; return P.through(on_data, null); }; this.$index_on = function() { var escape, names, on_data; names = 1 <= arguments.length ? __slice.call(arguments, 0) : []; escape = this._escape_for_index; on_data = function(record) { var id, index_record, name, realm, type, value, value_type, _i, _len; realm = 'gtfs'; type = record['gtfs-type']; id = record['gtfs-id']; for (_i = 0, _len = names.length; _i < _len; _i++) { name = names[_i]; value = record[name]; if (value == null) { continue; } if ((value_type = TYPES.type_of(value)) !== 'text') { throw new Error("building index from type " + (rpr(value_type)) + " not currently supported"); } value = escape(value); index_record = { 'id': "%|" + realm + "/" + type + "|" + name + ":" + value + "|" + id }; this.emit('data', index_record); } return this.emit('data', record); }; return P.through(on_data, null); }; this._escape_for_index = function(text) { var R; R = text; R = R.replace(/%/g, '%25'); R = R.replace(/\|/g, '%7C'); R = R.replace(/:/g, '%3A'); R = R.replace(/\//g, '%2F'); return R; }; this.read_agency = function(registry, route, handler) { var input; input = P.create_readstream(route, 'agency'); input.pipe(P.$split()).pipe(P.$skip_empty()).pipe(P.$parse_csv()).pipe(this.$clean_agency_record()).pipe(P.$delete_prefix('agency_')).pipe(P.$set('gtfs-type', 'agency')).pipe(P.$rename('id', 'gtfs-id')).pipe(this.$set_id_from_gtfs_id()).pipe(this.$clean_agency_record()).pipe(P.$dasherize_field_names()).pipe(this.$index_on('name')).pipe(REGISTRY.$register(registry)).pipe(P.$collect_sample(4, function(_, sample) { return whisper('agency', sample); })).pipe(P.$on_end(function() { return handler(null); })); return null; }; this.$clean_agency_record = function() { return $((function(_this) { return function(record, handler) { delete record['agency_phone']; delete record['agency_lang']; return handler(null, record); }; })(this)); }; /* TAINT name clash (filesystem route vs. GTFS route) */ this.read_routes = function(registry, route, handler) { var input, ratio; ratio = DEV ? 1 / 10 : 1; input = P.create_readstream(route, 'routes'); input.pipe(P.$split()).pipe(P.$skip_empty()).pipe(P.$sample(ratio, { headers: true, seed: 5 })).pipe(P.$parse_csv()).pipe(this.$clean_routes_record()).pipe(P.$dasherize_field_names()).pipe(P.$set('gtfs-type', 'routes')).pipe(P.$rename('route-id', 'gtfs-id')).pipe(P.$rename('agency-id', 'gtfs-agency-id')).pipe(P.$rename('route-short-name', 'name')).pipe(this.$set_id_from_gtfs_id()).pipe(this.$_XXXX_add_id_indexes()).pipe(this.$index_on('name')).pipe(REGISTRY.$register(registry)).pipe(P.$collect_sample(8, function(_, sample) { return whisper('route', sample); })).pipe(P.$on_end(function() { return handler(null); })); return null; }; this.$clean_routes_record = function() { return $((function(_this) { return function(record, handler) { delete record['route_long_name']; delete record['route_desc']; delete record['route_url']; delete record['route_color']; delete record['route_text_color']; return handler(null, record); }; })(this)); }; this.read_calendar_dates = function(registry, route, handler) { var input; input = P.create_readstream(route, 'calendar_dates'); input.pipe(P.$split()).pipe(P.$skip_empty()).pipe(P.$parse_csv()).pipe(P.$sample(1 / 100, { headers: true, seed: 5 })).pipe(this.$clean_calendar_date_record()).pipe(P.$set('gtfs-type', 'calendar_dates')).pipe(P.$rename('service_id', 'gtfs-id')).pipe(this.$set_id_from_gtfs_id()).pipe(REGISTRY.$register(registry)).pipe(P.$collect_sample(4, function(_, sample) { return whisper('service', sample); })).pipe(P.$on_end(function() { return handler(null); })); return null; }; this.$clean_calendar_date_record = function() { return $((function(_this) { return function(record, handler) { delete record['exception_type']; return handler(null, record); }; })(this)); }; this.read_trips = function(registry, route, handler) { var input, ratio; input = P.create_readstream(route, 'trips'); ratio = DEV ? 1 / 10 : 1; input.pipe(P.$split()).pipe(P.$skip_empty()).pipe(P.$parse_csv()).pipe(P.$sample(ratio, { headers: true, seed: 5 })).pipe(this.$clean_trip_record()).pipe(P.$delete_prefix('trip_')).pipe(P.$dasherize_field_names()).pipe(P.$set('gtfs-type', 'trips')).pipe(P.$rename('id', 'gtfs-id')).pipe(P.$rename('route-id', 'gtfs-routes-id')).pipe(P.$rename('service-id', 'gtfs-service-id')).pipe(this.$set_id_from_gtfs_id()).pipe(this.$_XXXX_add_id_indexes()).pipe(REGISTRY.$register(registry)).pipe(P.$collect_sample(4, function(_, sample) { return whisper('trip', sample); })).pipe(P.$on_end(function() { return handler(null); })); return null; }; this.$clean_trip_record = function() { return $((function(_this) { return function(record, handler) { delete record['trip_short_name']; delete record['direction_id']; delete record['block_id']; delete record['shape_id']; return handler(null, record); }; })(this)); }; this.read_stop_times = function(registry, route, handler) { var input, ratio; input = P.create_readstream(route, 'stop_times'); ratio = DEV ? 1 / 1000 : 1; input.pipe(P.$split()).pipe(P.$skip_empty()).pipe(P.$sample(ratio, { headers: true })).pipe(P.$parse_csv()).pipe(this.$clean_stop_times_record()).pipe(P.$set('gtfs-type', 'stop_times')).pipe(P.$dasherize_field_names()).pipe(P.$rename('trip-id', 'gtfs-trips-id')).pipe(P.$rename('stop-id', 'gtfs-stops-id')).pipe(P.$rename('arrival-time', 'arr')).pipe(P.$rename('departure-time', 'dep')).pipe(this.$add_stoptimes_gtfs_id()).pipe(this.$set_id_from_gtfs_id()).pipe(this.$_XXXX_add_id_indexes()).pipe(REGISTRY.$register(registry)).pipe(P.$collect_sample(5, function(_, sample) { return whisper('stop_time', sample); })).pipe(P.$on_end(function() { return handler(null); })); return null; }; this.$clean_stop_times_record = function() { return $((function(_this) { return function(record, handler) { delete record['stop_headsign']; delete record['pickup_type']; delete record['drop_off_type']; delete record['shape_dist_traveled']; return handler(null, record); }; })(this)); }; this.$add_stoptimes_gtfs_id = function() { var idx; idx = 0; return $((function(_this) { return function(record, handler) { record['gtfs-id'] = "" + idx; idx += 1; return handler(null, record); }; })(this)); }; this.read_stops = function(registry, route, handler) { var input; input = P.create_readstream(route, 'stops'); input.pipe(P.$split()).pipe(P.$skip_empty()).pipe(P.$parse_csv()).pipe(this.$clean_stops_record()).pipe(P.$delete_prefix('stop_')).pipe(P.$set('gtfs-type', 'stops')).pipe(P.$rename('id', 'gtfs-id')).pipe(this.$set_id_from_gtfs_id()).pipe(this.$convert_latlon()).pipe(this.$index_on('name')).pipe(REGISTRY.$register(registry)).pipe(P.$collect_sample(4, function(_, sample) { return whisper('stop', sample); })).pipe(P.$on_end(function() { return handler(null); })); return null; }; this.$clean_stops_record = function() { return $((function(_this) { return function(record, handler) { delete record['stop_desc']; delete record['zone_id']; delete record['stop_url']; delete record['location_type']; delete record['parent_station']; return handler(null, record); }; })(this)); }; this.main = function(registry, handler) { var gtfs_type, message, messages, method, method_name, no_method, no_source, ok_types, route, route_by_types, source_name, t0, tasks, _fn, _i, _j, _k, _len, _len1, _len2, _ref2, _ref3; t0 = 1 * new Date(); for (source_name in datasource_infos) { route_by_types = datasource_infos[source_name]; tasks = []; no_source = []; no_method = []; ok_types = []; _ref2 = options['data']['gtfs-types']; _fn = (function(_this) { return function(method_name, method, route) { return tasks.push(function(async_handler) { help("" + badge + "/" + method_name); return method(registry, route, function() { var P; P = 1 <= arguments.length ? __slice.call(arguments, 0) : []; info("" + badge + "/" + method_name); return async_handler.apply(null, P); }); }); }; })(this); for (_i = 0, _len = _ref2.length; _i < _len; _i++) { gtfs_type = _ref2[_i]; route = route_by_types[gtfs_type]; if (route == null) { no_source.push("skipping " + source_name + "/" + gtfs_type + " (no source file)"); continue; } help("found data source for " + source_name + "/" + gtfs_type); method_name = "read_" + gtfs_type; method = this[method_name]; if (method == null) { no_method.push("no method to read GTFS data of type " + (rpr(gtfs_type)) + "; skipping"); continue; } method = method.bind(this); ok_types.push(gtfs_type); _fn(method_name, method, route); } _ref3 = [no_source, no_method]; for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { messages = _ref3[_j]; for (_k = 0, _len2 = messages.length; _k < _len2; _k++) { message = messages[_k]; warn(message); } } info("reading data for " + ok_types.length + " type(s)"); info(" (" + (ok_types.join(', ')) + ")"); } ASYNC.series(tasks, (function(_this) { return function(error) { var t1; if (error != null) { throw error; } t1 = 1 * new Date(); urge('dt:', (t1 - t0) / 1000); return handler(null, registry); }; })(this)); return null; }; if (module.parent == null) { registry = REGISTRY.new_registry(); this.main(registry, function(error, registry) { if (error != null) { throw error; } return info(registry); }); } }).call(this);