Google maps API V3 - 完全相同位置的多个标记

时间:2010-08-23 15:13:45

标签: javascript google-maps google-maps-api-3

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

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

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

19 个答案:

答案 0 :(得分:110)

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

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

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

答案 1 :(得分:32)

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

  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. 然后,在initialize()函数中初始化地图并声明MarkerClusterer对象:

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

    其中multiChoice()是您的(尚未编写)函数,用于弹出InfoWindow,其中包含可供选择的选项列表。请注意,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;
    }
    

答案 2 :(得分:15)

我和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);

答案 3 :(得分:14)

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

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

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;
    }
};

答案 4 :(得分:9)

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

  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. 然后,在initialize()函数中初始化地图并声明MarkerClusterer对象:

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

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

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

答案 5 :(得分:4)

上面的答案更优雅,但我找到了一种快速而肮脏的方式,实际上真的非常好用。您可以在www.buildinglit.com

看到它的实际效果

我所做的只是在我的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);

注意在lat和long下有+偏移量。从上面的2个变量。我不得不将随机除以0,1000乘以10000000,以获得一个随机小的十进制小数,几乎不会移动标记。您可以随意修改该变量,以获得更符合您需求的变量。

答案 6 :(得分:3)

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

答案 7 :(得分:2)

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

答案 8 :(得分:1)

已更新,可与MarkerClustererPlus合作。

  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) {
    var markers = cClusterIcon.cluster_.markers_;
    var a = 360.0 / markers.length;
    for (var i=0; i < markers.length; i++)
    {
        var pos = markers[i].getPosition();
        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 finalLatLng = new google.maps.LatLng(newLat,newLng);
        markers[i].setPosition(finalLatLng);
        markers[i].setMap(cClusterIcon.cluster_.map_);
    }
    cClusterIcon.hide();
    return ;
  }
  // END MODIFICATION

答案 9 :(得分:1)

我喜欢简单的解决方案,所以这里是我的。 而不是修改lib,这将使得难以保持。你可以简单地观看这样的活动

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

然后你可以在

