    //<![CDATA[

    /**************************
    Module constants and variables
    **************************/

    // enumerated constants & static objects
    var LABEL_MODE = { Off: 0, Labels: 1, LabelsNotes: 3 };

    // module vars
    var m_Map;
    var m_EventsSearchMap;
    var m_Args, m_Params;
    var m_SerialVersion = Math.round(Math.random() * 1000000);

    var m_layerMap, m_layerSat, m_layerLabel;
    var m_ctrlPanel, m_btnLabels, m_btnRefresh, m_btnHover;
    var m_ctrlFadeBar, m_ctrlViewfinder, m_ctrlPreview, m_layerMapPreview;
    var m_ctrlMouseClickViewer;
    var m_dlgModal;

    /**************************
    Map viewer initialization
    **************************/

    function initializeMapViewer(nopos, miniviewer, searcheventobj)
    {
        Proj4js.defs["EPSG:3395"] = "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs ";

        OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
        OpenLayers.Util.onImageLoadErrorColor = "transparent";

        m_Args = OpenLayers.Util.getParameters();
        m_IsMiniViewer = miniviewer;

        m_EventsSearchMap = searcheventobj;
        if (m_EventsSearchMap)
        {
            m_EventsSearchMap.onsearchrequest.subscribe(onSearchRequestMap);
            m_EventsSearchMap.onsearchresults.subscribe(onSearchResultsMap);
        }

        if (!m_IsMiniViewer)
        {
            // init serach bar
            InitQueryCode('searchText1', 'http://www.zoomatlas.com/mediawiki/skins/za_skin/DoTypeAhead.php?q=');
            InitQueryEvents(searchchanged);
        }

        var bounds = new OpenLayers.Bounds(
            -18000000.0, 2000000.0,
            -5417088.0, 14582912.0                
        );
        var options = {
            controls: []
            , theme: null
            , maxExtent: bounds
            , maxResolution: 16384
		    , resolutions: [16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1,
			    0.6096, 0.3048, 0.1524, 0.0762, 0.0381, 0.01905, 0.009525, 0.0047625, 0.00238125, 0.001190625] // map units per pixel
            , projection: new OpenLayers.Projection('EPSG:3395')
            , displayProjection: new OpenLayers.Projection("EPSG:4326")
        };

        OpenLayers.ImgPath = "/Map/Vespucci/Theme/img/";

        // we set the framed cloud popup to use a fixed position
        //OpenLayers.Popup.FramedCloud.prototype.fixedRelativePosition = true;
        //OpenLayers.Popup.FramedCloud.prototype.relativePosition = "tl";
        OpenLayers.Popup.FramedCloud.prototype.panMapIfOutOfView = false;

        m_Map = new OpenLayers.Map('map', options);

        // we use an empty base layer
        var base = new OpenLayers.Layer("",{isBaseLayer: true}); 
        m_Map.addLayer(base);

		var urlsSat = ["http://sat0.zoomatlas.com/"
			, "http://sat1.zoomatlas.com/"
			, "http://sat2.zoomatlas.com/"
			, "http://sat3.zoomatlas.com/"
			, "http://sat4.zoomatlas.com/"
			, "http://sat5.zoomatlas.com/"
			, "http://sat6.zoomatlas.com/"
			, "http://sat7.zoomatlas.com/"
			, "http://sat8.zoomatlas.com/"
			, "http://sat9.zoomatlas.com/"
			, "http://sat10.zoomatlas.com/"
			, "http://sat11.zoomatlas.com/"
			];

        m_layerSat = new OpenLayers.Layer.TMS( "Satellite", urlsSat
        	,{
				maxExtent: bounds
        		, layername: 'sat'
        		, type: 'jpg'
        		, isBaseLayer: false
        		, displayInLayerSwitcher: false
        		, buffer: 0
        	}
        );
        m_layerSat.getURL = getSatURL;
        m_Map.addLayer(m_layerSat);

		var urlsMap = ["http://map0.zoomatlas.com/"
			, "http://map1.zoomatlas.com/"
			, "http://map2.zoomatlas.com/"
			, "http://map3.zoomatlas.com/"
			, "http://map4.zoomatlas.com/"
			, "http://map5.zoomatlas.com/"
			, "http://map6.zoomatlas.com/"
			, "http://map7.zoomatlas.com/"
			, "http://map8.zoomatlas.com/"
			, "http://map9.zoomatlas.com/"
			, "http://map10.zoomatlas.com/"
			, "http://map11.zoomatlas.com/"
			];

        m_layerMap = new OpenLayers.Layer.TMS( "WikiAtlas Map", urlsMap
        	,{
				maxExtent: bounds
        		, layername: 'map'
        		, type: 'png'
        		, isBaseLayer: false
        		, displayInLayerSwitcher: false
        		, buffer: 0
        	} 
        );
    	m_layerMap.getURL = getMapURL;
    	m_Map.addLayer(m_layerMap);

    	var urlsLabel = ["http://lbl0.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl1.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl2.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl3.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl4.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl5.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl6.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl7.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl8.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl9.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl0.zoomatlas.com/geowebcache/service/wms"
			, "http://lbl1.zoomatlas.com/geowebcache/service/wms"
			];

    	//urlsLabel = ["http://www.zoomatlas.com/geoserver/wms"];
    	
    	m_layerLabel = new OpenLayers.Layer.WMS("Labels", urlsLabel
		    ,{
    	        layers: 'wa:roads,wa:labels,wa:borders,wa:notes'
		        , styles: 'label_roads,labels,borders,notes'
		        , format: 'image/png'
		        , transparent: 'true'
                , projection: new OpenLayers.Projection('EPSG:3395')
		    } 
		    ,{
        		buffer: 0
            }
        );
        m_layerLabel.mode = LABEL_MODE.LabelsNotes;
        m_layerLabel.getURL = getLabelURL;
        m_Map.addLayer(m_layerLabel);

        m_ctrlHover = new OpenLayers.Handler.Hover({ map: m_Map }, { pause: this.selectHoverNote }, { delay: 850, pixelTolerance: 10 });

        if (!m_IsMiniViewer) m_Map.addControl(new OpenLayers.Control.MousePosition({ numDigits: 8 }));
        m_Map.addControl(new OpenLayers.Control.PanZoomBar());
        
        var navigation = new OpenLayers.Control.Navigation();
        m_Map.addControl(navigation);

        m_ctrlFadeBar = new OpenLayers.Control.ZoomAtlasFadeBar();
        m_Map.addControl(m_ctrlFadeBar);
        m_ctrlFadeBar.events.register('changeopacity', this, onChangeOpacity);

        m_btnLabels = new OpenLayers.Control.Button(
            {
                title: "Hide Labels"
                , displayClass: "zaControlLabelNoteOn"
                , trigger: OpenLayers.Function.bind(this.onClickLabelVisibility, this)
            }); 

        m_btnRefresh = new OpenLayers.Control.Button(
            {
                title: "Force Map Recalculation and Repaint"
                , displayClass: "zaControlRefresh"
                , trigger: OpenLayers.Function.bind(this.onClickRefresh, this)
            });
           
        m_btnHover = new OpenLayers.Control.Button(
        {
            title: "Switch to hover for notes"
            , displayClass: "zaControlHover"
            , active: false
            , type: OpenLayers.Control.TYPE_TOGGLE
        });

        m_ctrlPanel = new OpenLayers.Control.Panel();
        //m_ctrlPanel.addControls([m_btnLabels, m_btnRefresh, m_btnHover]);
        m_ctrlPanel.addControls([m_btnLabels, m_btnRefresh]);
        if (!m_IsMiniViewer) m_Map.addControl(m_ctrlPanel);

        m_btnHover.events.on({
            "activate": this.onClickHoverOn,
            "deactivate": this.onClickHoverOff,
            scope: this
        });

        m_ctrlViewfinder = new OpenLayers.Control.ZoomAtlasViewfinder();
        m_Map.addControl(m_ctrlViewfinder);

//        var previewsatlayer = m_layerSat.clone();
//        previewsatlayer.setIsBaseLayer(true);
//        m_layerMapPreview = m_layerMap.clone();
//        var ctrloptions =
//            {
//                size: new OpenLayers.Size(150, 150)
//                , previewZoom: 4
//                , viewfinder: m_ctrlViewfinder
//                , layers: [previewsatlayer, m_layerMapPreview]
//                , mapOptions:
//                {
//                    maxExtent: bounds
//                    , maxResolution: 16384
//                    , resolutions: [16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1,
//			            0.6096, 0.3048, 0.1524, 0.0762, 0.0381, 0.01905, 0.009525, 0.0047625, 0.00238125, 0.001190625] // map units per pixel
//                    , projection: "EPSG:3395"
//                    , units: "m"
//                }
//            };

//        m_ctrlPreview = new OpenLayers.Control.ZoomAtlasPreviewMap(ctrloptions);
//        m_Map.addControl(m_ctrlPreview);
//        m_ctrlPreview.activate();
//        m_ctrlPreview.minimizeControl(null, true);

//        m_ctrlPreview.events.register('toggle', this, onlayerMapPreviewToggle);
//        m_ctrlPreview.events.register('preupdate', this, onlayerMapPreviewPreUpdate);

        m_ctrlMouseClickViewer = new OpenLayers.Control();
        OpenLayers.Util.extend(m_ctrlMouseClickViewer,
            {
                draw: function()
                {
                    this.click = new OpenLayers.Handler.Click(this,
                        {
                            'click': onMapClickViewer
                        }
                    );
                    this.click.keyMask = [OpenLayers.Handler.MOD_CTRL, OpenLayers.Handler.MOD_ALT, OpenLayers.Handler.MOD_META];
                    this.click.activate();
                }
            }
        );
        m_Map.addControl(m_ctrlMouseClickViewer);
        if (!m_IsMiniViewer) m_ctrlMouseClickViewer.activate();

        if (!m_IsMiniViewer && m_Args.nomenu == undefined)
        {
            var rclick = new OpenLayers.Handler.Click(this,
                {
                    'rightclick': onMapRightClickViewer
                },
                {
                    'pixelTolerance': 5
                    , 'handleRightClicks': true
                }
            );
            rclick.setMap(m_Map);
            rclick.activate();
            //document.body.oncontextmenu = function() { return false; }
            m_Map.div.oncontextmenu = function() { return false; };
        }

        // for some reason the layers don't come out right if we set the opacity in the layer definition,
        // so we do it explicitly here
        m_layerSat.setVisibility(true);
        m_layerMap.setVisibility(true);
        m_layerSat.setOpacity(1.0);
        m_layerMap.setOpacity(1.0);

        var savedopacity = getUserOption("opacity");
        if (null != savedopacity)
        {
            setOpacity(savedopacity);
            m_ctrlFadeBar.setOpacity(savedopacity);
        }

        if (!m_IsMiniViewer)
        {
            // restore state from editor cookie
    //        var previewstate = getUserOption("preview");
    //        if ("maximized" == previewstate)
    //            m_ctrlPreview.maximizeControl(null, true);

            var savedlabels = getUserOption("labels");
            if (null != savedlabels)
            {
                setLabelMode(savedlabels)
            }
            else
            {
                setLabelMode(LABEL_MODE.LabelsNotes); // default is to have both labels and notes
            }

            m_dlgModal = new YAHOO.widget.Panel("dialog"
                , { width: "30em"
                , close: true
                , modal: true
		        , fixedcenter: true
		        , visible: false
		        , constraintoviewport: true
                });
            m_dlgModal.render();
        }
        
        if (!nopos)
            initSizeAndPosition(nopos, m_IsMiniViewer);

    }

    function g_enableMapViewMode(enable)
    {
        if (enable)
        {
            m_ctrlMouseClickViewer.activate();
        }
        else
        {
            m_ctrlMouseClickViewer.deactivate();
        }
    }

    function g_enableMapViewfinder(enable)
    {
        if (enable)
        {
            m_ctrlViewfinder.activate();
        }
        else
        {
            m_ctrlViewfinder.deactivate();
        }
    }

    function initSizeAndPosition(nopos, miniviewer)
    {
        var setpos = false;
        // init map view using passed params
        // first look for lid and other params in hash
        if (!miniviewer && document.location.hash != "" && document.location.hash.substring(1) != "")
        {
            var hash = document.location.hash.substring(1);
            var paramsstart = hash.indexOf(":");
            if (-1 == paramsstart)
                paramsstart = hash.indexOf("&"); // we look for old ampersand seperator too

            m_Params = {};
            // first get lid
            var lid = paramsstart == -1 ? hash : hash.substring(0, paramsstart);

            // then parse additional parameters
            if (paramsstart != -1)
            {
                m_Params = parseHashParams(hash.substr(paramsstart));
            }

            if (lid != "" && lid != "10041181901")
            {
                setpos = true;

                // we use this event to know it's time to process post-load parameters
                Event.onContentReady("floatpanel_contentDiv", this.setLoadOptions);
                // now position map and show cloud
                selectHover(lid, null, m_Params.refresh);
            }
        }
        if (!setpos) // nothing above setup the map position, this is the fallback case
        {
            if (m_Args.BBOX)
            {
                var bbox = new OpenLayers.Bounds.fromString(m_Args.BBOX.toString());
                ZoomToArea(bbox.left, bbox.bottom, bbox.right, bbox.top, (m_Args.zoom ? m_Args.zoom : null));
            }
            if (!m_Map.getCenter())
            {
                // if nothing above has setup map then center on continental US
                ZoomToArea("-14391036.5", "7053095.0", "-7509756.5", "2268967.0", 2);
                if (!miniviewer)
                {
                    g_SelectedLid = "10041181901"; // USA
                    document.location.hash = g_SelectedLid;
                    setStatusBar("<img title='Country' src='http://www.zoomatlas.com/pix/10_sm.png'/><span style='vertical-align: top;'>United States</span>");
                    YAHOO.util.Cookie.set("CurrentLID", g_SelectedLid, { domain: "zoomatlas.com", path: "/", expires: new Date("January 1, 2025") });

                    // notify tabs
                    onSelectedLidChange();
                }
            }
        }
    }

    function setLoadOptions()
    {
        if ((m_Params.notepanel0 != undefined) || (m_Params.notepanel1 != undefined))
        {
            // Open Note Panel with current lid
            setTimeout("slideTab('tabNotesContents', 'tabNotesContentsInner', false)", 1500);
	        if (m_Params.notepanel1 != undefined)
	        {
            	    // Select the "I'm Looking For" tab
            	    selectNoteTab(1);
	        }
        }
        else if ((m_Params.lifepathpanel0 != undefined) || (m_Params.lifepathpanel2 != undefined))
        {
            // Open LifePath Panel
    	    thisSlideTab = "tabMyPlacesContentsInner";
            setTimeout("slideTab('tabMyPlacesContents', 'tabMyPlacesContentsInner', false)", 1500);
	        if (m_Params.lifepathpanel2 != undefined)
	        {
            	    // Select "My Friends LifePaths"
            	    selectMyPlacesTab(2);
	        }
	    }
        else if (m_Params.articlepanel != undefined)
        {
            // Open Article Panel
    	    thisSlideTab = "tabArticleContentsInner";
            setTimeout("slideTab('tabArticleContents', 'tabArticleContentsInner', false)", 1500);
        }
    }

    function onMapClickViewer(e)
    {
        if (m_ctrlMouseClickViewer.active)
        {
            selectHover(e);
        }
    }

    function onMapRightClickViewer(e)
    {
        selectHover(e);
        return false;
    }

    var poi_popup;
    var poi_lonlat;
    var cur_request;

    function selectHoverNote(e)
    {
        if (LABEL_MODE.LabelsNotes == m_layerLabel.mode)
        {
            if (cur_request)
            {
                cur_request.abort();
                cur_request = null;
            }

            var params = {};
            poi_lonlat = m_Map.getLonLatFromViewPortPx(e.xy);
            params.lon = poi_lonlat.lon;
            params.lat = poi_lonlat.lat;

            params.lm = "no";

            params.z = (m_Map.zoom + 1);
            params.click = "Y";
            params.floatMode = "t";
            params.seqnum = Math.round(Math.random() * 1000000);

            cur_request = OpenLayers.Request.GET({ url: EP_INFOBOX, params: params, success: onLoadPOINote, scope: this });
        }
    }

    function selectHover(e, nopan, refresh)
    {
        if (poi_popup)
        {
            poi_popup.destroy();
            m_Map.removePopup(poi_popup);
            poi_popup = null;
        }
        if (cur_request)
        {
            cur_request.abort();
            cur_request = null;
        }

        setStatusBar("Selecting...");

        var params = {};
        if (typeof (e) == "number" || typeof (e) == "string") // scalar lid value
        {
            poi_lonlat = null;
            if (nopan)
            {
                poi_lonlat = -1;
            }
            else if (undefined != refresh)
            {
                poi_lonlat = -2;
            }
            params.lid = e;
        }
        else 
        {
            poi_lonlat = m_Map.getLonLatFromViewPortPx(e.xy);
            params.lon = poi_lonlat.lon;
            params.lat = poi_lonlat.lat;
        }
        switch (m_layerLabel.mode)
        {
            case LABEL_MODE.Labels:
                params.lm = "l";
                break;
            case LABEL_MODE.LabelsNotes:
                params.lm = "ln";
                break;
            case LABEL_MODE.Off:
            default:
                params.lm = "0";
                break;
        }
        params.z = (m_Map.zoom + 1);
        params.click = "Y";
        params.floatMode = "t";
        params.seqnum = Math.round(Math.random() * 1000000);
	    // No inside lid when user clicks on map
	    g_SelectedLidInside = "";

	    cur_request = OpenLayers.Request.GET({ url: EP_INFOBOX, params: params, success: onLoadPOI, scope: this });
    }

    function onLoadPOINote(response)
    {
        cur_request = null;
        if (response)
        {
            if (200 == response.status)
            {
                var format = new OpenLayers.Format.JSON();
                var data = format.read(response.responseText);
                var refreshmap = false;

                if (!data) return; // this can happen when there is bad data, like an orphaned note

                if (data.lid && data.lid.length)
                {
                    if (data.lon && data.lat)
                    {
                        var lonlat = new OpenLayers.LonLat(data.lon, data.lat); // infobox gave us centroid
                        poi_lonlat = lonlat;

                        if (data.style == "box")
                        {
                            poi_popup = new OpenLayers.Popup("floatpanel",
                    	        poi_lonlat,
                    	        new OpenLayers.Size(190, 220),
                    	        "<div>" + data.html + "</div>", true, onPopupClose);
                            poi_popup.closeDiv.style.right = "25px";
                            poi_popup.closeDiv.style.top = "12px";
                            m_Map.addPopup(poi_popup, true);
                        }
                    }
                }
            }
        }
    }

    function onLoadPOI(response)
    {
        OpenLayers.Console.log("za:onLoadPOI");
        cur_request = null;
        if (response)
        {
            if (200 == response.status)
            {
                var format = new OpenLayers.Format.JSON();
                var data = format.read(response.responseText);
                var refreshmap = false;

                if (!data) return; // this can happen when there is bad data, like an orphaned note
                
                // update our global var shared with other components (wiki tab, etc.)
                g_SelectedLid = data.lid ? data.lid : "";

                if (data.lid && data.lid.length)
                {
		    // We only show the cloud if the style is set to cloud AND there are real lon/lat coords
                    var showcloud = (data.style == "cloud");

                    if (!poi_lonlat || poi_lonlat == -1 || poi_lonlat == -2)
                    {
                        if (poi_lonlat == -2) refreshmap = true; // caller requested map refresh after load
                        
                        if (data.lon && data.lat)
                        {
                            var lonlat = new OpenLayers.LonLat(data.lon, data.lat); // infobox gave us centroid
                            // we only set map center and zoom when poi_lonlat is null (not when -1)
                            if (!poi_lonlat || poi_lonlat == -2) m_Map.setCenter(lonlat, parseInt(data.z) - 1);
                            poi_lonlat = lonlat;

			                // Clear global lonlat for InfoBox and note posting
			                ibFloatPanelLon = 0.0;
			                ibFloatPanelLat = 0.0;
			                ibFloatPanelZ = 15;
                        }
                        else
                        {
                            // we don't have a lon/lat so we don't show cloud
                            showcloud = false;
                            if (!m_Map.getCenter())
                            {
                                // if nothing above has setup map then center on continental US
                                ZoomToArea("-14391036.5", "7053095.0", "-7509756.5", "2268967.0");
                            }
                        }
                    }

                    if (showcloud)
                    {
                        poi_popup = new OpenLayers.Popup.FramedCloud("floatpanel",
                            poi_lonlat,
                            new OpenLayers.Size(265, 95),
                            "<div>" + data.html + "</div>", null, true, onPopupClose);
                        poi_popup.autoSize = false;
                        m_Map.addPopup(poi_popup, true);
                    }
		            // Style=="box" signifies a yellow note popup
		            if (data.style == "box")
            	    {
                	    poi_popup = new OpenLayers.Popup("floatpanel",
                    	        poi_lonlat,
                    	        new OpenLayers.Size(190, 220),
                    	        "<div>" + data.html + "</div>", true, onPopupClose);
                	    poi_popup.closeDiv.style.right = "28px";
            	        poi_popup.closeDiv.style.top = "10px";
                	    m_Map.addPopup(poi_popup, true);
            	    }
        		    // Note or cloud, we still set the status bar
                    setStatusBar(data.shortnamehtml);
                }
                else
                {
                        setStatusBar("No selection");
                }

                // set the hash for link
                document.location.hash = g_SelectedLid.length > 0 ? g_SelectedLid : "#";
                YAHOO.util.Cookie.set("CurrentLID", g_SelectedLid, { domain: "zoomatlas.com", path: "/", expires: new Date("January 1, 2025") });

                // notify tabs in case it's a new lid selected
                onSelectedLidChange();

                if (refreshmap) repaintMapTiles(m_Map.getExtent());
            }
        }
    }

    // A LID was selected from one of the tabbed windows (GoInside, Article, etc) or the breadcrumbs
    function g_setGlobalSelection(newLid, insideLid)
    {
        // Get rid of currently-displayed bubble
        closePopup();

	// Create the minimal InfoBox parameters to simulate a click
        var params = {};
        
	if (insideLid == null)  { params.lid = newLid; }
	else			{ params.lid = insideLid; }

	// We simulate a click on the property object, but set the insideLid if it was provided
	g_SelectedLidInside = insideLid;
        params.click = "Y";
        params.floatMode = "t";
        params.seqnum = Math.round(Math.random() * 1000000);

	// Force the onLoadPOI handler to listen to the suggested bounding box from InfoBox
        poi_lonlat = null;

        cur_request = OpenLayers.Request.GET({ url: EP_INFOBOX, params: params, success: onLoadPOI, scope: this });
    }
    
    function onPopupClose(e)
    {
        closePopup();
        OpenLayers.Event.stop(e);
    }

    function closePopup()
    {
        if (poi_popup)
        {
            poi_popup.destroy();
            m_Map.removePopup(poi_popup);
            poi_popup = null;
        }
    }
    
    /**************************
    Map tile display hooks
    **************************/

    function getSatURL(bounds, tile)
    {
        bounds = this.adjustBounds(bounds);
        var res = this.map.getResolution();
        var ctr = bounds.getCenterLonLat();
        var tileX = Math.floor((ctr.lon - this.maxExtent.left) / (res * this.tileSize.w));
        var tileY = Math.floor((ctr.lat - this.maxExtent.bottom) / (res * this.tileSize.h));
        var tileZ = this.map.zoom + 1;

        var path;
        var tileXmsw = 0;
        if (tileX > 10000)
        {
            tileXmsw = Math.floor(tileX / 10000);
        }
        tileX %= 10000;

        var tileYmsw = 0;
        if (tileY > 10000)
        {
            tileYmsw = Math.floor(tileY / 10000);
        }
        tileY %= 10000;

        path = "S" + tileZ + "/" + tileYmsw + "/" + tileY + "/" + tileXmsw + "/" + tileX + "." + this.type;

        var url = this.url;
        if (url instanceof Array)
        {
            url = this.selectUrl(path, url);
        }
        return url + path;
    }

    // TODO: get caching going on JAS1 instead of direct to MOM1
    var urlsMapOMatic = ["http://64.18.219.20/"
			, "http://64.18.219.21/"
			, "http://64.18.219.22/"
			, "http://64.18.219.23/"
			, "http://64.18.219.24/"
			, "http://64.18.219.25/"
			, "http://64.18.219.26/"
			, "http://64.18.219.27/"
			, "http://64.18.219.28/"
			, "http://64.18.219.29/"
			, "http://64.18.219.30/"
			, "http://64.18.219.31/"
			];
			
    function getMapURL(bounds, tile)
    {
        bounds = this.adjustBounds(bounds);
        var res = this.map.getResolution();
        var ctr = bounds.getCenterLonLat();
        var tileX = Math.floor((ctr.lon - this.maxExtent.left) / (res * this.tileSize.w));
        var tileY = Math.floor((ctr.lat - this.maxExtent.bottom) / (res * this.tileSize.h));
        var tileZ = this.map.zoom + 1;

        var url = this.url;
        var path;
        if (tileZ < 13) // use pregen mapman tiles for z1-12
        {
//            var path;
//            var tileXmsw = 0;
//            if (tileX > 10000)
//            {
//                tileXmsw = Math.floor(tileX / 10000);
//            }
//            tileX %= 10000;

//            var tileYmsw = 0;
//            if (tileY > 10000)
//            {
//                tileYmsw = Math.floor(tileY / 10000);
//            }
//            tileY %= 10000;

//            path = "maptiles/Z" + tileZ + "/" + tileYmsw + "/" + tileY + "/" + tileXmsw + "/" + tileX + "." + this.type;
            path = "MapManager/MapManager?z=" + tileZ + "&r=" + tileY + "&c=" + tileX + "&sn=" + m_SerialVersion;
        }
        else // use mapomatic
        {
            url = urlsMapOMatic;
            var serialversion = m_SerialVersion;
            if (tile.purgecache)
            {
                // this tile has been marked as being part of the invalidated area
                // from a refresh so we negate the serial number (client batch)
                // so mapomatic will purge any cache
                serialversion *= -1;
                tile.purgecache = false;
            }
            // SITEMOVE TODO - hook up cache to map-o-matic, for now we go direct
            //return "http://64.18.219.46/geowebcache/service/tms/" + this.serviceVersion + "/" + this.layername + "/" + tileZ + "/" + tileX + "/" + tileY + "." + this.type + "?sn=" + serialversion + ((m_Args.debug != undefined) ? "&debug=1" : "");
            path = this.serviceVersion + "/" + this.layername + "/" + tileZ + "/" + tileX + "/" + tileY + "." + this.type + "?sn=" + serialversion + ((m_Args.debug != undefined) ? "&debug=1" : "");

            if (m_Args.artist != undefined)
            {
                // invoked with 'artist' flag, use the dev site map-o-matic
                return EP_MAPOMATICART + path;
            }
        }
        
        if (url instanceof Array)
        {
            url = this.selectUrl(path, url);
        }
        return url + path;
    }

    function getLabelURL(bounds, tile)
    {
        bounds = this.adjustBounds(bounds);

        var imageSize = this.getImageSize();
        var newParams = {
            'BBOX': this.encodeBBOX ? bounds.toBBOX() : bounds.toArray()
            , 'WIDTH': imageSize.w
            , 'HEIGHT': imageSize.h
            , 'sn': m_SerialVersion
            , 'gkt': m_GKT
        };

        var alturl = null;
        if (tile.bypasscache)
        {
            if (this.map.zoom + 1 > 6)
            {
                alturl = "http://www.zoomatlas.com/gk/geoserver/wms";
            }
            tile.bypasscache = false;
        }

        var requestString = this.getFullRequestString(newParams, alturl);
        return requestString;
    }

    function onlayerMapPreviewPreUpdate(info)
    {
        var map = m_ctrlPreview.ovmap;
        var size = m_layerMapPreview.getTilesBounds().getSize();
        var center = info.center;
        var bounds = new OpenLayers.Bounds(center.lon - size.w / 2, center.lat - size.h / 2,
            center.lon + size.w / 2, center.lat + size.h / 2);
        var resolution = map.getResolution();
        var tileZ = info.zoom + 1;
        var tileX = Math.floor((bounds.left - map.maxExtent.left) / (resolution * map.tileSize.w));
        var tileY = Math.floor((bounds.bottom - map.maxExtent.bottom) / (resolution * map.tileSize.h));
        var hint = tileZ + "," + tileX + "," + tileY;

        tileX = Math.floor((bounds.right - map.maxExtent.left) / (resolution * map.tileSize.w));
        tileY = Math.floor((bounds.top - map.maxExtent.bottom) / (resolution * map.tileSize.h));
        hint += "," + tileX + "," + tileY;

        var params = { hint: hint, sn: Math.round(Math.random() * 1000000) };

        OpenLayers.Request.GET({ url: EP_MAPMANAGER, params: params, async: false });
    }

    function onlayerMapGridInit(info)
    {
        // this is a one-shot so we unhook
        m_layerMap.events.unregister("gridinitialized", this, onlayerMapGridInit);

        if (m_layerMap.grid)
        {
            for (var iRow = 0, rlen = m_layerMap.grid.length; iRow < rlen; iRow++)
            {
                var row = m_layerMap.grid[iRow];
                for (var iCol = 0, clen = row.length; iCol < clen; iCol++)
                {
                    var tile = row[iCol];
                    tile.purgecache = true;
                }
            }
        }
    }

    function onlayerLabelGridInit(info)
    {
        // this is a one-shot so we unhook
        m_layerLabel.events.unregister("gridinitialized", this, onlayerLabelGridInit);

        if (m_layerLabel.grid)
        {
            for (var iRow = 0, rlen = m_layerLabel.grid.length; iRow < rlen; iRow++)
            {
                var row = m_layerLabel.grid[iRow];
                for (var iCol = 0, clen = row.length; iCol < clen; iCol++)
                {
                    var tile = row[iCol];
                    tile.bypasscache = true;
                }
            }
        }
    }

    function repaintMapTiles(bbox, nobypasscache)
    {
        var params = {
            z: (m_Map.zoom + 1)
        , type: 17
        , bbox: bbox.toBBOX()
        , sn: Math.round(Math.random() * 1000000)
        };

        var request = OpenLayers.Request.GET({
            url: EP_MAPMANAGER
            , params: params
            , async: false
        });

        onRegenSuccess(request, nobypasscache);

        if (!m_IsMiniViewer && m_EditInitialized)
        {
            m_layerEdit.destroyFeatures();
            setMode(MODE.Select);
        }
    }

    function onRegenSuccess(response, nobypasscache)
    {
        var bounds = m_Map.getExtent();
        RedrawMap(bounds.left, bounds.bottom, bounds.right, bounds.top, true, nobypasscache);
        cursor_clear();
    }

    /**************************
        View UI event handlers
    **************************/
    function setUserOption(name, value)
    {
        var expires = new Date();
        expires.setFullYear(expires.getFullYear() + 1);

        YAHOO.util.Cookie.setSub("zamapeditor", name, value,
            { domain: "zoomatlas.com", path: "/", expires: expires });
    }

    function getUserOption(name)
    {
        return YAHOO.util.Cookie.getSub("zamapeditor", name, { domain: "zoomatlas.com", path: "/" });
    }

    function onChangeOpacity(info)
	{
	    setOpacity(info.opacity);
	    setUserOption("opacity", info.opacity)
	}
	
	function setOpacity(opacity)
	{
	    if (opacity < 0.5) // full map, turn off sat
	    {
	        m_layerSat.setVisibility(false);
	        m_ctrlFadeBar.setOpacity(0);
	    }
		else if (!m_layerSat.getVisibility())
		{
			m_layerSat.setVisibility(true);
		}
		if (opacity > 9.5) // full sat, turn off map
		{
			m_layerMap.setVisibility(false);
			m_ctrlFadeBar.setOpacity(10);
        }
		else if (!m_layerMap.getVisibility())
		{
			m_layerMap.setVisibility(true);
		}

		m_layerSat.setOpacity(Math.min(opacity / 5, 1.0));
		m_layerMap.setOpacity(Math.min(Math.max(10 - opacity, 0) / 5, 1.0));
	}

	function onClickLabelVisibility()
	{
	    switch (m_layerLabel.mode)
	    {
	        case LABEL_MODE.Off:
	            setLabelMode(LABEL_MODE.Labels);
	            break;
	        case LABEL_MODE.Labels:
	            setLabelMode(LABEL_MODE.LabelsNotes);
	            break;
	        case LABEL_MODE.LabelsNotes:
	            setLabelMode(LABEL_MODE.Off);
	            break;
	    }
	    setUserOption("labels", m_layerLabel.mode);
	}

	function setLabelMode(mode)
	{
	    switch (parseInt(mode))
	    {
	        case LABEL_MODE.Off:
	            m_layerLabel.setVisibility(false);
	            m_layerLabel.mode = LABEL_MODE.Off;
	            m_btnLabels.displayClass = "zaControlLabelOff";
	            // update prompt to show next state
	            m_btnLabels.panel_div.title = "Show Labels";
	            break;
	        case LABEL_MODE.Labels:
	            m_layerLabel.mergeNewParams({ layers: "wa:roads,wa:labels,wa:borders"
                    , styles: "label_roads,labels,borders"
	            });
	            m_layerLabel.mode = LABEL_MODE.Labels;
	            m_layerLabel.setVisibility(true);
	            m_btnLabels.displayClass = "zaControlLabelOn";
	            // update prompt to show next state
	            m_btnLabels.panel_div.title = "Show Notes and Labels";
	            break;
	        case LABEL_MODE.LabelsNotes:
	            m_layerLabel.mergeNewParams({ layers: "wa:roads,wa:labels,wa:borders,wa:notes"
                    , styles: "label_roads,labels,borders,notes"
	            });
	            m_layerLabel.mode = LABEL_MODE.LabelsNotes;
	            m_layerLabel.setVisibility(true);
	            m_btnLabels.displayClass = "zaControlLabelNoteOn";
	            // update prompt to show next state
	            m_btnLabels.panel_div.title = "Hide Labels";
	            break;
	    }
	    m_ctrlPanel.redraw();
	}

	function onClickRefresh()
	{
	    repaintMapTiles(m_Map.getExtent(), true);
	}

	function onClickHoverOn()
	{
	    m_btnHover.panel_div.title = "Switch to click for notes";
	    m_ctrlHover.activate();
	    setUserOption("hover", "on");
	}

	function onClickHoverOff()
	{
	    m_btnHover.panel_div.title = "Switch to hover for notes";
	    closePopup();
	    m_ctrlHover.deactivate();
	    setUserOption("hover", "off");
	}

	function onlayerMapPreviewToggle(info)
    {
        setUserOption("preview", info.state);
    }

    function cursor_wait()
    {
        var el = document.getElementById("map");
        if (el) el.style.cursor = 'wait';
        document.body.style.cursor = 'wait';
    }

    function cursor_clear()
    {
        var el = document.getElementById("map");
        if (el) el.style.cursor = 'default';
        document.body.style.cursor = 'default';
    }

    function searchchanged()
    {
        getSearch(document.getElementById('searchText1').value);
    }

    function getSearch(queryaddr)
    {
        if (m_EventsSearchMap.onsearchrequest.fire())
        {
            // close dialog that may be up showing full text search results
            closeDialog();
            
            var params = {};
            params.q = escape(queryaddr);
            params.sn = Math.round(Math.random() * 1000000);

            var url = "/mediawiki/skins/za_skin/DoNormalizeGetLID.php?" + getParameterString(params);

            YAHOO.util.Connect.asyncRequest("GET", url, { success: this.onSearchSuccessMap, failure: this.onSearchFailureMap });
        }
    }

    function onSearchRequestMap(type, args)
    {
        return true;
    }

    function onSearchFailureMap()
    {
        return true;
    }

    function onSearchSuccessMap(response)
    {
        var data = YAHOO.lang.JSON.parse(response.responseText);

        if (data)
        {
            if (data.queryaddr)
            {
                // no results, put up full text search results dialog
                getSearchFT(data.queryaddr);
            }
            else
            {
                m_EventsSearchMap.onsearchresults.fire(data);
            }
        }

        return data;
    }

    function getSearchFT(queryaddr, page)
    {
        var params = {};
        params.queryaddr = queryaddr;
        if (page) params.page = page;
        params.sc = "map";
        params.sn = Math.round(Math.random() * 1000000);

        var url = "/mediawiki/skins/za_skin/LookupFT.php?" + getParameterString(params);

        YAHOO.util.Connect.asyncRequest("GET", url, { success: this.onSearchFTSuccessMap, failure: this.onSearchFailureMap });
    }

    function onSearchFTSuccessMap(response)
    {
        openDialog(response.responseText, "700");
        m_dlgModal.setHeader("Search Results");
    }

    function onSearchResultsMap(type, args)
    {
        var data = args[0];
        
        if (data.feature_type == -2130706432)
        {
        }
        if (data.BBOX)
        {
            // zoom to extent
            m_Map.zoomToExtent(new OpenLayers.Bounds.fromString(data.BBOX));
        }
        else if (data.lid)
        {
            ZoomToLid(data.lid);
        }
        else if (data.lon && data.lat)
        {
            var lonlat = new OpenLayers.LonLat(data.lon, data.lat);
            m_Map.setCenter(lonlat, parseInt(data.z) - 1, false, true);
        }
        if (!m_IsMiniViewer && data.lid)
        {
            selectHover(data.lid, true);
        }
    }
    
    function checkInitialErase(matchstr)
    {
        var textObj = document.getElementById("searchText1");
        
        if (!matchstr) matchstr = "Start typing ";
        if (textObj.value.substr(0, matchstr.length) == matchstr)
        {
            textObj.value = "";
        }
    }

    var m_ZTLRefresh;

    function ZoomToLid(lid, refresh)
    {
        // Create the minimal InfoBox parameters to simulate a click
        var params = {};

        params.lid = lid;
        params.click = "Y";
        params.floatMode = "t";
        params.seqnum = Math.round(Math.random() * 1000000);

        m_ZTLRefresh = refresh;

        cur_request = OpenLayers.Request.GET({ url: EP_INFOBOX, params: params, success: onLoadZoomToLid, scope: this });
    }

    function onLoadZoomToLid(response)
    {
        cur_request = null;
        if (response)
        {
            if (200 == response.status)
            {
                var format = new OpenLayers.Format.JSON();
                var data = format.read(response.responseText);

                if (!data) return; // this can happen when there is bad data, like an orphaned note

                if (data.lid && data.lid.length)
                {
                    if (data.lon && data.lat)
                    {
                        var lonlat = new OpenLayers.LonLat(data.lon, data.lat); // infobox gave us centroid
                        m_Map.setCenter(lonlat, parseInt(data.z) - 1, false, true);

                        if (m_ZTLRefresh)
                        {
                            var bounds = m_Map.getExtent();
                            if (1 == m_ZTLRefresh) // bypass label/note cache
                            {
                                RedrawMap(bounds.left, bounds.bottom, bounds.right, bounds.top, true, false);
                            }
                            else if (2 == m_ZTLRefresh) // regen map tiles (also bypasses cache)
                            {
                                repaintMapTiles(bounds);
                            }
                        }
                    }
                }
            }
        }
    }

    function ZoomToArea(left, bottom, right, top, zoom)
    {
        var bounds = new OpenLayers.Bounds(left, bottom, right, top);
        // note we need to convert between openlayer's zoom levels and our (off by 1)
        m_Map.setCenter(bounds.getCenterLonLat(), zoom ? parseInt(zoom) - 1 : m_Map.getZoomForExtent(bounds));
    }

    function RedrawMap(left, bottom, right, top, local, nobypasscache)
    {
        // increment our serial number so the tiles don't come from the cache
        m_SerialVersion += 1;

        if (!nobypasscache)
        {
            // this will bypass the cache, we only do this in some cases (like edit) becuase it causes the
            // label layer to not use metatiling thus it looks inconsistent
            m_layerLabel.events.register("gridinitialized", this, onlayerLabelGridInit);
        }

        m_layerMap.events.register("gridinitialized", this, onlayerMapGridInit);

        m_layerLabel.redraw();
        m_layerMap.redraw();
    }

    function openDialog(body, width)
    {
        if (undefined != width)
        {
            m_dlgModal.cfg.setProperty("width", width + "px");
        }
	// m_dlgModal.cfg.setProperty("height", "300px");
        m_dlgModal.setBody(body);
        m_dlgModal.render();
        m_dlgModal.show();
    }

    function closeDialog()
    {
        m_dlgModal.hide();
    }

    function parseHashParams(paramsString)
    {
        var parameters = {};
        var pairs = paramsString.split(/[:;&]/);
        for (var i = 0, len = pairs.length; i < len; ++i)
        {
            var keyValue = pairs[i].split('=');
            if (keyValue[0])
            {
                var key = decodeURIComponent(keyValue[0]);
                var value = keyValue[1] || ''; //empty string if no value

                //decode individual values
                value = value.split(",");
                for (var j = 0, jlen = value.length; j < jlen; j++)
                {
                    value[j] = decodeURIComponent(value[j]);
                }

                //if there's only one value, do not return as array                    
                if (value.length == 1)
                {
                    value = value[0];
                }

                parameters[key] = value;
            }
        }
        return parameters;
    }

    //]]>
