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

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

nomenclature:

query refers to the lat/lon/elevation/... we're putting together to request from the server

answer refers to an earlier query; e.g. something we can elect to view
       we store the answers you've received in a cookie
       we build a list of answers from URL querystrings and your cookie and a the list of public answers
result is an answer we're displaying
an "answer" is just enough to help the user select (id, name, public status); a "result" is much richer
while "answers" could be called "results", I'm hoping different words will make this a bit easier

when we submit a query, it gets queued and we'll set the is_pending flag
occassionally we'll check all the is_pending entries
it turns out that we know enough about the answer -- name, lat, lon, ... -- for our UI,
so checking the entry simply means potentially flipping the is_pending flag,
and alerting the user the results are ready

when viewing 'all', map markers are answers; when viewing a result, markers are peaks.
refer to them generically as 'elements' in select_element()

in each peak or answer we expect
   id       to tie to a div in list_pane ("list_el_<id>")  (for answers this is the result id; for peaks it's just a serial number)
   lat
   lon
   az	    for peaks only, to draw the line on the silhouette
   marker   Gmarker associated with the element

to each marker we add:
  _element  the corresponding answer or peak
  _msg_f    function to call to create InfoWindow content


we DON'T bother with an array of markers because for answers, we want to be able to remove them at will

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



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

var current = {
  show_all: true,
  print_layout: false,
  query_is_visible: false,
  sponsor_is_visible: false,
  show_transect0: false,
  show_transect1: false,
  magnetic: true,
  allsummits_state: false,
  sil_alt_scale: false,

		  // cursor position on map
  lat: 0,
  lon: 0,
  latlng: null,
  elev: null,

  viewer_marker: null,
  latlon_marker: null,
  transect_marker: null,
  selected_element: null,
  bearing: null,

  use_metric: 0,
  degrees_format: 0,

  decimal_places: 0
};

var result = {
  id: null,
  name: '',
  status: '',

  viewer_lat: 0,
  viewer_lon: 0,
  viewer_latlng: null,

  elev: 0,
  elev_above_ground: 0,
  is_public: false,
  declination: 0,
  queued_time: 0,
  start_time: 0,
  end_time: 0
};


var answers = [];
var answer_hash = new Object();
var peaks = [];
var limits = [];
var azlimits = [];
var sil_parms = {};	// parameters (width, height, ...) for horizontal and vertical silhouettes

var centerline;
var centerline_gc;
var list_highlight;	// currently highlighted div in the list pane (answer or peak)


var METERS_PER_FOOT = .3048;
var METERS_PER_MILE = 1609.344;


function reset_display() {
  list_highlight = null;
  centerline = null;
  centerline_gc = null;
  current.viewer_marker = null;
  current.latlon_marker = null;
  current.selected_element = null;
	//BUG: in IE, this generates "unresponsive script" warning when leaving the all pans view
  map.clearOverlays();
	// because we just clearOverlays()'d, reset this
  current.allsummits_state = false;
  silmarkerstyle.display = 'none';
  set_sil_alt_scale(0);
  sil_azalt_div.innerHTML = '';
  disable_transect(0);
  disable_transect(1);
  //contour_widget.hide();
  contour_widget.reset();
  cloak_widget.hide();
  cloak_widget.reset();
  upintheair_widget.hide();
  upintheair_widget.reset();
  //maximize_widget.hide();
  clear_bearing();
}


function set_display_style(a, val) {
  a._foreach(function(s) { document.getElementById(s).style.display = val; });
}


function set_panes() {
  var off = ['sil_pane', 'sil_print_pane',
	     'all_top_pane', 'answer_top_pane',
	     'list_pane', 'list_print_pane', 'title_print_pane',
	     'query_pane', 'transect0_pane', 'transect1_pane',
	     'map_pane', 'bearing_div', 'recenter_div',
	     'topbutton_div', 'ads_pane', 'sponsor_pane' ];

  var on =  current.query_is_visible? ['query_pane', 'map_pane', 'topbutton_div', 'ads_pane']
          : current.show_all?         ['all_top_pane', 'list_pane', 'map_pane', 'topbutton_div', 'ads_pane']
	  : current.print_layout?     ['sil_print_pane', 'list_print_pane', 'title_print_pane']
          :                           ['sil_pane', 'list_pane', 'answer_top_pane', 'map_pane', 'recenter_div', 'topbutton_div', 'ads_pane'];

  set_display_style(off, 'none');
  set_display_style(on,  '');
}


function set_tabs() {
  ['all_tab', 'view_tab', 'print_tab', 'query_tab']._foreach(function(s) { document.getElementById(s).className = 'closedtab'; });

  document.getElementById(
	  current.query_is_visible? 'query_tab'
	: current.show_all?         'all_tab'
	: current.print_layout?     'print_tab'
	:                           'view_tab').className = 'opentab';

  document.getElementById('print_tab').style.display =
	current.show_all || current.query_is_visible? 'none' : '';

  map.getDragObject().setDraggableCursor(
	  current.query_is_visible? 'crosshair'
	: current.show_all?         'pointer'  // NO, it's -moz-grab under firefox and who knows what elsewhere
	: current.print_layout?     'pointer'
	:                           'crosshair');
}



function set_units(use_metric) {
  current.use_metric = use_metric;
  radio_set(document.f_settings.units, current.use_metric);
  set_cookie('units', current.use_metric);
  document.getElementById('query_elev_units').innerHTML = current.use_metric? 'meters' : 'feet';
  document.getElementById('query_elev_default').innerHTML = current.use_metric? '2 meters' : '6 feet';
  document.f_query.radius_feet.style.display   = current.use_metric? 'none' : '';
  document.f_query.radius_meters.style.display = current.use_metric? '' : 'none';
  redraw_contour_overlay();
  redraw_for_format_change();
  if (current.show_transect0 && document.f_transect0.axes.checked)
    draw_transect(0); // note: don't need to clear_transect_markers()
  if (current.show_transect1 && document.f_transect1.axes.checked)
    draw_transect(1);
}

function set_degrees_format(degrees_format) {
  current.degrees_format = degrees_format % 3;
  radio_set(document.f_settings.degrees_format, current.degrees_format);
  set_cookie('df', current.degrees_format);
  redraw_for_format_change();
}

function redraw_for_format_change() {
  update_latlon_div();
  //update_profile_gndlevel();
  allsummits_reset_units();

  if (!current.show_all && !current.query_is_visible) {
    map.closeInfoWindow();
    add_viewer_marker();
    write_result_title();
    write_peak_list_div_and_markers();
    if (current.bearing)
	draw_bearing();
  }
}


var before_query_was_show_result;

function show_query() {
  //if (current.print_layout)
  //  show_result(result.id, false, current.magnetic);
  before_query_was_show_result = !current.show_all;
  reset_display();
  current.show_all = false;
  current.sponsor_is_visible = false;
  current.query_is_visible = true;
  set_panes();
  set_tabs();
  update_allsummits();
  contour_zoom_callback(0, map.getZoom());
  set_lat_lon(current.lat, current.lon);

  wt_async_request('bin/query_estimate.cgi', 'QUERY ESTIMATE',
    function(minutes) {
      query_estimate_text.innerHTML = 'Requests are taking about ' + minutes + (minutes == 1?' minute' : ' minutes');
      document.getElementById('sponsor_estimate_text').innerHTML =
				      'Your panorama will be ready in about ' + minutes + (minutes == 1?' minute' : ' minutes');
    }
  );
}


function hide_query() {
  current.query_is_visible = false;
  if (before_query_was_show_result && result.id)
    show_result(result.id, current.print_layout, current.magnetic);
  else
    show_all();
}


function show_sponsor(url, ad, beta) {
  document.getElementById('sponsor_show_result').style.display = result.id? '' : 'none';
  document.getElementById('sponsor_beta_div').style.display    = beta?      '' : 'none';
  if (url) {
    document.getElementById('sponsor_url_div').style.display = '';
    document.getElementById('sponsor_url').href = url;
  } else {
    document.getElementById('sponsor_url_div').style.display = 'none';
  }
  document.getElementById('sponsor_ad_div').innerHTML = ad;

  var s = document.getElementById('sponsor_pane').style;
  s.height  = document.body.offsetHeight + 'px';
  s.width   = '100%';
  s.display = 'block';

  current.sponsor_is_visible = true;
}


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 show_google_earth() {
  location.href = results_file(result.id, 'wt.kmz');
}

var seen_sky_warning = 0;
function show_google_sky() {
  if (result.id
      && (seen_sky_warning
          || confirm("Click 'OK' and you'll receive a Network Link for Google Earth that will you show your horizon in the sky whenever you refresh it.  Requires the Google Sky functionality in beta 4.2 or later of Google Earth."))) {
    seen_sky_warning = 1;
    location.href = 'bin/planisphere_link.kml?id=' + result.id + '&tz=' + default_timezone();
  }
}

function show_all_google_earth() {
  location.href = 'bin/update_kml.kml';
}

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

function email_result() {
  if (result.id)
    location.href = 'mailto:?subject=HeyWhatsThat panorama&body=Take a look at http://www.heywhatsthat.com/?view=' + result.id + '\n';
}

function openinfowindow(marker) {
  if (!marker._msg_f)
    return;
  var s = marker._msg_f(marker);
  if (!s)
    return;
  marker.openInfoWindowHtml(s);
}


function select_element(e) {
  if (!e)
    return;

  current.selected_element = e;

  var m = e.marker;		// corresponding marker
  set_lat_lon(e.lat, e.lon, e.elev);

  openinfowindow(m);
  //m.openInfoWindowHtml(m._msg);

  if (list_highlight)
    list_highlight.style.backgroundColor = 'white';

  list_highlight = document.getElementById('list_el_' + e.id);
  if (list_highlight) {
    list_highlight.style.backgroundColor = '#dae9be';
    scroll_if_needed(list_highlight, list_div)
  }

  if (!current.show_all)
    draw_lines(e.az, e.range);
}

function select_home(recenter, open_info_window) {
  if (!open_info_window)
    map.closeInfoWindow;
  current.selected_element = null;
  set_lat_lon(result.viewer_lat, result.viewer_lon, null, recenter, 1, open_info_window);
  erase_lines();
}

function select_lat_lon(lat, lon, elev) {
  set_lat_lon(lat, lon, elev);
  current.selected_element = null;
  clear_list_highlight();
  if (current.show_all || current.query_is_visible || current.print_layout)
    return;

  var a = bearing_and_range(result.viewer_lat, result.viewer_lon, current.lat, current.lon);
  draw_lines(adjust_for_declination(a[0]), a[1]);
}


function clear_list_highlight() {
  if (list_highlight) {
    list_highlight.style.backgroundColor = 'white';
    list_highlight = null;
  }
}

var set_lat_lon_timeout_id = 0;
var draw_centerline_too = 0;
var set_to_viewer_marker = 0;
var open_viewer_marker_infowindow = 0;

function set_lat_lon(lat, lon, elev, set_center, its_the_viewer_marker, _open_viewer_marker_infowindow) {
  current.lat    = lat;
  current.lon    = lon;
  current.latlng = new GLatLng(lat, lon);
  current.elev   = elev;
  document.f_query.lat.value = lat;
  document.f_query.lon.value = lon;
  set_to_viewer_marker          = its_the_viewer_marker;
  open_viewer_marker_infowindow = _open_viewer_marker_infowindow;


  // if (state.query_form_visible) push onto stack (I guess without elev)
  // map.closeInfoWindow();

  if (current.latlon_marker)
    map.removeOverlay(current.latlon_marker);
  current.latlon_marker = new GMarker(current.latlng,
			{ icon: icon_plus, clickable: false, title: format_latlon(lat, lon, current.degrees_format, 0) });
	  // If we addOverlay immediately, the map never sees a double-click
  if (set_lat_lon_timeout_id)
    clearTimeout(set_lat_lon_timeout_id);
  set_lat_lon_timeout_id = setTimeout(set_lat_lon_timeout, 300);

  if (set_center)
    map.setCenter(current.latlng);
  else if (!map.getBounds().contains(current.latlng))
    map.panTo(current.latlng);

  update_latlon_div();
  //update_profile_gndlevel();

  if (current.elev == null) {
    get_elev(current.lat, current.lon, function(lat, lon, elev) {
      if (current.lat == lat && current.lon == lon) {
	current.elev = elev - 0;
	update_latlon_div();
	//update_profile_gndlevel();
        if (current.bearing)
           show_bearing(current.bearing.az, current.bearing.range);  // add altitude to bearing
      }
    });
  } else {
    set_elev(current.lat, current.lon, current.elev);
  }
}

function set_lat_lon_timeout() {
  map.addOverlay(current.latlon_marker);

	// Arghhh ... if we happen to place latlon_marker above viewer_marker, we can't click on the purple X!
  if (set_to_viewer_marker && current.viewer_marker) {
    add_viewer_marker();
    if (open_viewer_marker_infowindow)
      openinfowindow(current.viewer_marker);
      //current.viewer_marker.openInfoWindowHtml(current.viewer_marker._msg);
  }

  if (draw_centerline_too) {
    if (centerline)
      map.addOverlay(centerline);
    if (centerline_gc)
      map.addOverlay(centerline_gc);
    draw_centerline_too = 0;
  }

  set_lat_lon_timeout_id = 0;
}


function update_latlon_div() {
  map_latlon_div.innerHTML =   format_latlon(current.lat, current.lon, current.degrees_format, 1)
			     + (current.elev != null? ' ' + feet_or_meters(current.elev, 1) : '');
}

/*********
function update_profile_gndlevel() {
  if (current.elev != null) {
    document.f_transect0.addl_elev.disabled = 0;
    document.f_transect1.addl_elev.disabled = 0;
    document.getElementById("profile0_gndlevel").innerHTML = feet_or_meters(current.elev, 1);
    document.getElementById("profile1_gndlevel").innerHTML = feet_or_meters(current.elev, 1);
    if (transect0_pending)
	draw_transect(0);
    if (transect1_pending)
	draw_transect(1);
  } else {
    document.f_transect0.addl_elev.disabled = 1;
    document.f_transect1.addl_elev.disabled = 1;
  }
}
*************/

				//************************** ANSWERS UI

function write_answer_list_div() {
  var s = '';
  for (var i = 0; i < answers.length; i++) {
    r = answers[i];
    s +=  '<div id="list_el_' + r.id + '">'
			// can't rely on index into answers array (e.g. 'select_element(answers[i])'),
			// because we might remove_answer()
	+ answer_list_innerhtml(r)
	+ '</div>';
  }
  set_list_div_style('300px', answers.length > 18);
  list_div.innerHTML = s;
}

function answer_list_innerhtml(r) {
  return '' +
      '<div onclick="select_element(find_answer_by_id(\'' + r.id + '\'))"><b>' + r.name.substr(0,40) + '</b></div>'
    + '&nbsp;&nbsp;'
    + (!r.is_pending?
	  '<a href="javascript:show_id(\'' + r.id + '\', false, true)">view</a> '
        : '')
    + (!r.is_pending && r.is_mine && r.is_public?
	  '<a class="listbutton" href="javascript:map.closeInfoWindow();set_public_by_id(\'' + r.id + '\', 0)">make private</a> '
	: '')
    + (!r.is_pending && r.is_mine && !r.is_public?
	  '<a class="listbutton" href="javascript:map.closeInfoWindow();set_public_by_id(\'' + r.id + '\', 1)">make public</a> '
	: '')
    + (!r.is_pending && r.is_mine?
	  '<a class="listbutton" href="javascript:map.closeInfoWindow();remove_answer_by_id(\'' + r.id + '\')">remove</a><br>'
	: '')
    + (r.is_pending?
	  '<i>pending</i><br>'
	: '');
}


function add_answer_markers() {
  for (var i = 0; i < answers.length; i++)
    add_answer_marker(answers[i]);
}


function add_answer_marker(r) {
  var l = new GLatLng(r.lat, r.lon);
  var marker = new GMarker(l, { icon: r.is_mine? r.is_public? icon_my_public_answer : icon_my_private_answer : icon_public_answer, title: r.name });
  r.marker = marker;
  marker._element = r;
  marker._msg_f = answer_marker_msg;
  //marker._msg = answer_marker_msg(r);
  GEvent.addListener(marker, "click", function() { select_element(this._element); } );
  map.addOverlay(marker);
}


/*********
function answer_marker_msg(r) {
*****/
function answer_marker_msg(m) {
  var r = m._element;
  if (!m)
    return null;
  return '' +
      '<b>' + r.name + '</b><br>'
    + format_latlon(r.lat, r.lon, current.degrees_format, 1) + '<br>'
        // + r.lat + '&deg; ' + r.lon + '&deg;<br>'
    + (!r.is_pending?
	  '<a class="iwbutton" href="javascript:show_id(\'' + r.id + '\', false, true)">View</a><br>'
	: '')
    + (!r.is_pending && r.is_mine &&  r.is_public?
	  '<a class="iwbutton" href="javascript:map.closeInfoWindow();set_public_by_id(\'' + r.id + '\', 0)">Make private</a><br>'
	: '')
    + (!r.is_pending && r.is_mine && !r.is_public?
	  '<a class="iwbutton" href="javascript:map.closeInfoWindow();set_public_by_id(\'' + r.id + '\', 1)">Make public</a><br>'
	: '')
    + (!r.is_pending && r.is_mine?
	  '<a class="iwbutton" href="javascript:map.closeInfoWindow();remove_answer_by_id(\'' + r.id + '\')">Remove</a><br>'
	: '')
    + (r.is_pending?
	  '<i>pending</i>'
	: '')
}


				//************************** RESULT UI

function write_peak_list_div_and_markers() {
  var footer = 		     '<p><i>&nbsp;&nbsp;&nbsp;(Bearings are '
	           + (current.magnetic? 'magnetic; for true' : 'true; for magnetic')
		   + (current.print_layout? '<br>&nbsp;&nbsp;&nbsp;bearings ' : ' bearings<br>&nbsp;&nbsp;&nbsp;')
		   + ((result.declination >= 0) == current.magnetic? 'add ' : 'subtract ')
		   + Math.abs(result.declination)
		   + '&deg; or click <a href="javascript:show_id(result.id,current.print_layout,!current.magnetic)">here</a>)'
	           + '<br><input id="show_alts_in_peak_list" type="checkbox" onclick="write_peak_list_div_and_markers()"/>show alts'
                   + '</i>';

  var s = '';
  current.show_alts_in_peak_list = document.getElementById("show_alts_in_peak_list") && document.getElementById("show_alts_in_peak_list").checked;

  for (var i = 0; i < peaks.length; i++) {
    var p = peaks[i];
    var l = new GLatLng(p.lat, p.lon);
    var marker = new GMarker(l, { icon: type_icons[p.type] || default_type_icon, title: p.name });
    p.id = i;
    p.marker = marker;

    marker._element = p;
    marker._msg_f = peak_marker_msg;

/********
    var shown_at = '';
    if (p.true_lat != p.lat || p.true_lon != p.lon)
	shown_at = format_latlon(p.lat, p.lon, current.degrees_format, 1);
    if (p.true_elev != p.elev)
	shown_at += (shown_at? ' ' : '') + feet_or_meters(p.elev, 1);
    if (shown_at)
	shown_at = '(shown at ' + shown_at + ')';

    marker._msg = '<b>' + p.name + '</b><br>'
		  + round_bearing(p.az) + '&deg; '
		  + miles_or_km(p.range, 1) + '<br>'
		  + '<small>'
		    + format_latlon(p.true_lat, p.true_lon, current.degrees_format, 1) + '&nbsp;'
                    + feet_or_meters(p.true_elev, 1) + '<br>'
		    + shown_at
		  + '</small>';
*************/

    if (!current.print_layout)
      GEvent.addListener(marker, "click", function() { select_element(this._element); } );

    map.addOverlay(marker);

    if (current.print_layout)
      s += peaks.length >= 40? '<tr style="font-size:10px">' : '<tr>';
    else
      s += '<tr id=list_el_' + i + ' onclick=select_element(peaks[' + i + '])>';

    s +=   '<td align=right>' + round_bearing(p.az) + '&deg;'
           + (current.show_alts_in_peak_list? '/' + round_altitude(p.alt) + '&deg;' : '')
         + '</td>'
	 + '<td style="' + (type_styles[p.type] != null? type_styles[p.type] : default_type_style)
         +            '"><b>' + p.name            + '</b></td>'
	 + '<td align=right>' + miles_or_km(p.range, 1)    + '</td>'
	 + '<td align=right>' + feet_or_meters(p.true_elev, 1) + '</td>'
	 + '</tr>';
  }


  if (current.print_layout) {
    document.getElementById("list_print_div").innerHTML = '<table style="border-spacing: 15px 0px">' + s + '</table>' + footer;
  } else {
    set_list_div_style('375px', peaks.length > 38);
    list_div.innerHTML = '<table>' + s + '</table>' + footer;
  }

  document.getElementById("show_alts_in_peak_list").checked = current.show_alts_in_peak_list;
}

function peak_marker_msg(m) {
  var p = m._element;
  if (!p)
    return null;

  var shown_at = '';
  if (p.true_lat != p.lat || p.true_lon != p.lon)
    shown_at = format_latlon(p.lat, p.lon, current.degrees_format, 1);
  if (p.true_elev != p.elev)
    shown_at += (shown_at? ' ' : '') + feet_or_meters(p.elev, 1);
  if (shown_at)
    shown_at = '(shown at ' + shown_at + ')';

  return    '<b>' + p.name + '</b><br>'
	  + round_bearing(p.az) + '&deg; '
	  + miles_or_km(p.range, 1) + '<br>'
	  + '<small>'
	  + format_latlon(p.true_lat, p.true_lon, current.degrees_format, 1) + '&nbsp;'
          + feet_or_meters(p.true_elev, 1) + '<br>'
	  + shown_at
	  + '</small>';
}


function add_viewer_marker() {
  if (current.viewer_marker)
    map.removeOverlay(current.viewer_marker);
  current.viewer_marker = new GMarker(result.viewer_latlng, { icon: icon_x, title: result.name });
  current.viewer_marker._msg_f = viewer_marker_msg;
/*********
  current.viewer_marker._msg =   '<b>' + (result.name || 'Home') + '</b><br>'
		               + format_latlon(result.viewer_lat, result.viewer_lon, current.degrees_format, 1) + '<br>'
			       + 'view is from ' + feet_or_meters(result.elev_above_ground, 1) + ' above ground<br>'
                               + '&nbsp;&nbsp;(' + feet_or_meters(result.elev, 1) + ' above sea level)';
************/
  GEvent.addListener(current.viewer_marker, "click", function() { select_home(0, 1); });
  map.addOverlay(current.viewer_marker);
}

function viewer_marker_msg() {
  return   '<b>' + (result.name || 'Home') + '</b><br>'
	 + format_latlon(result.viewer_lat, result.viewer_lon, current.degrees_format, 1) + '<br>'
	 + 'view is from ' + feet_or_meters(result.elev_above_ground, 1) + ' above ground<br>'
         + '&nbsp;&nbsp;(' + feet_or_meters(result.elev, 1) + ' above sea level)';
}


function draw_lines(az, range) {
  if (centerline)
    map.removeOverlay(centerline);
  centerline = new GPolyline([result.viewer_latlng, current.latlng], '#8B6914', 3, .5, { clickable: false });
  if (centerline_gc)
    map.removeOverlay(centerline_gc);
  centerline_gc = new GPolyline([result.viewer_latlng, current.latlng], 'purple', 3, .5, { clickable: true, geodesic: true });
	// can't just map.addOverlay(centerline) here, because if the line extends under
	// the current mouse position, it will interfere with double click detection
	// Note that we only call draw_lines after a call to set_lat_lon, so we'll piggyback
	// on the timer we set there
  draw_centerline_too = 1;

  var x = az - sil_parms.first_segment * 360 / sil_parms.n_segments;
  if (x < 0)
    x += 360;
  silmarkerstyle.left = (sil_parms.az_scale * (x - sil_parms.az_min) + silmarker_offsetleft()) + 'px';
  silmarkerstyle.display = '';

  if (!range) {
    var a = bearing_and_range(result.viewer_lat, result.viewer_lon, current.lat, current.lon);
    range = a[1];
  }
  show_bearing(az, range);
  clear_transect_markers();
  draw_transect(0);
  draw_transect(1);
}


function show_bearing(az, range) {
  var a = bearing_and_range(current.lat, current.lon, result.viewer_lat, result.viewer_lon);
  current.bearing = { az: az, reverse_az: adjust_for_declination(a[0]), range: range };
  if (current.elev != null) {
		// assumes refraction coefficient .14
    var psi = range / EARTH_RADIUS;
    var cospsi = Math.cos(psi);
    var sinpsi = Math.sin(psi);
    current.bearing.alt = 1/RADIANS_PER_DEGREE
                      * (-Math.atan2( ((result.elev + EARTH_RADIUS) - cospsi * (current.elev + EARTH_RADIUS)),
				      (sinpsi * (current.elev + EARTH_RADIUS)) ) + .14*psi/2);
  }
  draw_bearing();
}

function draw_bearing() {
  bearing_div.innerHTML =   'bearing ' + round_bearing(current.bearing.az) + '&deg; '
                          + miles_or_km(current.bearing.range, 1)
                          + (current.bearing.alt?
				 // ' alt&nbsp;' + format_angle(current.bearing.alt, current.degrees_format, '', '', 1)
				' alt&nbsp;' + round_altitude(current.bearing.alt) + '&deg;'
	                      : '')
                          + (current.elev == null?
                                ''
			      : current.bearing.has_los == null?
			          ' <span style="display:" class="recenterbutton" onclick="compute_los()">compute LOS</span>'
			        : (   (current.bearing.has_los? ' has LOS' : ' no LOS')
                                    + '&nbsp;&nbsp;(need viewer ' + feet_or_meters(current.bearing.viewer_agl_for_los) + ' agl or target ' + feet_or_meters(current.bearing.dest_agl_for_los) + ' agl)'
			          )
                            );
  bearing_div.title = 'reverse bearing ' + round_bearing(current.bearing.reverse_az);
  bearing_div.style.display = '';
}

function clear_bearing() {
  current.bearing = null;
  bearing_div.style.display = 'none';
}


function compute_los() {
    var lat1 = current.lat;
    var lon1 = current.lon;
    wt_async_request_array('bin/get-los.cgi?lat0=' + result.viewer_lat + '&lon0=' + result.viewer_lon + '&agl0=' + result.elev_above_ground + '&lat1=' + lat1 + '&lon1=' + lon1,
	'LOS',
	function(a) {
	  return a.length == 2;
        },
	function(a) {
			// make sure we're still looking at same point
			// GET-LOS FORMAT
	    if (lat1 == current.lat && lon1 == current.lon) {
		current.bearing.viewer_agl_for_los = a[0] - 0;
		current.bearing.dest_agl_for_los   = a[1] - 0;
		current.bearing.has_los            = current.bearing.viewer_agl_for_los <= result.elev_above_ground || current.bearing.dest_agl_for_los <= 0;
		draw_bearing();
	    }
        });
}


function erase_lines() {
  if (centerline)
    map.removeOverlay(centerline);
  if (centerline_gc)
    map.removeOverlay(centerline_gc);
  silmarkerstyle.display = 'none';
  clear_bearing();
  clear_transect(0);
  clear_transect(1);
  clear_transect_markers();
}


function set_sil_alt_scale(p) {
  if (p) {
    silalt0style.top = (sil_parms.headroom + sil_parms.alt_scale * (sil_parms.alt_max - 0)) + 'px';
    silalt1style.top = (sil_parms.headroom + sil_parms.alt_scale * (sil_parms.alt_max - 1)) + 'px';
    silalt0style.left = silmarker_offsetleft() + 'px';
    silalt1style.left = silmarker_offsetleft() + 'px';
    silalt0style.display = '';
    silalt1style.display = '';
  } else {
    silalt0style.display = 'none';
    silalt1style.display = 'none';
  }
  document.getElementById('show_sil_alt').checked = p;
  current.sil_alt_scale = p;
}

function sil_div_mouseout() {
  sil_azalt_div.innerHTML = '';
}

function sil_div_mousemove(event) {
  if (event)
		// NOTE: don't need to adjust_for_declination()
    sil_azalt_div.innerHTML = 'az ' + round_bearing(sil_div_az(is_msie? event.x : event.layerX))
                              + '&deg; alt ' + round_altitude(sil_div_alt(is_msie? event.y : event.layerY)) + '&deg;';
}

function silmarker_mousemove(event) {
  if (event) {
    sil_azalt_div.innerHTML = 'az ' + round_bearing(sil_div_az(parseInt(silmarkerstyle.left)))
                              + '&deg; alt ' + round_altitude(sil_div_alt(is_msie? event.y : event.layerY)) + '&deg;';
    event.cancelBubble = true;
  }
}

	// BUG: should probably use this approach in sil_div_mousemove() and silmarker_mousemove() too
	//      -- I don't think anyone uses layerX anymore?? -- but seems kinda computationally intensive
	// Haven't tested ie
        // cf. http://www.quirksmode.org/dom/w3c_cssom.html
function silalt_mousemove(event) {
  if (!event)
    event = window.event;
  if (!event)
    return;
//  var el = event.currentTarget? event.currentTarget : event.srcElement;
//  if (!el)
//    return;

	// mouse position in document coordinates
  var mx = is_msie? event.clientX + document.scrollLeft : event.pageX;
  var my = is_msie? event.clientY + document.scrollTop  : event.pageY;

	// sil_div element position in document coordinates
  var ex = 0;
  var ey = 0;
  
  var el = sildiv;
  var next_el;
	// top of the offsetParent tree (body element) has no offsetParent
  while (next_el = el.offsetParent) {
    ex += el.offsetLeft;
    ey += el.offsetTop;
    el = next_el;
  }

  sil_azalt_div.innerHTML =      'az ' +  round_bearing(sil_div_az(mx - ex))  + '&deg;'
                             + ' alt ' + round_altitude(sil_div_alt(my - ey)) + '&deg;';
  event.cancelBubble = true;
}

function sil_div_az(x) {
  var az = (x - silmarker_offsetleft()) / sil_parms.az_scale + sil_parms.az_min;
  az += sil_parms.first_segment * 360 / sil_parms.n_segments;
  if (az >= 360)
    az -= 360;
  return az;
}

function sil_div_alt(y) {
  return (sil_parms.height - y) / sil_parms.alt_scale + sil_parms.alt_min;
}


function silhouetteclick(pane, event) {
  if (current.show_all)
    return;
  var az = sil_div_az(is_msie? event.x : event.layerX);
  var az_spread =  3 / sil_parms.az_scale;
  var alt_below = -4 / sil_parms.alt_scale;
  var alt_above =  7 / sil_parms.alt_scale;

	// if marker real close, use it
  for (var i = 0; i < peaks.length; i++) {
    if (peaks[i].az > az + az_spread)
      break;

		// click within several pixels of marker
		// (note that inverted triangle's hotspot is bottom of marker)
    if (Math.abs(az - peaks[i].az) <= az_spread) {
      var a = sil_div_alt(is_msie? event.y : event.layerY);
      a -= peaks[i].alt;
      if (alt_below <= a && a <= alt_above) { 
	select_element(peaks[i]);
	return;
      }
    }
  }

	// no nearby peak.  just draw lines
  map.closeInfoWindow();
  current.selected_element = null;
  var a = azlimits[Math.floor(az)];
  set_lat_lon(a[0], a[1]);
  draw_lines(az);
  clear_list_highlight();
}

function show_silhouette() {
  if (current.show_all)
    return;

  silmarkerstyle.display = 'none';
  if (!sil_parms.ok)
    return;

	// NOTE: assumes segmented for 'view', non-segmented for 'print'
  if (current.print_layout)
    document.getElementById("sil_print_img").src = sil_parms.image_file_f();
  else
    for (var i = 0; i < sil_parms.n_segments; i++)
      document.getElementById("sil_img" + i).src = sil_parms.image_file_f((i + sil_parms.first_segment) % sil_parms.n_segments);
}

function segment_rotate(i) {
  if (i < 0)
    i = sil_parms.n_segments + (i % sil_parms.n_segments);
  sil_parms.first_segment = (sil_parms.first_segment + i) % sil_parms.n_segments;
  show_silhouette();
}

function silmarker_offsetleft() {
  return sil_img0.offsetLeft;
}


function read_image_parms(id, filename, sil) {
  var a = wt_request_array(results_file(id, filename), 'IMAGE DATA', function(a) { return a.length == 10; });
  if (!a) {
    sil.ok = 0;
    return;
  }
  sil.ok = 1;
			// IMAGE DATA
  sil.width      = a.shift() - 0;
  sil.az_min     = a.shift() - 0;
  sil.az_max     = a.shift() - 0;
  sil.az_scale   = a.shift() - 0;
  sil.height     = a.shift() - 0;
  sil.alt_min    = a.shift() - 0;
  sil.alt_max    = a.shift() - 0;
  sil.alt_scale  = a.shift() - 0;
  sil.headroom   = a.shift() - 0;
  sil.n_segments = a.shift() - 0 || 1;
  sil.first_segment = 0;
  if (sil.n_segments == 1) {
    sil.image_file_f = function(n) { return results_file(id, filename + '.png') };
  } else {
    sil.image_file_f = function(n) { return results_file(id, filename + n + '.png') };
  }
}



					//*****************************  CREATE THE VIEWS


function show_all() {
  if (!reading_answers_done)
    return;
  reset_display();
  current.show_all = true;
  current.query_is_visible = false;
  current.sponsor_is_visible = false;
  set_panes();
  set_tabs();
  update_allsummits();
  add_answer_markers();
  set_lat_lon(current.lat, current.lon);
  write_answer_list_div();
  set_view_menus_to('*');
	// this is initialized by the set_lat_lon() call in index.html
  //map.setCenter(new GLatLng(current.lat, current.lon), 7);  // DEFAULT SHOW ALL ZOOM LEVEL
  contour_zoom_callback(0, map.getZoom());
}


	// 6/2009 now that show_result returns a value,
	//   we need something else that DOESN'T return a value
	//   for "javascript::show_result()" hrefs
function show_id(id, print_layout, magnetic) {
  show_result(id, print_layout, magnetic);
}


function show_result(id, _print_layout, _magnetic, dont_warn) {
  var a = wt_request_array(results_file(id, 'data'), 'DATA', function(a) { return a.length >= DATA_NAME_INDEX; });

  if (!a || a[0] != 'ok') {
    if (!dont_warn)
							// DATA
      alert(  !a ? 'unknown panorama ' + id
                 : a.slice(DATA_NAME_INDEX).join(' ')
							// STATUS
	           + (a[0] == 'error'? ' failed (missing data)' : ' not ready')
           );
    return 0;
  }

  reset_display();
  current.show_all = false;
  current.query_is_visible = false;
  current.sponsor_is_visible = false;
  current.print_layout = _print_layout;
  current.magnetic = _magnetic;
  set_panes();
  set_tabs();
  update_allsummits();
  cloak_widget.show();
  upintheair_widget.show();
  //contour_widget.disable();
  //contour_widget.show();
  //maximize_widget.show();

			// DATA
  result = {
    id:			id,
    status:		a.shift(),
    viewer_lat:		a.shift() - 0,
    viewer_lon:		a.shift() - 0,
    elev:		a.shift() - 0,
    elev_above_ground:	a.shift() - 0,
    queued_time:	a.shift() - 0,
    start_time:		a.shift() - 0,
    end_time:		a.shift() - 0,
    is_public:		a.shift() - 0,
    declination:	a.shift() - 0,
    client:		a.shift(),
    name:		a.join(' ')
  };

  set_cookie('last_result', id);
  result.viewer_latlng = new GLatLng(result.viewer_lat, result.viewer_lon);

  if (current.print_layout) {
    read_image_parms(id, 'image_v' + (current.magnetic? 'm' : ''), sil_parms);
    document.getElementById('scale_print_status_div').innerHTML =
				'(vertical scale<br>exaggerated ' + round1(sil_parms.alt_scale / sil_parms.az_scale) + 'x)';

  } else {
    read_image_parms(id, 'image' + (current.magnetic? '_m' : ''), sil_parms);
    document.getElementById('scale_status_div').innerHTML =
				'(vertical scale exaggerated ' + round1(sil_parms.alt_scale / sil_parms.az_scale) + 'x)';
    silmarkerstyle.height = sil_parms.headroom + sil_parms.alt_scale * (sil_parms.alt_max - sil_parms.alt_min) + 'px';
  }

  show_silhouette();
  set_sil_alt_scale(current.sil_alt_scale);
  write_result_title();

	// az alt lat lon  elev range hrange name
  a = wt_request_array_of_lines(results_file(id, 'peaks'), 'PEAKS');
  peaks = [];
  if (a) {
    for (var i = 0; i < a.length; i++) {
      var aa = a[i].split(' ');
      if (aa.length < 7)
        continue;
      aa[0] -= 0;
      if (current.magnetic)
        aa[0] -= result.declination;
      if (aa[0] < 0)
        aa[0] += 360;

		// PEAKS format
      peaks.push({   az: aa[0],
	            alt: aa[1] - 0,
                    lat: aa[2] - 0,
                    lon: aa[3] - 0,
                   elev: aa[4] - 0,
                  range: aa[5] - 0,
                 hrange: aa[6] - 0,
               true_lat: aa[7] - 0,
               true_lon: aa[8] - 0,
              true_elev: aa[9] - 0,
	     visibility: aa[10],
                   type: aa[11],
		   name: aa.slice(12).join(' ') });
    }
  }
  peaks.sort(function (a, b) { return a.az - b.az;});

	// lat lon alt.  runs from 0 to 359.
	// 5/09 also hold on to the altitude, in case we try the 'up in the air' stuff later
  a = wt_request_array_of_lines(results_file(id, 'limits'), 'LIMITS');
  limits = [];
  for (var i = 0; i < a.length; i++) {
    var aa = a[i].split(' ');
    if (aa.length < 2)
      continue;
    limits.push([aa[0] - 0, aa[1] - 0, aa[2] - 0]);
  }

	// we use azlimits[] to map an azimuth to a lat/lon on the google map.
	// if we're currently using magnetic bearings, then the azimuth they click
	// on the panorama will be magnetic, so we need to adjust appropriately
  if (current.magnetic && result.declination != 0)
    if (result.declination > 0)
      azlimits = limits.slice(result.declination).concat(limits.slice(0, result.declination - 1));
    else
      azlimits = limits.slice(360 + result.declination).concat(limits.slice(0, 360 + result.declination - 1));
  else
    azlimits = limits;

  add_viewer_marker();
  map.setCenter(result.viewer_latlng, 11);  // DEFAULT ZOOM LEVEL

  write_peak_list_div_and_markers();
  recenter_text.innerHTML = result.name;

  north_widget.set_state(current.magnetic);
  //cloak_widget.trigger(1);

  set_view_menus_to(id);
  set_lat_lon(result.viewer_lat, result.viewer_lon);
  transect0_widget.trigger(true);
  transect1_widget.trigger(false);
  return 1;
}


function write_result_title() {
  var s =   '<div class="bigtitle">'
          + result.name
	  + '</div>'
	  + '<div class="littletitle">'
          + 'latitude '  + format_lat(result.viewer_lat, current.degrees_format, 1) + ' '
          + 'longitude ' + format_lon(result.viewer_lon, current.degrees_format, 1) + '<br>'
	  + 'elevation ' + feet_or_meters(result.elev, 1) + ' above sea level (' + feet_or_meters(result.elev_above_ground, 1) + ' above ground)'
	  + '<br>&nbsp;&nbsp;&nbsp;&nbsp;<i>http://www.heywhatsthat.com/main-0904.html?view=' + result.id + '</i>'
//	  + (current.print_layout? '<br>http://www.heywhatsthat.com/main-0904.html?view=' + result.id : '')
	  + '</div>';

  if (current.print_layout)
    document.getElementById('title_print_pane').innerHTML = s;
  else
    answer_title_div.innerHTML = s;
}

					//******************************** QUERY HANDLING

				// BUG: only jump to new lat/lon once both have changed,
				//      or it's only a small (e.g. <1 degree) in just one of them,
				//      or a certain amount of time has passed

function handle_direct_lat_lon_entry() {
  if (!document.f_query.lat.value || !document.f_query.lon.value)
    return;
  var lat = read_angle(document.f_query.lat.value);
  var lon = read_angle(document.f_query.lon.value);
  set_lat_lon(lat, lon);
}


function handle_map_click(overlay, point, overlay_latlng) {
  if (point)
    select_lat_lon(round6(point.y), round6(point.x));

  if (overlay && (overlay == centerline || overlay == centerline_gc) && overlay_latlng) {
      transect_map_click(round6(overlay_latlng.lat()), round6(overlay_latlng.lng()), overlay == centerline_gc);
      return;
  }
}


function handle_pan_location(s) {
  if (s)
    geocode(s, function(l) { select_lat_lon(l.lat(), l.lng()); } );
}


function handle_location() {
  if (!document.f_query.location.value)
    return;

  geocode(document.f_query.location.value, function(l) { 
      set_lat_lon(l.lat(), l.lng(), null, 1);
    }
  );
}


function handle_move() {
  var radius = current.use_metric? document.f_query.radius_meters.value : document.f_query.radius_feet.value;
	// returns lat lon
  wt_async_request_array('bin/highpoint.cgi?lat=' + current.lat + '&lon=' + current.lon + '&radius=' + radius,
	      'MOVE', function(a) { return a[0] != 0 && a[1] != 0; }, function(a) { set_lat_lon(a[0], a[1], a[2]); });
}


var seen_default_ad = 0;

function handle_query() {
  var name = document.f_query.panoramatitle.value;
  if (!name) {
    alert('Please enter a title');
    return;
  }

  handle_direct_lat_lon_entry();	// in case we never got an onchange ...
  if (!current.lat || !current.lon) {
    alert('Please enter latitude and longitude');
    return;
  }

  //if (current.lat > 60 || current.lat < -54) {
  if (current.lat < -54 || current.lat >= 71 || (current.lat > 60 && (current.lon < -173 || current.lon >= -139))) {
    alert('Invalid latitude.\nWe currently cover latitude 60N to 54S and most of Alaska.');
    return;
  }

  var elev;
  var elev_is_absolute;
  if (document.f_query.elev.value == '') {
    elev = current.use_metric? 2 : 6;
    elev_is_absolute = 0;
  } else {
    elev = document.f_query.elev.value - 0;
    elev_is_absolute = radio_get(document.f_query.absolute) - 0;
  }

    // HACK first line is data array, subsequent lines are sponsor div
  var response = wt_request('bin/query.cgi?'
	+ 'lat='               + current.lat
	+ '&lon='              + current.lon
	+ '&elev='             + units_to_meters(elev)
	+ '&elev_is_absolute=' + elev_is_absolute
	+ '&name='             + encodeURI_more(name)
	+ '&public=0'
	+ '&return_data=1'
	+ (radio_get(document.f_query.newdata)? '&newdata=1' : '')
	+ (query_client? '&client=' + query_client : ''),
    'QUERY');

	// data line, optionally followed by sponsor url line and sponsor ad multiple lines
  if (!response)
    return;
  var response_parts = response.match(/(.*?)\n(?:(.*?)\n(.*))?/);
  if (!response_parts || !response_parts[1])
    return;
  var data = response_parts[1].split(' ');
  if (data.length < DATA_NAME_INDEX + 1)
    return;
  add_answer(data[0], 1, data.slice(1));
  set_view_menus();
  set_answer_cookie();

  document.f_query.panoramatitle.value = '';
  document.f_query.elev.value = '';
  radio_set(document.f_query.absolute, 0);

	// HACK.  client 'beta' if we're not sure of quality of coverage. NOT USED as of 1/2011
        // if there's an ad, go to sponsor pane
        // if not, alert and STAY ON QUERY pane
  if (response_parts[3]) {
    show_sponsor(response_parts[2], response_parts[3], data[DATA_NAME_INDEX] == 'beta');

  } else if (!seen_default_ad) {
    seen_default_ad = 1;
    show_sponsor('/bin/sponsor.cgi?sponsor=0&url=http://www.fiddlersgreenfarm.com',
                 '<a href="/bin/sponsor.cgi?sponsor=0&url=http://www.fiddlersgreenfarm.com" target="_blank"><img '
	          + (Math.random() < .5?
			'src="/sponsors/fiddlers_logo.jpg" width="511" height="355"'
		      :	'src="/sponsors/fiddlers_kip.jpg" width="300" height="491"')
                  + ' border="0" alt="Fiddler\'s Green Farm - Whole grain goodness from Maine"></a>',
		data[DATA_NAME_INDEX] == 'beta');
  } else {
    alert('"' + name + '" submitted.\nYou\'ll be alerted when it\'s ready.'
          + (data[DATA_NAME_INDEX] == 'beta'? '\nNote that the non-US data is still being evaluated.' : ''));
  }
}



					//***************************** ANSWER LIST HANDLING
function add_answer(id, is_mine, a) {
    // if id already exists, possibly update is_mine, and if it's not pending we're done
  var r = find_answer_by_id(id);
  if (r) {
    if (is_mine && !r.is_mine) {
      r.is_mine = 1;
		// this isn't necessary, because is_mine settings only can change
		// at startup (e.g. not while show_all() is active)
      //update_answer_ui(r);
    }
    if (!r.is_pending)
      return 1;
  }

  if (!a) {
    a = wt_request_array(results_file(id, 'data'),
			    '', // no error message
			    function(a) { return a.length >= DATA_NAME_INDEX; });
    if (!a)
      return 0;
  }

  if (!r) {
    r = new Object;
    answers.unshift(r);  // later additions appear first in list
    answer_hash[id] = r;
  }

  var was_pending = r.is_pending;
		// STATUS
  var is_pending  = (a[0] == 'queued' || a[0] == 'running' || a[0] == 'runningremote');

		// DATA
  r.id         = id;
  r.is_pending = is_pending;
  r.is_mine    = is_mine;
  r.status     = a[0];
  r.lat        = a[1] - 0;
  r.lon        = a[2] - 0;
  //r.elev       = a[3] - 0;
  r.elev_above_ground = a[4] - 0;   // so f_settings.panto can set addl_elev on the transects
  r.is_public  = a[DATA_PUBLIC_INDEX] - 0;
  r.name       = a.slice(DATA_NAME_INDEX).join(' ');

  if (was_pending && !is_pending)
		// this one is necessary, because it can happen anytime
    update_answer_ui(r);

  if (is_pending)
    start_pending_timer();

  return 1;
}


function add_answer_array(is_mine, a) {
  for (var i = 0; i < a.length; i++) {
    var aa = a[i].split(' ');
    if (aa.length < 1 + DATA_NAME_INDEX+1)  // 1 for id, and REQUIRE a name
      continue;
    add_answer(aa[0], is_mine, aa.slice(1));
  }
}


function update_answer_ui(r) {
  if (!current.show_all)
    return;
  if (current.selected_element == r)
    map.closeInfoWindow();
  if (r.marker) {
    map.removeOverlay(r.marker);
    add_answer_marker(r);
  }
  var e = document.getElementById('list_el_' + r.id);
  if (e)
    e.innerHTML = answer_list_innerhtml(r);
}


function find_answer_by_id(id) {
  return answer_hash[id];
}

/***
function find_answer_by_id(id) {
  for (var i = 0; i < answers.length; i++)
    if (answers[i].id == id)
      return answers[i];
  return null;
}
****/

function find_answer_index_by_id(id) {
  for (var i = 0; i < answers.length; i++)
    if (answers[i].id == id)
      return i;
  return null;
}


function remove_answer_by_id(id) {
  var i = find_answer_index_by_id(id);
  if (i == null)
    return;
  var r = answers[i];
  if (!wt_request_array('bin/remove.cgi?id=' + r.id, 'REMOVE'))
    return;

  if (r.marker)
    map.removeOverlay(r.marker);
  if (current.selected_element == r && current.show_all)
    map.closeInfoWindow();
  answers.splice(i, 1);
  write_answer_list_div();
  set_view_menus();
  set_answer_cookie();
}


function set_public_by_id(id, is_public) {
  var r = find_answer_by_id(id);
  if (r)
    set_public(r, is_public);
}

	// BUG: add a layer of redirection (or an account structure) to public answers, so folks can't
	// can't make 'em private or remove 'em once they get the ID
function set_public(r, is_public) {
  var a = wt_request_array('bin/set_public.cgi?id=' + r.id + '&is_public=' + is_public, 'SET PUBLIC', function(a) { return a.length == 1; });
  if (!a)
    return;
  r.is_public = a[0] - 0;
  update_answer_ui(r);
}


Array.prototype._remove = function(s) { for (var __i = 0; __i < this.length; ) if (this[__i] == s) this.splice(__i, 1); else __i++; };

function set_answer_cookie() {
  var old = read_cookie('results');
  if (old)
    old = old.split(' ');
  else
    old = [];
  var a = [];
  for (var i = 0; i < answers.length; i++) {
    if (answers[i].is_mine) {
      a.push(answers[i].id);
      old._remove(answers[i].id);
    }
  }
  set_cookie('results', a.join(' '));
  if (old.length) {
	// if we're dropping any results, add them to a one month cookie
    var bad = read_cookie('bad');
    set_cookie('bad', (bad? bad + ' ' : '') + old.join(' '), 31);
  }
}


function add_querystring_answers() {
	// view=id[+id ...]  just view
	// add=id[+id ...]   make it yours (e.g. add it to cookie)
	// print=id[+id ...] view in print layout
    words_param('add')._foreach(function(id) { if (!add_answer(id, 1)) alert('unknown panorama ' + id); });
   words_param('view')._foreach(function(id) { if (!add_answer(id, 0)) alert('unknown panorama ' + id); });
  words_param('print')._foreach(function(id) { if (!add_answer(id, 0)) alert('unknown panorama ' + id); });
}



var show_all_when_done = false;

function start_reading_answers(_show_all_when_done) {
  show_all_when_done = _show_all_when_done;
  reading_answers_done = false;

  read_answer_cookie(function() {
    add_public_answers(function() {
      answers_done();
    });
  });
}

function read_answer_cookie(done) {
  var s = read_cookie('results');
/***  FORCE THE ROUND TRIP SO WE CAN CHECK FOR BAD IDS
  if (!s) {
    done();
    return;
  }
***/
  if (!s)
    s = '';
  wt_async_request_array_of_lines('bin/get-data.cgi?ids=' + s, 'MY PANORAMAS', function(a) {
    add_answer_array(1, a);
    done();
  });
}

function add_public_answers(done) {
  wt_async_request_array_of_lines('results/public', 'PUBLIC', function(a) {
    add_answer_array(0, a);
    done();
  });
}

function answers_done() {
  answers.sort(function(a, b) {
    if (a.id == result.id) return -1;
    if (b.id == result.id) return 1;
    if (a.is_mine != b.is_mine) return b.is_mine - a.is_mine;
    return a.name.localeCompare(b.name);
  });
  set_answer_cookie();
  set_view_menus();
  reading_answers_done = true;
  if (show_all_when_done)
    show_all();
}

 


var pending_timer = 0;

function start_pending_timer() {
  if (!pending_timer)
    pending_timer = setTimeout(check_pending_answers, 10000);
}


function check_pending_answers() {
  pending_timer = 0;
  answers._foreach(function(r) {
    if (r.is_pending)
      wt_async_request_array(results_file(r.id, 'data'),
			    'PENDING DATA',
			    function(a) { return a.length >= DATA_NAME_INDEX; },
			    function(a) { add_answer(r.id, r.is_mine, a);
					  if (r.is_pending)
					    return;
						// HACK: if status==error, we know we'll just be doing an alert, so we'll then need to break down window
					  if (current.sponsor_is_visible && r.status == 'error') {
                                            show_result(r.id, false, true);
					    show_query();
					    return;
					  }
					  if (current.sponsor_is_visible || r.status == 'error' || confirm("'" + r.name + "' is ready\nView it now?"))
                                            show_result(r.id, false, true);
					})
    });
}


					//***************************** SELECTION DROPDOWNS FOR CHOOSING RESULTS

function handle_select_view(sel) {
  if (sel.value == '*')
    show_all();
  else if (sel.value)
    show_result(sel.value, false, true);
}

function set_view_menu(sel, include_all) {
  sel.length = 0;
  //if (include_all)
  //  select_add_to_end(sel, new Option('All Panoramas', '*', 1, 1));

		// BUG: rather than arbitrarily limiting the number of panoramas,
		//   do some clever cascade (e.g. by state or by distance from current)
  for (var i = 0; i < answers.length && i < 3000; i++)
    select_add_to_end(sel, new Option(answers[i].name.substr(0,30), answers[i].id, 0, 0));
}

function set_view_menu_to(sel, id) {
  var o = sel.options;
  for (var i = 0; i < o.length; i++)
    if (o[i].value == id) {
      o[i].selected = 1;
      return;
    }
}

function set_view_menus() {
  set_view_menu(document.f_act.sel, 0);
	// this isn't a 'view menu', just a list of answers
  set_view_menu(document.f_settings.panto, 0);
}

function set_view_menus_to(id) {
  set_view_menu_to(document.f_act.sel, id);
}


					//****************************** VISIBILITY CLOAK
var cloak_overlays = [];

function show_cloak() {
  cloak_overlays = [];
  var a = wt_request_array_of_lines('bin/list_cloakm.cgi?id=' + result.id, 'CLOAK');
  if (!a)
    return;
  for (var i = 0; i < a.length; i++) {
    var b = CloakOverlayByName(a[i]);
    if (b) {
      cloak_overlays.push(b);
      map.addOverlay(b);
    }
  }
}

function remove_cloak() {
  for (var i = 0; i < cloak_overlays.length; i++)
    map.removeOverlay(cloak_overlays[i]);
  cloak_overlays = [];
}


/***********
function srtm_name(lat, lon) {
  var slat = '00'  + Math.abs(lat);
  var slon = '000' + Math.abs(lon);
  return (lat >= 0? 'N' : 'S') + slat.substr(slat.length - 2) + (lon >= 0? 'E' : 'W') + slon.substr(slon.length - 3);
}

function CloakOverlay(lat, lon, result) {
  return new OneDegreeImgOverlay(lat, lon, 1 + 3./3600, '/results/' + result + '/cloak' + srtm_name(lat, lon) + '.png');
}
************/

var srtm_re = /([NS])(\d\d)([EW])(\d\d\d)\./;

function srtm_latlon(s) {
  var a = s.match(srtm_re);
  if (!a || a.length != 5)
    return null;
  return [ (a[1] == 'N'? 1 : -1) * a[2], (a[3] == 'E'? 1 : -1) * a[4] ];
}

function CloakOverlayByName(name) {
  var a = srtm_latlon(name);
  if (!a)
    return null;
  return new OneDegreeImgOverlay(a[0], a[1], .5/3600, name);
}


					//*********************** UP IN THE AIR
var altitudes;
var altitude_colors = [ 'orange', 'blue' ];
var upintheair_overlays;

function init_upintheair() {
  altitudes = current.use_metric? [ 3000, 10000 ] : [10000 * METERS_PER_FOOT, 30000 * METERS_PER_FOOT];
  document.f_settings.altitude0.value = round0(meters_to_units(altitudes[0]));
  document.f_settings.altitude1.value = round0(meters_to_units(altitudes[1]));
}


function show_upintheair() {
  remove_upintheair();
  if (limits.length != 360)
    return;

  document.f_settings.altitude0.style.display = 'inline';
  document.f_settings.altitude1.style.display = 'inline';

  altitudes[0] = units_to_meters(document.f_settings.altitude0.value - 0);
  altitudes[1] = units_to_meters(document.f_settings.altitude1.value - 0);

  if (document.f_settings.altitude0.value != '' && altitudes[0] < result.elev)
    alert('orange altitude too low');

  if (document.f_settings.altitude1.value != '' && altitudes[1] < result.elev)
    alert('blue altitude too low');

  var s = '';
  var lines = [];
  for (var j = 0; j < altitudes.length; j++) {
    lines.push([]);
  }

  var sinlat0 = Math.sin(result.viewer_lat * RADIANS_PER_DEGREE);
  var coslat0 = Math.cos(result.viewer_lat * RADIANS_PER_DEGREE);

  for (var i = 0; i < 360; i++) {
    var theta = RADIANS_PER_DEGREE * (limits[i][2] - 0);
    var kk = Math.cos(theta) * (EARTH_RADIUS + result.elev);
    for (var j = 0; j < altitudes.length; j++) {
      if (altitudes[j] < result.elev)
	continue;
      var psi = Math.acos(kk / (EARTH_RADIUS + altitudes[j])) - theta;
      var bearing = i * Math.PI / 180;

	// rortate so we're at lon 0
	// rotate about y so we're at the north pole
        // desired point is longitude == 180 - bearing, latitude = 90 - psi
        // rotate it back into position
      var sinlat1 =  Math.cos(psi);
      var coslat1 =  Math.sin(psi);
      var sinlon1 =  Math.sin(bearing);
      var coslon1 = -Math.cos(bearing);

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

      var xx =  x * sinlat0 + z * coslat0;
      var yy =  y;
      var zz = -x * coslat0 + z * sinlat0;

      lines[j][i] = new GLatLng(
			Math.atan2(zz, Math.sqrt(xx * xx + yy * yy)) / RADIANS_PER_DEGREE,
			result.viewer_lon + Math.atan2(yy, xx) / RADIANS_PER_DEGREE);

      var a = bearing_and_range(result.viewer_lat, result.viewer_lon, lines[j][i].lat(), lines[j][i].lng());
      if (Math.abs(psi * EARTH_RADIUS - a[1]) > 1 || Math.abs(bearing / RADIANS_PER_DEGREE - a[0]) > .0001)
        s += psi * EARTH_RADIUS + ' ' + a[1] + ' ' + bearing / RADIANS_PER_DEGREE + ' ' + a[0] + '\n';

      //s += i + ' ' + limits[i][2] + ' ' + lines[j][i].lat() + ' ' + lines[j][i].lng() + '\n';
    }
  }
  if (s) alert(s);
  upintheair_overlays = [];
  for (var j = 0; j < altitudes.length; j++) {
    if (altitudes[j] < result.elev)
      continue;
    p = new GPolyline(lines[j], altitude_colors[j], 1, 1, { clickable: false} );
    //p = new GPolygon(lines[j], altitude_colors[j], 1, 1, altitude_colors[j], .3, { clickable: false} );
    upintheair_overlays.push(p);
    map.addOverlay(p);
  }
}

function remove_upintheair() {
  document.f_settings.altitude0.style.display = 'none';
  document.f_settings.altitude1.style.display = 'none';

  if (upintheair_overlays) {
    upintheair_overlays._foreach(function(p) { map.removeOverlay(p); });
    upintheair_overlays = null;
  }
}

					//*********************** ALL SUMMITS

var allsummits_markers = [];
var seen_allsummits_block = new Object();
var allsummits_zoom_listener;
var allsummits_move_listener;
var ALLSUMMITS_MIN_ZOOM = 11;


function init_allsummits() {
  allsummits_markers = [];
  current.allsummits_state = false;
  allsummits_zoom_listener = GEvent.addListener(map, "zoomend", allsummits_zoom);
  allsummits_move_listener = GEvent.addListener(map, "moveend", allsummits_move);
}


function update_allsummits(zoom) {
  if (zoom == null)
    zoom = map.getZoom();
  var newstate = (allsummits_widget.state && zoom >= ALLSUMMITS_MIN_ZOOM);
  if (newstate && !current.allsummits_state) {
    current.allsummits_state = true;
    allsummits_markers._foreach(function(m) { map.addOverlay(m); });
    get_allsummits_data();
  } else if (!newstate && current.allsummits_state) {
    current.allsummits_state = false;
    allsummits_markers._foreach(function(m) { map.removeOverlay(m); });
  }
}

 
function allsummits_zoom(oldz, newz) {
  update_allsummits(newz);
  if (current.allsummits_state)
    get_allsummits_data();
}


function allsummits_move() {
  if (current.allsummits_state)
    get_allsummits_data();
}


function allsummits_reset_units() {
  allsummits_markers._foreach(function(m) { m.set_title() });
}


function get_allsummits_data() {
  var b = map.getBounds();
  for (var lat = Math.floor(b.getSouthWest().lat()); lat <= Math.floor(b.getNorthEast().lat()); lat++)
    for (var lon = Math.floor(b.getSouthWest().lng()); lon <= Math.floor(b.getNorthEast().lng()); lon++)
      get_and_show_allsummit_block(lat, lon);
}



function get_and_show_allsummit_block(lat, lon) {
  if (seen_allsummits_block[lat + ' ' + lon])
    return;
  seen_allsummits_block[lat + ' ' + lon] = true;
  wt_async_request_array_of_lines('bin/namesdata.cgi?lat0=' + lat + '&lon0=' + lon,
	      'NAMES DATA', function(a) {
		a._foreach(function(s) {
	          var t = s.match(/(-?[\d\.]+) (-?[\d\.]+) ([\-\d]+) \S+ \S+ (.*)/);
		  if (t) {
		    //var m = new GMarker(new GLatLng(t[1], t[2]), { icon: icon_data, clickable: false, title: feet_or_meters(t[3]) + ' ' + t[4] });
		    //m._wt_height = t[3];
		    var m = new SimpleSummitMarker(t[1], t[2], t[4], t[3]);
	            allsummits_markers.push(m);
		    map.addOverlay(m);
	          }
		});});
}


			// Hopefully, this is lighter-weight than a GMarker
SimpleSummitMarker = function(lat, lon, name, height) {
  this.lat		       = lat;
  this.lon		       = lon;
  this.latlng		       = new GLatLng(lat, lon);
  this.name		       = name;
  this.height		       = height;
  imgdiv                       = document.createElement('img');
  this.imgdiv                  = imgdiv;
  this.imgdiv.src              = 'images/blue-plus.png';
  this.imgdivstyle             = imgdiv.style;
  this.imgdivstyle.position    = 'absolute';
  this.imgdivstyle.display     = 'none';
  this.set_title();
};

derive(SimpleSummitMarker, GOverlay);
 
SimpleSummitMarker.prototype.initialize = function(map) {
  this.map = map;
  this.map.getPane(G_MAP_MARKER_PANE).appendChild(this.imgdiv);
};

SimpleSummitMarker.prototype.remove = function() {
  this.map.getPane(G_MAP_MARKER_PANE).removeChild(this.imgdiv);
};

SimpleSummitMarker.prototype.copy = function() {
   return new SimpleSummitMarker(this.lat, this.lon, this.name, this.height);
};

SimpleSummitMarker.prototype.redraw = function(force) {
  var center = this.map.fromLatLngToDivPixel(this.latlng);
  this.imgdivstyle.left    = (center.x - 6) + 'px';
  this.imgdivstyle.top     = (center.y - 6) + 'px';
  this.imgdivstyle.display = 'block';
};

SimpleSummitMarker.prototype.set_title = function() {
  this.imgdiv.title = this.name + (this.height != 0? ' (' + feet_or_meters(this.height) + ')' : ''); 
};



function getnamesdata() {
  alert('Hit "More features" on the map to see all the summits in the database');
}



					//*********************** IMGOVERLAY CLASS
function ImgOverlay(bounds, url) {
  this.bounds  = bounds;
  this.url     = url;
}

derive(ImgOverlay, GOverlay);

ImgOverlay.prototype.initialize = function(map) {
  this.map            = map;
  this.img            = document.createElement("img");
  this.img.src        = this.url;
  this.style          = this.img.style;
  this.style.position = "absolute";

  // Our image is flat against the map, so we add our selves to the MAP_PANE pane,
  // which is at the same z-index as the map itself (i.e., below the marker shadows)
  map.getPane(G_MAP_MAP_PANE).appendChild(this.img);
}

ImgOverlay.prototype.remove = function() {
  this.img.parentNode.removeChild(this.img);
}

ImgOverlay.prototype.copy = function() {
  return new ImgOverlay(this.bounds, this.url);
}

ImgOverlay.prototype.redraw = function(change_in_coordinate_system) {
  if (!change_in_coordinate_system)
    return;

  var sw   = this.map.fromLatLngToDivPixel(this.bounds.getSouthWest());
  var ne   = this.map.fromLatLngToDivPixel(this.bounds.getNorthEast());
  var s    = this.style;
  s.width  = (ne.x - sw.x) + "px";
  s.height = (sw.y - ne.y) + "px";
  s.left   = sw.x + "px";
  s.top    = ne.y + "px";
}

function OneDegreeImgOverlay(lat, lon, fudge, url) {
  return new ImgOverlay(new GLatLngBounds(new GLatLng(lat - fudge, lon - fudge), new GLatLng(lat + 1 + fudge, lon + 1 + fudge)), url);
}


					//***************************** 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];

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_with_units(z) {
  return contour_interval_ft(z) + ' ft';
}

function contour_interval(z) {
  return contour_interval_ft(z) / feet_per_meter;
}
******************/
				 // 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) * METERS_PER_FOOT;
}

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 enable_and_draw_transect(n) {
  enable_transect(n);
  if (result.viewer_latlng.equals(current.latlng))
    document.getElementById('transect' + n + '_img').src = 'images/profile-click.png';
  else
    draw_transect(n);
}

