/** 
 * @fileoverview cMapObjects - Generic Front-end control of GoogleMaps objects
 *
 * @author Bjorn Brala bjorn@swis.nl
 * @version 2.0
 */

/**
 * Construct the MapObjects object
 * @class cMapObjects class
 * @constructor
 */
 function cMapObjects(){
    /************************************************
    *                                               *
    *    Initialisation, listeners and settings.    *
    *                                               *
    ************************************************/
        /** 
        *   Initiazlize the map  
        *   @extends cMapObjects        
        *   @param string sMapid Name of map div
        *   @param float fLat latitute of startpoint
        *   @param float fLon longitute of startpoint
        *   @param int Izoom default zoom level
        **/     
        this.init = function(sMapId, fLat, fLong, iZoom){
            this.oMap = new GMap2(document.getElementById(sMapId));

            cMapObjectsPoly.apply(this, []); // Extend with poly objects
            
            this._fLat = parseFloat(fLat);                      // Start viewport
            this._fLng = parseFloat(fLong);                     // |
            this._iZoom = parseInt(iZoom);                    // |
            this.loadSettings();                    // Load map specific settings
            this.loadListeners();                   // Load map listeners

            
            if ( this._bPreloaderEnabled ){
                this.oPreloader = new cPreloader(this.oMap);    // Preloader
                this.oPreloader.init(this.oMap, 177);                // Bind to map object                
            }
            
            this.aCategorystate = new Array();                  // Category state array. Filled when category
                                                                // visibility.
            
            this.oPoints        = new Object();                 // Object contains geometry points
            
            //this._oTooltip = document.getElementById('GeoTooltip'); // Tooltip element
            
            this._oTooltip = new cTooltip(this.oMap, 'GeoTooltip', 22, 22, 1, 0);
            
            
            this._bUpdateWaiting = false;                       // Set queue to clean
                                            
            this.loadObjects();                                 // Start loading of markers in current view
        }
        
        
        /**
        *  @desc Clean init. 
        **/
        this.cleanInit = function(sMapId, fLat, fLong, iZoom, mapOptions){
            var opts = mapOptions || new Object();
            this.oMap = new GMap2(document.getElementById(sMapId), mapOptions);
            
            this._fLat = parseFloat(fLat);                      // Start viewport
            this._fLng = parseFloat(fLong);                     // |
            this._iZoom = parseInt(iZoom);                    // |
            
            this.oMap.setCenter(new GLatLng(this._fLat, this._fLng), this._iZoom); 
            
            this.oMap.enableContinuousZoom();
        }
        
        /**
        *    Load listeners needed on the oMap
        **/
        this.loadListeners = function(){
            
            var oRef = this;
        
            /** Update map objects on moveend ( also triggers on zooms ) **/
            GEvent.addListener(oRef.oMap, 'moveend', function() { oRef.loadObjects();} );
            
            /** Remove the tooltip when movement starts **/
            GEvent.addListener(oRef.oMap, 'movestart', function() { oRef._oTooltip.hide(); } );
            
            /** Polygone check map listener thingie **/
            GEvent.addListener(oRef.oMap, 'click', function(overlay, point) { 
                if ( point ) {
                    var result = oRef.checkClickedPoly(point);
                    if ( result ) {
                        //oRef.htmlSubgroepenRequest(oRef.oPoints[result]._GeoInfo['id']);
                        oRef.trigger(result, 'click');
                    }
                }
            });
            
            /* Hover en check poly hover
            GEvent.addListener(oRef.oMap, 'mousemove', function(point){
                var result = oRef.checkClickedPoly(point)
                if ( result ) {
                    oRef.trigger(result, 'mouseover');
                    if (false &&  oRef._bEnableHover ) {
                        oRef._oTooltip.setText(oRef.oPoints[result].titel);
                        oRef._oTooltip.show(point);
                    }
                } else {
                    oRef._oTooltip.hide();
                }
            });
            */
        }
        
        /**
        *    Load settings for the map.
        **/
        this.loadSettings = function(){
            this._iLoadInterval    = 100;       // ms Interval per group of objects
            this._iLoadStep        = 10;        // Objects per load
            this._bQueueLastRequest = true;     // Que next update call.
                                                    // True     - Update
                                                    // False    - Do nothing
                                                
            this._bPreloaderEnabled = oSettings._bPreloaderEnabled;              // Enable preloader
            this._bEnableHover      = oSettings._bEnableHover;                   // Enable hover
            this._bEnableBalloon    = this._bEnableBalloon          || 'true';   // Enable balloon
            this._iPreferredZoom    = parseInt(oSettings._iPreferredZoom);       // Preferred zoom level. Map will zoom 
                                                                                 // here when gotoHiddenId()
                                                
            this._sCategoryKey = 'sg_id';       // Category key in database
            
            // Wip 2.1 Filters
            // Initialize filter palceholders.
            // Use: 
            //      this.createFilter(sKey)
            //      this.removeFilter(sKey)
            //      this.addFilterItem(sKey, sValue)
            //      this.removeFilterItem(sKey, sValue)
            this._aGeoFilterKeys = new Array();                 
            this._aGeoFilterState = new Array();
            
                        
            this._sDataUrl = 'GeoStart/ajax/getData.php';    // Relative URL for GeoStart data requests            
            
            this._oMenu = oMenu;                // Menu object
            
            this.oMap.setCenter(new GLatLng(parseFloat(this._fLat), parseFloat(this._fLng)), parseInt(this._iZoom), eval(oSettings.map_default_type)); 

            this.oMap.enableScrollWheelZoom();    // Enable scroll wheel zoom.
            
            /**
            *   Powetred by geostart
            **/            
            this.oMap.addControl(new powered_by_geostart(this.oMap));
            
            /**
            *   Map controls.
            **/
            var eZoomPanControl= new Control_grey_panzoom(this.oMap);
            this.oMap.addControl(eZoomPanControl); 
            
            var eKaartControl = new Control_grey_center(this.oMap, 'Kaart');
            this.oMap.addControl(eKaartControl);  
            
            var eSatellietControl = new Control_grey_right(this.oMap, 'Satelliet');
            this.oMap.addControl(eSatellietControl); 
            
            var eHelpControl = new Control_grey_left(this.oMap, 'Help');
            this.oMap.addControl(eHelpControl); 
            
            var eGreenmap = new control_greenmap(this.oMap, 160, 0);
            this.oMap.addControl(eGreenmap); 
            
            var eLogoeu = new control_logoeu(this.oMap, 240, 0);
            this.oMap.addControl(eLogoeu); 
            
            var eLeaderplus = new control_leaderplus(this.oMap, 300, 0);
            this.oMap.addControl(eLeaderplus); 
            
            this.oMap.enableContinuousZoom();
            
        }
        
        
        /**
        *   Load listeners for geometry. Currently supports only markers.
        *   @param string sMode Currently only support "marker"
        *   @param object oRef Reference object to apply listeners to
        **/
        this.loadGeometryListeners = function(oRef){
            var oRefMapObjects = this;
            /** Open info window on marker **/
            GEvent.addListener(oRef, 'infowindowopen', function() {
                pageHit( sBaseUrl + 'locatie/' + oRef._GeoInfo['titel_key'] );
                if ( oRef.isHidden() ) { // Logic to show/hide marker in conjunction with hover
                    oRef.show();
                    GEvent.addListener(oRef, 'infowindowclose', function(){ oRef.hide(); });                
                }
                /** Add listener, if removed open special window to be able to zoom back to the marker. **/                             GEvent.addListener(oRef, 'remove', function() {
                        oRefMapObjects.oMap.closeInfoWindow();                                            
                        var iZoom   = oRef._GeoInfo['zoom'];
                        if ( iZoom > oRefMapObjects.oMap.getZoom() ){
                            var id = oRef._GeoInfo['id'];
                            var fLat    = oRef.getPoint().lat();
                            var fLng    = oRef.getPoint().lng();
                            oRefMapObjects.oMap.openInfoWindowHtml(oRef.getPoint(), '<div class="infoWindow">Om deze locatie te zien dient u verder ingezoomed te zijn.<br /><br /><hr /><a class="zoomAnchor" href="#" onclick="oMapObjects.oMap.closeInfoWindow();oMapObjects.gotoHiddenId(' + id + ');return false;"><img src="images/help_plus.gif" alt="" /> Zoom naar marker</a></div>');
                        }
                    });
            });
            
            /** Remove the 'remove' listener when infowindow is closed **/
            GEvent.addListener(oRef, 'infowindowclose', function(){
                GEvent.clearListeners(oRef, 'remove');
            });
            
            /** Onclick event. Hide tooltip ( looks bad on moving map ) and open info window **/
            GEvent.addListener(oRef, 'click', function() {
                    if ( oRefMapObjects._bEnableHover ) {
                        oRefMapObjects._oTooltip.hide();                          // Onclick hide the tooltip
                    }
                    //oRef.openInfoWindowHtml(oRef.content);                  // Open the InfoWindow
                    oRefMapObjects.htmlSubgroepenRequest(oRef._GeoInfo['id']);
                    swap('tab', false);                                     // Close the InfoTab
                    if ( oRef.bHoverShow ) {                                // Logic if tooltip is shown
                        oRef.tempMouseoutListener.remove();                     // Cancel listnerer that hides marker after mouseout
                        oRef.bForceShow = true;                                 // Tag the marker as forced to show
                        
                        // Remove the marker again when the infowindow is closed
                        oRef.tempClickListener = GEvent.addListener(oRef, 'infowindowclose', function(){
                            oRef.hide();                                
                            // Reset the mouseout listener for a hidden marker
                            GEvent.addListener(oRef, 'mouseout', function(){ oRef.hide(); });
                        });
                        oRef.show();                                            // Show the marker
                    }
            });                            
            
            /** Mouseover event to facilitatie our tooltip **/
            GEvent.addListener(oRef, "mouseover", function() {
                if ( oRefMapObjects._bEnableHover ) {
                    if ( oRefMapObjects._oTooltip.markerOnScreen(oRef.getPoint()) ) {                // Check if marker is onscreen
                        oRefMapObjects._oTooltip.checkAlign(oRef._GeoInfo['g_type']);
                        oRefMapObjects._oTooltip.setText(oRef.tooltip);
                        oRefMapObjects._oTooltip.show(oRef.getPoint());
                        
                        if ( oRef.isHidden() ) {                            // If marker is hidden show it
                            oRef.bHoverShow = true;                             // Mark the marker as "shown by hover"
                            oRef.show();                                        // Show the marker
                            
                            // Add a listener to hide it again on mouseout
                            oRef.tempMouseoutListener = GEvent.addListener(oRef, 'mouseout', function(){
                                oRef.hide();                                    // Hide the marker
                                oRef.tempMouseoutListener.remove();             // Remove self (listener)
                            });
                        }
                    }
                }
            });
            
            /** Mouseout event for tooltip **/                
            GEvent.addListener(oRef, "mouseout", function() {
                if ( oRefMapObjects._bEnableHover ) {
                    oRefMapObjects._oTooltip.hide();
                    oRef.bHoverShow = false;
                }
            });
        }
       
    /************************************************
    *                                               *
    *    HTML generation functions.                 *
    *                                               *
    ************************************************/
       /**
       *    generate InfoWindow with embed/link information for object
       *    @param string sKey key ( keyfield type ) for marker.
       *    @param int iId Marker object id
       *    @return string sHtml Window containing link and embed information
       **/
       this.htmlLinkEmbedWindow = function( sKey, iId ) {
           //pageHit( sBaseUrl + 'locatie/' + sKey + '/link' );
           var sHtml = '<div style="width:550px;"><div style="float:left;width:260px;">' + 
                            '<p style="color:#666;width:245px;">' + 
                                'Kopieer de onderstaande link om naar de locatie te linken:<br><input id="balloonPermLink" type="text" value="' + sBaseUrl + 'locatie/' + this.oPoints[iId]._GeoInfo['titel_key'] + '" onclick="this.select();"><br>' + 
                                '<br>Kopieer de onderstaande broncode in uw website voor een mini-kaart van de locatie.' + 
                            '</p>' + 
                            '<p id="iframeinput">' + 
                                'Breedte: <input onkeyup="oMapObjects.updateIframecode();" id="ifb" value="257" type="text"> <input type="hidden" value="' + iId + '" id="ifid"> Lengte: <input id="ifl" onkeyup="oMapObjects.updateIframecode();" value="310" type="text">' +
                            '</p>' +
                            'Broncode:<br><textarea onclick="this.select();" id="iframecode">' + this.getMiniMapCode(sKey, 310, 257) + '</textarea>' + 
                         '</div>' + 
                         '<div style="float:right;width:280px;">' +
                            'Voorbeeld:<br><iframe src="' + sBaseUrl + '?minimap=1&marker_key=' + sKey + '&w=257&h=310" width="257" height="310"></iframe>' +
                         '</div>' + 
                       '</div>' ;
           return sHtml;   
       }       
       
       
       /**
       *    Generate info window based on Array of information gotten from AJAX request
       *    @param array aInfo Information about the point
       *    @return string content String with info window html for marker
       **/
       this.htmlInfoWindow = function(aInfo) {
                /** Info window HTML code. **/
                var sDirection = '';
                var content = '<div class="infoWindow">';

                if(bWebbeheerLoggedIn){
                    content += '<img style="float:right;cursor:pointer;margin:0;width:20px;height:16px;background:transparent;" src="webbeheer/images/edit.gif" onclick="editItem(\'markers\',' + aInfo.id + ');" title="wijzig" alt="wijzig" />';
                    // content += '<img style="cursor:pointer;margin:0;width:17px;height:16px;background:transparent;" src="webbeheer/images/delete.gif" onclick="deleteItem(\'markers\',' + aInfo.id + ');" title="verwijder" alt="verwijder" /><br clear="all"/>';
                }


                content += "<b>" + aInfo.titel + "</b><br/>";
                if(aInfo.adres !== '') content += aInfo.adres  + '<br/>'; sDirection = aInfo.adres + ',';
                if(aInfo.postcode !== '') content += aInfo.postcode + '<br/>'; sDirection += aInfo.postcode + ',';
                if(aInfo.plaats !== '') content += aInfo.plaats + '<br/>'; sDirection += aInfo.plaats;
                
                if ( aInfo.omschrijving !== '' ){
                    content += '<a class="right" onclick="showInfo(\'' + aInfo.id + '\');return false;" href="#">Meer informatie</a><br/>';
                } else if ( aInfo.website != '' ) {                
                    if ( aInfo.website.substring(0,7) != 'http://') {
                        aInfo.website = 'http://' + aInfo.website;
                    }
                    
                    content += '<a target="_blank" class="right" href="' + aInfo.website + '">Bezoek de website</a>';
                }
                

                content += '<div id="ballonSubgroepen"><small>Rubrieken worden geladen...</small></div>';
                
                //content += '<div class="ballonSubgroep"><img align="absmiddle" class="png subgroep" src="' + oIcons[aInfo.sg_id].labelImage + '" style="clear:left;margin:0px 8px 0 0 ;vertical-align:middle;background-color:#' + oIcons[aInfo.sg_id].bgColor + ';height:21px;width:21px;"><a href="#" onclick="oMenu.getItemList(' + aInfo.sg_id + ');return false;">' + aInfo.sg_titel + '</a></div>';

                content += '<div class="ballonButton"><a class="zoomAnchor" href="#" onclick="oMapObjects.oMap.zoomIn();return false;"><img src="GeoStart/images/controls/grey/small_plus.gif" alt="" /> Zoom in</a> <a style="margin-left:10px;" class="zoomAnchor" onclick="oMapObjects.oMap.zoomOut();return false;" href="#"><img src="GeoStart/images/controls/grey/small_min.gif" alt="" /> Zoom uit</a> ';
                content += '<span style="display:block;clear:both;margin-top:3px;"><a href="' + sBaseUrl + 'locatie/' + aInfo.titel_key +'" onclick="oMapObjects.openEmbed(\'' + aInfo.titel_key + '\', \'' + aInfo.id + '\');return false;">Link</a> | <a onclick="pageHit(\'' + sBaseUrl + 'locatie/' + aInfo.titel_key + '/route\'); showDirForm(\'' + sDirection + '\', \'' + aInfo.titel.replace("'", "&quot;" ) + '\');return false;" href="#">Route</a></span></div>';
                                   

                content += '</div>';
                
                return content;
       }
       
       
       this.htmlSubgroepenRequest = function(iId){
            GDownloadUrl('GeoStart/ajax/getCatsForItem.php?iId='+iId, GEvent.callback(this, this.htmlSubgroepen));
       }
       this.htmlSubgroepen = function(oInfo){
            var json = eval('(' + oInfo + ')');
            
            
            var aInfo = json.aSubgroepen;
            var iId = json.aStats.iId;

            var sSubgroep = ''; 

            var i = aInfo.length;
            
            while ( i-- ) { 
                 sSubgroep += '<div class="ballonSubgroep"><img align="absmiddle" class="png subgroep" src="' + oIcons[aInfo[i].sg_id].labelImage + '" style="clear:left;margin:0px 8px 0 0 ;vertical-align:middle;background-color:#' + oIcons[aInfo[i].sg_id].bgColor + ';height:21px;width:21px;"><a href="#" onclick="oMenu.getItemList(' + aInfo[i].sg_id + ');return false;">' + aInfo[i].sg_titel + '</a></div>';
            }            
            oMapObjects.oPoints[iId].openInfoWindowHtml(oMapObjects.oPoints[iId].content.replace('<small>Rubrieken worden geladen...</small>', sSubgroep));
       }
              
       
       /**
       *    Html for the loading marker
       *    return string Element for use with infowindow
       **/
       this.htmlLoadingMarker = function(){
            return '<div class="infowindow">Locatie wordt geladen.<br>Een moment geduld aub.</div>';
       }
       
       /**
       *    Generate info window for minimap based on Array of information gotten from AJAX request
       *    @param array aInfo Information about the point
       *    @return string content String with info window html for marker
       **/
       this.htmlMiniInfoWindow = function(aInfo) {
                
                var content = '<div class="infoWindow">';
                content += '<b>' + aInfo.titel  + '</b><br/><br/>';
                
                if ( aInfo.omschrijving !== '' ){
                    content += '<a class="right" href="' + sBaseUrl + 'locatie/' + aInfo.titel_key + '" target="_blank">Meer informatie</a><br/>';
                } else if ( aInfo.website != '' ) {
                    if ( aInfo.website.substring(0,7) != 'http://') {
                        aInfo.website = 'http://' + aInfo.website;
                    }
                    content += '<a target="_blank" class="right" href="' + aInfo.website + '">Bezoek de website</a>';
                }
                
                //content += '<div><img class="png subgroep" src="' + oIcons[aInfo.sg_id].labelImage + '" style="clear:left;margin:10px 8px 0 0 ;vertical-align:middle;background-color:#' + oIcons[aInfo.sg_id].bgColor + ';height:21px;width:21px;">' + aInfo.sg_titel + '</div>';

                content += '<div class="ballonButton"><a class="zoomAnchor" href="#" onclick="oMapObjects.oMap.zoomIn();return false;"><img src="GeoStart/images/controls/grey/small_plus.gif" alt="" /> Zoom in</a> <a style="margin-left:10px;" class="zoomAnchor" onclick="oMapObjects.oMap.zoomOut();return false;" href="#"><img src="GeoStart/images/controls/grey/small_min.gif" alt="" /> Zoom uit</a> ';
                content += '<span style="display:block;clear:both;margin-top:3px;"><a href="' + sBaseUrl + 'locatie/' + aInfo.titel_key +'" target="_blank">Link</a> | <a target="_blank" href="' + sBaseUrl + 'locatie/' + aInfo.titel_key + '/route">Route</a></span></div>';
                
                content += '</div>';
                
                return content;             
       }
       

    
    /************************************************
    *                                               *
    *    Data handling.                             *
    *                                               *
    ************************************************/
        /**
        *    Handle the loading of data.
        *        - Check if currently loading, if so set queued request to true.
        *        - Generate get string for bounds + zoom
        *        - Call GDownloadUrl for asynchronous data request
        **/
        this.loadObjects = function(){
            if ( !this.isBusy() ) {                     // If no update is running.
                if ( this._bPreloaderEnabled ) {        // Check if Preloader is enabled.
                    this.oPreloader.start();            // Initialize the preloader 
                }
                this.bRemoving = true;                    // Update status, set object to busy removing
                this.bAdding = true;                    // and adding.
                
                var bounds = this.oMap.getBounds();        // Save current bounds
                var iZoom = this.oMap.getZoom();        // Save current zoom
                
                
                // Generate the $_GET string for _sDataUrl
                var sGet = "?nelng=" + ( bounds.getNorthEast().lng() ) + "&nelat=" + ( bounds.getNorthEast().lat() ) + "&swlng=" + ( bounds.getSouthWest().lng() ) + "&swlat=" + ( bounds.getSouthWest().lat() ) + "&iZoom=" + iZoom;
                
                // Check if filled for session problems ( F5 reloads JS, but not PHP Session! )
                sGet += '&oMapObjects=' + this.filled();
                
                // Start the Asynchonous request
                GDownloadUrl( this._sDataUrl + sGet, GEvent.callback(this, this.updateObjects));
            } else {                                    // If an update is running
                if ( this._bQueueLastRequest ){         // Check if queuing enabled.
                    this._bUpdateWaiting = true;
                }
            }
        }
        
        
        /**
        *    Check if MapObjects contains any geometry
        **/
        this.filled = function(){
            var i = 0; 
            for ( var x in this.oPoints ) { i += x; break; }  
            if ( i ) {  return true; }
            else {      return false; }
        }
        
        /**
        *    Check for queued commands and execuste appropiate commands if needed
        **/
        this.checkQueue = function(){
            if ( !this.isBusy() ) {                          // If not busy
                if ( this._bMenuUpdateWaiting ) {                   // If MENU update is waiting
                    this._bMenuUpdateWaiting = false;         
                    this.syncCategories();                   // Re-Synchronize category visiblity
                                                                    // Fix for: Click hide category when
                                                                    // updateing. Only the markers who
                                                                    // appear after the click are properly 
                                                                    // Hidden
                    oMenu.countOffItems('GeoCounter_visible'); // Recalculate the total visible markers.
                }
                                                                    // If viewport update is waiting
                if ( this._bQueueLastRequest && this._bUpdateWaiting ) {
                    this._bUpdateWaiting = false;                
                    this.loadObjects();                      // Re-load objects based on current viewport
                }
                
                
                // Handle queues events like clicks ( currrently only used for clicks (4-7-2007) )
                if ( typeof(this._oQueuedCommand) == 'object' && this._oQueuedCommand != null ){
                    this.trigger(this._oQueuedCommand['iId'], this._oQueuedCommand['sEvent']);
                    this._oQueuedCommand = null;
                }
            }
        }
        
        /**
        *    Check if currently busy updating
        **/
        this.isBusy = function(){
            if ( this.bRemoving || this.bAdding ) { return true;    } 
            else                                  { return false;   }
        }
                
        
        /**
        *   Update map objects. Parse data and send 
        *   requests to update functions for objects
        *   and the oMenu.
        *   @param JSON json Json string with data from getData.php
        **/
        this.updateObjects = function (json){
            var oData = eval('(' + json + ')');
            
            
            /** Load category states **/
                this.aCategorystate = this._oMenu.getCatStates();
            
            /**    Geometry data **/
                this.aNewGeometry     = oData.aNewGeometry;
                this.aRemoved        = oData.aRemovedGeometry;
    
            /** Load preloader **/
                var iOperations = oData.aOverview.iRemoved + oData.aOverview.iNew; // Count of actions, (add+remove)
                
                if ( this._bPreloaderEnabled ) {                // Check if preloader should be used.
                    this.oPreloader._iOperations = iOperations; // Initialize max counter
                }
            
            /** Start removal loader **/
                var delself = this;                             // Fix for reference
                this.iRemovedCounter = 0;                       
                this.iRemoved       = oData.aOverview.iRemoved;
                
                // Start the interval to remove per batch.
                this.oDelInterval = setInterval(function() { delself.removeBatch(); }, this._iLoadInterval);    
            
            /** Start preloader and add loader. **/
                var addself = this;                                // Fix for reference
                this.iNewCounter = 0;
                this.iNewGeometry = oData.aOverview.iNew;
                
                // Start the interval to add per batch
                this.oAddInterval = setInterval(function() { addself.displayBatch(); }, this._iLoadInterval);    
            
            
            /** Update menu data. **/
                this._oMenu.update(oData.oMenu);
        }
    
        /**
        *    Batch removal of data. Updates mapobjects internal arrays.
        **/
        this.removeBatch = function(){
            var i = 0;
            for ( var key in this.aRemoved ) {                            // Loop trough global map objects.
                if ( i < this._iLoadStep && this.iRemovedCounter <= this.iRemoved ) { // Loop until max per batch (_iLoadStep) 
                                                                                      // and check if all updates were done.
                    this.trigger(key, 'deleteObject');
                    delete(this.aRemoved[key]);                    // Remove object from remove data update array
                    this.iRemovedCounter++;                                // Global counter to check if remval is done.
                } else { 
                    break;    
                }
                i++;
            }
            if ( this._bPreloaderEnabled ) {                            // Only update preloader if enabled
                this.oPreloader.updateLoader( i );
            }
            
            // Little update check counter thingie...
            oMenu.countOffItems('GeoCounter_visible');

            // Cleanup functions if done.
            if ( this.iRemovedCounter >= this.iRemoved ) {
                clearInterval(this.oDelInterval);                 // Clear the loop.
                this.iRemovedCounter = 0;
                this.aRemoved = null;
                this.iRemoved = null;
                this.bRemoving = false;                            // Tell map objects hes done.
                
                this.checkQueue();                               /// Call checkqueue to handle queued requests
            }
        }
    
    
        /**
        *    Batch display of data. Updates mapobjects internal arrays.
        *    Comments added to lines different from this.removeBatch
        **/
        this.displayBatch = function(){
            var i = 0;
            for ( var key in this.aNewGeometry ) {
                if ( i < this._iLoadStep && this.iNewCounter <= this.iNewGeometry ) {
                    this.createPoint(this.aNewGeometry[key]);
                    delete(this.aNewGeometry[key]);                        // Remove from 'todo' object.
                    this.iNewCounter++;
                } else { 
                    break; 
                }
                i++;
            }
            if ( this._bPreloaderEnabled ) {
                this.oPreloader.updateLoader( i );                // Update preloader if enabled
            }
            
            // Little update check counter thingie...
            oMenu.countOffItems('GeoCounter_visible');

            
            // Cleanup functions if done.
            if ( this.iNewCounter >= this.iNewGeometry ) {
                clearInterval(this.oAddInterval);
                this.aNewGeometry = null;
                this.iNewGeometry = null;
                this.iNewCounter = 0;
                this.bAdding = false;
                if ( this._bPreloaderEnabled ) {
                    this.oPreloader.remove();             // Hide preloader again.
                }
                this.checkQueue();                        // Call checkqueue to handle queued requests
            }
        }
        
        
        /**
        *    Create point (marker) on map. 
        *    Uses information from cGeoData::aVisible, add any fields from there.
        *   @param array aInfo Array with information
        **/
        this.createPoint = function(aInfo){
            switch ( aInfo.g_type ) {
                case "POLYGON":
                    var oPoly = this.createPolygon(aInfo);                        
                    oPoly.titel = aInfo.titel;
                    oPoly.tooltip = aInfo.titel;                        
                    oPoly.type = "polygon";
                    oPoly.content = this.htmlInfoWindow(aInfo);
                    oPoly._GeoInfo  = aInfo;                // All information given by cGeoData(PHP!)
                    this.loadGeometryListeners(oPoly);
                    var id = aInfo.id;
                    this.oPoints[id] = oPoly;               // Add object to factory
                    this.checkVisibility(oPoly);
                    break;
                    
                case "LINESTRING":
                    var oPoly = this.createPolyline(aInfo);
                    oPoly.titel = aInfo.titel;
                    oPoly.tooltip = aInfo.titel;                        
                    oPoly.type = "linestring";
                    oPoly.content = this.htmlInfoWindow(aInfo);
                    oPoly._GeoInfo  = aInfo;                // All information given by cGeoData(PHP!)
                    this.loadGeometryListeners(oPoly);
                    var id = aInfo.id;
                    this.oPoints[id] = oPoly;               // Add object to factory
                    this.checkVisibility(oPoly);
                    break;
                    
                case "POINT":
                    var point = new LabeledMarker( new GLatLng( parseFloat(aInfo.lat), parseFloat(aInfo.lng) ), oIcons[aInfo.sg_id]);    // Create object
                    var id = aInfo.id;
                    this.oPoints[id] = point;               // Add object to factory
                    
                    point.title     = aInfo.titel;          // Define title
                    point.tooltip   = aInfo.titel;
                    point.type        = "point";            // Set geometry type. Not used yet in 1.0
                    point._GeoInfo  = aInfo;                // All information given by cGeoData(PHP!)

                    
                    point.content = this.htmlInfoWindow(aInfo);
                    
                    /** Default event listener for opening info window **/
                    this.loadGeometryListeners(point);
                    
                    /** Add the overlay to the map **/
                    this.oMap.addOverlay(this.oPoints[aInfo.id]);
                    
                    this.checkVisibility(point);
                    
                    break;
            }
        }
        
        
        /**
        *   Check visibility of object and hide if category isn't visible or filtered.
        **/
        this.checkVisibility = function(oRef) {
            // Nieuwe Regel: Bij beide zichtbaar dan laten zien
            if ( !this.checkVisibilityCategory(oRef) && !this.checkVisibilityFilter(oRef) ) {
                if ( oRef.isHidden() ) {
                    oRef.show(); 
                }                
            } else {
                oRef.hide();                
            }            
        }
        
        
        /**
        *   Check category visibility for referenced object.
        **/
        this.checkVisibilityCategory = function(oRef) {
            /** Categgory states for menu **/            
            if ( this.aCategorystate ) {
                /** Check if point is in hidden category **/
                var i = this.aCategorystate.length;
                while ( i-- ) {
                    if ( oRef._GeoInfo[this._sCategoryKey] == this.aCategorystate[i] ) {
                        return true;
                    }
                }
                return false;
            }        
        }
            
            
        /**
        *   Check filter for referenced item
        **/
        this.checkVisibilityFilter = function(oRef){
            if ( !this._aGeoFilterKeys ) {
                this._aGeoFilterKeys = new Array();
            }
            /** Check special filters **/           
            var j = this._aGeoFilterKeys.length;          // Initialize loop through filters
            
            while ( j-- ) { 
                // If filter contains 
                if ( this._aGeoFilterState[this._aGeoFilterKeys[j]] && this._aGeoFilterState[this._aGeoFilterKeys[j]].length > 0 ) { // Check if the filter has been filled
                    var k = this._aGeoFilterState[this._aGeoFilterKeys[j]].length;
                    while ( k-- ) { // Loop throught the filter states
                        if ( oRef._GeoInfo[this._aGeoFilterKeys[j]].indexOf(this._aGeoFilterState[this._aGeoFilterKeys[j]][k]) != -1 ) {
                            return true;
                        }
                    }                    
                }
            }
            return false;
        }
        
        /** 
        *   Add command to command queue. 
        *   @param string sCommand String of the event to be triggered
        *   @param int iSubject Id of the object on who the command should be triggered
        **/        
        this.queueCommand = function(sCommand, iSubject){
            this._oQueuedCommand            = new Object();
            this._oQueuedCommand['sEvent']  = sCommand;
            this._oQueuedCommand['iId']     = iSubject;
        }
    /************************************************
    *                                               *
    *    Filter functions                           *
    *                                               *
    ************************************************/
        /**
        *   Create filter for FIELD - Currently only suppport values to filter on, no ranges.
        *   @param string sKey Fieldname for filter, field should be in oPoints[]._GeoInfo
        **/
        this.createFilter = function(sKey) {
            this._aGeoFilterKeys.push(sKey);
            this._aGeoFilterState[sKey] = new Array();
        }
        
        /**
        *   Remove filter for FIELD
        *   @param string sKey Fieldname for filter
        **/
        this.removeFilter = function(sKey) {
            if ( this._aGeoFilterKeys ) {
                var i = this._aGeoFilterKeys.length;
                while( i-- ) {
                    if ( this._aGeoFilterKeys[i] == sKey ) {
                        delete(this._aGeoFilterKeys[i]);
                        break;
                    }
                }                
                if ( this._aGeoFilterState[sKey] ) {
                    delete(this._aGeoFilterState[sKey]);
                }
            }
        }
        
        /**
        *   Add value to filter 
        *   @param string sKey Keyname of the filter/_GeoInfo field.
        *   @param string sValue Value that should be shows on the map
        **/
        this.addFilterItem = function(sKey, sValue) {
            if ( this._aGeoFilterState[sKey] ) {
                this._aGeoFilterState[sKey].push(sValue);                
            }
        }
        
        /**
        *   Remove value from filter
        *   @param string sKey Keyname of the filter/_GeoInfo field.
        *   @param string sValue Value that should be shows on the map
        **/
        this.removeFilterItem = function(sKey, sValue) {
            if ( this._aGeoFilterState[sKey] ) {
                var i = this._aGeoFilterState[sKey].length;
                while ( i-- ) {
                    if ( this._aGeoFilterState[sKey][i] == sValue ) {
                        delete(this._aGeoFilterState[sKey][i]);
                        break;
                    }
                }
            }
        }
    


    /************************************************
    *                                               *
    *    MapObject functions. Front for all objects *
    *                                               *
    ************************************************/
        /**
        *   Trigger event on object.id. Contains logic for hover etc. Might need to move the logic to the listeners?
        *   @param int iId Id of map object
        *   @param sEvent string Event to be triggered on mapobject
        **/
        this.trigger = function(iId, sEvent){
            switch(sEvent){
                case 'mouseout':
                    if ( this._bEnableHover ) {
                        GEvent.trigger(this.oPoints[iId], sEvent);                        
                    }
                    break;
                case 'mouseover':
                    if ( this._bEnableHover ) {
                        GEvent.trigger(this.oPoints[iId], sEvent);                        
                    }
                    break;
                case 'click':
                    if ( this._bEnableBalloon ) {
                        GEvent.trigger(this.oPoints[iId], sEvent);                        
                    }
                    break;
                case 'deleteObject':
                    if ( this.oPoints[iId] && ( this.oPoints[iId]._GeoInfo['g_type'] == 'POLYGON' || this.oPoints[iId]._GeoInfo['g_type'] == 'LINESTRING' ) ) {
                        delete(this.aPolyBounds[iId]);
                    }
                    this.oMap.removeOverlay(this.oPoints[iId]);   // Remove object from the map.
                    delete(this.oPoints[iId]);                    // Remove object from map objects
                    break;
                default:
                    GEvent.trigger(this.oPoints[iId], sEvent);
                    break;
            }
        }
        
        
        /**
        *   Open a hidden object. Uses json string with object data as argument.
        *   @param string json Hidden marker data.
        **/
        this.gotoHidden = function(json){
            
            var aData =  eval ( '(' + json + ')');            
            
            var iZoom   = aData.zoom;
            var fLat    = aData.lat;
            var fLng    = aData.lng;
            var iId     = aData.id;
            
            /** Set zoom to marker zoom or minimum zoom for map **/
            if ( iZoom < this._iPreferredZoom ) { iZoom = this._iPreferredZoom; }
            
            /** Create placeholder point and goto **/
            var point = new GLatLng(parseFloat(fLat), parseFloat(fLng));
            this.oMap.setCenter(point, parseInt(iZoom));
            
            /** Add a command to the command queue **/
            this.queueCommand('click', iId);
            
            /** Loading marker if onclick. Real info only available after all markers are loaded. **/
            this.oMap.openInfoWindowHtml(point, this.htmlLoadingMarker());            
        }
        
        /**
        *   Open a hidden object, new style uses ajax request to get data 
        *   @param int iId MapObject id
        **/
        this.gotoHiddenId = function(iId){
            if ( !this.isBusy() ) {      // Check if an map update is running
                GDownloadUrl( sBaseUrl + 'GeoStart/ajax/getItemById.php?id='+iId, GEvent.callback( this, this.gotoHidden ) );
            } else {
                setTimeout('oMapObjects.gotoHiddenId('+iId+')', 500);
            }
        }

        
        
        /**
        *   Check zoom level and goto minimum zoom level. Returns true if no change.
        *   @return bool Changed zoom level
        **/
        this.checkZoom = function(){
            if ( this.oMap.getZoom() < this._iPreferredZoom ) {
                this.oMap.setZoom(this._iPreferredZoom);
                return false;
            } else {
                return true;
            }
        }
        
        
        /**
        *    Hide object.id
        *   @param int iId object Id
        **/
        this.hide = function(iId){
            this.oPoints[iId].hide();
        }
        
        /**
        *    Show object.id
        *   @param int iId object Id
        **/
        this.show = function(iId){
            this.oPoints[iId].show();
        }
        
        
        /**
        *   Hide category.id
        *   Uses this._sCategoryKey to identify category in oPoints[id].dbInfo
        *   @param int iId Id reference to MapObjects
        **/
        this.hideCategory = function(iId){
            // If busy remember to update categories after load.
            if ( this.isBusy() ) { this._bMenuUpdateWaiting = true; }

            for ( k in this.oPoints ) {
                if ( this.oPoints[k]._GeoInfo[this._sCategoryKey] == iId ) {
                    this.hide(k);
                }
            }            
        }
        
        /**
        *    Show category.id
        *    Uses this._sCategoryKey to identify category in oPoints[id].dbInfo
        *   @param int iId id of geomtry
        **/
        this.showCategory = function(iId){
            // If busy remember to update categories after load.
            if ( this.isBusy() ) { this._bMenuUpdateWaiting = true; }

            for ( var k in this.oPoints ) {
                if ( this.oPoints[k]._GeoInfo[this._sCategoryKey] == iId ) {
                    this.show(k);
                }
            }            
        }
        
        /**
        *   Show category.id by array 
        *   Uses this._sCategoryKey to identify category in oPoints[id].dbInfo
        *   @param array aInfo Array (NON ASSOCIATIVE) with the categories to be shown.
        **/
        this.showCategoryGroup = function(aInfo){
            // If busy remember to update categories after load.
            if ( this.isBusy() ) { this._bMenuUpdateWaiting = true; }
            
            var i = aInfo.length;
            for ( var k in this.oPoints ) {
                var j = i;
                while ( j-- ) {
                    if ( this.oPoints[k]._GeoInfo[this._sCategoryKey] == aInfo[j] ) {
                        this.show(k);
                    }
                }
            }            
        }

        /**
        *   Hide category.id by array 
        *   Uses this._sCategoryKey to identify category in oPoints[id].dbInfo
        *   @param array aInfo Array (NON ASSOCIATIVE) with the categories to be shown.
        **/
        this.hideCategoryGroup = function(aInfo){
            // If busy remember to update categories after load.
            if ( this.isBusy() ) { this._bMenuUpdateWaiting = true; }

            var i = aInfo.length;
            for ( var k in this.oPoints ) {
                var j = i;
                while ( j-- ) {
                    if ( this.oPoints[k]._GeoInfo[this._sCategoryKey] == aInfo[j] ) {
                        this.hide(k);
                    }
                }
            }            
        }
        
        /**
        *    Sync categorie and points
        *    Used for updateing map if categories got selected while updateing.
        **/
        this.syncCategories = function(){
            this.aCategorystate = this._oMenu.getCatStates();        // Get current state from Menu object
            var i = this.aCategorystate.length;
            
            for ( var k in this.oPoints ) {
                if ( !this.checkVisibility(this.oPoints[k]) ) {
                    this.oPoints[k].show();
                } else {
                    this.oPoints[k].hide();
                }
            }       
        }
        
        
        /**
        *   Show or hide ALL markers.
        **/
        this.setAll = function( bVisiblity ){
            // If busy remember to update categories after load.
            if ( this.isBusy() ) { this._bMenuUpdateWaiting = true; }
            
            if ( !bVisiblity ) { this.oMap.closeInfoWindow(); }
            
            for ( var k in this.oPoints ) {                
                if ( bVisiblity ) {                
                    if ( this.oPoints[k].isHidden() ) {
                        this.show(k);
                    }
                } else {
                    if ( !this.oPoints[k].isHidden() ) {
                        this.hide(k);
                    } 
                }
            }        
        }  
        
        
        /**
        *   Generate counters for visible / invisible items.
        **/      
        this.getVisiblityCounters = function(){
            var iVisible = 0;
            var iTotal = 0;
            for ( k in this.oPoints ) {
                iTotal++;
                if ( !this.oPoints[k].isHidden() ) {
                    iVisible++;
                }
            }
            
            return  {
                        'iTotal':iTotal,
                        'iVisible':iVisible,
                        'iHidden':iTotal - iVisible
                    };                                    
        }
        
        
    /************************************************
    *                                               *
    *    Minimap functions. Generate minimap code,  *
    *    load / init minimap                        *
    *                                               *
    ************************************************/

        /**
        *   Init a mini map. Clean map with one marker.
        *   Will callback this.loadMiniMap to load JSON data of marker.
        *   @param string sMapId Name of the map div
        *   @param int iId Id of the marker shown in the minimap
        **/
        this.initMinimap = function(sMapId, iId){
            this.oMap = new GMap2(document.getElementById(sMapId));
            GDownloadUrl( 'GeoStart/ajax/getItemById.php?id='+iId, GEvent.callback( this, this.loadMiniMap ) );
        }
        
        /**
        *   Callback function to load the minimap based on the init string.
        *   @param json JSON string with single marker data array
        *   @note Highly customized function for use with minimap.tpl
        **/
        this.loadMiniMap = function(JSON){
            aInfo = eval('(' + JSON + ')');
            
            this.oMap.setCenter(new GLatLng(aInfo.lat, aInfo.lng), aInfo.zoom);
            
            this.point = new LabeledMarker( new GLatLng( parseFloat(aInfo.lat), parseFloat(aInfo.lng) ), oIcons[aInfo.sg_id]);    // Create object
            this.point['_GeoInfo'] = aInfo;
            
            GEvent.addListener(this.point, 'click', function(){
                oMapObjects.point.openInfoWindowHtml(oMapObjects.htmlMiniInfoWindow(oMapObjects.point._GeoInfo));
            });
            
            this.oMap.addOverlay(this.point);
            
            document.getElementById('miniTitel').innerHTML = aInfo.titel;
            
            // Delay needed. oMap needs a little while... buggy API :/
            setTimeout('oMapObjects.oMap.setZoom('+aInfo.zoom+');', 100);
            
            
        }
        
        /**
        *   Generate minimap iFrame code for inclusion in antoher webpage. 
        *   @param int iMarkerId 
        *   @param int iH Height in pixels.
        *   @param int iW Width in pixels.
        **/
        this.getMiniMapCode = function(iMarkerId, iH, iW){
            var sCode = '<iframe style="float:none;margin:2px;" src="' + sBaseUrl + '?minimap=1&marker_key=__MARKERID__&w=__WIDTH__&h=__HEIGHT__" width="__WIDTH__" height="__HEIGHT__"></iframe>';
            
            /** Dynamicly replace variables. More of a proof of concept. Tho is makes changing a lot easier **/
            sCode = sCode.replace(/__HEIGHT__/g, iH);
            sCode = sCode.replace(/__WIDTH__/g, iW);
            sCode = sCode.replace(/__MARKERID__/g, iMarkerId);
            
            return sCode;                        
        }
        
        /**
        *   Open embed InfoWindowHtml at place of current InfoWindow
        *   @param string sKey Key of geometry/marker
        *   @param int iId Id of the geometry/marker in the database
        **/
        this.openEmbed = function(sKey, iId){
            var point = this.oMap.getInfoWindow().getPoint();
            this.oMap.closeInfoWindow();
            
            this.oMap.openInfoWindowHtml(point, this.htmlLinkEmbedWindow(sKey, iId));
        }
        
        /**
        *   Update the iFrame code based on inputs in cMapObjects::openEmbed
        **/
        this.updateIframecode = function(){
            $('iframecode').value = this.getMiniMapCode( $('ifid').value, $('ifl').value, $('ifb').value );
        }
        

}






