


var KM2MILE = 0.621371192;
var MILE2KM = 1 / KM2MILE;
var M2FT = 3.2808399;
var FT2M = 1 / M2FT;

var _calc_distance = function(latlng1, latlng2){
    var R = 6378137;
    var lat = [latlng1.lat(), latlng2.lat()]
    var lng = [latlng1.lng(), latlng2.lng()]
    var dLat = (lat[1] - lat[0]) * Math.PI / 180;
    var dLng = (lng[1] - lng[0]) * Math.PI / 180;
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat[0] * Math.PI / 180) * Math.cos(lat[1] * Math.PI / 180) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return Math.round(d);
};

(function($){
    $.magicmap = function(options){
        defaults = {
            startcenter: new google.maps.LatLng(31.6, 120.34),
            mapTypeId: google.maps.MapTypeId.ROADMAP,
            unit: google.maps.DirectionsUnitSystem.METRIC,
            zoom: 11,

            searchmode: "bounds", // "center" or "bounds"

            controlsOption: {
                enable: false,
                reset_callback: null
            },
            locationOption: {
                enable: false,
                location_callback: null
            },
            teamsOption: {
               enable: false
            },
            ridersOption: {
               enable: false
            },
            routesOption: {
                enable: false
            },
            showrouteOption: {
                enable: false,
                callback: null,
                polylineOptions: {}
            },
            directionOption: {
                enable: false,
                callback: null,
                travelMode: google.maps.DirectionsTravelMode.DRIVING
            },
            elevationsOption: {
                enable: false,
                callback: null,
                samples: 500,
                chartOption: {
                    chart: {
                        renderTo: null,
                        width: 640,
                        height: 300,
                        plotBorderWidth: 1,
                        spacingLeft: 5,
                        spacingTop: 5,
                        spacingBottom: 5,
                        zoomType: 'x',                    
                        defaultSeriesType: 'areaspline'
                    },
                    xAxis: {
                        min: 0,
                        maxZoom: 2.5,
                        minorTickInterval: 'auto',
                        title: {
                             text: 'distance (km)'
                        },
                        labels: {
                            formatter:function(){
                                return this.value; 
                            }
                        }
                    },
                    yAxis: {
                        minorTickInterval: 'auto',
                        minorGridLineDashStyle: 'Dash',
                        title: {
                             text: 'elevation (m)'
                        },
                        labels: {
                            formatter:function(){
                                return this.value; 
                            }
                        }
                    },
                    tooltip: {
                        formatter: function() {
                            show_mark( this.point.location);
                            return Highcharts.numberFormat(this.y, 0) +
                                get_unit_str(1) +' @ '+ 
                                Highcharts.numberFormat(this.x,1) + ' ' +
                                get_unit_str(0);
                        }
                    },
                    credits: {
                        enabled: false
                    },
                    title: {
                        text: 'Elevations',
                        margin: 5
                    },
                    plotOptions: {
                            areaspline: {
                                marker: {
                                    enabled: false,
                                    symbol: 'circle',
                                    fillColor: 'red',
                                    radius: 1,
                                    states: {
                                        hover: {
                                            enabled: true
                                        }
                                    }
                                },
                                fillOpacity: 0.5
                            }
                    },
                    series: [{
                        name: 'elevations',
                        showInLegend: false,
                        data: []
                    }]
                }
            }
        };
        mapoptions = {};
        $.extend(mapoptions, defaults, options);

        ui = {
            map_container: null,
            searchbox: null,
            searchbtn: null,
            waiting: null,
            searchresults: null,
            searchmsg: null,
            directions: null,
            elevations: null
        };
        
        listeners = {
            map_click: null,
            map_right_click: null,
            directions_change: null
        };

        reset_button = function(){
            resetdiv = document.createElement('input');
            resetdiv.type = 'button';
            resetdiv.style.margin = '4px 0 0 0';
            resetdiv.style.height = '23px';
            resetdiv.style.cursor = 'pointer';
            resetdiv.value = 'Reset';
            google.maps.event.addDomListener(resetdiv, 'click',
            function(){
                reset_ui();
                reset_directions();
                reset_elevations();
                if (mapoptions.controlsOption.reset_callback){
                    mapoptions.controlsOption.reset_callback();
                }
            });
            return resetdiv;
        }
        unit_select = function(){
            units_ui = $('<select id="units" style="margin:4px;">'+
                        ''+
                            '<option  selected="selected" '+
                                'value="metric"> metric </option>'+
                        ''+
                            '<option '+
                                'value="imperial"> imperial </option>'+
                        ''+
                    '</select>');
            $(document).append(units_ui);

            google.maps.event.addDomListener(units_ui[0], 'change', function() {
                unit = google.maps.DirectionsUnitSystem.METRIC;
                if ($(this).val() == "imperial"){
                    unit = google.maps.DirectionsUnitSystem.IMPERIAL;
                }
                set_map_unit(unit);
            });
            return units_ui[0];
        }
        mode_select = function(){
            modes = $('<select id="modes" style="margin:4px;">'+
                            '<option value="DRIVING">Driving</option>'+
                            '<option value="BICYCLING">Bicycling</option>'+
                            '<option value="WALKING">Walking</option>'+
                    '</select>');
            $(document).append(modes);
            google.maps.event.addDomListener(modes[0], 'change', function() {
                if ($(this).val() == "DRIVING"){
                    mapoptions.directionOption.travelMode = google.maps.DirectionsTravelMode.DRIVING;
                    start_get_directions();
                } else if ($(this).val() == "BICYCLING"){
                    if(confirm(" Bicycling only availabled in US, r u sure?")){
                        mapoptions.directionOption.travelMode = google.maps.DirectionsTravelMode.BICYCLING;
                        start_get_directions();
                    }
                    else{
                        $(this).val("DRIVING");
                        mapoptions.directionOption.travelMode = google.maps.DirectionsTravelMode.DRIVING;
                    }
                } else if ($(this).val() == "WALKING"){
                    mapoptions.directionOption.travelMode = google.maps.DirectionsTravelMode.WALKING;
                    start_get_directions();
                }
            });
            return modes[0];
        }

        set_map_unit = function(unit){
            if (unit in this.units) {
                if (mapoptions.unit != unit){
                    mapoptions.unit = unit;
                    start_get_directions();
                }
            }
        }

        //private functions
        get_unit_str = function(i){
            if (mapoptions.unit == google.maps.DirectionsUnitSystem.IMPERIAL) {
                return ["mi","ft"][i];
            }
            return ["km","m"][i];
        }
        reset_ui = function(){
            ui.searchresults.html('');
            ui.searchresults.hide();
            ui.searchmsg.html('');
            ui.waiting.hide();
        }

        reset_directions = function(){
            clear_marks();
            markers = [];
            if (directionsRenderer != null){
                directionsRenderer.setMap(null);
                directionsRenderer.setPanel(null);
            }
        }
        reset_elevations = function(){
            if (elevationChart != null){
                $.each(elevationChart.xAxis, function(i, axis) {
                    axis.setExtremes(null, null, true, false);
                });
                $.each(elevationChart.yAxis, function(i, axis) {
                    axis.setExtremes(null, null, true, false);
                });
            }
            if (ui.elevations != null){
                ui.elevations.hide();
            }
        }

        add_gmap_controls = function(){
            gmap.controls[google.maps.ControlPosition.RIGHT_TOP].push(new unit_select());
            gmap.controls[google.maps.ControlPosition.RIGHT_TOP].push(new mode_select());
            gmap.controls[google.maps.ControlPosition.TOP_RIGHT].push(new reset_button());
        }

        show_multi_results = function(results){
            table = $('<table cellspacing="0" cellpadding="0" class="results_table"></table>');
            for (var i = 0; i < results.length; i++){
                tr = $('<tr><td>' + results[i].formatted_address + '</td></tr>');

                tr.click({
                    result: results[i]
                },
                function(event){
                    ui.searchbox.val(event.data.result.formatted_address);
                    gmap.fitBounds(event.data.result.geometry.viewport);
                    gmap.setZoom(gmap.getZoom()+1);
                    if (mapoptions.locationOption.enable){
                        location_marker.setPosition(event.data.result.geometry.viewport.getCenter())
                        if (mapoptions.locationOption.location_callback){
                            mapoptions.locationOption.location_callback(event.data.result);
                        }
                    }
                    start_search();
                });

                table.append(tr);
            }
            position = ui.searchbox.offset();
            ui.searchresults.css({
                left: position.left + "px",
                top: position.top + ui.searchbox.height() +
                parseInt(ui.searchbox.css("border-top-width")) +
                parseInt(ui.searchbox.css("border-bottom-width")) +
                parseInt(ui.searchbox.css("padding-top")) +
                parseInt(ui.searchbox.css("padding-bottom")) + "px"
            });
            ui.searchresults.append(table);
            ui.searchresults.show();
        }

        /* Location Mode */
        var location_marker = null;
        create_location_marker = function(pos) {
            location_marker = new google.maps.Marker({
                position: pos,
                title: 'my location',
                map: gmap,
                draggable: true
            });
            // Add marker drag event listeners.
            google.maps.event.addListener(location_marker, 'dragend', function() {
                start_location_search_by_pos(location_marker.getPosition());
            });
        };
        start_location_search_by_pos = function(pos) {
            reset_ui();
            ui.waiting.show();
            geocoder.geocode({'latLng': pos}, callback_geocode);
        };

        callback_geocode = function(results, status){
            if (!results){
                ui.searchmsg.html("no response");
            }
            else{
                if (status == google.maps.GeocoderStatus.OK){
                    if (results.length == 1){
                        ui.searchbox.val(results[0].formatted_address);
                        gmap.fitBounds(results[0].geometry.viewport);
                        gmap.setZoom(gmap.getZoom()+1);

                        if (mapoptions.locationOption.enable){
                            location_marker.setPosition(results[0].geometry.viewport.getCenter())
                            if (mapoptions.locationOption.location_callback){
                                mapoptions.locationOption.location_callback(results[0]);
                            }
                        }
                        start_search();
                    }
                    else{
                        show_multi_results(results);
                    }
                }
                else{
                    ui.searchmsg.html("wrong address");
                }
            }
            ui.waiting.hide();
        }

        start_location_search_by_addr = function(){
            var query = ui.searchbox.val().replace(/(^\s*)|(\s*$)/g, "");;
            if (query.length <= 0)
            {
                return false;
            }
            reset_ui();
            ui.waiting.show();
            geocoder.geocode({'address': query}, callback_geocode);
        }

        // google services
        gmap = null;
        geocoder = null;
        routeIcon = null;
        riderIcon = null;
        teamIcon = null;

        elevationService = null;
        elevationChart = null;
        directionsService = null;
        directionsRenderer = null;

        //private data for directions

        var markers = [];
        var mousemarker = null;
        var elevationsResult = [];
        var directionsResult = null;

        add_marker = function(latlng){
            marker = new google.maps.Marker({
                position: latlng,
                map: gmap,
                icon: "http://map.google.com/mapfiles/marker" + String.fromCharCode(markers.length + 65) + ".png",
                draggable: true
            });
            google.maps.event.addListener(marker, 'dragend', function(e) {
                start_get_directions();
            });
            markers.push(marker);
        };

        clear_marks = function(){
            for (var i = 0; i < markers.length; i++){
                markers[i].setMap(null);
            }
        };
        
        refresh_markers = function(dResult){
            clear_marks();
            markers=[];
            if(dResult.routes.length>0){
                route = dResult.routes[0];
                add_marker(route.legs[0].start_location);
                $.each(route.legs, function(i,leg){
                    add_marker(leg.end_location);
                });
                clear_marks();
            }
        }

        create_route_map_click_handler = function(event){
            if (markers.length >= 10){
                alert("Maximum number of waypoints reached");
                return;
            }
            latLng = event.latLng;
            add_marker(latLng);
            start_get_directions();
        };

        show_route_map_click_handler = function(event){
            if (mapoptions.showrouteOption.enable && mapoptions.showrouteOption.callback)
            {
                latLng = event.latLng;
                mapoptions.showrouteOption.callback(latLng);
            }
        };
        
        show_directions = function(response, renderer_option){
            if (directionsRenderer){
                directionsRenderer.setMap(null);
                directionsRenderer.setPanel(null);
                directionsRenderer = null;
            }
            if(listeners.directions_change){
                google.maps.event.removeListener(listeners.directions_change);
            }
            
            $.extend(mapoptions.directionOption, mapoptions.directionOption, renderer_option || {});

            gmap.fitBounds(response.routes[0].bounds);
            directionsRenderer = new google.maps.DirectionsRenderer(
                mapoptions.directionOption);
            directionsRenderer.setMap(gmap);
            ui.directions && directionsRenderer.setPanel(ui.directions[0]);
            
            listeners.directions_change = google.maps.event.addListener(
                directionsRenderer, 
                'directions_changed', 
                function(){
                    directions = this.getDirections();
                    directionsResult = directions;
                    refresh_markers(directions);
                    if (mapoptions.elevationsOption.enable){
                        show_elevations(directions);
                    }
                    if (mapoptions.directionOption.callback){
                        mapoptions.directionOption.callback(directions);
                    }
                
            });
            
            directionsRenderer.setDirections(response);
        }

        start_get_directions = function(){
            if (markers.length < 2){
                return;
            }
            
            directionsResult = null;

            ui.waiting.show();
            ui.searchmsg.html("getting directions...");

            callback_route = function(response, status){
                ui.searchmsg.html("");
                if (status == google.maps.DirectionsStatus.OK){
                    directionsResult = response;
                    show_directions(response);
                } else{
                    ui.searchmsg.html("no way");
                }
                ui.waiting.hide();
            }
            
            origin = markers[0].getPosition();
            destination = markers[markers.length-1].getPosition();
            
            waypoints = [];
            for (var i=1; i<markers.length-1; i++){
                waypoints.push({ location: markers[i].getPosition(), stopover: true });
            }
            request = {
                origin: origin,
                destination: destination,
                waypoints: waypoints,
                travelMode: mapoptions.directionOption.travelMode,
                optimizeWaypoints: false,
                avoidHighways: true,
                avoidTolls: false,
                unitSystem: mapoptions.unit
            };
            directionsService.route(request, callback_route);
        };

        calc_total_distance = function(route){
            var total_distance = 0;
            for (var i = 0; i < route.legs.length; i++){
                total_distance += route.legs[i].distance.value;
            }
            return total_distance;
        };

        get_route_path = function(route){
            path = [];
            $.each(route.legs, function(i,leg){
                $.each(leg.steps, function(j,step){
                    path = $.merge(path, step.path);
                });
            });
            return path;
        }
        
        show_mark = function(position){
            if (mousemarker == null){
                mousemarker = new google.maps.Marker({
                    zIndex: 999,
                    position: position,
                    map: gmap,
                    icon: "http://map.google.com/mapfiles/ms/icons/green-dot.png"
                });
            } else{
                mousemarker.setPosition(position);
                mousemarker.setVisible(true);
            }
        }
        
        new_elevations_chart = function(panel){
            mapoptions.elevationsOption.chartOption.chart.renderTo = panel[0];
            return new Highcharts.Chart(mapoptions.elevationsOption.chartOption);
        }
        
        set_axis_title = function(axis, title){
            axis.options.title.text = title;
            if (axis.axisTitle) {
                axis.axisTitle.destroy();
                axis.axisTitle = null;
            }
            axis.hasRenderedTitle = false;
            axis.redraw();
        }

        show_elevations_chart = function(elevations, total_distance){
            if (mapoptions.elevationsOption.enable && ui.elevations){
                var total_distance = total_distance;
                total_distance = total_distance/1000; //m to km
                total_distance = get_localize_distance(total_distance); //km to mi
                
                ui.elevations.hide();
                ui.elevations.mouseout(function(){
                    if (mousemarker != null){
                        mousemarker.setVisible(false);
                    }
                });
                
                if(!elevationChart){
                    elevationChart = new_elevations_chart(ui.elevations);
                }
                set_axis_title(elevationChart.xAxis[0], "distance ("+get_unit_str(0)+")");
                set_axis_title(elevationChart.yAxis[0], "elevation ("+get_unit_str(1)+")");

                ui.elevations.show();
                var data = [];
                var ymax = 0;
                count = elevations.length;
                for (var i = 0; i < count; i++){
                    point = {x:total_distance / count * i,
                             y:  get_localize_elevation(elevations[i].elevation),
                             location: elevations[i].location
                             };
                    if (point.y > ymax){
                        ymax = point.y;
                    }
                    data.push(point);
                }

                if (ymax < 350){
                    elevationChart.yAxis[0].options.max = 400;
                }
                else{
                    elevationChart.yAxis[0].options.max = null;
                }
                elevationChart.series[0].setData(data, true);

                if (mapoptions.elevationsOption.callback){
                    mapoptions.elevationsOption.callback(elevations);
                }
            }
        }

        show_elevations_from_google = function(overview_path, total_distance){
            if (mapoptions.elevationsOption.enable && ui.elevations){
                var callback_getelevation = function(elevations, status){
                    if (status == google.maps.ElevationStatus.OK){
                        elevationsResult = elevations;
                        show_elevations_chart(elevations, total_distance);
                    }
                    else if(status == google.maps.ElevationStatus.OVER_QUERY_LIMIT){
                        alert("over the requests limit in too short a period of time, pls wait and try again");
                        if (mapoptions.elevationsOption.callback){
                            mapoptions.elevationsOption.callback(null);
                        }
                    }
                    else{
                        alert("get elevation error");
                        if (mapoptions.elevationsOption.callback){
                            mapoptions.elevationsOption.callback(null);
                        }
                    }
                };
                request = {
                    path: overview_path,//get_route_path(route),
                    samples: mapoptions.elevationsOption.samples
                }
                elevationService.getElevationAlongPath(request, callback_getelevation);
            }
        }

        show_elevations = function(directionsresult){
            var route = directionsresult.routes[0];
            var overview_path = route.overview_path;
            var total_distance = calc_total_distance(route);

            show_elevations_from_google(overview_path, total_distance);
        };

        var current_request = null;
        var working, markerclusterer;

        new_marker_clusterer = function(){
            markerclusterer = new MarkerClusterer(gmap, [], {maxZoom: 11, gridSize:15,
                                        minimumClusterSize: 2, averageCenter:true
                                        });
        };

        start_search = function(){
            if (working) {
                current_request.abort();
                ui.searchmsg.html("");
            }
            _search();
        };

        clear_markers_in_bounds = function(bounds){
            var markers_should_be_clear = [];
            var markers_in_cl = markerclusterer.getMarkers();
            for(var i=0; i<markers_in_cl.length; i++){
                marker = markers_in_cl[i];
                if ( bounds.contains(marker.getPosition()) ){
                    markers_should_be_clear.push(marker);
                    marker.setMap(null);
                }
            }
            markerclusterer.removeMarkers(markers_should_be_clear, true);
            markerclusterer.repaint();
        };
        
        show_route_info = function(marker){
            route = marker.route;
            var infoBox = new InfoBox({latlng: marker.getPosition(),
                                        map: gmap,
                                        mode: "route",
                                        route: route});
        };
        show_rider_info = function(marker){
            rider = marker.rider;
            var infoBox = new InfoBox({latlng: marker.getPosition(),
                                        map: gmap,
                                        mode: "rider",
                                        rider: rider});
        }

        var commentBox = new google.maps.InfoWindow({
            maxWidth: 230
        });
        show_comment_div = function(marker){
            function _build_comment_div(comment){
                var comment_info = $('<div><a target="_blank" class="riderphoto" href="#"><img width="40" height="40"/></a>' +
                        '<p class="top"></p><p class="review"></p>' +
                        '<img id="comment_photo"/></div>');

                rider_url = "/riders/" + comment.reviewer_uid;

                $(".riderphoto", comment_info).attr('href', rider_url);
                $(".riderphoto img", comment_info).attr("src", comment.reviewer_photourl || "/static/images/rider.jpg");
                $(".riderphoto img", comment_info).css({'float':'left',
                    'border': '1px solid #CCCCCC',
                    'padding': '2px',
                    'background': 'none repeat scroll 0 0 #FFFFFF',
                    'margin':'4px',
                    'display': 'block'
                });
                
                $(".top", comment_info).html("<a></a> "+
                        formatutc(new Date(comment.posted_on),"yyyy-MM-dd hh:mm") );
                $(".top a", comment_info).html(comment.reviewer);
                $(".top a", comment_info).attr('href', rider_url);

                $(".review", comment_info).html(comment.review.replace(/(\r)|(\n)/g, ' '));

                var img = $("#comment_photo", comment_info);
                if(comment.photourl){
                    img.css({
                        'max-height': '165px',
                        'max-width': '220px',
                        'border-radius': '4px',
                        'cursor': 'url("/static/images/zoom_in.cur"), pointer'
                    })
                    img.attr("src", comment.photourl);
                    if($.blockUI){
                        var imgBox = $('<div><a id="photo_link" target="_blank"><img src="/static/images/loading.gif"/></a>' +
                                    '<a class="close">x</div></div>');
                        $("#photo_link", imgBox).attr('href', comment.photourl);
                        imgBox.css({
                            'width': '640px',
                            'height':'480px',
                            'position': 'relative'
                        })
                        $("img", imgBox).css({
                            'max-width': '640px',
                            'max-height': '480px',
                            'cursor': 'url("/static/images/zoom_in.cur"), pointer'
                        });
                        $(".close", imgBox).click(function(){
                            $.unblockUI();
                        });
                        img.click(function() {
                            $.blockUI({
                                message: imgBox,
                                css: {
                                    top:  ($(window).height() - 480) /2 + 'px',
                                    left: ($(window).width() - 640) /2 + 'px',
                                    height: '480px',
                                    width: '640px'
                                },
                                onBlock: function() {
                                    $("img", imgBox).attr('src', comment.photourl);
                                    $('.blockOverlay').attr('title','Click to unblock').click($.unblockUI);
                                }
                            });
                        });
                    }

                } else {
                    img.remove();
                }
                return comment_info[0];
            }
            commentBox.setPosition(marker.getPosition());
            commentBox.setContent(_build_comment_div(marker.comment));
            commentBox.open(gmap);
        }


        var shown_bounds = [];
        if_bounds_shown = function(bounds){
            ne = bounds.getNorthEast();
            sw = bounds.getSouthWest();
            nw = new google.maps.LatLng(ne.lat(), sw.lng());
            se = new google.maps.LatLng(sw.lat(), ne.lng());

            points = [ne,sw,nw,se];
            for(var i=0; i<shown_bounds.length; i++){
                var b = shown_bounds[i];
                var allin = true;
                for(var j=0; j<points.length; j++){
                    p = points[j];
                    if (!b.contains(p)){
                        allin = false;
                        break;
                    }
                }
                if (allin){
                    return true;
                }
            }
            return false;
        };

        split_bounds_to_4 = function(bounds){
            var n = bounds.getNorthEast().lat();
            var e = bounds.getNorthEast().lng();
            var s = bounds.getSouthWest().lat();
            var w = bounds.getSouthWest().lng();

            var splittedbounds = [];
            splittedbounds.push( new google.maps.LatLngBounds(new google.maps.LatLng(s,(w+e)/2),
                                                              new google.maps.LatLng((s+n)/2, e)));
            splittedbounds.push( new google.maps.LatLngBounds(new google.maps.LatLng(s,w),
                                                              new google.maps.LatLng((s+n)/2, (w+e)/2)));
            splittedbounds.push( new google.maps.LatLngBounds(new google.maps.LatLng((s+n)/2,(w+e)/2),
                                                              new google.maps.LatLng(n, e)));
            splittedbounds.push( new google.maps.LatLngBounds(new google.maps.LatLng((s+n)/2,w),
                                                              new google.maps.LatLng(n, (w+e)/2-0.001)));
            return splittedbounds;
        }

        var _search = function(){
            if (!(mapoptions.routesOption.enable || mapoptions.ridersOption.enable || mapoptions.teamsOption.enable)){
                return;
            }

            var total_routes = 0;
            var total_riders = 0;
            var total_teams = 0;

            _show_routes_by_bounds = function(bounds){
                paramters = {method:"get_routes_by_bounds",
                                  north: bounds.getNorthEast().lat(),
                                  east: bounds.getNorthEast().lng(),
                                  south: bounds.getSouthWest().lat(),
                                  west: bounds.getSouthWest().lng()
                             };
                if (if_bounds_shown(bounds)){
                    return;
                }
                ui.waiting.show();
                ui.searchmsg.html("routes search...");
                current_request = $.ajax({
                    type:"POST",
                    url:"/apis/rest/",
                    data: paramters,
                    beforeSend: function(){
                        working = true;
                    },
                    success: function(data){
                        data = _US(data);
                        if (data.status=="ok") {
                            var routes = data.routes;
                            total_routes += routes.length;

                            if (routes.length > 0){
                                clear_markers_in_bounds(bounds);
                            }

                            var markers = [];
                            for(var i=0;i<routes.length;i++){
                                var route = routes[i];
                                var marker = new google.maps.Marker({position: route.location,
                                    icon: routeIcon,
                                    title: route.name + ' - ' + route.creator
                                    });
                                marker.route = route;
                                google.maps.event.addListener(marker, 'click', function() {
                                        show_route_info(this);
                                    });
                                markers.push(marker);
                            }
                            markerclusterer.addMarkers(markers, true);
                            markerclusterer.repaint();

                            ui.searchmsg.html("Found"+" "+ total_routes + " "+"routes");

                        }  else{
                            alert(data.message);
                        }
                        ui.waiting.hide();
                        working = false;
                    },
                    error:function(xhr, msg){
                        ui.searchmsg.html(xhr.statusText + xhr.responseText);
                        ui.waiting.hide();
                        working = false;
                    }
                });
            }

            _show_riders_by_bounds = function(bounds){
                paramters = {method:"get_riders_by_bounds",
                                  north: bounds.getNorthEast().lat(),
                                  east: bounds.getNorthEast().lng(),
                                  south: bounds.getSouthWest().lat(),
                                  west: bounds.getSouthWest().lng()
                             };
                if (if_bounds_shown(bounds)){
                    return;
                }
                ui.waiting.show();
                ui.searchmsg.html("riders search...");
                current_request = $.ajax({
                    type:"POST",
                    url:"/apis/rest/",
                    data: paramters,
                    beforeSend: function(){
                        working = true;
                    },
                    success: function(data){
                        data = _US(data);
                        if (data.status=="ok") {
                            var riders = data.riders;
                            total_riders += riders.length;

                            if (riders.length > 0){
                                clear_markers_in_bounds(bounds);
                                /*rectangle = new google.maps.Rectangle({
                                      map: gmap,
                                     bounds: bounds,
                                     strokeWeight:0
                                    });*/
                            }

                            var markers = [];
                            for(var i=0;i<riders.length;i++){
                                var rider = riders[i];
                                var marker = new google.maps.Marker({position: rider.location,
                                    icon: riderIcon,
                                    title: rider.name
                                    });
                                marker.rider = rider;
                                google.maps.event.addListener(marker, 'click', function() {
                                        show_rider_info(this);
                                    });
                                markers.push(marker);
                            }
                            markerclusterer.addMarkers(markers, true);
                            markerclusterer.repaint();

                            ui.searchmsg.html("Found"+" "+ total_riders + " "+"Riders");

                        }  else{
                            alert(data.message);
                        }
                        ui.waiting.hide();
                        working = false;
                    },
                    error:function(xhr, msg){
                        ui.searchmsg.html(xhr.statusText + xhr.responseText);
                        ui.waiting.hide();
                        working = false;
                    }
                });
            }

            _show_teams_by_bounds = function(bounds){
                
            }
            
            var bounds = gmap.getBounds();

            if (mapoptions.searchmode == "center"){
                center = gmap.getCenter();
                max_distance = 100000;
                var circle = new google.maps.Circle({
                    center: center,
                    radius: max_distance
                });
                bounds = circle.getBounds();
            }

            var splittedbounds = split_bounds_to_4(bounds);

            for(var i=0; i<splittedbounds.length; i++)
            {
                if (mapoptions.routesOption.enable){
                    _show_routes_by_bounds(splittedbounds[i]);
                }
                if (mapoptions.ridersOption.enable){
                    _show_riders_by_bounds(splittedbounds[i]);
                }
                if (mapoptions.teamsOption.enable){
                    _show_teams_by_bounds(splittedbounds[i]);
                }
                
            }
            shown_bounds.push(bounds); // insert bounds to shown list
        };
        
        //Test functions

        test_add_routes = function(){
            var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(21.482959705849556, 88.20267089843753),
                                                new google.maps.LatLng(48.71689339414397, 125.47732910156253));
            var southWest = bounds.getSouthWest();
            var northEast = bounds.getNorthEast();
            var latSpan = northEast.lat() - southWest.lat();
            var lngSpan = northEast.lng() - southWest.lng();
            for (var i = 0; i < 2000; i++  ) {
                var lat = southWest.lat() + latSpan * Math.random();
                var lng = southWest.lng() + lngSpan * Math.random();
                var latlng = new google.maps.LatLng(lat, lng);
                var marker = new google.maps.Marker({
                  position: latlng
                });
                routemarkers.push(marker);
            }
            var markerclusterer = new MarkerClusterer(gmap, routemarkers ,{maxZoom: 12,
                    minimumClusterSize: 10,
                    averageCenter:true
                    });
        };
        
        //public functions
        get_localize_distance = function(km){
            if (mapoptions.unit == google.maps.DirectionsUnitSystem.IMPERIAL) {
                return km * KM2MILE;
            }
            return km;
        };
        get_localize_elevation = function(m){
            if (mapoptions.unit == google.maps.DirectionsUnitSystem.IMPERIAL) {
                return m * M2FT;
            }
            return m;
        };
        get_directions = function(){
            return directionsResult;
        }
        get_elevations = function(){
            return elevationsResult;
        };
        calc_elevations = function(elevationResult){
            if(!elevationResult){ return null;}
            var len = elevationResult.length;
            var startElevation = elevationResult[0].elevation;
            var endElevation = elevationResult[len-1].elevation;
            var maxElevation = startElevation;
            var totalUp = 0;
            var totalDown = 0;

            var prev = startElevation;
            for(var i=1; i<len; i++){
                cur = elevationResult[i].elevation;
                if (cur > maxElevation) { maxElevation = cur; }
                diff = cur - prev;
                if (diff > 0){
                    totalUp += diff;
                } else {
                    totalDown += diff;
                }
                prev = cur;
            }
            return {
                start: get_localize_elevation(startElevation),
                end: get_localize_elevation(endElevation),
                highest: get_localize_elevation(maxElevation),
                up: get_localize_elevation(totalUp),
                down: get_localize_elevation(totalDown)
            }
        }
        reset = function(){
            reset_ui();
            reset_directions();
            reset_elevations();
            if (mapoptions.controlsOption.reset_callback){
                mapoptions.controlsOption.reset_callback();
            }
        }

        
        this.get_unit_str = get_unit_str;
        this.get_localize_distance = get_localize_distance;
        this.get_localize_elevation = get_localize_elevation;
        this.show_elevations_chart = show_elevations_chart;
        this.show_elevations_from_google = show_elevations_from_google;
        this.calc_total_distance = calc_total_distance;
        this.get_directions = get_directions;
        this.get_elevations = get_elevations;
        this.calc_elevations = calc_elevations;
        this.reset = reset;
            
        this.set_directions = function(directions, renderer_option){ 
            directionsResult = directions;
            renderer_option = renderer_option || {
                polylineOptions: {
                    clickable: false,
                    strokeColor: '#0000FF',
                    strokeOpacity: 0.5,
                    strokeWeight: 5
                }
            };
            show_directions(directionsResult, renderer_option);
        };

        this.get_address = function(latLng, callback){
            geocoder.geocode({'latLng': latLng}, function(results, status){
                if (status == google.maps.GeocoderStatus.OK){
                    callback(results[0].formatted_address);
                }else{
                    callback("");
                }
            });
        };

        this.show_comment = function(comment){
            var icon = '/static/images/comment_'+ comment.category +'.png';
            if((comment.category == "text") &&comment.photourl){ icon = "/static/images/comment_photo.png"; }

            var anchor = null;
            if (comment.category == "text" || comment.category == "photo"){
                anchor = new google.maps.Point(4, 25);
            }
            
            var draggable = ''==comment.reviewer_uid ? true : false;
            var marker = new google.maps.Marker({position: comment.location,
                map: gmap,
                icon: new google.maps.MarkerImage(icon,null,null,anchor,null),
                draggable: draggable,
                title: comment.category
                });
            marker.comment = comment;
            google.maps.event.addListener(marker, 'click', function() {
                show_comment_div(this);
            });
            if(draggable){
                google.maps.event.addListener(marker, "dragend", function(evt){
                    var newlocation = evt.latLng;
                    $.post("/apis/rest/",
                            {method:"save_comment_location",
                             commentid: this.comment.id,
                             newlocation: _S(newlocation)
                            },
                            function(data){
                                data = _US(data);
                                if (data.status=="ok") {
                                } else {
                                    alert(data.message);
                                }
                    });
                });
            }
        }

        this.show_comments = function(comments){           
            for(var i=0;i<comments.length;i++){
                this.show_comment(comments[i]);
            }
        }
        
        this.gmap = function(){ return gmap; };
        
        return this;
    };
    $.magicmap.prototype = {
        name: "magicmap",
        author: "gully chen",
        version: "1.0",
        units: [google.maps.DirectionsUnitSystem.METRIC, google.maps.DirectionsUnitSystem.IMPERIAL]
    };

    // public functions
    $.magicmap.prototype.initmap = function(options, uicontrols){
        $.extend(mapoptions, mapoptions, options);
        // uicontrols are all jquery object
        ui = uicontrols;

        gmap_option = {
            zoom: mapoptions.zoom,
            mapTypeId: mapoptions.mapTypeId,
            scaleControl: true,
            scaleControlOptions: {position: google.maps.ControlPosition.BOTTOM_CENTER},
            center: mapoptions.startcenter
        }

        gmap = new google.maps.Map(ui.map_container[0], gmap_option);
        geocoder = new google.maps.Geocoder();

        if (mapoptions.controlsOption.enable){
            add_gmap_controls();
        }
        
        if (mapoptions.routesOption.enable || mapoptions.ridersOption.enable || mapoptions.teamsOption.enable){
            routeIcon = new google.maps.MarkerImage(url="/static/images/route_marker.png" 
                                                      ,null
                                                      ,null
                                                      ,anchor = new google.maps.Point(2, 31)
                                                     );
            riderIcon = new google.maps.MarkerImage(url="/static/images/rider_icon.png"
                                                      ,null
                                                      ,null
                                                      ,null
                                                     );
            teamIcon = new google.maps.MarkerImage(url="/static/images/route_marker.png"
                                                      ,null
                                                      ,null
                                                      ,null
                                                     );
            new_marker_clusterer();
            google.maps.event.addListenerOnce(gmap,'bounds_changed',function(){ start_search(); });
            google.maps.event.addListener(gmap,'dragend',function(){ start_search(); });
            google.maps.event.addListener(gmap,'zoom_changed',function(){ start_search(); });
        }

        //bind event handler
        ui.searchbox && ui.searchbox.keyup(function(event){
            if (event.keyCode == '13'){
                start_location_search_by_addr();
                return false;
            }
        });
        ui.searchbtn && ui.searchbtn.click(function(){
            start_location_search_by_addr();
            return false;
        });
        ui.searchresults && ui.searchresults.hover(
            function(){
                $(this).show();
            },
            function(){
                $(this).hide();
            }
        );

        if (mapoptions.locationOption.enable){
            create_location_marker(mapoptions.startcenter);
        }
        return this;
    };

    $.magicmap.prototype.enable_directions = function(options, panel){
        $.extend(mapoptions.directionOption, mapoptions.directionOption, options);

        ui.directions = panel; //panel is a jquery object

        directionsService = new google.maps.DirectionsService();

        listeners.map_click && google.maps.event.removeListener(listeners.map_click);

        listeners.map_click = google.maps.event.addListener(gmap, 'click', create_route_map_click_handler);
        
        mapoptions.directionOption.enable = true;
        return this;
    };
    $.magicmap.prototype.disable_directions = function(){
        clear_marks();
        markers=[];
        directionsService = null;
        if (directionsRenderer != null){
            directionsRenderer.setMap(null);
            directionsRenderer = null;
        }
        if (listeners.map_click){
            google.maps.event.removeListener(listeners.map_click);
        }

        mapoptions.directionOption.enable = false;
        return this;
    }
    $.magicmap.prototype.enable_show_route_mode = function(options, panel){
        ui.directions = panel; //panel is a jquery object
        $.extend(mapoptions.showrouteOption, mapoptions.showrouteOption, options);
        listeners.map_right_click = google.maps.event.addListener(gmap, 'rightclick', show_route_map_click_handler);
        mapoptions.showrouteOption.enable = true;
        return this;
    };

    $.magicmap.prototype.enalbe_elevations = function(options, panel){
        $.extend(mapoptions.elevationsOption, mapoptions.elevationsOption, options);

        ui.elevations = panel;
        ui.elevations.hide();
        //panel is a jquery object	    

        elevationService = new google.maps.ElevationService();

        mapoptions.elevationsOption.enable = true;
        elevationChart = new_elevations_chart(panel);
        return this;
    }
    $.magicmap.prototype.disable_elevations = function(){
        if (ui.elevations){
            ui.elevations.hide();
        }
        elevationService = null;
        elevationChart = null;
        mapoptions.elevationsOption.enable = false;
        return this;
    }
    $.magicmap.prototype.set_unit = function(unit){
        set_map_unit(unit);
        return this;
    }

    $.magicmap.get_location_by_ip = function(){
        if (google.loader && google.loader.ClientLocation){
            return new google.maps.LatLng(google.loader.ClientLocation.latitude, google.loader.ClientLocation.longitude);
        }
        return null;
    }

    $.magicmap.get_location_by_lang = function(){
        var china = new google.maps.LatLng(35.86166, 104.1954);
        var usa = new google.maps.LatLng(37.09024, -95.7129);
        var japan = new google.maps.LatLng(36.204824, 138.2529);

        if (navigator.userLanguage){
            baseLang = navigator.userLanguage.substring(0, 2).toLowerCase();
        } else{
            baseLang = navigator.language.substring(0, 2).toLowerCase();
        }
        switch (baseLang){
        case "en":
            return usa;
        case "ja":
            return japan;
        case "zh":
            return china;
        default:
            return china;
        }
    }; 
})(jQuery);


