Created
March 30, 2025 10:33
-
-
Save deton/56db388abd85b0e9748ec7eea6424bf0 to your computer and use it in GitHub Desktop.
Convert GTFS to GeoJSON for kepler.gl Trip Layer
This file contains hidden or 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"> | |
<!-- modified from https://github.com/frogcat/gtfs-preview/ --> | |
<title>GTFS to JSON for IconTripLayer</title> | |
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" /> | |
<script src="https://unpkg.com/[email protected]/dist/jszip.min.js"></script> | |
<script src="https://unpkg.com/[email protected]/papaparse.min.js"></script> | |
</head> | |
<body> | |
<h1>Convert GTFS to JSON for <a href="https://gist.github.com/deton/57ae19aed61b03e954df43fa10aa5584">IconTripLayer</a></h1> | |
<div id="map">Drag and drop gtfs.zip file here.</div> | |
<script> | |
const parse = (z, n) => z.file(n).async("text").then(t => Papa.parse(t.trim(), { | |
header: true, | |
skipEmptyLines: true | |
}).data); | |
function convert(zip, fname) { | |
const stops = {}; | |
parse(zip, "stops.txt").then(rows => { | |
rows.forEach(stop => { | |
stops[stop.stop_id] = [parseFloat(stop.stop_lon), parseFloat(stop.stop_lat)]; | |
}); | |
}).then(() => { | |
return parse(zip, "stop_times.txt"); | |
}).then(rows => { | |
const st = {}; | |
// TODO: filter trip_id by service_id(trips.txt) or date(calendar.txt) | |
rows.forEach(row => { | |
if (st[row.trip_id] === undefined) { | |
st[row.trip_id] = []; | |
} | |
const seq = parseInt(row.stop_sequence); | |
st[row.trip_id].push({ | |
seq: seq, | |
stop_id: row.stop_id, | |
timestamp: row.arrival_time, | |
}); | |
if (row.departure_time && row.departure_time !== row.arrival_time) { | |
st[row.trip_id].push({ | |
seq: seq + 0.5, | |
stop_id: row.stop_id, | |
timestamp: row.departure_time, | |
}); | |
} | |
}); | |
return st; | |
}).then(stopTimes => { | |
const result = []; | |
const now = new Date(); | |
const todayms = new Date().setHours(0, 0, 0, 0); | |
Object.keys(stopTimes).forEach(trip_id => { | |
const st = stopTimes[trip_id].sort((a, b) => a.seq - b.seq); | |
result.push({ | |
vendor: trip_id, | |
path: st.map(x => stops[x.stop_id]), | |
timestamps: st.map(x => { | |
return (now.setHours(...x.timestamp.split(':'), 0) - todayms) / 1000; | |
}), | |
}); | |
}); | |
const outfname = fname.replace(/\.zip$/, '.json'); | |
const outfile = new File([JSON.stringify(result)], outfname, { | |
type: 'application/json' | |
}); | |
const a = document.createElement('a'); | |
a.href = URL.createObjectURL(outfile); | |
a.download = outfile.name; | |
a.click(); | |
URL.revokeObjectURL(a.href); | |
}).catch(console.error); | |
} | |
(function (me) { | |
me.addEventListener("dragover", ev => { | |
ev.preventDefault(); | |
ev.stopPropagation(); | |
}); | |
me.addEventListener("drop", ev => { | |
ev.preventDefault(); | |
ev.stopPropagation(); | |
const promises = Array.from(ev.dataTransfer.files).map(file => { | |
return JSZip.loadAsync(file).then(zip => { | |
convert(zip, file.name); | |
}); | |
}); | |
Promise.all(promises).catch(console.error); | |
}); | |
})(document.getElementById("map")); | |
</script> | |
</body> | |
</html> |
This file contains hidden or 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"> | |
<!-- modified from https://github.com/frogcat/gtfs-preview/ --> | |
<title>GTFS to GeoJSON for kepler.gl Trip Layer</title> | |
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" /> | |
<script src="https://unpkg.com/[email protected]/dist/jszip.min.js"></script> | |
<script src="https://unpkg.com/[email protected]/papaparse.min.js"></script> | |
</head> | |
<body> | |
<h1>Convert GTFS to GeoJSON for kepler.gl Trip Layer</h1> | |
<div id="map">Drag and drop gtfs.zip file here.</div> | |
<script> | |
const parse = (z, n) => z.file(n).async("text").then(t => Papa.parse(t.trim(), { | |
header: true, | |
skipEmptyLines: true | |
}).data); | |
function convert(zip, fname) { | |
const stops = {}; | |
parse(zip, "stops.txt").then(rows => { | |
rows.forEach(stop => { | |
stops[stop.stop_id] = [parseFloat(stop.stop_lon), parseFloat(stop.stop_lat)]; | |
}); | |
}).then(() => { | |
return parse(zip, "stop_times.txt"); | |
}).then(rows => { | |
const st = {}; | |
// TODO: filter trip_id by service_id(trips.txt) or date(calendar.txt) | |
rows.forEach(row => { | |
if (st[row.trip_id] === undefined) { | |
st[row.trip_id] = []; | |
} | |
const seq = parseInt(row.stop_sequence); | |
st[row.trip_id].push({ | |
seq: seq, | |
stop_id: row.stop_id, | |
timestamp: row.arrival_time, | |
}); | |
if (row.departure_time && row.departure_time !== row.arrival_time) { | |
st[row.trip_id].push({ | |
seq: seq + 0.5, | |
stop_id: row.stop_id, | |
timestamp: row.departure_time, | |
}); | |
} | |
}); | |
return st; | |
}).then(stopTimes => { | |
const result = []; | |
const now = new Date(); | |
Object.keys(stopTimes).forEach(trip_id => { | |
const st = stopTimes[trip_id].sort((a, b) => a.seq - b.seq); | |
const coords = st.map(x => { | |
const ts = (now.setHours(...x.timestamp.split(':'), 0)) / 1000; | |
// lon,lat,alt,timestamp for kepler.gl | |
return [...stops[x.stop_id], 0, ts]; | |
}); | |
result.push({ | |
type: 'Feature', | |
properties: { | |
trip_id: trip_id, | |
}, | |
geometry: { | |
type: 'LineString', | |
coordinates: coords, | |
}, | |
}); | |
}); | |
const geojson = {type: 'FeatureCollection', 'features': result}; | |
const outfname = fname.replace(/\.zip$/, '.geojson'); | |
const outfile = new File([JSON.stringify(geojson)], outfname, { | |
type: 'application/geo+json' | |
}); | |
const a = document.createElement('a'); | |
a.href = URL.createObjectURL(outfile); | |
a.download = outfile.name; | |
a.click(); | |
URL.revokeObjectURL(a.href); | |
}).catch(console.error); | |
} | |
(function (me) { | |
me.addEventListener("dragover", ev => { | |
ev.preventDefault(); | |
ev.stopPropagation(); | |
}); | |
me.addEventListener("drop", ev => { | |
ev.preventDefault(); | |
ev.stopPropagation(); | |
const promises = Array.from(ev.dataTransfer.files).map(file => { | |
return JSZip.loadAsync(file).then(zip => { | |
convert(zip, file.name); | |
}); | |
}); | |
Promise.all(promises).catch(console.error); | |
}); | |
})(document.getElementById("map")); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment