﻿// register the namespace first
//
function registerNS(ns)
{
    var nsParts = ns.split(".");
    var root = window;

    for(var i=0; i<nsParts.length; i++) {
        if(typeof root[nsParts[i]] == "undefined")
        root[nsParts[i]] = new Object();
        root = root[nsParts[i]];
    }
}

registerNS('o8.Gis');


// this is a proxy used to call the script service methods
//


o8.Gis.fetchDataFromService = function(operationName, jsonData, successFunc) {
// fetch markers within bounds
	$.ajax({
  		type: 'POST',
  		url: geoScriptServiceUrl + '/' + operationName,
  		data: jsonData,
  		contentType: 'application/json; charset=utf-8',
  		dataType: 'json',
  		success: successFunc
	});
}

o8.Gis.fetchDataFromServiceSync = function(operationName, jsonData) {
    var result = null;
	$.ajax({
	    async:false,
  		type: 'POST',
  		url: geoScriptServiceUrl + '/' + operationName,
  		data: jsonData,
  		contentType: 'application/json; charset=utf-8',
  		dataType: 'json',
  		success: function(msg) { result = msg; }
	});
	return result;
}



// this is the google map implementation
//
o8.Gis.Map = function(customLayoutCallback, markerInfoHtmlProvider) {
    this._customLayoutCallback = customLayoutCallback;
    this._markerInfoHtmlProvider = markerInfoHtmlProvider;
    this._map = null;
    this._mapControls = [];
    this._avgPixelDistance = 0.0;
    this._bounds = null;
}

o8.Gis.Map.prototype.initialize = function(containerid, centerPos, zoomLevel) {
	if (!GBrowserIsCompatible()) {
	    alert('Sorry, your browser does not support this map!');
	    return;
	}
	
	var self = this;

    // init google maps
	this._map = new GMap2(document.getElementById(containerid));
	this._map.setCenter(new GLatLng(centerPos.Lat, centerPos.Lon), zoomLevel);
	this._avgPixelDistance = this.getAvgMapPixelDistance();
	
	// wire events
	GEvent.addListener(this._map,'move', GEvent.callback(this, this.move));
	GEvent.addListener(this._map,'moveend', GEvent.callback(this, this.moveEnd));
	GEvent.addListener(this._map,'zoomend', GEvent.callback(this, this.zoomEnd));	
	// wire the click handler
	GEvent.addListener(this._map, 'click', function(overlay, latlng, overlaylatlng) {
	    if(overlay instanceof GMarker) {
	        self.displayMarkerInfoWindow(overlay);
	    }
	    else if(overlay instanceof GPolyline) {
	        self.displayPolylineInfoWindow(overlay);
	    }
	    else {
	        // do nothing
	    }
	});	
	
	// layout map controls
	if(!this._customLayoutCallback) {
	    // add the default map controls

	    //var uiMapOptions = this._map.getDefaultUI();
        //uiMapOptions.zoom.doubleclick = false;
        //this._map.setUI(uiMapOptions);
        this._map.setUIToDefault();
	}
	else {	    
	    this._customLayoutCallback(self);	    
	}
}

// public interface

// restricts the scrollable area to specific bounds
o8.Gis.Map.prototype.setBounds = function(southWest, northEast) {
    // rtemember the set bounds
    this._bounds = new GLatLngBounds(new GLatLng(southWest.Lat, southWest.Lon), new GLatLng(northEast.Lat, northEast.Lon));
    // do an initial bounds check
    this.checkBounds();   
}

// sets the zoom level range to max 1..18. where 18 is the most detailed view.
o8.Gis.Map.prototype.setAllowedZoomLevels = function(minZoom, maxZoom) {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    
    if(minZoom >= maxZoom) {
        alert('minZoom is greater or equals to maxZoom');
        return;
    }
    
    var mt = this._map.getMapTypes();
    // Overwrite the getMinimumResolution() and getMaximumResolution() methods
    for (var i = 0; i < mt.length; i++) {
        mt[i].getMinimumResolution = function() {return minZoom;}
        mt[i].getMaximumResolution = function() {return maxZoom;}
    }
}

o8.Gis.Map.prototype.isLoaded = function() {
    if(!this._map) {
        return false;
    }
    
    return this._map.isLoaded();
}

o8.Gis.Map.prototype.getCenter = function() {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    var centerPos = this._map.getCenter();
    return {'Lat': centerPos.lat(), 'Lon': centerPos.lng() };
}