/* An InfoBox is like an info window, but it displays
* under the marker, opens quicker, and has flexible styling.
* @param {GLatLng} latlng Point to place bar at
* @param {Map} map The map on which to display this InfoBox.
* @param {Object} opts Passes configuration options - content,
* offsetVertical, offsetHorizontal, className, height, width
*/
function InfoBox(opts) { 
    google.maps.OverlayView.call(this);
    this.latlng_ = opts.latlng;
    this.map_ = opts.map;
    this.mode_ = opts.mode;
    this.route_ = opts.route;
    this.rider_ = opts.rider;
    this.team_ = opts.team;
    this.comment_ = opts.comment;

    this.offsetVertical_ = -207;
    this.offsetHorizontal_ = 0;

    if (this.mode_ == "route"){
        this.height_ = 180;
        this.width_ = 286;
    } else if(this.mode_ == "rider"){
        this.height_ = 115;
        this.width_ = 266;
        this.offsetVertical_ = -142;
    } else if(this.mode_ == "team"){
        this.height_ = 200;
        this.width_ = 306;
    } else if(this.mode_ == "comment"){
        this.height_ = 200;
        this.width_ = 266;
    }

    
    var me = this;
    this.boundsChangedListener_ =
        google.maps.event.addListener(this.map_, "bounds_changed", function() {
            return me.panMap.apply(me);
        });
    // Once the properties of this OverlayView are initialized, set its map so
    // that we can display it.  This will trigger calls to panes_changed and
    // draw.
    this.setMap(this.map_);
}

