﻿// 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.length; i++) {
                for (var j = 0; j < selectedLayers.length; j++) {
                    if (data[i].LayerId == selectedLayers[j].getLayerId()) {
                        selectedLayers[j].updateMarkers(data[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.length; i++) {
                for (var j = 0; j < selectedLayers.length; j++) {
                    if (data[i].LayerId == selectedLayers[j].getLayerId()) {
                        selectedLayers[j].updatePolylines(data[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));
}