Javascript 谷歌地图 API V3 - 同一地点的多个标记

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3548920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:07:33  来源:igfitidea点击:

Google maps API V3 - multiple markers on exact same spot

javascriptgoogle-mapsgoogle-maps-api-3

提问by Louzoid

Bit stuck on this one. I am retrieving a list of geo coords via JSON and popping them onto a google map. All is working well except in the instance when I have two or more markers on the exact same spot. The API only displays 1 marker - the top one. This is fair enough I suppose but would like to find a way to display them all somehow.

有点卡在这个上。我正在通过 JSON 检索地理坐标列表并将它们弹出到谷歌地图上。除了在完全相同的位置有两个或更多标记的情况外,一切都运行良好。API 仅显示 1 个标记 - 最上面的标记。我想这很公平,但我想找到一种方法来以某种方式显示它们。

I've searched google and found a few solutions but they mostly seem to be for V2 of the API or just not that great. Ideally I'd like a solution where you click some sort of group marker and that then shows the markers clustered around the spot they are all in.

我在谷歌上搜索并找到了一些解决方案,但它们似乎大多适用于 API 的 V2 或者不是那么好。理想情况下,我想要一个解决方案,您可以单击某种组标记,然后显示聚集在它们所在位置周围的标记。

Anybody had this problem or similar and would care to share a solution?

任何人都有这个问题或类似的问题,并愿意分享解决方案?

采纳答案by Tad Wohlrapp

Take a look at OverlappingMarkerSpiderfier.
There's a demo page, but they don't show markers which are exactly on the same spot, only some which are very close together.

看看OverlappingMarkerSpiderfier
有一个演示页面,但它们没有显示完全在同一位置的标记,只有一些非常靠近。

But a real life example with markers on the exact same spot can be seen on http://www.ejw.de/ejw-vor-ort/(scroll down for the map and click on a few markers to see the spider-effect).

但是在http://www.ejw.de/ejw-vor-ort/上可以看到一个真实的例子,在完全相同的地方有标记(向下滚动地图并点击几个标记以查看蜘蛛效果)。

That seems to be the perfect solution for your problem.

这似乎是您问题的完美解决方案。

回答by Ignatius

Offsetting the markers isn't a real solution if they're located in the same building. What you might want to do is modify the markerclusterer.js like so:

如果标记位于同一建筑物中,则偏移标记并不是真正的解决方案。您可能想要做的是像这样修改 markerclusterer.js:

  1. Add a prototype click method in the MarkerClusterer class, like so - we will override this later in the map initialize() function:

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    
  2. In the ClusterIcon class, add the following code AFTER the clusterclick trigger:

    // Trigger the clusterclick event.
    google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
    
    var zoom = this.map_.getZoom();
    var maxZoom = markerClusterer.getMaxZoom();
    // if we have reached the maxZoom and there is more than 1 marker in this cluster
    // use our onClick method to popup a list of options
    if (zoom >= maxZoom && this.cluster_.markers_.length > 1) {
       return markerClusterer.onClickZoom(this);
    }
    
  3. Then, in your initialize() function where you initialize the map and declare your MarkerClusterer object:

    markerCluster = new MarkerClusterer(map, markers);
    // onClickZoom OVERRIDE
    markerCluster.onClickZoom = function() { return multiChoice(markerCluster); }
    

    Where multiChoice() is YOUR (yet to be written) function to popup an InfoWindow with a list of options to select from. Note that the markerClusterer object is passed to your function, because you will need this to determine how many markers there are in that cluster. For example:

    function multiChoice(mc) {
         var cluster = mc.clusters_;
         // if more than 1 point shares the same lat/long
         // the size of the cluster array will be 1 AND
         // the number of markers in the cluster will be > 1
         // REMEMBER: maxZoom was already reached and we can't zoom in anymore
         if (cluster.length == 1 && cluster[0].markers_.length > 1)
         {
              var markers = cluster[0].markers_;
              for (var i=0; i < markers.length; i++)
              {
                  // you'll probably want to generate your list of options here...
              }
    
              return false;
         }
    
         return true;
    }
    
  1. 在 MarkerClusterer 类中添加一个原型 click 方法,就像这样 - 我们稍后将在 map initialize() 函数中覆盖它:

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    
  2. 在 ClusterIcon 类中,在 clusterclick 触发器之后添加以下代码:

    // Trigger the clusterclick event.
    google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
    
    var zoom = this.map_.getZoom();
    var maxZoom = markerClusterer.getMaxZoom();
    // if we have reached the maxZoom and there is more than 1 marker in this cluster
    // use our onClick method to popup a list of options
    if (zoom >= maxZoom && this.cluster_.markers_.length > 1) {
       return markerClusterer.onClickZoom(this);
    }
    
  3. 然后,在您初始化地图并声明您的 MarkerClusterer 对象的 initialize() 函数中:

    markerCluster = new MarkerClusterer(map, markers);
    // onClickZoom OVERRIDE
    markerCluster.onClickZoom = function() { return multiChoice(markerCluster); }
    

    multiChoice() 是您的(尚未编写)函数,用于弹出一个带有可供选择的选项列表的信息窗口。请注意,markerClusterer 对象被传递给您的函数,因为您将需要它来确定该集群中有多少个标记。例如:

    function multiChoice(mc) {
         var cluster = mc.clusters_;
         // if more than 1 point shares the same lat/long
         // the size of the cluster array will be 1 AND
         // the number of markers in the cluster will be > 1
         // REMEMBER: maxZoom was already reached and we can't zoom in anymore
         if (cluster.length == 1 && cluster[0].markers_.length > 1)
         {
              var markers = cluster[0].markers_;
              for (var i=0; i < markers.length; i++)
              {
                  // you'll probably want to generate your list of options here...
              }
    
              return false;
         }
    
         return true;
    }
    