/* InfoBox extends GOverlay class from the Google Maps API
 */
InfoBox.prototype = new google.maps.OverlayView();

/* Creates the DIV representing this InfoBox
 */
InfoBox.prototype.remove = function() {
    if (this.div_) {
        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;
    }
};

/* Redraw the Bar based on the current projection and zoom level
 */
InfoBox.prototype.draw = function() {
    // Creates the element if it doesn't exist already.
    if (this.mode_ == "route"){
        this.createRouteElement();
    } else if(this.mode_ == "rider"){
        this.createRiderElement();
    } else if(this.mode_ == "team"){
        this.createTeamElement();
    } else if(this.mode_ == "comment"){
        this.createCommentElement();
    }

    if (!this.div_) return;

    // Calculate the DIV coordinates of two opposite corners of our bounds to
    // get the size and position of our Bar
    var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
    if (!pixPosition) return;

    // Now position our DIV based on the DIV coordinates of our bounds
    this.div_.style.width = this.width_ + "px";
    this.div_.style.left = (pixPosition.x + this.offsetHorizontal_) + "px";
    this.div_.style.height = this.height_ + "px";
    this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
    this.div_.style.display = 'block';
};

/* Creates the DIV representing this InfoBox in the floatPane.  If the panes
 * object, retrieved by calling getPanes, is null, remove the element from the
 * DOM.  If the div exists, but its parent is not the floatPane, move the div
 * to the new pane.
 * Called from within draw.  Alternatively, this can be called specifically on
 * a panes_changed event.
 */