o8.Gis.Map.prototype.setCenter = function(geoPos, zoomLevel) {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    this._map.setCenter(new GLatLng(geoPos.Lat, geoPos.Lon), zoomLevel);
}

o8.Gis.Map.prototype.getBounds = function() {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    var bounds = this._map.getBounds();
    return {'NorthEast': {'Lat': bounds.getNorthEast().lat(), 'Lon': bounds.getNorthEast().lng()},
            'SouthWest': {'Lat': bounds.getSouthWest().lat(), 'Lon': bounds.getSouthWest().lng()} };
}

o8.Gis.Map.prototype.panTo = function(geoPos) {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    this._map.panTo(new GLatLng(geoPos.Lat, geoPos.Lon));
}

o8.Gis.Map.prototype.getZoom = function() {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    return this._map.getZoom();
}

o8.Gis.Map.prototype.setZoom = function(zoomLevel) {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    this._map.setZoom(zoomLevel);
}

o8.Gis.Map.prototype.zoomIn = function() {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    this._map.zoomIn();
}

o8.Gis.Map.prototype.zoomOut = function() {
    if(!this.isLoaded()) {
        alert('Map not initialized!');
        return;
    }
    this._map.zoomOut();
}

o8.Gis.Map.prototype.addMoveEndListener = function(fn) {
    $(this).bind('gismapmoveend', fn);
}

// private interface

// adds a control to the map at a specific position
o8.Gis.Map.prototype.addControl = function(mapControl, controlPosition) {
    this._mapControls.push(mapControl);
    this._map.addControl(mapControl, controlPosition);
}

// shows the info window of the clicked marker.
o8.Gis.Map.prototype.displayMarkerInfoWindow = function(marker) {
    if(this._markerInfoHtmlProvider) {
        var markerId = marker.extendedGisMarkerId;
        var markerType = marker.extendedGisMarkerType;
	    var markerProperties = marker.extendedGisMarkerProperties;
        var markerHtml = this._markerInfoHtmlProvider(markerId, markerType, markerProperties);
        
        //suppressMapPan is an undocumented option
        marker.openInfoWindowHtml(markerHtml, { suppressMapPan: true });
    }
}

// shows the info window of the clicked polyline.
o8.Gis.Map.prototype.displayPolylineInfoWindow = function(polyline) {
    if(this._polylineInfoHtmlProvider) {
        var polylineId = polyline.extendedGisPolylineId;
	    var polylineProperties = marker.extendedGisPolylineProperties;
        var polylineHtml = this._polylineInfoHtmlProvider(polylineId, polylineProperties);
        
        //suppressMapPan is an undocumented option
        polyline.openInfoWindowHtml(polylineHtml, { suppressMapPan: true });
    }
}


o8.Gis.Map.prototype.checkBounds = function() {
    if(!this.isLoaded() || !this._bounds) {
        return;
    }
    
    // Perform the check and return if OK
    var mapCeter = this._map.getCenter();
    if (this._bounds.contains(mapCeter)) {
        return;
    }
    
    // It`s not OK, so find the nearest allowed point and move there
    var X = mapCeter.lng();
    var Y = mapCeter.lat();

    var AmaxX = this._bounds.getNorthEast().lng();
    var AmaxY = this._bounds.getNorthEast().lat();
    var AminX = this._bounds.getSouthWest().lng();
    var AminY = this._bounds.getSouthWest().lat();

    if (X < AminX) {X = AminX;}
    if (X > AmaxX) {X = AmaxX;}
    if (Y < AminY) {Y = AminY;}
    if (Y > AmaxY) {Y = AmaxY;}
    this._map.setCenter(new GLatLng(Y,X));
}

// move handler
o8.Gis.Map.prototype.move = function() {
    // check if we're (still) in the allowed bounds, if any
    this.checkBounds();
}

// move end handler. the markers out of the visible bounds will be removed.
// markers in the visible area are (re)loaded.
o8.Gis.Map.prototype.moveEnd = function() {
    // remove markers out of bounds on the visible layers
    var selectedLayers = this.getSelectedLayers();    
    var workingAreaBounds = this.getWorkingAreaBounds();
    
    for(var i = 0; i < selectedLayers.length; i++) {
        selectedLayers[i].removeMarkers(workingAreaBounds);
    }
    
	// update markers
	this.updateMarkers();
	// update polylines
	this.updatePolylines();
	
	$(this).trigger('gismapmoveend');
}


o8.Gis.Map.prototype.zoomEnd = function(oldLevel, newLevel) {
	// recalculate the average pixel distance
    this._avgPixelDistance = this.getAvgMapPixelDistance();
}