回答by Steve Graham

I used this alongside jQuery and it does the job:

我将它与 jQuery 一起使用,它完成了这项工作:

var map;
var markers = [];
var infoWindow;

function initialize() {
    var center = new google.maps.LatLng(-29.6833300, 152.9333300);

    var mapOptions = {
        zoom: 5,
        center: center,
        panControl: false,
        zoomControl: false,
        mapTypeControl: false,
        scaleControl: false,
        streetViewControl: false,
        overviewMapControl: false,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      }


    map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

    $.getJSON('jsonbackend.php', function(data) {
        infoWindow = new google.maps.InfoWindow();

        $.each(data, function(key, val) {
            if(val['LATITUDE']!='' && val['LONGITUDE']!='')
            {                
                // Set the coordonates of the new point
                var latLng = new google.maps.LatLng(val['LATITUDE'],val['LONGITUDE']);

                //Check Markers array for duplicate position and offset a little
                if(markers.length != 0) {
                    for (i=0; i < markers.length; i++) {
                        var existingMarker = markers[i];
                        var pos = existingMarker.getPosition();
                        if (latLng.equals(pos)) {
                            var a = 360.0 / markers.length;
                            var newLat = pos.lat() + -.00004 * Math.cos((+a*i) / 180 * Math.PI);  //x
                            var newLng = pos.lng() + -.00004 * Math.sin((+a*i) / 180 * Math.PI);  //Y
                            var latLng = new google.maps.LatLng(newLat,newLng);
                        }
                    }
                }

                // Initialize the new marker
                var marker = new google.maps.Marker({map: map, position: latLng, title: val['TITLE']});

                // The HTML that is shown in the window of each item (when the icon it's clicked)
                var html = "<div id='iwcontent'><h3>"+val['TITLE']+"</h3>"+
                "<strong>Address: </strong>"+val['ADDRESS']+", "+val['SUBURB']+", "+val['STATE']+", "+val['POSTCODE']+"<br>"+
                "</div>";

                // Binds the infoWindow to the point
                bindInfoWindow(marker, map, infoWindow, html);

                // Add the marker to the array
                markers.push(marker);
            }
        });

        // Make a cluster with the markers from the array
        var markerCluster = new MarkerClusterer(map, markers, { zoomOnClick: true, maxZoom: 15, gridSize: 20 });
    });
}

function markerOpen(markerid) {
    map.setZoom(22);
    map.panTo(markers[markerid].getPosition());
    google.maps.event.trigger(markers[markerid],'click');
    switchView('map');
}

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

回答by DzinX

Expanding on Chaoley's answer, I implemented a function that, given a list of locations (objects with lngand latproperties) whose coordinates are exactly the same, moves them away from their original location a little bit (modifying objects in place). They then form a nice circle around the center point.

扩展Chaoley 的答案,我实现了一个函数,在给定坐标完全相同的位置列表(具有lnglat属性的对象)的情况下,将它们从原始位置移开一点(修改对象到位)。然后它们围绕中心点形成一个漂亮的圆圈。

I found that, for my latitude (52deg North), 0.0003 degrees of circle radius work best, and that you have to make up for the difference between latitude and longitude degrees when converted to kilometres. You can find approximate conversions for your latitude here.

我发现,对于我的纬度(北纬 52 度),0.0003 度的圆半径效果最好,并且当转换为公里时,您必须弥补纬度和经度之间的差异。您可以在此处找到您所在纬度的近似换算。