var get_km_or_mile = function(distance){
    
        return (distance/1000).toFixed(0);
    
};

InfoBox.prototype.createRouteElement = function() {
  var panes = this.getPanes();
  var div = this.div_;
  if (!div) {
      // This does not handle changing panes.  You can set the map to be null and
      // then reset the map to move the div.
      html_div = '<div class="infobox">'                                                             +
                    '<div class="topdiv">'                                                              +
                        '<span class="route_name"><a href="#"></a></span>'                            +
                        '<img src="/static/images/closebigger.gif"/>' +
                    '</div><div class="clear"></div>'                                                  +
                    '<div class="content">'                                                             +
                        '<a href="#"><img class="preview" /></a>'                                       +
                        '<div class="content_right">'                                                   +
                          '<div class="distance"></div>'                                               +
                          '<div class="command"></div>'                                                 +
                        '</div>'                                                                          +
                    '</div>'                                                                              +
                  '</div>';

      route_div = $(html_div);
      img_close = $(".topdiv img", route_div);

      topdiv = $(".topdiv", route_div);

      route_name = $(".route_name a", topdiv);
      route_name.html(this.route_.name + ' -- ' + this.route_.creator);
      route_name.attr("href", "/routes/"+this.route_.id);
      route_name.attr("target", "_blank");

      content = $(".content", route_div);
      preview = $(".preview", content)
      preview.attr("src", this.route_.preview_url);

      $("a", content).attr("href", "/routes/"+this.route_.id);
      $("a", content).attr("target", "_blank");

      distance = $(".distance", content);
      distance.html('<span class="value">'+
              get_km_or_mile(this.route_.distance) + 
              ' <br/>km</span>');
      command = $(".command", content);
      command.html('<a target="_blank" class="yellowbutton" href="' + '/routes/' +
              this.route_.id +
              '">Details</a>' +
              '<a target="_blank" class="yellowbutton" href="/rides/logride/' + this.route_.id +'">Done('+
              this.route_.rides_count + ')</a>' +

              '<a name="likeit" href="javascript:;" class="yellowbutton">Like('+
              this.route_.fav_count + ')</a>'
              );

      var me=this;
      var likeit = $("a[name='likeit']", command);
      likeit.click(function(){
          $.post("/apis/rest/",
                {method:"add_favorite_route",
                 routeid: me.route_.id
                },
                function(data){
                    data = _US(data);
                    if (data.status=="ok") {
                        likeit.addClass("disabled");
                        $.growlUI && $.growlUI('Route added into your Favorites',' ');
                    } else {
                        alert(data.message);
                    }
          });
      });
      
      $(document).append(route_div);
      function removeInfoBox(ib) {
          return function() {
            ib.setMap(null);
          };
      }
      google.maps.event.addDomListener(img_close[0], 'click', removeInfoBox(this));
      div = this.div_ = route_div[0];
      div.style.width = this.width_ + "px";
      div.style.height = this.height_ + "px";
      div.style.display = 'none';

      panes.floatPane.appendChild(div);
      this.panMap();
  } else if (div.parentNode != panes.floatPane) {
      // The panes have changed.  Move the div.
      div.parentNode.removeChild(div);
      panes.floatPane.appendChild(div);
  } else {
      // The panes have not changed, so no need to create or move the div.
  }
}

