/* Copyright (C) 2010 Michael Kosowsky  All rights reserved. */

					//************************** GENERIC UI

var DR_NONE      = 0;
var DR_SMOOTH    = 1;
var DR_PIECEWISE = 2;

var current = {
  magnetic: false,
  points: [],
  total_dist: 0,
  latlon_mode: 0,
  show_grade: 0,
  show_rise: 0,
  include_grade: 0,
  use_metric: 0,
  elev_source: 0,
  decimal_places: 0,
  driving_route: 0,
  print: 0,
  print_comments: 0
};


var colors = [ 'ff0000', '00c000', '0000ff' ];

function backspace() {
  if (current.points.length == 0)
    return;
  if (current.points.length == 1) {
    clear();
    return;
  }
  var p = current.points.pop();
  map.removeOverlay(p.marker);
  if (p.leg) {
    remove_leg_lines(p);
    current.total_dist -= p.leg.dist;
  }
  write_list_div();
  draw_transects();
}

function clear() {
  current.points._foreach(function(p) { if (p.marker) map.removeOverlay(p.marker); if (p.leg) remove_leg_lines(p); });
  current.points = [];
  current.total_dist = 0;
  write_list_div();
}


function show_faq() {
  window.open('faq.html', '_faq', 'height=600,width=500,scrollbars');
}

function show_help_0904() {
  window.open('testfaq.html', '_faq', 'height=600,width=500,scrollbars');
}

function email_us() {
  location.href = 'mailto:comments-0904-profiler@hey' + '' + 'what' + 'sthat.com';
}

function set_show_grade(x) {
  current.show_grade = x;
  document.getElementById('show_grade').checked = x;
  reset_current_points();
}

function set_show_rise(x) {
  current.show_rise = x;
  document.getElementById('show_rise').checked = x;
  reset_current_points();
}

function set_include_grade(x) {
  current.include_grade = x;
  document.getElementById('include_grade').checked = x;
  reset_current_points();
}

function set_driving_route(x, piecewise) {
  current.driving_route = x? piecewise? DR_PIECEWISE : DR_SMOOTH : DR_NONE;
  document.getElementById('driving_route').checked = current.driving_route == DR_SMOOTH;
  document.getElementById('driving_route_piecewise').checked = current.driving_route == DR_PIECEWISE;
}

function set_latlon_mode(x) {
  current.latlon_mode = x;
   // same cookie as main site index.html ('df' is 'degrees_format')
  set_cookie('df', x);
  update_latlon_div();
  reset_current_points();
}

function set_metric(x) {
  current.use_metric = x;
  set_cookie('units', x);
  radio_set(units_radio, x);
  reset_current_points();
  redraw_contour_overlay();
}

function set_print(p) {
  current.print = p;
  document.getElementById('print').style.display                = p? '' : 'none';
  document.getElementById('nonprint').style.display             = p? 'none' : '';
  document.getElementById('listbutton_div').style.display       = p? 'none' : '';
  document.getElementById('list_pane_form').style.display       = p? 'none' : '';
  reset_current_points();  // in case we need comments fields
}


function handle_map_click(overlay, point) {
  if (!point)
    return;
  add_point_drv(point.y, point.x);
  draw_transects();
}

function reset_current_points() {
  var o = current.points;
  clear();
  o._foreach(function(p) { add_point(p.lat, p.lon, p.address, p.leg, p.elev_data, p.elev_override, p.elev_relative, p.pending_route) });
  draw_transects();
}


function add_point_drv(lat, lon, address) {
  add_point(lat, lon, address, null, null, null, null, current.driving_route);
  if (current.driving_route == DR_SMOOTH)
    get_route(1, current.points[current.points.length - 1]);
  if (current.driving_route == DR_PIECEWISE)
    get_route(0, current.points[current.points.length - 1]);
}


function add_point(lat, lon, address, leg, elev_data, elev_override, elev_relative, pending_route) {
  lat = round6(lat);
  lon = round6(lon);
  var latlng = new GLatLng(lat, lon);

  var p = { lat:           lat,
	    lon:	   lon,
	    latlng:        latlng,
	    address:	   address,
	    elev_data:     elev_data,
	    elev_override: elev_override,
	    elev_relative: elev_relative,
	    pending_route: pending_route,
	    marker:        editing_3d()? null : new GMarker(latlng, { icon: icon_plus, clickable: false, title: address? address : lat_lon_to_string(lat, lon, 0) })
	  };
  set_elev(p);
  p.html = point_html(p);

  if (current.points.length > 0) {
    var prev = current.points[current.points.length - 1];
    p.prev = prev;

	// bearing_and_range returns 0 if less than a few cm. separation.  if so, abort the point
    var b = bearing_and_range(prev.lat, prev.lon, lat, lon);
    if (b[1] == 0)
      return;

    var altitude = null;
    if (p.elev != null && prev.elev != null) {
	  // calculate altitude. assumes refraction coefficient .14
      var psi      = b[1] / EARTH_RADIUS;
      var cospsi   = Math.cos(psi);
      var sinpsi   = Math.sin(psi);
      var altitude = 1/DEGREES_TO_RADIANS
                      * (-Math.atan2( ((prev.elev + EARTH_RADIUS) - cospsi * (p.elev + EARTH_RADIUS)),
				                                   (sinpsi * (p.elev + EARTH_RADIUS)) )
                         + .14*psi/2);
    }
    var run  =   (leg && leg.steps && leg.steps.length > 2)? route_run(leg.steps)
							   : b[1];
    var dist =   !current.include_grade?                     run
               : (leg && leg.steps && leg.steps.length > 2)? route_hypotenuse(leg.steps)
               :					     Math.sqrt(b[1] * b[1] + (p.elev - prev.elev) * (p.elev - prev.elev));
    current.total_dist += dist;
    var rb   = bearing_and_range(lat, lon, prev.lat, prev.lon);

    p.leg = { color:           leg?              leg.color : colors[(current.points.length - 1) % colors.length],
	      steps:           leg && leg.steps? leg.steps : [ prev.latlng, p.latlng ],
	      dist:            dist,
              run:             run,
 	      total_dist:      current.total_dist,
	      bearing:         b[0],
	      reverse_bearing: rb[0],
	      altitude:        altitude,
	      r_f:	       leg? leg.r_f : null
    };

    if (current.show_rise) {
      if (p.leg.r_f == null) {

		// create object for the results of the async call to rise.cgi
		// (so it will stick around between calls to reset_current_points)
        p.leg.r_f = { rise: null, fall: null };
        p.leg.total_rise = null;
        p.leg.total_fall = null;
        get_rise_and_fall(p.leg.r_f, prev, p);

      } else {

        if (!prev.leg) {
          p.leg.total_rise = p.leg.r_f.rise;
          p.leg.total_fall = p.leg.r_f.fall;
        } else if (prev.leg.total_rise == null || p.leg.r_f.rise == null) {
  	  p.leg.total_rise = null;
  	  p.leg.total_fall = null;
        } else {
          p.leg.total_rise = prev.leg.total_rise + p.leg.r_f.rise;
          p.leg.total_fall = prev.leg.total_fall + p.leg.r_f.fall;
        }

      }
    }

    p.leg.html = leg_html(p);
    add_leg_lines(p);
  }

  current.points.push(p);
  if (p.marker)
    map.addOverlay(p.marker);
  write_list_div();
}