// determines the average distance in meters of one pixel. this is
// used for the clustering algorithm.
o8.Gis.Map.prototype.getAvgMapPixelDistance = function() {
	var size = this._map.getSize();

	// at low zoom levels, for places far from the equator, the
	// lngdist and latdist may differ significantly
	var p1 = this._map.fromContainerPixelToLatLng(new GPoint(1,size.height / 2)); 
    var p2 = this._map.fromContainerPixelToLatLng(new GPoint(1,(size.height / 2) + 1)); 
	var p3 = this._map.fromContainerPixelToLatLng(new GPoint(2,size.height / 2)); 
    var lngdist = p1.distanceFrom(p2);       
    var latdist = p1.distanceFrom(p3); 

	return (lngdist + latdist) / 2.0;
}

// toggles the sidepanel (open/close)
o8.Gis.Map.prototype.toggleSidePanel = function(xPosition) {
	this._map.removeControl(this.lmc);
	this._map.addControl(this.lmc, new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(xPosition + 30, 7)));
}

// determines the working area of the map. it is intentionally slightliy
// greater (the double size of the currently visible area) than the
// visible area to provide a more fluent interaction. 
o8.Gis.Map.prototype.getWorkingAreaBounds = function() {
    var self = this;
	var bounds = self._map.getBounds();
	var southWest = bounds.getSouthWest();
	var northEast = bounds.getNorthEast();	

	var lngExtend = (northEast.lng() - southWest.lng()) / 2.0;
	var latExtend = (northEast.lat() - southWest.lat()) / 2.0;
	// limit the glatlng values from -180 to 180 and -90 to 90
	var tmpLat = southWest.lat() - latExtend < -90.0 ? -90.0 : southWest.lat() - latExtend;
	var tmpLng = southWest.lng() - lngExtend < -180.0 ? -180.0 : southWest.lng() - lngExtend;
	var workingSouthWest = new GLatLng(tmpLat, tmpLng);
	tmpLat = northEast.lat() + latExtend > 90.0 ? 90.0 : northEast.lat() + latExtend;
	tmpLng = northEast.lng() + lngExtend > 180.0 ? 180.0 : northEast.lng() + lngExtend;
	var workingNorthEast = new GLatLng(tmpLat, tmpLng);
	var workingBounds = new GLatLngBounds(workingSouthWest, workingNorthEast);

	return workingBounds;
}

// this is a helper function to retrieve the selected dynamic
// layers that might require updated data.
o8.Gis.Map.prototype.getSelectedLayers = function() {
    var selectedLayers = [];
	for(var i = 0; i < this._mapControls.length; i++) {
        if(this._mapControls[i].getSelectedLayers) {
            var tmpLayers = this._mapControls[i].getSelectedLayers();
            for(var j = 0; j < tmpLayers.length; j++) {
                selectedLayers.push(tmpLayers[j]);
            }
        }
    }
    return selectedLayers;
}

// updates the markers of the map on all visible layers.
o8.Gis.Map.prototype.updateMarkers = function() {
    // determine the selected layers, if any		
	var selectedLayers = this.getSelectedLayers();
	var layerIds = [];
    for(var i = 0; i < selectedLayers.length; i++) {
        layerIds.push(selectedLayers[i].getLayerId());
    }

    //nothing to do?
    if(layerIds.length == 0) {
	    return;
	}	
	
	// get working area bounds
	var self = this;
	var bounds = this.getWorkingAreaBounds();
	var southWest = bounds.getSouthWest();
	var northEast = bounds.getNorthEast();
	
	var layerIdStr = "['" + layerIds.join("','") + "']";	
	var jsonReq = '{layerIds:' + layerIdStr + ', southWest:{Lat:' + southWest.lat() + ', Lon:' + southWest.lng() + '}, northEast:{Lat:' + northEast.lat() + ', Lon:' + northEast.lng() + '}, clusterRadius:' + (self._avgPixelDistance * 30) + '}';	

	// fetch markers within bounds
	$.ajax({
  		type: "POST",
  		url: geoScriptServiceUrl + '/GetLyrMarkersClustered',
  		data: jsonReq,
  		contentType: 'application/json; charset=utf-8',
  		dataType: 'json',
  		success: function(data) {
  		    var selectedLayers = self.getSelectedLayers();
  		    for(var i = 0; i < data.d.length; i++) {
  		        for(var j = 0; j < selectedLayers.length; j++) {
  		            if(data.d[i].LayerId == selectedLayers[j].getLayerId()) {
  		                selectedLayers[j].updateMarkers(data.d[i]);
                        break;
  		            }
                }
  		    }
  		}
	});
}

