/*

maptable-1.0.3.js  (version 1.0.3)
by Keith Jenkins (kgj2  A_T  cornell  D_O_T  edu), 2010-01-19

This Javascript file creates a 'maptable' function, making it easier to add new basemaps and overlays to a Google Map.


DEPENDENCIES (load these first):
    Google Maps API v.2
    jQuery 1.3.2
    (and MapIconMaker 1.1, which is embedded below)


USAGE:
    <script type="text/javascript" src="http://www.google.com/jsapi?key=YOUR_MAPS_API_KEY"></script>
    <script type="text/javascript">
        google.load("maps", "2");
        google.load("jquery", "1.3.2");
    </script>
    <script type="text/javascript" src="maptable-0.4.0.js"></script>
    <script type="text/javascript">
    
        $(document).ready(function(){
            var options = {
                overlays: {
                    type: 'table',
                    url: 'SUNY_campuses.txt'
                }
            }
            var map = new google.maps.Map2(document.getElementById('map'));
            maptable(map, options);
        });
    </script>


MORE COMPLEX EXAMPLE OF OPTIONS:
    var campuses = {
        type: 'table',
        url: 'SUNY_campuses.txt',
        idColumn: 'ID',
        nameColumn: 'Name',
        typeColumn: 'Type',
        template: '<b>[Name]</b><br /><a href="[URL]">[URL]</a><p>[Type], [County] county</p>',
        filters: [ 'Type', 'County' ],
        style: {
            'Community College': { color:'0000ff' },
            'Technology College': { color:'00cc00' },
            'University': { color:'ffff00' },
            'University and Graduate School' : { color:'ff8800' }
        }
    }
    var counties = {
        type: 'kml',
        url: 'http://gmaps-maptable.googlecode.com/files/US.NY_county.kmz'
    };
    var earthquakes = {
        type: 'georss',
        url: 'http://earthquake.usgs.gov/eqcenter/catalogs/eqs7day-M5.xml',
        zoomTo: true
    };
    var weather = {
        type: 'wms',
        name: 'Weather',
        url: 'http://mesonet.agron.iastate.edu/cgi-bin/wms/goes/east_vis.cgi?',
        layers: 'east_vis_1km',
        credit: 'NOAA',
        opacity: 0.7
    };
    var options = {
        maptype: 'terrain',
        basemaps: [ weather ],
        overlays: [ campuses, counties, earthquakes ],
        maxzoom: 12
    }

*/