function enable_transect(n) {
  document.getElementById('transect' + n + '_pane').style.display = '';
  current['show_transect' + n] = true;
}

function disable_transect(n, workaround_reflow_bug) {
  current['show_transect' + n] = false;
  document.getElementById('transect' + n + '_img').src = '';
  document.getElementById('transect' + n + '_pane').style.display = 'none';
  if (!current['show_transect' + (1 - n)])
    clear_transect_markers();
  else
    clear_transect_marker(n);

  if (workaround_reflow_bug && !is_msie) {
		// REFLOW WORKAROUND
    document.getElementById('map_pane').style.display = 'none';
    setTimeout(function() { document.getElementById('map_pane').style.display = ''; }, 100);
  }
}

function draw_transect(n, warn) {
  if (!current['show_transect' + n] || result.viewer_latlng.equals(current.latlng))
    return;

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

  var a = form.addl_elev.value.match(/^\s*(\+?)\s*(-?[\.\d]+)$/);
  if (a && a[0]) {
    elev = units_to_meters(a[2] - 0);
    if (a[1] == '+')
      if (current.selected_element)
	elev += current.selected_element.elev;
      else
        groundrelative = '&groundrelative=1';

  } else if (current.selected_element) {
    elev = current.selected_element.elev;
  }

  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=main-0904'
	  + '&pt0=' + result.viewer_lat + ',' + result.viewer_lon + ',8b6914,'
		    + (groundrelative == ''? result.elev : result.elev_above_ground) + ',9906ff'
          + '&pt1=' + current.lat       + ',' + current.lon
          + (elev? ',,' + elev : '')
          + ( current.selected_element? ',' + (type_colors[current.selected_element.type] || default_type_color) : '')
	+ (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 : '')
        + groundrelative;
}