InfoBox.prototype.createRiderElement = function() {
var panes = this.getPanes();
  var div = this.div_;
  if (!div) {
      // This does not handle changing panes.  You can set the map to be null and
      // then reset the map to move the div.
      html_div = '<div class="infobox">'                                                               +
                    '<div class="topdiv">'                                                               +
                        '<span class="rider_name"><a href="#"></a></span>'                               +
                        '<img src="/static/images/closebigger.gif"/>' +
                    '</div><div class="clear"></div>'                                                     +
                    '<div class="content">'                                                               +
                        '<a class="photo" href="#"><img class="my_photo"/></a>'                       +
                        '<p class="route_stats"></p>'  +
                        '<p class="ride_stats"></p>'  +
                        '<p class="time_stats"></p>'  +
                        '<p><a target="_blank" class="yellowbutton">Details</a></p>'
                    '</div>'                                                                              +
                  '</div>';

      rider_div = $(html_div);
      img_close = $(".topdiv img", rider_div);

      rider_name = $(".rider_name a", rider_div);
      rider_name.html(this.rider_.name);
      rider_name.attr("href", "/riders/"+this.rider_.uid);
      rider_name.attr("target", "_blank");
      rider_name.attr("title", this.rider_.name + ' ');
      for(var i=0; i<this.rider_.suns; i++){
          rider_name.append('<img width="16" height="16" src="/static/images/sun.png"/>');
      }
      for(var i=0; i<this.rider_.stars; i++){
          rider_name.append('<img width="16" height="16" src="/static/images/star.png"/>');
      }
      if(this.rider_.stars<1){
          rider_name.append('<img width="16" height="16" src="/static/images/star_half.png"/>');
      }

      var photourl = this.rider_.photourl;
      if(photourl==""){
          photourl = "/static/images/rider.jpg";
      }
      $(".content img", rider_div).attr("src", photourl);
      $(".photo", rider_div).attr("href", "/riders/"+this.rider_.uid);
      $(".route_stats", rider_div).html( this.rider_.routes_count + ' Routes ' +
              get_km_or_mile(this.rider_.routes_total_distance) + ' km ');
      $(".ride_stats", rider_div).html( this.rider_.rides_count + ' Rides ' +
              get_km_or_mile(this.rider_.rides_total_distance) + ' km ');
      $(".time_stats", rider_div).html( '<b>' + this.rider_.hours_on_bike.toFixed(0) + '</b> ' +
                        'hours on bike');
      $(".yellowbutton", rider_div).attr("href", "/riders/"+this.rider_.uid);

      $(document).append(rider_div);
      function removeInfoBox(ib) {
          return function() {
            ib.setMap(null);
          };
      }
      google.maps.event.addDomListener(img_close[0], 'click', removeInfoBox(this));
      div = this.div_ = rider_div[0];
      div.style.width = this.width_ + "px";
      div.style.height = this.height_ + "px";
      div.style.display = 'none';

      panes.floatPane.appendChild(div);
      this.panMap();
  } else if (div.parentNode != panes.floatPane) {
      // The panes have changed.  Move the div.
      div.parentNode.removeChild(div);
      panes.floatPane.appendChild(div);
  } else {
      // The panes have not changed, so no need to create or move the div.
  }
}
InfoBox.prototype.createTeamElement = function() {

}
InfoBox.prototype.createCommentElement = function() {
  var panes = this.getPanes();
  var div = this.div_;
  if (!div) {
      // This does not handle changing panes.  You can set the map to be null and
      // then reset the map to move the div.
      html_div = '<div class="infobox">'                                                               +
                    '<div class="topdiv">'                                                               +
                        '<span class="rider_name"><a href="#"></a></span>'                               +
                        '<img src="/static/images/closebigger.gif"/>' +
                    '</div><div class="clear"></div>'                                                     +
                    '<div class="content">'                                                               +
                        '<span></span>'                            +
                        '<a href="#"><img class="my_photo"/></a>'                                 +
                    '</div>'                                                                              +
                  '</div>';

      comment_div = $(html_div);
      img_close = $(".topdiv img", comment_div);

      rider_name = $(".rider_name a", comment_div);
      rider_name.html(this.comment_.reviewer);
      rider_name.attr("href", "/riders/"+this.comment_.reviewer_uid);
      rider_name.attr("target", "_blank");

      $(".content span", comment_div).html(this.comment_.review)

      var photourl = this.comment_.photourl;
      if(photourl==""){
          $(".content img", comment_div).remove();
      }else{
          $(".content img", comment_div).attr("src", photourl);
      }

      $(document).append(comment_div);
      function removeInfoBox(ib) {
          return function() {
            ib.setMap(null);
          };
      }
      google.maps.event.addDomListener(img_close[0], 'click', removeInfoBox(this));
      div = this.div_ = comment_div[0];
      div.style.width = this.width_ + "px";
      div.style.height = this.height_ + "px";
      div.style.display = 'none';

      panes.floatPane.appendChild(div);
      this.panMap();
  } else if (div.parentNode != panes.floatPane) {
      // The panes have changed.  Move the div.
      div.parentNode.removeChild(div);
      panes.floatPane.appendChild(div);
  } else {
      // The panes have not changed, so no need to create or move the div.
  }
}
/* Pan the map to fit the InfoBox.
 */
