/* DOCUMENTATION

Initialization
Include the following lines in the <head> section:
	<script type="text/javascript" src="http://www.google.com/jsapi?key=GOOGLE_MAPS_API_KEY"></script>
	<script type="text/javascript" src="google_maps.js"></script>
	<link media="all" rel="stylesheet" href="google_maps.css" type="text/css" />

Make sure the html element includes an XML namespace declaration for the prefix 'map':
	<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:map="http://creuna.dk/google_maps">

(Replace the Google Maps API key with a key matching the server's domain.)

Maps
To add a Google map, add a div element and add the class "google_map":
	<div id="first_map" class="google_map"></div>

Specifying starting position
You must speficy the size of the map using css (either inline as style="width: 200px; height: 200px" or be specifying one or more additional styles) as the default size is 0,0 (i.e. invisible).
To specify the center of the map (default is the Eiffel Tower in Paris) use either coordinates or an address as attributes on the div. (Note that all map-specific attributes are prefixed with "map:").

Latitude and longitude coordinates
	mat:lat="[latitude (float)]" map:lng="[longitude (float)]"
	<div id="first_map" class="google_map" style="width: 600px; height: 400px" map:lat="55.6882" map:lng="12.5905"></div>

Address
	map:address="[street, city, country]"
	<div id="first_map" class="google_map" style="width: 600px; height: 400px" map:address="hammerensgade 4, københavn, dk"></div>
 
Zoom level can be specified with the map:zoom attribute (default is 15)
	map:zoom="[zoom level (integer)]"
	<div id="first_map" class="google_map" style="width: 600px; height: 400px" map:lat="55.6882" map:lng="12.5905" map:zoom="17"></div>

Controls can be added to the map by specifying a comma- (or space-) separated string in the map:controls attribute with the names (from the Google Maps API) of the controls.
The possible controls are:
- GLargeMapControl - a large pan/zoom control used on Google Maps. Appears in the top left corner of the map by default.
- GSmallMapControl - a smaller pan/zoom control used on Google Maps. Appears in the top left corner of the map by default.
- GSmallZoomControl - a small zoom control (no panning controls) used in the small map blowup windows used to display driving directions steps on Google Maps.
- GScaleControl - a map scale
- GMapTypeControl - buttons that let the user toggle between map types (such as Map and Satellite)
- GOverviewMapControl- a collapsible overview map in the corner of the screen
	map:controls="GLargeMapControl|GSmallMapControl|GSmallZoomControl|GScaleControl|GMapTypeControl|GOverviewMapControl"
	<div id="other_map" class="google_map medium_map" map:type="hybrid" map:address="times square, ny" map:zoom="17" map:controls="GMapTypeControl, GLargeMapControl, GScaleControl"></div>


<div class="google_map"
	map:address=""
	map:lat=""
	map:lng=""
	map:zoom=""
	map:controls="">
	
	<div class="google_marker"
		map:parent=""
		map:lat=""
		map:lng=""
		map:googleIcon=""
		map:icon="image.png"
		map:iconSize="10,30"
		map:shadow="image.png"
		map:shadowSize="28,31"
		map:iconAnchor="5,28"
		map:infoWindowAnchor="3,0" >
	
			info window html
		
			<div map:tabTitle="">
				info window tab html
			</div>

	</div>

</div>

markers JSON file format:
		[
		    {"lat":"55.687897356076505", "lng":"12.589666843414307", "icon":"google_maps_icons/blue_marker.png", zIndex: 1},
		    {"lat":"55.688272321016775", "lng":"12.589570283889770", "icon":"google_maps_icons/green_markerX.png", "infoWindowTabs":
		    [
		       {"title":"Adresse", "html":"Who knows?..."},
		       {"title":"Aktiviteter", "html":"Who cares?..."}
		    ]},
		    {"lat":"55.688683568752310", "lng":"12.589634656906128", "icon":"google_maps_icons/green_markerY.png", "infoWindowHtml":"Her <b>er</b> faktisk noget."},
		    {"lat":"55.687383285395040", "lng":"12.590214014053345", "icon":"google_maps_icons/blue_marker.png"},
		    {"lat":"55.687915499624150", "lng":"12.590578794479370", "icon":"google_maps_icons/blue_marker.png"}.
			{lat:55.6725751, lng:12.5641536, title:'København Hovedbanegård', infoWindowHtml:'København Hovedbanegård', customIcon:
				{
					iconUrl: 'helmet_icon.png',
					iconSize: '28,22',
					iconShadowUrl: 'helmet_shadow.png',
					iconShadowSize: '40,22',
					iconAnchor: '10,16',
					iconInfoAnchor: '20,3'
				}
			}
		]

*/