function point_html(p) {
  return    (p.address? '<tr><td colspan=4><i>' + p.address + '</i></td></tr>' : '')
          + '<tr align=right>'
          + '<td>' + format_angle(p.lat, current.latlon_mode, 'N', 'S', 1) + '</td>'
          + '<td>' + format_angle(p.lon, current.latlon_mode, 'E', 'W', 1) + '</td>'
					// HACK using lat and lon to find ourselves if they click on elevation
	  + '<td><span class="listbutton" onclick="get_elev_override(' + p.lat + ',' + p.lon + ')">'
          +   (p.elev != null? feet_or_meters(p.elev) : '-')
          + '</span></td>'
          +   (p.elev_override != null && p.elev_data != null? 
			'<td>(+' + feet_or_meters_no_label(p.elev_override - p.elev_data) + ')</td>' : '')
	  + (current.print && current.print_comments? '<td><input type=text size=20></input></td>' : '')
	  + '</tr>';
}


function leg_html(p) {
  var l = p.leg;
  if (!l)
    return '';
  var start_elev = current.points[0].elev;
  var prev_elev  = p.prev.elev;
  var next_elev  = p.elev;

  return   '<tr align=right style="color: #' + l.color + '">'
         + '<td title="reverse bearing ' + round_bearing(adjust_for_declination(l.reverse_bearing)) + '">'
            + round_bearing(adjust_for_declination(l.bearing)) + '&deg;</td>'
         + '<td>' + miles_or_km1(l.dist) + '</td>'
	 + '<td>' + miles_or_km0_no_label(l.total_dist) + '&nbsp;total</td></tr>'
	 + (current.show_grade && next_elev != null && start_elev != null && prev_elev != null?
	        '<tr align=right style="color: #' + l.color + '">' 
              + '<td>' + feet_or_meters(next_elev - prev_elev)
                + ' ('
                  + percent1((next_elev - prev_elev)/l.run)
		  + (l.altitude != null? ' ' + degrees1(l.altitude) : '')
                + ')</td>'
	      + '<td>' + feet_or_meters_no_label(next_elev - start_elev) + '&nbsp;total</td></tr>'
            : '')
	 + (current.show_rise && p.leg.r_f.rise != null?
	        '<tr align=right style="color: #' + l.color + '">' 
              + '<td>' + feet_or_meters_no_label(p.leg.r_f.rise) + '/' + feet_or_meters(p.leg.r_f.fall) + '</td>'
	      + (p.leg.total_rise != null?
		  '<td>' + feet_or_meters_no_label(p.leg.total_rise) + '/' + feet_or_meters_no_label(p.leg.total_fall) + '&nbsp;total</td>'
		: '')
            : '');
}


function add_leg_lines(p) {
  var l = p.leg;
  if (!l)
    return;
  l.line           = editing_3d()? null : new GPolyline(l.steps, '#' + l.color, 3, .5);
  l.google_gc_line = editing_3d()? null : new GPolyline(l.steps, 'purple', 1, .5, { geodesic: true })
  if (l.line)
    map.addOverlay(l.line);
  if (l.google_gc_line)
    map.addOverlay(l.google_gc_line);
}

function remove_leg_lines(p) {
  var l = p.leg;
  if (l) {
    if (l.line) map.removeOverlay(l.line);
    if (l.google_gc_line) map.removeOverlay(l.google_gc_line);
    //if (l.wt_gc_line) l.wt_gc_line._foreach(function(m) { map.removeOverlay(m) });
    l.line = null;
    l.google_gc_line = null;
  }
}


function add_steps(p, steps) {
  remove_leg_lines(p);
  var l    = p.leg;
  l.steps  = steps;
  l.run    = route_run(steps);
  var dist = current.include_grade? route_hypotenuse(steps) : l.run;
  l.total_dist += dist - l.dist;
  l.dist   = dist;
  get_rise_and_fall(p.leg.r_f, p.prev, p);
  add_leg_lines(p);
  update_legs(p);
}


function update_legs(start) {
  var i;

  if (start) {
    current.total_dist = start.leg.total_dist;
    //current.total_dist_is_true = start.total_dist_is_true;
    for (i = 0; i < current.points.length && current.points[i] != start; i++) ;
    i++;
  } else {
    current.total_dist = 0;
    //current.total_dist_is_true = true;
    i = 0;
  }
  for (; i < current.points.length; i++) {
    var p = current.points[i];
    var l = p.leg;
    current.total_dist += l.dist;
    //current.total_dist_is_true &= l.dist_is_true;
    l.total_dist = current.total_dist;
    //l.total_dist_is_true = current.total_dist_is_true;
    l.html = leg_html(p);
  }
  write_list_div();
}


function route_run(a) {
  var t = 0;
  for (var i = 0; i < a.length - 1; i++) {
    var b = bearing_and_range(a[i].lat(), a[i].lng(), a[i+1].lat(), a[i+1].lng());
    t += b[1];
  }
  return t;
}

function route_hypotenuse(a) {
  var t = 0;
  for (var i = 0; i < a.length - 1; i++) {
    var b = bearing_and_range(a[i].lat(), a[i].lng(), a[i+1].lat(), a[i+1].lng());
    var y = a[i+1].elev() - a[i].elev();
    t += Math.sqrt(b[1] * b[1] + y * y);
  }
  return t;
}

function get_route(smooth, p) {
  if (p == null || p == current.points[0])
    return;
	// can't trust that the point will still exist when route comes back, so we'll have to search for it
  var lat0 = p.prev.lat;
  var lon0 = p.prev.lon;
  var lat1 = p.lat;
  var lon1 = p.lon;
  wt_async_request_array_of_lines('/bin/directions.cgi?max=50'
                                    + '&elevs=1'
                                    + '&q=from+' + p.prev.lat + ',' + p.prev.lon + '+to+' + p.lat + ',' + p.lon,
		                  'Directions',
				  function(a) { parse_directions(smooth, lat0, lon0, lat1, lon1, a) });
}


