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(); Hashtable stationPoints = new Hashtable(); boolean showTimeBands = false; Station selected = null; Station ellipseCentre = null; PImage ellipses = null; PImage wms = null; Triangle[] triangles; Point3d[] points; void setup() { size(800,500,P3D); // size(800,500,OPENGL); // size(screen.width,screen.height,OPENGL); //size(screen.width,screen.height,P3D); //pixelsPerMinute = 10.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.1*(maxLon-minLon)/width, width, height); //new Thread(new Runnable() { // public void run() { //wms = loadImage("http://onearth.jpl.nasa.gov/wms.cgi?request=GetMap&layers=modis,global_mosaic&styles=&srs=EPSG:4326&format=image/jpeg" + "&bbox=" + mercator.bbox() + "&width=" + width + "&height=" + height); // } //}).start(); // wms.save("map"); //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(); int gridw = 4; int gridh = 4; int gridcount = gridw*gridh; points = new Point3d[ stations.size() + gridcount + 3 ]; for (int i = 0; i < gridcount; i++) { float x = width*(i%gridw)/(gridw-1); float y = height*(i/gridw)/(gridh-1); points[i] = new Point3d(x,y,0); } statIter = stations.values().iterator(); for (int i = gridcount; statIter.hasNext(); i++) { Station s = (Station)statIter.next(); points[i] = new Point3d( s.mapx, s.mapy, 0.0 ); stationPoints.put(points[i],s); } Arrays.sort(points,0,points.length-3); points[points.length-1] = new Point3d(); points[points.length-2] = new Point3d(); points[points.length-3] = new Point3d(); Triangle temp[] = new Triangle[ (points.length-3) * 3 ]; for (int i = 0; i < temp.length; i++) { temp[i] = new Triangle(); } int ntri = Triangulate.Triangulate( points.length-3, points, temp ); triangles = new Triangle[ntri]; for (int i = 0; i < triangles.length; i++) { triangles[i] = temp[i]; } // ellipses = new PImage(new int[width*height], width, height, ARGB); } void draw() { background(showTimeBands ? 128 : 255); if (showTimeBands) { if (ellipseCentre != null && ellipses == null) { ellipseMode(CENTER); float rmax = mag(width,height); float maxmins = rmax / pixelsPerMinute; //println(maxmins); int maxminsround = int(ceil(maxmins/10.0)*10.0); //println(maxminsround); strokeWeight(2.0); for (int i=maxminsround; i > 0; i-=10) { float prop = pow(float(i-10)/(maxminsround-10),0.5); fill(255 - (128*prop),255 - (128*prop),255 - (64*prop)); //stroke(255 - (64*prop),255 - (64*prop),255 - (32*prop)); noStroke(); float r = i*pixelsPerMinute; ellipse(ellipseCentre.targetx,ellipseCentre.targety,2*r,2*r); } ellipses = get(0,0,width,height); } } if (ellipseCentre != null) { noStroke(); // noSmooth(); fill(255); textureMode(IMAGE); for (int tt=0; tt 1) { stroke(0); fill(255); ellipse(s.mapx,s.mapy,5.0,5.0); } } if (selected != null) { stroke(255,255,0); fill(255,255,0); ellipse(selected.mapx,selected.mapy,5.0,5.0); String label = selected.name; if (selected.timeToCentre != 0 && showTimeBands) { label += " (" + selected.timeToCentre + " minutes travel)"; } textFont(labelFont,10); rectMode(CENTER); fill(200); noStroke(); triangle(selected.mapx,selected.mapy,selected.mapx+5,selected.mapy-6,selected.mapx+5,selected.mapy+6); fill(255); stroke(200); rect(selected.mapx+(textWidth(label)/2)+8,selected.mapy,textWidth(label)+4,16); fill(0); textAlign(LEFT); text(label,int(selected.mapx+8),int(selected.mapy+4)); } stroke(0); strokeWeight(1.0); pushMatrix(); translate(width/20,height-(width/20)); textAlign(LEFT); 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",int(-textWidth("0")/2),15); line(5.0/(float)mercator.kilometerinpixels(),0.0,5.0/(float)mercator.kilometerinpixels(),5.0); text("5",int(5/(float)mercator.kilometerinpixels()-textWidth("5")/2),15); line(10.0/(float)mercator.kilometerinpixels(),0.0,10.0/(float)mercator.kilometerinpixels(),5.0); text("10",int(10/(float)mercator.kilometerinpixels()-textWidth("10")/2),15); line(0.0,5.0,10.0/(float)mercator.kilometerinpixels(),5.0); text("kilometers",int(5.0/(float)mercator.kilometerinpixels())-int(textWidth("kilometers")/2),25); // } popMatrix(); if (showTimeBands) { pushMatrix(); translate(width-(width/20)-pixelsPerMinute*45,height-(height/20)-20); rectMode(CORNER); float rmax = mag(width,height); for (float r = 0; r < pixelsPerMinute*90; r += pixelsPerMinute*10) { float prop = pow(r/rmax,0.5); fill(255 - (128*prop),255 - (128*prop),255 - (64*prop)); stroke(255 - (64*prop),255 - (64*prop),255 - (32*prop)); rect(r/2,0,pixelsPerMinute*5,10); } fill(0); for (int i = 0; i <= 90; i+=10) { text(""+i,int(i*pixelsPerMinute/2-textWidth(""+i)/2),20); } text("minutes to travel",int(45*pixelsPerMinute/2)-int(textWidth("minutes to travel")/2),30); popMatrix(); } textAlign(LEFT); textFont(headerFont,16); String t = ""; if (showTimeBands) { t = "Time to Travel from " + ellipseCentre.name; } else { t = "Geographic Tube Map (click a station for travel time map)"; } stroke(255,64); fill(255,128); rectMode(CENTER); rect(width/2,-4+width/20,8+textWidth(t),24); fill(0); text(t,int((width-textWidth(t))/2.0),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; } ellipseCentre = null; ellipses = null; 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); } } } } void parseStations() { String strings[] = loadStrings("stations.csv"); for (int i = 1; i < strings.length; i++) { // stations.csv: "id","latitude","longitude","name","display_name","zone","total_lines","rail" String words[] = split(strings[i],","); if (words.length > 8) { // glue things back together for "Heathrow Terminals 1, 2 & 3" (and curse the comma!) while (!words[3].endsWith("\"")) { words[3] += "," + words[4]; arraycopy(words,5,words,4,words.length-5); shorten(words); } while (!words[4].endsWith("\"")) { words[4] += "," + words[5]; arraycopy(words,6,words,5,words.length-6); shorten(words); } } int id = int(words[0]); double lat = Double.parseDouble(words[1]); double lon = Double.parseDouble(words[2]); String name = words[3].substring(1,words[3].length()-1); String displayName = words[4]; float zone = float(words[5]); int totalLines = int(words[6]); int rail = int(words[7]); stations.put(new Integer(id), new Station(id, lon, lat, name, displayName, zone, totalLines, rail)); } /* Vector statIndex = new Vector(); Iterator keyIter = stations.keySet().iterator(); while(keyIter.hasNext()) { Station s = (Station)stations.get(keyIter.next()); statIndex.add(s.name + ":" + s.id); } Collections.sort(statIndex); String[] stringsToWrite = new String[statIndex.size()]; for (int i = 0; i < stringsToWrite.length; i++) { String s = (String)statIndex.get(i); String[] words = split(s,":"); stringsToWrite[i] = ""; } saveStrings("station-links.html",stringsToWrite); */ } //static public void main(String args[]) { // PApplet.main(new String[] { "--present", "tube_map_travel_times_texture_grid" }); //}