function clear_transect(n) {
  if (current['show_transect' + n]) {
    document.getElementById('transect' + n + '_img').src = 'images/no-profile.png';
    clear_transect_marker(n);
  }
}

function clear_transect_marker(n) {
  document.getElementById('transect' + n + '_marker').style.display = 'none';
}

function clear_transect_markers() {
  clear_transect_marker(0);
  clear_transect_marker(1);
  clear_transect_map_marker();
}

function clear_transect_marker(n) {
  document.getElementById('transect' + n + '_marker').style.display = 'none';
}

function clear_transect_map_marker() {
  if (current.transect_marker) {
    map.removeOverlay(current.transect_marker);
    current.transect_marker = null;
  }
}


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


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 transect_click(n, event) {
  if (!current['show_transect' + n] || result.viewer_latlng.equals(current.latlng))
    return;

  var img              = document.getElementById('transect' + n + '_img');
  var form             = eval('document.f_transect' + n);

  if (!(radio_get(form.path) - 0)) {
    alert('This profile must be set to "great circle" for a click on the graph to update the map.');
    return;
  }

	// BUG: assumes scale_width in profile.cpp is 30
  var scalew           = form.axes.checked? 30 : 0;
  var x                = ((is_msie? event.x : event.layerX) - img.offsetLeft - scalew) / (img.width - scalew);
  var gc = bearing_and_range(result.viewer_lat, result.viewer_lon, current.lat, current.lon);
  var ll = along_gc(result.viewer_lat, result.viewer_lon, gc[0], x * gc[1]);
  mark_transects(x, ll[0], ll[1]);
}