// Construct a dictionary for loaded markers.
var loadedMarkers = new Object;


// global vars
// load api
google.load("maps", "2.x");


	var Creuna = {};
	Creuna.GoogleMaps =
{
    // "global" vars
    maps: {},
    geocoder: { loaded: false },

    // Call this function when the page has been loaded
    initialize: function(baseCountryCode) {
        // initialize CircleOverlay
        initCircleOverlay();

        // find all <div class="google_map"> elements
        // we need to copy all existing divs into a new array since document.getElementsByTagName() will change as new dics are added to the document (tiles and markers)
        var origDivs = document.getElementsByTagName('DIV');
        var divs = [];
        for (var i = 0; i < origDivs.length; i++)
            divs.push(origDivs[i]);

        for (i = 0; i < divs.length; i++) {
            if (this.arrayContains(divs[i].className.split(' '), 'google_map')) {
                var map_div = divs[i];
                var map_id = map_div.id;
                this.maps[map_id] = new google.maps.Map2(map_div);
                this.maps[map_id]["addDraggableMarker"] = this._addDraggableMarker;
                this.maps[map_id]["addStaticMarker"] = this._addStaticMarker;
                this.maps[map_id]["loadMarkersFromJson"] = this._loadMarkersFromJson;
                this.maps[map_id]["clearMarkersFromJson"] = this._clearMarkersFromJson;
                this.maps[map_id]["goToAddress"] = this._goToAddress;

                // extract properties
                // map:zoom -> zoom level
                var zoom = map_div.getAttribute('map:zoom');
                if (zoom) {
                    zoomLevel = Number(zoom);
                }
                else
                    zoomLevel = 15;

                // c:lat and c:lng -> center coordinates
                var lat = map_div.getAttribute('map:lat');
                var lng = map_div.getAttribute('map:lng');
                if (lat && lng)
                    center = new google.maps.LatLng(lat, lng);
                else {
                    // there is no coordinates specified. see if an address has been specified
                    var address = map_div.getAttribute('map:address');
                    if (address) {
                        // yes, we have an address. let's try and geocode it
                        if (Creuna.GoogleMaps.geocoder.loaded == false) {
                            Creuna.GoogleMaps.geocoder = new GClientGeocoder();
                            if (baseCountryCode != null) {
                                Creuna.GoogleMaps.geocoder.setBaseCountryCode(baseCountryCode);
                            }
                        }

                        Creuna.GoogleMaps.geocoder.getLatLng(address, this.createGeocoderReturnFunction(map_id, zoomLevel));
                    }
                    center = new google.maps.LatLng(48.85850346934554, 2.2945117950439453);
                }

                this.maps[map_id].setCenter(center, zoomLevel);

                // map:view = "map" | "satellite" | "hybrid" -> default view
                var view = map_div.getAttribute('map:type');
                switch (view) {
                    case 'satellite':
                        type = G_SATELLITE_MAP;
                        break;

                    case 'hybrid':
                        type = G_HYBRID_MAP;
                        break;

                    case 'map':
                    default:
                        type = G_NORMAL_MAP;
                        break;
                }
                this.maps[map_id].setMapType(type);

                // map:disable -> disable default behaviours
                var disables = map_div.getAttribute('map:disable');
                if (disables) {
                    var disablesListArr = disables.split(/[,\s]/);
                    for (var d in disablesListArr) {
                        if (typeof (disablesListArr[d]) == "string")
                            switch (disablesListArr[d].toLowerCase()) {
                            case 'zoom':
                                this.maps[map_id].disableDoubleClickZoom();
                                break;

                            case 'pan':
                                this.maps[map_id].disableDragging();
                                break;

                            case 'infowindows':
                            case 'infowindow':
                            case 'info':
                                this.maps[map_id].disableInfoWindow();
                                break;
                        } // end-switch
                    } 		// end-for
                } 			// end-if( disables )

                // map:controls -> add map controls
                var controls = map_div.getAttribute('map:controls');
                if (controls) {
                    var controlsListArr = controls.split(/[,\s]/);
                    for (r in controlsListArr) {
                        if (typeof (controlsListArr[r]) == "string")
                            switch (controlsListArr[r].toLowerCase()) {
                            case 'glargemapcontrol':
                                this.maps[map_id].addControl(new GLargeMapControl());
                                break;

                            case 'glargemapcontrol3d':
                                this.maps[map_id].addControl(new GLargeMapControl3D());
                                break;

                            case 'gsmallmapcontrol':
                                this.maps[map_id].addControl(new GSmallMapControl());
                                break;

                            case 'gsmallzoomcontrol':
                                this.maps[map_id].addControl(new GSmallZoomControl());
                                break;

                            case 'gscalecontrol':
                                this.maps[map_id].addControl(new GScaleControl());
                                break;

                            case 'gmaptypecontrol':
                                this.maps[map_id].addControl(new GMapTypeControl());
                                break;

                            case 'goverviewmapcontrol':
                                this.maps[map_id].addControl(new GOverviewMapControl());
                                break;

                            case 'googlebar':
                                this.maps[map_id].enableGoogleBar();
                                break;
                        } // end-switch
                    } 		// end-for
                } 		// end-if (controls)

            } 	// end-if (className contains google_maps)

            // <div class="google_marker"> -> markers
            else if (divs[i].className == 'google_marker') {
                // div has correct class. does it have a map:parent attr?
                var marker_div = divs[i];
                var parent_map_id = marker_div.getAttribute('map:parent');
                if (parent_map_id) {
                    // yes: map:parent is present. does it have map:lat and map:lng?
                    marker_lat = marker_div.getAttribute('map:lat');
                    marker_lng = marker_div.getAttribute('map:lng');

                    // get address for geocoding if it exists
                    marker_address = marker_div.getAttribute('map:address');

                    if ((marker_lat && marker_lng) || marker_address) {
                        // yes: coordinates are present. do we have custom map:icon specification?
                        if (marker_div.getAttribute('map:icon')) {
                            // yes: extract all custom marker attributes
                            iconUrl = marker_div.getAttribute('map:icon');
                            iconSize = marker_div.getAttribute('map:iconSize');
                            iconShadowUrl = marker_div.getAttribute('map:shadow');
                            iconShadowSize = marker_div.getAttribute('map:shadowSize');
                            iconAnchor = marker_div.getAttribute('map:iconAnchor');
                            iconInfoAnchor = marker_div.getAttribute('map:infoWindowAnchor');

                            // create custom icon and set attrs
                            customIcon = new GIcon();
                            customIcon.image = iconUrl;
                            customIcon.shadow = iconShadowUrl;
                            customIcon.iconSize = new GSize(Number(iconSize.split(',')[0]), Number(iconSize.split(',')[1]));
                            customIcon.shadowSize = new GSize(Number(iconShadowSize.split(',')[0]), Number(iconShadowSize.split(',')[1]));
                            customIcon.iconAnchor = new GPoint(Number(iconAnchor.split(',')[0]), Number(iconAnchor.split(',')[1]));
                            customIcon.infoWindowAnchor = new GPoint(Number(iconInfoAnchor.split(',')[0]), Number(iconInfoAnchor.split(',')[1]));

                            // Set up our GMarkerOptions object literal
                            markerOptions = { icon: customIcon };
                        } // end-if (has map:icon attr)
                        else if (marker_div.getAttribute('map:googleIcon')) {
                            // no, but we have google style icon
                            customIcon = new GIcon(G_DEFAULT_ICON);
                            customIcon.image = marker_div.getAttribute('map:googleIcon');
                            markerOptions = { icon: customIcon };
                        }
                        else
                        // no custom icon
                            markerOptions = null;


						

                        // does the marker div contain html for info window?
                        if (marker_div.innerHTML != '') {
                            // iterate innerHTML and add key/value from <div map:tabTitle="key">value</div>
                            var tabs = {};
                            var hasTabs = false;
                            for (r in marker_div.childNodes) {
                                if (marker_div.childNodes[r].tagName == 'DIV') {
                                    // found <div>: use tabs in info window
                                    hasTabs = true;
                                    tabs[marker_div.childNodes[r].getAttribute('map:tabTitle')] = marker_div.childNodes[r].innerHTML;
                                }
                            }

                            // add tabs or simple info window
                            var markerOverlay;
                            if (hasTabs) {
                                if (marker_address)
                                    this.createMarkerTabsAddress(parent_map_id, marker_address, markerOptions, tabs);
                                else {
                                    markerOverlay = this.createMarkerTabs(marker_lat, marker_lng, markerOptions, tabs);
                                    this.maps[parent_map_id].addOverlay(markerOverlay);
                                }
                            }
                            else {
                                if (marker_address)
                                    this.createMarkerSimpleAddress(parent_map_id, marker_address, markerOptions, marker_div.innerHTML);
                                else {
									
                                    markerOverlay = this.createMarkerSimple(marker_lat, marker_lng, markerOptions, marker_div.innerHTML);
                                    this.maps[parent_map_id].addOverlay(markerOverlay);
                                }
                            }

                            if (marker_div.id) {
                                if (!this.maps[parent_map_id].markers)
                                    this.maps[parent_map_id].markers = {};

                                this.maps[parent_map_id].markers[marker_div.id] = markerOverlay;
                            } // end-if (marker_div.id)
                        } 	// end-if (marker_div has innerHTML)
                        else {
                            if (marker_address)
                            // simple marker from address
                                this.createMarkerSimpleAddress(parent_map_id, marker_address, markerOptions, null);
                            else
                            // simple marker with coords
                                this.maps[parent_map_id].addOverlay(this.createMarkerSimple(marker_lat, marker_lng, markerOptions, null));
                        }
                    } // end-if (has lat and lng)
                } 	// end-if (har map:parent)
            } 		// end-if (class is google_marker)
        } 			// end-for (all divs)
    }, 				// end-function (initialize)

    // closure function for adding a marker from geocoding
    createMarkerSimpleAddress: function(map_name, address, markerOptions, infoWindowHtml) {
        if (Creuna.GoogleMaps.geocoder.loaded == false)
            Creuna.GoogleMaps.geocoder = new GClientGeocoder();

        Creuna.GoogleMaps.geocoder.getLatLng(address, function(point) {
            if (point) {
                Creuna.GoogleMaps.maps[map_name].addOverlay(Creuna.GoogleMaps.createMarkerSimple(point.lat(), point.lng(), markerOptions, infoWindowHtml));
            }
        });
    },

    // closure funtion for adding a marker from geocoding with tabs
    createMarkerTabsAddress: function(map_name, address, markerOptions, tabs) {
        if (Creuna.GoogleMaps.geocoder.loaded == false)
            Creuna.GoogleMaps.geocoder = new GClientGeocoder();

        Creuna.GoogleMaps.geocoder.getLatLng(address, function(point) {
            if (point) {
                Creuna.GoogleMaps.maps[map_name].addOverlay(Creuna.GoogleMaps.createMarkerTabs(point.lat(), point.lng(), markerOptions, tabs));
            }
        });
    },

    // closure function for adding an onClick handler to a marker with a simple html info window
    createMarkerSimple: function(lat, lng, markerOptions, infoWindowHtml) {
        // initialize marker with position and options if specified
        //console.log(markerOptions)
        var marker;
        if (markerOptions != null) {
            // if a custom z-index has been specified, we need to add it to the marker object as an attribute and we must specify a z-index processing function
            if (markerOptions.zIndex)
                markerOptions.zIndexProcess = Creuna.GoogleMaps.zIndexProcessCustom;

            marker = new GMarker(new GLatLng(lat, lng), markerOptions);

            // add the custom z-index property (this will be evaluated when the zIndexProcessCustom() function is run on addOverlay)
            if (markerOptions.zIndex)
                marker.customZIndex = markerOptions.zIndex;
        }
        else
            marker = new GMarker(new GLatLng(lat, lng));

        // add event listener and return marker
        if (infoWindowHtml) {
			
            var tabArray = [];
            tabArray.push(new GInfoWindowTab('Info', infoWindowHtml));
            tabArray.push(new GInfoWindowTab('Directions', '<strong id="from_header">Get directions to here from:</strong><br /><form id="from_address" class="menu_form" onsubmit="return false"><dl class="pane"><dt>Address</dt><dd><input type="text" name="from_street" id="from_street" class="textfield" /></dd><dt>City</dt><dd><input type="text" name="from_city" id="from_city" class="textfield" value="københavn"/></dd><dt></dt><dd><input type="submit" onclick="getDirectionsFromCoords( ' + lat + ',' + lng + ' )" value="Get directions" class="formbutton" /></dd></dl></form>'));
            GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml(infoWindowHtml) }); 	// comment out this line to include "Directions" tab 
            //		GEvent.addListener( marker, "click", function() { marker.openInfoWindowTabsHtml( tabArray ) } );		// comment in this line to include "Directions" tab
            //		GEvent.addListener( marker, "mouseover", function() { marker.openInfoWindowHtml( infoWindowHtml ) } );	// comment out this line to disable window popup on mouseover
        }
        return marker;
    },

    zIndexProcessCustom: function(marker, b) {
        if (marker.customZIndex)
            return marker.customZIndex;
        else
            return 1;
    },

    // closure function for adding an onClick handler to a marker with a html tabs info window
    createMarkerTabs: function(lat, lng, markerOptions, tabs) {
        // initialize marker with position and options if specified
        var marker;
        if (markerOptions != null) {
            // if a custom z-index has been specified, we need to add it to the marker object as an attribute and we must specify a z-index processing function
            if (markerOptions.zIndex)
                markerOptions.zIndexProcess = Creuna.GoogleMaps.zIndexProcessCustom;

            marker = new GMarker(new GLatLng(lat, lng), markerOptions);

            // add the custom z-index property (this will be evaluated when the zIndexProcessCustom() function is run on addOverlay)
            if (markerOptions.zIndex)
                marker.customZIndex = markerOptions.zIndex;
        }
        else
            marker = new GMarker(new GLatLng(lat, lng));

        // iterate all objects and add as tabs
        var tabArray = [];
        for (tabTitle in tabs) {
            var tab = new GInfoWindowTab(tabTitle, tabs[tabTitle]);
            tabArray.push(tab);
        }

        // add event listener and return marker
        GEvent.addListener(marker, "click", function() { marker.openInfoWindowTabsHtml(tabArray) });
        GEvent.addListener(marker, "mouseover", function() { marker.openInfoWindowTabsHtml(tabArray) }); 	// comment out this line to disable window popup in mouseover
        return marker;
    },

    // closure function to create a geocoding callback function
    createGeocoderReturnFunction: function(map_id, zoomLevel) {
        func = function(point) {
            if (!point) {
                alert(address + " not found");
            }
            else {
                Creuna.GoogleMaps.maps[map_id].setCenter(point, zoomLevel);
            }
        }

        return func;
    },

    // set onLoad handler
    // google.setOnLoadCallback( initialize );

    // an instance of this function is attached to all maps and is invoked with
    // Creuna.GoogleMaps.maps.map_name.panToAddress( 'address', true )
    // 
    // address		complete address to geocode
    // pan			(Boolean) if true, use panTo, otherwise use setCenter
    _goToAddress: function(address, pan) {
        // create geocoder instance if not already done
        if (Creuna.GoogleMaps.geocoder.loaded == false) {
            Creuna.GoogleMaps.geocoder = new GClientGeocoder();
        }

        var targetMap = this;

        // geocode and pan
        Creuna.GoogleMaps.geocoder.getLatLng(address, function(point) {
            if (!point) {
                alert(address + " not found");
            }
            else {
                if (pan)
                    targetMap.panTo(point);
                else
                    targetMap.setCenter(point);
            }
        });
    },

    // an instance of this function is attached to all maps
    // it can be activated with
    //	maps.map_name.addDraggableMarker( input_field_id )
    //
    // the ID of a an input field can be passed as a parameter to the function. The value property of this element will be updated with the marker's position on every dragEnd event.
    _addDraggableMarker: function(coordDivId, customIconUrl) {
        var marker;

        if (customIconUrl && customIconUrl != '') {
            customIcon = new GIcon(G_DEFAULT_ICON);
            customIcon.image = customIconUrl;
            markerOptions = { icon: customIcon, draggable: true };

            marker = new GMarker(this.getCenter(), markerOptions);

        }
        else
            marker = new GMarker(this.getCenter(), { draggable: true });

        if (coordDivId && coordDivId != '') {
            document.getElementById(coordDivId).value = marker.getLatLng().toString();
            GEvent.addListener(marker, "dragend", function() {
                document.getElementById(coordDivId).value = marker.getLatLng().toString();
            });
        }
        this.addOverlay(marker);

        // return the marker if we need it
        return marker;
    },

    // adds a simple (non-draggable) marker at the specified coordinates
    // a custom (Google style) icon can be used by specifying the url to the icon image file
    // the function is attached to the maps and can be called with
    //		maps.map_name.addStaticMarker( 52.123, 12.23434, 'http://site.com/images/google_Purple.png' )
    _addStaticMarker: function(lat, lng, customIconUrl) {
        if (customIconUrl && customIconUrl != '') {
            customIcon = new GIcon(G_DEFAULT_ICON);
            customIcon.image = customIconUrl;
            markerOptions = { icon: customIcon };

            this.addOverlay(new GMarker(new GLatLng(lat, lng), markerOptions));
        }
        else
            this.addOverlay(new GMarker(new GLatLng(lat, lng)));
    },

    // Creuna.GoogleMaps.addMarker( 'map2', 55.6472528, 12.5500452, {image: 'http://dev.m.dk/Metro/google-maps/arrow.png',shadow: 'http://dev.m.dk/Metro/google-maps/arrowshadow.png', iconSize: new GSize( 23, 23 ), shadowSize: new GSize( 23, 23 ), iconAnchor: new GPoint( 8,23 )} )
    // 
    addMarker: function(mapName, lat, lng, customIconParameters) {
        if (customIconParameters) {
            var customIcon = new GIcon(G_DEFAULT_ICON);
            for (property in customIconParameters) {
                customIcon[property] = customIconParameters[property];
            }
            var markerOptions = { icon: customIcon };
            var marker = new GMarker(new GLatLng(lat, lng), markerOptions);
            this.maps[mapName].addOverlay(marker);

            return marker;
        }
        else
            this.maps[mapName].addOverlay(new GMarker(new GLatLng(lat, lng)));
    },

    // loads and shows markers from a JSON formatted file
    // the function can be called with
    //		maps.name_name.loadMarkersFromJson( '/file_path/file.json' );
    //
    // the format of the JSON file is as follows:
    //		[
    //		    {"lat":"55.687897356076505", "lng":"12.589666843414307", "icon":"google_maps_icons/blue_marker.png"},
    //		    {"lat":"55.688272321016775", "lng":"12.589570283889770", "icon":"google_maps_icons/green_markerX.png", "infoWindowTabs":
    //		    [
    //		       {"title":"Adresse", "html":"Who knows?..."},
    //		       {"title":"Aktiviteter", "html":"Who cares?..."}
    //		    ]},
    //		    {"lat":"55.688683568752310", "lng":"12.589634656906128", "icon":"google_maps_icons/green_markerY.png", "infoWindowHtml":"Her <b>er</b> faktisk noget."},
    //		    {"lat":"55.687383285395040", "lng":"12.590214014053345", "icon":"google_maps_icons/blue_marker.png"},
    //		    {"lat":"55.687915499624150", "lng":"12.590578794479370", "icon":"google_maps_icons/blue_marker.png"}.
    //			{lat:55.6725751, lng:12.5641536, title:'København Hovedbanegård', infoWindowHtml:'København Hovedbanegård', customIcon:
    //				{
    //					iconUrl: 'helmet_icon.png',
    //					iconSize: '28,22',
    //					iconShadowUrl: 'helmet_shadow.png',
    //					iconShadowSize: '40,22',
    //					iconAnchor: '10,16',
    //					iconInfoAnchor: '20,3'
    //				}
    //			}
    //		]
    //
    // i.e. it must contain an array of objects with "lat" and "lng" properties and optional "icon" and either "infoWindowHtml" or "infoWindowTabs" objects.
    // the "infoWindowTabs" object must be an array of objects with "title" and "html" properties
    _loadMarkersFromJson: function(url, updateCenter) {
        var parentMap = this;

        // create XmlHttp request and load
        var request = GXmlHttp.create();
        request.open("GET", url, true);

        // set "complete" event handler
        request.onreadystatechange = function() {
            if (request.readyState == 4) {
                var centerLatMin = 99999, centerLatMax = -99999;
                var centerLngMin = 99999, centerLngMax = -99999;

                var markers = eval(request.responseText);

                for (i = 0; i < markers.length; i++) {
					var marker = markers[i];
                
                    // get coordinates
                    var lat = parseFloat(marker.lat);
                    var lng = parseFloat(marker.lng);

                    // find min/max marker positions
                    if (lat < centerLatMin)
                        centerLatMin = lat;
                    if (lat > centerLatMax)
                        centerLatMax = lat;

                    if (lng < centerLatMin)
                        centerLngMin = lng;
                    if (lng > centerLngMax)
                        centerLngMax = lng;

                    // has a standard icon been specified (icon="http://...")
                    if (marker.icon) {
                        customIcon = new GIcon(G_DEFAULT_ICON);
                        customIcon.image = marker.icon;
                        markerOptions = { icon: customIcon };
                    }

                    // has a custom icon been specified (customIcon:)
                    else if (markers[i].customIcon) {
						
                        customIcon = new GIcon();
                        customIcon.image = marker.customIcon.iconUrl;
                        customIcon.shadow = marker.customIcon.iconShadowUrl;
                        customIcon.iconSize = new GSize(Number(marker.customIcon.iconSize.split(',')[0]), Number(marker.customIcon.iconSize.split(',')[1]));
                        customIcon.shadowSize = new GSize(Number(marker.customIcon.iconShadowSize.split(',')[0]), Number(marker.customIcon.iconShadowSize.split(',')[1]));
                        customIcon.iconAnchor = new GPoint(Number(marker.customIcon.iconAnchor.split(',')[0]), Number(marker.customIcon.iconAnchor.split(',')[1]));
                        customIcon.infoWindowAnchor = new GPoint(Number(marker.customIcon.iconInfoAnchor.split(',')[0]), Number(marker.customIcon.iconInfoAnchor.split(',')[1]));
                        customIcon.url = marker.url;
                        markerOptions = { icon: customIcon };
                    }
                    else
                        markerOptions = {};

                    // z-index specified (zIndex: 12)
                    if (marker.zIndex)
                        markerOptions.zIndex = marker.zIndex;

                    // info window html?
                    if (marker.infoWindowHtml) {
                        var loadedMarker = Creuna.GoogleMaps.createMarkerSimple(lat, lng, markerOptions, marker.infoWindowHtml);
                        parentMap.addOverlay(loadedMarker);

                        if (loadedMarkers[marker.type] == null)
                            loadedMarkers[marker.type] = new Array();

                        loadedMarkers[marker.type].push(loadedMarker);
                    }

                    // info window tabs?
                    else if (marker.infoWindowTabs) {
                        tabs = {};
                        for (r in marker.infoWindowTabs) {
                            tabs[marker.infoWindowTabs[r].title] = marker.infoWindowTabs[r].html;
                        }
                        parentMap.addOverlay(Creuna.GoogleMaps.createMarkerTabs(lat, lng, markerOptions, tabs));
                    }

                    // neither info window html or info window tabs: add simple tab
                    else
                    {
                        parentMap.addOverlay(Creuna.GoogleMaps.createMarkerSimple(lat, lng, markerOptions, null));
					}
                }

                if (updateCenter) {
                    // place map in the center of markers
                    var centerLat = centerLatMin + (centerLatMax - centerLatMin) / 2;
                    var centerLng = centerLngMin + (centerLngMax - centerLngMin) / 2;

                    var bounds = new GLatLngBounds(new google.maps.LatLng(centerLatMin, centerLngMin), new google.maps.LatLng(centerLatMax, centerLngMax));
                    var zoom = parentMap.getBoundsZoomLevel(bounds);
                    parentMap.setCenter(new google.maps.LatLng(centerLat, centerLng), zoom);
                }

            } // end-if (readyState==4)
        } // end-inline-function

        request.send(null);
    },

    _clearMarkersFromJson: function(type) {
        while (loadedMarkers[type].length > 0) {
            this.removeOverlay(loadedMarkers[type].pop());
        }
    },

    // utility function to test whether an array contains the specified element
    // (this is necessary because IE does not support Array.indexOf)
    arrayContains: function(array, element) {
        for (var c = 0; c < array.length; c++)
            if (array[c] == element)
            return true;

        return false;
    },

    Metro:
{
    addMarker: function(mapId, lat, lng) {
        Creuna.GoogleMaps.addMarker(mapId, lat, lng, { image: 'http://dev.m.dk/Metro/google-maps/arrow.png', shadow: 'http://dev.m.dk/Metro/google-maps/arrowshadow.png', iconSize: new GSize(23, 23), shadowSize: new GSize(23, 23), iconAnchor: new GPoint(7, 23) })
    }
}
};

