Google Maps API V3 - кілька маркерів на одному і тому ж місці


115

Трохи застряг на цьому. Я отримую список геокоординатів за допомогою JSON і переношу їх на карту Google. Все працює добре, за винятком випадків, коли я маю два та більше маркера на тому самому місці. API відображає лише 1 маркер - верхній. Я вважаю, що це досить справедливо, але я хотів би знайти спосіб, щоб їх якось показати.

Я шукав google і знайшов декілька рішень, але вони, здається, стосуються V2 API або просто не дуже. В ідеалі я хотів би вирішити, де ви натискаєте якийсь груповий маркер, який потім показує маркери, скупчені навколо місця, в якому вони знаходяться.

Хтось мав цю проблему чи подібне і хотів би поділитися рішенням?

Відповіді:


116

Погляньте на OverlappingMarkerSpiderfier .
Існує демонстраційна сторінка, але вони не показують маркерів, які точно знаходяться на одному місці, лише ті, які знаходяться дуже близько.

Але приклад із реального життя з маркерами на тому самому місці можна побачити на веб-сайті http://www.ejw.de/ejw-vor-ort/ (прокрутіть карту вниз і натисніть кілька маркерів, щоб побачити ефект павука ).

Це здається ідеальним рішенням вашої проблеми.


Це приголомшливо і ідеально відповідає потребі, що залишилася від бібліотеки кластерів, яка не обробляє маркери з таким же точним lat / long.
Джастін

Якщо використовувати OverlappingMarkerSpiderfierв поєднанні з MarkerClusterer, хорошим варіантом є встановити maxZoomпараметр на конструкторі кластерів. Це "знімає" маркери після зазначеного масштабування.
Дідерік

@Tad Я не знаю, чи ви розробник цього веб-сайту, але я хотів повідомити вам, що карта на ejw.de зламана. Я отримую помилку JS.
Олексій

Я перевірив запропоновану демонстраційну версію, але вона виявляється мертвою. Після подальших досліджень я знайшов цей приклад щодо інтеграції маркерних кластерів та Spiderifier. Немає демо-версії, а просто клонуйте та слідкуйте за readme. github.com/yagoferrer/markerclusterer-plus-spiderfier-example
Sax

34

Зсув маркерів не є справжнім рішенням, якщо вони розташовані в одній будівлі. Що ви можете зробити, це змінити markerclusterer.js так:

  1. Додайте метод клацання прототипу до класу MarkerClusterer, як-от так - ми перекриємо це пізніше у функції ініціалізації () карти:

    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
  2. У класі ClusterIcon додайте наступний код ПІСЛЯ тригера кластерного клацання:

    // 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 () - ВАША (ще не записана) функція, щоб відкрити вікно InfoWipe зі списком опцій, з яких можна вибрати. Зауважте, що об’єкт 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

Красиве та елегантне рішення. Дякую!
Концепція211

Ідеальне рішення компенсує небагато! Чи можливо, ми наводимо ми на маркер і чим він відображає кілька маркерів
Intekhab Khan,

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

WebMW: Після вилучення маркерів з XML, спершу потрібно згрупувати їх у списки, щоб кожен список містив маркери, які вказували саме на те саме місце. Потім у кожному списку, який містить більше ніж один елемент, запустіть правильнийLocList (). Лише після цього створіть об’єкти google.maps.Marker.
DzinX

Його не працює .... Він просто міняє мою лат нудить з 37.68625400000000000,-75.71669800000000000на 37.686254,-75.716698теж, що однаково для всіх значень ... Я очікував щось на кшталт ... 37.68625400000000000,-75.71669800000000000на 37.6862540000001234,-75.7166980000001234 37.6862540000005678,-75.7166980000005678..etc .. Я також спробував змінити свій кут.
Премшанкар Тіварі

Хммм, давайте трохи поринемо і сподіваємось, що @DzinX все ще знаходиться в цьому районі. Чи можете ви товаришувати (чи хтось інший) пояснити, як ви отримуєте всі ці цифри? Lng_radius тощо? Thx багато. :)
Vae

1
@Vae: Радіус - це наскільки великим ви хочете коло. Ви хочете, щоб коло виглядало круглим у пікселях, тому вам потрібно знати, у якому місці ви співвідносите 1 градус широти та 1 градус довготи (у пікселях на карті). Ви можете знайти це на деяких веб-сайтах або просто експериментувати, поки не отримаєте потрібну кількість. Кут - скільки першого шпильки праворуч зверху.
DzinX

11

@Ignatius найвидатніша відповідь, оновлена ​​для роботи з v2.0.7 MarkerClustererPlus.

  1. Додайте метод клацання прототипу до класу MarkerClusterer, як-от так - ми перекриємо це пізніше у функції ініціалізації () карти:

    // BEGIN MODIFICATION (around line 715)
    MarkerClusterer.prototype.onClick = function() { 
        return true; 
    };
    // END MODIFICATION
  2. У класі ClusterIcon додайте наступний код ПІСЛЯ тригера клацання / кластерного клацання:

    // 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 () - ВАША (ще не записана) функція, щоб відкрити вікно InfoWipe зі списком опцій, з яких можна вибрати. Зауважте, що об’єкт 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; } }