function transect_map_click(lat, lon) {
  if (!(current.show_transect0 || current.show_transect1) || result.viewer_latlng.equals(current.latlng))
    return;
  if (!(   current.show_transect0 && radio_get(document.f_transect0.path) - 0
        || current.show_transect1 && radio_get(document.f_transect1.path) - 0 )) {
    alert('A profile must be set to "great circle" to click on the path and show the point on the profile.');
    return;
  }

  var gc0 = bearing_and_range(result.viewer_lat, result.viewer_lon, current.lat, current.lon);
  var gc  = bearing_and_range(result.viewer_lat, result.viewer_lon, lat, lon);
  mark_transects(gc[1]/gc0[1], lat, lon);
}

function mark_transects(x, lat, lon) {
  if (current.show_transect0)
    mark_transect(0, x);
  if (current.show_transect1)
    mark_transect(1, x);

  var latlng = new GLatLng(lat, lon);
  clear_transect_map_marker();
  current.transect_marker = new GMarker(latlng, { icon: icon_transect, clickable: false, title: format_latlon(lat, lon, current.degrees_format, 0) });
  if (!map.getBounds().contains(latlng))
    map.panTo(latlng);
  map.addOverlay(current.transect_marker);
}

function mark_transect(n, x) {
  var form             = eval('document.f_transect' + n);
  if (!(radio_get(form.path) - 0))
    return;
  var img              = document.getElementById('transect' + n + '_img');
  var marker_style     = document.getElementById('transect' + n + '_marker').style;
  var scalew           = form.axes.checked? 30 : 0;
  marker_style.left    = (x * (img.width - scalew) + img.offsetLeft + scalew) + 'px';
  marker_style.display = '';
}