/*	Circle overlay
 *	
 *	example:
 	center = new GLatLng( 55.6469864, 12.5553131 );								// center
	circle = new CircleOverlay( center, 1, '#ff7700', 1, 1, '#ff7700', 0.25);	// center, radius (kilometers), stroke color, stroke width, stroke opacity, fill color, fill opacity
	Creuna.GoogleMaps.maps.map1.addOverlay( circle );
*/
// This file adds a new circle overlay to GMaps2
// it is really a many-pointed polygon, but look smooth enough to be a circle.
var CircleOverlay = function(latLng, radius, strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity) {
	this.latLng = latLng;
	this.radius = radius;
	this.strokeColor = strokeColor;
	this.strokeWidth = strokeWidth;
	this.strokeOpacity = strokeOpacity;
	this.fillColor = fillColor;
	this.fillOpacity = fillOpacity;
}

function initCircleOverlay()
{
	// Implements GOverlay interface
	CircleOverlay.prototype = GOverlay;

	CircleOverlay.prototype.initialize = function(map) {
		this.map = map;
	}

	CircleOverlay.prototype.clear = function() {
		if(this.polygon != null && this.map != null) {
			this.map.removeOverlay(this.polygon);
		}
	}

	// Calculate all the points and draw them
	CircleOverlay.prototype.redraw = function(force) {
		var d2r = Math.PI / 180;
		var circleLatLngs = new Array();
		var circleLat = this.radius/1.609344 * 0.014483;  // Convert statute kilometers into degrees latitude
		var circleLng = circleLat / Math.cos(this.latLng.lat() * d2r);
		var numPoints = 40;

		// 2PI = 360 degrees, +1 so that the end points meet
		for (var i = 0; i < numPoints + 1; i++) { 
			var theta = Math.PI * (i / (numPoints / 2)); 
			var vertexLat = this.latLng.lat() + (circleLat * Math.sin(theta)); 
			var vertexLng = this.latLng.lng() + (circleLng * Math.cos(theta));
			var vertextLatLng = new GLatLng(vertexLat, vertexLng);
			circleLatLngs.push(vertextLatLng); 
		}

		this.clear();
		this.polygon = new GPolygon(circleLatLngs, this.strokeColor, this.strokeWidth, this.strokeOpacity, this.fillColor, this.fillOpacity);
		this.map.addOverlay(this.polygon);
	}

	CircleOverlay.prototype.remove = function() {
		this.clear();
	}

	CircleOverlay.prototype.containsLatLng = function(latLng) {
		// Polygon Point in poly 
		if(this.polygon.containsLatLng) {
			return this.polygon.containsLatLng(latLng);
		}
	}

	CircleOverlay.prototype.setRadius = function(radius) {
		this.radius = radius;
	}

	CircleOverlay.prototype.setLatLng = function(latLng) {
		this.latLng = latLng;
	}
}

