最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Drawing Clean Neighborhood Boundaries on Google Maps - Stack Overflow

programmeradmin4浏览0评论

I have a google map that drops a lot of pins throughout the city of Manhattan. To better organize this claustrophobic mess of pins, I want to provide a zoomed out view that divides manhattan into cleanly delineated neighborhoods that you can click on, which will then zoom in and display the individual pins in that neighborhood region. Here is an example of what I'd like to achieve:

.81910776309414&where%5Bbounds%5D%5B%5D=-73.87714974792482&where%5Bbounds%5D%5B%5D=40.74046602072578&where%5Bbounds%5D%5B%5D=-74.07713525207521&where%5Bzoom%5D=13

I'm not sure where to start with this. I've read the google maps documentation, but I'm still unsure of (a) the javascript method I should be using for drawing the boundaries, and (b) where I can get meaningful information about how to draw the manhattan neighborhood boundaries.

Anyone have experience with this?

I have a google map that drops a lot of pins throughout the city of Manhattan. To better organize this claustrophobic mess of pins, I want to provide a zoomed out view that divides manhattan into cleanly delineated neighborhoods that you can click on, which will then zoom in and display the individual pins in that neighborhood region. Here is an example of what I'd like to achieve:

http://42floors./ny/new-york?where%5Bbounds%5D%5B%5D=40.81910776309414&where%5Bbounds%5D%5B%5D=-73.87714974792482&where%5Bbounds%5D%5B%5D=40.74046602072578&where%5Bbounds%5D%5B%5D=-74.07713525207521&where%5Bzoom%5D=13

I'm not sure where to start with this. I've read the google maps documentation, but I'm still unsure of (a) the javascript method I should be using for drawing the boundaries, and (b) where I can get meaningful information about how to draw the manhattan neighborhood boundaries.

Anyone have experience with this?

Share Improve this question asked Jul 10, 2013 at 15:18 user1427661user1427661 11.8k31 gold badges95 silver badges133 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

Luckily, there is one site I have found where you can get great info on neighborhood boundaries in GeoJson format. Ever heard of "the-neighborhoods-project"? It's here http://zetashapes./editor/36061 where I did a search for "Manhattan" and found that page. They have many other cities neighborhoods too. As you will notice, there is a button there to download the GeoJson data as a .json file. Now, I feel it's our responsibility as webmasters & developers to ensure the user need not download more data than necessary, as well as reduce our bandwidth requirements, and I find GeoJson to be bloated and overly large for what we need to get from it. So, first thing is to create a .php file on your server or localhost and name it 'reduce.php' or whatever you want, and fill it with the following php code:

<?php
$jsonString = file_get_contents('36061.json');
$obj = json_decode($jsonString);
$arr = array();
foreach($obj->features as $feature) {
    echo $feature->properties->label.'<br>';//just to let you see all the neighborhood names
    array_push($arr, array($feature->properties->label, $feature->geometry->coordinates));
}
file_put_contents('36061_minimal.json', json_encode($arr));
?>

Then place the '36061.json' file in same directory as the above php file and then run the php file by viewing in browser, and it will create a '36061_minimal.json' file which will be about half the size. O.k, now that that's taken care of, for the example which follows below you will need the following javascript file, in which there is a NeighborhoodGroup constructor that is used to keep track of our different neighborhoods. The most important things to know about it are you should instantiate a new instance of NeighborhoodGroup, and then you add neighborhoods to it by calling it's addNeighborhood(name, polygon) method which you feed a name and a google.maps.Polygon instance, then you can add marker objects to your neighborhood group by calling the addMarker(marker) method & feeding it a google.maps.Marker object, and the marker will be delegated to the appropriate neighborhood if possible or else addMarker will return false if the marker does not fit in any of our neighborhoods. So, name the following file "NeighborhoodGroup.js":

//NeighborhoodGroup.js
//requires that the google maps geometry library be loaded
//via a "libraries=geometry" parameter on the url to the google maps script

function NeighborhoodGroup(name) {
    this.name = name;
    this.hoods = [];
     //enables toggling markers on/off between different neighborhoods if set to true
    this.toggleBetweenHoods = false;
     //enables panning and zooming to fit the particular neighborhood in the viewport
    this.fitHoodInViewport = true;
    this.selectedHood = null;
    this.lastSelectedHood = null;
}

NeighborhoodGroup.prototype.getHood = function (name) {
    for (var i = 0, len = this.hoods.length; i < len; i++) {
        if (this.hoods[i].name == name) {
            return this.hoods[i];
        }
    }
    return null;
};

