
function Mappable(json) {
    this._marker = null;

    this.setData = function(json) {
        for (key in json) {
            this[key] = json[key];
        }
        if (this.latitude < -90 || this.latitude > 90 || this.longitude < -180 || this.longitude > 180) {
            this.latitude = 0;
            this.longitude = 0;
        }
    }
    this.setData(json);

    this.isComplete = function() {
        return (this.overview || this.address);
    }

    this.openInfoWindow = function() {
        var infowindow = Mappable._map._openInfoWindow(this);

        var self = this;
        // note this only fires when the user closes the window via the close button, not when .close() is called.
        google.maps.event.addListener(infowindow, "closeclick", function() {
            Mappable.onCloseInfoWindow(self);
        });

        Mappable.onOpenInfoWindow(this, infowindow);
    }

    this.getMarker = function() {
        if (!isLatLngCool(this.latitude, this.longitude)) {
            this._marker = null;
        } else if (this._marker == null) {
            this._marker = new google.maps.Marker(
                    {
                        position: new google.maps.LatLng(this.latitude,this.longitude),
                        icon: Mappable.MARKER_IMAGES.get(this.ID.split('_')[0], this.propertyTypeID, false),
                        shadow: Mappable.SHADOW_MARKER_IMAGE
                    });

            var self = this;
            google.maps.event.addListener(this._marker, "click", function() {
                self.openInfoWindow();
            });
        }
        return this._marker;
    }

    this.setHighlighted = function(highlighted) {
        if (this._marker) {
            this._marker.setIcon(Mappable.MARKER_IMAGES.get(this.ID.split('_')[0], this.propertyTypeID, highlighted));
        }
    }

    this.showDetails = function() {
        new DetailsView().showDetails(this.ID);
    }
    this.getInfoHTML = function() {
        var html = "<img src=\"/images/search/loading.gif\" />";

        if (this.isComplete()) {
            var entityType = this.ID.split('_')[0];

            html = '<ul class="map-details">';
            html += '   <li id="bubble-title-{0}" class="title {8}">';
            html += '       <span class="input">' + (!this.isPrivate ? '<input type="checkbox" class="selectableEntity" value="{0}" title="Highlight this {9}" onclick="new ActionManager().toggleSelected(\'{0}\')" {10} />' : '&nbsp;') + '</span>';
            html += '       <a href="javascript:Mappable.onShowDetails(\'{0}\')">{6}</a><br />';
            html += '       <span class="address">{1}</span>';
            html += '   </li>';
            html += '   <li class="map-type">';
            html += '       <a href="javascript:Mappable._map.zoomTo({latitude:{12},longitude:{13}})" class="zoom">Zoom to {9}</a>{3}';
            if (entityType != PROPERTY) {
                html += ' for {11}';
            }
            html += '   </li>';
            if (this.thumbnail) {
                html += '               <li class="image"><a href="javascript:Mappable.onShowDetails(\'{0}\')"><img class="mapInfoWindowImage" src="{2}" alt="{1}" border="0" /></a></li>\n';
            }
            if (this.price) {
                html += '   <li>{4}</li>';
            }
            html += '   <li>{5}</li>';
            html += '   <li>ID #{0}</li>';
            html += '   <li class="overview">{7}</li>';
            html += '   </ul>';
            var selectedString = (Mappable.isSelected(this)?" checked='true' ":"");
            var leaseOrSale = eval(this.isLeaseAndSale)?"Sale or Lease":(eval(this.isLease)?"Lease":"Sale");

            var desc = "";
            if (entityType == COMPARABLE) {
                desc = "Comparable";
            } else if (entityType == LISTING) {
                desc = "Listing";
            } else if (entityType == PROPERTY) {
                desc = "Property";
            }  else if (entityType == SUITE) {
                desc = "Suite";
            }
            html = html.format(this.ID, this.address, this.thumbnail, this.propertyType, this.price, this.size, this.title, this.overview,(Mappable.isSelected(this)?" highlighted ":""), desc, selectedString, leaseOrSale, this.latitude, this.longitude);
        }
        return html;
    }

    this.getInfoRowHTML = function() {
        var html = '';
        if (this.isComplete()) {
            html += '<tr class="evenodd" id="cluster-detail-{0}">';
            html += '<td>';
            if (this.isPrivate) {
                html += '&nbsp;';
            } else {
                html += '<input type="checkbox" class="selectableEntity" value="{0}" title="Highlight this {6}" onclick="new ActionManager().toggleSelected(\'{0}\')" />';
            }
            html += '</td>';
            html += '<td><a href="javascript:Mappable.onShowDetails(\'{0}\')">{1}</a> <span class="address">{2}</span></td>';
            html += '<td>';
            if (this.price) {
                html += '<span class="price">{4}</span> ';
            }
            html += '<span class="size">{5}</span> <span class="property-type">{3}</span>';
            html += '</td>';
            html += '</tr>';

            var entityType = this.ID.split('_')[0];
            var desc = "";
            if (entityType == COMPARABLE) {
                desc = "Comparable";
            } else if (entityType == LISTING) {
                desc = "Listing";
            } else if (entityType == PROPERTY) {
                desc = "Property";
            }  else if (entityType == SUITE) {
                desc = "Suite";
            }
            html = html.format(
                    this.ID,
                    this.title,
                    this.address,
                    this.propertyType,
                    this.price,
                    this.size,
                    desc);
        }
        return html;
    }
}
var MARKER_HOME = "http://" + window.location.host + "/images/map/";
Mappable.SHADOW_MARKER_IMAGE = new google.maps.MarkerImage(MARKER_HOME + "marker_shadow.png", new google.maps.Size(48, 31), null, new google.maps.Point(5, 31));
Mappable.MARKER_IMAGES = {
    _offmarket: new Object(),
    _active: new Object(),
    get: function(entityType, propertyType, highlighted) {
        entityType = parseFloat(entityType);
        propertyType = parseFloat(propertyType);

        var entityImages = (entityType == PROPERTY) ? this._offmarket : this._active;

        var propertyTypeImages = entityImages[propertyType];
        if (!propertyTypeImages) {
            propertyTypeImages = {
                highlight: null,
                standard: null
            }
            entityImages[propertyType] = propertyTypeImages;
        }

        var image = highlighted ? propertyTypeImages.highlight : propertyTypeImages.standard;
        if (!image) {
            //let's keep them all red for now
            //var baseURL = MARKER_HOME + ((entityType == PROPERTY) ? 'offmarket' : 'listing');
            var baseURL = MARKER_HOME + ((entityType == PROPERTY) ? 'listing' : 'listing');
            var standardURL;
            var highlightURL;
            switch (parseFloat(propertyType)) {
            case OFFICE:
                standardURL = baseURL + '_office.png';
                highlightURL = baseURL + '_office_high.png';
                break;
            case RETAIL:
                standardURL = baseURL + '_shoppingcenter.png';
                highlightURL = baseURL + '_shoppingcenter_high.png';
                break;
            case INDUSTRIAL:
                standardURL = baseURL + '_industrial.png';
                highlightURL = baseURL + '_industrial_high.png';
                break;
            case MULTIFAMILY:
                standardURL = baseURL + '_multifamily.png';
                highlightURL = baseURL + '_multifamily_high.png';
                break;
            case VACANTLAND:
                standardURL = baseURL + '_vacantland.png';
                highlightURL = baseURL + '_vacantland_high.png';
                break;
            case HOSPITALITY:
                standardURL = baseURL + '_hospitality.png';
                highlightURL = baseURL + '_hospitality_high.png';
                break;
            case FARMRANCH:
                standardURL = baseURL + '_farmranch.png';
                highlightURL = baseURL + '_farmranch_high.png';
                break;
            case RETAIL_COMMERCIAL:
                standardURL = baseURL + '_retailcommercial.png';
                highlightURL = baseURL + '_retailcommercial_high.png';
                break;
            case SPECIAL_PURPOSE:
                standardURL = baseURL + '_specialpurpose.png';
                highlightURL = baseURL + '_specialpurpose_high.png';
                break;
            case BIZ_OPP:
                standardURL = baseURL + '_bizopp.png';
                highlightURL = baseURL + '_bizopp_high.png';
                break;
            default:
                standardURL = MARKER_HOME + 'marker_red.png';
                highlightURL = MARKER_HOME + 'marker_red_high.png';
                break;
            }

            propertyTypeImages.standard = new google.maps.MarkerImage(standardURL, new google.maps.Size(28, 32));
            propertyTypeImages.highlight = new google.maps.MarkerImage(highlightURL, new google.maps.Size(28, 32));

            image = highlighted ? propertyTypeImages.highlight : propertyTypeImages.standard;
        }
        return image;
    }
}

