//import processing.opengl.*; OSMMercator mercator; float pixelsPerMinute; PFont scaleFont; PFont headerFont; PFont labelFont; Hashtable stations = new Hashtable(); Hashtable connections = new Hashtable(); Hashtable routes = new Hashtable(); boolean showTimeBands = false; Station selected = null; Station ellipseCentre = null; PImage ellipses = null; void setup() { size(800,500); //size(screen.width,screen.height,OPENGL); //size(screen.width,screen.height); // pixelsPerMinute = 5.0; // JAVA2D, 800, 500 pixelsPerMinute = width/150.0; smooth(); // strange things happen with GL ellipses? // scaleFont = loadFont("CenturyGothic-10b.vlw"); // limited chars headerFont = loadFont("GillSansMT-16.vlw"); //loadFont("CenturyGothic-24.vlw"); scaleFont = labelFont = loadFont("GillSansMT-10.vlw"); parseStations(); parseConnections(); parseRoutes(); double maxLon=Double.MIN_VALUE,minLon=Double.MAX_VALUE,maxLat=Double.MIN_VALUE,minLat=Double.MAX_VALUE; Iterator statIter = stations.values().iterator(); while(statIter.hasNext()) { Station s = (Station)statIter.next(); // println(s); maxLon = Math.max(s.lon,maxLon); minLon = Math.min(s.lon,minLon); maxLat = Math.max(s.lat,maxLat); minLat = Math.min(s.lat,minLat); } // println(maxLon+","+minLon+","+maxLat+","+minLat); mercator = new OSMMercator((minLat+maxLat)/2.0, (minLon+maxLon)/2.0, 1.05*(maxLon-minLon)/width, width, height); //pixelsPerMinute = 1.0/(float)mercator.kilometerinpixels(); statIter = stations.values().iterator(); while(statIter.hasNext()) { Station s = (Station)statIter.next(); s.mapx = (float)mercator.x(s.lon); s.mapy = (float)mercator.y(s.lat); s.screenx = s.mapx; s.screeny = s.mapy; s.targetx = s.mapx; s.targety = s.mapy; } /* Iterator conns = connections.values().iterator(); while (conns.hasNext()) { Iterator lineIter = ((Collection)conns.next()).iterator(); while(lineIter.hasNext()) { Connection c = (Connection)lineIter.next(); Route r = (Route)routes.get(c.line); print(c.line + "\t" + c.a.id + "\t" + c.b.id + "\t" + r.name + "\t" + c.a.name + "\t" + c.b.name + "\n"); } } */ // noLoop(); } void draw() { background(255); smooth(); if (showTimeBands) { // if (ellipses != null) { // image(ellipses,0,0); // } // else { ellipseMode(CENTER); for (float r = max(width,height), i=0; r > 0; r -= pixelsPerMinute*10, i++) { fill(i%2==0?230:255); noStroke(); ellipse(ellipseCentre.targetx,ellipseCentre.targety,2*r,2*r); } // ellipses = get(0,0,width,height); // } } Iterator statIter = stations.values().iterator(); while(statIter.hasNext()) { Station s = (Station)statIter.next(); s.animate(); } Iterator conns = connections.values().iterator(); while (conns.hasNext()) { Iterator lineIter = ((Collection)conns.next()).iterator(); while(lineIter.hasNext()) { Connection c = (Connection)lineIter.next(); Route r = (Route)routes.get(c.line); fill(r.colour); noStroke(); ellipse(c.a.screenx,c.a.screeny,5.0,5.0); ellipse(c.b.screenx,c.b.screeny,5.0,5.0); } } conns = connections.values().iterator(); while (conns.hasNext()) { Iterator lineIter = ((Collection)conns.next()).iterator(); while(lineIter.hasNext()) { Connection c = (Connection)lineIter.next(); Route r = (Route)routes.get(c.line); stroke(r.colour); strokeWeight(5.0); line(c.a.screenx,c.a.screeny,c.b.screenx,c.b.screeny); } } conns = connections.values().iterator(); while (conns.hasNext()) { Iterator lineIter = ((Collection)conns.next()).iterator(); while(lineIter.hasNext()) { Connection c = (Connection)lineIter.next(); Route r = (Route)routes.get(c.line); if (r.stripe != 0) { stroke(r.stripe); strokeWeight(3.0); line(c.a.screenx,c.a.screeny,c.b.screenx,c.b.screeny); } } } float minDist = Float.MAX_VALUE; ellipseMode(CENTER); strokeWeight(1.0); statIter = stations.values().iterator(); selected = null; while(statIter.hasNext()) { Station s = (Station)statIter.next(); float distance = dist(mouseX,mouseY,s.screenx,s.screeny); if (distance < minDist && distance < 20.0) { selected = s; minDist = distance; } if (s.totalLines-s.rail > 1) { stroke(0); fill(255); ellipse(s.screenx,s.screeny,5.0,5.0); } } if (selected != null) { stroke(255,255,0); fill(255,255,0); ellipse(selected.screenx,selected.screeny,5.0,5.0); String label = selected.name; if (selected.timeToCentre != 0 && showTimeBands) { label += " (" + selected.timeToCentre + " minutes travel)"; } textFont(labelFont,10); textAlign(CENTER); rectMode(CENTER); fill(200); noStroke(); triangle(selected.screenx,selected.screeny,selected.screenx+5,selected.screeny-6,selected.screenx+5,selected.screeny+6); fill(255); stroke(200); rect(selected.screenx+(textWidth(label)/2)+8,selected.screeny,textWidth(label)+4,16); fill(0); text(label,selected.screenx+(textWidth(label)/2)+8,selected.screeny+4); } stroke(0); strokeWeight(1.0); pushMatrix(); translate(width/20,height-(width/20)); textAlign(CENTER); textFont(scaleFont,10); fill(0); if (showTimeBands) { line(0,0,0,5); text("0",0,15); line(5*pixelsPerMinute,0,5*pixelsPerMinute,5); text("5",5*pixelsPerMinute,15); line(10*pixelsPerMinute,0,10*pixelsPerMinute,5); text("10",10*pixelsPerMinute,15); line(0,5,10*pixelsPerMinute,5); text("minutes",5*pixelsPerMinute,25); } else { line(0.0,0.0,0.0,5.0); text("0",0,15); line(5.0/(float)mercator.kilometerinpixels(),0.0,5.0/(float)mercator.kilometerinpixels(),5.0); text("5",5/(float)mercator.kilometerinpixels(),15); line(10.0/(float)mercator.kilometerinpixels(),0.0,10.0/(float)mercator.kilometerinpixels(),5.0); text("10",10/(float)mercator.kilometerinpixels(),15); line(0.0,5.0,10.0/(float)mercator.kilometerinpixels(),5.0); text("kilometers",5.0/(float)mercator.kilometerinpixels(),25); } popMatrix(); textAlign(CENTER); textFont(headerFont,16); if (showTimeBands) { text("Time to Travel from " + ellipseCentre.name,width/2,width/20); } else { text("Geographic Tube Map (click a station for travel time map)",width/2,width/20); } if (keyPressed && (key == 'g' || key == 'G')) { layoutAbout(0); } } void mouseReleased() { if (selected != null) { layoutAbout(selected); } } color stringToColor(String string) { if (!string.equals("NULL")) { return unhex("FF"+string.substring(1,7)); } else { return 0; } } void layoutAbout(int centreID) { if (centreID == 0) { Iterator statIter = stations.values().iterator(); while(statIter.hasNext()) { Station s = (Station)statIter.next(); s.targetx = s.mapx; s.targety = s.mapy; } showTimeBands = false; } else { Object s = stations.get(new Integer(centreID)); if (s != null) { layoutAbout((Station)s); } } } void layoutAbout(Station centre) { updateShortestPaths(centre); ellipseCentre = centre; ellipses = null; showTimeBands = true; // centre.targetx = width/2.0; // centre.targety = height/2.0; centre.targetx = centre.mapx; centre.targety = centre.mapy; Iterator statIter = stations.values().iterator(); while(statIter.hasNext()) { Station s = (Station)statIter.next(); layout(s,centre); } } void layout(Station a, Station centre) { float ang = atan2(a.mapy - centre.mapy, a.mapx - centre.mapx); float rad = pixelsPerMinute * (float)a.timeToCentre; // todo: limit to min(width/2,height/2)? a.targetx = centre.targetx + (rad * cos(ang)); a.targety = centre.targety + (rad * sin(ang)); } // cribbed from here in double quick time: http://www.cs.cmu.edu/~crpalmer/sp/ void updateShortestPaths(Station centre) { Iterator statIter = stations.values().iterator(); while (statIter.hasNext()) { Station s = (Station)statIter.next(); s.timeToCentre = (s == centre) ? 0 : Integer.MAX_VALUE; s.pathParent = null; } Stack queue = new Stack(); queue.push(centre); while (queue.size() > 0) { Collections.sort(queue); Station v = (Station)queue.pop(); for (int i = 0; i < v.conns.size(); i++) { Connection c = (Connection)v.conns.get(i); Station u = (c.a == v) ? c.b : c.a; if (c.time + v.timeToCentre < u.timeToCentre) { u.pathParent = v; u.timeToCentre = c.time + v.timeToCentre; queue.push(u); } } } }