InfoBox.prototype.panMap = function() {
  // if we go beyond map, pan map
  var map = this.map_;
  var bounds = map.getBounds();
  if (!bounds) return;

  // The position of the infowindow
  var position = this.latlng_;

  // The dimension of the infowindow
  var iwWidth = this.width_;
  var iwHeight = this.height_;

  // The offset position of the infowindow
  var iwOffsetX = this.offsetHorizontal_;
  var iwOffsetY = this.offsetVertical_;

  // Padding on the infowindow
  var padX = 40;
  var padY = 40;

  // The degrees per pixel
  var mapDiv = map.getDiv();
  var mapWidth = mapDiv.offsetWidth;
  var mapHeight = mapDiv.offsetHeight;
  var boundsSpan = bounds.toSpan();
  var longSpan = boundsSpan.lng();
  var latSpan = boundsSpan.lat();
  var degPixelX = longSpan / mapWidth;
  var degPixelY = latSpan / mapHeight;

  // The bounds of the map
  var mapWestLng = bounds.getSouthWest().lng();
  var mapEastLng = bounds.getNorthEast().lng();
  var mapNorthLat = bounds.getNorthEast().lat();
  var mapSouthLat = bounds.getSouthWest().lat();

  // The bounds of the infowindow
  var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX;
  var iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX;
  var iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY;
  var iwSouthLat = position.lat() - (iwOffsetY + iwHeight + padY) * degPixelY;

  // calculate center shift
  var shiftLng =
      (iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0) +
      (iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0);
  var shiftLat =
      (iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0) +
      (iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0);

  // The center of the map
  var center = map.getCenter();

  // The new map center
  var centerX = center.lng() - shiftLng;
  var centerY = center.lat() - shiftLat;

  // center the map to the new shifted center
  map.setCenter(new google.maps.LatLng(centerY, centerX));

  // Remove the listener after panning is complete.
  google.maps.event.removeListener(this.boundsChangedListener_);
  this.boundsChangedListener_ = null;
};