var correctLocList = function (loclist) {
    var lng_radius = 0.0003,         // degrees of longitude separation
        lat_to_lng = 111.23 / 71.7,  // lat to long proportion in Warsaw
        angle = 0.5,                 // starting angle, in radians
        loclen = loclist.length,
        step = 2 * Math.PI / loclen,
        i,
        loc,
        lat_radius = lng_radius / lat_to_lng;
    for (i = 0; i < loclen; ++i) {
        loc = loclist[i];
        loc.lng = loc.lng + (Math.cos(angle) * lng_radius);
        loc.lat = loc.lat + (Math.sin(angle) * lat_radius);
        angle += step;
    }
};

回答by Nathan Colgate

@Ignatius most excellent answer, updated to work with v2.0.7 of MarkerClustererPlus.

@Ignatius 最优秀的答案,已更新以与 MarkerClustererPlus 的 v2.0.7 一起使用。

  1. Add a prototype click method in the MarkerClusterer class, like so - we will override this later in the map initialize() function:

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
    
  2. In the ClusterIcon class, add the following code AFTER the click/clusterclick trigger:

    // EXISTING CODE (around line 143)
    google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
    google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
    
    // BEGIN MODIFICATION
    var zoom = mc.getMap().getZoom();
    // Trying to pull this dynamically made the more zoomed in clusters not render
    // when then kind of made this useless. -NNC @ BNB
    // var maxZoom = mc.getMaxZoom();
    var maxZoom = 15;
    // if we have reached the maxZoom and there is more than 1 marker in this cluster
    // use our onClick method to popup a list of options
    if (zoom >= maxZoom && cClusterIcon.cluster_.markers_.length > 1) {
        return mc.onClick(cClusterIcon);
    }
    // END MODIFICATION
    
  3. Then, in your initialize() function where you initialize the map and declare your MarkerClusterer object:

    markerCluster = new MarkerClusterer(map, markers);
    // onClick OVERRIDE
    markerCluster.onClick = function(clickedClusterIcon) { 
      return multiChoice(clickedClusterIcon.cluster_); 
    }
    

    Where multiChoice() is YOUR (yet to be written) function to popup an InfoWindow with a list of options to select from. Note that the markerClusterer object is passed to your function, because you will need this to determine how many markers there are in that cluster. For example:

    function multiChoice(clickedCluster) {
      if (clickedCluster.getMarkers().length > 1)
      {
        // var markers = clickedCluster.getMarkers();
        // do something creative!
        return false;
      }
      return true;
    };
    
  1. 在 MarkerClusterer 类中添加一个原型 click 方法,就像这样 - 我们稍后将在 map initialize() 函数中覆盖它:

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
    
  2. 在 ClusterIcon 类中,在 click/clusterclick 触发器之后添加以下代码:

    // EXISTING CODE (around line 143)
    google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
    google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
    
    // BEGIN MODIFICATION
    var zoom = mc.getMap().getZoom();
    // Trying to pull this dynamically made the more zoomed in clusters not render
    // when then kind of made this useless. -NNC @ BNB
    // var maxZoom = mc.getMaxZoom();
    var maxZoom = 15;
    // if we have reached the maxZoom and there is more than 1 marker in this cluster
    // use our onClick method to popup a list of options
    if (zoom >= maxZoom && cClusterIcon.cluster_.markers_.length > 1) {
        return mc.onClick(cClusterIcon);
    }
    // END MODIFICATION
    
  3. 然后,在您初始化地图并声明您的 MarkerClusterer 对象的 initialize() 函数中:

    markerCluster = new MarkerClusterer(map, markers);
    // onClick OVERRIDE
    markerCluster.onClick = function(clickedClusterIcon) { 
      return multiChoice(clickedClusterIcon.cluster_); 
    }
    

    multiChoice() 是您的(尚未编写)函数,用于弹出一个带有可供选择的选项列表的信息窗口。请注意,markerClusterer 对象被传递给您的函数,因为您将需要它来确定该集群中有多少个标记。例如:

    function multiChoice(clickedCluster) {
      if (clickedCluster.getMarkers().length > 1)
      {
        // var markers = clickedCluster.getMarkers();
        // do something creative!
        return false;
      }
      return true;
    };
    

回答by Matthew Fox

The answers above are more elegant, but I found a quick and dirty way that actually works really really incredibly well. You can see it in action at www.buildinglit.com

上面的答案更优雅,但我发现了一种快速而肮脏的方法,实际上效果非常好。您可以在www.buildinglit.com 上看到它的实际效果

All I did was add a random offset to the latitude and longditude to my genxml.php page so it returns slightly different results each time with offset each time the map is created with markers. This sounds like a hack, but in reality you only need the markers to move a slight nudge in a random direction for them to be clickable on the map if they are overlapping. It actually works really well, I would say better than the spider method because who wants to deal with that complexity and have them spring everywhere. You just want to be able to select the marker. Nudging it randomly works perfect.