Mappable.onOpenInfoWindow = function(mappable, infowindow) {}
Mappable.onCloseInfoWindow = function(mappable) {}
Mappable.onShowDetails = function(id) {}
Mappable.isSelected = function(mappable) {
    return false;
}

function Cluster(json) {
    this.ID = json.latitude.toFixed(7) + ',' + json.longitude.toFixed(7) + ',' + json.clusterSize;

    this._entities = null;
    this._overlay = null;

    this.getBounds = function() {
        var bounds = null;
        if (this._overlay) {
            var gbounds = this._overlay.getBounds();
            if (gbounds) {
                bounds = {
                    ne: gbounds.getNorthEast(),
                    sw: gbounds.getSouthWest()
                };
            }
        }
        return bounds;
    }

    this.getMarker = function() {
        if (!this._overlay) {
            this._overlay = new ClusterOverlay({
                position: new google.maps.LatLng(json.latitude, json.longitude),
                count: json.clusterSize,
                bounds: new google.maps.LatLngBounds(
                            new google.maps.LatLng(json.bounds.s, json.bounds.w),
                            new google.maps.LatLng(json.bounds.n, json.bounds.e))
            });

            var self = this;
            this._overlay.explode = function() {
                self.openInfoWindow();
            };
        }
        return this._overlay;
    }

    this.openInfoWindow = function() {
        var infowindow = Cluster._map._openInfoWindow(this);

        var self = this;
        // note this only fires when the user closes the window via the close button, not when .close() is called.
        google.maps.event.addListener(infowindow, "closeclick", function() {
            Cluster.onCloseInfoWindow(self);
        });

        Cluster.onOpenInfoWindow(this, infowindow);
    };

    this.getInfoHTML = function() {
        var html = '<img src="/images/search/loading.gif" />';
        if (this._entities) {
            html = '<table class="cluster-details">';
            for (var i = 0; i < this._entities.length; i++) {
                html += this._entities[i].getInfoRowHTML().replace('evenodd', i%2 == 0 ? 'even' : 'odd');
            }
            html += '</table>';
        }
        return html;
    }

    this.isComplete = function() {
        return this._entities != null;
    }
    this.setEntities = function(entities) {
        this._entities = entities;
    }
    this.getEntities = function() {
        return this._entities;
    }
}
Cluster.onOpenInfoWindow = function(cluster, infowindow) {}
Cluster.onCloseInfoWindow = function(cluster) {}

