//created=27 Jan 08
//description=GPS paths registered on USGS elevation data
//title=Yosemite
//publish=false
//titlebar=3D GPS paths on USGS elevation data
 
 
import Tuple.*;
import SGCamera.*;
import SGBackground.*;
 
SGCamera cam;
 
HikePoint [] hike;
String [] txtlog;
 
Sky sky;
 
PImage tex;
 
float scalor = 1000;
float heightScale = 80.f;
float demWide, demHigh;
 
int step = 1;
 
float seaLevel = 100;
 
int which = 0;
 
float lx = -119.577080;
float ly = 37.710972;
float rx = -119.507919;
float uy = 37.760414;
 
//divide by two because we're using a hlaf resolution map to reduce download times.
int nrows = 179/2;
int ncols = 250/2;
 
float [][] hts; //heightmap
 
PImage hmap = new PImage(nrows,ncols); //texture to overlay
 
PFont font;
 
int running = 0;
int hide = 0;
 
void setup(){
 
float delta= (2685-1208); //I forgot what this does ... I should comment *as* I code, not later.
 
demWide = (lx - rx)/ncols;
demHigh = (uy - ly)/nrows;
size(550,550,P3D);
frameRate(30);
 
font = loadFont("writing.vlw");
tex = loadImage("yosemite.jpg");
 
String [] heights = loadStrings("yosemite.txt");
 
 
StringTokenizer ht = new StringTokenizer(heights[0]);
hts = new float[ncols][nrows];
for (int j =0; j < nrows; j++){
for (int i =0; i < ncols; i++){
hts[ncols-i-1][j] = new Float(ht.nextToken()).floatValue();
 
}
 
}
 
/*
//this is the code to create a simplified version of a hires map
StringBuffer simple = new StringBuffer();
for (int j =0; j < nrows; j+=2){
for (int i =0; i < ncols; i+=2){
simple.append(hts[ncols-i-1][j] + " ");
}
}
String[] out = new String[1];
out[0] = simple.toString();
saveStrings("bob.txt",out);
*/
 
 
//funtime!
 
//load the mapsource file and extract the time and position strings
String [] mapSource = loadStrings("hike.txt");
hike = new HikePoint[mapSource.length];
for (int i = 0; i < mapSource.length; i++){
 
String [] tokens = mapSource[i].split("\t");
hike[i] = new HikePoint(tokens[1],tokens[2],tokens[9]);
 
}
 
//now, we need to run through the hike and segment by time I turned the GPS off
for (int i=0; i < hike.length-1; i++){
 
if(hike[i].distanceSquared(hike[i+1]) > 2){
hike[i].endPoint = true;
}
 
}
 
int mid = hike.length/2;
 
//cam = new SGCamera(this,hike[mid].x+50,hike[mid].z,hike[mid].y-50,hike[mid].x,hike[mid].z,hike[mid].y);
//let's just hardcode some values in here...
cam = new SGCamera(this,37742,-35,-119501,hike[mid].x,hike[mid].z,hike[mid].y);
cam.setMouseMode(LEFT,SGCamera.ORBIT);
cam.setMouseMode(RIGHT,SGCamera.DOLLY);
cam.setVerticalLimits(0,1.6);
cam.setFOV(1.5);
 
//grid is high enough res that we do not need this.
//hint(g.ENABLE_ACCURATE_TEXTURES);
//hint(ENABLE_DEPTH_SORT);
((PGraphics3D)g).triangle.setCulling(true);
 
 
//traverse the hike and append log entries
int currLog = 0;
txtlog = loadStrings("log.txt");
String [] logChunk = txtlog[currLog].split("@");
 
Date logTime = parseTime(logChunk[0]);
 
for (int i =0; i < hike.length; i++){
if (logTime.compareTo(hike[i].gpsTime) > 0 && logTime.compareTo(hike[i+1].gpsTime) < 0){
hike[i].setText(logChunk[1],logChunk[2]);
 
currLog++;
if (currLog < txtlog.length){
logChunk = txtlog[currLog].split("@");
logTime = parseTime(logChunk[0]);
}
}
}
 
sky = new Sky(this,cam);
sky.setTurbidity(1.9f);
 
}
 
 
float fixHeight(float lat, float lon, float alt){
 
float bLat = abs((ly-lat)/demWide);
float bLon = abs((rx-lon)/demHigh);
 
int bx = (int)bLat;
int by = (int)bLon;
 
float remX = bLat - bx;
float remY = bLon - by;
 
//interpolate along both x edges, and then do a y interpolation between those two results.
float ex1 = lerp(hts[by][bx], hts[by][bx+1],remX);
float ex2 = lerp(hts[by+1][bx], hts[by+1][bx+1],remX);
 
return -(lerp(ex1,ex2,remY))-5;
 
 
}
 
