Upload a GTFS file that contains shapes.txt
. Example GTFS files can be found at the GTFS Data Exchange.
-
-
Save rcknr/6910bb8602c2d27c5bbf3b82e151d3ec to your computer and use it in GitHub Desktop.
GTFS Viewer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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); | |
}); | |
}; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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