Rotating globe in JavaScript

<?xml version="1.0" encoding="utf-8" ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>rotating globe</title><!-- misc.js --><script language="JavaScript" >/*------------------------------------------------------------------------------Copyright (c) 2005 Tyrell Corporation.This file is part of the Locative Blog project.http://locblog.sourceforge.net/Author : $Author: darkeye $Version : $Revision: 1.1.1.1 $Location : $Source: /cvsroot/locblog/rotatingGlobe/var/public_html/misc.js,v $Abstract : Utility functions.Copyright notice:This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2of the License, or (at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.------------------------------------------------------------------------------*//*** Constructor to reate an object, based on a named element from the document,* browser-independently.* The created object will have two properties:* obj - the named object itself* style - the stylesheet object for the named object.** based on http://www.quirksmode.org/js/dhtmloptions.html** @param name the name of the element to create this object upon.*/function getObj(name){if (document.getElementById) {this.obj = document.getElementById(name);this.style = document.getElementById(name).style;} else if (document.all) {this.obj = document.all[name];this.style = document.all[name].style;} else if (document.layers) {this.obj = document.layers[name];this.style = document.layers[name];}}/*** Find the X coordinate of an element in the document.* based on http://www.quirksmode.org/js/findpos.html** @param obj the element in the document.* @return the x coordinate for this element.*/function findPosX(obj){var curleft = 0;if (obj.offsetParent) {while (obj.offsetParent) {curleft += obj.offsetLeftobj = obj.offsetParent;}} else if (obj.x) {curleft += obj.x;}return curleft;}/*** Find the Y coordinate of an element in the document.* based on http://www.quirksmode.org/js/findpos.html** @param obj the element in the document.* @return the x coordinate for this element.*/function findPosY(obj){var curtop = 0;if (obj.offsetParent) {while (obj.offsetParent) {curtop += obj.offsetTopobj = obj.offsetParent;}} else if (obj.y) {curtop += obj.y;}return curtop;}/*** Replace the contenst of a DOM element.* based on http://www.quirksmode.org/js/layerwrite.html** @param id the id of the element.* @param text the new text of the element.*/function replaceContent(id, text){if (document.getElementById) {x = document.getElementById(id);x.innerHTML = '';x.innerHTML = text;} else if (document.all) {x = document.all[id];x.innerHTML = text;} else if (document.layers) {x = document.layers[id];text2 = '<P CLASS="testclass">' + text + '</P>';x.document.open();x.document.write(text2);x.document.close();}}/*** Function to calculate the X coordinate of an orthographic projection.* based on http://mathworld.wolfram.com/OrthographicProjection.html** @param phi the lattitude of the point on the sphere* @param lambda the longitude of the point on the sphere* @param phi1 the lattitude of the viewpoint* @param lambda1 the longitued of the viewpoint* @return the x coordinate of the specified point.*/function orthographicX(phi, lambda, phi1, lambda1){var x = Math.cos(phi) * Math.sin(lambda - lambda1);return x;}/*** Function to calculate the Y coordinate of an orthographic projection* based on http://mathworld.wolfram.com/OrthographicProjection.html** @param phi the lattitude of the point on the sphere* @param lambda the longitude of the point on the sphere* @param phi1 the lattitude of the viewpoint* @param lambda1 the longitued of the viewpoint* @return the y coordinate of the specified point.*/function orthographicY(phi, lambda, phi1, lambda1){var y = Math.cos(phi1) * Math.sin(phi)- Math.sin(phi1) * Math.cos(phi) * Math.cos(lambda - lambda1);return y;}/*** Helper function to convert a latitude or longitued into radii.** @param degrees the latitude or longitude* @return the same value, in radii*/function toRadii(degrees){return (degrees / 180.0) * Math.PI;}</script><!-- GeoLocation.js --><script language="JavaScript">/*------------------------------------------------------------------------------Copyright (c) 2005 Tyrell Corporation.This file is part of the Locative Blog project.http://locblog.sourceforge.net/Author : $Author: darkeye $Version : $Revision: 1.1.1.1 $Location : $Source: /cvsroot/locblog/rotatingGlobe/var/public_html/GeoLocation.js,v $Abstract : A class representing a geogpraphical location.Copyright notice:This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2of the License, or (at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.------------------------------------------------------------------------------*//*** Constructor for a geographic location, with a link.** @param id the id of the location (no spaces, etc.)* @param name the name of the location* @param longitude the longitude coordinate of the location* @param latitude the latitude coordinate of the location* @param url the url for more info on the location*/function GeoLocation(id,name,longitude,latitude,url){// the id of the locationthis.id = id;// the name of the locationthis.name = name;// the longitude coordinate of the locationthis.longitude = longitude < 0 ? 360 + longitude : longitude;// the latitude coordinate of the locationthis.latitude = latitude;// the url for more info on the locationthis.url = url;// the image used to mark the location visuallythis.markerImage = 'globeImages/5x5.png';// the width of the marker imagethis.markerWidth = 5;// the height of the marker imagethis.markerHeight = 5;// the x coordinate of this location, relative to the page windowthis.x = 0;// the y coordinate of this location, relative to the page windowthis.y = 0;// create the CSS definition for this locationthis.styleDefinition = geoLocationStyleDefinition;// create the HTML element source for this locationthis.htmlDefinition = geoLocationHtmlDefinition;// display the location object on a RotatingGlobe objectthis.display = geoLocationDisplay;// move the marker depiciting the location to the specified positionthis.moveMarker = geoLocationMoveMarker;// hide the marker depiciting the locationthis.hideMarker = geoLocationHideMarker;// show the location popup near the geo locationthis.showPopup = geoLocationShowPopup;// hide the location popup near the geo locationthis.hidePopup = geoLocationHidePopup;// Tell if the longitude coordinate of the GeoLocation object is visible// from a reference viewpoint.this.longitudeVisible = geoLocationLongitudeVisible;}/*** Create the CSS definition for this location** @return the CSS definition for this location, as a string*/function geoLocationStyleDefinition(){return '#' + this.id + '{'+ ' position: absolute; '+ ' top: 0px; '+ ' left: 0px; '+ '}n';}/*** Create the HTML element source for this location** @return the HTML element source for this location, as a string.*/function geoLocationHtmlDefinition(){return '<a id="' + this.id + '" href="' + this.url + '">'+ '<img name="' + this.id + '" alt="' + this.name + '" '+ 'src="' + this.markerImage + '" '+ 'width="' + this.markerWidth + '" '+ 'height="' + this.markerHeight + '" border="0"/>'+ '</a>n';}/*** Move the marker depicting the location to the specified coordinates.*/function geoLocationMoveMarker(){var locationObject = new getObj(this.id);locationObject.style.top = this.y + "px";locationObject.style.left = this.x + "px";locationObject.style.visibility = "visible";}/*** Hide the Marker depicting the location.*/function geoLocationHideMarker(){var locationObject = new getObj(this.id);locationObject.style.visibility = "hidden";}/*** Show the popup text near the location.*/function geoLocationShowPopup(){replaceContent("locationPopup", this.name);var popup = new getObj("locationPopup");// display a bit to the right and abovepopup.style.left = (this.x + 10) + "px";popup.style.top = (this.y - 15) + "px";popup.style.visibility = "visible";}/*** Hide the popup near the location.*/function geoLocationHidePopup(){var popup = new getObj("locationPopup");popup.style.visibility = "hidden";}/*** Display the location on the map.** @param globe a RotatingGlobe object, on which to display the location*/function geoLocationDisplay(globe){longStepSize = 360.0 / globe.maxPosition;viewLongitude = globe.position * longStepSize;if (!this.longitudeVisible(viewLongitude)) {this.hideMarker();return;}lambda = toRadii(this.longitude);phi = toRadii(this.latitude);lambda1 = toRadii(viewLongitude);phi1 = toRadii(0);x = (orthographicX(phi, lambda, phi1, lambda1) * (globe.realGlobeWidth/2))+ (globe.globeImage.width / 2);y = globe.globeImage.height- ((orthographicY(phi, lambda, phi1, lambda1) * (globe.realGlobeHeight/2))+ (globe.globeImage.height / 2) );this.x = globe.globeImageX + x;this.y = globe.globeImageY + y;this.moveMarker();}/*** Tell if the longitude coordinate of the GeoLocation object is visible* from a reference viewpoint.* It's not visible if it would be "on the other side of the planet"** @param refLongitude the reference view point, to check from.* @return true if longitude is visible, false otherwise*/function geoLocationLongitudeVisible(refLongitude){var westEdge = refLongitude - 90;var eastEdge = refLongitude + 90;if (westEdge >= 0 && eastEdge < 360) {return westEdge <= this.longitude && this.longitude <= eastEdge;} else if (eastEdge >= 360) {eastEdge -= 360;return westEdge <= this.longitude || this.longitude <= eastEdge;} else {westEdge += 360;return westEdge <= this.longitude || this.longitude <= eastEdge;}}</script><!-- RotatingGlobe.js --><script language="JavaScript">/*------------------------------------------------------------------------------Copyright (c) 2005 Tyrell Corporation.This file is part of the Locative Blog project.http://locblog.sourceforge.net/Author : $Author: darkeye $Version : $Revision: 1.1.1.1 $Location : $Source: /cvsroot/locblog/rotatingGlobe/var/public_html/RotatingGlobe.js,v $Abstract : A rotating globe JavaScript object.Copyright notice:This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2of the License, or (at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.------------------------------------------------------------------------------*//*** Event handler for a preloaded image finishing loading.* This function expects the rotating globe, for which preloaded images* are checked, being available at the global variable named 'globe'.** @return true if all images are loaded, false otherwise.*/function globeImageLoaded(){if (globe.imagesLoaded()) {globe.init();return true;}return false;}/*** Start dragging the image by the mouse move.* This function expects the rotating globe at a global variable named 'globe'*/function globeDragImage(){globe.startDragImage();document.globe.onmousemove = globeTrackMouseMove;}/*** Stop dragging the image by the mouse move.* This function expects the rotating globe at a global variable named 'globe'*/function globeUndragImage(){document.globe.onmousemove = null;globe.stopDragImage();}/*** Track the mouse movement, and change the globe view if necessary.* This function expects the rotating globe at a global variable named 'globe'** @param event the mouse movement event.*/function globeTrackMouseMove(event){// first tell the current mouse position, possibly in all browsers// thanks to http://www.quirksmode.org/js/events_properties.htmlvar posx = 0;var posy = 0;if (!event) {var event = window.event;}if (event.pageX || event.pageY) {posx = event.pageX;posy = event.pageY;} else if (event.clientX || event.clientY) {posx = event.clientX + document.body.scrollLeft;posy = event.clientY + document.body.scrollTop;}// display the detected coordinates, for debug reasonsdocument.test.x.value = posx;document.test.y.value = posy;globe.updateView(posx, posy);}/*** The contructor class depicting the rotating globe.** @param globeImage the globe <img/> tag in the page,* that holds the globe images.* @param globeBorder the number of pixels the image has a border around* the enclosed globe. it is expected that the image has a globe at* its center, which is surrounded by a border on each side with* this many pixels. thus the actual globe is* globeImage.width - (2 * globeBorder) wide, and* globeImage.height - (2 * globeBorder) in height.* @param locations an array holding GeoLocation object, the list of* interesting locations.*/function RotatingGlobe(globeImage, globeBorder, locations){// the <img/> element in the page, holding the globe imagesthis.globeImage = globeImage;// the size of the border around the globe in the imagethis.globeBorder = globeBorder;// the interesting locations on the globethis.locations = locations;// the X coordinate of the upper-left corner of the globe image// in the browser windowthis.globeImageX = findPosX(document.globe);// the Y coordinate of the upper-left corner of the globe image// in the browser windowthis.globeImageY = findPosY(document.globe);// the real height of the globe itself, inside the imagethis.realGlobeHeight = this.globeImage.width - (2 * this.globeBorder);// the real width of the globe itself, inside the imagethis.realGlobeWidth = this.globeImage.height - (2 * this.globeBorder);// the number of view positions availablethis.maxPosition = 36;// the current view positionthis.position = 0;// the prefix for each globe image// the image names are: imagePrefix + position + imagePostfixthis.imagePrefix = "globeImages/globe";// the postfix for each globe image// the image names are: imagePrefix + position + imagePostfixthis.imagePostfix = ".jpg";// the globe images, as an arraythis.images = Array(this.maxPosition);// the x coordinate, which if passed by the mouse, triggers a move to eastthis.eastThreshold = 0;// the x coordinate, which if passed by the mouse, triggers a move to westthis.westThreshold = 0;// the size of the x coordinate window to trigger a move to east or west// by the mouse movementthis.thresholdStep = 20;// a flag indicating if this object has already been initializedthis.initialized = false;// preload all the images for the globethis.preloadImages = rotatingGlobePreloadImages;// tell if all images that are to be preloaded have completed loading.this.imagesLoaded = rotatingGlobeImagesLoaded;// initialize the globe, after all the images have been loadedthis.init = rotatingGlobeInit;// start mouse draggingthis.startDragImage = rotatingGlobeStartDragImage;// stop mouse draggingthis.stopDragImage = rotatingGlobeStopDragImage;// update the view point according to the new mouse coordinates, if neededthis.updateView = rotatingGlobeUpdateView;// function to return the name of an image for a specified positionthis.imageName = rotatingGlobeImageName;// rotate the viewpoint one step to the eastthis.rotateEast = rotatingGlobeRotateEast;// rotate the viewpoint one step to the westthis.rotateWest = rotatingGlobeRotateWest;// reset the mouse movement thresholds triggering view point changethis.resetThreshold = rotatingGlobeResetThreshold;// display the locationsthis.displayLocations = rotatingGlobeDisplayLocations;}/*** Function returning the name of an image for a specified position** @param position the view position to get the image for, must be* in the interval [0:maxPosition[* @return the name of the appropariate globe image, as a string*/function rotatingGlobeImageName(position){return this.imagePrefix + position + this.imagePostfix;}/*** Rotate the current viewpoint one step to the east.*/function rotatingGlobeRotateEast(){this.position = (++this.position) % this.maxPosition;this.globeImage.src = this.images[this.position].src;this.displayLocations();}/*** Rotate the current viewpoint one step to the east.*/function rotatingGlobeRotateWest(){this.position = (--this.position) % this.maxPosition;if (this.position < 0) {this.position += this.maxPosition;}this.globeImage.src = this.images[this.position].src;this.displayLocations();}/*** Preload all the images, so that they are in the browsers cache.*/function rotatingGlobePreloadImages(){this.initialized = false;for (i = 0; i < this.maxPosition; ++i) {this.images[i] = new Image(this.globeImage.width,this.globeImage.height);this.images[i].onload = globeImageLoaded;this.images[i].src = this.imageName(i);}}/*** Tell if all images that are to be preloaded have completed loading.** @return true if all images have been loaded, false otherwise*/function rotatingGlobeImagesLoaded(){var allCompleted = true;for (i = 0; i < this.maxPosition; ++i) {if (!this.images[i].complete) {allCompleted = false;break;}}return allCompleted;}/*** Initialize the globe, after all the images have been loaded.* This means grabbing event handlers, etc.*/function rotatingGlobeInit(){if (this.initialized) {return;}this.thresholdStep = this.globeImage.width / (this.maxPosition + 1);this.globeImage.onmousedown = globeDragImage;this.globeImage.onmouseup = globeUndragImage;this.globeImage.src = this.images[0].src;this.initialized = true;for (i = 0; i < locations.length; ++i) {var obj = new getObj(locations[i].id);obj.obj.onmouseover = locationShowPopup;obj.obj.onmouseout = locationHidePopup;}this.displayLocations();}/*** Find a location in an array of locations, by its id.** @param locations an array of GeoLocation objects.* @param id the id of the location to look for* @return the requested GeoLocation object, or null if not found.*/function getLocationById(locations, id) {for (i = 0; i < locations.length; ++i) {if (locations[i].id == id) {return locations[i];}}return null;}/*** Event hanlder for showing the popup near a location.** @param event the event triggering this call. it is expected that the event* target name is a valid GeoLocation id in the locations array.*/function locationShowPopup(event) {geoLocation = getLocationById(locations, event.target.name);if (geoLocation != null ) {geoLocation.showPopup();}}/*** Event hanlder for hiding the popup near a location.** @param event the event triggering this call. it is expected that the event* target name is a valid GeoLocation id in the locations array.*/function locationHidePopup(event) {geoLocation = getLocationById(locations, event.target.name);if (geoLocation != null ) {geoLocation.hidePopup();}}/*** Reset the mouse movement thresholds. Set the new limits for when* to change the view point, if the mouse move beyonds these limints.** @param posx the current position, from where the relative thresholds* have to be set up.*/function rotatingGlobeResetThreshold(posx){this.eastThreshold = posx - this.thresholdStep;this.westThreshold = posx + this.thresholdStep;}/*** Start mouse dragging*/function rotatingGlobeStartDragImage(){this.eastThreshold = 0;this.westThreshold = 0;}/*** Stop mouse dragging.*/function rotatingGlobeStopDragImage(){this.eastThreshold = 0;this.westThreshold = 0;}/*** Update the view point according to the new mouse coordinates, if needed.** @param x the new x coordinate for the mouse* @param y the new y coordinate for the mouse*/function rotatingGlobeUpdateView(x, y){// if this is the first event after started dragging, initialize// the appropriate thresholdsif (this.eastThreshold == 0 && this.westThreshold == 0) {this.resetThreshold(x);}// change the view point, if neededif (x > this.westThreshold) {this.rotateWest();this.resetThreshold(x);} else if (x < this.eastThreshold) {this.rotateEast();this.resetThreshold(x);}}/*** Display all interesting locations on the map.*/function rotatingGlobeDisplayLocations(){for (i = 0; i < locations.length; ++i) {this.locations[i].display(this);}}</script><script language="JavaScript">/*** The global rotating globe object, the event handlers depend on this.*/var globe;/*** The locations.*/var locations = Array();</script><!-- locations.js --><script language="JavaScript">/*------------------------------------------------------------------------------Copyright (c) 2005 Tyrell Corporation.This file is part of the Locative Blog project.http://locblog.sourceforge.net/Author : $Author: darkeye $Version : $Revision: 1.1.1.1 $Location : $Source: /cvsroot/locblog/rotatingGlobe/var/public_html/locations.js,v $Abstract : A sample location data definition file.Copyright notice:This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2of the License, or (at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.------------------------------------------------------------------------------*/locations.push(new GeoLocation("amsterdam", "Amsterdam", 4.54, 52.23, "/amsterdam"));locations.push(new GeoLocation("bangkok", "Bangkok", 100.50, 13.73, "/bangkok"));locations.push(new GeoLocation("budapest", "Budapest", 19.05, 47.50, "/budapest"));locations.push(new GeoLocation("chiang_mai", "Chiang Mai", 99.03, 18.82, "/chiang_mai"));locations.push(new GeoLocation("linz", "Linz", 14.18, 48.19, "/linz"));locations.push(new GeoLocation("london", "London", -0.16, 51.50, "/london"));locations.push(new GeoLocation("luang_prabang", "Luang Prabang", 102.14, 19.88, "/luang_prabang"));locations.push(new GeoLocation("new_york", "New York", -74.00, 40.70, "/new_york"));locations.push(new GeoLocation("Paris", "Paris", 2.33, 48.87, "/paris"));locations.push( new GeoLocation("philadelphia", "Philadelphia", -75.12, 39.96, "/philadelphia"));locations.push( new GeoLocation("prague", "Prague", 14.43, 50.10, "/prague"));locations.push( new GeoLocation("rotterdam", "Rotterdam", 4.29, 51.55, "/rotterdam"));locations.push( new GeoLocation("taipei", "Taipei", 121.36, 25.01, "/taipei"));locations.push( new GeoLocation("tokyo", "Tokyo", 139.50, 35.75, "/tokyo"));locations.push( new GeoLocation("vienna", "Vienna", 16.36, 48.20, "/vienna"));locations.push( new GeoLocation("washington", "Washington DC", -77.03, 38.88, "/washington"));</script><script language="JavaScript">/*** Initialize the page, after it has loaded.*/function onPageLoad(){globe = new RotatingGlobe(document.globe, 30, locations);// this sets up event handlers for the preloaded imagesglobe.preloadImages();// as not all event handlers are called (damn these lame browswers)// set a timout value and call the load image hanlder ourselvesmakeSureImagesAreLoaded()}/*** Make sure all images are loaded, by checking on this regularly.*/function makeSureImagesAreLoaded(){if (!globeImageLoaded()) {// if not ready, check a second later as wellsetTimeout("makeSureImagesAreLoaded()", 1000);}}/*** Rotate the globe one step to the east.*/function rotateEast(){globe.rotateEast();}/*** Rotate the globe one step to the west.*/function rotateWest(){globe.rotateWest();}</script><script language="JavaScript">// create the style sheet definition for each geo locationdocument.write('<style type="text/css">');for (i = 0; i < locations.length; ++i) {document.write(locations[i].styleDefinition());}document.write('</style>');</script><style type="text/css">#locationPopup {position: absolute;top: 0px;left: 0px;visibility: hidden;font: 14px arial;font-weight: bold;color: yellow;}</style></head><body onLoad="onPageLoad();"><a href="#" onClick="javascript:rotateWest(); return false;">west</a>|<a href="#" onClick="javascript:rotateEast(); return false;">east</a><br/><img name="globe" src="images/loading.png" width="650" height="650" alt="globe"/><br/><form name="test">X: <input type="text" name="x" size="4">Y: <input type="text" name="y" size="4"></form><script language="JavaScript">// create the HTML element for each geo locationfor (i = 0; i < locations.length; ++i) {document.write(locations[i].htmlDefinition());}</script><div id="locationPopup"></div></body>