function maptable(map, options) {

    var mapDefaults = {
        basemaps: [],
        overlays: [],
        maptype: 'terrain',
        maxzoom: 20,
        ui: 'default'
    };

    var styleDefaults = {
        '': {
            color:'ff4444',
            size:32,
            style:'marker'
        }
    };

    var tableDefaults = {
        idColumn:'ID',
        nameColumn:'Name',
        latColumn:'Latitude',
        lngColumn:'Longitude',
        zoomTo: true,
        randomize:0,
        style: styleDefaults
    };

    var maptypes = {
        'roadmap': G_NORMAL_MAP,
        'satellite': G_SATELLITE_MAP,
        'hybrid': G_HYBRID_MAP,
        'terrain': G_PHYSICAL_MAP,
        '3d': G_SATELLITE_3D_MAP
    };

    var config = $.extend({}, mapDefaults, options);

    // Save the original page title
    $('body').data('origtitle', document.title);

    if (! map.isLoaded()) {
        map.setCenter(new GLatLng(0,0),10);
    }

    if (config.ui=='default') {
        map.setUIToDefault();
    }

    if (config.maptype=='3d') { // add 3D Google Earth maptype if requested TODO the markers don't show up!
        map.addMapType(maptypes['3d']);
    }
    map.setMapType(maptypes[config.maptype]);

    //map.enableContinuousZoom();
    map.__mt = { count:0, markers:{}, overlays:{}, config:config };
    GEvent.addListener(map, 'zoomend', function() {
        var max = config.maxzoom;
        if (map.getZoom()>max) {
            map.setZoom(max);
        }
    });

    // Add any basemaps to the map
    var basemaps = config.basemaps;
    if (basemaps && ! $.isArray(basemaps)) basemaps = [basemaps];
    for (var i=0; i<basemaps.length; i++) {
        var b = basemaps[i];
        var wms = wmsTileLayer(b);
        var name = b.name || 'Custom';
        if (! b.base || ! maptypes[b.base]) {
            b.base = 'satellite';
        }
        var baseLayer = maptypes[b.base].getTileLayers()[0];
        var myMapTypeLayers = [ baseLayer, wms ];
        var myCustomMapType = new GMapType(myMapTypeLayers, G_SATELLITE_MAP.getProjection(), name, G_SATELLITE_MAP);
        map.addMapType(myCustomMapType);
        if (b.active) map.setMapType(myCustomMapType);     
    }

    // Add any overlays to the map
    var overlays = config.overlays;
    if (overlays && ! $.isArray(overlays)) overlays = [overlays];
    for (var i=0; i<overlays.length; i++) {
        var o = overlays[i];
        if (! o.url) continue;
        if (o.type=='kml' || o.type=='georss') {
            var x = new GGeoXml(o.url);
            map.addOverlay(x);
            if (o.zoomTo) {
                x.gotoDefaultViewport(map);
                map.__mt.bounds = map.getBounds();
            }
        }
        else if (o.type=='wms') {
            var wms = wmsTileLayer(o);
            wms.baseURL = o.url;
            if (o.layers) wms.layers = o.layers;
            if (o.opacity) wms.opacity = o.opacity;
            map.addOverlay(new GTileLayerOverlay(wms));
        }
        else if (o.type=='table') {
            o = $.extend({}, tableDefaults, o);

            // prepare styles
            var s = o.style;
            s[''] = $.extend({}, styleDefaults[''], s['']);
            for (key in s) {
                var sk = s[key];
                sk = $.extend({}, s[''], sk);
                sk.height |= sk.size;
                sk.width |= sk.size;
                sk.count = 0;
                o.style[key] = sk;
            }
            
            // prepare filters
            var f = o.filters;
            if (f && $.isArray(f)) {
                var f2 = {};
                for (var j=0; j<f.length; j++) {
                    f2[f[j]] = { label:f[j] }            
                }
                o.filters = f2;
            }

            overlays[i] = o;
            $.get(o.url, gotTable, 'text');
        }
    }







    function gotTable(text) {
        // Find the matching overlay
        for (var i=0; i<overlays.length; i++) {
            if (overlays[i].url==this.url) {
                o = overlays[i];
                break;
            }
        }

        // Ingest the tab-delimited data table
        var rows = text.split(/[\x0d\x0a]+/);
        var columnNames = rows[0].split("\t");
                    
        // Process each row as a record
        for (var i=1; i<rows.length; i++) {
            var data = {};
            var cols = rows[i].replace(/[\x0d\x0a]/, '').split("\t");
            if (cols.length<columnNames.length) continue; // skip non-data rows
            for (var c=0; c<cols.length; c++) {
                var val = cols[c].replace(/^"(.*)"$/, "$1"); // remove quotes from text-formatted cells
                data[columnNames[c]] = val;
            }
            if (data[o.idColumn]) {
                var id = data[o.idColumn].replace(/\W+/g, '_'); // internal id suitable for anchors and URL links
            }
            else {
                var id = 'ID' + i; // create an id if idColumn is not defined
            }
            var name = data[o.nameColumn] || id;
            var type = data[o.typeColumn] || null;
            var lat = data[o.latColumn];
            var lng = data[o.lngColumn];
            if (lat!=0 && lng!=0) { // make sure coordinates are non-zero
                if (o.randomize>0) { // random offset (optional)
                    lat = 1*lat + 2*o.randomize*(Math.random()-0.5);
                    lng = 1*lng + 2*o.randomize*(Math.random()-0.5);
                }
                var latlng = new GLatLng(lat, lng);
                if (! o.style[type]) {
                    type = '';
                }
                s = o.style[type];
                o.style[type].count += 1; // count type styles (for legend)

                // Create the icon
                if (s.image) { // image icon?
                    var icon = new GIcon(G_DEFAULT_ICON);
                    icon.image = s.image;
                    icon.shadow = null;
                    icon.iconSize = new GSize(s.width, s.height);
                    icon.shadowSize = icon.iconSize;
                    icon.iconAnchor = new GPoint(s.width/2, s.height);
                    icon.infoWindowAnchor = new GPoint(s.width/2, s.height*2/3);
                }
                else if (s.style=='flat') { // flat icon?
                    var icon = MapIconMaker.createFlatIcon({
                        width: s.width,
                        height: s.height,
                        primaryColor: s.color,
                        shadowColor:'000000'
                    });
                    s.image = icon.image; // remember this image url
                }
                else { // default marker icon?
                    var icon = MapIconMaker.createMarkerIcon({
                        width: (s.width),
                        height: (s.height),
                        primaryColor: s.color,
                        cornerColor: 'ffffff',
                        strokeColor: '000000'
                    });
                    icon.infoWindowAnchor = new GPoint(s.width/2, s.height/2);
                }
                s.url = icon.image; // remember this image url
                var marker = new GMarker(latlng, { icon:icon, title:name });
                marker.__mt = { // internal data for later reference
                    data:data,
                    id: id,
                    name: name,
                    type: type,
                    lat: lat,
                    lng: lng,
                    overlay: o,
                    style: s
                };
                GEvent.addListener(marker, 'click', function() {
                    selectPoint(this.__mt.id);
                });
                map.addOverlay(marker);
                map.__mt.markers[id] = marker; // store a list of markers in the map
                map.__mt.count++; // TODO is this needed?

                if (o.zoomTo) {
                    // Expand the map bounds so that it contains all the points
                    if (! map.__mt.bounds) {
                        map.__mt.bounds = new GLatLngBounds(latlng, latlng);
                    }
                    else {
                        map.__mt.bounds.extend(latlng);
                    }
                }
            }
        }
        if (o.zoomTo && map.__mt && map.__mt.bounds) {
            map.setCenter(map.__mt.bounds.getCenter(),map.getBoundsZoomLevel(map.__mt.bounds));
        }
        showLegend(o);
        showList(o);
        interpretHash();
        showFilterForm(o);
    }

    function selectPoint(id) {
        id = id.replace(/\W+/g,'_');
        location.replace(location.href.replace(/#.*$/, '') + '#' + id);
        window.setTimeout(interpretHash, 10);
    }

    function interpretHash() {
        // Examine the hash of the URL (after the '#')
        // if numeric, show the point for that ID
        // if var=val, filter accordingly
        map.closeInfoWindow();
        var hash = location.hash.substr(1);
        if (hash.search(/=/) > -1) { // #var=val => filter
            var c = hash.split('=');
            var column = c[0];
            var value = c[1];
            if (column && value) {
                document.title = $('body').data('origtitle') + ": " + column + "=" + value;
                config.filter = { column:column, value:value }; // what about multiple filters?
                var count = map.__mt.count;
                for (var id in map.__mt.markers) {
                    var m = map.__mt.markers[id];
                    if (m.__mt.data[column]==value || column=='') {
                        m.show(); // show map marker
                        $('#maptable-list-'+id).show(); // show name in list
                        var latlng = new GLatLng(m.__mt.lat, m.__mt.lng);
                        if (!bounds) {
                            var bounds = new GLatLngBounds(latlng, latlng);
                        }
                        else {
                            bounds.extend(latlng);
                        }
                    }
                    else {
                        m.hide(); // hide map marker
                        $('#maptable-list-'+id).hide(); // hide name in list
                        count -= 1;
                    }
                }
                if (count>0) {
                    $('#maptable-list h2').html(count + " point" + (count!=1 ? 's' : '') + ":");
                    var z = map.getBoundsZoomLevel(bounds);
                    if (z>config.maxzoom) {
                        z = config.maxzoom;
                    }
                    map.setCenter(bounds.getCenter(), z);
                    return;
                }
            }
        }
        else if (hash.length>0) { // #id
            // Show info for the current point (as specified by the id in the URL hash)
            var id = hash;
            var marker = map.__mt.markers[id];
            if (marker) {
                var name = marker.__mt.name;
                document.title = $('body').data('origtitle') + ": " + name;
                map.__mt.filter = { column:null, value:null };

                // Generate HTML for info pane
                var d = marker.__mt.data;
                if (marker.__mt.overlay.template) {
                    var html = marker.__mt.overlay.template;
                }
                else {
                    var html = '';
                    if (name) {
                        html += "<h1>" + name + "</h1>";
                    }
                    html += "<div class='section'>";
                    for (var v in d) {
                        html += "<b>"+v+"</b>: ["+v+"]<br />\n";
                    }
                    html += "</div>";
                }

                for (var v in d) {
                    // escape regexp special characters
                    var vsafe = v.replace(/([\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
                    var re = new RegExp('\\['+vsafe+'\\]', 'g');
                    if (d[v].length>0) {
                        if (d[v].substring(0,4)=='http') { // don't accidently put <span> within an <a href>
                            html = html.replace(re, d[v]);
                        }
                        else {
                            html = html.replace(re, '<span class="data">'+d[v]+'</span>');
                        }
                    }
                    else {
                        html = html.replace(re, '<span class="nodata"></span>');
                    }
                }
                // Remove "nodata" lines, but only if there are no other "data" on the line
                var lines = html.split(/[\n\r]+/);
                var html2 = '';
                for (var i=0; i<lines.length; i++) {
                    var line = lines[i];
                    if (line.search(/<span class="nodata"/)==-1 || line.search(/<span class="data"/)>-1) {
                        html2 += line + "\n";
                    }
                }

                // Create a temporary div, in order to use jQuery to remove any sections lacking data
                $("<div id='maptable-temp'>"+html2+"</div>").hide().appendTo('body');
                $('#maptable-temp div').each(function(){
                    if ($(this).find('span.data').length==0) {
                        $(this).remove();
                    }
                })
                var html = $('#maptable-temp').html();
                $('#maptable-temp').remove();

                // Display ballon on map
                marker.openInfoWindowHtml("<div class='maptable-balloon'>"+html+"</div>");

                window.scroll(0,0); // scroll to top of page
                return;
            }
        }
        // no hash or invalid hash -> Show everything
        document.title = $('body').data('origtitle');
        config.filter = { column:'', value:'' };
        var count = map.__mt.count;
        $('#maptable-list h2').html(count + " point" + (count!=1 ? 's' : '') + ":");
        for (var id in map.__mt.markers) {
            var m = map.__mt.markers[id];
            m.show();
            $('#maptable-list-'+id).show();
        }
        var b = map.__mt.bounds;
        map.setCenter(b.getCenter(), map.getBoundsZoomLevel(b));
    }


    function showFilterForm(o) {
        if (! o.filters) {
            $('#maptable-filter').hide();
            return;
        }
        var selectColumn = $("<select id='filterColumn'></select>");
        selectColumn.append("<option value=''>ALL SITES</option>");
        for (var f in o.filters) {
            selectColumn.append("<option value='"+f+"'>"+o.filters[f].label+"</option>");
        }
        selectColumn.change(function(){
            $('#filterValue').parent().remove();
            var c = $(this).val();
            if (c=='') {
                selectFilter();
                return;
            }

            // get list of all values in this column
            var seen = Array();
            for (var i in map.__mt.markers) {
                var val = map.__mt.markers[i].__mt.data[c];
                if (val!='') seen[val] = 1;
            }
            var vals = Array();
            for (var k in seen) {
                vals.push(k);
            }
            vals.sort();

            var selectVal = $("<select id='filterValue'></select>");
            selectVal.append("<option value=''>ALL</option>");
            for (var i in vals) {
                selectVal.append("<option>"+vals[i]+"</option>");
            }
            selectVal.change(selectFilter);
            $('<span> ...show:</span>').append(selectVal).appendTo('#maptable-filter');
        });
        $("#maptable-filter").append("Filter by:").append(selectColumn);
        if (!config.filter || ! config.filter.column) {
            config.filter = { column:o.filters[0], value:null };
        }
        else {
            $('#filterColumn').val(config.filter.column).change();
            $('#filterValue').val(config.filter.value);
        }
    }


    function selectFilter() {
        var column = $('#filterColumn').val();
        var value = $('#filterValue').val();
        if (column && value) {
            var hash = '#' + column + "=" + value;
        }
        else {
            var hash = '#';
        }
        location.replace(location.href.replace(/#.*$/, '') + hash);
        window.setTimeout(interpretHash, 100);
    }


    function showLegend(o) {
        var s = o.style;
        countTypes = 0;
        for (var t in s) {
            countTypes += 1;
        }
        if (! s || countTypes==1) { // supress trivial legends
            $('#maptable-legend').hide();
            return;
        }
        else {
            var html = '<ul>';
            for (var t in s) {
                if (s[t].count==0) continue; // skip type if no such markers
                var label = s[t].label;
                if (label==null) label = t ? t : "Other";
                html += "<li><img src='"+s[t].url+"' height='"+s[t].height+"' width='"+s[t].width+"'/>"+label+"</li>";
            }
            html += '</ul>';
            $('#maptable-legend').append(html);
        }
    }


    function showList(o) {
        var count = map.__mt.count;
        $('#maptable-list').html("<h2>" + count + " point" + (count!=1 ? 's' : '') + ":</h2><ul></ul>");
        function byName(a,b) {
            aa = a.__mt.name;
            bb = b.__mt.name;
            if (aa < bb) return -1;
            else if (aa > bb) return 1;
            else return 0;
        }
        var markers = map.__mt.markers;
        var list = [];
        for (var id in markers) {
            if (markers[id].__mt.overlay == o) {
                list.push(markers[id]);
            }
        }
        list.sort(byName);
        for (var i in list) {
            var m = list[i].__mt;
            $('#maptable-list ul').append("<li id='maptable-list-"+m.id+"'><img src='"+m.style.url+"' height='"+m.style.height+"' width='"+m.style.width+"' /><a href='#"+m.id+"'>"+m.name+"</a></li>");
        }
        $('#maptable-list a').click(function(){
            var id = $(this).attr("href").replace(/^.*#/, ''); // beware: the href value varies depending on the browser!
            selectPoint(id);
            return false;
        });
    }

    function debug(str) {
        $('#debug').append("<br />"+str);
    }





    function wmsTileLayer(options) {
        var credit = options.credit || '';
        var west = options.west || -180;
        var south = options.south || -90;
        var east = options.east || 180;
        var north = options.north || 90;
        var format = options.format || 'image/png';
        var opacity = options.opacity || 1.0;
        
        var myCopyright = new GCopyrightCollection(' ');
        myCopyright.addCopyright(new GCopyright(1, new GLatLngBounds(new GLatLng(west,south), new GLatLng(east,north)), 0, credit));
        var wms = new GTileLayer(myCopyright);
        wms. getTileUrl = function(point, zoom){
            var proj = G_SATELLITE_MAP.getProjection();
            var tileSize = G_SATELLITE_MAP.getTileSize();
            var ulPixel = new GPoint(point.x*tileSize, (point.y+1)*tileSize);
            var lrPixel = new GPoint((point.x+1)*tileSize, point.y*tileSize);
            var ul = proj.fromPixelToLatLng(ulPixel, zoom);
            var lr = proj.fromPixelToLatLng(lrPixel, zoom);

            var url = options.url;
            url += "&REQUEST=GetMap";
            url += "&SERVICE=WMS";
            url += "&VERSION=1.1.1";
            if (options.layers) url += "&LAYERS=" + options.layers;
            if (options.styles) url += "&STYLES=" + options.styles; 
            if (options.sld) url += "&SLD=" + options.sld; 
            url += "&FORMAT=" + format;
            url += "&BGCOLOR=0xFFFFFF";
            url += "&TRANSPARENT=TRUE";
            url += "&SRS=" + 'EPSG:4326';
            url += "&BBOX=" + ul.lng() + ',' + ul.lat() + ',' + lr.lng() + ',' + lr.lat();
            url += "&WIDTH=" + tileSize;
            url += "&HEIGHT=" + tileSize;
            return url;
        };
        wms.isPng = function(){ return format == 'image/png'; };
        wms.getOpacity = function(){ return opacity; };
        return wms;
    }



};





//---------------------------------------------------------------------------
// BEGIN CODE FROM MapIconMaker
// http://gmaps-utility-library-dev.googlecode.com/svn/tags/mapiconmaker/1.1/src/mapiconmaker.js


/**
 * @name MapIconMaker
 * @version 1.1
 * @author Pamela Fox
 * @copyright (c) 2008 Pamela Fox
 * @fileoverview This gives you static functions for creating dynamically
 *     sized and colored marker icons using the Charts API marker output.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

/**
 * @name MarkerIconOptions
 * @class This class represents optional arguments to {@link createMarkerIcon}, 
 *     {@link createFlatIcon}, or {@link createLabeledMarkerIcon}. Each of the
 *     functions use a subset of these arguments. See the function descriptions
 *     for the list of supported options.
 * @property {Number} [width=32] Specifies, in pixels, the width of the icon.
 *     The width may include some blank space on the side, depending on the
 *     height of the icon, as the icon will scale its shape proportionately.
 * @property {Number} [height=32] Specifies, in pixels, the height of the icon.
 * @property {String} [primaryColor="#ff0000"] Specifies, as a hexadecimal
 *     string, the color used for the majority of the icon body.
 * @property {String} [cornerColor="#ffffff"] Specifies, as a hexadecimal
 *     string, the color used for the top corner of the icon. If you'd like the
 *     icon to have a consistent color, make the this the same as the
 *     {@link primaryColor}.
 * @property {String} [strokeColor="#000000"] Specifies, as a hexadecimal
 *     string, the color used for the outside line (stroke) of the icon.
 * @property {String} [shadowColor="#000000"] Specifies, as a hexadecimal
 *     string, the color used for the shadow of the icon. 
 * @property {String} [label=""] Specifies a character or string to display
 *     inside the body of the icon. Generally, one or two characters looks best.
 * @property {String} [labelColor="#000000"] Specifies, as a hexadecimal 
 *     string, the color used for the label text.
 * @property {Number} [labelSize=0] Specifies, in pixels, the size of the label
 *     text. If set to 0, the text auto-sizes to fit the icon body.
 * @property {String} [shape="circle"] Specifies shape of the icon. Current
 *     options are "circle" for a circle or "roundrect" for a rounded rectangle.
 * @property {Boolean} [addStar = false] Specifies whether to add a star to the
 *     edge of the icon.
 * @property {String} [starPrimaryColor="#FFFF00"] Specifies, as a hexadecimal
 *     string, the color used for the star body.
 * @property {String} [starStrokeColor="#0000FF"] Specifies, as a hexadecimal
 *     string, the color used for the outside line (stroke) of the star.
 */

/**
 * This namespace contains functions that you can use to easily create
 *     dynamically sized, colored, and labeled icons.
 * @namespace
 */
var MapIconMaker = {};

/**
 * Creates an icon based on the specified options in the 
 *   {@link MarkerIconOptions} argument.
 *   Supported options are: width, height, primaryColor, 
 *   strokeColor, and cornerColor.
 * @param {MarkerIconOptions} [opts]
 * @return {GIcon}
 */
MapIconMaker.createMarkerIcon = function (opts) {
  var width = opts.width || 32;
  var height = opts.height || 32;
  var primaryColor = opts.primaryColor || "#ff0000";
  var strokeColor = opts.strokeColor || "#000000";
  var cornerColor = opts.cornerColor || "#ffffff";
   
  var baseUrl = "http://chart.apis.google.com/chart?cht=mm";
  var iconUrl = baseUrl + "&chs=" + width + "x" + height + 
      "&chco=" + cornerColor.replace("#", "") + "," + 
      primaryColor.replace("#", "") + "," + 
      strokeColor.replace("#", "") + "&ext=.png";
  var icon = new GIcon(G_DEFAULT_ICON);
  icon.image = iconUrl;
  icon.iconSize = new GSize(width, height);
  icon.shadowSize = new GSize(Math.floor(width * 1.6), height);
  icon.iconAnchor = new GPoint(width / 2, height);
  icon.infoWindowAnchor = new GPoint(width / 2, Math.floor(height / 12));
  icon.printImage = iconUrl + "&chof=gif";
  icon.mozPrintImage = iconUrl + "&chf=bg,s,ECECD8" + "&chof=gif";
  iconUrl = baseUrl + "&chs=" + width + "x" + height + 
      "&chco=" + cornerColor.replace("#", "") + "," + 
      primaryColor.replace("#", "") + "," + 
      strokeColor.replace("#", "");
  icon.transparent = iconUrl + "&chf=a,s,ffffff11&ext=.png";

  icon.imageMap = [
    width / 2, height,
    (7 / 16) * width, (5 / 8) * height,
    (5 / 16) * width, (7 / 16) * height,
    (7 / 32) * width, (5 / 16) * height,
    (5 / 16) * width, (1 / 8) * height,
    (1 / 2) * width, 0,
    (11 / 16) * width, (1 / 8) * height,
    (25 / 32) * width, (5 / 16) * height,
    (11 / 16) * width, (7 / 16) * height,
    (9 / 16) * width, (5 / 8) * height
  ];
  for (var i = 0; i < icon.imageMap.length; i++) {
    icon.imageMap[i] = parseInt(icon.imageMap[i]);
  }

  return icon;
};


/**
 * Creates a flat icon based on the specified options in the 
 *     {@link MarkerIconOptions} argument.
 *     Supported options are: width, height, primaryColor,
 *     shadowColor, label, labelColor, labelSize, and shape..
 * @param {MarkerIconOptions} [opts]
 * @return {GIcon}
 */
MapIconMaker.createFlatIcon = function (opts) {
  var width = opts.width || 32;
  var height = opts.height || 32;
  var primaryColor = opts.primaryColor || "#ff0000";
  var shadowColor = opts.shadowColor || "#000000";
  var label = MapIconMaker.escapeUserText_(opts.label) || "";
  var labelColor = opts.labelColor || "#000000";
  var labelSize = opts.labelSize || 0;
  var shape = opts.shape ||  "circle";
  var shapeCode = (shape === "circle") ? "it" : "itr";

  var baseUrl = "http://chart.apis.google.com/chart?cht=" + shapeCode;
  var iconUrl = baseUrl + "&chs=" + width + "x" + height + 
      "&chco=" + primaryColor.replace("#", "") + "," + 
      shadowColor.replace("#", "") + "ff,ffffff01" +
      "&chl=" + label + "&chx=" + labelColor.replace("#", "") + 
      "," + labelSize;
  var icon = new GIcon(G_DEFAULT_ICON);
  icon.image = iconUrl + "&chf=bg,s,00000000" + "&ext=.png";
  icon.iconSize = new GSize(width, height);
  icon.shadowSize = new GSize(0, 0);
  icon.iconAnchor = new GPoint(width / 2, height / 2);
  icon.infoWindowAnchor = new GPoint(width / 2, height / 2);
  icon.printImage = iconUrl + "&chof=gif";
  icon.mozPrintImage = iconUrl + "&chf=bg,s,ECECD8" + "&chof=gif";
  icon.transparent = iconUrl + "&chf=a,s,ffffff01&ext=.png";
  icon.imageMap = []; 
  if (shapeCode === "itr") {
    icon.imageMap = [0, 0, width, 0, width, height, 0, height];
  } else {
    var polyNumSides = 8;
    var polySideLength = 360 / polyNumSides;
    var polyRadius = Math.min(width, height) / 2;
    for (var a = 0; a < (polyNumSides + 1); a++) {
      var aRad = polySideLength * a * (Math.PI / 180);
      var pixelX = polyRadius + polyRadius * Math.cos(aRad);
      var pixelY = polyRadius + polyRadius * Math.sin(aRad);
      icon.imageMap.push(parseInt(pixelX), parseInt(pixelY));
    }
  }

  return icon;
};


/**
 * Creates a labeled marker icon based on the specified options in the 
 *     {@link MarkerIconOptions} argument.
 *     Supported options are: primaryColor, strokeColor, 
 *     starPrimaryColor, starStrokeColor, label, labelColor, and addStar.
 * @param {MarkerIconOptions} [opts]
 * @return {GIcon}
 */
MapIconMaker.createLabeledMarkerIcon = function (opts) {
  var primaryColor = opts.primaryColor || "#DA7187";
  var strokeColor = opts.strokeColor || "#000000";
  var starPrimaryColor = opts.starPrimaryColor || "#FFFF00";
  var starStrokeColor = opts.starStrokeColor || "#0000FF";
  var label = MapIconMaker.escapeUserText_(opts.label) || "";
  var labelColor = opts.labelColor || "#000000";
  var addStar = opts.addStar || false;
  
  var pinProgram = (addStar) ? "pin_star" : "pin";
  var baseUrl = "http://chart.apis.google.com/chart?cht=d&chdp=mapsapi&chl=";
  var iconUrl = baseUrl + pinProgram + "'i\\" + "'[" + label + 
      "'-2'f\\"  + "hv'a\\]" + "h\\]o\\" + 
      primaryColor.replace("#", "")  + "'fC\\" + 
      labelColor.replace("#", "")  + "'tC\\" + 
      strokeColor.replace("#", "")  + "'eC\\";
  if (addStar) {
    iconUrl += starPrimaryColor.replace("#", "") + "'1C\\" + 
        starStrokeColor.replace("#", "") + "'0C\\";
  }
  iconUrl += "Lauto'f\\";

  var icon = new GIcon(G_DEFAULT_ICON);
  icon.image = iconUrl + "&ext=.png";
  icon.iconSize = (addStar) ? new GSize(23, 39) : new GSize(21, 34);
  return icon;
};


/**
 * Utility function for doing special chart API escaping first,
 *  and then typical URL escaping. Must be applied to user-supplied text.
 * @private
 */
MapIconMaker.escapeUserText_ = function (text) {
  if (text === undefined) {
    return null;
  }
  text = text.replace(/@/, "@@");
  text = text.replace(/\\/, "@\\");
  text = text.replace(/'/, "@'");
  text = text.replace(/\[/, "@[");
  text = text.replace(/\]/, "@]");
  return encodeURIComponent(text);
};


// END CODE FROM MapIconMaker
//---------------------------------------------------------------------------