/**********
function transect_click(n, event) {
  if (!current['show_transect' + n] || result.viewer_latlng.equals(current.latlng))
    return;
  clear_transect_marker(1 - n);

  var img              = document.getElementById('transect' + n + '_img');
  var form             = eval('document.f_transect' + n);
  var marker_style     = document.getElementById('transect' + n + '_marker').style;

  var x                = is_msie? event.x : event.layerX;
  marker_style.left    = x + 'px';
  marker_style.display = '';

	// BUG: assumes scale_width in profile.cpp is 30
  var xx = form.axes.checked? (x - img.offsetLeft - 30) / (img.width - 30) : (x - img.offsetLeft) / img.width;
  var gc = bearing_and_range(result.viewer_lat, result.viewer_lon, current.lat, current.lon);
  var ll = along_gc(result.viewer_lat, result.viewer_lon, gc[0], xx * gc[1]);
  var latlng = new GLatLng(ll[0], ll[1]);
  current.transect_marker = new GMarker(latlng, { icon: icon_transect, clickable: false, title: format_latlon(ll[0], ll[1], current.degrees_format, 0) });
  if (!map.getBounds().contains(latlng))
    map.panTo(latlng);
  map.addOverlay(current.transect_marker);
}
*****************/



					//**************************** ELEVATION DATA CACHE
