web-dev-qa-db-ja.com

Google Maps API V3-まったく同じ場所に複数のマーカー

これに少しこだわった。 JSONを介して地理座標のリストを取得し、それらをGoogleマップにポップしています。まったく同じ場所に2つ以上のマーカーがある場合を除き、すべて正常に機能しています。 APIは、マーカーを1つだけ表示します-一番上のマーカー。これは私が思うに十分に公平ですが、それらをすべて何らかの形で表示する方法を見つけたいと思います。

私はグーグルを検索し、いくつかの解決策を見つけましたが、それらは主にAPIのV2用であるか、それほど素晴らしいものではないようです。理想的には、何らかの種類のグループマーカーをクリックして、すべてのマーカーがそのスポットの周りにクラスター化されたソリューションが必要です。

誰もがこの問題または同様の問題を抱えていて、解決策を共有したいと思いますか?

109
Louzoid

OverlappingMarkerSpiderfier をご覧ください。
デモページはありますが、同じ場所にあるマーカーは表示されず、非常に近いマーカーのみが表示されます。

しかし、マーカーがまったく同じ場所にある実際の例は、 http://www.ejw.de/ejw-vor-ort/ で見ることができます(マップをスクロールして、いくつかをクリックしますスパイダー効果を見るためのマーカー)。

それはあなたの問題の完璧な解決策のようです。

111
Tad

同じ建物内にマーカーが配置されている場合、マーカーのオフセットは実際の解決策ではありません。あなたがしたいことは、markerclusterer.jsを次のように変更することです:

  1. 以下のように、MarkerClustererクラスにプロトタイプのクリックメソッドを追加します-後でマップの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()は、選択するオプションのリストを含むInfoWindowをポップアップするYOUR(まだ記述されていない)関数です。 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;
    }
    
32
Ignatius

私はこれを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);
15
Steve Graham

Chaoley's answer を展開すると、座標がまったく同じである場所(lngおよびlatプロパティを持つオブジェクト)のリストを指定して、それらを移動する関数を実装しました元の場所を少し変更します(所定の場所にオブジェクトを変更します)。次に、中心点の周りに素敵な円を形成します。

私の緯度(北緯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;
    }
};
14
DzinX

@Ignatiusの最も優れた回答。MarkerClustererPlusv2.0.7で動作するように更新されました。

  1. 以下のように、MarkerClustererクラスにプロトタイプのクリックメソッドを追加します-後でマップの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()は、選択するオプションのリストを含むInfoWindowをポップアップするYOUR(まだ記述されていない)関数です。 markerClustererオブジェクトは関数に渡されることに注意してください。これは、クラスター内にマーカーがいくつあるかを判断するために必要になるためです。例えば:

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

上記の答えはよりエレガントですが、実際に非常にうまく機能する迅速で汚い方法を見つけました。実際の動作は 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の下に+ offsetがあることに注意してください。上記の2つの変数から。マーカーを移動するのに十分なだけランダムに小さい小数を取得するには、ランダムに0,1000で10000000で除算する必要がありました。必要に応じてより正確な変数を取得するには、その変数を自由に変更してください。

4
Matthew Fox

同じ建物内に複数のサービスがある状況では、実際のポイントから半径内でマーカーを少し(たとえば、.001度)オフセットできます。これにより、素敵な視覚効果も得られます。

3
Chaoley

これは、今回はJavaScriptを使用して、Matthew Foxが提案するものに似た、一時的な「迅速で汚い」ソリューションです。

JavaScriptでは、小さなランダムオフセットを両方に追加することで、すべての場所の緯度と経度をオフセットできますe.g。

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

250で割ると十分な分離が得られるが、特定の住所などの正確な位置からマーカーを大きく動かさないことがわかりました)

これは、それらを互いにオフセットするのにかなり良い仕事になりますが、それはあなたが密接にズームインした後にのみです。ズームアウトしても、場所に複数のオプションがあることは依然として明確ではありません。

2
Chris Halcrow

V3の場合は Marker Clusterer をチェックしてください-このライブラリは近くのポイントをグループマーカーにクラスタリングします。クラスタがクリックされると、マップがズームインします。ただし、右にズームすると、同じ場所にあるマーカーでも同じ問題が発生すると思います。

2
Budgie

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
1
thoshino

私は簡単な解決策が好きなので、これが私のものです。 libを変更する代わりに、管理が難しくなります。このようなイベントを簡単に見ることができます

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

その後、あなたはそれを管理することができます

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

i、つまり、リストにパネルを表示するためにbootstrapを使用しました。 「混雑した」場所でのスパイダーフィッシングよりもずっと快適で使いやすいと思います。 (クラスタ化の機会を使用している場合は、スパイダーになったときに衝突が発生する可能性があります)。ズームも確認できます。

ところで。私はちょうどリーフレットを見つけましたが、クラスターAND spiderfyは非常に滑らかに動作します http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html =そして、それはオープンソースです。

1
Nande

それをどうやってやるのか..[Swift]

    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

0
Alexandros

上記の回答を展開して、マップオブジェクトを初期化するときにmaxZoomオプションを設定するようにしてください。

0
HosseinSafy

これを確認してください: https://github.com/plank/MarkerClusterer

これは、同じ位置に複数のマーカーがある場合に、クラスターマーカーに情報ウィンドウを持つように変更されたMarkerClusterです。

ここでどのように機能するかを見ることができます: http://culturedays.ca/en/2013-activities

0

マシューフォックスの卑劣な天才の答えに加えて、マーカーオブジェクトを設定するときに、各緯度と経度に小さなランダムオフセットを追加しました。例えば:

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;
}
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を処理してマーカーを一番上に表示します。

0
Satheesh G