/*	creates a circle on the map as a polyline (40-sided non-filled polygon)
 *	
 *	lat				center latitude
 *	lng				center longitude
 *	radius			circle radius in kilometers
 *	strokeColor		(optional) string specifying line color (e.g. '#ff7700')
 *	strokeWidth		(optional) integer specifying stroke width in pixels
 *	strokeOpacity	(optional) number between 0 and 1 specifying stroke opactity
 *
 *	returns: a GPolyline to add using GMap2.addOverlay.
 *	
 *	usage example: 
 *		var circle = circlePolyline( map.getCenter().lat(), map.getCenter().lng(), 0.55, null, 2, 0.5 );
 *		map.addOverlay( circle );
 */
function circlePolyline( lat, lng, radius, strokeColor, strokeWidth, strokeOpacity )
{
	var d2r = 0.017453292519943295;					// NOTE: Math.PI/180 for conversion into radians precalculated for performance
	var circleLatLngs = new Array();
	var circleLat = radius * 0.008999318977173307;	// Was: radius * 0.014483. Changed to conversion of statute kilometers (instead of miles) into degrees latitude
	var circleLng = circleLat / Math.cos( lat * d2r );
	var numPoints = 40;

	// 2PI = 360 degrees, +1 so that the end points meet
	for (var i = 0; i < numPoints + 1; i++) 
	{ 
		var theta = Math.PI * (i / (numPoints / 2)); 
		var vertexLat = lat + (circleLat * Math.sin(theta)); 
		var vertexLng = lng + (circleLng * Math.cos(theta));
		var vertextLatLng = new GLatLng( vertexLat, vertexLng );
		circleLatLngs.push( vertextLatLng ); 
	}

	return new GPolyline( circleLatLngs, strokeColor, strokeWidth, strokeOpacity );
}