var elevs = new Object;

function set_elev(lat, lon, elev) {
  elevs[round6(lat) + ' ' + round6(lon)] = elev;
}

function get_elev(lat, lon, callback) {
  var lat0 = round6(lat);
  var lon0 = round6(lon);
  if (elevs[lat0 + ' ' + lon0] != null)
    callback(lat, lon, elevs[lat0 + ' ' + lon0])
  else
		// request_array_of_lines may be appropriate, because points will take many lat/lon pairs at once
    wt_async_request_array('bin/points.cgi?lat0=' + lat0 + '&lon0=' + lon0, 'Elevation',
	function(a) {
	  return 1; //a.length == 3;
        },
	function(a) {
			// points.cgi returns lat/lon with 6 digits of precision, including trailing zeroes,
			// but round6() strips trailing zeroes
	  if (a.length == 3) {
	    elevs[round6(a[0]) + ' ' + round6(a[1])] = a[2];
	    callback(a[0], a[1], a[2]);
          }
        });
}


					//***************************** 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;
    round_altitude = n < 2? round2 : round_bearing;
    current.decimal_places = n;
    document.f_settings.decimalplaces.value = n;
    redraw_for_format_change();
  }
}
					//***************************** 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);

	// X marks lat/lon of viewer of current result
var icon_x = new GIcon();
icon_x.image = "images/orchid-x.png";
icon_x.shadow = null;
icon_x.iconSize = new GSize(24, 24);
icon_x.shadowSize = new GSize(0, 0);
icon_x.iconAnchor = new GPoint(12, 12);
icon_x.infoWindowAnchor = new GPoint(12, 5);


