Deprecated
Google has now stopped its Earth API and Maps API is a paid for API nowadays. Some capabilities have also been either removed or changed. Therefore, I had to remove all examples of VizMap applications I had created. I didn’t remove the entire topic as I thought some of the code may still be useful with some modifications. 

VizMap: Javascript howTos

This relates to Data Driven Mapping applications

Firstly – I am not an expert in javaScript, but in the accelerated learning process to discover what was necessary to create this application generator, there were some things that were hard to track down. In this section I will share those with you and hope you find them useful.

The VizMap generated application

Using our example application ,  we will use extracts  in the following pages to illustrate ‘howTos’

Here is the complete generated code including the data

<!DOCTYPE html>
<HTML>
<HEAD>

<STYLE type="text/css">
html {
 height: 100%
}

body {
 height: 100%;
 margin: 0;
 padding: 0;
}

#map_canvas {
 height: 100%;
}

.mcinfo {
 margin: 0px;
 font-family: sans-serif;
 font-size: 0.7em;
 padding: 0px;
 background-color: #0;
 vertical-align: top;
}
.mcelementtd {
 vertical-align:top;
 align-text:left;
}
.mcli {
 display: inline;
 border: 1px solid Teal;
 padding: .2em;
 padding-bottom: 0px;
 background-color: Azure;
 border-top-left-radius: 5px;
 -moz-border-radius-topleft: 5px;
 border-top-right-radius: 5px;
 -moz-border-radius-topright: 5px;
}

.mctabselected {
 background-color: WhiteSmoke;
 border-bottom: 1px solid WhiteSmoke;
}

.mchover:hover {
 background-color: LightSeaGreen;
}

.mcselected {
 background-color: WhiteSmoke;
 margin: 0px;
 padding: .3em;
 border: 1px solid Teal;
 border-bottom-left-radius: 15px;
 -moz-border-radius-bottomleft: 15px;
 border-bottom-right-radius: 15px;
 -moz-border-radius-bottomright: 15px;
 border-top-right-radius: 15px;
 -moz-border-radius-topright: 15px;
}
.mccheck {
    width : 0.7em;
 height :0.7em;
 padding: 0px;
 margin: 0px;
}

.mcstatus {
 display: inline;
 font-family: sans-serif;
 font-size: 0.7em;
 border: 1px solid Gray;
 border-radius: 5px;
 -moz-border-radius: 5px;
 float: right;
}

.mcready {
 color: WhiteSmoke;
 background-color: Teal;
}

.mcbusy {
 color: WhiteSmoke;
 background-color: FireBrick;
}

.mctable {
 font-family: sans-serif;
 font-size: 0.7em;
 border: 1px solid Gray;
}
.mcearth {
    width:240px;
    height:180px;
    border: 1px solid gray;
   
}

.mctableheader {
 font-family: sans-serif;
 font-size: 0.6em;
 background-color: Azure;
 border: 1px solid Gray;
}

.mctweetheader {
 font-family: sans-serif;
 font-size: 1em;
 background-color: gainsboro;
 color:darkslategrey;
 align:left;
 vertical-align:top;

}
.mctweet {
 font-family: sans-serif;
 font-size: 1em;
 background-color:WhiteSmoke;
 color:DarkSlateGrey;
 align:left;
 vertical-align:top;
 
}
.mchide {
 display: none;
}

