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


115

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

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

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

回答:


116

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

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

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


これはすばらしいことであり、正確に同じ緯度/経度のマーカーを処理しないクラスターライブラリーからの必要性を完全に満たします。
ジャスティン

MarkerClustererOverlappingMarkerSpiderfierと組み合わせて使用する場合の適切なオプションは、クラスターコンストラクターでオプションを設定することです。これにより、指定したズームレベルの後にマーカーが「クラスター化」されます。maxZoom
Diederik 2015年

@TadあなたがそのWebサイトの開発者かどうかはわかりませんが、ejw.deのマップが壊れていることをお知らせします。JSエラーが発生します。
Alex

提案されたデモを確認しましたが、死んでいるようです。さらなる研究の後、私はこの例をマーカークラスターとSpiderifierを統合する方法で見つけました。ライブデモはありませんが、readmeを複製してそれに従ってください。 github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax

34

マーカーが同じ建物内にある場合、マーカーをオフセットすることは実際の解決策ではありません。あなたがしたいかもしれないことは、以下のように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をポップアップする(まだ作成されていない)関数です。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;
    }

17

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

Thxは私にとっては完璧に動作します:)何時間か以来私が探していたもの:p
Jicao

美しくエレガントなソリューション。ありがとうございました!
Concept211 2018年

少し相殺する完璧なソリューション!可能ですか、マーカーに
カーソルを合わせる

14

Chaoleyの回答を拡張して、場所のリスト(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;
    }
};

1
DzinX、私は過去4時間を費やして、無駄にコードを私のコードに実装する方法を理解しようとしました。私はこれで下手であるという結論に達しました、そしてあなたの助けを本当に感謝します。ここに私がコードをプラグインしようとしているところがあります:pastebin.com/5qYbvybm-どんな助けもいただければ幸いです!ありがとう!
WebMW 2013

WebMW:XMLからマーカーを抽出した後、最初にそれらをリストにグループ化して、各リストにまったく同じ場所を指すマーカーが含まれるようにする必要があります。次に、複数の要素を含む各リストで、correctLocList()を実行します。その後、google.maps.Markerオブジェクトを作成します。
DzinX 2013

そのそれはちょうどから私の緯度long型を変更する....動作していない 37.68625400000000000,-75.7166980000000000037.686254,-75.716698、すべての値に対して同じであるにも... Mは以下のように...何かを期待37.68625400000000000,-75.71669800000000000して 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678、私はまた私の角度を変えてみました... ..etc。
Premshankar Tiwari 2013

うーん、少し掘り下げて、@ DzinXがまだこのエリアにあることを期待しましょう。メイト(または他の誰か)がこれらの数値をすべて取得する方法を説明できますか?Lng_radiusなど?ありがとう。:)
Vae

1
@Vae:半径は、円の大きさです。円がピクセル単位で丸く見えるようにしたいので、現在の場所で、緯度1度と経度1度の比率が何であるかを知る必要があります(マップ上のピクセル単位)。これは一部のWebサイトで見つけることができますが、数値が正しくなるまで試してみることもできます。角度は、最初のピンが上から右にどれだけあるかです。
DzinX 2017

11

@Ignatiusの最も優れた回答。MarkerClustererPlusのv2.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をポップアップする(まだ作成されていない)関数です。markerClustererオブジェクトは関数に渡されることに注意してください。これは、そのクラスターにあるマーカーの数を決定するために必要になるためです。例えば:

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

1
ありがとう、これは完全に機能します!あなたのmaxZoom問題に関しては、これはmaxZoomが設定されていないか、クラスターのmaxZoomがマップのズームよりも大きい場合にのみ問題になると思います。ハードコードをこのスニペットに置き換えましたが、うまく機能しているようです:var maxZoom = mc.getMaxZoom(); if (null === maxZoom || maxZoom > mc.getMap().maxZoom) { maxZoom = mc.getMap().maxZoom; if (null === maxZoom) { maxZoom = 15; } }
Pascal

私はこれが本当に古いことを知っていますが、私はこのソリューションを使用しようとしています。動作していますが、クラスターデータで情報ウィンドウを表示する場合は、最初にクリックしたクラスターのマーカー位置を取得して、情報ウィンドウを表示するにはどうすればよいですか?マーカーは私のデータの配列です...しかし、クラスターのアイコン/マーカーに情報ウィンドウを表示するにはどうすればよいですか?
user756659 2014

@ user756659 MULTICHOICEであなたのget緯度/経度経由:clickedCluster.center_.lat()/clickedCluster.center_.lng()
イェンス・コールズ

1
@Pascalと少し難読化すると、これでうまくいく可能性がありますが、Pythonのtaoが言うように。「可読性」。var maxZoom = Math.min(mc.getMaxZoom()|| 15、mc.getMap()。maxZoom || 15);
Nande、2016年

6

上記の答えはよりエレガントですが、私は実際に本当に信じられないほどうまく機能する迅速で汚い方法を見つけました。あなたは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 and longの下に+ offsetがあることに注意してください。上記の2つの変数から。マーカーをほとんど動かさない程度にランダムに小さい10進数を取得するには、ランダムを0,1000で10000000で割る必要がありました。その変数を自由にいじって、ニーズにより正確な変数を取得してください。


涼しい!これは私が非常に便利だと思った汚いハックです
。GoogleMaps

3

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


3

これは、Matthew Foxが提案するもの、今回はJavaScriptを使用するものに似た、より迅速な「迅速かつダーティ」なソリューションです。

JavaScriptではあなただけの両方にオフセット小さいランダム追加することによって、そして長いあなたの場所のすべての緯度をオフセットすることができます例えば

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

25000で割ると十分な分離が得られますが、特定の住所など、マーカーを正確な位置から大幅に移動しないことが)

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


1
私はこれが好き。正確に代表的なマーカーが必要ない場合は、25000を250または25に下げてください。シンプルで迅速なソリューション。
2018

2

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


うん、その便利な小さなライブラリを見てみたが、それでもまだ問題を克服していないようだ。私は、同じオフィスブロックに複数のサービスが存在することがあり、そのためまったく同じ地理座標を持つ会社のサービスロケータを作成しています。1つの情報ウィンドウにさまざまなサービスを表示できますが、これは最もエレガントな解決策ではないようです...
Louzoid

@Louzoid Marker Clustererは、私が見つけたばかりの問題を克服しません。同じ問題が発生しています。1つの建物内に複数の会社があり、マーカーが1つしか表示されません。
Rob

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

このアプローチでは、ユーザーがクラスターアイコンをクリックする必要があり、ズームスライダーまたはマウスのスクロールでナビゲーションが行われるユースケースは対象外です。これらの問題に対処する方法はありますか?
Remi Sture 14

1

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

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

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

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

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

ところで。私はちょうどリーフレットを発見し、それははるかに良い仕事に思える、クラスタとspiderfy作品は非常に流動的http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html 、それはオープンソースです。



0

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


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 Swapを使用してzIndexハンドルを上に移動します。


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


    }

結果

ここに画像の説明を入力してください


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

-2

上記の回答を拡張して、位置を追加/減算しないで(たとえば、「37.12340-0.00069」)結合された文字列を取得した場合は、元の緯度/経度をfloatに変換して(たとえば、parseFloat()を使用)、修正を追加または減算します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.