var icon_transect = new GIcon();
icon_transect.image = "images/green-plus.png";
icon_transect.shadow = null;
icon_transect.iconSize = new GSize(10, 60);
icon_transect.shadowSize = new GSize(0, 0);
icon_transect.iconAnchor = new GPoint(5, 30);


	// summit markers
var default_type_icon = new GIcon();
default_type_icon.image = "images/nonsummit.png";
default_type_icon.shadow = "images/summit_shadow.png";
default_type_icon.iconSize = new GSize(12, 20);
default_type_icon.shadowSize = new GSize(22, 20);
default_type_icon.iconAnchor = new GPoint(6, 20);
default_type_icon.infoWindowAnchor = new GPoint(5, 1);

var type_icons = new Object;
type_icons['summit'] = new GIcon(default_type_icon);
type_icons['summit'].image = "images/summit.png";
type_icons['island'] = type_icons['summit'];

var default_type_color = '5959ff';
var type_colors = {
  summit: 'c60205',
  island: 'c60205'
};

var default_type_style = '';
var type_styles = {
   antenna: 'color: #5959ff'
};



	// answers (queries we've run)
var icon_my_private_answer = new GIcon(default_type_icon);
icon_my_private_answer.image = "images/google_mm_20_red.png";
icon_my_private_answer.shadow = "images/google_mm_20_shadow.png";

