/** 
 * @fileoverview cMapObjects - Generic Front-end control of GoogleMaps objects
 *
 * @author Bjorn Brala bjorn@swis.nl
 * @version 1 + 2.0 FILTER
 */


/**
 * 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));

            this._fLat = fLat;                    // Start viewport
            this._fLng = fLong;                    // |
            this._iZoom = 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);                // 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._bUpdateWaiting = false;                       // Set queue to clean
            
            // Special filter
            this.createFilter('doelgroep');
            this.addFilterItem('doelgroep', '1');
            this.addFilterItem('doelgroep', '2');
            this.addFilterItem('doelgroep', '3');
            this.addFilterItem('doelgroep', '4');
            
                                            
            this.loadObjects();                                 // Start loading of markers in current view
        }
        
        /**
        *    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() { hideTooltip(); } );
        }
        
        /**
        *    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 = this._bPreloaderEnabled    || 'true';   // Enable preloader
            this._bEnableHover      = this._bEnableHover         || 'true';   // Enable hover
            this._bEnableBalloon    = this._bEnableBalloon       || 'true';   // Enable balloon
            this._iPreferredZoom    = this._iPreferredZoom       || 10;       // 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._oMenu = oMenu;                // Menu object
            
            this.oMap.setCenter(new GLatLng(this._fLat, this._fLng), this._iZoom); 
            this.oMap.enableScrollWheelZoom();    // Enable scroll wheel zoom.
            
            /**
            *   Powetred by geostart
            **/            
            this.oMap.addControl(new powered_by_geostart(this.oMap));
            
            this.oMap.setMapType(G_NORMAL_MAP);
            
            /**
            *   Map controls.
            **/
            var eZoomPanControl= new Control_grey_panzoom(this.oMap);
            this.oMap.addControl(eZoomPanControl); 
            
            var eKaartControl = new Control_grey_center(this.oMap, _sKaart);
            this.oMap.addControl(eKaartControl);  
            
            var eSatellietControl = new Control_grey_right(this.oMap, _sSatelliet);
            this.oMap.addControl(eSatellietControl); 
            
            var eHelpControl = new Control_grey_left(this.oMap, _sBeide);
            this.oMap.addControl(eHelpControl); 
            
            this.oMap.enableContinuousZoom();
            
            this._sDataUrl = sBaseUrl + 'GeoStart/ajax/getData.php';    // Relative URL for GeoStart data requests
        }
        
        
        /**
        *   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(sMode, oRef){
            var oRefMapObjects = this;
            if ( sMode == 'marker' ) {                
                /** 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>');
                            }
                        });
                    
                    if ( IEFix == true ){
                        if ( $('infovideo') != null ){
                            setTimeout( "$('infovideo').innerHTML = oMapObjects.oPoints["+ oRef._GeoInfo['id'] +"]._GeoInfo['video']", 500 );
                        }
                    }
                });
                
                /** 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() {
                        hideTooltip();                                          // 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 ( markerOnScreen(oRef.getPoint()) ) {                // Check if marker is onscreen
                        oCoordinates = getPosition(oRef.getPoint());        // Get the pixel-position of the marker
                        showTooltip(oCoordinates, oRef.tooltip);            // Shwo tooltip at point
                        
                        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() {
                    hideTooltip();
                    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 ) {
           switch ( sLang ) {
                case "nl":
                    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 + sLang + '/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 onkeyup="oMapObjects.updateIframecode();" type="hidden" value="' + sKey + '" id="ifid"> Lengte: <input id="ifl" 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 + 'index.php?minimap=1&marker_key=' + sKey + '&w=257&h=310&language='+sLang+'" width="257" height="310"></iframe>' +
                             '</div>' + 
                           '</div>' ;
                           break;
                case "en":
                    var sHtml = '<div style="width:550px;"><div style="float:left;width:260px;">' + 
                                '<p style="color:#666;width:245px;">' + 
                                    'Copy the link below to link to this location:<br><input id="balloonPermLink" type="text" value="' + sBaseUrl + sLang + '/locatie/' + this.oPoints[iId]._GeoInfo['titel_key'] + '" onclick="this.select();"><br>' + 
                                    '<br>Copy the sourcesode below to create a minimap on you webpage.' + 
                                '</p>' + 
                                '<p id="iframeinput">' + 
                                    'Width: <input onkeyup="oMapObjects.updateIframecode();" id="ifb" value="257" type="text"> <input onkeyup="oMapObjects.updateIframecode();" type="hidden" value="' + sKey + '" id="ifid"> Length: <input id="ifl" value="310" type="text">' +
                                '</p>' +
                                'Sourcecode:<br><textarea onclick="this.select();" id="iframecode">' + this.getMiniMapCode(sKey, 310, 257) + '</textarea>' + 
                             '</div>' + 
                             '<div style="float:right;width:280px;">' +
                                'Example:<br><iframe src="' + sBaseUrl + 'index.php?minimap=1&marker_key=' + sKey + '&w=257&h=310&language='+sLang+'" width="257" height="310"></iframe>' +
                             '</div>' + 
                           '</div>' ;
                           break;
                case "de":
                    var sHtml = '<div style="width:550px;"><div style="float:left;width:260px;">' + 
                                '<p style="color:#666;width:245px;">' + 
                                    'Für eine Verlinkung mit dem Standort bitte den unten stehenden Link kopieren:<br><input id="balloonPermLink" type="text" value="' + sBaseUrl + sLang + '/locatie/' + this.oPoints[iId]._GeoInfo['titel_key'] + '" onclick="this.select();"><br>' + 
                                    '<br>Wenn Sie den unten stehenden Quellcode kopieren und in Ihre Webseite einfügen, erscheint eine Mini-Karte des Standortes.' + 
                                '</p>' + 
                                '<p id="iframeinput">' + 
                                    'Breite: <input onkeyup="oMapObjects.updateIframecode();" id="ifb" value="257" type="text"> <input onkeyup="oMapObjects.updateIframecode();" type="hidden" value="' + sKey + '" id="ifid"> Länge: <input id="ifl" value="310" type="text">' +
                                '</p>' +
                                'Quellcode:<br><textarea onclick="this.select();" id="iframecode">' + this.getMiniMapCode(sKey, 310, 257) + '</textarea>' + 
                             '</div>' + 
                             '<div style="float:right;width:280px;">' +
                                'Beispiel:<br><iframe src="' + sBaseUrl + 'index.php?minimap=1&marker_key=' + sKey + '&w=257&h=310&language='+sLang+'" width="257" height="310"></iframe>' +
                             '</div>' + 
                           '</div>' ;
                           break;
           }
           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 extra = '';
                var totalWidth = 515;
                switch ( aInfo.infoType ) {
                    case "video":
                        if ( IEFix == true )
                            var extra = "<div id='infovideo' style='float:right;padding:20px 0 0 20px;border-left:1px solid #666;'><div style='height:211px;width:256px;'></div></div>";
                        else
                            var extra = "<div id='infovideo' style='float:right;padding:20px 0 0 20px;border-left:1px solid #666;'>" + aInfo.video + "</div>";
                        break;
                    case "image":
                        var extra = "<div style='float:right;padding:20px 0 0 20px;border-left:1px solid #666;'><img height='211' width='256' src='" + sBaseUrl + "images/ballon/" + aInfo.afbeelding + "' alt='" + aInfo.titel + "'></div>";
                        var totalWidth = 258 + 256;
                        break;                    
                }
                
                
                var sDirection = '';
                var content = '';
                
                if ( extra != '' ) {
                    content += '<div id="infoWindowBiggie" style="width:' + totalWidth + 'px;">';
                    content += extra;
                }
                
                content += '<div class="infoWindow" style="float:left;">';
                content += "<b>" + aInfo.titel + "</b><br/>";
                
                if ( aInfo.korte_omschrijving !== '' ) {
                    content += aInfo.korte_omschrijving;
                } else {
                    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.lange_omschrijving !== '' ){
                    content += '<a class="right" onclick="showInfo(\'' + aInfo.id + '\');return false;" href="#">' + _sMeerInfo + '</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 + '">' + _sBezoekSite + '</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>';

                
                var sParseTitel = aInfo.titel.replace(/&#39;/g, "\\&#39;");
                
                content += '<div class="ballonButton"><a class="zoomAnchor" href="#" onclick="oMapObjects.oMap.zoomIn();return false;"><img src="'+sBaseUrl+'GeoStart/images/controls/grey/small_plus.gif" alt="" /> ' + _sZoomin + ' </a> <a style="margin-left:10px;" class="zoomAnchor" onclick="oMapObjects.oMap.zoomOut();return false;" href="#"><img src="'+sBaseUrl+'GeoStart/images/controls/grey/small_min.gif" alt="" /> ' + _sZoomuit + '</a> ';
                content += '<span style="display:block;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 + '\', \'' + sParseTitel + '\');return false;" href="#">Route</a></span></div>';
                
                                   
                
                // editinplace icons
                if(bWebbeheerLoggedIn){
                    content += '<hr/>';
                    content += '<img style="cursor:pointer;margin:0;width:20px;height:16px;background:transparent;" src="'+sBaseUrl+'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="'+sBaseUrl+'webbeheer/images/delete.gif" onclick="deleteItem(\'markers\',' + aInfo.id + ');" title="verwijder" alt="verwijder" />';
                }
                content += '</div>';
                
                if ( extra != ''  ) {
                    content += '</div>';
                }

                
                return content;
       }
       
       
       this.htmlSubgroepenRequest = function(iId){
            GDownloadUrl(sBaseUrl + '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">' + _sLoading + '</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="'+sBaseUrl+'GeoStart/images/controls/grey/small_plus.gif" alt="" /> ' + _sZoomin + ' </a> <a style="margin-left:10px;" class="zoomAnchor" onclick="oMapObjects.oMap.zoomOut();return false;" href="#"><img src="'+sBaseUrl+'GeoStart/images/controls/grey/small_min.gif" alt="" /> ' + _sZoomin + ' </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 ( currently only checks for Points ).
        **/
        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('Geo_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.
                    //GEvent.clearInstanceListeners(this.oPoints[key]);    // Clear event listeners ( against leakage )
                    this.oMap.removeOverlay(this.oPoints[key]);            // Remove object from the map.
                    delete(this.oPoints[key]);                    // Remove object from map objects
                    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 );
            }
            
            // 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
            }
            // 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){
            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.type        = "point";            // Set geometry type. Not used yet in 1.0
            point._GeoInfo  = aInfo;                // All information given by cGeoData(PHP!)
            
            /* EXTRA FOR HANZESTAD TRANSLATION 
            point._GeoInfo['titel_key'] = aInfo[sLang + '_titel_key'];
            point._GeoInfo['titel'] = aInfo[sLang + '_titel'];
            point._GeoInfo['lange_omschrijving'] = aInfo[sLang + '_lange_omschrijving'];
            point._GeoInfo['korte_omschrijving'] = aInfo[sLang + '_korte_omschrijving'];
            */
            
            point.title     = point._GeoInfo['titel'];          // Define title
            point.tooltip   = point._GeoInfo['titel'];          // Define title
            
            point.content = this.htmlInfoWindow(point._GeoInfo);
            
            
            /** Default event listener for opening info window **/
            this.loadGeometryListeners('marker', point);
            
            /** Add the overlay to the map **/
            this.oMap.addOverlay(this.oPoints[aInfo.id]);
            
            
                        /** Check if TIP! **/
            if ( aInfo.aanbevolen == 2 ) {
                point.div_.className = 'aanbevolen_marker';
            }

            
            this.checkVisibility(point);                
        }
        
        /** 
        *   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;
        }

        /**
        *   Check visibility of object and hide if category isn't visible.
        **/
        this.checkVisibility = function(oRef) {
            /*
            if ( this.checkVisibilityCategory(oRef) ) {
                oRef.hide();
            } else {
                if ( oRef.isHidden() ) { oRef.show(); }
            }
            
            if ( !this.checkVisibilityFilter(oRef) ) {
                oRef.hide();
            } else {
                if ( oRef.isHidden() ) { 
                    oRef.show(); 
                }                
            }
            */
            // 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 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 item for filter
        **/
        this.checkVisibilityFilter = function(oRef){
            /** 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;
        }
        
    /************************************************
    *                                               *
    *    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 ) {
                        this._aGeoFilterState[sKey].splice(i,1);
                        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 && !this.isBusy() ) {
                        GEvent.trigger(this.oPoints[iId], sEvent);                        
                    }
                    break;
                case 'mouseover':
                    if ( this._bEnableHover && !this.isBusy() ) {
                        GEvent.trigger(this.oPoints[iId], sEvent);                        
                    }
                    break;
                case 'click':
                    if ( this._bEnableBalloon && !this.isBusy() ) {
                        GEvent.trigger(this.oPoints[iId], sEvent);                        
                    }
                default:
                    if ( !this.isBusy() ) 
                        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+')', 100);
            }
        }

        
        
        /**
        *   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 mapobject
        **/
        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 ) {
                this.checkVisibility(this.oPoints[k]);
            }       
        }
        
        
        /**
        *   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);
                    } 
                }
            }        
        }        
        
        
    /************************************************
    *                                               *
    *    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( sBaseUrl+'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 + ')');
            
            var iZoom = aInfo.zoom || this._iPreferredZoom;
            
            this.oMap.setCenter(new GLatLng(aInfo.lat, aInfo.lng), iZoom);
            
            this.oMap.setMapType(G_SATELLITE_MAP);
            
            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('+iZoom+');', 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 + 'index.php?minimap=1&marker_key=__MARKERID__&w=__WIDTH__&h=__HEIGHT__&language=__LANG__" 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);
            sCode = sCode.replace(/__LANG__/g, sLang);
            
            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 );
        }
        

}