function parse_directions(smooth, lat0, lon0, lat1, lon1, a) {
  var p = find_point_needing_route(smooth? DR_SMOOTH : DR_PIECEWISE, lat0, lon0, lat1, lon1);
	// BUG: relax requirement that it be final point!
  if (!p || (!smooth && p != current.points[current.points.length - 1]))
    return;
  p.pending_route = DR_NONE;

  if (a.length > 100)
    return;
  if (a.length < 3) {
    alert('No driving route available');
    return;
  }

					// replace start and end with the points we requested
					// (sometimes Google directions don't quite hit the endpoints)
					// and make sure no segments are too short
					// (in particular, this means when adding final point, back up through the array)
  a.pop(); a.unshift();
  var lles = [ [lat0, lon0, p.prev.elev] ];
  var lastlat = lat0;
  var lastlon = lon0;
  a._foreach(function(b) {
     var lle = b.split(' ');
     lle[0] = lle[0] - 0;
     lle[1] = lle[1] - 0;
     lle[2] = lle[2] - 0;
     if (!near(lastlat, lastlon, lle[0], lle[1])) {
       lles.push(lle);
       lastlat = lle[0];
       lastlon = lle[1];
     }
  });
  for (;;) {
    var i = lles.length - 1;
    if (i < 1) {
      alert('No driving route available');
      return;
    }
    if (!near(lat1, lon1, lles[i][0], lles[i][1]))
      break;
    lles.pop();
  }

  if (smooth) {
    steps = [];
    lles.push([p.lat, p.lon, p.elev]);
    lles._foreach(function(lle) { steps.push(new LatLngElev(lle[0], lle[1], lle[2])); });
    add_steps(p, steps);
  } else {
    backspace(); // directions end with this point
      lles._foreach(function(lle) { add_point(lle[0], lle[1], null, null, lle[2]); });
    add_point(p.lat, p.lon, p.address, null, p.elev_data, p.elev_override);
  }

  draw_transects();
}



LatLngElev = function(lat, lon, elev) {
  this._elev = elev;
  GLatLng.call(this, lat, lon);
};
derive(LatLngElev, GLatLng);

LatLngElev.prototype.elev = function() {
  return this._elev;
}



/**************
function point_rectangle(p1, p2) {
  var lat0 = p1.lat;
  var lon0 = p1.lon;
  var lat1 = p2.lat;
  var lon1 = p2.lon;

  if (lat0 > lat1) { var x = lat0; lat0 = lat1; lat1 = x; }
  if (lon0 > lon1) { var x = lon0; lon0 = lon1; lon1 = x; }
  return new GLatLngBounds(new GLatLng(lat0, lon0), new GLatLng(lat1, lon1));
}
********************/

function find_point_needing_route(type, lat0, lon0, lat1, lon1) {
  for (var i = current.points.length - 1; i > 0; i--) {
    var p = current.points[i];
    if (p.pending_route == type && nearpt(p.prev, lat0, lon0) && nearpt(p, lat1, lon1))
      return p;
  }
  return null;
}


function find_point_by_latlon(lat, lon) {
  for (var i = current.points.length - 1; i >= 0; i--) {
    var p = current.points[i];
    if (p.lat == lat && p.lon == lon)
      return p;
  }
  return null;
}


function get_rise_and_fall(r_f, prev, p) {
  var l = p.leg;

  var s = 'pt0=' + prev.lat + ',' + prev.lon + (prev.elev_override != null? ',' + prev.elev_override : '');
  var k = 1;

  var limit = ((is_msie? 2083 : 8190) - 110 - 37 * 1) / 27 - 5;
  inc = l.steps.length < limit? 1 : 1 + Math.floor((l.steps.length - 1)/limit);

  for (var j = 1; j < l.steps.length - 1; j += inc)
    s += '&pt' + k++ + '=' + l.steps[j].lat() + ',' + l.steps[j].lng();
  s += '&pt' + k + '=' + p.lat + ',' + p.lon + (p.elev_override != null? ',' + p.elev_override : '');

  wt_async_request_array('bin/rise.cgi?' + s,
			 'RISE AND FALL',
			 null,
			 function(a) {
			   if (a.length < 2) {
			     alert('Path too long for rise/fall');
			     set_show_rise(false);
			     return;
			   }
			   r_f.rise = a[0] - 0;
			   r_f.fall = a[1] - 0;
			   reset_current_points();
			 }
			);
}


var icon_wt_gc = new GIcon();
icon_wt_gc.image = "images/blue-plus.png";
icon_wt_gc.shadow = null;
icon_wt_gc.iconSize = new GSize(13, 13);
icon_wt_gc.shadowSize = new GSize(0, 0);
icon_wt_gc.iconAnchor = new GPoint(6, 6);


/*********
function get_wt_gc_line(l, check_index, lat0, lon0, lat1, lon1) {
  wt_async_request_array_of_lines(
    'bin/i?n=10&lat0=' + lat0 + '&lon0=' +  lon0 + '&lat1=' + lat1 + '&lon1=' +  lon1,
    'GC',
    function(a) {
		// we may have deleted this leg by now
      if (current.legs[check_index] != l)
	return;
      l.wt_gc_line = [];
      a._foreach(function(s) {
	var t = s.match(/(-?[\d\.]+) (-?[\d\.]+)/);
	if (t) {
	  l.wt_gc_line.push(new GMarker(new GLatLng(t[1], t[2]), { icon: icon_wt_gc, clickable: false }));
        }
      });
      l.wt_gc_line._foreach(function(m) { map.addOverlay(m) });
    });
}
************/

function handle_map_mousemove(latlng) {
  if (!latlng)
    return;
  update_latlon_div(latlng.lat(), latlng.lng());
}


var lastlat;
var lastlon;
function update_latlon_div(lat, lon) {
  if (lat != null && lon != null) {
    lastlat = lat;
    lastlon = lon;
  }
  if (lastlat != null && lastlon != null)
    map_latlon_div.innerHTML = lat_lon_to_string(lastlat, lastlon, 1);
}


function write_list_div() {
  set_list_div_style('300px', current.points.length > 12);

  if (!current.points.length) {
    list_div.innerHTML = '';
    return;
  }

  var s = '<table>';
  for (var i = 0; i < current.points.length; i++)
    s += (current.points[i].leg? current.points[i].leg.html : '') + current.points[i].html;
  s += '</table>';
  list_div.innerHTML = s;
}


function add_location(s) {
		// is_address is false if they entered a lat,lon
  geocode(s, function(ll, is_address) { add_point_drv(ll.lat(), ll.lng(), is_address? s : null); map.setCenter(ll, 12); draw_transects(); });
}

function handle_location() {
  if (!document.f_list_pane.location.value)
    return;
  add_location(document.f_list_pane.location.value);
}

function checkpoint() {
  var warn = false;
  current.points._foreach(function(p) { if (p.leg && p.leg.steps.length > 2) warn = true; })
  if (warn && !confirm("Sorry, checkpoint can't handle the non-piecewise driving routes and will just checkpoint the endpoints.  Checkpoint anyway?"))
    return;

  var s = '';
  var i = 1;
  current.points._foreach(function(p) {
    s += '&ll' + i + '=' + p.lat + ',' + p.lon
	  + (p.elev_override? ',' + (p.elev_relative? '+' + p.elev_relative : p.elev_override) : ''); //RELATIVE
    i++;
  });

		// use_metric and latlon_mode are saved in a cookie
  location.href = 'profiler-0904.html'
		  + '?show_grade='     + current.show_grade
		  + '&show_rise='      + current.show_rise
		  + '&include_grade='  + current.include_grade
                  //+ '&curvature='    + current.curvature
                  + '&decimal_places=' + current.decimal_places
                  + '&elev_source='    + current.elev_source
                  + s;
}


