//============================================================================= // // File: cartae.js // // Language: JavaScript // // Contents: Cartae is a cartographic display class designed to display // map images within a DIV of fixed size: 516px wide and 413px high. // Each image overlaps its neighbor by two-thirds (344px width and 275px height). // The encoded extent of the images is expressed in seconds from the Prime Meridian or equator // // Dependencies: bif.gestures.js // bif.event.js // // Note: Since this creates elements with fixed names, there can't be more than // one of these in a document // // Author: Joe Honton © 2009 // // Initial date: Jun 26, 2009 // //============================================================================= /* // The CSS that should accompany this includes: .cs_516map { margin-left: 214px; width: 516px; height: 413px; overflow: hidden; cursor: crosshair; border: solid 1px $kuler4; } .cs_516map:hover { border: solid 1px $burnt_orange; } .cs_516map:hover .cs_map_nav_bu {opacity: 0.3; filter:alpha(opacity=30);} .cs_516map:hover .cs_map_nav_bu:hover {opacity: 1.0; filter:alpha(opacity=100); cursor: pointer;} .cs_516map:hover .cs_zoom_bu { opacity: 0.2; filter:alpha(opacity=20); } .cs_516map:hover .cs_zoom_bu:hover { opacity: 1.0; filter:alpha(opacity=100); cursor: pointer; } .cs_map_nav_bu { opacity: 0.0; filter:alpha(opacity=0); } #id_west_bu { background: url(map-sprite.gif) no-repeat -18px 0px; width: 16px; height: 32px; } #id_west_bu:active { background: url(map-sprite.gif) no-repeat 0px 0px; } #id_east_bu { background: url(map-sprite.gif) no-repeat -18px -36px; width: 16px; height: 32px; } #id_east_bu:active { background: url(map-sprite.gif) no-repeat 0px -36px; } #id_north_bu { background: url(map-sprite.gif) no-repeat 0px -82px; width: 32px; height: 16px; } #id_north_bu:active { background: url(map-sprite.gif) no-repeat 0px -64px; } #id_south_bu { background: url(map-sprite.gif) no-repeat 0px -118px; width: 32px; height: 16px; } #id_south_bu:active { background: url(map-sprite.gif) no-repeat 0px -100px; } .cs_zoom_bu { background: url(map-sprite.gif) no-repeat 0px -136px; width: 26px; height: 26px; } .cs_zoom_bu { opacity: 0.0; filter:alpha(opacity=0); } .cs_zoom_bu:active { background: url(map-sprite.gif) no-repeat 0px -164px; } */ //----------------------------------------------- // namespace var bif; if (!bif) bif = {}; //----------------------------------------------- // Constructor //> idMap is the HTML element ID for the div, something like:
bif.Cartae = function( idMap ) { // local constants var c_buttonDimension16 = 16; // height of north and south button, width of east and west buttons var c_buttonDimension32 = 32; // width of north and south button, height of east and west buttons // object constants this.c_mapWidth = 516; // canvas and map chicklet width this.c_mapHeight = 413; // canvas and map chicklet height this.c_mapScrollAmountWidth = 172; // 1/3 (the amount to scroll) this.c_mapScrollAmountHeight = 138; // 1/3 (the amount to scroll) this.c_mapOverlapAmountWidth = 344; // 2/3 overlap with its neighbors this.c_mapOverlapAmountHeight = 275; // 2/3 overlap with its neighbors // Smooth movement of new map into the viewport this.smoothMovement = null; // the SmoothMovement object this.smoothTimer = null; // the timer for smooth movement this.idViewport = idMap; this.viewport = document.getElementById( idMap ); // the element in the document that holds everything this.imageDirectory = ''; // the location of the map images: either relative the the URL root, or a URI with http protocol. Be sure to set this before calling showMap this.encodedExtent = null; // the string corresponding to the zoomfactor, quadrant, base_latitude, base_longitude this.zoomfactor = null; // a scaling factor (2,3,4,5,6,7,8) that corresponds to 1/4, 1/2, 1, 2, 4, 8, or 16 degrees latitude spanned by the image. this.quadrant = null; // 1 = NE, 2 = NW, 3 = SE, 4 = SW this.base_latitude = null; // the latitude of the principle corner, in seconds from the equator this.base_longitude = null; // the longitude of the principle corner, in seconds from the Prime Meridian this.scroll_latitude = null; // the number of seconds latitude from this base_latitude to the next map's base_latitude this.scroll_longitude = null; // the number of seconds longitude from this base_longitude to the next map's base_longitude this.coverage_latitude = null; // the number of seconds latitude, spanned by this map this.coverage_longitude = null; // the number of seconds longitude, spanned by this map this.zoomin_enabled = 'N'; this.zoomout_enabled = 'N'; this.scroll_exists = 'NNNNNNNN'; // can the map be scrolled N,NE,E,SE,S,SW,W,NW // Two inner DIVs used to display the current map, and the next map, using background images var map = this.createMapChicklets( 'id_center_map' ); this.createMapChicklets( 'id_next_map' ); // A third DIV, placed on top, for use as a rubberband selector var rb = this.createMapChicklets( 'id_gestures' ); rb.bifExtensionObject = new bif.Gestures( 'id_gestures' ); // extend the element to have special behavior via bifExtensionObject // stuff a pointer to "this" into the element for use by custom event messages rb.bifCartaeObject = this; bif.event.custom( 'id_gestures', 'gesture_click', this.onClickMap ); bif.event.custom( 'id_gestures', 'gesture_rightclick', this.onRightClickMap ); bif.event.custom( 'id_gestures', 'gesture_scroll_north', this.onClickNorth ); bif.event.custom( 'id_gestures', 'gesture_scroll_south', this.onClickSouth ); bif.event.custom( 'id_gestures', 'gesture_scroll_east', this.onClickEast ); bif.event.custom( 'id_gestures', 'gesture_scroll_west', this.onClickWest ); bif.event.custom( 'id_gestures', 'gesture_scroll_northeast', this.onClickNorthEast ); bif.event.custom( 'id_gestures', 'gesture_scroll_northwest', this.onClickNorthWest ); bif.event.custom( 'id_gestures', 'gesture_scroll_southeast', this.onClickSouthEast ); bif.event.custom( 'id_gestures', 'gesture_scroll_southwest', this.onClickSouthWest ); // margin, as defined by the user's placement of the element in the document this.topMargin = map.offsetTop; this.leftMargin = map.offsetLeft; // four navigation buttons this.createNavButtons( 'id_west_bu', 'cs_map_nav_bu', this.leftMargin, (this.topMargin + (this.c_mapHeight - c_buttonDimension32)/2) ); this.createNavButtons( 'id_east_bu', 'cs_map_nav_bu', (this.leftMargin + this.c_mapWidth - c_buttonDimension16), (this.topMargin + (this.c_mapHeight - c_buttonDimension32)/2) ); this.createNavButtons( 'id_north_bu', 'cs_map_nav_bu', (this.leftMargin + (this.c_mapWidth - c_buttonDimension32)/2), this.topMargin ); this.createNavButtons( 'id_south_bu', 'cs_map_nav_bu', (this.leftMargin + (this.c_mapWidth - c_buttonDimension32)/2), (this.topMargin + this.c_mapHeight - c_buttonDimension16) ); /* // nine zoom buttons var x1 = this.leftMargin + this.c_mapWidth/6; var y1 = this.topMargin + this.c_mapHeight/6; var x2 = x1 + this.c_mapWidth/3; var y2 = y1 + this.c_mapHeight/3; var x3 = x2 + this.c_mapWidth/3; var y3 = y2 + this.c_mapHeight/3; this.createNavButtons( 'id_zoom_nw', 'cs_zoom_bu', x1, y1 ); this.createNavButtons( 'id_zoom_n', 'cs_zoom_bu', x2, y1 ); this.createNavButtons( 'id_zoom_ne', 'cs_zoom_bu', x3, y1 ); this.createNavButtons( 'id_zoom_w', 'cs_zoom_bu', x1, y2 ); this.createNavButtons( 'id_zoom_c', 'cs_zoom_bu', x2, y2 ); this.createNavButtons( 'id_zoom_e', 'cs_zoom_bu', x3, y2 ); this.createNavButtons( 'id_zoom_sw', 'cs_zoom_bu', x1, y3 ); this.createNavButtons( 'id_zoom_s', 'cs_zoom_bu', x2, y3 ); this.createNavButtons( 'id_zoom_se', 'cs_zoom_bu', x3, y3 ); */ // event listeners bif.event.element( 'id_north_bu', 'click', this.onClickNorth ); bif.event.element( 'id_south_bu', 'click', this.onClickSouth ); bif.event.element( 'id_west_bu', 'click', this.onClickWest ); bif.event.element( 'id_east_bu', 'click', this.onClickEast ); }; //----------------------------------------------- //^ The createMapChicklets function is used to create the two // DIVs used for the current map and the next map and a third one // for the rubberband selector. Each will be the full size of the // viewport. bif.Cartae.prototype.createMapChicklets = function( id ) { var mapElement = document.createElement( 'div' ); mapElement.id = id; mapElement.style.position = 'absolute'; mapElement.style.backgroundRepeat = 'no-repeat'; mapElement.style.width = this.c_mapWidth + 'px'; mapElement.style.height = this.c_mapHeight + 'px'; mapElement.style.overflow = 'hidden'; this.viewport.appendChild( mapElement ); return mapElement; }; //----------------------------------------------- //^ The createNavButtons function is used to create the four // north, south, west, east, navigation buttons bif.Cartae.prototype.createNavButtons = function( id, classname, left, top ) { var button = document.createElement( 'div' ); button.id = id; button.className = classname; button.style.position = 'absolute'; button.style.left = left + 'px'; button.style.top = top + 'px'; this.viewport.appendChild( button ); // stuff a pointer to "this" into the button button.bifCartaeObject = this; }; //============================================================================= // internal event handler //============================================================================= //----------------------------------------------- //^ The onClickEast function is triggered by the user pressing a nav button or by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickNorth = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panNorth( extent.base_latitude + extent.scroll_latitude, extent.base_longitude ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickEast function is triggered by the user pressing a nav button or by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickSouth = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panSouth( extent.base_latitude - extent.scroll_latitude, extent.base_longitude ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickEast function is triggered by the user pressing a nav button or by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickWest = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panWest( extent.base_latitude, extent.base_longitude + extent.scroll_longitude ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickEast function is triggered by the user pressing a nav button or by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickEast = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panEast( extent.base_latitude, extent.base_longitude - extent.scroll_longitude ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickNorthWest function is triggered by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickNorthWest = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panWest( extent.base_latitude, extent.base_longitude + extent.scroll_longitude ); // place "this" into the global scope for use by setTimeout() g_bifCurrentCartae = cartae; // scroll north after a 1/2 second delay setTimeout( 'var extent = {}; g_bifCurrentCartae.getAllValues( extent ); g_bifCurrentCartae.panNorth( extent.base_latitude + extent.scroll_latitude, extent.base_longitude );', 500 ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickNorthEast function is triggered by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickNorthEast = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panEast( extent.base_latitude, extent.base_longitude - extent.scroll_longitude ); // place "this" into the global scope for use by setTimeout() g_bifCurrentCartae = cartae; // scroll north after a 1/2 second delay setTimeout( 'var extent = {}; g_bifCurrentCartae.getAllValues( extent ); g_bifCurrentCartae.panNorth( extent.base_latitude + extent.scroll_latitude, extent.base_longitude );', 500 ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickSouthWest function is triggered by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickSouthWest = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panWest( extent.base_latitude, extent.base_longitude + extent.scroll_longitude ); // place "this" into the global scope for use by setTimeout() g_bifCurrentCartae = cartae; // scroll north after a 1/2 second delay setTimeout( 'var extent = {}; g_bifCurrentCartae.getAllValues( extent ); g_bifCurrentCartae.panSouth( extent.base_latitude - extent.scroll_latitude, extent.base_longitude );', 500 ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The onClickSouthEast function is triggered by the gesture interface. // Send a new message to the viewport which is visible to the outside world bif.Cartae.prototype.onClickSouthEast = function( event ) { if ( !event ) { event = window.event; } // IE var elem = event.target; if (!elem) elem = event.srcElement; // IE var cartae = elem.bifCartaeObject; if ( !cartae ) return; var extent = {}; cartae.getAllValues( extent ); cartae.panEast( extent.base_latitude, extent.base_longitude - extent.scroll_longitude ); // place "this" into the global scope for use by setTimeout() g_bifCurrentCartae = cartae; // scroll north after a 1/2 second delay setTimeout( 'var extent = {}; g_bifCurrentCartae.getAllValues( extent ); g_bifCurrentCartae.panSouth( extent.base_latitude - extent.scroll_latitude, extent.base_longitude );', 500 ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', cartae.idViewport ); }; //----------------------------------------------- //^ The zoomIn function is called explicitly by the user without clicking on the map. // Since we don't have a location to go to, just go to the middle of the map. bif.Cartae.prototype.zoomIn = function() { this.zoomToXY( this.c_mapWidth/2, this.c_mapHeight/2 ); }; //----------------------------------------------- //^ The zoomOut function is called explicitly by the user. bif.Cartae.prototype.zoomOut = function( new_encoded_extent ) { if ( new_encoded_extent != 'unavailable' ) { var zoomfactor = new_encoded_extent.substr( 0, 1 ); var quadrant = new_encoded_extent.substr( 1, 1 ); var base_latitude = new_encoded_extent.substr( 2, 6 ); var base_longitude = new_encoded_extent.substr( 8, 6 ); this.showMap( zoomfactor, quadrant, base_latitude, base_longitude ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', this.idViewport ); } }; //----------------------------------------------- //^ The onClickMap function is the signal to zoom in bif.Cartae.prototype.onClickMap = function( event ) { if ( !event ) { event = window.event; } // IE // The 'id_gestures' element sent this var elem = event.target; if (!elem) elem = event.srcElement; // IE var gesObj = elem.bifExtensionObject; if ( !gesObj ) return; // NOTE: this part below written using the rubberband interface, but it still // works using the gesture interface. var extent = {}; // the mouse coordinates (top,left and bottom,right) of the user's click or rubberband gesObj.getSartingEndingCoordinates( extent ); // find the cartae element, which is the gesture's parent var cartae = elem.parentNode; if ( cartae ) { var cartaeObj = cartae.bifExtensionObject; if ( cartaeObj ) { // if the user clicked, use the left, top as the click position if ( extent.right == 0 && extent.bottom == 0 ) cartaeObj.zoomToXY( extent.left, extent.top ); // if the user drew a rubberband, use the midpoint else // ( extent.right != 0 && extent.bottom != 0 ) cartaeObj.zoomToXY( Math.round((extent.left + extent.right)/2), Math.round((extent.top + extent.bottom)/2) ); } } }; //----------------------------------------------- //^ The onRightClickMap function is the signal to zoom out bif.Cartae.prototype.onRightClickMap = function( event ) { if ( !event ) { event = window.event; } // IE // The 'id_gestures' element sent this var elem = event.target; if (!elem) elem = event.srcElement; // IE // find the cartae element, which is the gesture's parent var cartae = elem.parentNode; if ( cartae ) { var cartaeObj = cartae.bifExtensionObject; if ( cartaeObj ) { // make sure that zooming out is enabled if ( this.zoomout_enabled == 'N' ) return; // send message to document element that the right button has been clicked bif.event.sendCustom( 'rightclick', cartaeObj.idViewport ); } } }; //----------------------------------------------- // This global variable points to the object that is the target of the current UI callback var g_bifCurrentCartae = null; //============================================================================= // "set" external interface functions //============================================================================= //----------------------------------------------- //^ The setImageDirectory function is during initialization // It could be used whenever changing the zoom factor, if the map images // are kept in different subdirectories on a zoomfactor basis. //> image_directory is the location of the PNG files, it could be something like '/graticule/1-degree' (with no trailing slash). // or something like 'http://graticule.postvitae.com' (with no trailing slash). // bif.Cartae.prototype.setImageDirectory = function( image_directory ) { this.imageDirectory = image_directory; }; //----------------------------------------------- //^ The assembleImageFilename function // The subdirectory structure is composed of a 2-char subdirectory and a 6-character subdirectory // The encoded_extent is the 14-character base filename of the map image // Results in something like .../graticule/http/32/062400/32062400230550.png bif.Cartae.prototype.assembleImageFilename = function() { return this.sprintf( "%s/%1d%1d/%06d/%s.png", this.imageDirectory, this.zoomfactor, this.quadrant, this.base_latitude, this.encodedExtent ); }; //----------------------------------------------- //^ The showMap function is used to arbitrarily show a map. This must be called at least once after initialization // but doesn't need to be called again, when using only the scrolling functions. //> new_zoomfactor is 2,3, 4, 5, 6, 7, or 8 //> new_quadrant is 1, 2, 3, or 4. //> new_base_latitude is the the latitude of the principle corner //> new_base_longitude is the the longitude of the principle corner // bif.Cartae.prototype.showMap = function( new_zoomfactor, new_quadrant, new_base_latitude, new_base_longitude ) { this.zoomfactor = parseInt( new_zoomfactor, 10); // 2, 3, 4, 5, 6, 7, or 8 this.quadrant = parseInt( new_quadrant, 10 ); // 1 = NE, 2 = NW, 3 = SE, 4 = SW this.base_latitude = parseInt( new_base_latitude, 10 ); // the latitude of the principle corner this.base_longitude = parseInt( new_base_longitude, 10 ); // the longitude of the principle corner this.encodedExtent = this.formatEncodedExtent(); var filename = this.assembleImageFilename(); // $latitude_coverage = array( 2=>300, 3=> 600, 4=> 1200, 5=> 2400, 6=> 4800, 7=> 9600, 8=> 19200 ); // $longitude_coverage = array( 2=>375, 3=> 750, 4=> 1500, 5=> 3000, 6=> 6000, 7=> 12000, 8=> 24000 ); // $latitude_coverage = array( 2=>900, 3=> 1800, 4=> 3600, 5=> 7200, 6=> 14400, 7=> 28800, 8=> 57600 ); // $longitude_coverage = array( 2=>1125,3=> 2250, 4=> 4500, 5=> 9000, 6=> 18000, 7=> 36000, 8=> 72000 ); if ( this.zoomfactor == 2 ) { this.scroll_latitude = 300; this.scroll_longitude = 375; this.coverage_latitude = 900; this.coverage_longitude = 1125; } else if ( this.zoomfactor == 3 ) { this.scroll_latitude = 600; this.scroll_longitude = 750; this.coverage_latitude = 1800; this.coverage_longitude = 2250; } else if ( this.zoomfactor == 4 ) { this.scroll_latitude = 1200; this.scroll_longitude = 1500; this.coverage_latitude = 3600; this.coverage_longitude = 4500; } else if ( this.zoomfactor == 5 ) { this.scroll_latitude = 2400; this.scroll_longitude = 3000; this.coverage_latitude = 7200; this.coverage_longitude = 9000; } else if ( this.zoomfactor == 6 ) { this.scroll_latitude = 4800; this.scroll_longitude = 6000; this.coverage_latitude = 14400; this.coverage_longitude = 18000; } else if ( this.zoomfactor == 7 ) { this.scroll_latitude = 9600; this.scroll_longitude = 12000; this.coverage_latitude = 28800; this.coverage_longitude = 36000; } else if ( this.zoomfactor == 8 ) { this.scroll_latitude = 19200; this.scroll_longitude = 24000; this.coverage_latitude = 57600; this.coverage_longitude = 72000; } else alert( 'invalid zoomfactor value' ); // to avoid flicker, prefetch the new map over HTTP and place in nextMap, then wait 1/2 second var nextMap = document.getElementById( 'id_next_map' ); if ( nextMap ) { nextMap.style.backgroundImage = "url('" + filename + "')"; // place "this" into the global scope for use by showMapAfterDelay() g_bifCurrentCartae = this; setTimeout( this.showMapAfterDelay, 500 ); } }; //----------------------------------------------- bif.Cartae.prototype.showMapAfterDelay = function() { var cartae = g_bifCurrentCartae; if ( !cartae ) return; var nextMap = document.getElementById( 'id_next_map' ) var centerMap = document.getElementById( 'id_center_map' ) if ( nextMap && centerMap ) { centerMap.style.backgroundImage = nextMap.style.backgroundImage; centerMap.style.backgroundPosition = '0px 0px'; centerMap.style.width = cartae.c_mapWidth + 'px'; centerMap.style.height = cartae.c_mapHeight + 'px'; centerMap.style.visibility = 'visible'; } }; //----------------------------------------------- //^ The enableZoominZoomout function enables or disables the left-click zoom-in interface // and the right-click zoom-out interface //> zoomInYN is 'Y' to enable zooming in and 'N' to disable zooming in //> zoomOutYN is 'Y' to enable zooming out and 'N' to disable zooming out // bif.Cartae.prototype.enableZoominZoomout = function( zoomInYN, zoomOutYN ) { this.zoomin_enabled = zoomInYN; this.zoomout_enabled = zoomOutYN; }; //----------------------------------------------- //^ The enableScrolling function accepts an eight-character string composed of Y/N // which answers the question, "Can the map be scrolled to this compass point?" // Call this function after calling showMap // The order of the eight values is: 0 = N, 1 = NE, 2 = E, 3 = SE, 4 = S, 5 = SW, 6 = W, 7 = NW // bif.Cartae.prototype.enableScrolling = function( scroll_exists ) { if ( scroll_exists.length != 8 ) return; this.scroll_exists = scroll_exists; var enable = 'cs_map_nav_bu'; var disable = 'cs_map_nav_bu cs_hidden'; var el = document.getElementById( 'id_north_bu' ) if (el) el.className = scroll_exists[0] == 'Y' ? enable : disable; el = document.getElementById( 'id_east_bu' ) if (el) el.className = scroll_exists[2] == 'Y' ? enable : disable; el = document.getElementById( 'id_south_bu' ) if (el) el.className = scroll_exists[4] == 'Y' ? enable : disable; el = document.getElementById( 'id_west_bu' ) if (el) el.className = scroll_exists[6] == 'Y' ? enable : disable; }; //============================================================================= // "get" external interface functions //============================================================================= //----------------------------------------------- //^ The getAllValues function returns the zoomfactor, quadrant, base_latitude and base_longitude // of the current map. //< nswe is the object passed by referenced, returned by this function // bif.Cartae.prototype.getAllValues = function( nswe ) { if ( this.zoomfactor && this.quadrant && this.base_latitude && this.base_longitude ) { nswe.zoomfactor = this.zoomfactor; nswe.quadrant = this.quadrant; nswe.base_latitude = this.base_latitude; nswe.base_longitude = this.base_longitude; nswe.scroll_latitude = this.scroll_latitude; nswe.scroll_longitude = this.scroll_longitude; return true; } else return false; }; //----------------------------------------------- //^ The getEncodedExtent function gets the encoded extent, which is the base // part of the map chicklet filename, and is a suitable unique key for using // in external database queries to additional information // bif.Cartae.prototype.getEncodedExtent = function() { if ( this.encodedExtent ) return this.encodedExtent; else return ''; }; //============================================================================= // Panning functions //============================================================================= //----------------------------------------------- //^ The panNorth function moves the viewport north, sliding the new map into view. //> new_base_latitude is the base latitude of the new map //> new_base_longitude is the base longitude of the new map // bif.Cartae.prototype.panNorth = function( new_base_latitude, new_base_longitude ) { // Make sure scrolling is enabled for this direction // 0 = N, 1 = NE, 2 = E, 3 = SE, 4 = S, 5 = SW, 6 = W, 7 = NW if ( this.scroll_exists[0] == 'N' ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // cancel any unfinished activity if ( this.smoothTimer ) { window.clearInterval( this.smoothTimer ); this.smoothTimer = null; // short circuit the smooth movement and immediately place the next map into the current map element centerMap.style.backgroundImage = nextMap.style.backgroundImage; } this.base_latitude = new_base_latitude; this.base_longitude = new_base_longitude; this.encodedExtent = this.formatEncodedExtent(); var filename = this.assembleImageFilename(); // place "this" into the global scope for use by callbackPanNorth() g_bifCurrentCartae = this; // fix the left and width of both maps centerMap.style.left = this.leftMargin + 'px'; nextMap.style.left = this.leftMargin + 'px'; centerMap.style.width = this.c_mapWidth + 'px'; nextMap.style.width = this.c_mapWidth + 'px'; // position the new map above the top of the current map, and completely outside the map viewport nextMap.style.top = this.topMargin + 'px'; nextMap.style.height = '0px'; // place chicklet onto the next map background nextMap.style.backgroundImage = "url('" + filename + "')"; nextMap.style.backgroundPosition = '0px 0px'; nextMap.style.visibility = 'visible'; this.smoothMovement = new bif.Cartae.SmoothMovement( 1, this.c_mapScrollAmountHeight ); this.smoothTimer = window.setInterval( 'bif.Cartae.prototype.callbackPanNorth()', 1 ); }; //------------------------------------- // The callbackPanSouth function is called repeatedly, at regular itervals, until // the current map is fully out of the viewport and the next map is // fully in the viewport. bif.Cartae.prototype.callbackPanNorth = function() { var cartae = g_bifCurrentCartae; if ( !cartae ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // call the SmoothMovement object to get the new edge boundary between // the current map's top edge and the next maps's bottom edge. var a = cartae.smoothMovement.updatePosition(); // a number from 1 to 274 var A = cartae.c_mapScrollAmountHeight - a; // a number from 273 to 0 nextMap.style.top = cartae.topMargin + 'px'; nextMap.style.height = a + 'px'; nextMap.style.backgroundPosition = '0px ' + (0 - A) + 'px'; centerMap.style.top = (cartae.topMargin + a) + 'px'; centerMap.style.height = (A + cartae.c_mapOverlapAmountHeight) + 'px'; centerMap.style.backgroundPosition = '0px 0px'; if ( cartae.smoothMovement.hasStopped() ) { window.clearInterval( cartae.smoothTimer ); cartae.smoothTimer = null; // replace the center map with next map centerMap.style.visibility = 'hidden'; centerMap.style.backgroundImage = nextMap.style.backgroundImage; centerMap.style.backgroundPosition = '0px 0px'; centerMap.style.top = cartae.topMargin + 'px'; centerMap.style.height = cartae.c_mapHeight + 'px'; centerMap.style.visibility = 'visible'; nextMap.style.visibility = 'hidden'; } }; //----------------------------------------------- //^ The panSouth function moves the viewport south, sliding the new map into view. //> new_base_latitude is the base latitude of the new map //> new_base_longitude is the base longitude of the new map // bif.Cartae.prototype.panSouth = function( new_base_latitude, new_base_longitude ) { // Make sure scrolling is enabled for this direction // 0 = N, 1 = NE, 2 = E, 3 = SE, 4 = S, 5 = SW, 6 = W, 7 = NW if ( this.scroll_exists[4] == 'N' ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // cancel any unfinished activity if ( this.smoothTimer ) { window.clearInterval( this.smoothTimer ); this.smoothTimer = null; // short circuit the smooth movement and immediately place the next map into the current map element centerMap.style.backgroundImage = nextMap.style.backgroundImage; } this.base_latitude = new_base_latitude; this.base_longitude = new_base_longitude; this.encodedExtent = this.formatEncodedExtent(); var filename = this.assembleImageFilename(); // place "this" into the global scope for use by callbackPanSouth() g_bifCurrentCartae = this; // fix the left and width of both maps centerMap.style.left = this.leftMargin + 'px'; nextMap.style.left = this.leftMargin + 'px'; centerMap.style.width = this.c_mapWidth + 'px'; nextMap.style.width = this.c_mapWidth + 'px'; // position the new map below the current map, and completely outside the map viewport nextMap.style.top = this.topMargin + this.c_mapHeight + 'px'; nextMap.style.height = '0px'; // place chicklet onto the next map background nextMap.style.backgroundImage = "url('" + filename + "')"; nextMap.style.backgroundPosition = '0px 0px'; nextMap.style.visibility = 'visible'; this.smoothMovement = new bif.Cartae.SmoothMovement( 1, this.c_mapScrollAmountHeight ); this.smoothTimer = window.setInterval( 'bif.Cartae.prototype.callbackPanSouth()', 1 ); }; //------------------------------------- // The callbackPanSouth function is called repeatedly, at regular itervals, until // the current map is fully out of the viewport and the next map is // fully in the viewport. bif.Cartae.prototype.callbackPanSouth = function() { var cartae = g_bifCurrentCartae; if ( !cartae ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // call the SmoothMovement object to get the new edge boundary between // the current map's bottom edge and the next maps's top edge. var a = cartae.smoothMovement.updatePosition(); // a number from 1 to 274 var A = cartae.c_mapScrollAmountHeight - a; // a number from 273 to 0 nextMap.style.top = (cartae.topMargin + A) + 'px'; nextMap.style.height = (a + + cartae.c_mapOverlapAmountHeight) + 'px'; nextMap.style.backgroundPosition = '0px 0px'; centerMap.style.top = cartae.topMargin + 'px'; centerMap.style.height = (A + cartae.c_mapOverlapAmountHeight) + 'px'; centerMap.style.backgroundPosition = '0px ' + (0 - a) + 'px'; if ( cartae.smoothMovement.hasStopped() ) { window.clearInterval( cartae.smoothTimer ); cartae.smoothTimer = null; // replace the center map with next map centerMap.style.visibility = 'hidden'; centerMap.style.backgroundImage = nextMap.style.backgroundImage; centerMap.style.backgroundPosition = '0px 0px'; centerMap.style.top = cartae.topMargin + 'px'; centerMap.style.height = cartae.c_mapHeight + 'px'; centerMap.style.visibility = 'visible'; nextMap.style.visibility = 'hidden'; } }; //----------------------------------------------- //^ The panWest function moves the viewport west, sliding the new map into view. //> new_base_latitude is the base latitude of the new map //> new_base_longitude is the base longitude of the new map // bif.Cartae.prototype.panWest = function( new_base_latitude, new_base_longitude ) { // Make sure scrolling is enabled for this direction // 0 = N, 1 = NE, 2 = E, 3 = SE, 4 = S, 5 = SW, 6 = W, 7 = NW if ( this.scroll_exists[6] == 'N' ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // cancel any unfinished activity if ( this.smoothTimer ) { window.clearInterval( this.smoothTimer ); this.smoothTimer = null; // short circuit the smooth movement and immediately place the next map into the current map element centerMap.style.backgroundImage = nextMap.style.backgroundImage; } this.base_latitude = new_base_latitude; this.base_longitude = new_base_longitude; this.encodedExtent = this.formatEncodedExtent(); var filename = this.assembleImageFilename(); // place "this" into the global scope for use by callbackPanWest() g_bifCurrentCartae = this; // fix the top and height of both maps centerMap.style.top = this.topMargin + 'px'; nextMap.style.top = this.topMargin + 'px'; centerMap.style.height = this.c_mapHeight + 'px'; nextMap.style.height = this.c_mapHeight + 'px'; // position the new map to the left of the current map, and completely outside the map viewport var nextMap = document.getElementById( 'id_next_map' ) nextMap.style.left = this.leftMargin; nextMap.style.width = '0px'; // place chicklet onto the next map background nextMap.style.backgroundImage = "url('" + filename + "')"; nextMap.style.backgroundPosition = (0 - this.c_mapWidth) + 'px 0px'; nextMap.style.visibility = 'visible'; this.smoothMovement = new bif.Cartae.SmoothMovement( 1, this.c_mapScrollAmountWidth ); this.smoothTimer = window.setInterval( 'bif.Cartae.prototype.callbackPanWest()', 1 ); }; //------------------------------------- // The callbackPanWest function is called repeatedly, at regular itervals, until // the current map is fully out of the viewport and the next map is // fully in the viewport. bif.Cartae.prototype.callbackPanWest = function() { var cartae = g_bifCurrentCartae; if ( !cartae ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // call the SmoothMovement object to get the new edge boundary between // the current map's left edge and the next maps's right edge. var a = cartae.smoothMovement.updatePosition(); // a number from 1 to 344 var A = cartae.c_mapScrollAmountWidth - a; // a number from 343 to 0 nextMap.style.left = cartae.leftMargin + 'px'; nextMap.style.width = a + 'px'; nextMap.style.backgroundPosition = (0 - A) + 'px 0px'; centerMap.style.left = (cartae.leftMargin + a) + 'px'; centerMap.style.width = (A + cartae.c_mapOverlapAmountWidth) + 'px'; centerMap.style.backgroundPosition = '0px 0px'; if ( cartae.smoothMovement.hasStopped() ) { window.clearInterval( cartae.smoothTimer ); cartae.smoothTimer = null; // replace the center map with next map centerMap.style.visibility = 'hidden'; centerMap.style.backgroundImage = nextMap.style.backgroundImage; centerMap.style.backgroundPosition = '0px 0px'; centerMap.style.left = cartae.leftMargin + 'px'; centerMap.style.width = cartae.c_mapWidth + 'px'; centerMap.style.visibility = 'visible'; nextMap.style.visibility = 'hidden'; } }; //----------------------------------------------- //^ The panEast function moves the viewport east, sliding the new map into view. //> new_base_latitude is the base latitude of the new map //> new_base_longitude is the base longitude of the new map // bif.Cartae.prototype.panEast = function( new_base_latitude, new_base_longitude ) { // Make sure scrolling is enabled for this direction // 0 = N, 1 = NE, 2 = E, 3 = SE, 4 = S, 5 = SW, 6 = W, 7 = NW if ( this.scroll_exists[2] == 'N' ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // cancel any unfinished activity if ( this.smoothTimer ) { window.clearInterval( this.smoothTimer ); this.smoothTimer = null; // short circuit the smooth movement and immediately place the next map into the current map element centerMap.style.backgroundImage = nextMap.style.backgroundImage; } this.base_latitude = new_base_latitude; this.base_longitude = new_base_longitude; this.encodedExtent = this.formatEncodedExtent(); var filename = this.assembleImageFilename(); // place "this" into the global scope for use by callbackPanEast() g_bifCurrentCartae = this; // fix the top and height of both maps centerMap.style.top = this.topMargin + 'px'; nextMap.style.top = this.topMargin + 'px'; centerMap.style.height = this.c_mapHeight + 'px'; nextMap.style.height = this.c_mapHeight + 'px'; // position the new map to the right of the current map, and completely outside the map viewport nextMap.style.left = this.leftMargin + this.c_mapWidth; nextMap.style.width = '0px'; // place chicklet onto the next map background nextMap.style.backgroundImage = "url('" + filename + "')"; nextMap.style.backgroundPosition = '0px 0px'; nextMap.style.visibility = 'visible'; this.smoothMovement = new bif.Cartae.SmoothMovement( 1, this.c_mapScrollAmountWidth ); this.smoothTimer = window.setInterval( 'bif.Cartae.prototype.callbackPanEast()', 1 ); }; //------------------------------------- // The callbackPanEast function is called repeatedly, at regular itervals, until // the current map is fully out of the viewport and the next map is // fully in the viewport. bif.Cartae.prototype.callbackPanEast = function() { var cartae = g_bifCurrentCartae; if ( !cartae ) return; var centerMap = document.getElementById( 'id_center_map' ) var nextMap = document.getElementById( 'id_next_map' ) // call the SmoothMovement object to get the new edge boundary between // the current map's right edge and the next maps's left edge. var a = cartae.smoothMovement.updatePosition(); // a number from 1 to 344 var A = cartae.c_mapScrollAmountWidth - a; // a number from 343 to 0 nextMap.style.left = (cartae.leftMargin + A) + 'px'; nextMap.style.width = (a + cartae.c_mapOverlapAmountWidth) + 'px'; nextMap.style.backgroundPosition = '0px 0px'; centerMap.style.left = cartae.leftMargin + 'px'; centerMap.style.width = (A + cartae.c_mapOverlapAmountWidth) + 'px'; centerMap.style.backgroundPosition = (0 - a) + 'px 0px'; if ( cartae.smoothMovement.hasStopped() ) { window.clearInterval( cartae.smoothTimer ); cartae.smoothTimer = null; // replace the center map with next map centerMap.style.visibility = 'hidden'; centerMap.style.backgroundImage = nextMap.style.backgroundImage; centerMap.style.backgroundPosition = '0px 0px'; centerMap.style.left = cartae.leftMargin + 'px'; centerMap.style.width = cartae.c_mapWidth + 'px'; centerMap.style.visibility = 'visible'; nextMap.style.visibility = 'hidden'; } }; //----------------------------------------------- //^ The zoomToXY function //> x is the number of pixels from the top, left corner of the map //> y is the number of pixels from the top, left corner of the map bif.Cartae.prototype.zoomToXY = function( x, y ) { // make sure that zooming in is enabled if ( this.zoomin_enabled == 'N' ) return; // Convert the javascript x,y coordinates to normalized map coordinates, // still in units of pixels, but now oriented with the latitude/longitude quadrant. var normalizedX = 0; var normalizedY = 0; if ( this.quadrant == 1 ) // NE { normalizedX = x; normalizedY = this.c_mapHeight - y; } else if ( this.quadrant == 2 ) // NW { normalizedX = this.c_mapWidth - x; normalizedY = this.c_mapHeight - y; } else if ( this.quadrant == 3 ) // SE { normalizedX = x; normalizedY = y; } else if ( this.quadrant == 4 ) // SW { normalizedX = this.c_mapWidth - x; normalizedY = y; } // determine the latitude/longitude, in seconds (of the current quadrant) var seconds_per_pixelY = this.coverage_latitude / this.c_mapHeight; var seconds_per_pixelX = this.coverage_longitude / this.c_mapWidth; var targetLatitude = this.base_latitude + (normalizedY * seconds_per_pixelY); var targetLongitude = this.base_longitude + (normalizedX * seconds_per_pixelX); var new_base_latitude = 0; var new_base_longitude = 0; // each larger map is covered by parts of six smaller maps latitudinally and longitudinally // try the two central map-parts first, then the next outer two, then the outermost two var order = [ 2, 3, 1, 4, 0, 5 ]; for ( var i = 0; i < 6; i++ ) { var halfScroll = (this.scroll_latitude / 2); var test_bottom = order[i] * halfScroll; var test_top = test_bottom + halfScroll; if ( (targetLatitude >= this.base_latitude + test_bottom) && (targetLatitude <= this.base_latitude + test_top) ) { // with the optimal map-part known, step back a half-scroll to put the target close to the center new_base_latitude = (this.base_latitude + test_bottom) - halfScroll; break; } } for ( var i = 0; i < 6; i++ ) { var halfScroll = (this.scroll_longitude / 2); var test_right = order[i] * halfScroll; var test_left = test_right + halfScroll; if ( (targetLongitude >= this.base_longitude + test_right) && (targetLongitude <= this.base_longitude + test_left) ) { // with the optimal map-part known, step back a half-scroll to put the target close to the center new_base_longitude = (this.base_longitude + test_right) - halfScroll; break; } } // must be zoomfactor 3 or higher, since 2 is the smallest if ( this.zoomfactor > 2 ) { // show the new map this.showMap( this.zoomfactor - 1, this.quadrant, new_base_latitude, new_base_longitude ); // send message to document element that the extent has changed bif.event.sendCustom( 'extentchanged', this.idViewport ); } }; //============================================================================= // internal helpers //============================================================================= //----------------------------------------------- //^ The formatEncodedExtent function is used to convert the coverage, quadrant, base_latitude, base_longitude values into a string bif.Cartae.prototype.formatEncodedExtent = function() { return this.sprintf( "%1d%1d%06d%06d", this.zoomfactor, this.quadrant, this.base_latitude, this.base_longitude ); }; //----------------------------------------------- bif.Cartae.prototype.str_repeat = function(i, m) { for ( var o = []; m > 0; o[--m] = i ) continue; return( o.join('') ); }; //----------------------------------------------- //^ The sprintf function // // Copyright (c) 2007 Alexandru Marasteanu