﻿/// <reference path="~/js/prototype.js" />
/*
Script: StaticMapBuilder.js

License:
	MIT-style license.

StaticMapBuilder Copyright:
	copyright (c) 2008 Groundspeak, Inc, <http://www.groundspeak.com>

Requires:
    Prototype 1.6 or higher
*/

/*global StaticMap, Class, window, console, $ */

/*
Class: StaticMapLatLng
	Simple Latitude and Longitude class that is used in other classes

Example:
	(start code)

	(end)
*/
var StaticMap = Class.create();

var StaticMapOptions = Object.extend({
    GoogleStaticMapUrl: 'http://maps.google.com/staticmap?',
    minimumZoomLevel: 1,
    maximumZoomLevel: 19,
    zoomInImage: '../images/zoom_in.png',
    zoomOutImage: '../images/zoom_out.png',
    mapDraggableImage: '../images/map_draggable.png',
    loadingImage: '../images/loading.gif',
    maximumMarkerCount: 15

}, window.StaticMapOptions || {} );

StaticMap.LatLng = Class.create({
        initialize: function (lat, lng) {
            this.lat = lat;
            this.lng = lng;
        },
        lat: function () {
            return this.lat;
        },
        lng: function () {
            return this.lng;
        },
        toString: function () {
            //return a lat/lng in the format of 'xx.xxxxx,xxx.xxxxxx'
            try {
                return parseFloat(this.lat).toFixed(6) + ',' + parseFloat(this.lng).toFixed(6);
            } catch(err) {
                return '90.0000,180.0000';
            }
        }
    });