function add_points_from_kml(kml) {
	// not sure what happens if you combine //g with parentheses across browwsers, so being a bit pendantic
  var a = kml.replace(/[\s\r\n]+/g, ' ').match(/<coordinates>.*?<\/coordinates>/g);
  var sc = 1;
  if (a) {
    a._foreach(function(b) {
      var c = b.match(/<coordinates>\s*(.*?)\s*<\/coordinates>/);
      if (c && c[1]) {
        c[1].split(' ')._foreach(function(d) {
	  var e = d.split(',');
	  add_point(e[1], e[0]);
	  if (sc) {
	    map.setCenter(new GLatLng(e[1], e[0]), 12);
	    sc = 0;
          }
        });
      }
    });
    draw_transects();
  }
}


					//***************************** CONTOURS
var contour_overlay;
var contour_widget;
var contour_listener;

				 // 0     1     2     3     4     5     6     7    8    9   10   11   12   13  14  15  16  17
var contour_interval_ft_array = [2500, 2500, 2500, 2500, 2500, 1000, 1000, 1000, 500, 250, 100, 100, 100, 100, 50, 25, 10];
var contour_interval_m_array  = [1000, 1000,  750,  750,  750,  250,  250,  250, 200, 100,  50,  50,  25,  25, 25, 10,  3];

function contour_interval_ft(z) {
  if (z >= contour_interval_ft_array.length)
    return contour_interval_ft_array[contour_interval_ft_array.length - 1];
  return contour_interval_ft_array[z];
}

function contour_interval_m(z) {
  if (z >= contour_interval_m_array.length)
    return contour_interval_m_array[contour_interval_m_array.length - 1];
  return contour_interval_m_array[z];
}

function contour_interval_with_units(z) {
  return current.use_metric? contour_interval_m(z) + ' m' : contour_interval_ft(z) + ' ft';
}

function contour_interval(z) {
  return current.use_metric? contour_interval_m(z) : contour_interval_ft(z) / feet_per_meter;
}

function redraw_contour_overlay() {
  if (contour_overlay) {
    show_contour_overlay();
    if (contour_widget)
      contour_widget.set_title();
  }
}

function show_contour_overlay() {
  if (contour_overlay)
    map.removeOverlay(contour_overlay);
  contour_overlay = new GTileLayerOverlay(TileLayer(0, 17, 'Contours (C) 2007', 'Michael Kosowsky',
	    function(point, zoom) {
	      // if (!contour_intervals[zoom])
	      //  return 'images/no-contour.png';
	      // force blue color if looking at anything other than 'map' type
		 return 'http://contour.heywhatsthat.com/bin/contour_tiles.cgi?x=' + point.x + '&y=' + point.y + '&zoom=' + zoom
		                           + '&interval=' + contour_interval(zoom)
					   + (map.getCurrentMapType().getName() != 'Map'? '&color=0000FF30' : '');
            }));
  map.addOverlay(contour_overlay);
}

function create_contour_widget(x, width) {
  WTGControl(map, x, width, 'Contours', '<b>Contours</b>',
    function() {
      return this.state?
		 'contour interval ' + contour_interval_with_units(map.getZoom())
               : 'Click to see contours';
    },
    function(i) {
      if (i) {
	show_contour_overlay();
      } else {
	if (contour_overlay)
          map.removeOverlay(contour_overlay);
	contour_overlay = null;
      }
    },
    function() {
      contour_widget = this;
    });

  contour_listener = GEvent.addListener(map, "zoomend", contour_zoom_callback);
}


function contour_zoom_callback(oldz, newz) {
  if (contour_widget)
    contour_widget.set_title();
}



	// Google Maps Bug: copyright stuff seems to only work for map types, NOT for tile overlays
	// NB: copyright_prefix isn't enough; copyright must evaluate to true (e.g. can't be null or '')
function TileLayer(min_zoom, max_zoom, copyright_prefix, copyright, url_function) {
  var cc = new GCopyrightCollection(copyright_prefix);
  cc.addCopyright(new GCopyright(2, new GLatLngBounds(new GLatLng(-54,-180), new GLatLng(60,180)), min_zoom, copyright));
  var t = new GTileLayer(cc, min_zoom, max_zoom);
  t.getTileUrl = url_function;
  return t;
}


function topo_maptype() {
		// use Microsoft Research Map's WMS server
		// inspired by http://roadlessland.org/js/wms236.js John Deck, UC Berkeley
		// and http://roadlessland.org/notes/wms-usgs-example.html
  return new GMapType([
    TileLayer(1, 17,
	      '', 'Topo maps courtesy <a href="http://www.usgs.gov">USGS</a> via <a href="http://msrmaps.com">MRS Maps</a>',
	      function(pt, zoom) {

			// using fromPixelToLatLng is very clever; cf. bin/gmt.cgi for Perl code to do the conversion
      var ul = G_NORMAL_MAP.getProjection().fromPixelToLatLng(new GPoint(256 *  pt.x,      256 * (pt.y + 1)), zoom);
      var lr = G_NORMAL_MAP.getProjection().fromPixelToLatLng(new GPoint(256 * (pt.x + 1), 256 *  pt.y     ), zoom);

      return   "http://msrmaps.com/ogcmap.ashx"               // was http://www.terraserver-usa.com/ogcmap6.ashx
             + "?version=1.1.1"
             + "&request=GetMap"
             + "&layers=DRG"				      // DRG or DOQ or UrbanArea
             + "&styles=default"                              // "Geo Grid Dark Golden"
             + "&srs=EPSG:4326"                               // 4326 is WGS84 (Plate-Carree?)
  							      //    cf. http://spatialreference.org/ref/epsg/4326/
  							      // 269xx are UTM in various zones
  							      // 3785 and 9000913 are said to be spherical mercator
  							      //  (used by gmaps: mercator on a sphere, rather than an ellipse)
             + "&bbox=" + [ul.x, ul.y, lr.x, lr.y].join(",")  // lon,lat (specification is x,y)
             + "&width=256"
             + "&height=256"
             + "&format=image/jpeg"
             + "&exceptions=se_inimage";
  	     // other fields  &service=wms &reaspect=false &bgcolor=0xffffff &transparent=true &groupname=drg  &crs (in v1.3.0)
    })
    ], G_NORMAL_MAP.getProjection(), "Topo", { alt: "Show USGS topo maps" });
}


					//***************************** TRANSECTS
function init_transect() {
    document.getElementById('transect0_img').src = 'images/profile-hit-calculate.png';
    document.getElementById('transect1_img').src = 'images/profile-hit-calculate.png';
}

function draw_transects() {
   if (transect0_pane.style.display != 'none')
     draw_transect(0);
   if (transect1_pane.style.display != 'none')
     draw_transect(1);
}