/*  required google map api script, json2 and magicmap2*/
custom_stringify = function(name, value){
    if (/^(lat_lngs|path|overview_path)/.test(name)) {
        return google.maps.geometry.encoding.encodePath(value);
    } else if (value instanceof google.maps.LatLng){
        return 'LL(' + value.lat() + ',' + value.lng() + ')';
    } else if (value instanceof google.maps.LatLngBounds){
        return 'LB(' +
            value.getSouthWest().lat() + ',' + value.getSouthWest().lng() + ',' +
            value.getNorthEast().lat() + ',' + value.getNorthEast().lng() + ')';
    } else {
        return value;
    }
}

custom_parse = function(name, value){
    if (/^(lat_lngs|path|overview_path)/.test(name)) {
        return google.maps.geometry.encoding.decodePath(value);
    } else if (/^LL\(/.test(value)){
        var match = /LL\(([^,]+),([^,]+)\)/.exec(value);
        return new google.maps.LatLng(match[1], match[2]);
    } else if (/^LB\(/.test(value)){
        var match = /LB\(([^,]+),([^,]+),([^,]+),([^,]+)\)/.exec(value);
        return new google.maps.LatLngBounds(new google.maps.LatLng(match[1], match[2]),
                                            new google.maps.LatLng(match[3], match[4]));
    } else{
        return value;
    }
}

_S = function(obj){
    return JSON.custom_stringify(obj, custom_stringify);
};
_US = function(str){
    return JSON.custom_parse(str, custom_parse);
};

formatutc = function(date, format){
 /*
  * eg:format="YYYY-MM-dd hh:mm:ss";
  */
 localDate = date - (date.getTimezoneOffset() * 60);
 localDate = new Date(localDate);
 var o = {
      "M+" :  localDate.getMonth()+1,  //month
      "d+" :  localDate.getDate(),     //day
      "h+" :  localDate.getHours(),    //hour
      "m+" :  localDate.getMinutes(),  //minute
      "s+" :  localDate.getSeconds(), //second
      "q+" :  Math.floor((localDate.getMonth()+3)/3),  //quarter
      "S"  :  localDate.getMilliseconds() //millisecond
   }

   if(/(y+)/.test(format)) {
    format = format.replace(RegExp.$1, (localDate.getFullYear()+"").substr(4 - RegExp.$1.length));
   }

   for(var k in o) {
    if(new RegExp("("+ k +")").test(format)) {
      format = format.replace(RegExp.$1, RegExp.$1.length==1 ? o[k] : ("00"+ o[k]).substr((""+ o[k]).length));
    }
   }
 return format;
};

(function($){
    var ledmap = new Array();
    ledmap[" "] = [[]];
    ledmap["0"] = [[2,3,4,5],[1,6],[0,7],[1,6], [2,3,4,5]];
    ledmap["1"] = [[],[1,7],[0,1,2,3,4,5,6,7],[7]];
    ledmap["2"] = [[1,6,7], [0,5,7], [0,4,7], [0,3,7], [1,2,7]];
    ledmap["3"] = [[1,6],[0,7],[0,3,7],[0,3,7], [1,2,3,4,5,6]];
    ledmap["4"] = [[3,4], [2,4],[1,4],[0,1,2,3,4,5,6,7],[4]];
    ledmap["5"] = [[0,1,2,6],[0,3,7],[0,3,7],[0,3,7], [0,4,5,6]];
    ledmap["6"] = [[1,2,3,4,5,6],[0,3,7],[0,3,7],[0,3,7],[1,4,5,6]];
    ledmap["7"] = [[0],[0,5,6,7],[0,4],[0,3],[0,1,2]];
    ledmap["8"] = [[1,2,4,5,6],[0,3,7],[0,3,7],[0,3,7],[1,2, 4,5,6]];
    ledmap["9"] = [[1,2,6],[0,3,7],[0,3,7],[0,3,7],[1,2,3,4,5,6]];
    ledmap["."] = [[6,7],[6,7]];
    ledmap[":"] = [[2,5]];

	$.fn.led = function(rows){
		var rows = rows;
        $(this).each(function(){
            var led = $(this);
			var text_map = get_led_map(led.text());
            create_lights(led, text_map.length, rows, text_map.length);
            draw_led(led,text_map);
		});

        function get_led_map(text)
        {
            var text_map = new Array();
            var totalCols = 0;
            for(i=0; i < text.length; i++){
                var charMap = ledmap[text.charAt(i)];
                if(charMap != undefined){
                    for(j = 0; j < charMap.length; j++){
                        text_map[totalCols] = charMap[j];
                        totalCols++;
                    }
                    // Add a blank col
                    text_map[totalCols] = [];
                    totalCols++;
                }
            }
            return text_map;
        };
		function create_lights(led, cols, rows, displayWidth)
        {
            var pcb = $('<div></div>');
            led.html("");

            for(i = 0; i < rows; i++){
                for(j = 0; j < cols; j++){
                    pcb.append('<span rel="'+j+'" class="light col-'+j+' row-'+i+'" id="c'+j+'-r'+i+'"></span>');
                }
            }

            var lightSize = 4; // width of light + margin
            led.append(pcb);
            pcb.css({width: (cols*lightSize)+1 +'px', height: rows*lightSize +'px'});
            led.css({width: displayWidth*lightSize +'px', height: rows*lightSize +'px'});
        };
        function draw_led(led,text_map)
        {
            for(i=0; i<text_map.length; i++){
                for(j=0; j<8; j++){
                    var exist = (text_map[i] == undefined) ? -1 : $.inArray( j, text_map[i] );
                    if(exist != undefined && exist >= 0){
                         $('#c'+i+'-r'+j,led).addClass('on');
                     }
                }
            }
        };
	};
})(jQuery);

var Utf8 = {

	// public method for url encoding
	encode : function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";

		for (var n = 0; n < string.length; n++) {

			var c = string.charCodeAt(n);

			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}

		}

		return utftext;
	},

	// public method for url decoding
	decode : function (utftext) {
		var string = "";
		var i = 0;
		var c = c1 = c2 = 0;

		while ( i < utftext.length ) {

			c = utftext.charCodeAt(i);

			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			}
			else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			}
			else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}

		}

		return string;
	}

}