Паскаль

Я знаю, що це справді старе, але я намагаюся використовувати це рішення. У мене це працює, але у випадку відображення інформаційного вікна з даними кластера ... як я можу отримати позицію маркера кластера, яку натиснули в першу чергу, щоб я міг показати інформаційне вікно? маркери - це мій масив даних ... але як я можу відобразити інформаційне вікно на піктограмі / маркері кластера?
користувач756659

@ user756659 у multiChoice ви отримуєте lat / lng через: clickedCluster.center_.lat()/clickedCluster.center_.lng()
Jens Kohl

1
@Pascal і з невеликим затуманенням ми закінчимося цим, що могло б спрацювати, але як говорить дао python. "кількість читабельності". var maxZoom = Math.min (mc.getMaxZoom () || 15, mc.getMap (). maxZoom || 15);
Нанде

6

Наведені вище відповіді більш елегантні, але я знайшов швидкий і брудний спосіб, який насправді працює справді неймовірно добре. Ви можете побачити його в дії на www.buildinglit.com

Все, що я зробив, було додати випадкове зміщення до широти та довготи до моєї сторінки genxml.php, щоб воно щоразу повертало дещо інші результати із зміщенням кожного разу, коли карта створюється маркерами. Це звучить як злом, але насправді маркери потрібні лише для легкого натискання у випадковому напрямку, щоб вони могли натискати на карті, якщо вони перетинаються. Насправді це працює дуже добре, я б сказав, що краще, ніж метод павука, тому що хто хоче вирішити цю складність і вести їх всюди. Ви просто хочете мати можливість вибрати маркер. Стискання його випадковим чином працює ідеально.

Ось приклад створення вузла ітерації оператора while у моєму php_genxml.php

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

Зауважте, що під лат і довгий є зміщення +. з 2-х змінних вище. Мені довелося ділити випадкову кількість на 0,1000 на 10000000, щоб отримати десятковий розмір, який був випадково достатньо малим, щоб ледь перемістити маркери. Не соромтеся поцікавитись цією змінною, щоб отримати більш точну для ваших потреб.


Класно! це брудний злом, який я вважаю дуже корисним, не потрібно возитися з кодом api Google Maps
CuongDC

3

У ситуаціях, коли в одній будівлі є кілька служб, ви можете злегка змістити маркери (скажімо, на .001 градус) у радіусі від фактичної точки. Це також повинно створювати приємний візуальний ефект.


3

Це скоріше «швидке та брудне» рішення про зупинку, подібне до запропонованого Меттью Фокса, цього разу за допомогою JavaScript.

У JavaScript ви можете просто компенсувати ширину та довжину всіх своїх локацій, додавши невелике випадкове зміщення до обох, наприклад

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

(Я виявив, що ділення на 25000 дає достатньо відокремлення, але не переміщує маркер значно від точного місця, наприклад, конкретної адреси)

Це робить досить хорошим завданням компенсувати їх один від одного, але лише після того, як ви ретельно збільшили масштаб. При зменшенні масштабу все одно не буде зрозуміло, що існує декілька варіантів розташування.


1
Мені подобається це. Якщо вам не потрібні точно представницькі маркери, просто опустіть 25000 до 250 або 25. Просте і швидке рішення.
Фід

2

Ознайомтеся з кластерним маркером для V3 - ця бібліотека кластеризує сусідні точки в груповий маркер. Карта збільшує масштаб при натисканні кластерів. Я б міг уявити, що коли ви збільшили масштаб прямо, у вас все одно буде однакова проблема з маркерами на одному місці.


Так, погляньте на цю зручну маленьку бібліотеку, але вона все ще не здається подолати проблему. Я роблю локатор обслуговування для компанії, яка інколи має більше одного сервісу в одному офісному блоці, а отже, і ті самі геокоорди. Я можу показати різні сервіси в одному інформаційному вікні, але це просто не здається найелегантнішим рішенням ...
Louzoid

@Louzoid Marker Clusterer не подолає проблему, як я щойно дізнався. У мене однакова проблема, у мене є кілька компаній в одній будівлі, і тому вона показує лише 1 маркер.
Роб

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

1

Мені подобаються прості рішення, тому ось моє. Замість того, щоб змінювати кришку, це ускладнювало б утримання. ви можете просто подивитися подію, як це

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

тоді ви можете ним керувати

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

я, тобто, використовував завантажувальний інструмент для показу панелі зі списком. що мені здається набагато зручнішим і кориснішим, ніж павук на "людному" місці. (якщо ви користуєтесь шансерами кластера, ви зіткнетеся, як тільки ви спайдерфікуєте). ви також можете перевірити масштаб.

btw. Я щойно знайшов листівку і, здається, працює набагато краще, кластер І 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, щоб підняти маркер зверху за допомогою zIndex Swap.


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

Додаючи до підступної геніальної відповіді Меттью Фокса, я додав невеликий випадковий зсув до кожного лату та lng при встановленні об'єкта маркера. Наприклад:

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"), перетворіть свій початковий лат / довготу в плавці, наприклад, використовуючи parseFloat (), а потім додайте або віднімайте виправлення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.