/*	creates a circle on the map as a polygon (40-sided filled polygon)
 *	
 *	lat		center latitude
 *	lng		center longitude
 *	radium	circle radius in kilometers
 *	strokeColor		(optional) string specifying line color (e.g. '#ff7700')
 *	strokeWidth		(optional) integer specifying stroke width in pixels
 *	strokeOpacity	(optional) number between 0 and 1 specifying stroke opactity
 *	fillColor		(optional) string specifying fill color (e.g. '#ff7700')
 *	fillOpacity		(optional) number between 0 and 1 specifying fill opacity
 *
 *	returns: a GPolygon to add using GMap2.addOverlay.
 *	
 *	usage example: 
 *		var circle = circlePolyline( map.getCenter().lat(), map.getCenter().lng(), 0.55 );
 *		map.addOverlay( circle );
 */
function circlePolygon( lat, lng, radius, strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity )
{
	var d2r = 0.017453292519943295;					// NOTE: Math.PI/180 for conversion into radians precalculated for performance
	var circleLatLngs = new Array();
	var circleLat = radius * 0.008999318977173307;	// Was: radius * 0.014483. Changed to conversion of statute kilometers (instead of miles) into degrees latitude
	var circleLng = circleLat / Math.cos( lat * d2r );
	var numPoints = 40;

	// 2PI = 360 degrees, +1 so that the end points meet
	for (var i = 0; i < numPoints + 1; i++) 
	{ 
		var theta = Math.PI * (i / (numPoints / 2)); 
		var vertexLat = lat + (circleLat * Math.sin(theta)); 
		var vertexLng = lng + (circleLng * Math.cos(theta));
		var vertextLatLng = new GLatLng( vertexLat, vertexLng );
		circleLatLngs.push( vertextLatLng ); 
	}

	return new GPolygon( circleLatLngs, strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity );
}


