<commit_before>/**
 * Map module that wraps a Google Map and gives it the ability to handle app 
 * level events and perform AJAX calls to the server. It surfaces custom
 * map controls with predefined slots. 
 * 
 * Event binding:
 *     ADD_MAP_CONTROL - Adds a control to the map.
 *     ADD_LAYER - Displays the layer on the map.
 * 
 * Event triggering:
 *     None
 */
MOL.modules.Map = function(mol) { 
    
    mol.ui.Map = {};
    
    /**
     * The Map Engine.
     */
    mol.ui.Map.Engine = mol.ui.Engine.extend(
        {
            /**
             * Constucts a new Map Engine.
             *
             * @param api the mol.ajax.Api for server communication
             * @param bus the mol.events.Bus for event handling 
             * @constructor
             */
            init: function(api, bus) {
                this._api = api;
                this._bus = bus;  
                this._points = {};
                this._layers = {};
                this._controlDivs = {};
                this._currentMapTypeIndex = 0;
                this._mapTypeIndexes = {};
                this._mapTypes = {};
            },            
            
            /**
             * Starts the engine and provides a container for its display.
             * 
             * @param container the container for the engine display 
             * @override mol.ui.Engine.start
             */
            start: function(container) {
                var MarkerCanvas = mol.ui.Map.MarkerCanvas;

                this._bindDisplay(new mol.ui.Map.Display(), container);

                this._markerCanvas = new MarkerCanvas(15, 15);

                this._addMapControlEventHandler();
                this._addLayerEventHandler();
                this._addColorEventHandler();
            },

            /**
             * Gives the engine a new place to go based on a browser history
             * change.
             * 
             * @param place the place to go
             * @override mol.ui.Engine.go
             */
            go: function(place) {
                mol.log.todo('Map.Engine.go()');
            },

            _bindDisplay: function(display, container) {
                this._display = display;
                display.setEngine(this);                

                container.append(display.getElement());
                
                this._map = display.getMap();

                this._addControls();
            },

            _addControls: function() {
                var map = this._map,
                    controls = map.controls,
                    ControlPosition = google.maps.ControlPosition,
                    TOP_RIGHT = ControlPosition.TOP_RIGHT,
                    TOP_CENTER = ControlPosition.TOP_CENTER,
                    BOTTOM_LEFT = ControlPosition.BOTTOM_LEFT,
                    TOP_LEFT = ControlPosition.TOP_LEFT,
                    Control = mol.ui.Map.Control;
                
                this._rightControl = new Control('RightControl');
                controls[TOP_RIGHT].clear();
                controls[TOP_RIGHT].push(this._rightControl.getDiv());
                                
                this._centerTopControl = new Control('CenterTopControl');
                controls[TOP_CENTER].clear();
                controls[TOP_CENTER].push(this._centerTopControl.getDiv());

                this._leftTopControl = new Control('TopLeftControl');
                controls[TOP_LEFT].clear();
                controls[TOP_LEFT].push(this._leftTopControl.getDiv());  
                
                this._leftBottomControl = new Control('LeftBottomControl');
                controls[BOTTOM_LEFT].clear();
                controls[BOTTOM_LEFT].push(this._leftBottomControl.getDiv());                
            },

            /**
             * Adds an event handler for new layers.
             */
            _addLayerEventHandler: function() {
                var bus = this._bus,
                    LayerEvent = mol.events.LayerEvent,
                    ColorEvent = mol.events.ColorEvent,
                    deleteLayer = this._deleteLayer,
                    layers = this._layers,
                    self = this;
                
                bus.addHandler(
                    LayerEvent.TYPE,
                    function(event) {
                        var layer = event.getLayer(),
                            lid = layer.getId(),
                            action = event.getAction(),
                            config = {
                                action: 'get',
                                category: layer.getType(),
                                id: lid
                            };
                                                
                        switch (action) {

                        case 'add':
                            if (layers[lid]) {
                                mol.log.info('Map ignoring layer id ' + lid);
                                return;
                            }                            
                            layers[lid] = layer;
                            bus.fireEvent(new ColorEvent(config));
                            break;

                        case 'delete':
                            deleteLayer(lid);
                            break;

                        case 'checked':
                            self._toggleLayer(layers[lid], true);
                            break;                            
                        case 'unchecked':
                            self._toggleLayer(layers[lid], false);
                            break;                            
                        }                        
                    }
                );
            },
            
            /**
             * Adds an event handler so that displays can be added to the map as
             * controls simply by firing a MapControlEvent.
             */
            _addMapControlEventHandler: function() {
                var bus = this._bus,
                    MapControlEvent = mol.events.MapControlEvent,
                    controls = this._map.controls,
                    controlDivs = this._controlDivs,
                    ControlPosition = mol.ui.Map.Control.ControlPosition,
                    TOP_RIGHT = ControlPosition.TOP_RIGHT,
                    TOP_CENTER = ControlPosition.TOP_CENTER,
                    BOTTOM_LEFT = ControlPosition.BOTTOM_LEFT,
                    TOP_LEFT = ControlPosition.TOP_LEFT,
                    topRightControl = this._rightControl,
                    leftTopControl = this._leftTopControl,
                    centerTopControl = this._centerTopControl,
                    leftBottomControl = this._leftBottomControl;
                                
                bus.addHandler(
                    MapControlEvent.TYPE,
                    function(event) {
                        var action = event.getAction(),
                            display = event.getDisplay(),
                            controlPosition = event.getControlPosition(),
                            displayPosition = event.getDisplayPosition(),
                            control = null;

                        switch (action) {

                        case 'add':
                            switch (controlPosition) {
                                
                            case TOP_RIGHT:
                                control = topRightControl;
                                break;
                                
                            case TOP_CENTER:
                                control = centerTopControl;
                                break;

                            case TOP_LEFT:
                                control = leftTopControl;
                                break;

                            case BOTTOM_LEFT:
                                control = leftBottomControl;
                                break;
                            }
                            control.addDisplay(display, displayPosition);
                            break;

                        case 'remove':
                            // TODO: Remove custom map control.
                            mol.log.todo('Remove custom map control');
                            break;                            
                        }
                    }
                );
            },

            _addColorEventHandler: function() {
                var ColorEvent = mol.events.ColorEvent,
                    bus = this._bus,
                    points = this._points,
                    layers = this._layers,
                    self = this,
                    map = this._map,
                    overlayMapTypes = this._map.overlayMapTypes,
                    mapTypes = this._mapTypes,
                    mapTypeIndexes = this._mapTypeIndexes,
                    currentMapTypeIndex = this._currentMapTypeIndex,
                    mapType = null;

                bus.addHandler(
                    ColorEvent.TYPE,
                    function(event) {
                        var color = event.getColor(),
                            category = event.getCategory(),
                            layerId = event.getId(),
                            layer = layers[layerId],
                            action = event.getAction();

                        // Ignores event since we don't have the layer associated with it:
                        if (!layer) {
                            return;
                        }
                        
                        // Sets the layer color:
                        layer.setColor(color);

                        switch (action) {

                        case 'change':
                            switch (category) {
                                
                            case 'points':
                                // Gets the point icon based on color and either
                                // creates the points with new icons or updates 
                                // icons of existing points.
                                self._getPointIcon(
                                    color,
                                    function(icon) {
                                        layer.setIcon(icon);                                    
                                        if (!points[layerId]) {
                                            self._createPoints(layer);    
                                        } else {
                                            self._updateLayerColor(layer);
                                        }
                                    }
                                );
                                break;

                            case 'range':
                                mapType = self._rangeImageMapType(layer, color);

                                if (mapTypes[layerId]) {
                                    // The mapType exists, so remove it first:
                                    overlayMapTypes.removeAt(mapTypeIndexes[layerId]);                            
                                } else {
                                    // This is a new mapType, so let us keep track of it:
                                    mapTypes[layerId] = mapType;
                                }

                                // Adds the mapType to the map:
                                overlayMapTypes.insertAt(
                                    self._currentMapTypeIndex, 
                                    mapType);
                                
                                // Updates the index of the mapType on the map:
                                mapTypeIndexes[layerId] = currentMapTypeIndex;
                                currentMapTypeIndex = currentMapTypeIndex + 1;
                                break;                                
                            }                           
                        }
                    }
                );                
            },
            
            _updateLayer: function(layer) {
                _updateLayerColor(layer);
            },

            _updateLayerColor: function(layer) {
                var points = this._points[layer.getId()],
                    type = layer.getType(),
                    urls = this._getIconUrls(layer.getIcon()),
                    markerCanvas = this._markerCanvas,
                    iconUrl = urls.iconUrl,
                    w = markerCanvas.getIconWidth(),
                    h = markerCanvas.getIconHeight(),
                    point = null,
                    MarkerImage = google.maps.MarkerImage,
                    Size = google.maps.Size,
                    Marker = google.maps.Marker,
                    image = new MarkerImage(iconUrl, new Size(w, h));
                
                switch (type) {

                case 'points':
                    for (x in points) {
                        point = points[x];
                        if (point instanceof Marker) {
                            point.setIcon(image);
                        }                        
                    }
                    break;

                case 'range':
                    // TODO
                    break;

                }
            },

            _getPointIcon: function(color, callback) {
                var icon = new Image(),
                    src = '/test/colorimage/pm-color.png?'
                          + 'r=' + color.getRed() 
                          + '&g=' + color.getGreen() 
                          + '&b=' + color.getBlue();                
                icon.onload = function() {
                    callback(icon);
                };                
                icon.src = src;
            },

            /**
             * Deletes a layer from the map.
             * 
             * @param layerId the id of the layer to delete
             */
            _deleteLayer: function(layerId) {
                var points = this._points[layerId];
                for (x in points) {
                    points[x].setMap(null);
                    delete points[x];
                }
                delete this._points[layerId];
                this._points[layerId] = null;
            },

            /**
             * Displays the layer on the map.
             * 
             * @param the layer to display
             */
            _toggleLayer: function(layer, show) {
                var lid = layer.getId(),
                    index = this._mapTypeIndexes[lid],
                    type = layer.getType(),
                    points = this._points[lid],
                    overlayMapTypes = this._map.overlayMapTypes,
                    map = show ? this._map : null,
                    mapType = this._mapTypes[lid];

                switch (type) {

                case 'points':
                    for (x in points) {
                        points[x].setMap(map);
                    }
                    break;
                    
                case 'range':
                    if (show) {
                        overlayMapTypes.insertAt(index, mapType);
                    } else {
                        overlayMapTypes.removeAt(index);                            
                    }
                    break;
                }
            },            

            _getIconUrls: function(icon) {
                var markerCanvas = this._markerCanvas,
                    canvasSupport = markerCanvas.canvasSupport(),
                    icons = markerCanvas.getIcons(),
                    background = icons.background,
                    foreground = icons.foreground,
                    error = icons.error,
                    ctx = markerCanvas.getContext(),
                    w = markerCanvas.getIconWidth(),
                    h = markerCanvas.getIconHeight(),
                    url = null,
                    errorUrl = null;
                if (!canvasSupport) {
                    return {iconUrl: icon.src, iconErrorUrl: icon.src};
                }
                ctx.drawImage(background, 0, 0, w, h);
                ctx.drawImage(icon, 0, 0, w, h);
                ctx.drawImage(foreground, 0, 0, w, h);
                url = markerCanvas.getDataURL();
                ctx.drawImage(error, 0, 0, w, h);
                errorUrl = markerCanvas.getDataURL();
                return {iconUrl: url, iconErrorUrl: errorUrl};
            },

            _createPoints: function(layer) {
                var lid = layer.getId(),
                    center = null,
                    marker = null,
                    circle = null,
                    coordinate = null,
                    resources = [],
                    occurrences = [],
                    data = layer._json,
                    icon = layer.getIcon(),
                    urls = this._getIconUrls(icon),
                    iconUrl = urls.iconUrl,
                    iconErrorUrl = urls.iconErrorUrl;
                this._points[lid] = [];
                for (p in data.records.publishers) {
                    resources = data.records.publishers[p].resources;
                    for (r in resources) {
                        occurrences = resources[r].occurrences;
                        for (o in occurrences) {
                            coordinate = occurrences[o].coordinates;
                            marker = this._createMarker(coordinate, iconUrl);
                            this._points[lid].push(marker);                      
                            circle = this._createCircle(
                                marker.getPosition(),
                                coordinate.coordinateUncertaintyInMeters);                            
                            if (circle) {
                                this._points[lid].push(circle);
                            }     
                        }
                    }
                }
            },

            /**
             * Private function that creates a Google circle object.
             * 
             * @param center the center LatLng of the circle
             * @param coordinateUncertaintyInMeters the circle radius
             * @return a new Google circle object
             */
            _createCircle: function(center, coordinateUncertaintyInMeters) {          
                if (coordinateUncertaintyInMeters == null) {
                    return null;
                }
                var map = this._display.getMap(),
                    radius = parseFloat(coordinateUncertaintyInMeters),
                    opacity = 0.85,
                    circle = new google.maps.Circle(
                        {
                            map: map,
                            center: center,
                            radius: radius,
                            fillColor: '#CEE3F6',
                            strokeWeight: 1,                                
                            zIndex: 5
                        }
                    );
                return circle;
            },
            
            /**
             * Private function that creates a Google marker object.
             * 
             * @param coordinate the coordinate longitude and latitude
             * @return a new Google marker object
             */
            _createMarker: function(coordinate, iconUrl) {
                var map = this._display.getMap(),
                    lat = parseFloat(coordinate.decimalLatitude),
                    lng = parseFloat(coordinate.decimalLongitude),
                    center = new google.maps.LatLng(lat, lng),
                    w = this._markerCanvas.getIconHeight(),
                    h = this._markerCanvas.getIconWidth(),
                    MarkerImage = google.maps.MarkerImage,
                    Size = google.maps.Size,
                    Marker = google.maps.Marker,
                    image = new MarkerImage(iconUrl, new Size(w, h)),
                    marker = new Marker(
                        { 
                            position: center,
                            map: map,
                            icon: image
                        }
                    );
                return marker;
            },

            _rangeImageMapType: function(layer, color) {   
                var self = this,
                    params = {                        
                        name: layer.getName().toLowerCase().replace(' ', '_'),
                        rank: 'species',
                        type: 'range',
                        class: 'animalia',
                        r: color.getRed(),
                        g: color.getGreen(),
                        b: color.getBlue()
                    };

                return new google.maps.ImageMapType(
                    {
                        getTileUrl: function(coord, zoom) {
                            var normalizedCoord = self._getNormalizedCoord(coord, zoom);<commit_msg>Surfacing some initial ecoregion layer support<commit_after>/**
 * Map module that wraps a Google Map and gives it the ability to handle app 
 * level events and perform AJAX calls to the server. It surfaces custom
 * map controls with predefined slots. 
 * 
 * Event binding:
 *     ADD_MAP_CONTROL - Adds a control to the map.
 *     ADD_LAYER - Displays the layer on the map.
 * 
 * Event triggering:
 *     None
 */
MOL.modules.Map = function(mol) { 
    
    mol.ui.Map = {};
    
    /**
     * The Map Engine.
     */
    mol.ui.Map.Engine = mol.ui.Engine.extend(
        {
            /**
             * Constucts a new Map Engine.
             *
             * @param api the mol.ajax.Api for server communication
             * @param bus the mol.events.Bus for event handling 
             * @constructor
             */
            init: function(api, bus) {
                this._api = api;
                this._bus = bus;  
                this._points = {};
                this._layers = {};
                this._controlDivs = {};
                this._currentMapTypeIndex = 0;
                this._mapTypeIndexes = {};
                this._mapTypes = {};
            },            
            
            /**
             * Starts the engine and provides a container for its display.
             * 
             * @param container the container for the engine display 
             * @override mol.ui.Engine.start
             */
            start: function(container) {
                var MarkerCanvas = mol.ui.Map.MarkerCanvas;

                this._bindDisplay(new mol.ui.Map.Display(), container);

                this._markerCanvas = new MarkerCanvas(15, 15);

                this._addMapControlEventHandler();
                this._addLayerEventHandler();
                this._addColorEventHandler();
            },

            /**
             * Gives the engine a new place to go based on a browser history
             * change.
             * 
             * @param place the place to go
             * @override mol.ui.Engine.go
             */
            go: function(place) {
                mol.log.todo('Map.Engine.go()');
            },

            _bindDisplay: function(display, container) {
                this._display = display;
                display.setEngine(this);                

                container.append(display.getElement());
                
                this._map = display.getMap();

                this._addControls();
            },

            _addControls: function() {
                var map = this._map,
                    controls = map.controls,
                    ControlPosition = google.maps.ControlPosition,
                    TOP_RIGHT = ControlPosition.TOP_RIGHT,
                    TOP_CENTER = ControlPosition.TOP_CENTER,
                    BOTTOM_LEFT = ControlPosition.BOTTOM_LEFT,
                    TOP_LEFT = ControlPosition.TOP_LEFT,
                    Control = mol.ui.Map.Control;
                
                this._rightControl = new Control('RightControl');
                controls[TOP_RIGHT].clear();
                controls[TOP_RIGHT].push(this._rightControl.getDiv());
                                
                this._centerTopControl = new Control('CenterTopControl');
                controls[TOP_CENTER].clear();
                controls[TOP_CENTER].push(this._centerTopControl.getDiv());

                this._leftTopControl = new Control('TopLeftControl');
                controls[TOP_LEFT].clear();
                controls[TOP_LEFT].push(this._leftTopControl.getDiv());  
                
                this._leftBottomControl = new Control('LeftBottomControl');
                controls[BOTTOM_LEFT].clear();
                controls[BOTTOM_LEFT].push(this._leftBottomControl.getDiv());                
            },

            /**
             * Adds an event handler for new layers.
             */
            _addLayerEventHandler: function() {
                var bus = this._bus,
                    LayerEvent = mol.events.LayerEvent,
                    ColorEvent = mol.events.ColorEvent,
                    deleteLayer = this._deleteLayer,
                    layers = this._layers,
                    self = this;
                
                bus.addHandler(
                    LayerEvent.TYPE,
                    function(event) {
                        var layer = event.getLayer(),
                            lid = layer.getId(),
                            action = event.getAction(),
                            config = {
                                action: 'get',
                                category: layer.getType(),
                                id: lid
                            };
                                                
                        switch (action) {

                        case 'add':
                            if (layers[lid]) {
                                mol.log.info('Map ignoring layer id ' + lid);
                                return;
                            }                            
                            layers[lid] = layer;
                            bus.fireEvent(new ColorEvent(config));
                            break;

                        case 'delete':
                            deleteLayer(lid);
                            break;

                        case 'checked':
                            self._toggleLayer(layers[lid], true);
                            break;                            
                        case 'unchecked':
                            self._toggleLayer(layers[lid], false);
                            break;                            
                        }                        
                    }
                );
            },
            
            /**
             * Adds an event handler so that displays can be added to the map as
             * controls simply by firing a MapControlEvent.
             */
            _addMapControlEventHandler: function() {
                var bus = this._bus,
                    MapControlEvent = mol.events.MapControlEvent,
                    controls = this._map.controls,
                    controlDivs = this._controlDivs,
                    ControlPosition = mol.ui.Map.Control.ControlPosition,
                    TOP_RIGHT = ControlPosition.TOP_RIGHT,
                    TOP_CENTER = ControlPosition.TOP_CENTER,
                    BOTTOM_LEFT = ControlPosition.BOTTOM_LEFT,
                    TOP_LEFT = ControlPosition.TOP_LEFT,
                    topRightControl = this._rightControl,
                    leftTopControl = this._leftTopControl,
                    centerTopControl = this._centerTopControl,
                    leftBottomControl = this._leftBottomControl;
                                
                bus.addHandler(
                    MapControlEvent.TYPE,
                    function(event) {
                        var action = event.getAction(),
                            display = event.getDisplay(),
                            controlPosition = event.getControlPosition(),
                            displayPosition = event.getDisplayPosition(),
                            control = null;

                        switch (action) {

                        case 'add':
                            switch (controlPosition) {
                                
                            case TOP_RIGHT:
                                control = topRightControl;
                                break;
                                
                            case TOP_CENTER:
                                control = centerTopControl;
                                break;

                            case TOP_LEFT:
                                control = leftTopControl;
                                break;

                            case BOTTOM_LEFT:
                                control = leftBottomControl;
                                break;
                            }
                            control.addDisplay(display, displayPosition);
                            break;

                        case 'remove':
                            // TODO: Remove custom map control.
                            mol.log.todo('Remove custom map control');
                            break;                            
                        }
                    }
                );
            },

            _addColorEventHandler: function() {
                var ColorEvent = mol.events.ColorEvent,
                    bus = this._bus,
                    points = this._points,
                    layers = this._layers,
                    self = this,
                    map = this._map,
                    overlayMapTypes = this._map.overlayMapTypes,
                    mapTypes = this._mapTypes,
                    mapTypeIndexes = this._mapTypeIndexes,
                    currentMapTypeIndex = this._currentMapTypeIndex,
                    mapType = null;

                bus.addHandler(
                    ColorEvent.TYPE,
                    function(event) {
                        var color = event.getColor(),
                            category = event.getCategory(),
                            layerId = event.getId(),
                            layer = layers[layerId],
                            action = event.getAction();

                        // Ignores event since we don't have the layer associated with it:
                        if (!layer) {
                            return;
                        }
                        
                        // Sets the layer color:
                        layer.setColor(color);

                        switch (action) {

                        case 'change':
                            switch (category) {
                                
                            case 'points':
                                // Gets the point icon based on color and either
                                // creates the points with new icons or updates 
                                // icons of existing points.
                                self._getPointIcon(
                                    color,
                                    function(icon) {
                                        layer.setIcon(icon);                                    
                                        if (!points[layerId]) {
                                            self._createPoints(layer);    
                                        } else {
                                            self._updateLayerColor(layer);
                                        }
                                    }
                                );
                                break;

                            case 'range':
                                mapType = self._buildImageMapType(layer, color, 'range');

                                if (mapTypes[layerId]) {
                                    // The mapType exists, so remove it first:
                                    overlayMapTypes.removeAt(mapTypeIndexes[layerId]);                            
                                } else {
                                    // This is a new mapType, so let us keep track of it:
                                    mapTypes[layerId] = mapType;
                                }

                                // Adds the mapType to the map:
                                overlayMapTypes.insertAt(
                                    self._currentMapTypeIndex, 
                                    mapType);
                                
                                // Updates the index of the mapType on the map:
                                mapTypeIndexes[layerId] = currentMapTypeIndex;
                                currentMapTypeIndex = currentMapTypeIndex + 1;
                                break;                                
                            }                           
                        }
                    }
                );                
            },
            
            _updateLayer: function(layer) {
                _updateLayerColor(layer);
            },

            _updateLayerColor: function(layer) {
                var points = this._points[layer.getId()],
                    type = layer.getType(),
                    urls = this._getIconUrls(layer.getIcon()),
                    markerCanvas = this._markerCanvas,
                    iconUrl = urls.iconUrl,
                    w = markerCanvas.getIconWidth(),
                    h = markerCanvas.getIconHeight(),
                    point = null,
                    MarkerImage = google.maps.MarkerImage,
                    Size = google.maps.Size,
                    Marker = google.maps.Marker,
                    image = new MarkerImage(iconUrl, new Size(w, h));
                
                switch (type) {

                case 'points':
                    for (x in points) {
                        point = points[x];
                        if (point instanceof Marker) {
                            point.setIcon(image);
                        }                        
                    }
                    break;

                case 'range':
                    // TODO
                    break;

                }
            },

            _getPointIcon: function(color, callback) {
                var icon = new Image(),
                    src = '/test/colorimage/pm-color.png?'
                          + 'r=' + color.getRed() 
                          + '&g=' + color.getGreen() 
                          + '&b=' + color.getBlue();                
                icon.onload = function() {
                    callback(icon);
                };                
                icon.src = src;
            },

            /**
             * Deletes a layer from the map.
             * 
             * @param layerId the id of the layer to delete
             */
            _deleteLayer: function(layerId) {
                var points = this._points[layerId];
                for (x in points) {
                    points[x].setMap(null);
                    delete points[x];
                }
                delete this._points[layerId];
                this._points[layerId] = null;
            },

            /**
             * Displays the layer on the map.
             * 
             * @param the layer to display
             */
            _toggleLayer: function(layer, show) {
                var lid = layer.getId(),
                    index = this._mapTypeIndexes[lid],
                    type = layer.getType(),
                    points = this._points[lid],
                    overlayMapTypes = this._map.overlayMapTypes,
                    map = show ? this._map : null,
                    mapType = this._mapTypes[lid];

                switch (type) {

                case 'points':
                    for (x in points) {
                        points[x].setMap(map);
                    }
                    break;
                    
                case 'range':
                    if (show) {
                        overlayMapTypes.insertAt(index, mapType);
                    } else {
                        overlayMapTypes.removeAt(index);                            
                    }
                    break;
                }
            },            

            _getIconUrls: function(icon) {
                var markerCanvas = this._markerCanvas,
                    canvasSupport = markerCanvas.canvasSupport(),
                    icons = markerCanvas.getIcons(),
                    background = icons.background,
                    foreground = icons.foreground,
                    error = icons.error,
                    ctx = markerCanvas.getContext(),
                    w = markerCanvas.getIconWidth(),
                    h = markerCanvas.getIconHeight(),
                    url = null,
                    errorUrl = null;
                if (!canvasSupport) {
                    return {iconUrl: icon.src, iconErrorUrl: icon.src};
                }
                ctx.drawImage(background, 0, 0, w, h);
                ctx.drawImage(icon, 0, 0, w, h);
                ctx.drawImage(foreground, 0, 0, w, h);
                url = markerCanvas.getDataURL();
                ctx.drawImage(error, 0, 0, w, h);
                errorUrl = markerCanvas.getDataURL();
                return {iconUrl: url, iconErrorUrl: errorUrl};
            },

            _createPoints: function(layer) {
                var lid = layer.getId(),
                    center = null,
                    marker = null,
                    circle = null,
                    coordinate = null,
                    resources = [],
                    occurrences = [],
                    data = layer._json,
                    icon = layer.getIcon(),
                    urls = this._getIconUrls(icon),
                    iconUrl = urls.iconUrl,
                    iconErrorUrl = urls.iconErrorUrl;
                this._points[lid] = [];
                for (p in data.records.publishers) {
                    resources = data.records.publishers[p].resources;
                    for (r in resources) {
                        occurrences = resources[r].occurrences;
                        for (o in occurrences) {
                            coordinate = occurrences[o].coordinates;
                            marker = this._createMarker(coordinate, iconUrl);
                            this._points[lid].push(marker);                      
                            circle = this._createCircle(
                                marker.getPosition(),
                                coordinate.coordinateUncertaintyInMeters);                            
                            if (circle) {
                                this._points[lid].push(circle);
                            }     
                        }
                    }
                }
            },

            /**
             * Private function that creates a Google circle object.
             * 
             * @param center the center LatLng of the circle
             * @param coordinateUncertaintyInMeters the circle radius
             * @return a new Google circle object
             */
            _createCircle: function(center, coordinateUncertaintyInMeters) {          
                if (coordinateUncertaintyInMeters == null) {
                    return null;
                }
                var map = this._display.getMap(),
                    radius = parseFloat(coordinateUncertaintyInMeters),
                    opacity = 0.85,
                    circle = new google.maps.Circle(
                        {
                            map: map,
                            center: center,
                            radius: radius,
                            fillColor: '#CEE3F6',
                            strokeWeight: 1,                                
                            zIndex: 5
                        }
                    );
                return circle;
            },
            
            /**
             * Private function that creates a Google marker object.
             * 
             * @param coordinate the coordinate longitude and latitude
             * @return a new Google marker object
             */
            _createMarker: function(coordinate, iconUrl) {
                var map = this._display.getMap(),
                    lat = parseFloat(coordinate.decimalLatitude),
                    lng = parseFloat(coordinate.decimalLongitude),
                    center = new google.maps.LatLng(lat, lng),
                    w = this._markerCanvas.getIconHeight(),
                    h = this._markerCanvas.getIconWidth(),
                    MarkerImage = google.maps.MarkerImage,
                    Size = google.maps.Size,
                    Marker = google.maps.Marker,
                    image = new MarkerImage(iconUrl, new Size(w, h)),
                    marker = new Marker(
                        { 
                            position: center,
                            map: map,
                            icon: image
                        }
                    );
                return marker;
            },

            _buildImageMapType: function(layer, color, type) {   
                var self = this,
                    rank = 'species',
                    cls = 'animalia',
                    name = layer.getName().toLowerCase().replace(' ', '_'),
                    r = color.getRed(),
                    g = color.getGreen(),
                    b = color.getBlue(),
                    params = null;
                
                switch (type) {
                case 'range':
                    params = {                        
                        name: name,
                        rank: rank,
                        type: type,
                        cls: cls,
                        r: r,
                        g: g,
                        b: b
                    };
                    break;

                case 'ecoregion':
                    params = {     
                        id: [cls, rank, name].join('/'),
                        r: color.getRed(),
                        g: color.getGreen(),
                        b: color.getBlue()
                    };
                    break;
                }
                
                
                return new google.maps.ImageMapType(
                    {
                        getTileUrl: function(coord, zoom) {
                            var normalizedCoord = self._getNormalizedCoord(coord, zoom);
{'fraction_non_alphanumeric': 0.07374125712168234, 'fraction_numerical': 0.0009727175876603826, 'mean_word_length': 0.7928500249128052, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 78, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1959620', 'n_tokens_mistral': 9384, 'n_tokens_neox': 8540, 'n_words': 1941}