function CMap(containerID, overlayOptions, latitude, longitude, zoom) {
    Mappable._map = this;
    Cluster._map = this;

    this._specifiedCenter = new google.maps.LatLng(latitude||39.774769485295465, longitude||-94.74609375);
    this._zoom = zoom||4;

    this._container = document.getElementById(containerID);
    this._map = new google.maps.Map(this._container,
            {
                streetViewControl: true,
                zoom: this._zoom,
                center: this._specifiedCenter,
                mapTypeId: google.maps.MapTypeId.ROADMAP,
                mapTypeControlOptions: {
                    mapTypeIds: [google.maps.MapTypeId.HYBRID,google.maps.MapTypeId.ROADMAP,google.maps.MapTypeId.SATELLITE,google.maps.MapTypeId.TERRAIN],
                    style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
                },
                navigationControlOptions: {
                    position: google.maps.ControlPosition.LEFT_TOP
                }
            });

    this._overlayControl = null;

    if (overlayOptions) {
        this._overlayControl = new COverlayControl(overlayOptions);
        this._overlayControl.setMap(this._map);
        COverlayControl.onOverlayChange = function() { CMap.onOverlayChange() };

        var self = this;
        COverlayControl.onShowSearchResultsChange = function(showThem) {
            if (showThem) {
                self._showResults();
            } else {
                self._hideResults();
            }
        };
    }

    if (window["CZoomControl"] && window["CDistanceMeasureControl"]) {
        var containerDiv = $('<div id="mapToolsContainer"/>')[0];

        var self = this;

        this._distanceControl = new CDistanceMeasureControl();
        this._distanceControl.setMap(this._map, containerDiv);
        this._distanceControl.onDrawing = function(start) {
            for (var id in self._markersByID) {
                self._markersByID[id].setClickable(!start);
            }
            for (var id in self._clustersByID) {
                self._clustersByID[id].setClickable(!start);
            }
        };


        this._zoomControl = new CZoomControl();
        this._zoomControl.setMap(this._map, containerDiv);

        this._zoomControl.onZoom = function(n,e,s,w) {
            self.zoomToFit(n,s,e,w);
            // zoomToFit is too conservative so we'll crank it up another level
            self._setZoom(self.getZoom() + 1);
        }
        this._zoomControl.onZooming = function(start) {
            if (start) {
                if (self._polygonControl) {
                    self._polygonControl.cancelShape();
                }
                if (self._distanceControl) {
                    self._distanceControl.cancel();
                }
            }
        }

        containerDiv.index = 0;
        this._map.controls[google.maps.ControlPosition.LEFT_TOP].push(containerDiv);
    }


    this._hiddenResults = false;

    this._polygonControl = null;

    this._clustersByID = new Object();
    this._markersByID = new Object();
    this._shapesByName = new Object();

    this._openedInfoWindow = null;

    google.maps.event.addListener(this._map, "center_changed", function(){CMap.onPan()});
    google.maps.event.addListener(this._map, "zoom_changed", function(){CMap.onZoom()});
    google.maps.event.addListener(this._map, "idle", function(){ CMap.onPostPanZoom()});

    this.resized = function() {
        google.maps.event.trigger(this._map, 'resize');
    }

    this.getOverlayControl = function() {
        return this._overlayControl;
    }

    this.showPolygon = function(showit) {
        if (!showit && this._polygonControl) {
            this._polygonControl.setMap(null);
            this.clearUserShape();

        } else if (showit) {
            if (window["CPolygonControl"]) {
                var self = this;
                this._polygonControl = new CPolygonControl();
                this._polygonControl.onSearchByPolygon = function(gpolygon) {
                    self._removeShapeByName('user');
                    self._addShapeByName(gpolygon, 'user');

                    var poly = Polygon._fromGooglePolygon(gpolygon);
                    CMap.onSearchByPolygon(poly);

                    if (self._overlayControl) {
                        self._overlayControl.setPolygon(poly);
                    }
                    if (self._zoomControl) {
                        self._zoomControl.cancelZoom();
                    }
                    if (self._distanceControl) {
                        self._distanceControl.cancel();
                    }
                }
                this._polygonControl.onDrawing = function(start) {
                    if (start) {
                        if (self._zoomControl) {
                            self._zoomControl.cancelZoom();
                        }
                        if (self._distanceControl) {
                            self._distanceControl.cancel();
                        }
                    }
                    // cancelling the other controls resets the cursor, so we'll hack it manually
                    self._map.setOptions({draggableCursor: start ? 'crosshair' : null});
                }
                this._polygonControl.setMap(this);
            }
        }
    }

    this.getContainer = function() {
        return this._container;
    }

    this.setCenter = function(latLng, zoom) {
        if (isLatLngCool(latLng.lat(), latLng.lng())) {
            this._specifiedCenter = new google.maps.LatLng(latLng.lat(), latLng.lng());
            this._map.setCenter(this._specifiedCenter);

            this._setZoom(zoom);
        }
    }
    this._setZoom = function(zoom) {
        if (zoom > 0) {
            this._zoom = zoom;
            this._map.setZoom(this._zoom);
        }
    }

    this.getCenter = function() {
        return this._map.getCenter();
    }
    this.getZoom = function() {
        return this._map.getZoom();
    }

    this.reset = function() {
        this._map.setCenter(this._specifiedCenter);
        this._map.setZoom(this._zoom);
    }

    this.getBounds = function() {
        var bounds = this._map.getBounds();
        if (bounds) {
            return {
                ne: bounds.getNorthEast(),
                sw: bounds.getSouthWest()
            };
        } else {
            return null;
        }
    }

    this.replaceOverlaysForClusters = function(clusters, keepInfoWindow) {
        var clustersToRemove = new Object();
        for (var id in this._clustersByID) {
            clustersToRemove[id] = true;
        }

        if (clusters) {
            for (var i = 0; i < clusters.length; i++) {
                if (!this._clustersByID[clusters[i].ID]) {
                    this._addClusterByID(clusters[i].ID, clusters[i].getMarker());
                }

                delete clustersToRemove[clusters[i].ID];
            }
        }

        for (var id in clustersToRemove) {
            var doRemove = true;
            if (this._openedInfoWindow && this._openedInfoWindow._mappableID == 'C:' + id) {
                doRemove = !keepInfoWindow;
                if (!keepInfoWindow) {
                    this.closeInfoWindow();
                }
            }
            if (doRemove) {
                this._removeClusterByID(id);
            }
        }
    }

    this.addOverlaysForClusters = function(clusters) {
        if (!isArray(clusters)) {
            clusters = [clusters];
        }
        for (var i = 0; i < clusters.length; i++) {
            this._removeClusterByID(clusters[i].ID);
            this._addClusterByID(clusters[i].ID, clusters[i].getMarker());
        }
    }
    this.hasClusterOverlay = function(cluster) {
        return this._addClusterByID[cluster.ID] != null;
    }


    this.replaceOverlaysForMappables = function(mappables, keepInfoWindow) {
        if (!isArray(mappables)) {
            mappables = [mappables];
        }

        var markersToRemove = new Object();
        for (var id in this._markersByID) {
            markersToRemove[id] = true;
        }

        for (var i=0;i<mappables.length;i++) {
            var id = mappables[i].ID;
            delete markersToRemove[id];

            if (!this.hasMappableOverlay(mappables[i])) {
                var marker = mappables[i].getMarker();
                if (marker) {
                    this._addMarkerByID(id, marker);
                }
            }

        }

        for (var id in markersToRemove) {
            if (this._openedInfoWindow && this._openedInfoWindow._mappableID == 'M:' + id) {
                if (!keepInfoWindow) {
                    this.closeInfoWindow();
                    this._removeMarkerByID(id);
                }
            } else {
                this._removeMarkerByID(id);
            }
        }
    }

    this.addOverlaysForMappables = function(mappables) {
        if (!isArray(mappables)) {
            mappables = [mappables];
        }

        for (var i=0;i<mappables.length;i++) {
            var id = mappables[i].ID;
            this._removeMarkerByID(id);

            var marker = mappables[i].getMarker();
            if (marker) {
                this._addMarkerByID(id, marker);
            }
        }
    }
    this.hasMappableOverlay = function(mappable) {
        return this._markersByID[mappable.ID] != null;
    }

    this._addClusterByID = function(id, marker) {
        this._clustersByID[id] = marker;
        if (!this._hiddenResults) {
            marker.setMap(this._map);
        }
    }
    this._removeClusterByID = function(id) {
        var cluster = this._clustersByID[id];
        if (cluster) {
            cluster.setMap(null);
        }
        delete this._clustersByID[id];
    }

    this._addMarkerByID = function(id, marker) {
        this._markersByID[id] = marker;
        if (!this._hiddenResults) {
            marker.setMap(this._map);
        }
    }
    this._removeMarkerByID = function(id) {
        var marker = this._markersByID[id];
        if (marker) {
            marker.setMap(null);
        }
        delete this._markersByID[id];
    }

    this._addShapeByName = function(shape, name) {
        this._shapesByName[name] = shape;
        shape.setMap(this._map);
    }
    this._removeShapeByName = function(name) {
        var shape = this._shapesByName[name];
        if (shape) {
            shape.setMap(null);
        }
        delete this._shapesByName[name];
    }

    this.getOpenInfoWindowID = function() {
        if (this._openedInfoWindow) {
            return this._openedInfoWindow._mappableID;
        } else {
            return '';
        }
    }
    this._openInfoWindow = function(item) {
        this.closeInfoWindow();

        var infowindow = new google.maps.InfoWindow({content:item.getInfoHTML(), maxWidth:330});
        this._openedInfoWindow = infowindow;

        this._openedInfoWindow._mappableID = (item instanceof Mappable ? 'M:' : 'C:') + item.ID;

        var self = this;
        google.maps.event.addListener(infowindow, "closeclick", function() {
            if (self._openedInfoWindow != null) {
                self._openedInfoWindow = null;
            }
        });

        var streetView = this._map.getStreetView();
        if (streetView && streetView.getVisible()) {
            infowindow.open(streetView, item.getMarker());
        } else {
            infowindow.open(this._map, item.getMarker());
        }

        return infowindow;
    }

    this.closeInfoWindow = function() {
        if (this._openedInfoWindow != null) {
            this._openedInfoWindow.close();
            this._openedInfoWindow = null;
        }
    }

    this.setUserShape = function(polygon) {
        this._removeShapeByName('user');
        this._addShapeByName(polygon._getGooglePolygon(), 'user');

        if (this._overlayControl) {
            this._overlayControl.setPolygon(polygon);
        }
    }

    this.clearUserShape = function() {
        this._removeShapeByName('user');
        if (this._polygonControl) {
            this._polygonControl.cancelShape();
        }

        if (this._overlayControl) {
            this._overlayControl.setPolygon(null);
        }
    }

    this.zoomToFit = function(north,south,east,west) {
        north  = parseFloat(north);
        south  = parseFloat(south);
        east  = parseFloat(east);
        west  = parseFloat(west);

        if (isLatLngCool(north, east) && isLatLngCool(south, west)) {
            var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(south,west), new google.maps.LatLng(north,east));
            this._map.fitBounds(bounds);

            this._zoom =  this._map.getZoom();
            this._specifiedCenter = this._map.getCenter();
        }
    }

    this.zoomTo = function(mappable) {
        var lat = mappable.latitude;
        var lng = mappable.longitude;
        this._map.setCenter(new google.maps.LatLng(lat, lng));
        this._map.setZoom(16);
    }

    this._hideResults = function() {
        if (!this._hiddenResults) {
            this._hiddenResults = true;

            for (var id in this._markersByID) {
                this._markersByID[id].setMap(null);
            }
            for (var id in this._clustersByID) {
                this._clustersByID[id].setMap(null);
            }
        }
    }
    this._showResults = function() {
        if (this._hiddenResults) {
            this._hiddenResults = false;

            for (var id in this._markersByID) {
                this._markersByID[id].setMap(this._map);
            }
            for (var id in this._clustersByID) {
                this._clustersByID[id].setMap(this._map);
            }
        }
    }
}
CMap.onPostPanZoom = function() {}
CMap.onPan = function() {}
CMap.onZoom = function() {}
CMap.onSearchByPolygon = function(polygon) {}
CMap.onOverlayChange = function() {}