我所做的只是向我的 genxml.php 页面添加纬度和经度的随机偏移量,因此每次使用标记创建地图时,每次都会返回略有不同的结果和偏移量。这听起来像一个黑客,但实际上你只需要标记在随机方向上轻微移动,如果它们重叠,它们就可以在地图上点击。它实际上工作得非常好,我会说比蜘蛛方法更好,因为谁想要处理这种复杂性并让它们无处不在。您只想能够选择标记。随机轻推它很完美。

Here is an example of the while statement iteration node creation in my php_genxml.php

这是我的 php_genxml.php 中的 while 语句迭代节点创建示例

while ($row = @mysql_fetch_assoc($result)){ $offset = rand(0,1000)/10000000;
$offset2 = rand(0, 1000)/10000000;
$node = $dom->createElement("marker");
$newnode = $parnode->appendChild($node);
$newnode->setAttribute("name", $row['name']);
$newnode->setAttribute("address", $row['address']);
$newnode->setAttribute("lat", $row['lat'] + $offset);
$newnode->setAttribute("lng", $row['lng'] + $offset2);
$newnode->setAttribute("distance", $row['distance']);
$newnode->setAttribute("type", $row['type']);
$newnode->setAttribute("date", $row['date']);
$newnode->setAttribute("service", $row['service']);
$newnode->setAttribute("cost", $row['cost']);
$newnode->setAttribute("company", $company);

Notice under lat and long there is the +offset. from the 2 variables above. I had to divide random by 0,1000 by 10000000 in order to get a decimal that was randomly small enough to just barely move the markers around. Feel free to tinker with that variable to get one that is more precise for your needs.

注意 lat 和 long 下有 +offset。从上面的2个变量。我必须将 random 除以 0,1000 除以 10000000 才能得到一个随机小到足以几乎不移动标记的小数。随意修改该变量以获得更适合您需求的变量。

回答by Chaoley

For situations where there are multiple services in the same building you could offset the markers just a little, (say by .001 degree), in a radius from the actual point. This should also produce a nice visual effect.

对于在同一建筑物中有多个服务的情况,您可以将标记稍微偏移(例如 0.001 度),在距离实际点的半径内。这也应该产生很好的视觉效果。

回答by Budgie

Check out Marker Clustererfor V3 - this library clusters nearby points into a group marker. The map zooms in when the clusters are clicked. I'd imagine when zoomed right in you'd still have the same problem with markers on the same spot though.

查看V3 的Marker Clusterer- 这个库将附近的点聚集成一个组标记。单击群集时地图会放大。我想当你放大时,你仍然会在同一个地方有同样的问题。

回答by Chris Halcrow

This is more of a stopgap 'quick and dirty' solution similar to the one Matthew Fox suggests, this time using JavaScript.

这更像是一种类似于 Matthew Fox 建议的权宜之计的“快速而肮脏”的解决方案,这次使用的是 JavaScript。

In JavaScript you can just offset the lat and long of all of your locations by adding a small random offset to both e.g.

在 JavaScript 中,您可以通过向所有位置添加一个小的随机偏移量来抵消所有位置的经纬度,例如

myLocation[i].Latitude+ = (Math.random() / 25000)

myLocation[i].Latitude+ = (Math.random() / 25000)

(I found that dividing by 25000gives enough separation but doesn't move the marker significantly from the exact location e.g. a specific address)

(我发现除以25000 可以提供足够的间隔,但不会将标记从确切位置(例如特定地址)显着移动)

This makes a reasonably good job of offsetting them from one another, but only after you've zoomed in closely. When zoomed out, it still won't be clear that there are multiple options for the location.

这可以很好地将它们相互抵消,但前提是您必须仔细放大。缩小后,仍然不清楚该位置有多个选项。

回答by Nande

I like simple solutions so here's mine. Instead of modifying the lib, which would make it harder to mantain. you can simply watch the event like this

我喜欢简单的解决方案,所以这是我的。而不是修改lib,这会使维护变得更加困难。你可以简单地观看这样的活动

google.maps.event.addListener(mc, "clusterclick", onClusterClick);

then you can manage it on

然后你可以管理它

function onClusterClick(cluster){
    var ms = cluster.getMarkers();

i, ie, used bootstrap to show a panel with a list. which i find much more confortable and usable than spiderfying on "crowded" places. (if you are using a clusterer chances are you will end up with collisions once you spiderfy). you can check the zoom there too.

我,即,使用引导程序来显示带有列表的面板。我发现这比在“拥挤”的地方爬行更舒适和可用。(如果您使用的是聚类器,那么一旦您使用蜘蛛网,您最终会发生碰撞)。你也可以在那里检查缩放。

btw. i just found leaflet and it seems to work much better, the cluster AND spiderfy works very fluidly http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.htmland it's open-source.

顺便提一句。我刚刚找到了传单,它似乎工作得更好,集群和 spiderfy 工作得非常流畅http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html并且它是开源的。