/**
*   Extend on cMapObjects
*   Adds poly functinality
**/
function cMapObjectsPoly(){
    this.bPolyLoaded = true;
    this.aPolyBounds = new Object();

    /**
    *   Create a polyline
    **/   
    this.createPolyline = function(aInfo){
        var aPoints = this.createPoints(aInfo);
        var oPoly = new GPolyline(aPoints, "#" + oIcons[aInfo.sg_id].bgColor, 4, 0.5);
        oPoly._GeoInfo = aInfo;
        this.oMap.addOverlay(oPoly);
        return oPoly;
    }
    
    /**
    *   Create a polyline
    **/   
    this.createPolygon = function(aInfo){
        
        var aPoints = this.createPoints(aInfo);
        var sEdgecolor = "#" + sColor;
        var oPoly = new GPolygon(aPoints, "#"+sColor, 1, 1, '#' + oIcons[aInfo.sg_id].bgColor, 0.2);
        oPoly._GeoInfo = aInfo;
        this.oMap.addOverlay(oPoly);        
        return oPoly;
    }
    
    /**
    *   Convert array of lat's and lng's to real GPoints()
    **/
    this.createPoints = function(aInfo){
        this.aPolyBounds[aInfo.id] = new GLatLngBounds();
        var aPoints = aInfo['aGeometry'];
        var i = aPoints.length;
        var aGPoints = new Array();
        aPoints.reverse;
        while ( i -- ) {
            point = new GLatLng( parseFloat(aPoints[i].lat), parseFloat(aPoints[i].lng) );
            if ( aInfo['g_type'] == 'POLYGON' ) {
                this.addPolyBound(aInfo.id, point);
            }
            aGPoints.push(point);            
        }
        return aGPoints;
    }
    
    /**
    *   @desc Fills collection of bounds for polygones.
    **/    
    this.addPolyBound = function(iId, point){
        this.aPolyBounds[iId].extend(point);
    }
    
    /**
    *   @desc Check if point is in polygon. Trigger click on item.
    *   @param point GPoint
    *   @return boolean Was an item found.
    **/
    this.checkClickedPoly = function(point){
        if ( point ) {
            for ( var i in this.aPolyBounds ){
                if (this.aPolyBounds[i].contains(point)){
                    if (this.oPoints[i].Contains(point)){
                        return i;
                        break;
                    }
                }
            }
        }
        return false;
    }

    
}
