timetable
Version:
Using GTFS and other sources to produce beautiful and usable timetables for public transport.
391 lines (341 loc) • 16.8 kB
JavaScript
// 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);