// updates the polylines of the map on all visible layers. 
o8.Gis.Map.prototype.updatePolylines = function() {
    // determine the selected layers, if any
	var selectedLayers = this.getSelectedLayers();	
	var layerIds = [];
    for(var i = 0; i < selectedLayers.length; i++) {
        if(!selectedLayers[i].polylinesLoaded()) {
            layerIds.push(selectedLayers[i].getLayerId());
        }
    }
    
    //nothing to do?
    if(layerIds.length == 0) {
	    return;
	}

    var self = this;
	var layerIdStr = "['" + layerIds.join("','") + "']";	
	var jsonReq = '{layerIds:' + layerIdStr + '}';	

	// fetch markers within bounds
	$.ajax({
  		type: "POST",
  		url: geoScriptServiceUrl + '/GetLyrPolylines',
  		data: jsonReq,
  		contentType: 'application/json; charset=utf-8',
  		dataType: 'json',
  		success: function(data) {
  		    var selectedLayers = self.getSelectedLayers();
  		    for(var i = 0; i < data.d.length; i++) {
  		        for(var j = 0; j < selectedLayers.length; j++) {
  		            if(data.d[i].LayerId == selectedLayers[j].getLayerId()) {
  		                selectedLayers[j].updatePolylines(data.d[i]);
                        break;
  		            }
                }
  		    }
  		}
	});
}


// this class implements the layering functionality of google maps.
//
o8.Gis.MapLayer = function(layerId, icons) {
    this._layerId = layerId;
    this._icons = icons;
    this._map = null;
    this._polylinesLoaded = false;	
	this._markers = [];
	this._polylines = [];
}

o8.Gis.MapLayer.prototype = new GOverlay();

o8.Gis.MapLayer.prototype.getLayerId = function() {
    return this._layerId;
}

o8.Gis.MapLayer.prototype.polylinesLoaded = function() {
    return this._polylinesLoaded;
}

// interface implementation of GOverlay. Initializes the layer.
o8.Gis.MapLayer.prototype.initialize = function(map) {
	this._map = map;
	
	// forces the map to refresh (load) the markers
	this._map.setCenter(this._map.getCenter());
}

// interface implementation of GOverlay. Removes the current layer
// with all contents from the map.
o8.Gis.MapLayer.prototype.remove = function() {
    this._polylinesLoaded = false;

	this.removeMarkers();
	this.removePolylines();
}

// interface implementation of GOverlay. Don't get the idea behind.
o8.Gis.MapLayer.prototype.copy = function() {
	return new GOverlay();
}

// interface implementation of GOverlay. This method is called from
// within Google Maps. The argument force is true upon map scaling.
o8.Gis.MapLayer.prototype.redraw = function(force) {
	// force is true, on zoom in/out
	if(force) {
	    // redraw is called earlier than moveend
	    // byremoving the markers here displaying the markers shortly
	    // on a wrong position is prevented	
		this.removeMarkers();	
	}	
}

// adds a new marker to the map layer. The layer now takes care of
// removin it if it's falls out of the visibility range.
o8.Gis.MapLayer.prototype.addMarker = function(marker) {
    // check, if marker not already added
	if(this.containsMarker(marker)) {
			return false;
    }

    // add the new marker
	this._markers.push(marker);
	this._map.addOverlay(marker);
	return true;
}

// removes a marker from the map layer.
o8.Gis.MapLayer.prototype.removeMarker = function(marker) {
	// do event housekeeping (prevent memory leaking)
	GEvent.clearInstanceListeners(marker);

	// remove the marker from the map and from the array
	this._map.removeOverlay(marker);
	this.removeFromArray(this._markers, marker); 
}

// determines, if there is a marker at the given geo position.
o8.Gis.MapLayer.prototype.containsMarker = function(marker) {
	var found = false;
	for(var i = 0; i < this._markers.length; i++) {
		var otherLatLong = this._markers[i].getLatLng();
		if(otherLatLong.equals(marker.getLatLng())) {
			found = true;
			break;
		}
	}

	return found;
}

// removes all markers on the current map layer if no bounds are
// given, all markers will be removed.
o8.Gis.MapLayer.prototype.removeMarkers = function(bounds) {
	for (var i = this._markers.length - 1; i >= 0; i--) {
	    if(!bounds || !bounds.containsLatLng(this._markers[i].getLatLng())) {
		    this.removeMarker(this._markers[i]);
		}
	}
	
	//GLog.write('removeMarkers .. now containing ' + this._markers.length + ' markers');
}