/*
 * base64.js - Base64 encoding and decoding functions
 *
 * See: http://developer.mozilla.org/en/docs/DOM:window.btoa
 *      http://developer.mozilla.org/en/docs/DOM:window.atob
 *
 * Copyright (c) 2007, David Lindquist <david.lindquist@gmail.com>
 * Released under the MIT license
 */

if (typeof btoa == 'undefined') {
    function btoa(str) {
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        var encoded = [];
        var c = 0;
        while (c < str.length) {
            var b0 = str.charCodeAt(c++);
            var b1 = str.charCodeAt(c++);
            var b2 = str.charCodeAt(c++);
            var buf = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0);
            var i0 = (buf & (63 << 18)) >> 18;
            var i1 = (buf & (63 << 12)) >> 12;
            var i2 = isNaN(b1) ? 64 : (buf & (63 << 6)) >> 6;
            var i3 = isNaN(b2) ? 64 : (buf & 63);
            encoded[encoded.length] = chars.charAt(i0);
            encoded[encoded.length] = chars.charAt(i1);
            encoded[encoded.length] = chars.charAt(i2);
            encoded[encoded.length] = chars.charAt(i3);
        }
        return encoded.join('');
    }
}

if (typeof atob == 'undefined') {
    function atob(str) {
        str = str.replace(/[\r\n]/, '');
        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        var invalid = {
            strlen: (str.length % 4 != 0),
            chars:  new RegExp('[^' + chars + ']').test(str),
            equals: (/=/.test(str) && (/=[^=]/.test(str) || /={3}/.test(str)))
        };
        if (invalid.strlen || invalid.chars || invalid.equals)
            throw new Error('Invalid base64 data');
        var decoded = [];
        var c = 0;
        while (c < str.length) {
            var i0 = chars.indexOf(str.charAt(c++));
            var i1 = chars.indexOf(str.charAt(c++));
            var i2 = chars.indexOf(str.charAt(c++));
            var i3 = chars.indexOf(str.charAt(c++));
            var buf = (i0 << 18) + (i1 << 12) + ((i2 & 63) << 6) + (i3 & 63);
            var b0 = (buf & (255 << 16)) >> 16;
            var b1 = (i2 == 64) ? -1 : (buf & (255 << 8)) >> 8;
            var b2 = (i3 == 64) ? -1 : (buf & 255);
            decoded[decoded.length] = String.fromCharCode(b0);
            if (b1 >= 0) decoded[decoded.length] = String.fromCharCode(b1);
            if (b2 >= 0) decoded[decoded.length] = String.fromCharCode(b2);
        }
        return decoded.join('');
    }
}