function draw_transect(n, warn) {
  if (current.points.length < 2) {
    if (warn)
      alert("Click on at least two points,\nthen hit 'calculate'");
    return;
  }

  var form = eval('document.f_transect' + n);

	// consider URL length limits: Apache 8190 and IE 2083
	// 110 bytes address and args
	// 37 for each anchor point
	// 27 for each intermediate point
	// reduce by 5 for safety

  var limit = ((is_msie? 2083 : 8190) - 110 - 37 * current.points.length) / 27 - 5;
  if (limit <= 0) {
    alert("Too many points to draw profile");
    return;
  }
  var t = 0;
  current.points._foreach(function(p) { var l = p.leg; if (l) t += l.steps.length - 2; });
  var inc = t < limit? 1 : 1 + Math.floor((t - 1)/limit);

  var s = '';
  var k = 0;
  for (var i = 1; i < current.points.length; i++) {
    var p = current.points[i].prev;
    var l = current.points[i].leg;
    s += '&pt' + k++ + '=' + p.lat + ',' + p.lon + ',' + l.color + (p.elev_override != null? ',' + p.elev_override : '');
    for (var j = 1; j < l.steps.length - 1; j += inc)
      s += '&pt' + k++ + '=' + l.steps[j].lat() + ',' + l.steps[j].lng();
  }
  var p = current.points[current.points.length - 1];
  s += '&pt' + k++ + '=' + p.lat + ',' + p.lon + (p.elev_override != null? ',,' + p.elev_override : '');

/************

  current.legs._foreach(function(l) {
    for (var j = 0; j < l.steps.length - 1; j += inc) {
      s += '&pt' + i + '=' + l.steps[j].lat() + ',' + l.steps[j].lng();
      if (j == 0) {
        s += ',' + l.color;
        if (l.prev.elev_override)
	  s += ',' + l.prev.elev_override;
      }
      i++;
    }
  });
  var last = current.points[current.points.length - 1];
  s += '&pt' + i + '=' + last.lat + ',' + last.lon + (last.elev_override? ',,' + last.elev_override : '');

//  current.points._foreach(function(a) {
//    s += '&pt' + i + '='
//         + a.lat + ',' + a.lon + ','
//         + colors[i % colors.length ] + (a.elev_override != null? ',' + a.elev_override : '');
//    i++;
//  });
*************/

  var yrange = '';
  if (form.yrange.value) {
    var a = form.yrange.value.split(',');
    yrange = units_to_meters(a[0]) + ',' + units_to_meters(a[1]);
  }

  document.getElementById('transect' + n + '_img').src =
	  '/bin/profile-0904.cgi?src=profiler-0904'
	+ (radio_get(form.curvature) - 0? '&curvature=1' : '')
	+ (form.axes.checked? '&axes=1' : '')
	+ (radio_get(form.los) - 0? '&los=1' : '')
	+ (radio_get(form.path) - 0? '&greatcircle=1' : '')
	+ '&metric=' + current.use_metric
	+ '&freq=' + form.freq.value
	+ '&refraction=' + form.refraction.value
	+ '&exaggeration=' + form.exaggeration.value
	+ (yrange? '&yrange=' + yrange : '')
	+ (radio_get(form.elev_source) - 0? '&elev_source=1' : '')
	+ s;
}


function reset_transect_parms(n) {
  var form = eval('document.f_transect' + n);
  form.axes.checked = 1;
  radio_set(form.curvature, 0);
  radio_set(form.path, 1);
  radio_set(form.los, 0);
  form.freq.value = "";
  form.refraction.value = "";
  form.exaggeration.value = "";
  form.yrange.value = "";
  radio_set(form.elev_source, 0);
  draw_transect(n);
}


function set_elev_source(n) {
  var form = eval('document.f_transect' + n);

  if (radio_get(form.elev_source) - 0) {
		// Google elevation web service
    radio_set(form.path, 1);
    radio_disable(form.path, 1);

  } else {
    radio_disable(form.path, 0);
  }

  check_elev_sources();
}



function radio_disable(r, v) {
  for (var i = 0; i < r.length; i++)
    r[i].disabled = v;
}




					//***************************** DECIMAL PLACES
function set_decimal_places(n) {
  n = Math.floor(n - 0);
  if (n >= 0 && n <= 6) {
    eval("round_bearing = round" + n);
    round_distance = round_bearing;
    current.decimal_places = n;
    document.f_units.decimalplaces.value = n;
    reset_current_points();
  }
}

					//***************************** CONTROLS

function WTControl(parent, n_states, enablef, disablef, innerHTMLf, titlef, clickf) {
  this.div        = document.createElement('div');
  parent.appendChild(this.div);
  this.style      = this.div.style;
  this.n_states   = n_states;
  this.enablef    = enablef;
  this.disablef   = disablef;
  this.innerHTMLf = innerHTMLf;
  this.clickf     = clickf;
  this.titlef     = titlef;
  this.state      = 0;
  this.enabled    = 0;
  this.enable();
  this.update();
  return this;
}

WTControl.prototype.enable = function()  {
  this.enablef(this.div);
  var t = this;
  this.div.onclick = function() { t.onclick(); };
  this.enabled = 1;
  this.set_title();
}

WTControl.prototype.disable = function() {
  this.enabled = 0;
  this.disablef(this.div);
  this.div.onclick = null;
  this.set_title();
}

WTControl.prototype.show = function() {
  this.style.display = '';
}

WTControl.prototype.hide = function() {
  this.style.display = 'none';
}

WTControl.prototype.is_visible = function() {
  return this.style.display != 'none';
}

WTControl.prototype.update = function() {
  this.div.innerHTML = exec_or_value(this.innerHTMLf, this, this.state);
  this.set_title();
}

WTControl.prototype.callback = function() {
  if (this.clickf)
    this.clickf(this.state);
}

WTControl.prototype.onclick = function() {
  this.state++;
  if (this.state >= this.n_states)
    this.state = 0;
  this.update();
  this.callback();
}

//WTControl.prototype.clear_title = function() {
// this.div.title = null;
//}

WTControl.prototype.set_title = function() {
  if (this.titlef)
    this.div.title = exec_or_value(this.titlef, this);
}

WTControl.prototype.set_state = function(state) {
  this.state = state;
  this.update();
}

WTControl.prototype.trigger = function(state) {
  this.state = state;
  this.update();
  this.callback();
}

WTControl.prototype.reset = function() {
  this.trigger(0);
}



	// for these guys, the action happens when the map calls initialize
function WTGControl(map, x, width, text0, text1, titlef, onclick, oncreate) {
  var c = new GControl(0, 0);
  c.initialize = function(map) {
    var w = new WTControl(map.getContainer(), 2,
	          function(d) { s = d.style;
				s.border          = '1px solid black'
				s.padding         = '0px 3px';
				s.backgroundColor = 'white';
				s.color           = 'black';
				s.fontSize        = '12px';
				s.fontFamily      = 'Arial,sans-serif';
				s.cursor          = 'pointer';
				s.width           = width + 'px';
				s.textAlign       = 'center';
	          },
		  null,  // for disable, tried  function(d) { d.style.color = '#606060'; },
		  function(i) { return i? text1 : text0; },
		  titlef,
		  onclick
            );

    if (oncreate)
      oncreate.call(w);
    return w.div;
  };
  map.addControl(c, new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(x, 30)));
}

function answerbutton_enable(d) {
  d.className = 'answerbutton';
}


					//***************************** ICONS

	// plus sign is current cursor (e.g. lat/lon for next query)
var icon_plus = new GIcon();
icon_plus.image = "images/black-plus.png";
icon_plus.shadow = null;
icon_plus.iconSize = new GSize(24, 24);
icon_plus.shadowSize = new GSize(0, 0);
icon_plus.iconAnchor = new GPoint(12, 12);


					//**************************** ELEVATION DATA

// CLARIFICATION: we hold on to elev_relative only until we get the elev_data, then we roll it into elev_override
// BUG: this doesn't work now that we allow multiple data sources (ie. want to keep notion of relative elev)

function set_elev(p) {
  if (p.elev_data == null && p.elev_override == null)
    p.elev_data = get_elev_data(p.lat, p.lon);

  if (p.elev_relative != null && p.elev_data != null) {
		// setting relative to zero is NOT an override (we may do it when we leave ge-api edit mode)
    if (p.elev_relative != 0)
      p.elev_override = p.elev_data + p.elev_relative;
    p.elev_relative = null; //RELATIVE
  }

  p.elev = p.elev_override != null? p.elev_override : p.elev_data;
}

function get_elev_override(lat, lon) {
  var p = find_point_by_latlon(lat, lon);
  if (!p)
    return;

  var e = prompt(p.elev_data != null?
		     "Enter elevation (precede with '+' if relative to ground; ground is " + feet_or_meters(p.elev_data) + ")"
	           : "Enter elevation relative to sea level",
		 p.elev_relative? '+' + feet_or_meters_no_label(p.elev_relative) //RELATIVE
	         : p.elev_override? feet_or_meters_no_label(p.elev_override)
	         : '');
  if (e == null)
    return;

  var a;
  if ((a = e.match(/^\s*(\+?)\s*(-?[\.\d]+)$/)) && a[0]) {
    var x = round2(units_to_meters(a[2] - 0));
    if (a[1] == '') {
      p.elev_override = x;
      p.elev_relative = null;
    } else {
      p.elev_override = null;
      p.elev_relative = x;
    }
  } else {
    p.elev_override = null;
    p.elev_relative = null;
  }
  set_elev(p);
  p.html = point_html(p);
	// BUG: just redo from here on, not the whole thing
  reset_current_points();
}


var elevs = new Object;

function elev_request(s) {
	// reverse engineers: DON'T use my server's points-google.cgi; it's a service Google describes at
	//  http://code.google.com/apis/maps/documentation/elevation/ and http://code.google.com//apis/maps/documentation/v3/services.html#Elevation
  wt_async_request_array_of_lines('bin/' + (current.elev_source? 'points-google' : 'points') + '.cgi?' + s, 'Elevation', receive_elev);
}


function get_elev_data(lat, lon) {
  if (elevs[lat + ' ' + lon] != null)
    return elevs[lat + ' ' + lon];
  elev_request('&lat0=' + lat + '&lon0=' + lon);
  return null;
}

function check_elev_sources() {
  // document.getElementById('google_options_div').style.display =
  //	   transect0_pane.style.display == '' && radio_get(document.f_transect0.elev_source) - 0
  //	|| transect1_pane.style.display == '' && radio_get(document.f_transect1.elev_source) - 0? '' : 'none';

	// in the list, we show elevations corresponding to the first open transect pane
  var elev_source =   transect0_pane.style.display == ''? radio_get(document.f_transect0.elev_source) - 0
                    : transect1_pane.style.display == ''? radio_get(document.f_transect1.elev_source) - 0
		    : 0;
  if (current.elev_source != elev_source) {
    current.elev_source = elev_source;
    document.getElementById('elev_source_div').innerHTML = current.elev_source? 'Google elevation data' : 'SRTM elevation data';
    reread_elevs();
  }
}


function reread_elevs() {
  elevs = new Object;
  var s = '';
  var i = 0;

  current.points._foreach(function(p) {
    p.elev_data = null;
    if (p.elev_relative)
      p.elev_override = null;
    s += '&lat' + i + '=' + p.lat + '&lon' + i + '=' + p.lon;
    i++;
  });
  if (s)
    elev_request(s);
    // receive_elev() will reset_current_points() once data arrives
}


function receive_elev(a) {
  if (a.length > 100)
    return;
	// points.cgi returns lat/lon with 6 digits of precision, including trailing zeroes,
	// but round6() strips trailing zeroes and that's what we've stored in p.lat and p.lon 
	// (and points-google.cgi may return more digits)
  a._foreach(function(b) { var c = b.split(' '); if (c.length == 3) elevs[round6(c[0]) + ' ' + round6(c[1])] = c[2] - 0; });
  check_elevs();
}

function check_elevs() {
  var got_one = 0;
  current.points._foreach(function(p) {
    var e;
    if (p.elev_data == null && (e = elevs[p.lat + ' ' + p.lon]) != null) {
      p.elev_data = e;
      //if (p.elev_relative) redraw = 1;
      set_elev(p);
      //p.html = point_html(p); // XXXX is this necessary?
      got_one = 1;
    }
  });

  if (got_one)
		// BUG: just redo from first point on, not the whole thing
		// BUG: don't redraw transect if our now knowing the elevation doesn't affect the profile
    reset_current_points();
}


					//******************** Google Maps Data API

function import_url_or_msid() {
  var url = document.f_import.url.value;

// Don't remember why we needed this hack; if necessary, add another button to the import form
//  if (!url) {
//    google.accounts.user.logout();
//    return;
//  }

  var a;
  a = url.match(/(^|msid=\s*)([a-f\d]+)\.([a-f\d]+)/i);
  if (a && a[2] && a[3]) {
    load_msid(a[2], a[3]);
    return;
  }

  document.f_import.elev_source.value = current.elev_source;
		// rough limit; (is_msie? 2000 : 8000) / 37
  document.f_import.max_points.value  = is_msie? 50 : 200;

  var es = current.elev_source;
  var sc = 1;

  wt_async_request_post_succeed_fail(
    'bin/get-path-from-kml.cgi',
    new FormData(document.f_import),
    function(s) {
      var a = s.split('\n');
      if (a.length)
	a.pop();
      if (!a.length) {
        alert('No points found');
        return;
      }
      a._foreach(function(s) {
        var t = s.match(/(-?[\d\.]+) (-?[\d\.]+) (-?[\d\.]+)/);
        if (t) {
					// make sure user didn't change data source while we were waiting
          add_point(t[1], t[2], null, null, (current.elev_source == es? t[3] : null));
          if (sc) {
	    map.setCenter(new GLatLng(t[1], t[2]), 12);
	    sc = 0;
	  }
        }
      });
      draw_transects();
    },
    function() {
      alert("Import failed");
    }
  );


/*
  wt_async_request_array_of_lines('bin/get-path-from-kml.cgi?url=' + url + '&elev_source=' + current.elev_source,
	      'KML PATH', function(a) {
		a._foreach(function(s) {
	          var t = s.match(/(-?[\d\.]+) (-?[\d\.]+) (-?[\d\.]+)/);
		  if (t) {
					// make sure user didn't change data source while we were waiting
		    add_point(t[1], t[2], null, null, (current.elev_source == es? t[3] : null));
		    if (sc) {
		      map.setCenter(new GLatLng(t[1], t[2]), 12);
		      sc = 0;
		    }
		  }
		});
	        draw_transects();
              });
*/

}


var gmapsdata_service;
var MAPS_SCOPE = 'http://maps.google.com/maps/feeds/';

function load_msid(user, map) {

/***
  GMAPSDATA BUG: couldn't get dynamically loading the gdata lib with callback to work;
                 the subsequent checkLogin() would always return false and lead
		 to never-ending trips to google accounts
  if (!gmapsdata_service) {
    gmapsdata_onload_callback = function() { load_msid(user, map); };
    google.load('gdata', '2.x', { packages: ['maps'] , callback: function() {
      gmapsdata_service = new google.gdata.maps.MapsService('profiler-0904');
      load_msid(user, map);
    }});
    return;
  }
****/

  if (!gmapsdata_service)
    gmapsdata_service = new google.gdata.maps.MapsService('profiler-0904');

  if (!google.accounts.user.checkLogin(MAPS_SCOPE)) {
    alert('Log in to Google, then re-enter your request');
		// this doesn't return; Google handles login then redirects back to this page, setting cookie
    google.accounts.user.login(MAPS_SCOPE);
    return;
  }

  gmapsdata_service.getMapEntry(
    'http://maps.google.com/maps/feeds/maps/' + user + '/full/' + map + '?alt=json',
    function(entryRoot) {
      feature_feed = entryRoot.entry.getContent().getUri();
      gmapsdata_service.getFeatureFeed(
	feature_feed,
	function(feedRoot) {
	  var features = feedRoot.feed.getEntries();
	  features._foreach(function(f) { add_points_from_kml(f.getContent().getText()); });
        },
	gmapsdata_error);
     },
     gmapsdata_error);
}


function gmapsdata_error(e) {
   alert('gmaps data error: ' + (e.cause ? e.cause.statusText : e.message));
}



					//***************************** MISCELLANEOUS

function miles_or_km0_no_label(x) {
  return round_distance(current.use_metric? x/1000 : x/1609.344);
}

function miles_or_km1(x) {
  return current.use_metric?      (x/1000).toFixed(Math.max(current.decimal_places, 1)) + '&nbsp;km'
	                    : (x/1609.344).toFixed(Math.max(current.decimal_places, 1)) + '&nbsp;miles';
}

function pretty_degrees(x, pos, neg) {
    return round6(Math.abs(x)) + '&deg;' + (x >= 0? pos : neg);
}

function pretty_lat(x) {
    return pretty_degrees(x, 'N', 'S');
}

function pretty_lon(x) {
    return pretty_degrees(x, 'E', 'W');
}


var feet_per_meter = 3.2808399;

function units_to_meters(x) {
  return current.use_metric? x : x / feet_per_meter;
}

function feet_or_meters_no_label(x) {
  return current.use_metric? round_distance(x) : round_distance(x * feet_per_meter);
}

function feet_or_meters(x) {
  return current.use_metric? round_distance(x) + ' m' : round_distance(x * feet_per_meter) + ' ft';
}

function percent1(x) {
  return (x * 100).toFixed(Math.max(current.decimal_places, 1)) + '%';
}

function degrees1(x) {
  return x.toFixed(Math.max(current.decimal_places, 1)) + '&deg;';
}

function lat_lon_to_string(lat, lon, is_html) {
  return format_latlon(lat, lon, current.latlon_mode, is_html);
}


var DEGREES_TO_RADIANS = Math.PI / 180.;
var EARTH_RADIUS = 6367447;

function bearing_and_range(lat0, lon0, lat1, lon1) {
	// rotate so viewer at zero longitude
  lon1 -= lon0;

	// about 11cm.  prevents NaN values below
  if (Math.abs(lat1 - lat0) + Math.abs(lon1) < .000001)
     return [0, 0];

  lat0 *= DEGREES_TO_RADIANS;
  lat1 *= DEGREES_TO_RADIANS;
  lon1 *= DEGREES_TO_RADIANS;

  var sinlon1 = Math.sin(lon1);
  var coslon1 = Math.cos(lon1);
  var sinlat0 = Math.sin(lat0);
  var coslat0 = Math.cos(lat0);
  var sinlat1 = Math.sin(lat1);
  var coslat1 = Math.cos(lat1);

  var x = coslat1 * coslon1;
  var y = coslat1 * sinlon1;
  var z = sinlat1;

	// rotate about y axis so viewer at north pole
  var xx =  x * sinlat0 - z * coslat0;
  var yy =  y;
  var zz =  x * coslat0 + z * sinlat0;

  var theta = Math.PI - Math.atan2(yy, xx);
  var phi   = Math.acos(zz);
  return [ theta/DEGREES_TO_RADIANS, phi * EARTH_RADIUS];
}


function adjust_for_declination(d) {
  if (current.magnetic) {
    d -= current.declination;
    if (d < 0)
       d += 360;
    else if (d > 360)
       d -= 360;
  }
  return d;
}

function scroll_if_needed(e, div) {
  var i = e.offsetTop - div.scrollTop;
  if (i < 0)
    e.scrollIntoView();
  else if (i > parseInt(div.style.height) - 23)
    e.scrollIntoView(false);
}


/*
Trying to avoid scrollbars on the list_div
Due to silliness in CSS spec, on firefox (but interestingly
not on IE6 and IE7), once you ask for vertical scrollbar,
you have to specify a horizontal size. Further, layout gets
confused on firefox if you toggle the vertical scrollbar
on and off.
*/

function set_list_div_initial() {
  if (is_msie) {
    list_div.style.overflowY = 'auto';
  } else {
    list_div.style.overflow  = 'auto';
    list_div.style.height    = '500px';
  }
}

function set_list_div_style(min_width, show_vertical_scrollbar) {
  if (is_msie) {
    list_div.style.height    = show_vertical_scrollbar? '500px' : 'auto';
  } else {
    list_div.style.minWidth  = min_width;
  }
}


// hack to add overlay of feature data (until we implement it right)

var icon_data = new GIcon();
icon_data.image = "images/blue-plus.png";
icon_data.shadow = null;
icon_data.iconSize = new GSize(13, 13);
icon_data.shadowSize = new GSize(0, 0);
icon_data.iconAnchor = new GPoint(6, 6);

function getnamesdata() {
  var b = map.getCenter();
  wt_async_request_array_of_lines('bin/namesdata.cgi?lat0=' + b.lat() + '&lon0=' +  b.lng(),
	      'NAMES DATA', function(a) {
		a._foreach(function(s) {
	          var t = s.match(/(-?[\d\.]+) (-?[\d\.]+) ([\-\d]+) \S+ \S+ (.*)/);
		  if (t)
		    map.addOverlay(new GMarker(new GLatLng(t[1], t[2]),
				{ icon: icon_data, clickable: false, title: feet_or_meters(t[3]) + ' ' + t[4] }));
		});});
}


/**************************
// simplify adding hacks (cf. http://www.elsewhere.org/journal/gmaptogpx)
// just need a bookmark that looks like 'javascript:addon("hack.js")'

function addon(s) {
  var script = document.createElement('script');
  script.src = s;
  document.getElementsByTagName('head')[0].appendChild(script);
}
*****************************/


function window_width() {
  return(   (window.self?              window.self.innerWidth               : 0)
	 || (document.documentElement? document.documentElement.clientWidth : 0));
}

function window_height() {
  return(   (window.self?              window.self.innerHeight               : 0)
	 || (document.documentElement? document.documentElement.clientHeight : 0));
}

function element_offset_top(element, base) {
  var o = 0;
  for (var e = element; e && e != base; e = e.offsetParent)
    o += e.offsetTop;
  return o;
}

/*********
function element_width(e) {
  return style_px(e, 'width');
}

function element_height(e) {
  return style_px(e, 'height');
}

function style_px(e, attr) {
  var a = e.style[attr].match(/(\d+)/);
  return a && a[0]? a[0] - 0 : 0;
}
*************/

function element_width(e) {
  return e.clientWidth;
}

function element_height(e) {
  return e.clientHeight;
}


function set_mapdiv_size() {
//alert([element_width(map_div)+'=', window_width(), element_width(list_div), 'xxx', element_height(map_div)+'=', window_height(), element_offset_top(map_div), element_height(error_pane)]);
  var w = window_width()  - element_width(list_pane) - 50;
  var h = window_height() - element_offset_top(map_div) - element_height(error_pane) - 50;
  if (w > 0)
    map_div.style.width = w + 'px';
  if (h > 0)
    map_div.style.height = h + 'px';
}

function block(e) {
  if (is_msie)
    e.cancelBubble = true;
  else
    e.stopPropagation();
}


function show_popup(div, width_guess, height_guess) {
/* XXXXXXX
  get window_height() and window_width() and height() and width() from wisp/index.html

  var h = window_height();
  $$s('cloak_div').height = h + 'px';
  $$s('cloak_div').display = '';

  div.style.top = (h - height(div, height_guess))/2 + 'px';
  div.style.left = (window_width() - width(div, width_guess))/2 + 'px';
*/
  div.style.display = '';
}

function close_popup(div) {
/*
  $$s('cloak_div').display = 'none';
*/
  div.style.display = 'none';
}


/************************ GE API *********************/

var was_3d = false;
var linestring_placemark;
var linestring_timeout_id;

function ge_viewchange() {
  var now_3d = (map.getCurrentMapType() == G_SATELLITE_3D_MAP);
  if (now_3d && !was_3d) {
    was_3d = true;
    edit3d.enable();
  } else if (!now_3d && was_3d) {
    was_3d = false;
    edit3d.disable();
    edit3d.reset();
    finish_3d_edit();
  }
}


  /******
  var targetType = kmlEvent.getTarget().getType();
  var currentTargetType = kmlEvent.getCurrentTarget().getType();
  var button = kmlEvent.getButton());
  var clientX = kmlEvent.getClientX();
  var clientY = kmlEvent.getClientY();
  var screenX = kmlEvent.getScreenX();
  var screenY = kmlEvent.getScreenY();
  var latitude = kmlEvent.getLatitude();
  var longitude = kmlEvent.getLongitude();
  var altitude = kmlEvent.getAltitude();
  var didHitGlobe = kmlEvent.getDidHitGlobe();
  var altKey = kmlEvent.getAltKey();
  var ctrlKey = kmlEvent.getCtrlKey();
  var shiftKey = kmlEvent.getShiftKey();
  var timeStamp = kmlEvent.getTimeStamp();
  *****/

var mousedown_at;
function ge_mousedown(kmlEvent) {
  mousedown_at = kmlEvent.getTimeStamp();
}

function ge_click(kmlEvent) {
	// make sure it was a quick click (and not a drag of the globe)
  if (!linestring_placemark && mousedown_at && kmlEvent.getTimeStamp() - mousedown_at < 250 /* ms */) {
		// kmlEvent.getAltitude() is ground level, but we'll defer to our data
    add_point_drv(kmlEvent.getLatitude(), kmlEvent.getLongitude());
    draw_transects();
  }
}


function editing_3d() {
  return linestring_placemark != null;
}

function start_3d_edit() {
  map.clearOverlays();
  linestring_placemark = ge.createPlacemark('');
  var linestring = ge.createLineString('');
  linestring_placemark.setGeometry(linestring);

  var a = linestring.getCoordinates();
  current.points._foreach(function(p) { a.pushLatLngAlt(p.lat, p.lon, p.elev_data == null? 0 : p.elev - p.elev_data); });
  //linestring.setAltitudeMode(ge.ALTITUDE_ABSOLUTE);
  linestring.setAltitudeMode(ge.ALTITUDE_RELATIVE_TO_GROUND);
  //linestring.setTessellate(true);
  ge.getFeatures().appendChild(linestring_placemark);

  gex.edit.editLineString(linestring, { editCallback: function() {
	if (linestring_timeout_id)
	  clearTimeout(linestring_timeout_id);
	linestring_timeout_id = setTimeout(function() {
	    linestring_timeout_id = null;
            read_linestring();
        }, 500);
  } });
}



function near(lat0, lon0, lat1, lon1) {
  return Math.abs(lat1 - lat0) + Math.abs(lon1 - lon0) < .000001;
}

function nearpt(p, lat, lon) {
  return Math.abs(p.lat - lat) + Math.abs(p.lon - lon) < .000001;
}

function nearpt2(p, q) {
  return Math.abs(p.lat - q.lat) + Math.abs(p.lon - q.lon) < .000001;
}


function read_linestring() {
  var a = linestring_placemark.getGeometry().getCoordinates();
  var oldpoints = current.points;
  current.points = [];
  current.total_dist = 0;

  for (var i = 0; i < a.getLength(); i++) {
    var c = a.get(i);
    var clat = c.getLatitude();
    var clon = c.getLongitude();

    for (var oi = 0; ; oi++) {
      if (oi >= oldpoints.length) {
        add_point(c.getLatitude(), c.getLongitude(), null, null, null, null, c.getAltitude());
        break;
      } else {
        var o = oldpoints[oi];
        if (nearpt(o, clat, clon)) {
          add_point(o.lat,
		    o.lon,
	            o.address,
		    o.leg && nearpt2(o.leg.prev, current.points[current.points.length-1])? o.leg : null,
		    o.elev_data,
		    o.elev_override,
		    o.elev_relative,
		    o.pending_route);
	  break;
        }
      }
    }
  }
}


function finish_3d_edit() {
  if (linestring_timeout_id) {
    clearTimeout(linestring_timeout_id);
    linestring_timeout_id = null;
  }
  if (!linestring_placemark)
    return;
  gex.edit.endEditLineString(linestring_placemark.getGeometry());
  ge.getFeatures().removeChild(linestring_placemark);
  read_linestring();
  linestring_placemark = null;
  reset_current_points();
}