StaticMap.prototype = {
        initialize: function (options) {
            options = options || {};
            
            //zoom levels are actuall determined by Google but I'm setting max/min levels for now.
            this.maxZoomLevel = 19;
            this.minZoomLevel = 0;
            
            this.markers = [];
            this.polygons = [];
            
            this.center = options.center; // center lat/lng
            
            this.url = options.url;
            this.key = options.key;

            this.size = options.size; // width x height
            this.zoom = options.zoom; // 0 - 19
            this.mapType = options.mapType; // roadmap, mobile
            
            this.imageFormat = options.imageFormat; // gif, jpg, png32
            this.maxMarkerCount = options.maxMarkerCount; // currently the google only allows up to 50 markers.

            // API Key
            if (this.key) {
                this.key = '&key=' + this.key;
            }
                
            // base Url
            if (!this.url) {
                this.url = StaticMapOptions.GoogleStaticMapUrl;
            }
            
            // default image size
            if (!this.size) {
                this.size = {width: 300, height: 300};
            }
            
            // image format type
            if (!this.imageFormat) {
                this.imageFormat = 'gif';
            }
            
            if (!this.maxMarkerCount) {
                this.maxMarkerCount = StaticMapOptions.maximumMarkerCount;
            }
        },
        
        reset: function () {
            this.markers = [];
            this.polygons = [];
        },
        
        /*
        Method Name: addMarker
        
            Add's a marker to the static map
        
            Required:
                latLng: LatLng object
            
            Marker Options:
                size:
                    specifies the size of marker from the set {tiny, mid, small}. 
                    If no size parameter is set, the marker will appear in its default {normal} size.
                    
                color:
                    specifies a color from the set {black, brown, green, purple, yellow, blue, gray, orange, red, white}.
                    
                character:
                    specifies a single lowercase alphabetic character from the set {a-z}. 
                    Note that default and mid sized markers are the only markers capable of 
                    displaying an alpha-character parameter. tiny and small markers are not 
                    capable of displaying an alpha-character.
            
            Example:
                object.addMarker(new StaticMap.LatLng(47, -122), {color: 'blue', size: 'mid', char: 'a'});
        
        */
        addMarker: function (latLng, options) {
            if (this.markers.length >= this.maxMarkerCount) {
                return;
            }
            
            var markerString = latLng.toString() + ',';
            
            options = options || {};
            
            if (options) {
                if (options.size) {
                    markerString += options.size; 
                }
                if (options.color) {
                    markerString += options.color; 
                }
                if (options.character) {
                    markerString += options.character; 
                }
            } else {
                markerString += 'normal';
            }
            
            this.markers.push(markerString);
        },
        
        /*
        Method Name: addPolygon
        
            Add's a polygon to the static map.
        
            Required:
                points: Array of StaticMap.LatLng objects
            
            Polygon Options:
                rgbColor:
                    specifies a standard RGB color scheme using a 24-bit hex color value 
                    of the form 0xffffff as the pathColorValue. The default opacity for rgb paths is 50%.
                
                weight:
                    specifies the thickness of the given path, in pixels.

            Example:
                var myPolygon = [];
                
                myPolygon.push(new StaticMap.LatLng(40.737102,-73.990318));
                myPolygon.push(new StaticMap.LatLng(40.749825,-73.987963));
                myPolygon.push(new StaticMap.LatLng(40.752946,-73.987384));
                myPolygon.push(new StaticMap.LatLng(40.755823,-73.986397));
            
                object.addPolygon(myPolygon, {rbgColor: '0000ff', weight: '3'});
                
        */
        
        addPolygon: function (points, options) {
            var polygonString = '&path=';
            
            if (options) {
                if (options.rgbColor) {
                    polygonString += 'rgb:0x' + options.rgbColor + ',';
                } else {
                    polygonString += 'rgb:0x0000ff,';
                }
                                    
                if (options.weight) {
                    polygonString += 'weight:' + options.weight + '|';
                } else {
                    polygonString += 'weight:2|';
                }
            } else {
                polygonString+='path=rgb:0xff0000,weight:2|';
            }
            
            if (Object.isArray(points)) {
                this.polygons.push(polygonString + points.invoke('toString').join('|'));
            }
        },

        /*
        Method Name: addCircle
        
            Create's a circle centered on the provided Lat/Lng with the give diameter in miles.
        
            Required:
                centerLatLng: StaticMap.LatLng object
                distance: distance in miles .5 would be half a mile.
            
            Circle Options:
                rgbColor:
                    specifies a standard RGB color scheme using a 24-bit hex color value 
                    of the form 0xffffff as the pathColorValue. The default opacity for rgb paths is 50%.
                
                weight:
                    specifies the thickness of the given path, in pixels.

            Example:
                
                object.addCircle(new StaticMap.LatLng(40.737102,-73.990318), 1, {rbgColor: '0000ff', weight: '3'});
                
        */
        addCircle: function (centerLatLng, distance, options) {
            var latlngPair = [];
	        var centLat = centerLatLng.lat; 
	        var centLng = centerLatLng.lng;
	        var d2r = Math.PI/180, r2d = 180/Math.PI;
	        var cLat = (distance/3963)*r2d, cLng = cLat/Math.cos(centLat * d2r);
	        var cX = 0.0, cY = 0.0, theta = 0.0;
	        
	        for (var i=0; i < 16; i++) {
		        theta = Math.PI * (i/7);
		        cY = centLat + (cLat * Math.sin(theta));
		        cX = centLng + (cLng * Math.cos(theta));
		        latlngPair.push(new StaticMap.LatLng(cY.toFixed(6), cX.toFixed(6)));
	        }
	        	    
	        this.addPolygon(latlngPair, options);
	    },

        /*
        Method Name: getImageUrl
            
            Returns the fully qualified URL of the static image from Google
            
            Required:
                Nothing
            
            Example:
                
                $('myMapImage').src = object.getImageUrl();
                
        */
         getImageUrl: function() {
            var imageUrl = this.url;
            imageUrl+= 'size='+this.size.width+'x'+this.size.height;
            
            if (this.imageFormat != 'gif') {
                imageUrl += '&format=' + this.imageFormat;
            }
            
            if (this.center) {
                imageUrl+= '&center=' + this.center.toString();
            }
            
            if (this.zoom) {
                imageUrl+= '&zoom=' + this.zoom;
            }
            
            if (this.mapType) {
                imageUrl += '&maptype=' + this.mapType;
            }
            
            imageUrl += '&markers=' + this.markers.join('|') + this.polygons.join('|');
            
            imageUrl += this.key;
            
            return imageUrl
        },

        /*
        Method Name: setImageSrc
            
            Set's the [src] of the image element that's passed in.  Resizes the image width and height to accomidate the new image size.
            
            Required:
                el: IMG element. 
            
            Example:
                
                object.setImageSrc('imgMap');
                
        */
        setImageSrc: function (el) {
            var mapImage = $(el);
            if (el) {
                mapImage.setStyle({width:this.size.width, height:this.size.height});
                mapImage.src = this.getImageUrl();
            }
        },
        	    
        /*
        Method Name: createInteractiveMap
            
            Returns the fully qualified URL of the static image from Google
            
            Required:
                el: DIV element that becomes the interactive map. Create's + / - links and supports hot linking the image to the 
            
            Options:
                clickUrl:  ** NOT IMPLEMENTED **
                    templated URL that will pass in the Lat and Lng of the map.  
                    This will either be populated on the "center" of the map or the first Marker that is in the marker collection.
                    TODO: figure out better logic
            
            Example:
                
                object.createInteractiveMap('divMap', {clickUrl: '../map/default.aspx?lat=#{lat}&lng=#{lng}'} );
                
        */
	    createInteractiveMap: function(el, options) {
	        // get the main container
	        var divMap = $(el);
	        
	        options = options || {};
	        
            this.clickUrl = options.clickUrl;
            this.me = options.me;
            this.objName = options.objName;
            
            this.allowDraggableMap = true;
            
	        // do we need to create a wrapper Anchor tag?
	        if (this.clickUrl) {
	        
	            this.event_MapClick = {
	                fx: function(event) {
	                        event.stop(); 
	                        this._getClickUrl();
	                    }
                };
                
                this.event_MapClick.bfx = this.event_MapClick.fx.bindAsEventListener(this);
                Event.observe(divMap, 'click', this.event_MapClick.bfx);
	        }
	        
	        var mapImageUrl = this.getImageUrl();
	        
	        var zoomImageWrapper = new Element('div').setStyle({padding: '3px 0px 0px 3px', width: '16px', cssFloat: 'left'});

	        if (!this.zoom) { this.zoom = 15; }

	        zoomImageWrapper.insert(
	            new Element('a', {href:'javascript:void(0);', id:'lnkZoomIn', title: 'Zoom In' }).insert(
	                new Element('img', {src: StaticMapOptions.zoomInImage, border: 0, alt: 'Zoom In'}).setStyle({opacity: 0.75}).addClassName('ie-fix-opacity')
	            ).setStyle( {cssFloat: 'left'} )
            );
	        
	        zoomImageWrapper.insert(
	            new Element('a', {href:'javascript:void(0);', id:'lnkZoomOut', title: 'Zoom Out'}).insert(
	                new Element('img', {src: StaticMapOptions.zoomOutImage, border: 0, alt: 'Zoom Out'}).setStyle({opacity: 0.75}).addClassName('ie-fix-opacity')
                ).setStyle( {cssFloat: 'left'} )
            );  

            if (this.objName) {
	            divMap.insert(
	                new Element('a', {href:'javascript:void(0);', id:'lnkSwitchToDraggableMap', title: 'Switch to Draggable Map'}).insert(
	                    new Element('img', {src: StaticMapOptions.mapDraggableImage, border: 0, alt: 'Switch to Draggable Map'}).setStyle({opacity: 0.75, cssFloat: 'right'}).addClassName('ie-fix-opacity')
                    ).setStyle( {padding: '3px 3px 0px 0px', width: '16px', cssFloat: 'right'} )
                );  
            }
	        
	        //need to insert into the dom before binding click events
	        divMap.insert(zoomImageWrapper);

	        $('lnkZoomIn').observe('click', ( function(event) { event.stop(); this._zoomIn(el, this.zoom + 1);}).bindAsEventListener(this));
	        $('lnkZoomOut').observe('click', ( function(event) { event.stop(); this._zoomOut(el, this.zoom - 1);}).bindAsEventListener(this));
            
            if (this.objName) {
    	        $('lnkSwitchToDraggableMap').observe('click', ( function(event) { event.stop(); this._switchToDraggableMap(el);}).bindAsEventListener(this));
            }
            
            divMap.setStyle({
                backgroundRepeat: 'no-repeat',
                backgroundImage: 'url(' + mapImageUrl + ')',
                border:'1px solid #c0c0c0'
            });
	    },
	    // Private Event
	    _zoomOut: function (el, newZoom) {
            if (newZoom <= StaticMapOptions.minimumZoomLevel) {
                newZoom = StaticMapOptions.minimumZoomLevel;
            }
            this.zoom = newZoom;
            $(el).setStyle({
                backgroundRepeat: 'no-repeat',
                backgroundImage: 'url(' + this.getImageUrl() + ')',
                border:'1px solid #c0c0c0'
            });
            
	    },
	    // Private Event
	    _zoomIn: function (el, newZoom) {
            if (newZoom >= StaticMapOptions.maximumZoomLevel) {
                newZoom = StaticMapOptions.maximumZoomLevel;
            }
            this.zoom = newZoom;
            $(el).setStyle({
                backgroundRepeat: 'no-repeat',
                backgroundImage: 'url(' + this.getImageUrl() + ')',
                border:'1px solid #c0c0c0'
            });
	    },
	    // Private Event
	    _getClickUrl: function () {
            window.open( this.clickUrl );
	    },
	    
	    // Private Event
	    _switchToDraggableMap: function (el) {
	        var divMap = $(el);
	        
	        if (this.clickUrl) {
	            Event.stopObserving(divMap, 'click', this.event_MapClick.bfx);
	        }
	    
            divMap.setStyle({
                backgroundRepeat: 'no-repeat',
                backgroundImage: 'url(' + StaticMapOptions.loadingImage + ')',
                backgroundPosition: 'center center',
                border:'1px solid #c0c0c0'
            });
            
            //var script = new Element("script", {type: "text/javascript", defer:true}).update("function " + this.objName + "_showDraggable() { " + this.objName + "._buildDraggableMap('" + el + "'); }");
            var script = document.createElement("script");
            script.text = "function " + this.objName.replace('[', '').replace(']', '') + "_showDraggable() { " + this.objName + "._buildDraggableMap('" + el + "'); }";
            script.type = "text/javascript";
            script.defer = true;
            
            document.getElementsByTagName("head")[0].appendChild(script);
            
            LoadGoogleMapAPI(this.objName.replace('[', '').replace(']', '') + "_showDraggable");
	    },
	    // Private Callback
	    _buildDraggableMap: function(el) {

            google.load("maps", "2", {"callback" : ( function () {
                var map_element = $(el);
                var map = new google.maps.Map2(map_element);
                
                map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13);
                
                var mapControl = new google.maps.HierarchicalMapTypeControl();
                map.addMapType(G_PHYSICAL_MAP);
                map.addControl(new google.maps.SmallMapControl());
                map.addControl(new google.maps.ScaleControl());

                mapControl.clearRelationships();
                mapControl.addRelationship(G_SATELLITE_MAP, G_HYBRID_MAP, "Labels", false);
                map.addControl(mapControl);

                map.setMapType(G_PHYSICAL_MAP);
                
                var smobj = eval(this.objName);
                var markerInfo = null, latLng = null;
                var bounds = new google.maps.LatLngBounds();
                
                for(var x=0, l=smobj.markers.length; x<l; x++) {
                    markerInfo = smobj.markers[x].split(',');
                    latLng = new google.maps.LatLng(markerInfo[0], markerInfo[1]);
                    bounds.extend(latLng);
                    map.addOverlay(new google.maps.Marker(latLng));
                }
                
                map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds))
                
            }).bindAsEventListener(this) });
	    }
	    
    };