.mcshow {
 display: block;
}
</STYLE>
<SCRIPT type='text/javascript'
 src='http://maps.googleapis.com/maps/api/js?sensor=false'></SCRIPT><script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<SCRIPT type='text/javascript' src='https://www.google.com/jsapi'></SCRIPT><SCRIPT type="text/javascript">var mcpher = new function() {
// ---ramblings.mcpher.com
// ---insert functions to provide data just before here
// mcpherVizMap
// you may use this code as you wish
// but please acknowledge in code, and in postings as below
// Bruce Mcpherson
// ramblings.mcpher.com
// Any feedback or questions - http://groups.google.com/group/excel-ramblings 
// ---
 var vMap;
 var providers  = {'google earth':'earth','google maps':'maps'};
 mcpherInitialize();
// --get the included packages and draw everything when done
 function mcpherInitialize() {
  google.load('earth', '1');
  google.load('visualization', '1', 
    {packages:['table','imagebarchart','orgchart']});
  google.setOnLoadCallback(mcpherDrawEverything);
 }

 //-- prepare all the content
 function mcpherDrawEverything() {
  vMap = new mcpherVizMap();
  // get the data and framework
  if ( vMap.populate()) {
   // assuming there is some data
   if (vMap.cJobject.length > 0) {
    // set up all the spots
    if (vMap.create()) {
     //maps or earth?
     vMap.createProvider();
     //create map items for each data item
     vMap.createItems();
     // add spots to map 
     switch (vMap.provider) {
     case 'maps':
      vMap.spotEverything();
      break;
     case 'earth':
      // will be called asynchronously when earth is ready
      break;
     default:
      alert ('draweverythinginsanity');
     }
    }
   }
   else {
    this.statusUpdate('..nothing to do',true,'there was no data');
   } 
  }
 }

//---Excel generated framework
function mcpherGetFramework () 
 { return (
  {
   "framework":{
      "dictionary":{
         "Latitude":"number",
         "Longtitude":"number",
         "SpotID":"number",
         "Title":"string",
         "Content":"string",
         "Code":"string",
         "Country":"string",
         "Venue":"string",
         "State":"string",
         "City":"string",
         "Address":"string",
         "Shows":"number",
         "ID":"number",
         "Average Price":"number",
         "Price":"number",
         "Date":"date",
         "Artist":"string",
         "Artist Photo":"string",
         "Venue Photo":"string"
      },
      "measures":{
         "Average Price":"average",
         "Shows":"count"
      },
      "control":{
         "browser":"default",
         "provider":"google earth",
         "tilt":"60",
         "range":"500",
         "flyspeed":"0.9",
         "flyrange":"200",
         "flyincrement":"1",
         "imagewidth":"80",
         "placemarkscale":"3",
         "adjustlightfortimeofday":"yes",
         "twitterimagewidth":"50",
         "twitterradius":".5mi",
         "twitterlanguage":"en",
         "twittermaxtweets":"10"
      },
      "tabs":{
         "Detail":{
            "filter":[
               "SpotID"               
             ],
            "chart":[
               "Artist",
               "Average Price"               
             ],
            "table":[
               "Date",
               "City",
               "State",
               "Country",
               "Venue",
               "Artist",
               "Price"               
             ],
            "twitter":[
               "Artist"               
             ],
            "image":"Venue Photo",
            "charttype":"barchart"
         },
         "Artist":{
            "filter":[
               "Artist"               
             ],
            "chart":[
               "City",
               "Average Price"               
             ],
            "table":[
               "Date",
               "City",
               "State",
               "Country",
               "Venue",
               "Artist",
               "Price"               
             ],
            "twitter":[
               "Artist"               
             ],
            "image":"Artist Photo",
            "charttype":"barchart"
         },
         "Country":{
            "filter":[
               "Code"               
             ],
            "chart":[
               "Artist",
               "Average Price"               
             ],
            "table":[
               "Date",
               "City",
               "State",
               "Venue",
               "Artist",
               "Price"               
             ],
            "twitter":[
               "Country",
               "Artist"               
             ],
            "image":"Venue Photo",
            "charttype":"barchart"
         },
         "City":{
            "filter":[
               "Code",
               "State",
               "City"               
             ],
            "chart":[
               "Artist",
               "Average Price"               
             ],
            "table":[
               "Date",
               "Venue",
               "Artist",
               "Price"               
             ],
            "twitter":[
               "City",
               "Artist"               
             ],
            "image":"Venue Photo",
            "charttype":"barchart"
         },
         "Venue":{
            "filter":[
               "ID"               
             ],
            "chart":[
               "Artist",
               "Average Price"               
             ],
            "table":[
               "Date",
               "Artist",
               "Price"               
             ],
            "twitter":[
               "Venue"               
             ],
            "image":"Venue Photo",
            "charttype":"barchart"
         },
         "World":{
            "chart":[
               "Artist",
               "Shows",
               "Average Price"               
             ],
            "table":[
               "Date",
               "Venue",
               "Artist",
               "Price"               
             ],
            "twitter":[
               "Artist"               
             ],
            "image":"Venue Photo",
            "charttype":"barchart"
         }
      },
      "elements":{
         "content":{
            "position":"2",
            "show":"yes"
         },
         "filters":{
            "position":"4",
            "show":"no"
         },
         "image":{
            "position":"3",
            "show":"yes"
         },
         "checks":{
            "position":"5",
            "show":"yes"
         },
         "table":{
            "position":"6",
            "show":"yes"
         },
         "summary":{
            "position":"7",
            "show":"yes"
         },
         "chart":{
            "position":"8",
            "show":"yes"
         },
         "title":{
            "position":"1",
            "show":"no"
         },
         "navigate":{
            "position":"11",
            "show":"yes"
         },
         "twitter":{
            "position":"0",
            "show":"yes"
         }
      },
      "spots":{
         "earth marker":"Artist Photo",
         "maps marker":""
      }
   }
 }
 ) ; }
//---ramblings.mcpher.com
//---Excel generated data
  function mcpherGetData () 
 { return (
  {
   "data":{
      "cJobject":[         {
            "Latitude":"41.976641",
            "Longtitude":"-87.86369",
            "SpotID":"101",
            "Title":"Rosemount theatre",
            "Content":"\<b\>Rosemount theatre\</b\>\<br\>Rosemont Theater\<br\> 5400 N River Rd\<br\> Rosemont\<br\> IL 60018-5409\<br\> USA\<br\>",
            "Code":"US",
            "Country":"United States",
            "Venue":"Rosemount theatre",
            "State":"IL",
            "City":"Rosemont",
            "Address":"Rosemont Theater, 5400 N River Rd, Rosemont, IL 60018-5409, USA",
            "Shows":"101",
            "ID":"101",
            "Average Price":"100",
            "Price":"100",
            "Date":"11/5/2011",
            "Artist":"Sting",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic243.png",
            "Venue Photo":"https://ramblings.mcpher.com/images/pic239.png"
         },
         {
            "Latitude":"41.976641",
            "Longtitude":"-87.86369",
            "SpotID":"101",
            "Title":"Rosemount theatre",
            "Content":"\<b\>Rosemount theatre\</b\>\<br\>Rosemont Theater\<br\> 5400 N River Rd\<br\> Rosemont\<br\> IL 60018-5409\<br\> USA\<br\>",
            "Code":"US",
            "Country":"United States",
            "Venue":"Rosemount theatre",
            "State":"IL",
            "City":"Rosemont",
            "Address":"Rosemont Theater, 5400 N River Rd, Rosemont, IL 60018-5409, USA",
            "Shows":"101",
            "ID":"101",
            "Average Price":"90",
            "Price":"90",
            "Date":"11/13/2011",
            "Artist":"Paul Simon",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic241.png",
            "Venue Photo":"https://ramblings.mcpher.com/images/pic239.png"
         },
         {
            "Latitude":"30.029015",
            "Longtitude":"-90.0522531",
            "SpotID":"102",
            "Title":"UNO Lakefront Arena",
            "Content":"\<b\>UNO Lakefront Arena\</b\>\<br\>6801 Franklin Ave\<br\> New Orleans\<br\> LA 70122\<br\> USA\<br\>",
            "Code":"US",
            "Country":"United States",
            "Venue":"UNO Lakefront Arena",
            "State":"LA",
            "City":"New Orleans",
            "Address":"6801 Franklin Ave, New Orleans, LA 70122, USA",
            "Shows":"102",
            "ID":"102",
            "Average Price":"120",
            "Price":"120",
            "Date":"11/4/2011",
            "Artist":"Linkin Park",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic242.png",
            "Venue Photo":"https://ramblings.mcpher.com/images/pic238.png"
         },
         {
            "Latitude":"34.6381228",
            "Longtitude":"135.4253848",
            "SpotID":"103",
            "Title":"Intex",
            "Content":"\<b\>Intex\</b\>\<br\>Nankokita\<br\> Suminoe Ward\<br\> Osaka\<br\> Osaka Prefecture 559-0034\<br\> Japan\<br\>",
            "Code":"JP",
            "Country":"Japan",
            "Venue":"Intex",
            "State":"",
            "City":"Suminoe Ward",
            "Address":"Nankokita, Suminoe Ward, Osaka, Osaka Prefecture 559-0034, Japan",
            "Shows":"103",
            "ID":"103",
            "Average Price":"80",
            "Price":"80",
            "Date":"9/16/2011",
            "Artist":"Linkin Park",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic242.png",
            "Venue Photo":"https://ramblings.mcpher.com/images/pic240.png"
         },
         {
            "Latitude":"37.5198209",
            "Longtitude":"127.1227901",
            "SpotID":"104",
            "Title":"olympic park",
            "Content":"\<b\>olympic park\</b\>\<br\>Olympic Park\<br\> 88 Bangi-dong\<br\> Songpa-gu\<br\> Seoul\<br\> South Korea\<br\>",
            "Code":"KR",
            "Country":"South Korea",
            "Venue":"olympic park",
            "State":"",
            "City":"Seoul",
            "Address":"Olympic Park, 88 Bangi-dong, Songpa-gu, Seoul, South Korea",
            "Shows":"104",
            "ID":"104",
            "Average Price":"67",
            "Price":"67",
            "Date":"9/8/2011",
            "Artist":"Linkin Park",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic242.png",
            "Venue Photo":""
         },
         {
            "Latitude":"13.8448335",
            "Longtitude":"100.3610179",
            "SpotID":"105",
            "Title":"aktiv square",
            "Content":"\<b\>aktiv square\</b\>\<br\>Ban Mai\<br\> Bang Yai\<br\> Nonthaburi 11140\<br\> Thailand\<br\>",
            "Code":"TH",
            "Country":"Thailand",
            "Venue":"aktiv square",
            "State":"",
            "City":"Bang Yai",
            "Address":"Ban Mai, Bang Yai, Nonthaburi 11140, Thailand",
            "Shows":"105",
            "ID":"105",
            "Average Price":"50",
            "Price":"50",
            "Date":"9/23/2011",
            "Artist":"Linkin Park",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic242.png",
            "Venue Photo":""
         },
         {
            "Latitude":"55.7532225",
            "Longtitude":"37.6215052",
            "SpotID":"106",
            "Title":"red square",
            "Content":"\<b\>red square\</b\>\<br\>Red Square\<br\> Moscow\<br\> Russia\<br\>",
            "Code":"RU",
            "Country":"Russian Federation",
            "Venue":"red square",
            "State":"administrativnyy okrug Tsentral\'nyy",
            "City":"Moscow",
            "Address":"Red Square, Moscow, Russia",
            "Shows":"106",
            "ID":"106",
            "Average Price":"20",
            "Price":"20",
            "Date":"6/23/2011",
            "Artist":"Linkin Park",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic242.png",
            "Venue Photo":""
         },
         {
            "Latitude":"52.3123574",
            "Longtitude":"4.9441509",
            "SpotID":"107",
            "Title":"heineken hall",
            "Content":"\<b\>heineken hall\</b\>\<br\>Arena Boulevard 590\<br\> 1101 Amsterdam Zuidoost\<br\> The Netherlands\<br\>",
            "Code":"NL",
            "Country":"The Netherlands",
            "Venue":"heineken hall",
            "State":"Government of Amsterdam",
            "City":"Amsterdam Zuidoost",
            "Address":"Arena Boulevard 590, 1101 Amsterdam Zuidoost, The Netherlands",
            "Shows":"107",
            "ID":"107",
            "Average Price":"40",
            "Price":"40",
            "Date":"6/23/2011",
            "Artist":"Toto",
            "Artist Photo":"https://ramblings.mcpher.com/images/pic244.png",
            "Venue Photo":""
         }
       ]
   }
 }
 ) ; }
//---ramblings.mcpher.com
// Start of mcpherVizMap
 var mcpherVizMap = function  () { 

  this.cssBusy = 'mcstatus mcbusy';
  this.cssReady = 'mcstatus mcready';
  this.cssInfoWindow = 'mcinfo';
  this.cssLi = 'mcli';
  this.cssTab = this.cssLi + ' mchover';
  this.cssTabSelected = this.cssTab + ' mctabselected';
  this.cssSelected = 'mcselected';
  this.cssHide = 'mchide';
  this.cssShow = 'mcshow';
  this.cssChart = 'mchart';
  this.cssTableOptions =  { 
    alternatingRowStyle: true, 
    showRowNumber: false, 
    allowHtml: true, 
    cssClassNames: { 
     headerCell: 'mctableheader',
     tableCell:  'mctable' 
    }
  };
 };

 mcpherVizMap.prototype.populate = function() {
  //some setup we can do while waiting for the google modules to load
  // get the jSon  for the tabular data
  this.data = mcpherGetData().data;
  this.framework = mcpherGetFramework().framework;

  // these are all the fields that are known
  if (!(this.dictionary = this.framework.dictionary)) {
   alert('no data dictionary found');
   return (null);
  }
  // various control information
  if (!(this.control = this.framework.control)) {
   alert('no control found');
   return (null);
  }
  // various control information
  if (!(this.spotControl = this.framework.spots)) {
   alert('no spot control found');
   return (null);
  }
  // the element containing the data
  if (!(this.cJobject = this.data.cJobject)) {
   alert('no data found');
   return (null);
  }
  // get the names of the tabs
  if (!(this.cTabs = this.framework.tabs )) {
   alert('no tab definitions found');
   return (null);
  }
  // and the measures 
  if (!(this.cMeasures = this.framework.measures )) {
   alert('no measure definitions found');
   return (null);
  }

  // and the elements 
  if (!(this.cElements = this.framework.elements )) {
   alert('no element definitions found');
   return (null);
  }

  // check we have minimum mapping fields
  for (var key in {'Latitude':0,'Longtitude':0,'Content':0,'Title':0,'SpotID':0}) {
   if (!this.dictionary[key]) {
    alert ('Missing required dictionary field ' + key);
    return (null);
   }
  }
  // find out if we are doing maps or earth
  if (this.control.provider){
   this.provider = providers[this.control.provider];
  }
  if (!this.provider){
   alert('unknown provider ' + this.control.provider);
   return (null);
  }
  // these are the things that will be used in the summary and the chart
  this.measures = new Array ();
  for (var mkey in this.cMeasures) {
   this.measures.push(mkey);
  }
  // this is for positioning each of the tables & charts etc
  this.elements = new Array();
  for (var ekey in this.cElements) {
   this.elements.push(ekey);
  }

  // these are the tabs that will be shown  
  this.tabs = new Array ();
  for (var tkey in this.cTabs) {
   this.tabs.push(tkey);
  }
  return(this);
 };
 mcpherVizMap.prototype.create = function() {

  this.statusDiv = document.getElementById('status_div');
  this.statusUpdate('..loading data and map',true);
  this.tempDiv = document.body.appendChild(document.createElement('div'));
  this.tempDiv.className = this.cssHide;
  // create a set of markers
  this.items = new Array (this.cJobject.length);
  this.mTable = this.masterTable();

  // get the unique spots
  this.spots = new Array();

  for ( var i = 0 ; i < this.cJobject.length;i++) {
   for (var j=0; j < this.spots.length ;j++) {
    if (this.spots[j].spotId == this.cJobject[i].SpotID) {
     break;
    }
   }
   if (j >= this.spots.length) { 
    this.spots.push(new mcpherSpot(this,this.spots.length,i));
   }
  } 
  return (this);

 };

 mcpherVizMap.prototype.createProvider = function() { 
  // get parameters if any
  var qparams = mcpherGetqparams();

  var cj = this.cJobject;
  this.mapDiv = document.getElementById('map_canvas');

  switch (this.provider) {

  case 'maps':
   var bounds = new google.maps.LatLngBounds();
   // create a bounding box
   for ( var i = 0; i < cj.length; i++ ) {
    bounds.extend (new google.maps.LatLng(cj[i].Latitude, 
      cj[i].Longtitude));
   }
   var myOptions = {
     mapTypeId: google.maps.MapTypeId.ROADMAP
   };

   if (!qparams['zoom']) {
    qparams['zoom'] = 2;
   }
   myOptions['zoom'] = parseInt(qparams['zoom']);
   // create the map and show an info window

   this.map = new google.maps.Map(this.mapDiv, myOptions);
   this.map.fitBounds(bounds);
   break;

  case 'earth':
   this.vEarth = new mcpherEarth(this,'google');
   this.vEarth.createInstance(this.mapDiv);
   break;

  default:
   alert ('lost the provider');
  break;
  }
 };

 mcpherVizMap.prototype.spotEverything = function() {
  var vm=this;
  for ( var i = 0 ; i < vm.spots.length;i++) {
   vm.spots[i].createSpot ();
  }
  // add the spots to each item
  vm.spotItems();
  vm.statusUpdate('..Good to go!',false,null);
 };
 mcpherVizMap.prototype.gotoAnotherSpot = function (spot,newSpot) {  
  var vm = this;
  if (newSpot.spotIndex != spot.spotIndex ){
   vm.statusUpdate('change spot from '+
     spot.title + ' to '+ newSpot.title,true,null); 
   switch (vm.provider){
   case 'maps':
    spot.infoWindow.close();
    break;
   case 'earth':
    var ve = vm.vEarth;
    ve.ge.setBalloon(null);
    ve.showLookAt(newSpot);
    break;
   default:
    alert('dealwithinsanity');
   break;
   }
   vm.statusUpdate('changed spot from '+
     spot.title + ' to '+ newSpot.title,false,null); 
  }
 };
 mcpherVizMap.prototype.statusUpdate = function(s,isbusy,a) {
  if (this.statusDiv){ 
   this.statusDiv.innerHTML = (new Date()).toTimeString() + '-' + s;
   if (isbusy) {
    this.statusDiv.className = this.cssBusy;
   }
   else {
    this.statusDiv.className = this.cssReady;
   }
  }
  if (a) {
   alert(a);
  }
 };
 mcpherVizMap.prototype.isMeasure = function(key) {
  for ( var i= 0; i < mcpherSize(this.measures) ; i++) {
   if (this.measures[i] == key ) {
    return (true);
   }
  }
  return(false);
 };
 mcpherVizMap.prototype.columnType = function(key) {
  return (this.dictionary[key]); 
 };
 mcpherVizMap.prototype.columnValue = function(key,value) {
  // given a key, set the type for a table (it will probably be string in the incoming json
  var x;
  switch (  this.columnType(key)  ){
  case 'number':
   if (!(mcpherIsNumber(value))){
    alert (key + ' has invalid value ' + value + 
      ' for type' + this.columnType(key) );
   }
   else {
    x = mcpherToNumber(value);
   }
    
   break;

  case 'date':
   if (!(x=mcpherToDate(value))){
    alert (key + ' has invalid value ' + value + 
      ' for type' + this.columnType(key) );
   }
   break;

  default:
   x=value;
  break;
  }  
  return (x);
 };
 mcpherVizMap.prototype.masterTable = function() {
  // create a master google viz table
  gTable = new google.visualization.DataTable();
  for ( key in this.dictionary)  {
   gTable.addColumn (this.columnType(key), key);
  }

  var currentCol;
  for (var i = 0 ; i < this.cJobject.length ; i++){
   var currentRow = gTable.addRow();
   for ( var key in this.cJobject[i]){
    if ( (currentCol = this.columnIndex (gTable,key)) >= 0){
     gTable.setCell(currentRow,currentCol,this.columnValue(key,this.cJobject[i][key]));
    }
    else { 
     alert ("Unknown column name " + key + " at row " + currentRow + " in cJobject data");       
    }
   }
  }
  return (gTable);
 };
 mcpherVizMap.prototype.spotItems = function () {

  for (var i=0; i<this.items.length;i++) { 
   // find matching spot;
   var spot = null;
   for (var j=0; j < this.spots.length ;j++){ 
    if (this.spots[j].spotId == this.items[i].spotId) {
     spot = this.spots[j];
     break;
    }
   }
   if (spot){
    this.items[i].spot = this.spots[j];
   }
   else {
    alert('couldnt find spot for item ' + i);
   }
    }
 };

 mcpherVizMap.prototype.createItems = function () {
  //  create the infotab contents for each plottable item

  for (var i=0; i<this.cJobject.length;i++) {
   this.items[i] = new mcpherItem();
   this.items[i].create (this, i);
  }
 };
 mcpherVizMap.prototype.columnIndex = function (gTab,label) {
  for (var i=0; i < gTab.getNumberOfColumns() ;i++) {
   if (gTab.getColumnLabel(i) == label) {
    return (i);
   }
  }
  return (-1);
 };
 mcpherVizMap.prototype.nextSpot = function (spot) {
  var vm=this;
  return (vm.spots[(spot.spotIndex +1) % vm.spots.length]);
 };
// END OF mcpherVizMap

// --INFOTAB custom object to hold infowindow tab data
 var mcpherInfoTab = function () {this.init();};
 mcpherInfoTab.prototype.init = function () { 
  return (this);
 };
 mcpherInfoTab.prototype.showOrNot =function () {
  // are we showing it?
  var vm = this.parent.parent;
  for (var key in this.infoElements) {
   if (this.checkBoxes[key]){
    this.infoElements[key].className = 
     this.checkBoxes[key].checked ? vm.cssShow : vm.cssHide;
   }
  }
 };
 mcpherInfoTab.prototype.createDivElements = function (container) {
  // this creates the structure for div elements of the infowindow that are indexed by the framework
  this.divElements = new Array();
  this.divContainer = container.appendChild(document.createElement('div'));
  var labBody = this.divContainer.appendChild( 
    document.createElement('table')).appendChild(document.createElement('tbody'));
  var labTds = new Array();     
  // because of the rowspans and colspans its kind of tricky
  // r0 3 cells
  this.createTds(labBody,labTds, 1 , 3);
  // r1 2 cells
  this.createTds(labBody,labTds, 1 , 2);
  // r2-5 1 cells
  this.createTds(labBody,labTds, 4 , 1);
  // r6 2 cell
  this.createTds(labBody,labTds, 1 , 2);
  
  // set up colspans & row spans and assign to positions
  this.pushDiv(labTds , 0 ,0 , 0, 0,  1);
  this.pushDiv(labTds , 1 ,0 , 0, 0,  2); 
  this.pushDiv(labTds , 1 ,1 , 2, 0,  3); 
  this.pushDiv(labTds , 2 ,0 , 0, 0,  4);
  this.pushDiv(labTds , 3 ,0 , 0, 2,  5); 
  this.pushDiv(labTds , 4 ,0 , 0, 2,  6); 
  this.pushDiv(labTds , 5 ,0 , 0, 2,  7); 
  this.pushDiv(labTds , 6 ,0 , 0, 2,  8); 
  this.pushDiv(labTds , 0 ,2 , 6, 0,  9); 
  this.pushDiv(labTds , 6 ,1 , 0, 0,  10); 
  this.pushDiv(labTds , 0 ,1 , 0, 0,  11); 

 };

 mcpherInfoTab.prototype.createTds =function (labBody,labTds, nRows, nCols ) {

  for (var i=0; i < nRows ;i++){
   var labTrTds = new Array();
   var labTr = labBody.appendChild(document.createElement('tr'));
   for (var j=0; j < nCols ; j++){
    labTrTds.push (labTr.appendChild(document.createElement('td')));
   }
   labTds.push (labTrTds);
  }
 };

 mcpherInfoTab.prototype.pushDiv =function (container, r ,c , rs, cs, pos ) {
  var cTd = container[r];

  cTd.className= 'mcelementtd';

  if (rs) {
   cTd.rowSpan = rs;
  }
  if (cs) {
   cTd.colSpan = cs;
  }
  this.divElements.push(cTd.appendChild(document.createElement('div')));

 };
 mcpherInfoTab.prototype.create =function (mm , tabIndex) {
  this.parent = mm;
  var vm = mm.parent;
  var ve = vm.ve;
  var conTab = this;
  this.tabIndex = tabIndex;

  this.tab = this.parent.ulContainer.appendChild( 
    document.createElement('li'));
  this.tab.className = vm.cssTab ;

  mcpherAddEvent (this.tab,"click",
    function(){ mm.fillDiv(conTab);}
  ,false ,true) ;
  this.tab.appendChild(document.createTextNode 
    (vm.tabs[this.tabIndex]));

  // create the structure of this info tab
  this.createDivElements(mm.container.appendChild 
    (document.createElement('div')));

  // now assign these divs to required elements
  var ie = this.infoElements  = new Array();
  for (key in vm.cElements){
   var p = parseInt(vm.cElements[key].position);

   if (p){
    if ( p<0 || p > this.divElements.length){
     alert(' position ' + p + ' is not valid - refer to diagram in parameter sheet');
    }
    else{
     this.infoElements[key] = this.divElements[p -1];
    }
   }
  }

  if (ie['title']) {
   ie['title'].appendChild (document.createTextNode(mm.cJobject.Title));
  }
  // fill all that up
  if (ie['content']) {
   ie['content'].innerHTML= mm.cJobject.Content;
  }

  // add checkboxes
  if (ie['checks']) {
   this.checkBoxes = new Array();
   for (key in vm.cElements) {
    if ( parseInt(vm.cElements[key].position)) {
     var cbx = document.createElement("input"); 
     cbx.className = 'mccheck';
     cbx.type = "checkbox";  
     cbx.id = "mcphercbx" + key;
     ie['checks'].appendChild(
       document.createTextNode('\u00a0\u00a0'+ key ));
     this.checkBoxes[key]= ie['checks'].appendChild(cbx);
     if (vm.cElements[key].show == "yes") {
      cbx.setAttribute("checked", "checked" );
     }
     mcpherAddEvent (cbx,"click",function(){ 
      conTab.showOrNot(); 
     },false ,true) ;
    }
   }

  }
  if (ie['navigate']){
   var bx = document.createElement('button');
   bx.className = 'mcnextbutton';
   bx.appendChild(document.createTextNode('Next Spot'));
   ie['navigate'].appendChild(bx);

   mcpherAddEvent (bx,"click",function(){ 
    var nextSpot =vm.nextSpot(mm.spot)
    vm.gotoAnotherSpot(mm.spot,nextSpot);
    if (vm.provider=='maps') nextSpot.createInfoWindow(0);
   },false ,true) ;

   if (ve) {
    var bflyx = document.createElement('button');
    bflyx.className = 'mcnextbutton';
    bflyx.appendChild(document.createTextNode('Fly Around'));
    ie['navigate'].appendChild(bflyx);
    mcpherAddEvent (bflyx,"click",function(){ 
     mm.spot.initFlyAround();
    },false ,true) ;
   }

  }
  // is there an image?
  var imgField = vm.cTabs[vm.tabs[this.tabIndex]]['image'];

  if (ie['image'] && imgField && 
    mm.cJobject[imgField]){ 
   if ( mcpherIsImage(mm.cJobject[imgField])) {
    var labImage = ie['image'].appendChild(document.createElement('img'));
    labImage.src = mm.cJobject[imgField];
    if (mcpherIsNumber(vm.control.imagewidth)){
     labImage.width = mcpherToNumber(vm.control.imagewidth);
    }
    labImage.title = vm.Title;  
   }    
  }
  this.vTable = this.createTable();
  // and a google viz table of qualifying matching transactions
  if (ie['table']){
   if (this.vTable) {
    this.table = new google.visualization.Table(ie['table']);
    this.table.draw(this.vTable , mm.parent.cssTableOptions);
    // allow selection on this table 
    var vizTab = this.table;
    var datTab = this.vTable;
    google.visualization.events.addListener(vizTab, 
      'select', function() {
     conTab.dealWithSelection (vizTab,datTab);
    });
   }
  }

  // and a summary table
  if (ie['summary'] || ie['chart'] ){
   if (this.cTable = this.chartTable()) {
    if(ie['summary']){
     this.chtable = 
      new google.visualization.Table(ie['summary']);
     this.chtable.draw(this.cTable , 
       vm.cssTableOptions);
    }
   }
  }

  if (ie['chart']){
   if (this.cTable) {
    var chtType =  vm.cTabs[ vm.tabs[conTab.tabIndex] ]['charttype'];
    switch (chtType) {

    case 'barchart':
     this.chart = new google.visualization.ImageBarChart(ie['chart']);
     this.chart.draw(this.cTable, 
       {title:vm.tabs[this.tabIndex],
      isStacked:false});
     break;

    case 'orgchart':
     this.oTable = this.orgTable();
     this.chart = new google.visualization.OrgChart(ie['chart']);
     this.chart.draw(this.oTable, 
       {title:vm.tabs[this.tabIndex]});
     // allow selection on this chart
     var vizTab = this.chart;
     var datTab = this.oTable;
     google.visualization.events.addListener(vizTab, 
       'select', function() { 
      conTab.dealWithSelection (vizTab,datTab);
     });
     
     break;

    case 'none':
     break;

    default:
     alert('unknown charttype ' + chtType);
    break;
    }
   }
  }
  // and which filters were selected
  if (ie['filters']){
   this.fTable = new google.visualization.DataTable();
   this.fTable.addColumn('string','Where');
   this.fTable.addColumn('string','is');

   for ( var key in this.filters) { 
    var cLab = vm.mTable.getColumnLabel(
      this.filters[key].column) ;
    this.fTable.addRow (
      [cLab,this.filters[key].value.toString()]);
   }   
   this.filterTable = new google.visualization.Table(ie['filters']);  
   this.filterTable.draw(this.fTable,vm.cssTableOptions); 
  }
        // tweets needed - we'll do these in real time so they are up to date when selected.
  // but create a view table of the query items that we'll need
  if (ie['twitter']){
   this.tTable = this.tweetTable();
  }

  conTab.showOrNot();
  return (this);
 };
 mcpherInfoTab.prototype.viewTable = function (vType,needGroup,complain) {
  var mm = this.parent.parent;
  var tItems = mm.cTabs[ mm.tabs[this.tabIndex] ][vType];
  var colIndex;

  if (!tItems) {
   if (complain)alert ('Cant find ' + vType + 'fields for ' + 
     mm.tabs[this.tabIndex]);
   return (null); }
  else {
   // only include data columns referenced in tab[].table
   var viewColumns = new Array();
   for ( var i=0 ; i < tItems.length ; i++ ){
    if ( (colIndex = mm.columnIndex(
      mm.mTable,tItems[i])) >= 0){
     viewColumns.push (colIndex);
    }
    else {
     alert (tItems[i] + ' is required for ' +
       vType + ' in tab ' + 
       mm.tabs[this.tabIndex] + 
     ' but was not found in the dictionary');
    }
   }      
   var gTable = new google.visualization.DataView(mm.mTable);
   //now filter rows
   var fItems = mm.cTabs[ mm.tabs[this.tabIndex] ]['filter'];

   if (fItems) {
    this.filters = new Array();
    for (var i=0; i < fItems.length ; i++){ 
     if ( (colIndex = mm.columnIndex(
       mm.mTable,fItems[i])) >= 0) {
      this.filters.push ({'column' : colIndex, 
       'value' : mm.columnValue(
         fItems[i],this.parent.cJobject[fItems[i]]) } );
     }
     else{
      alert (fItems[i] + ' is required filter for ' + 
        vType + ' in tab ' +
        mm.tabs[this.tabIndex] + 
      ' but was not found in the dictionary');
     }
    }
    gTable.setRows (gTable.getFilteredRows(this.filters));
   }
   if (!viewColumns.length) {
    alert ('there were no columns required for ' + 
      vType + ' in tab ' 
      + mm.tabs[this.tabIndex] );
    return(null);
   }
   if (needGroup) {
    var grouping = new Array();
    var aggregate = new Array();
    for (var i=0; i < viewColumns.length ;i++ ) {
     var lab;
     if (mm.isMeasure(
       lab=gTable.getColumnLabel(viewColumns[i]))) {
      aggregate.push ( {'column': viewColumns[i],
       'aggregation' : mcpherGag(mm.cMeasures[lab]),
       'type' : 'number'} );
     }
     else{
      grouping.push (viewColumns[i]);
     }
    }

    return( google.visualization.data.group (
      gTable, grouping, aggregate )) ;
   }
   else {   
    gTable.setColumns(viewColumns);
    return (gTable) ;
   }
  } 
 };

 mcpherInfoTab.prototype.dealWithSelection = function (vizTab,datTab) {  

  var row = vizTab.getSelection()[0].row;
  var newIndex = datTab.getTableRowIndex(row);
  
  // now we have the original item -  we may need oc lose this infowindow and go somewhere else
  var im = this.parent;
  var vm = im.parent;
  var spot = im.spot;
  var markerIndex = im.markerIndex;
  
  if (newIndex != markerIndex){

   switch (vm.provider){
   case 'maps':
    spot.infoWindow.close();
    break;
   case 'earth':
    break;
   default:
    alert('dealwithinsanity');
   }
   // so we may need to close this one
   var newSpot =vm.items[newIndex].spot;
   vm.gotoAnotherSpot (spot, newSpot);
   newSpot.usingIndex = newIndex;
   newSpot.createInfoWindow(0);
  }
 };

 mcpherInfoTab.prototype.createTable = function () {  
  return (this.viewTable ('table', false,true));
 };
 mcpherInfoTab.prototype.chartTable = function () {  
  return (this.viewTable ('chart',true,true));
 };
 mcpherInfoTab.prototype.orgTable = function () {  
  return (this.viewTable ('chart',false,true));
 };
 mcpherInfoTab.prototype.tweetTable = function () {  
  return (this.viewTable ('twitter',false,false));
 };
// END OF INFOTAB
// mcpherItem
// custom object to hold marker plus associated data
 var mcpherItem = function () {this.init();};
 mcpherItem.prototype.init = function () { 
  return (this);
 };
 mcpherItem.prototype.create = function (mm , markerIndex) {
  this.parent = mm;
  mMark=this;
  this.cJobject = this.parent.cJobject[markerIndex];
  this.spotId= this.cJobject.SpotID;
  this.markerIndex = markerIndex;
  // this will be the info box detail
  this.container = this.parent.tempDiv.appendChild(
    document.createElement('div'));
  this.container.cssName = this.parent.cssHide;
  this.container.id ='itemcon'+this.markerIndex;
  // to hold the tab selectors
  this.tabContainer = this.container.appendChild(
    document.createElement('div'));
  this.infoTabs = new Array (this.parent.tabs.length);

  // making tabbed infowindow

  this.ulContainer = this.tabContainer;
  for (var i = 0; i < this.infoTabs.length;i++ ){
   this.infoTabs[i] = new mcpherInfoTab();
   this.infoTabs[i].create (this,i);
  } 
  return this;
 };

 mcpherItem.prototype.fillDiv =function (conTab) {
  var mm = this;
  mm.refreshTweets(conTab);
  // change the selected tab and load up the appropriate divs
  for (var i=0; i < mm.infoTabs.length ; i++) { 
   if (conTab.tabIndex == i ) {
    mm.infoTabs[i].tab.className = mm.parent.cssTabSelected;
    mm.infoTabs[i].divContainer.className = 
     mm.parent.cssShow + ' ' + mm.parent.cssSelected;
   }
   else {
    mm.infoTabs[i].tab.className = mm.parent.cssTab;
    mm.infoTabs[i].divContainer.className = mm.parent.cssHide;
   }
  } 

 };
 mcpherItem.prototype.refreshTweets = function (conTab) {    
  var ie=conTab.infoElements['twitter'];
  var qt=conTab.tTable;
  var spot = this.spot;
  var vm = spot.parent;
  if (!ie)return(null);
  ie.innerHTML='';
  var sArg = "?";
  var sRest = "http://search.twitter.com/search.json";
  // create the query from any terms in the twitter table
  var sQ ='';
  if (qt){
   for (var i =0; i < qt.getNumberOfRows(); i++) {
    var sa='';
    for (var j =0; j < qt.getNumberOfColumns(); j++) {
     var s = qt.getValue(i,j).toString();
     if (s) {
      if(sa) sa += '+AND+';
      sa += s ;
     }
    }
    if (sa) {
     if (sQ) sQ += '+OR+';
     sQ += '(' + sa + ')';
    }
   }
  }
  if (sQ){
   sRest += sArg + "q=" + sQ;
   sArg = "&";
  }
  // location based
  if (vm.control.twitterradius){
   sRest += sArg + 'geocode=' + spot.lat + "," + spot.lng + "," + vm.control.twitterradius;
   sArg = "&";
  }
  //language selected
  if (vm.control.twitterlanguage){
   sRest += sArg + 'lang=' + vm.control.twitterlanguage;
   sArg = "&";
  }
  //limit
  if (vm.control.twittermaxtweets){
   sRest += sArg + 'rpp=' + vm.control.twittermaxtweets + '&page=1';
   sArg = "&";
  }

  $.getJSON(encodeURI(sRest) + '&callback=?',
    function(resp){
   $(ie).append('<table><th class="mctweetheader" colspan=2>' +
     '<a href="https://twitter.com/brucemcpherson">' + 
     'Follow ramblings.mcpher.com @brucemcpherson</a>' + 
     (vm.control.twitterradius ?  '<br>Tweets within ' + vm.control.twitterradius + ' of ' + spot.title:'') +
   '</th>');
   $.each(resp.results, function(i,item){
    $(ie).append('<tr><td class="mctweet">' +
      '<img src="'+item.profile_image_url+ '" ' +
      (mcpherIsNumber(vm.control.twitterimagewidth) ?  
        'width="' + vm.control.twitterimagewidth +'"' : '') +
      '"</img></td>'+
      '<td class="mctweet" rowspan="2">' + item.created_at+':'+item.text + 
      '</tr><tr><td class="mctweet">@' + item.from_user + ':</td>' +
    '</tr>');
   });
   $(ie).append('</table>');
  });
 };



// END OF mcpherItem

// mcpherSpot
// custom object to hold spot plus associated data
 var mcpherSpot = function (mp,idx,ddx) {this.init(mp,idx,ddx);};
 mcpherSpot.prototype.init = function (mp,idx,ddx) { 

  this.parent = mp;
  this.spotIndex = idx;
  this.usingIndex = ddx;
  this.cJobject = mp.cJobject[ddx];
  this.spotId = this.cJobject.SpotID;
  this.title = this.cJobject.Title;
  this.lat = parseFloat(this.cJobject.Latitude);
  this.lng = parseFloat(this.cJobject.Longtitude);
  return (this);
 };
 mcpherSpot.prototype.createSpot = function () {

  // create a marker on the map
  var vm = this.parent;
  var spot = this;

  switch (vm.provider){
  case 'maps':
   var gMarker = new google.maps.Marker({
    position: new google.maps.LatLng(this.lat,this.lng), 
    map: this.parent.map,
    title: this.title
   });
   this.marker = gMarker;
   google.maps.event.addListener(gMarker, 'click', 
     function(e)  { mcpherClicks(e,spot,true); } );
   break;
  case 'earth':
   vm.ve.plotSpot(spot);
   google.earth.addEventListener(spot.placemark, 'click', 
     function(e)  { mcpherClicks(e,spot,false); } );

   break;
  default:
   alert('createspotinsanity');
  break;
  }
  return this;
 };
 function mcpherClicks(e,spot,plot){

  if(e.preventDefault)e.preventDefault();
  if(e.stopPropagation)e.stopPropagation();
  if (spot.flying){
   var ge = spot.parent.ve.ge;
   spot.stopFlyAround();
   google.earth.addEventListener(ge.getView(), 
     'viewchangeend', mcpherFlyingClicks(spot,plot));
  }
  else {
   spot.dealWithClicks(plot);
  }
 }
 function mcpherFlyingClicks(spot,plot){
  var vm = spot.parent;
  var ge = vm.ve.ge;
  if(spot.timer) clearTimeout(spot.timer);
  spot.timer = setTimeout( function(){
   spot.dealWithClicks(plot);
  }, 100);
 }
 function mcpherPlotBalloon(spot, iw ){
  var vm = spot.parent;
  var ge = vm.ve.ge;
  vm.statusUpdate ('..waiting for view to settle down',true);
  google.earth.addEventListener(ge.getView(), 
    'viewchangeend', mcpherBalloon(spot,iw));
 }
 function mcpherBalloon(spot, iw ){
  var vm = spot.parent;
  var ge = vm.ve.ge;
  if(spot.timer) clearTimeout(spot.timer);
  spot.timer = setTimeout( function(){ 
   ge.setBalloon(iw);
   vm.statusUpdate ('..information balloon set for ' + spot.title,false);
  }, 100);
 }
 mcpherSpot.prototype.stopFlyAround = function() {
  var spot = this;

  // clean up after flying
  if (spot.flying) {
   var vm = spot.parent;
   var ve = vm.ve;
   var ge = ve.ge;
   spot.flying = false;   
   google.earth.removeEventListener(ge, 'frameend', 
     function () { mcpherFly(spot); } );
   spot.lookAt.setHeading(spot.initialLookAtHeading);
   spot.lookAt.setRange(parseFloat(vm.control.range)); 
   ge.getOptions().setFlyToSpeed(ge.SPEED_TELEPORT);
   ve.showLookAt(spot);
   vm.statusUpdate('..finished flying around '+spot.title,false);
  }
  return(spot.flying);
 }

 mcpherSpot.prototype.initFlyAround = function() {
  var spot = this;
  // been asked to rotate around the spot
  if (!spot.flying) {
   var vm = spot.parent;
   var ve = vm.ve;
   var ge = ve.ge;
   spot.flying = true;
   ge.setBalloon(null);
   spot.lookAt.setRange(parseFloat(vm.control.flyrange)); 
   ge.getOptions().setFlyToSpeed(parseFloat(vm.control.flyspeed));
   spot.initialLookAtHeading = spot.lookAt.getHeading();
   google.earth.addEventListener(ge, 'frameend', 
     function () { mcpherFly(spot); });
   vm.statusUpdate('..flying around '+spot.title,true);
   mcpherFly(spot);
  }  
 }
 function mcpherFly(spot){
  // whats the point of this?
  // seems that i can't addevent listener of spot.fly directly...
  spot.fly();
 }
 mcpherSpot.prototype.fly = function() { 
  // changing the heading and re- do
  var spot = this;
  if (spot.flying){
   var vm = spot.parent;
   var ve=vm.ve;
   var ge = ve.ge;
   var newHeading = spot.lookAt.getHeading() + 
   parseFloat(vm.control.flyincrement);
   if (newHeading > 360) {
    newHeading -= 360;
   }
   spot.lookAt.setHeading(newHeading);
   ve.showLookAt(spot);

  }
  return (spot.flying);
 }

 mcpherSpot.prototype.dealWithClicks = function(showit) { 
  var spot=this;
  var vm = this.parent;
  var doubleClickTimeout = 300;

  // now see if its the second click within the timeout period
  if (!vm.dealingWithClicks){ 
   vm.dealingWithClicks = true;
   // this is single click behavior
   setTimeout( function() 
     { if(vm.dealingWithClicks) {
      vm.dealingWithClicks = false;
      spot.createInfoWindow  (0); }}
   , doubleClickTimeout);
  }
  else { 
   // double click behavior
   vm.dealingWithClicks = false;
   vm.gotoAnotherSpot(spot,vm.nextSpot(spot));
   if(showit)vm.nextSpot.createInfoWindow(0);
  }

  return(false);
 };

 mcpherSpot.prototype.createInfoWindow = function (startTabIndex){
  //default is the first tab
  var vm = this.parent;
  var spot=this;
  var gMap = spot.parent.map;
  var uItem = vm.items[spot.usingIndex];
  var infoTab = uItem.infoTabs[startTabIndex];

  vm.statusUpdate('..creating info for '+spot.title,true);
  // select the appropriate tab
  uItem.fillDiv(infoTab);
  // set the content
  var iDiv = document.createElement('div');
  this.container = iDiv;
  iDiv.appendChild (uItem.container);
  iDiv.className = vm.cssShow + ' ' + vm.cssInfoWindow;
  var s= '..plotted info for '+spot.title;

  switch (vm.provider){
  case 'maps':
   var iw = spot.infoWindow = new google.maps.InfoWindow (); 
   iw.setContent (iDiv);
   iw.open(gMap,spot.marker);
   google.maps.event.addListener(iw, 'domready', function() {
    vm.statusUpdate(s,false,null);
   }); 
   break;

  case 'earth':
   var ge = vm.vEarth.ge;
   var iw = spot.balloon = ge.createHtmlDivBalloon('');
   iw.setFeature(spot.placemark);
   iw.setContentDiv(iDiv);
   mcpherPlotBalloon(spot, iw );
   //ge.setBalloon(iw)

   break;

  default:
   alert ('createinfowindowinstanity');
  break;
  }
  return (iw);
 };

// endof mcpherspot

// mcpherEarth
 var mcpherEarth = function (vm,control) {
  if (control == 'google') {
   if (google.earth.isInstalled()){  
    this.vMap = vm;
    vMap.ve = this;
    this.control = control;
    return this;
   }
   else {
    alert("you need to install the google earth plug-in");
    return null;
   }
  }
  else {
   alert ('unknown earth provider ' + control);
   return null;
  }
 };

 mcpherEarth.prototype.createInstance  = function (div) {
  var ve=this;
  var vm = this.vMap;
  ve.container = div;
  vm.statusUpdate('..creating google earth instance',true,null);
  google.earth.createInstance(ve.container,  
    initCallback, failureCallback);
 };

 mcpherEarth.prototype.success  = function (instance) {
  var ve = this;
  var vm = ve.vMap;
  // the first spot
  var spot = vm.spots[0];
  ve.ge = ge = instance;
  ge.getWindow().setVisibility(true);
  // add some layers
  ge.getLayerRoot().enableLayerById(ge.LAYER_ROADS, true);
  ge.getLayerRoot().enableLayerById(ge.LAYER_BUILDINGS, true);
  ge.getNavigationControl().setVisibility(ge.VISIBILITY_AUTO);
  if (vm.control.adjustlightfortimeofday == 'yes'){
   ge.getSun().setVisibility(true);
   ge.getOptions().setAtmosphereVisibility(true);
  }
  vm.statusUpdate('..google earth successfully created',false,null);
  vm.spotEverything();
  // go to the first spot
  ve.showLookAt(spot);

 };

 mcpherEarth.prototype.plotSpot = function (spot) {
  var ve = this;
  spot.lookAt = ve.makeLookAt(spot);
  spot.placemark = ve.makePlacemark (spot);
  
  return(spot);
 };
 mcpherEarth.prototype.makeLookAt = function (spot) {
  var ve= this;
  var ge = ve.ge;
  var vm = this.vMap;
  // look at something
  var lookAt = ge.getView().copyAsLookAt (ge.ALTITUDE_RELATIVE_TO_GROUND); 
  // Set the position values
  lookAt.setLatitude(spot.lat);
  lookAt.setLongitude(spot.lng);
  lookAt.setRange(parseFloat(vm.control.range)); 
  lookAt.setTilt(parseFloat(vm.control.tilt));
  return (lookAt);
 };

 mcpherEarth.prototype.makePlacemark = function (spot) {
  //var ge = this.ge;
  var ge= this.ge;
  var vm = this.vMap;
  var uItem = vm.items[spot.usingIndex];
  var marker = vm.spotControl['earth marker'];

  var placemark = ge.createPlacemark('');
  placemark.setName(spot.title);

  // Create style map for placemark
  if(marker){
   var image = uItem.cJobject[marker];
   if (image) {
    if (mcpherIsImage(image)){
     var icon = ge.createIcon('');
     icon.setHref(image);
     var style = ge.createStyle('');
     style.getIconStyle().setIcon(icon);
     // make marker bigger than usual
     if (vm.control.placemarkscale){
      var x;
      if(x=mcpherToNumber(vm.control.placemarkscale)){
       style.getIconStyle().setScale(x);
      }
     }
     placemark.setStyleSelector(style);
    }
    else {
     alert (image + ' is not a valid image link to use as a marker');
    }
   }
  }
  
  var point = ge.createPoint('');
  point.setLatitude(spot.lookAt.getLatitude());
  point.setLongitude(spot.lookAt.getLongitude());
  placemark.setGeometry(point);
  ge.getFeatures().appendChild(placemark);
  return(placemark);
 };  

 mcpherEarth.prototype.showLookAt = function (spot) {
  var lookAt = spot.lookAt;
  this.ge.getView().setAbstractView(lookAt);
  return (lookAt);
 }; 

 mcpherEarth.prototype.failure = function (errorCode) {
  var vm = this.vMap;
  alert('error loading google earth' + errorCode);
  //vm.statusUpdate('..google earth failed',false,'error ' + errorCode);
 };
 function initCallback (instance) {
  vMap.ve.success(instance);
 }
 function failureCallback (errorCode) {
  vMap.ve.failure(errorCode);
 }

// END of mcpherEarth
// MCPHERFUNCTIONS
 function mcpherAddEvent (o,e,f,b,complain){
  // because IE is different
  if(o.addEventListener) 
   return(o.addEventListener(e, f, b));

  else 
   if (o.attachEvent) 
    return(o.attachEvent('on' + e, f));
   else 
    if (complain) 
     alert ('browser doesnt support events');


  return(null);

 }
 function mcpherIsNumber(x){
  return (!isNaN(parseFloat(x))); 
 }
 function mcpherToNumber(x) {
  if (mcpherIsNumber(x))
   return (parseFloat(x));
  else
   return (null);
 }
 function mcpherToDate(x) {
  if (mcpherIsDate(x))
   return (new Date(Date.parse(x)));
  else
   return (null);
 }
 function mcpherIsImage(url) {
  return ((/http:\/\/.+?\.jpg|jpeg|png|gif/).test (url.toLowerCase()) );

 }
 function mcpherSize(o) {
  var s=0;
  for (var key in o) if (o.hasOwnProperty(key))s++;
  return (s);
 }
 function mcpherIsDate(s){
  return (!isNaN(Date.parse(s))); 
 }
 function mcpherGetqparams(){
  // this is just a utility to pick up command line params
  var qparams = new Array();
  var htmlquery = window.location.search.substring(1);
  var htmlparams = htmlquery.split('&');
  for ( var i=0; i < htmlparams.length;i++) {
   var k = htmlparams[i].indexOf('=');
   if (k > 0) {
    qparams[ htmlparams[i].substring(0,k) ] = 
     decodeURI(htmlparams [i].substring(k+1));
   }
  }
  return qparams;
 } 
 function mcpherGag(x){
  switch (x) {
  case 'average':
   return (google.visualization.data.avg);
  case 'count':
   return (google.visualization.data.count);
  case 'sum':
   return (google.visualization.data.sum);
  case 'min':
   return (google.visualization.data.min);
  case 'max':
   return (google.visualization.data.max);
  default:
   alert('Unknown google aggregation request ' + x);
  return (null);
  }

 }
 function oShow (x,o) {
  var s =x+'(object)';
  for (var key in o) s += key + ",";
  s+='(array)';
  for (var i=0; i < o.length; i++) s += o[i] + ',';
  s+='(size)' + mcpherSize(o) + '(length)' + o.length;
  alert (s);

 }


};
</script></head>
<body style="font-family: sans-serif;">

 <b>VizMap Venues Demonstration Application Excel to Google Mapping</b>
 <br>For More details
 <a href="http://rambings.mcpher.com">Visit Excel and Google
  Ramblings</a>
 <div id="status_div" class ="mcstatus"></div>
 <div id="map_canvas" style="width: 100%; height: 90%"></div>
    
</body>
</html>

For help and more information join our forum, follow the blog, follow me on Twitter