上进行管理
function onClusterClick(cluster){
    var ms = cluster.getMarkers();

i,即使用bootstrap显示带有列表的面板。我发现它比蜘蛛网更加舒适和可用,拥挤的#34;地方。 (如果你使用的是聚类器,那么一旦你发生了蜘蛛侠,你最终会遇到碰撞)。 你也可以在那里查看缩放。

顺便说一句。我刚刚发现传单,它似乎工作得更好,群集和蜘蛛网非常流畅http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html 它是开源的。

答案 10 :(得分:1)

这更像是一个快速而又肮脏的权宜之计。类似于马修福克斯建议的解决方案,这次是使用JavaScript。

在JavaScript中,您可以通过向添加一个小的随机偏移来偏移所有位置的纬度和长度。

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

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

这可以很好地抵消彼此之间的差异,但只有在你紧密放大之后才能相互抵消。缩小时,仍然不清楚该位置有多个选项。

答案 11 :(得分:0)

扩展上面给出的答案,只需确保在初始化地图对象时设置maxZoom选项。

答案 12 :(得分:0)

请检查:https://github.com/plank/MarkerClusterer

当您在同一位置有多个标记时,这是MarkerCluster被修改为在群集标记中具有infoWindow。

您可以在此处查看其工作原理:http://culturedays.ca/en/2013-activities

答案 13 :(得分:0)

当用户放大到最大值时,给予偏移会使标记变得更远。所以我找到了实现这一目标的方法。这可能不是一个正确的方法,但它运作得很好。

// This code is in swift
for loop markers
{
//create marker
let mapMarker = GMSMarker()
mapMarker.groundAnchor = CGPosition(0.5, 0.5)
mapMarker.position = //set the CLLocation
//instead of setting marker.icon set the iconView
let image:UIIMage = UIIMage:init(named:"filename")
let imageView:UIImageView = UIImageView.init(frame:rect(0,0, ((image.width/2 * markerIndex) + image.width), image.height))
imageView.contentMode = .Right
imageView.image = image
mapMarker.iconView = imageView
mapMarker.map = mapView
}

设置标记的zIndex,以便您可以看到要在顶部看到的标记图标,否则它将为自动交换等标记设置动画。当用户点击标记处理时,zIndex使用zIndex Swap将标记置于顶部。

答案 14 :(得分:0)

如何逃脱它.. 的 [夫特]

    var clusterArray = [String]()
    var pinOffSet : Double = 0
    var pinLat = yourLat
    var pinLong = yourLong
    var location = pinLat + pinLong

即将创建一个新标记?检查clusterArray并操纵它的偏移量

 if(!clusterArray.contains(location)){
        clusterArray.append(location)
    } else {

        pinOffSet += 1
        let offWithIt = 0.00025 // reasonable offset with zoomLvl(14-16)
        switch pinOffSet {
        case 1 : pinLong = pinLong + offWithIt ; pinLat = pinLat + offWithIt
        case 2 : pinLong = pinLong + offWithIt ; pinLat = pinLat - offWithIt
        case 3 : pinLong = pinLong - offWithIt ; pinLat = pinLat - offWithIt
        case 4 : pinLong = pinLong - offWithIt ; pinLat = pinLat + offWithIt
        default : print(1)
        }


    }

结果

enter image description here

答案 15 :(得分:0)

在Matthew Fox狡猾的天才答案中,我在设置标记对象时为每个经纬度添加了一个小的随机偏移量。例如:

new LatLng(getLat()+getMarkerOffset(), getLng()+getMarkerOffset()),

private static double getMarkerOffset(){
    //add tiny random offset to keep markers from dropping on top of themselves
    double offset =Math.random()/4000;
    boolean isEven = ((int)(offset *400000)) %2 ==0;
    if (isEven) return  offset;
    else        return -offset;
}

答案 16 :(得分:0)

除了上述答案外,还提供了php和wordpress中的替代快速解决方案。对于此示例,我将通过ACF存储location字段,并遍历帖子以获取该数据。

我发现将lat / lng存储在一个数组中,并检查该数组的值以查看循环是否匹配,然后我们可以用要移动点数的量更新该数组中的值。

//This is the value to shift the pips by. I found this worked best with markerClusterer
$add_to_latlng = 0.00003;

while ($query->have_posts()) {
    $query->the_post();
    $meta = get_post_meta(get_the_ID(), "postcode", true); //uses an acf field to store location
    $lat = $meta["lat"];
    $lng = $meta["lng"];

    if(in_array($meta["lat"],$lat_checker)){ //check if this meta matches
        
        //if matches then update the array to a new value (current value + shift value)
        // This is using the lng value for a horizontal line of pips, use lat for vertical, or both for a diagonal
        if(isset($latlng_storer[$meta["lng"]])){
            $latlng_storer[$meta["lng"]] = $latlng_storer[$meta["lng"]] + $add_to_latlng;
            $lng = $latlng_storer[$meta["lng"]];
        } else {
            $latlng_storer[$meta["lng"]] = $meta["lng"];
            $lng = $latlng_storer[$meta["lng"]];
        }

    } else {
        $lat_checker[] = $meta["lat"]; //just for simple checking of data
        $latlng_storer[$meta["lat"]] = floatval($meta["lat"]);
        $latlng_storer[$meta["lng"]] =  floatval($meta["lng"]);
    }

    $entry[] = [
        "lat" => $lat,
        "lng" => $lng,
    //...Add all the other post data here and use this array for the pips
    ];
} // end query

一旦我抓住了这些位置,我就会对$ entry变量进行json编码,并在我的JS中使用它。

let locations = <?=json_encode($entry)?>;

我知道这是一个相当具体的情况,但我希望这能对某人有所帮助!

答案 17 :(得分:0)

我使用了markerclustererplus,对我来说这是有效的:

//Code
google.maps.event.addListener(cMarkerClusterer, "clusterclick", function (c) {
            var markers = c.getMarkers();
            
            //Check Markers array for duplicate position and offset a little
            if (markers .length > 1) {
                //Check if all markers are in the same position (with 4 significant digits)
                if (markers .every((val, index, arr) => (val.getPosition().lat().toFixed(4) == arr[0].getPosition().lat().toFixed(4)) && (val.getPosition().lng().toFixed(4) == arr[0].getPosition().lng().toFixed(4)))) { /
                    //Don't modify first element
                    for (i = 1; i < markers.length; i++) {
                        var existingMarker = markers[i];
                        var pos = existingMarker.getPosition();                        
                        var quot = 360.0 / markers.length;
                        var newLat = pos.lat() + -.00008 * Math.cos(+quot * i); //+ -.00008 * Math.cos((+quot * i) / 180 * Math.PI);  //x                        
                        var newLng = pos.lng() + -.00008 * Math.sin(+quot * i);  //+ -.0008 * Math.sin((+quot * i) / 180 * Math.PI);  //Y
                        existingMarker.setPosition(new google.maps.LatLng(newLat, newLng));                        
                    }
                    let cZoom = map.getZoom();
                    map.setZoom(cZoom-1);
                    map.setZoom(cZoom+1);
                } 
            }            
        });

答案 18 :(得分:-2)

扩展上面的答案,当你加入字符串,而不是添加/减去位置(例如&#34; 37.12340-0.00069&#34;)时,将你原来的纬度/经度转换成浮点数,例如:使用parseFloat(),然后添加或减去更正。