// adds a polyline to the current layer
o8.Gis.MapLayer.prototype.addPolyline = function(polyline) {
    this._polylinesLoaded = true;
    // add the new polylines
	this._polylines.push(polyline);
	this._map.addOverlay(polyline);
	return true;
}

// removes all polylines from the current layer
o8.Gis.MapLayer.prototype.removePolylines = function() {
    for(var i = this._polylines.length - 1; i >= 0; i--) {
        // do event housekeeping (prevent memory leaking)
	    GEvent.clearInstanceListeners(this._polylines[i]);

	    // remove the marker from the map and from the array
	    this._map.removeOverlay(this._polylines[i]);
	    this.removeFromArray(this._polylines, this._polylines[i]); 
    }
}

o8.Gis.MapLayer.prototype.updateMarkers = function(data) {
    if(data == null) {
        return;
    }

    // add markers to map
	if(data.GeoMarkers != null) {
	    for(var i = 0; i < data.GeoMarkers.length; i++) {
	        var geoMarker = data.GeoMarkers[i];
		    var latLong = new GLatLng(geoMarker.GeoPos.Lat, geoMarker.GeoPos.Lon);
		    var m = new GMarker(latLong, { icon: this._icons[geoMarker.Type], title: geoMarker.Title });
		    // add specific properties
		    m.extendedGisMarkerId = geoMarker.MarkerId;
		    m.extendedGisMarkerType = geoMarker.Type;
		    m.extendedGisMarkerProperties = geoMarker.Properties;
            if(this.addMarker(m)) {
                // click handler is registeres on the map level				
		    }
	    }
	}
	
	if(data.GeoMarkerClusters != null) {
	    var self = this;
	    for(var i = 0; i < data.GeoMarkerClusters.length; i++) {
	        var geoMarkerCluster = data.GeoMarkerClusters[i];
		    var latLong = new GLatLng(geoMarkerCluster.GeoPos.Lat, geoMarkerCluster.GeoPos.Lon);
		    var m = new GMarker(latLong, { icon: this._icons[geoMarkerCluster.Type], title: geoMarkerCluster.ClusterSize + ' Optionen' });
	        // add specific properties
		    m.extendedGisMarkerId = geoMarkerCluster.MarkerId;
		    m.extendedGisMarkerType = geoMarkerCluster.Type;
		    m.extendedGisMarkerProperties = geoMarkerCluster.Properties;
	        if(this.addMarker(m)) {
			    // move the clicked cluster to center, zoom in
			    GEvent.addListener(m, 'click', function() { 
           		    self._map.setCenter(this.getLatLng(), self._map.getZoom() + 2);
        	    });
		    }
	    }
	}
	
	//GLog.write('Fetched (' + data.GeoMarkers.length + ') markers...');
	//GLog.write('Fetched (' + data.GeoMarkerClusters.length + ') marker clusters...');
	//GLog.write('now containing ' + this._markers.length + ' markers');
}

o8.Gis.MapLayer.prototype.updatePolylines = function(data) {
    if((data == null) || (data.GeoPolylines == null)) {
        return;
    }
    
    for(var i = 0; i < data.GeoPolylines.length; i++) {
        var polyline = data.GeoPolylines[i];
        var m = GPolyline.fromEncoded({   
            color: '#FF0000',   
            weight: polyline.Weight,   
            opacity: polyline.Opacity,   
            points: polyline.PointsEncoded,   
            levels: polyline.LevelsEncoded,   
            numLevels: polyline.NumLevels,   
            zoomFactor: polyline.ZoomFactor   
        });
        
        // add specific properties
        m.extendedGisPolylineId = polyline.PolylineId;
        m.extendedGisPolylineProperties = polyline.Properties;
        
        this.addPolyline(m);   
    }
}

/**
 * Removes value from array. O(N).
 *
 * @param {Array} array  The array to modify.
 * @param {any} value  The value to remove.
 * @param {Boolean} opt_notype  Flag to disable type checking in equality.
 * @return {Number}  The number of instances of value that were removed.
 */
o8.Gis.MapLayer.prototype.removeFromArray = function(array, value, opt_notype) {
	var shift = 0;
	for(var i = 0; i < array.length; ++i) {
		if (array[i] === value || (opt_notype && array[i] == value)) {
			array.splice(i--, 1);
			shift++;
		}
	}
	return shift;
}



// Layer Control
o8.Gis.LayerControl = function(opts, controlPos) {
    this._opts = opts;
    this._controlPos = controlPos;
    this._layerstate = null;
}