NeighborhoodGroup.prototype.addNeighborhood = function (name, polygon) {
    var O = this,
        hood = new Neighborhood(name, polygon);
    O.hoods.push(hood);
    google.maps.event.addListener(polygon, 'click', function () {
        if (O.toggleBetweenHoods) {
            O.lastSelectedHood = O.selectedHood;
            O.selectedHood = hood;
            if (O.lastSelectedHood !== null && O.lastSelectedHood.name != name) {
                O.lastSelectedHood.setMarkersVisible(false);
            }
        }
        hood.setMarkersVisible(!hood.markersVisible);
        if (O.fitHoodInViewport) {
            hood.zoomTo();
        }
    });
};

//marker must be a google.maps.Marker object
//addMarker will return true if the marker fits within one
//of this NeighborhoodGroup object's neighborhoods, and
//false if the marker does not fit any of our neighborhoods
NeighborhoodGroup.prototype.addMarker = function (marker) {
    var bool,
        i = 0,
        len = this.hoods.length;
    for (; i < len; i++) {
        bool = this.hoods[i].addMarker(marker);
        if (bool) {
            return bool;
        }
    }
    return bool;
};

//the Neighborhood constructor is not intended to be called
//by you, is only intended to be called by NeighborhoodGroup.
//likewise for all of it's prototype methods, except for zoomTo
function Neighborhood(name, polygon) {
    this.name = name;
    this.polygon = polygon;
    this.markers = [];
    this.markersVisible = false;
}

//addMarker utilizes googles geometry library!
Neighborhood.prototype.addMarker = function (marker) {
    var isInPoly = google.maps.geometry.poly.containsLocation(marker.getPosition(), this.polygon);
    if (isInPoly) {
        this.markers.push(marker);
    }
    return isInPoly;
};

Neighborhood.prototype.setMarkersVisible = function (bool) {
    for (var i = 0, len = this.markers.length; i < len; i++) {
        this.markers[i].setVisible(bool);
    }
    this.markersVisible = bool;
};

Neighborhood.prototype.zoomTo = function () {
    var bounds = new google.maps.LatLngBounds(),
        path = this.polygon.getPath(),
        map = this.polygon.getMap();
    path.forEach(function (obj, idx) {
        bounds.extend(obj);
    });
    map.fitBounds(bounds);
};

The following example utilizes googles places library to load up to 60 (actually I think it gets around 54 of them) different locations of Starbucks in Manhattan (I believe there are more than that, but this is googles results limitations). How it works is, in the initialize() function we setup the map, then use ajax to load our '36061_minimal.json' file which contains the neighborhood names and coordinates we need, then the [private] setupNeighborhoods() function utilizes that data to create polygons and add them to our instance of NeighborhoodGroup then the [private] loadPlaces() function is used to add marker objects to the map and also registers the marker with our instance of NeighborhoodGroup. On to the example:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Manhattan Neighborhoods</title>
<!--
NeighborhoodGroup.js requires that the geometry library be loaded,
just this example utilizes the places library
-->
<script src="https://maps.googleapis./maps/api/js?v=3.exp&sensor=false&libraries=geometry,places"></script>
<script type="text/javascript" src="NeighborhoodGroup.js"></script>
<script>

/***
The '36061_minimal.json' file is derived from '36061.json' file
obtained from the-neighborhoods-project at:
http://zetashapes./editor/36061
***/

//for this example, we will just be using random colors for our polygons, from the array below
var aBunchOfColors = [
'Aqua', 'Aquamarine', 'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue', 'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenRod', 'DarkGray', 'DarkGreen', 'DarkKhaki', 'DarkMagenta', 'DarkOliveGreen', 'Darkorange', 'DarkOrchid', 'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', 'DarkSlateGray', 'DarkTurquoise', 'DarkViolet', 'DeepPink', 'DeepSkyBlue', 'DodgerBlue', 'FireBrick', 'ForestGreen', 'Fuchsia', 'Gold', 'GoldenRod', 'Gray', 'Green', 'GreenYellow', 'HotPink', 'IndianRed', 'Indigo', 'LawnGreen', 'Lime', 'LimeGreen', 'Magenta', 'Maroon', 'MediumAquaMarine', 'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue', 'Navy', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 'Peru', 'Pink', 'Plum', 'Purple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon', 'SandyBrown', 'SeaGreen', 'Sienna', 'SkyBlue', 'SlateBlue', 'SlateGray', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'Yellow', 'YellowGreen'
];

Array.prototype.randomItem = function () {
    return this[Math.floor(Math.random()*this.length)];
};

