xxxxxxxxxx
350
/*
2017.10.21 -- CitiBike Usage Visualizer
by Aidan Nelson
Desciption: Visualization of my Citibike bike-sharing usage in the past year (Oct 2016 - Oct 2017). I
use citibike often, so there are a relatively large number of trip data points (525) in that time frame.
Citibike trip data
is from their website (after login, there is a 'trips' menu where you can download data) and station
information (containing longitude/latitude) is from citibike's JSON data. Mapping will happen using
Mappa, which interfaces between p5.js and various online mapping APIs.
Citibike information index JSON: http://gbfs.citibikenyc.com/gbfs/gbfs.json
Citibike station information JSON: https://gbfs.citibikenyc.com/gbfs/en/station_information.json
Mappa: https://github.com/cvalenzuela/Mappa
QUESTIONS:
1. Are these arrays of arrays an OK way to hold the station and trip data? A JSON file seems more logical,
in that each data-point (one trip, or one station) would be held together rather than in 3 separate columns
with a single row index, but I don't know what the logic behind storing and accessing this data is... it
basically feels semantically unwise to require someone accessing trip info to know that start_time is at
index 0 and start_station is at index 1, etc.
2. Does "push" copy information or just reference it? IE does "station_info.push(names);" copy the "names" array
into "station_info" array, or just place a reference there? I am wondering this because the "names" array
is, in this case, local to the loadJSON callback function, and if it were referenced, I don't know why it was
not deleted after the function ended...
3. loop protect -- where to add //noprotect? why? what is wrong w/ addCoordinates function?
4. using map functions in object definitions (latLngToPixel..)?
TO DO:
1. figure out regular expressions: how to match entire station names, rather than parts of names?
IE how to avoid matching "jay st" with "jay st & york st"
2. infinite loop error
*/
// create arrays to hold trip and station info
let all_trips = [];
let station_info = [];
let tripsTaken = [];
let tripsIndex;
//google maps directions test:
let route = [{
"lat": 40.6977549,
"lng": -73.96772899999999
}, {
"lat": 40.6982737,
"lng": -73.9805511
}, {
"lat": 40.699748,
"lng": -73.9804601
}, {
"lat": 40.6998395,
"lng": -73.9830842
}, {
"lat": 40.6997874,
"lng": -73.9830894
}, {
"lat": 40.6999402,
"lng": -73.9863792
}, {
"lat": 40.6998511,
"lng": -73.9863408
}, {
"lat": 40.7156504,
"lng": -73.9944112
}, {
"lat": 40.71572760000001,
"lng": -73.99436829999999
}, {
"lat": 40.7159286,
"lng": -73.9948476
}, {
"lat": 40.7212588,
"lng": -73.99223150000002
}, {
"lat": 40.7216138,
"lng": -73.9934573
}, {
"lat": 40.7223341,
"lng": -73.9931936
}, {
"lat": 40.7236843,
"lng": -73.99646849999999
}, {
"lat": 40.7272842,
"lng": -73.993628
}, {
"lat": 40.7273868,
"lng": -73.9938318
}];
//create variable to hold the current data
let now = Date.parse("September 1, 2016");
//create variables to hold the map, canvas, and "Mappa" instance
let myMap;
let canvas;
let mappa;
// options for mappa object
let options = {
//set starting coordinates to NYC
lat: 40.7128,
lng: -73.950,
//set zoom level
zoom: 12,
style: "http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
}
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
function preload() {
console.log("in preload");
loadJSON("https://gbfs.citibikenyc.com/gbfs/en/station_information.json", makeStations);
loadStrings("cb_trips.txt", makeTrips);
// //load song
// soundFormats('mp3');
// song = loadSound('bicycle_race.mp3');
}
function setup() {
console.log("in setup");
// console.log(station_info.length);
// Create a canvas 640x640
canvas = createCanvas(640, 640);
mappa = new Mappa('Leaflet');
//call mappa object to create a tilemap at lat,long, zoom level
myMap = mappa.tileMap(options);
myMap.overlay(canvas); //create map overlay of a canvas
// Associate redrawMap callback function with an "onChange" event of the map
myMap.onChange(redrawMap);
//play the song
// song.setVolume(0.1);
// song.play();
tripsIndex = all_trips.length - 1;
}
function draw() {
clear();
update();
displayStations();
console.log("abt to display text");
displayText();
}
function update() {
//every so often, increase the date
if (frameCount % 30 == 0) {
//add one week to current date
now += 1000 * 60 * 60 * 24 * 7;
updateStations();
}
}
//this function should do the following: increase a counter on the station objects,
//such that I know how many times each has been visited by XX date / pull all
//stations that have been visited by now into a new array to be given to display
//function
function updateStations() {
//iterate through all_trips list backwards (in chronological order)
for (let i = all_trips.length - 1; i >= 0; i--) {
//if the trip happened after now, all subsequent trips will also be after now
if (all_trips[i].startTime > now) {
break;
} else {
//placing all of this code inside this funciton means the load is spread out and may not
//throw an infinite loop error...
for (let j = 0; j < station_info.length; j++) { //iterate through stations
if (all_trips[i].startStation == station_info[j].name) {
station_info[j].timesVisited++; //add to station visit counter
all_trips[i].orig.lat = station_info[j].lat;
all_trips[i].orig.lng = station_info[j].lng;
}
if (all_trips[i].endStation == station_info[j].name) {
station_info[j].timesVisited++;
all_trips[i].dest.lat = station_info[j].lat;
all_trips[i].dest.lng = station_info[j].lng;
}
}
tripsTaken.push(all_trips.pop());
console.log(tripsTaken.length);
}
//iterate this index so we aren't repeatedly adding same trips
//does this belong in else...?
// tripsIndex = i;
// console.log("tripsIndex: " + tripsIndex);
}
}
function displayStations() {
for (let j = 0; j < tripsTaken.length; j++) {
let pnt = myMap.latLngToPixel(tripsTaken[j].lat, tripsTaken[j].lng);
let size = station_info[j].timesVisited * 1;
fill(0, 0, 255, 75);
ellipse(pnt.x, pnt.y, size, size);
}
// for (let j = 0; j < station_info.length; j++) {
// if (station_info[j].timesVisited == 0) {
// continue;
// }
}
function displayText() {
//display now on upper left
textSize(12);
console.log("really");
textAlign(LEFT);
console.log("really");
fill(255);
console.log("really");
text(new Date(now), 50, 50);
}
//function to redraw points
function redrawMap() {
// clear();
// let pnt = myMap.latLngToPixel(route[0].lat, route[0].lng);
// for (let i = 1; i < route.length; i++) {
// // console.log(i);
// let pPnt = pnt;
// pnt = myMap.latLngToPixel(route[i].lat, route[i].lng);
// console.log("x: " + pnt.x + " /y: " + pnt.y);
// stroke(255, 0, 155);
// line(pPnt.x, pPnt.y, pnt.x, pnt.y);
// }
// clear();
// //draw every station up to the current date, if a station has been drawn more than once,
// //increase the size
// //iterate through all_trips list
// for (let i = 0; i < all_trips.length; i++) {
// //if the trip happened after now
// if (all_trips[i].startTime > now) {
// //for each trip, iterate through station list
// for (let j = 0; j < station_info.length; j++) {
// //if the trip start station matches current station (by name), draw blue ellipse at that point
// if (all_trips[i].startStation == station_info[j].name) {}
// }
// // //if the trip end station matches current station (by name), draw red ellipse at that point
// // if (all_trips[3][i] == station_info[0][j]) {
// // let pnt = myMap.latLngToPixel(station_info[1][j], station_info[2][j]);
// // // console.log(pnt);
// // fill(255, 0, 0);
// // ellipse(pnt.x, pnt.y, 5, 5);
// // }
// }
// }
}
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
//why doesn't the following function work?
// function getLatLng() {
// for (let k = 0; k < all_trips.length; k++) {
// // console.log(station_info.length);
// for (let q = 0; q < station_info.length; q++) {
// console.log(q);
// if (all_trips[k].endStation == station_info[q].name) {
// all_trips[k].dest.lat = station_info[q].lat;
// all_trips[k].dest.lng = station_info[q].lng;
// console.log("Destination is at " + all_trips[k].dest.lat + "/ " + all_trips[k].dest.lng);
// }
// if (all_trips[k].startStation == station_info[q].name) {
// all_trips[k].orig.lat = station_info[q].lat;
// all_trips[k].orig.lng = station_info[q].lng;
// console.log("Origin is at " + all_trips[k].dest.lat + "/ " + all_trips[k].dest.lng);
// }
// }
// }
// }
function makeStations(cb_stations) {
console.log("in makeStations");
//set up a short link to the station data
let stations = cb_stations.data.stations;
//iterate through info, adding to arrays
for (let i = 0; i < stations.length; i++) {
let s = new Station(stations[i].name, stations[i].lat, stations[i].lon);
station_info.push(s);
}
console.log("done w makeStations");
}
//this function takes citibike trip data and adds it to an array of arrays
//such that index i will correspond to a single trips data
function makeTrips(cb_trips) {
console.log("in makeTrips");
//initiate a new Trip object:
let t = new Trip();
//create an index for the line numbers
let lineIndex = 1;
//iterate through cb_trips
for (let i = 0; i < cb_trips.length; i++) {
//depending on which lineIndex we are at, add current string to one of the above arrays
if (lineIndex % 5 == 0) {
t.duration = trim(cb_trips[i]);
} else if (lineIndex % 4 == 0) {
t.endStation = trim(cb_trips[i]);
// for (let q=0;q<station_info.length;q++){
// console.log(q);
// if (t.endStation == station_info[q].name){
// t.dest.lat = station_info[q].lat;
// t.dest.lng = station_info[q].lng;
// console.log("Destination is at " + t.dest.lat + "/ " + t.dest.lng);
// }
// }
} else if (lineIndex % 3 == 0) {
t.endTime = Date.parse(trim(cb_trips[i]));
} else if (lineIndex % 2 == 0) {
t.startStation = trim(cb_trips[i]);
// for (let q=0;q<station_info.length;q++){
// if (t.startStation == station_info[q].name){
// t.orig.lat = station_info[q].lat;
// t.orig.lng = station_info[q].lng;
// }
// }
} else {
t.startTime = Date.parse(trim(cb_trips[i]));
}
lineIndex++;
if (lineIndex > 5) {
//return index to 1
lineIndex = 1;
//add trip object to the array of trip objects
all_trips.push(t);
//reuse t as a new trip object
t = new Trip();
}
}
console.log("done w makeTrips");
}