Processing sketch:
//lat,lon,alt,date,device,os,version import processing.opengl.*; import codeanticode.glgraphics.*; import de.fhpotsdam.unfolding.*; import de.fhpotsdam.unfolding.geo.*; import de.fhpotsdam.unfolding.utils.*; import de.fhpotsdam.unfolding.providers.*; import de.fhpotsdam.unfolding.events.EventDispatcher; import de.fhpotsdam.unfolding.interactions.MouseHandler; de.fhpotsdam.unfolding.Map map; import java.util.*; import java.text.SimpleDateFormat; ArrayList<Visit> visits = new ArrayList(); //MercatorMap mercatorMap; void setup() { size(1000, 1000, GLConstants.GLGRAPHICS); String connStr = "jdbc:sqlite:" + ("/Users/federicozannier/mySchool/ITPSpring2013/DataRep/07_week/data/openpaths.mbtiles"); map = new de.fhpotsdam.unfolding.Map(this, new MBTilesMapProvider(connStr)); MapUtils.createDefaultEventDispatcher(this, map); EventDispatcher eventDispatcher = new EventDispatcher(); MouseHandler mouseHandler = new MouseHandler(this, map); eventDispatcher.addBroadcaster(mouseHandler); eventDispatcher.register(map, "zoom", map.getId()); map.setZoomRange(0, 8); loadOpenPaths("../data/openpaths_fezannier.csv"); setTime(); } void draw() { background(0); map.draw(); Location mapTopLeft = map.getTopLeftBorder(); Location mapBottomRight = map.getBottomRightBorder(); println(mapTopLeft); println(mapBottomRight); placeVisits(mapTopLeft, mapBottomRight); float tF = (float) frameCount % 1000; tF = map(tF,0,1000,0,1); stroke(255,0,0); line(tF*width,0,tF*width,height); String timestamp = ""; for(Visit v:visits) { if(v.timeFraction < tF) { v.update(); v.render(); timestamp = v.timeString; } } fill(255,0,0); text(timestamp, 10, 10, 200, 80); // Text wraps within text box } void loadOpenPaths(String url) { Table t = new Table(url); for(int i = 1; i < t.getRowCount(); i++) { Visit v = new Visit(); v.lonlat.x = t.getFloat(i,1); v.lonlat.y = t.getFloat(i,0); v.alt = t.getFloat(i,2); v.timeString = t.getString(i,3); v.init(); visits.add(v); println(v.time); } } void placeVisits(PVector tl, PVector br) { for(Visit v:visits) { MercatorMap mercatorMap = new MercatorMap(1000, 1000, tl.x, br.x, tl.y, br.y); v.tpos = mercatorMap.getScreenLocation(new PVector(v.lonlat.y, v.lonlat.x)); } } void setTime() { Date startDate = visits.get(0).time; Date endDate = visits.get(visits.size() - 1).time; println(startDate.getTime() + "--" + endDate.getTime()); for(Visit v:visits) { long startTime = startDate.getTime(); long endTime = endDate.getTime(); long visitTime = v.time.getTime(); v.timeFraction = ((float)(visitTime - startTime))/((float)(endTime-startTime)); } } /** * Utility class to convert between geo-locations and Cartesian screen coordinates. * Can be used with a bounding box defining the map section. * * (c) 2011 Till Nagel, tillnagel.com */ public class MercatorMap { public static final float DEFAULT_TOP_LATITUDE = 80; public static final float DEFAULT_BOTTOM_LATITUDE = -80; public static final float DEFAULT_LEFT_LONGITUDE = -180; public static final float DEFAULT_RIGHT_LONGITUDE = 180; /** Horizontal dimension of this map, in pixels. */ protected float mapScreenWidth; /** Vertical dimension of this map, in pixels. */ protected float mapScreenHeight; /** Northern border of this map, in degrees. */ protected float topLatitude; /** Southern border of this map, in degrees. */ protected float bottomLatitude; /** Western border of this map, in degrees. */ protected float leftLongitude; /** Eastern border of this map, in degrees. */ protected float rightLongitude; private float topLatitudeRelative; private float bottomLatitudeRelative; private float leftLongitudeRadians; private float rightLongitudeRadians; public MercatorMap(float mapScreenWidth, float mapScreenHeight) { this(mapScreenWidth, mapScreenHeight, DEFAULT_TOP_LATITUDE, DEFAULT_BOTTOM_LATITUDE, DEFAULT_LEFT_LONGITUDE, DEFAULT_RIGHT_LONGITUDE); } /** * Creates a new MercatorMap with dimensions and bounding box to convert between geo-locations and screen coordinates. * * @param mapScreenWidth Horizontal dimension of this map, in pixels. * @param mapScreenHeight Vertical dimension of this map, in pixels. * @param topLatitude Northern border of this map, in degrees. * @param bottomLatitude Southern border of this map, in degrees. * @param leftLongitude Western border of this map, in degrees. * @param rightLongitude Eastern border of this map, in degrees. */ public MercatorMap(float mapScreenWidth, float mapScreenHeight, float topLatitude, float bottomLatitude, float leftLongitude, float rightLongitude) { this.mapScreenWidth = mapScreenWidth; this.mapScreenHeight = mapScreenHeight; this.topLatitude = topLatitude; this.bottomLatitude = bottomLatitude; this.leftLongitude = leftLongitude; this.rightLongitude = rightLongitude; this.topLatitudeRelative = getScreenYRelative(topLatitude); this.bottomLatitudeRelative = getScreenYRelative(bottomLatitude); this.leftLongitudeRadians = getRadians(leftLongitude); this.rightLongitudeRadians = getRadians(rightLongitude); } /** * Projects the geo location to Cartesian coordinates, using the Mercator projection. * * @param geoLocation Geo location with (latitude, longitude) in degrees. * @returns The screen coordinates with (x, y). */ public PVector getScreenLocation(PVector geoLocation) { float latitudeInDegrees = geoLocation.x; float longitudeInDegrees = geoLocation.y; return new PVector(getScreenX(longitudeInDegrees), getScreenY(latitudeInDegrees)); } private float getScreenYRelative(float latitudeInDegrees) { return log(tan(latitudeInDegrees / 360f * PI + PI / 4)); } protected float getScreenY(float latitudeInDegrees) { return mapScreenHeight * (getScreenYRelative(latitudeInDegrees) - topLatitudeRelative) / (bottomLatitudeRelative - topLatitudeRelative); } private float getRadians(float deg) { return deg * PI / 180; } protected float getScreenX(float longitudeInDegrees) { float longitudeInRadians = getRadians(longitudeInDegrees); return mapScreenWidth * (longitudeInRadians - leftLongitudeRadians) / (rightLongitudeRadians - leftLongitudeRadians); } } class Table { String[][] data; int rowCount; Table() { data = new String[10][10]; } Table(String filename) { String[] rows = loadStrings(filename); data = new String[rows.length][]; for (int i = 0; i < rows.length; i++) { if (trim(rows[i]).length() == 0) { continue; // skip empty rows } if (rows[i].startsWith("#")) { continue; // skip comment lines } // split the row on the tabs String[] pieces = split(rows[i], ','); // copy to the table array data[rowCount] = pieces; rowCount++; // this could be done in one fell swoop via: //data[rowCount++] = split(rows[i], TAB); } // resize the 'data' array as necessary data = (String[][]) subset(data, 0, rowCount); } int getRowCount() { return rowCount; } // find a row by its name, returns -1 if no row found int getRowIndex(String name) { for (int i = 0; i < rowCount; i++) { if (data[i][0].equals(name)) { return i; } } println("No row named '" + name + "' was found"); return -1; } String getRowName(int row) { return getString(row, 0); } String getString(int rowIndex, int column) { return data[rowIndex][column]; } String getString(String rowName, int column) { return getString(getRowIndex(rowName), column); } int getInt(String rowName, int column) { return parseInt(getString(rowName, column)); } int getInt(int rowIndex, int column) { return parseInt(getString(rowIndex, column)); } float getFloat(String rowName, int column) { return parseFloat(getString(rowName, column)); } float getFloat(int rowIndex, int column) { return parseFloat(getString(rowIndex, column)); } void setRowName(int row, String what) { data[row][0] = what; } void setString(int rowIndex, int column, String what) { data[rowIndex][column] = what; } void setString(String rowName, int column, String what) { int rowIndex = getRowIndex(rowName); data[rowIndex][column] = what; } void setInt(int rowIndex, int column, int what) { data[rowIndex][column] = str(what); } void setInt(String rowName, int column, int what) { int rowIndex = getRowIndex(rowName); data[rowIndex][column] = str(what); } void setFloat(int rowIndex, int column, float what) { data[rowIndex][column] = str(what); } void setFloat(String rowName, int column, float what) { int rowIndex = getRowIndex(rowName); data[rowIndex][column] = str(what); } // Write this table as a TSV file void write(PrintWriter writer) { for (int i = 0; i < rowCount; i++) { for (int j = 0; j < data[i].length; j++) { if (j != 0) { writer.print(TAB); } if (data[i][j] != null) { writer.print(data[i][j]); } } writer.println(); } writer.flush(); } } class Visit { String timeString; Date time; float timeFraction; PVector lonlat = new PVector(); Float alt; PVector pos = new PVector(); PVector tpos = new PVector(); void init() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { time = sdf.parse(timeString); } catch( Exception e) { println("ERROR PARSING DATE " + e); } } void update() { pos.x = tpos.x; pos.y = tpos.y; pos.z = tpos.z; } void render() { noStroke(); fill(255,0,0,50); pushMatrix(); translate(pos.x,pos.y,pos.z); ellipse(-2.5,-2.5,5,5); popMatrix(); } }