void draw(){
 
if (running != 0){
for (int i=0; i < 2; i++){
which+=running;
which = (int)constrain(which,0,hike.length-1);
if (hike[which].logText != null){
running = 0;
hide = 0;
break;
}
}
}
sky.draw(30);
cam.feed();
 
float sx = screenX(hike[which].x,hike[which].z,hike[which].y);
float sy = screenY(hike[which].x,hike[which].z,hike[which].y);
 
unhint(DISABLE_DEPTH_TEST);
 
stroke(255,255,0);
noFill();
 
int h =0;
//render hike tracks. Skip from endpoint to endpoint.
while(h < hike.length){
beginShape();
for ( ; h < hike.length; h++){
vertex(hike[h].x,hike[h].z,hike[h].y);
if (hike[h].endPoint){
h++;
break;
}
}
endShape();
}
 
noStroke();
fill(255,255,255,100);
textureMode(NORMALIZED);
 
translate(ly*scalor,0,rx*scalor);
 
float fcols = (float)ncols;
float frows = (float)nrows;
 
for (int j = 0; j < nrows-step; j += step){
beginShape(QUAD_STRIP);
for (int i = 0; i < ncols-step; i+=step){
texture(tex);
vertex(scalor*(j+step)*demHigh, -hts[i][j+step]/heightScale , scalor*(i)*demWide,1-(i)/fcols,1-(j+step)/frows);
vertex(scalor*(j)*demHigh, -hts[i][j]/heightScale , scalor*(i)*demWide,1-(i)/fcols,1-(j)/frows);
}
endShape();
}
 
//ensure we are looking at the current hike location
cam.lookAt(hike[which].x,hike[which].z,hike[which].y);
 
//use the GPS time to move the sky to the appropriate angle
Calendar cal = new GregorianCalendar();
cal.setTime(hike[which].gpsTime);
sky.setSunByTime(0,hike[which].getLon(),cal.get(Calendar.DAY_OF_YEAR),hike[which].gpsTime.getHours() + hike[which].gpsTime.getMinutes()/60.f ,-8);
 
//move to screenspace
camera();
perspective();
 
 
 
fill(255,255,255,200);
noStroke();
textFont(font);
textMode(SCREEN);
hint(DISABLE_DEPTH_TEST);
if (hide == 0){
int bx = 260;
int by = 10;
int border = 5;
int gutter = 10;
 
if (hike[which].img != null){
bx = hike[which].img.width + 2*border;
by = hike[which].img.height + 2*border;
}
 
gutter += 15*hike[which].txtHeight + border;
 
gutter += 15;
 
int lx = 5;
int ly = 5;
int wd = 30;
 
//arrow pointing to the hike location
beginShape();
vertex(sx,sy);
vertex(lx + bx - 60,ly + by + gutter);
vertex(lx + bx - 60 - wd,ly + by + gutter);
endShape();
 
//box around the text and picture
beginShape();
vertex(lx,ly);
vertex(lx + bx,ly);
vertex(lx + bx,ly + by + gutter);
vertex(lx,ly + by + gutter);
endShape();
 
fill(0);
 
//time
text("" + hike[which].gpsTime,lx + 10,ly + by + 10);
 
//caption
if (hike[which].logText != null) {
text("" + hike[which].logText,lx + 10,ly + by + 25);
}
 
//image
if (hike[which].img != null){
image(hike[which].img,lx + 5,ly + 5);
}
}
else{
fill(255);
 
beginShape();
vertex(sx,sy);
vertex(sx+5,sy-20);
vertex(sx-5,sy-20);
endShape();
 
}
}
 
void keyPressed(){
if (key == 'a') step++;
if (key == 'z') step--;
if (key == 's') running = 1;
if (key == 'x') running = -1;
if (step < 1) step = 1;
if (key == ' ' ) hide = 1-hide;
if (key == 'g') which++;
}
 
Date parseTime(String time){
 
Date date = null;
//parse the time of the point
try {
DateFormat formatter = new SimpleDateFormat("M/d/y hh:mm:ss a");
date = (Date)formatter.parse(time);
 
}
catch (ParseException e) {
println(e);
}
return date;
}
class HikePoint extends Tuple3f{
Date gpsTime;
public boolean endPoint = false;
String logText = null;
PImage img = null;
int txtHeight = 0;
public float getLat(){
return x/scalor;
}
public float getLon(){
return y/scalor;
}
public HikePoint(String time, String alt, String lonlat){
//parse the time of the point
try {
DateFormat formatter = new SimpleDateFormat("M/d/y hh:mm:ss a");
gpsTime = (Date)formatter.parse(time);
 
} catch (ParseException e) {
println(e);
}
z = -new Float(alt.substring(0,alt.length()-2)).floatValue();
//convert feet to meters
z *= .3048;
 
String [] coords = lonlat.split(" ");
x = new Float(coords[0].substring(1,coords[0].length())).floatValue();
y = new Float(coords[1].substring(1,coords[1].length())).floatValue();
 
//account for global coordinate flop
if (coords[1].charAt(0) == 'W') y = -y;
if (coords[0].charAt(0) == 'S') x = -x;
 
z = fixHeight(x, y, z);
 
x *= scalor;
y *= scalor;
z /= heightScale;
 
 
 
}
 
void setText(String txt, String imgName){
 
 
if (!imgName.equals("none")){
println(imgName);
img = loadImage( imgName);
}
StringTokenizer st = new StringTokenizer(txt," ");
StringBuffer ret = new StringBuffer("");
//paragraphize the string. I expect to see this on dailywtf soon,
//but only took 2 minutes and works.
String nxt;
int sub = 0;
int charWide = 40;
if (img != null) charWide = img.width/6;
while(st.hasMoreTokens()){
nxt = st.nextToken();
 
if (ret.length()-sub + nxt.length() > charWide){
sub += charWide;
ret.append("\n");
txtHeight++;
}
else{
ret.append(" ");
}
ret.append(nxt);
}
logText = ret.toString();
 
}
 
}