// Our control inherits from GControl
o8.Gis.LayerControl.prototype = new GControl();

o8.Gis.LayerControl.prototype.initialize = function(map) {  
    // creating the needed div's
    var container = document.createElement('div');
    var button = document.createElement('div');
    var panel = document.createElement('div');
    var timer = null;

    container.className = 'gis-layer-ctl-container';
    button.className = 'gis-layer-ctl-more-btn';
    panel.className = 'gis-layer-ctl-pnl';

    button.appendChild(document.createTextNode('Mehr...'));

    // adding functionalities to the button events
    $(button).hover(function() {
            clearTimeout(timer);
            // using jQuery fade-in animation
            $(panel).animate({ opacity: 'show' }, 100);
        },
        function() {
            timer = setTimeout(function() {
                // using jQuery fade-out animation                                             
                $(panel).animate({ opacity: 'hide' }, 150);
            }, 200);
    });

    // adding button to the container div
    container.appendChild(button);

    // adding funcionalities to the panel events
    $(panel).hover(function() {
            clearTimeout(timer);
        },
        function() {
            clearTimeout(timer);
            timer = setTimeout(function() {
                $(panel).animate({ opacity: 'hide' }, 400);
            }, 500);
    });

    // creating checkboxes and adding them to the panel
    this._layerstate = [];
    var self = this;
    for (var i = 0; i < this._opts.length; i++) {
        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.defaultChecked = this._opts[i].checked;
        $(checkbox).attr('layerindex', i);     
    
        $(checkbox).click(function() {    
            var index = $(this).attr('layerindex');
            if(this.checked) {
                map.addOverlay(self._opts[index].obj);
            }
            else {
                map.removeOverlay(self._opts[index].obj);
            }
        });

        panel.appendChild(checkbox);

        var label = document.createElement('label');
        label.appendChild(document.createTextNode(this._opts[i].name));        
        panel.appendChild(label);
        panel.appendChild(document.createElement('br'));

        this._layerstate[i] = this._opts[i];
        this._layerstate[i].cbx = checkbox;

        // adding default layers
        if (checkbox.defaultChecked) {
            map.addOverlay(this._opts[i].obj);
        }
    }
    //Horizontal bar only when more than one check box
    if(this._opts.length > 1)
    {
        // adding horizontal separator line
        var hr = document.createElement('hr');
        hr.style.width = '92%';
        hr.style.height = '1px';
        hr.style.textAlign = 'center';
        hr.style.border = '1px';
        hr.style.color = '#e2e2e2';
        hr.style.backgroundColor = '#e2e2e2';
        panel.appendChild(hr);
    }
    
    // adding 'Hide All' Link
    var linkDiv = document.createElement('div');
    linkDiv.style.textAlign = 'center';
    var hideAllLnk = document.createElement('a');
    hideAllLnk.className = 'gis-layer-ctl-hide-all-lnk';
    hideAllLnk.appendChild(document.createTextNode('alle löschen'));
    $(hideAllLnk).click(function() {
        for(var i = 0; i < self._layerstate.length; i++) {
            if(self._layerstate[i].cbx.checked) {
                $(self._layerstate[i].cbx).attr('checked', '');
                map.removeOverlay(self._opts[i].obj);
            }
        }
    });
    //'Alle löschen' only when more than one check box
    if(this._opts.length > 1)
    {
        linkDiv.appendChild(hideAllLnk);
        panel.appendChild(linkDiv);
    }

    container.appendChild(panel);
    map.getContainer().appendChild(container);
    return container;
}

o8.Gis.LayerControl.prototype.getSelectedLayers = function() {
    var selLayers = [];
    for(var i = 0; i < this._layerstate.length; i++) {
        if(this._layerstate[i].cbx.checked &&
                    (this._layerstate[i].obj instanceof o8.Gis.MapLayer)) {
            selLayers.push(this._layerstate[i].obj);
        }
    }   
    return selLayers;
}
	  
o8.Gis.LayerControl.prototype.getDefaultPosition = function() {
    if(typeof(this._controlPos) != 'undefined') {
	    return this._controlPos;
    }
    else {
        return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(230, 7));
    }
}



// Sidebar Control
o8.Gis.SidebarControl = function(parent, opts, controlPos) {
    this.parentInst = parent;
    this._opts = opts;
    this._controlPos = controlPos;
    this._map = null;
    this._layerstate = null;
}

o8.Gis.SidebarControl.prototype = new GControl();