var icon_my_public_answer = new GIcon(icon_my_private_answer);
icon_my_public_answer.image = "images/mm_20_red_blue.png";

var icon_public_answer = new GIcon(icon_my_private_answer);
icon_public_answer.image = "images/google_mm_20_blue.png";



					//***************************** MISCELLANEOUS
/******************
function read_querystring_position() {
  var lat  = location.search.match('(?:\\?|&)' + 'lat'  + '=(-?[\\.\\d]+)');
  if (!lat)
     return 0;
  var lon  = location.search.match('(?:\\?|&)' + 'lon'  + '=(-?[\\.\\d]+)');
  if (!lon)
     return 0;
  var elev = location.search.match('(?:\\?|&)' + 'elev' + '=(-?[\\.\\d]+)');
  if (!elev)
     return 0;
  return [ read_angle(lat[1]), read_angle(lon[1]), elev[1] - 0 ];
}
*****************/

function feet_or_meters(x, is_html) {
  return current.use_metric? round0(x) + 'm' : round0(x / METERS_PER_FOOT) + (is_html? '&nbsp;' : ' ') + 'ft';
}

function miles_or_km(x, is_html) {
  return current.use_metric? round_distance(x/1000)            + (is_html? '&nbsp;' : ' ') + 'km'
			   : round_distance(x/METERS_PER_MILE) + (is_html? '&nbsp;' : ' ') + (round_distance(x/METERS_PER_MILE) == 1? 'mile' : 'miles');
}

function units_to_meters(x) {
  return current.use_metric? x : x * METERS_PER_FOOT;
}

function meters_to_units(x) {
  return current.use_metric? x : x / METERS_PER_FOOT;
}

var RADIANS_PER_DEGREE = 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 *= RADIANS_PER_DEGREE;
  lat1 *= RADIANS_PER_DEGREE;
  lon1 *= RADIANS_PER_DEGREE;

  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/RADIANS_PER_DEGREE, phi * EARTH_RADIUS ];
}


function along_gc(lat0, lon0, bearing, range) {
  lat0 *= RADIANS_PER_DEGREE;
  var sinlat0 = Math.sin(lat0);
  var coslat0 = Math.cos(lat0);

  var psi     = range / EARTH_RADIUS;
  var sinlat1 = Math.cos(psi);
  var coslat1 = Math.sin(psi);

  var t       = (180 - bearing) * RADIANS_PER_DEGREE;
  var sinlon1 = Math.sin(t);
  var coslon1 = Math.cos(t);

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

  var xx =  x * sinlat0 + z * coslat0;
  var yy =  y;
  var zz = -x * coslat0 + z * sinlat0;
  
  return [ Math.atan2(zz, Math.sqrt(1 - zz * zz)) / RADIANS_PER_DEGREE,
           lon0 + Math.atan2(yy, xx) / RADIANS_PER_DEGREE, ];
}


function adjust_for_declination(d) {
  if (current.magnetic) {
    d -= result.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    = '650px';
  }
}

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



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