function initialize() {
    var i,
        hoodGroup = new NeighborhoodGroup('Manhattan'),
        polys = [],
        gm = google.maps,
        xhr = getXhr(), //will use this to load json via ajax
        mapOptions = {
            zoom: 11,
            scaleControl: true,
            center: new gm.LatLng(40.79672159345707, -73.952665677124),
            mapTypeId: gm.MapTypeId.ROADMAP,
        },
        map = new gm.Map(document.getElementById('map_canvas'), mapOptions),
        service = new google.maps.places.PlacesService(map),
        infoWindow = new gm.InfoWindow();

    function setMarkerClickHandler(marker, infoWindowContent) {
        google.maps.event.addListener(marker, 'click', function () {
            if (!(infoWindow.anchor == this) || !infoWindow.isVisible) {
                infoWindow.setContent(infoWindowContent);
                infoWindow.open(map, this);
            } else {
                infoWindow.close();
            }
            infoWindow.isVisible = !infoWindow.isVisible;
            infoWindow.anchor = this;
        });
    }

     /*******
     * the loadPlaces function below utilizes googles places library to load
     * locations of up to 60 starbucks stores in Manhattan. You should replace
     * the code in this function with your own to just add Marker objects to
     *the map, though it's important to still use the line below which reads:
     *                  hoodGroup.addMarker(marker);
     * and I'd also strongly remend using the setMarkerClickHandler function as below
     *******/
    function loadPlaces() {
        var placesResults = [],
            request = {
                location: mapOptions.center,
                radius: 25 / 0.00062137, //25 miles converted to meters
                query: 'Starbucks in Manhattan'
        };
        function isDuplicateResult(res) {
            for (var i = 0; i < placesResults.length; i++) {
                if (res.formatted_address == placesResults[i].formatted_address) {
                    return true;
                }
            }
            placesResults.push(res);
            return false;
        }
        service.textSearch(request, function (results, status, pagination) {
            if (status == google.maps.places.PlacesServiceStatus.OK) {
                for (var res, marker, i = 0, len = results.length; i < len; i++) {
                    res = results[i];
                    if (!isDuplicateResult(res)) {
                        marker = new google.maps.Marker({
                            map: map,
                            visible: false,
                            position: res.geometry.location
                        });
                        setMarkerClickHandler(marker, res.name + '<br>' + res.formatted_address);
                        hoodGroup.addMarker(marker);
                    }
                }
            }
            if (pagination && pagination.hasNextPage) {
                pagination.nextPage();
            }
        });
    }

    function setupNeighborhoods(arr) {
        var item, poly, j,
            i = 0,
            len = arr.length,
            polyOptions = {
                strokeWeight: 0, //best with no stroke outline I feel
                fillOpacity: 0.4,
                map: map
            };
        for (; i < len; i++) {
            item = arr[i];
            for (j = 0; j < item[1][0].length; j++) {
                var tmp = item[1][0][j];
                item[1][0][j] = new gm.LatLng(tmp[1], tmp[0]);
            }
            color = aBunchOfColors.randomItem();
            polyOptions.fillColor = color;
            polyOptions.paths = item[1][0];
            poly = new gm.Polygon(polyOptions);
            hoodGroup.addNeighborhood(item[0], poly);
        }
        loadPlaces();
    }
    //begin ajax code to load our '36061_minimal.json' file
    if (xhr !== null) {
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    setupNeighborhoods(eval('(' + xhr.responseText + ')'));
                } else {
                    alert('failed to load json via ajax!');
                }
            }
        };
        xhr.open('GET', '36061_minimal.json', true);
        xhr.send(null);
    }
}

google.maps.event.addDomListener(window, 'load', initialize);

function getXhr() {
    var xhr = null;
    try{//Mozilla, Safari, IE 7+...
        xhr = new XMLHttpRequest();
        if (xhr.overrideMimeType) {
            xhr.overrideMimeType('text/xml');
        }
    } catch(e) {// IE 6, use only Msxml2.XMLHTTP.(6 or 3).0,
         //see: http://blogs.msdn./xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
        try{
            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
        }catch(e){
            try{
                xhr = new ActiveXObject("Msxml2.XMLHTTP.3.0");
            }catch(e){}
        }
    }
    return xhr;
}

</script>
</head>
<body>
<div id="map_canvas" style="width:780px; height:600px; margin:10px auto;"></div>
</body>
</html>

For neighborhood boundaries you'll need to look at a third party supplier like Maponics (now part of Pitney-Bowes). Here's an example from Realtor. using the Maponics neighborhood boundaries on a different platform: https://www.realtor./realestateandhomes-search/Greenwich-Village_New-York_NY#/lat-40.731/lng--73.994/zl-15

发布评论

评论列表(0)

  1. 暂无评论