function isLatLngCool(lat, lng) {
    return lat <= 90 && lat >= -90 && lng <= 180 && lng >= -180;
}

function Polygon() {
    this.points = new Array();

    this.addPoint = function(gLatLng) {
        this.points.push(gLatLng);
    }

    this.getSerializedPoints = function() {
        var pointsSerializedInArray = new Array();

        for (var i=0;i<this.points.length;i++) {
            var point = this.points[i];
            pointsSerializedInArray.push(point.x + ":" + point.y);
        }
        return pointsSerializedInArray.join(",");
    }

    this.close = function() {
        var first = this.points[0];
        var last = this.points[this.points.length - 1];
        var isClosed =  (this.points.length > 1 && first.lat() == last.lat() && first.lng() == last.lng());
        if (!isClosed) {
            this.addPoint(first);
        }

    }

    this.clear = function() {
        this.points = new Array();
    }

    this.containsPoint = function(testPoint) {
         //based on work in http://www.scottandrew.com/js/js_util.js
         var lngnew,latnew,lngold,latold,lng1,lat1,lng2,lat2,i;
         var inside=false;

         if (this.points.length >= 3 && testPoint) { // points don't describe a polygon, or there is no valid testPoint
            plng = testPoint.lng();
            plat = testPoint.lat();
            var lastPoint = this.points[this.points.length - 1];
            lngold = lastPoint.lng();
            latold = lastPoint.lat();

            for (var i=0 ; i < this.points.length ; i++) {
                var point = this.points[i];
                lngnew = point.lng();
                latnew = point.lat();
                if (lngnew > lngold) {
                    lng1=lngold;
                    lng2=lngnew;
                    lat1=latold;
                    lat2=latnew;
                }else {
                    lng1=lngnew;
                    lng2=lngold;
                    lat1=latnew;
                    lat2=latold;
                }
                if ((lngnew < plng) == (plng <= lngold) && ((plat-lat1)*(lng2-lng1) < (lat2-lat1)*(plng-lng1))) {
                    inside=!inside;
                }
                lngold=lngnew;
                latold=latnew;
            }
        }
        return inside;
    }

    this.equals = function(other) {
        var equals = (this.points.length == other.points.length);
        for (var i=0;equals && i<this.points.length;i++) {
            equals = this.points[i].equals(other.points[i]);
        }
        return equals;
    }

    this._getGooglePolygon = function() {
        var pts = new Array();
        for (var i = 0; i < this.points.length; i++) {
            var latLng;
            if (this.points[i] instanceof google.maps.LatLng) {
                latLng = this.points[i];
            } else {
                latLng = new google.maps.LatLng(this.points[i].lat(), this.points[i].lng());
            }
            pts.push(latLng);
        }

        return new google.maps.Polygon({
                    clickable: false,
                    paths: pts,
                    strokeColor: '#FF0000',
                    strokeWeight: 2,
                    strokeOpacity: 0.7,
                    fillColor: '#FF0000',
                    fillOpacity: 0.1
                });
    }

    for (var i=0;i<arguments.length;i++) {
        this.addPoint(arguments[i]);
    }
}
Polygon._fromGooglePolygon = function(gpolygon) {
    var polygon = null;
    if (gpolygon) {
        polygon = new Polygon();
        gpolygon.getPath().forEach(function(element, index) {
            polygon.addPoint(element);
        });
    }
    return polygon;
}

