Skip to content

Instantly share code, notes, and snippets.

@rcknr
Forked from kaezarrex/README.md
Last active May 4, 2016 20:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rcknr/6910bb8602c2d27c5bbf3b82e151d3ec to your computer and use it in GitHub Desktop.
Save rcknr/6910bb8602c2d27c5bbf3b82e151d3ec to your computer and use it in GitHub Desktop.
GTFS Viewer

Upload a GTFS file that contains shapes.txt. Example GTFS files can be found at the GTFS Data Exchange.

(function() {
zip.workerScriptsPath = './'
var readEntry = function(entry, onend, onprogress) {
entry.getData(new zip.TextWriter(), onend, onprogress);
}
var getEntries = function(file, callback) {
zip.createReader(new zip.BlobReader(file), function(zipReader) {
zipReader.getEntries(callback);
}, onerror);
};
var mapEntries = function(entries, callbackMap) {
var feedFiles = d3.map();
var cbMap = d3.map(callbackMap);
entries.forEach(function(entry) {
feedFiles.set(entry.filename, entry);
});
cbMap.forEach(function(filename, callback) {
if (feedFiles.has(filename)) readEntry(feedFiles.get(filename), callback);
else alert(filename + ' does not exist');
});
};
window.parseGtfs = function(file, actions) {
getEntries(file, function(entries) {
mapEntries(entries, actions);
});
};
})();
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.2/leaflet.css">
<style>
body { padding: 0; margin: 0; }
html, body, #map { height: 100%; }
#uploader {
position: absolute;
top: 10px ;
left: 50px;
background-color: white;
border: 2px solid black;
padding: 10px 20px;
}
</style>
</head>
<body>
<div id="map"></div>
<input type="file" id="uploader">
<script src="https://cdn.jsdelivr.net/zip.js/0.1/zip.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.2/leaflet.js"></script>
<script src="tile.stamen.js"></script>
<script src='gtfsParser.js'></script>
<script src='main.js'></script>
</body>
</html>
var feature;
var stopMarker;
var shapeHusk;
var stopHusk;
var minLat;
var maxLat;
var minLng;
var maxLng;
var strokeWidth = 3;
var map = new L.Map("map", {center: [35.78, -78.68], zoom: 13}),
hidpi = window.devicePixelRatio > 1 ? '@'+window.devicePixelRatio+'x' : '';
L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}' + hidpi + '.png').addTo(map);
var svg = d3.select(map.getPanes().overlayPane).append("svg"),
stopHuskGroup = svg.append("g").attr("class", "stop-husk-group leaflet-zoom-hide"),
shapeHuskGroup = svg.append("g").attr("class", "shape-husk-group leaflet-zoom-hide"),
stopGroup = svg.append("g").attr("class", "stop-group leaflet-zoom-hide"),
shapeGroup = svg.append("g").attr("class", "shape-group leaflet-zoom-hide");
var pointCache = {};
var projectPoint = function(point) {
var key = point[0] + ',' + point[1];
if (pointCache[key] === undefined) {
pointCache[key] = map.latLngToLayerPoint(new L.LatLng(point[0], point[1]));
}
return pointCache[key];
}
var color = d3.scale.category20();
var line = d3.svg.line()
.x(function(d) { return projectPoint([d.lat, d.lon]).x; })
.y(function(d) { return projectPoint([d.lat, d.lon]).y; })
var drawShapes = function(shapeRows) {
pointCache = {};
var shapes = shapeRows.reduce(combineShapeRows);
var lats = shapeRows.map(function(shape) { return shape.lat });
var lngs = shapeRows.map(function(shape) { return shape.lon });
minLat = d3.min(lats);
minLng = d3.min(lngs);
maxLat = d3.max(lats);
maxLng = d3.max(lngs);
var topLeft = projectPoint([maxLat, minLng]);
var bottomRight = projectPoint([minLat, maxLng]);
var southWest = L.latLng(minLat, minLng);
var northEast = L.latLng(maxLat, maxLng);
var bounds = L.latLngBounds(southWest, northEast);
topLeft.x = topLeft.x - strokeWidth;
topLeft.y = topLeft.y - strokeWidth;
bottomRight.x = bottomRight.x + strokeWidth;
bottomRight.y = bottomRight.y + strokeWidth;
svg.attr("width", bottomRight.x - topLeft.x)
.attr("height", bottomRight.y - topLeft.y)
.style("left", topLeft.x + "px")
.style("top", topLeft.y + "px");
shapeHuskGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
shapeGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
shapeHusk = shapeHuskGroup.selectAll('.husk')
.data(d3.entries(shapes), function(d) { return d.key; })
shapeHusk.enter().append('path')
.attr('class', 'husk')
.attr("d", function(d) { return line(d.value); })
.style({
fill: 'none',
'stroke': '#fff',
'stroke-width': strokeWidth * 2,
'stroke-opacity': 1
});
shapeHusk.exit().remove();
feature = shapeGroup.selectAll('.feature')
.data(d3.entries(shapes), function(d) { return d.key; })
feature.enter().append('path')
.attr('class', 'feature')
.attr('d', function(d) { return line(d.value); })
.style('stroke', function(d, i) { return color(i); })
.style({
fill: 'none',
'stroke-width': strokeWidth,
'stroke-opacity': 0.5
});
feature.exit().remove();
map.fitBounds(bounds);
};
var resetShapes = function() {
pointCache = {};
strokeWidth = map.getZoom() < 9 ? 1 : (map.getZoom() - 8);
var topLeft = projectPoint([maxLat, minLng]);
var bottomRight = projectPoint([minLat, maxLng]);
topLeft.x = topLeft.x - strokeWidth;
topLeft.y = topLeft.y - strokeWidth;
bottomRight.x = bottomRight.x + strokeWidth;
bottomRight.y = bottomRight.y + strokeWidth;
svg.attr("width", bottomRight.x - topLeft.x)
.attr("height", bottomRight.y - topLeft.y)
.style("left", topLeft.x + "px")
.style("top", topLeft.y + "px");
shapeHuskGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
shapeGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
shapeHusk.attr("d", function(d) { return line(d.value); })
.style({'stroke-width': strokeWidth * 2});
feature.attr("d", function(d) { return line(d.value); })
.style({'stroke-width': strokeWidth});
}
var drawStops = function(data) {
pointCache = {};
var lats = data.map(function(stop) { return stop.lat });
var lngs = data.map(function(stop) { return stop.lon });
var topLeft = projectPoint([d3.max(lats), d3.min(lngs)]);
var bottomRight = projectPoint([d3.min(lats), d3.max(lngs)]);
topLeft.x = topLeft.x - strokeWidth;
topLeft.y = topLeft.y - strokeWidth;
bottomRight.x = bottomRight.x + strokeWidth;
bottomRight.y = bottomRight.y + strokeWidth;
stopHuskGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
stopGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
stopHusk = stopHuskGroup.selectAll('.stop-husk')
.data(data, function(d) { return d.id; })
stopHusk.enter().append('circle')
.attr('class', 'stop-husk')
.attr('r', strokeWidth * 2)
.attr('cx', function(d) { return projectPoint([d.lat, d.lon]).x; })
.attr('cy', function(d) { return projectPoint([d.lat, d.lon]).y; })
.style('fill', '#fff');
stopHusk.exit().remove();
stopMarker = stopGroup.selectAll('.stop')
.data(data, function(d) { return d.id; })
stopMarker.enter().append('circle')
.attr('class', 'stop')
.attr('r', strokeWidth)
.attr('cx', function(d) { return projectPoint([d.lat, d.lon]).x; })
.attr('cy', function(d) { return projectPoint([d.lat, d.lon]).y; })
.style('fill', '#35A9FC');
stopMarker.exit().remove();
};
var resetStops = function() {
pointCache = {};
strokeWidth = map.getZoom() < 9 ? 1 : (map.getZoom() - 8);
var topLeft = projectPoint([maxLat, minLng]);
var bottomRight = projectPoint([minLat, maxLng]);
topLeft.x = topLeft.x - strokeWidth;
topLeft.y = topLeft.y - strokeWidth;
bottomRight.x = bottomRight.x + strokeWidth;
bottomRight.y = bottomRight.y + strokeWidth;
stopHuskGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
stopGroup.attr("transform", "translate(" + -topLeft.x + "," + -topLeft.y + ")");
stopHusk.attr('r', strokeWidth * 2)
.attr('cx', function(d) { return projectPoint([d.lat, d.lon]).x; })
.attr('cy', function(d) { return projectPoint([d.lat, d.lon]).y; })
stopMarker.attr('r', strokeWidth)
.attr('cx', function(d) { return projectPoint([d.lat, d.lon]).x; })
.attr('cy', function(d) { return projectPoint([d.lat, d.lon]).y; })
};
var cleanShapeRow = function(row) {
return {
id: row.shape_id,
lat: parseFloat(row.shape_pt_lat),
lon: parseFloat(row.shape_pt_lon),
sequence: row.shape_pt_sequence
};
};
var cleanStopRow = function(row) {
return {
id: row.stop_id,
code: row.stop_code,
lat: parseFloat(row.stop_lat),
lon: parseFloat(row.stop_lon),
name: row.stop_name
};
};
var combineShapeRows = function(previous, current, index) {
if (index === 1) {
var tmp = {};
tmp[previous.id] = [previous];
previous = tmp;
}
if (!previous.hasOwnProperty(current.id)) {
previous[current.id] = [];
}
previous[current.id].push(current);
return previous;
};
var load_shapes = function(csv) {
var data = d3.csv.parse(csv, cleanShapeRow);
drawShapes(data);
};
var load_stops = function(csv) {
var data = d3.csv.parse(csv, cleanStopRow);
drawStops(data);
};
var upload_button = function(el) {
var uploader = document.getElementById(el);
var handleFiles = function() {
parseGtfs(this.files[0], {
'shapes.txt': load_shapes,
//'stops.txt': load_stops
});
};
uploader.addEventListener("change", handleFiles, false);
};
upload_button("uploader");
map.on('viewreset', function() {
//resetShapes();
//resetStops();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment