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

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

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


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

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

function clear() {
  current.legs._foreach(function(l) { remove_leg_lines(l); });
  current.points._foreach(function(p) { if (p.marker) map.removeOverlay(p.marker); });
  current.points = [];
  current.legs = [];
  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('help-0904.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_include_grade(x) {
  current.include_grade = x;
  document.getElementById('include_grade').checked = x;
  reset_current_points();
}


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(x) {
  document.getElementById('print').style.display          = x? '' : 'none';
  document.getElementById('nonprint').style.display       = x? 'none' : '';
  document.getElementById('listbutton_div').style.display = x? 'none' : '';
  document.getElementById('show_grade_div').style.display = x? 'none' : '';
}


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

function set_points(a) {
  clear();
  a._foreach(function(x) { add_point(x.lat, x.lon, x.address, x.elev_data, x.elev_override, x.elev_relative) });
  draw_transects();
}

function reset_current_points() {
  set_points(current.points);
}

function add_point(lat, lon, address, elev_data, elev_override, elev_relative) {
  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,
	    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];

	// 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);
      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 rb = bearing_and_range(lat, lon, prev.lat, prev.lon);


/****
    var color = '#' + colors[(current.points.length - 1) % colors.length];

    var start_elev;
    var prev_elev;
    if (current.points.length > 0) {
	start_elev = current.points[0].elev;
	prev_elev  = current.points[current.points.length - 1].elev;
    }
****/
    var dist = (current.include_grade && p.elev != null && prev.elev != null)? Math.sqrt(b[1] * b[1] + (p.elev - prev.elev) * (p.elev - prev.elev)) : b[1];
    current.total_dist += dist;


    var l = { prev:            prev,
	      next:            p,
	      color:           colors[(current.points.length - 1) % colors.length],
	      steps:           [ prev.latlng, p.latlng ],
	      dist:            dist,
              run:             b[1],
 	      total_dist:      current.total_dist,
	      bearing:         b[0],
	      reverse_bearing: rb[0],
	      altitude:        altitude
           };
    current.legs.push(l);
    l.html = leg_html(l);
    add_leg_lines(l);
  }

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


function point_html(p) {
  return    (p.address? '<tr><td colspan=3><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>' : '')
	  + '</tr>';
}


function leg_html(l) {
  var start_elev = current.points[0].elev;
  var prev_elev  = l.prev.elev;
  var next_elev  = l.next.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>'
            : '');
}


function add_leg_lines(l) {
  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(l) {
  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_to_leg(l, steps) {
  l.steps = steps;
  var dist = route_length(steps);
  l.total_dist += dist - l.dist;
  l.dist = dist;
  l.html = leg_html(l);
  remove_leg_lines(l);
  add_leg_lines(l);
  update_legs(l);
}


function update_legs(start) {
  var i;

  if (start) {
    current.total_dist = start.total_dist;
    current.total_dist_is_true = start.total_dist_is_true;
    for (i = 0; i < current.legs.length && current.legs[i] != start; i++) ;
    i++;
  } else {
    current.total_dist = 0;
    current.total_dist_is_true = true;
    i = 0;
  }
  for (; i < current.legs.length; i++) {
    var l = current.legs[i];
    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;
    set_leg_html(l);
  }
  write_list_div();
}


function route_length(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 get_route(as_steps, l) {
  if (l == null)
    l = current.legs[current.legs.length - 1];
  wt_async_request_array_of_lines('/bin/directions.cgi?max=50&elevs=1&q=from+'
					+ l.prev.lat + ',' + l.prev.lon + '+to+' + l.next.lat + ',' + l.next.lon,
		                  'Directions',
				  as_steps? function(a) { parse_directions_as_steps(l, a) } : parse_directions_as_legs);
}

/**************
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 parse_directions_as_legs(a) {
  if (a.length > 100)
    return;
  if (a.length <= 2) {
    alert('No driving route available');
    return;
  }

  backspace(); // directions end with this point
  a._foreach(function(b) { var c = b.split(' '); add_point(c[0], c[1], null, c[2]); });
  draw_transects();

  //var r = point_rectangle(current.points[0], current.points[current.points.length - 1]);
  //map.setCenter(r.getCenter(), map.getBoundsZoomLevel(r));
}


function parse_directions_as_steps(l, a) {
  if (a.length > 100)
    return;
  var steps = [];
  a._foreach(function(b) { var c = b.split(' '); steps.push(new GLatLng(c[0], c[1])); });

  if (steps.length <= 2) {
    alert('No driving route available');
    return;
  }

  add_steps_to_leg(l, steps);
  draw_transects();
}


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;
}


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 - 1; i++)
    s += current.points[i].html + current.legs[i].html;
  s += current.points[current.points.length - 1].html + '</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(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.legs._foreach(function(l) { if (l.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
		  + '&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.legs._foreach(function(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 = 0; i < current.points.length - 1; i++) {
    var p = current.points[i];
    var l = current.legs[i];
    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/'
	+ (form.old.checked? 'profile' : 'profile-0904')
        + '.cgi?src=profiler'
	+ (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 set_old(n) {
  var form = eval('document.f_transect' + n);

  if (form.old.checked) {
    radio_set(form.path, 0);
    radio_set(form.los, 0);
    form.exaggeration.value = "";
    form.refraction.value = "";
    form.freq.value = "";
    radio_set(form.elev_source, 0);

    radio_disable(form.path, 1);
    radio_disable(form.los, 1);
    form.exaggeration.disabled = 1;
    form.refraction.disabled = 1;
    form.freq.disabled = 1;
    radio_disable(form.elev_source, 1);

  } else {
    radio_disable(form.path, 0);
    radio_disable(form.los, 0);
    form.exaggeration.disabled = 0;
    form.refraction.disabled = 0;
    form.freq.disabled = 0;
    radio_disable(form.elev_source, 0);
  }
}


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();
}

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);
      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 
    reset_current_points();
}


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

var gmapsdata_service;

function init_gmapsdata(_callback) {
  if (gmapsdata_service)
    return;

  google.load('gdata', '2.x', { packages: ['maps'] , callback: function() {
    gmapsdata_service = new google.gdata.maps.MapsService('profiler-0904');
//    if (!google.accounts.user.checkLogin('http://maps.google.com/maps/feeds'))
      var token = google.accounts.user.login('http://maps.google.com/maps/feeds');
    if (_callback)
      _callback();
  } });
}

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

function import(url) {
  if (!url)
    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;
  }

	// NO. cross-domain XHttpRequest
	// we'll proxy it through our server (and pick up elevations at same time)
 	// BUG: would be nice to handle the msid version on the server too
  //if (!url.match(/^[a-z]+:/i))
  //  url = 'http://' + url;
  //wt_async_request(url, 'KML file', add_points_from_kml);

  var es = current.elev_source;
  var sc = 1;
  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, (current.elev_source == es? t[3] : null));
		    if (sc) {
		      map.setCenter(new GLatLng(t[1], t[2]), 12);
		      sc = 0;
		    }
		  }
		});
	        draw_transects();
              });
}


function load_msid(user, map) {
  if (!gmapsdata_service) {
    init_gmapsdata(function() { load_msid(user, map) });
    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);
}



					//***************************** 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';
}


/************************ 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(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 read_linestring() {
  var a = linestring_placemark.getGeometry().getCoordinates();
  current.points = [];
  current.legs = [];
  current.total_dist = 0;
  for (var i = 0; i < a.getLength(); i++) {
    var c = a.get(i);
		//BUG: lose address
    add_point(c.getLatitude(), c.getLongitude(), null, null, null, c.getAltitude());
  }
}


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();
}