o8.Gis.SidebarControl.prototype.initialize = function(map) {
    this._map = map;
    
    // creating the needed div's
    var sidebarContainer = document.createElement('div');
    var sidebarPanel = document.createElement('div');
    var sidebarToggleButton = document.createElement('div');

    sidebarContainer.className = 'gis-sidebar-ctl-container';
    sidebarPanel.className = 'gis-sidebar-ctl-pnl';

    // adding 'Fill All' Link
    linkDiv = document.createElement('div');
    linkDiv.style.textAlign = 'center';
    hideAllLnk = document.createElement('a');
    hideAllLnk.className = 'gis-layer-ctl-hide-all-lnk';
    hideAllLnk.appendChild(document.createTextNode('Alle Activitäten einblenden'));
    $(hideAllLnk).click(function() {
        for(var i = 0; i < self._layerstate.length; i++) {
            if(!self._layerstate[i].cbx.checked) {
                $(self._layerstate[i].cbx).attr('checked', 'checked');
                map.addOverlay(self._opts[i].obj);
            }
        }
    });

    linkDiv.appendChild(hideAllLnk);
    sidebarPanel.appendChild(linkDiv);

    // adding 'Hide All' Link
    var linkDiv = document.createElement('div');
    linkDiv.style.textAlign = 'center';
    var hideAllLnk = document.createElement('a');
    hideAllLnk.className = 'gis-layer-ctl-hide-all-lnk';
    hideAllLnk.appendChild(document.createTextNode('Alle Activitäten ausblenden'));
    $(hideAllLnk).click(function() {
        for(var i = 0; i < self._layerstate.length; i++) {
            if(self._layerstate[i].cbx.checked) {
                $(self._layerstate[i].cbx).attr('checked', '');
                map.removeOverlay(self._opts[i].obj);
            }
        }
    });
    
    linkDiv.appendChild(hideAllLnk);
    sidebarPanel.appendChild(linkDiv);
    
    // adding horizontal separator line
    var hr = document.createElement('hr');
    hr.style.width = '92%';
    hr.style.height = '1px';
    hr.style.textAlign = 'center';
    hr.style.border = '1px';
    hr.style.color = '#e2e2e2';
    hr.style.backgroundColor = '#e2e2e2';
    sidebarPanel.appendChild(hr);

    this._layerstate = [];
    var self = this;
    for(var i = 0; i < this._opts.length; i++) {
        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.defaultChecked = this._opts[i].Checked;
        checkbox.className = 'gis-layer-checkbox-'+i;
        $(checkbox).attr('layerindex', i);
        
        checkbox.mapUpdateLayer = function(){
            var index = $(this).attr('layerindex');
            if($(this).is(":checked")){
                //if(!checkbox.mapOverlayIsShown){
                    map.addOverlay(self._opts[index].obj);
                //    checkbox.mapOverlayIsShown = true;
                //}
            }
            else{
                //if(checkbox.mapOverlayIsShown){
                    map.removeOverlay(self._opts[index].obj);
                //    checkbox.mapOverlayIsShown = false;
                //}
            }            
        }    
    
        $(checkbox).click(function() {    
           this.mapUpdateLayer();
        });
        
        sidebarPanel.appendChild(checkbox);
       
        if(this._opts[i].iconUrl) {
            var icon = document.createElement('img');
            icon.src = this._opts[i].iconUrl;
            sidebarPanel.appendChild(icon);
        }    
        var label = document.createElement('label');
        label.appendChild(document.createTextNode(this._opts[i].name));       
        sidebarPanel.appendChild(label);
        sidebarPanel.appendChild(document.createElement('br'));

        this._layerstate[i] = this._opts[i];
        this._layerstate[i].cbx = checkbox;

        // add default layers
        if (checkbox.defaultChecked) {
            map.addOverlay(this._opts[i].obj);
        }
    }

    sidebarToggleButton.className = 'gis-sidebar-ctl-tgl-btn';
    sidebarContainer.appendChild(sidebarToggleButton);
    sidebarContainer.appendChild(sidebarPanel);

    var theParent = this.parentInst;
    $(sidebarToggleButton).click().toggle(function() {   
        $(sidebarPanel).animate({
                width: 'show',
                opacity: 'show'        
            },{
                duration: 200,
                complete: function() { theParent.toggleSidePanel($(sidebarContainer).width()); }
        });        
    }, function() {
        $(sidebarPanel).animate({
                width: 'hide',
                opacity: 'hide'         
            },{
                duration: 200,
                complete: function() { theParent.toggleSidePanel(0); }
        });
        
    });
    
    map.getContainer().appendChild(sidebarContainer);
    return sidebarContainer;
}

o8.Gis.SidebarControl.prototype.getSelectedLayers = function() {
    var selLayers = [];
    for(var i = 0; i < this._layerstate.length; i++) {
        if(this._layerstate[i].cbx.checked &&
                    (this._layerstate[i].obj instanceof o8.Gis.MapLayer)) {
            selLayers.push(this._layerstate[i].obj);
        }
    }   
    return selLayers;
}

o8.Gis.SidebarControl.prototype.getDefaultPosition = function() {
    if(typeof(this._controlPos) != 'undefined') {
	    return this._controlPos;
    }
    else {
	    return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(0, 7));
    }
}



// Coordbox Control
o8.Gis.CoordboxControl = function(controlPos) {
    this.controlPos = controlPos;
    this._map = null;
    this.div = null;
    this.movehandler = null;
}

o8.Gis.CoordboxControl.prototype = new GControl(false, true);

o8.Gis.CoordboxControl.prototype.initialize = function(map) {
    this._map = map;

    this.container = document.createElement('div');
    this.container.className = 'GMapCtlCoordbox';
    this.container.style.width = '180px';
    this.container.style.border = '1px solid black';
    this.container.style.backgroundColor = 'white';
    this.container.style.color = 'black';
    this.container.style.textAlign = 'center';
    this.container.style.font = 'normal 12px Arial,sans-serif';
    this.container.style.padding = '1px 3px';
    
    map.getContainer().appendChild(this.container);
    var self = this;
    this.movehandler = GEvent.addListener(this._map, 'move', function() { self.update(); });

    this.update();
    return this.container;
}

o8.Gis.CoordboxControl.prototype.getDefaultPosition = function() {
    if(typeof(this.controlPos) != 'undefined') {
	return this.controlPos;
    }
    else {
	return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 35));
    }
}

o8.Gis.CoordboxControl.prototype.update = function() {
    var lat = this._map.getCenter().lat();
    var lng = this._map.getCenter().lng();
    // Latitude
    var sign = (lat > 0 ? 'N' : 'S');
    lat = Math.abs(lat);
    var degrees = Math.floor(lat);
    var minutes = Math.floor((lat - degrees) * 60);
    var seconds = Math.floor(((lat - degrees) * 60 - minutes) * 60);
    var deglat = degrees + '°' + minutes + '\' ' + seconds + '" ' + sign;
    // Longitude
    sign = (lng > 0 ? 'E' : 'W');
    lng = Math.abs(lng);
    degrees = Math.floor(lng);
    minutes = Math.floor((lng - degrees) * 60);
    seconds = Math.floor(((lng - degrees) * 60 - minutes) * 60);
    var deglng = degrees + '°' + minutes + '\' ' + seconds + '" ' + sign;
    this.container.innerHTML = deglat + ' ' + deglng;
}

o8.Gis.CoordboxControl.prototype.unload = function() {
    GEvent.removeListener(this.movehandler);
    return;
}



// Crosshair Control
o8.Gis.CrosshairControl = function() {
    this._map = null; 
    this.movehandler = null;
}

o8.Gis.CrosshairControl.prototype = new GControl(false, false);

o8.Gis.CrosshairControl.prototype.unload = function() {
    GEvent.removeListener(this.movehandler);
}

o8.Gis.CrosshairControl.prototype.initialize = function(map) {
    this._map = map;
    
    this.container = document.createElement('div');
    this.container.style.width = '15px';
    this.container.style.height = '15px';
    
    var box1 = document.createElement('div');
    box1.style.position = 'absolute';
    box1.style.width = '7px';
    box1.style.height = '7px';
    box1.style.borderRight = 'solid 1px black';
    box1.style.borderBottom = 'solid 1px black';
    this.container.appendChild(box1);
    
    var box2 = document.createElement('div');
    box2.style.position = 'absolute';
    box2.style.top = '7px';
    box2.style.left = '7px';
    box2.style.width = '7px';
    box2.style.height = '7px';
    box2.style.borderLeft = 'solid 1px black';
    box2.style.borderTop = 'solid 1px black';
    this.container.appendChild(box2);
    
    map.getContainer().appendChild(this.container);   
    return this.container;
}

o8.Gis.CrosshairControl.prototype.getDefaultPosition = function() {
    var cc = this._map.fromLatLngToDivPixel(this._map.getCenter());
    var left = cc.x - 15 / 2;
    var right = cc.y - 15 / 2;
    return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(left, right));
}

