George MacKerron: code blog

GIS, software development, and other snippets

Overlapping markers on your Google Map? Meet OverlappingMarkerSpiderfier

Ever noticed how, in Google Earth, marker pins that overlap each other spring apart gracefully when you click them, so you can pick the one you meant?

And ever noticed how, when using the Google Maps API, the exact same thing doesn’t happen?

This code makes Google Maps API version 3 map markers behave in that Google Earth way. Small numbers of markers (up to 8, configurable) spiderfy into a circle. Larger numbers fan out into a (more space-efficient) spiral.

Try it! (You can reload this page to re-randomise the marker positions).

The code is MIT licenced, has no dependencies, and is under 3K compiled (from CoffeeScript), minified and gzipped. I wrote it as part of the data download feature for Mappiness.

It’s on Github, where you can find out more, download or fork the code.

Share

Written by George

June 22nd, 2011 at 9:19 pm

  • Daniel

    Fantastic! Thank you for making it. I am wondering, though, is there any way to make it work with .kml – generated placemarks?

  • Hi Daniel. Unfortunately I suspect not, since of KML layers the docs say: “Importantly, however, these objects are not Google Maps API Markers, Polylines, Polygons or GroundOverlays; instead, they are rendered into a single object on the map.” (http://code.google.com/apis/maps/documentation/javascript/overlays.html#KMLLayers)

  • Rupendra

    Amazing Work Sir … excellent

  • Rupendra

    But is there a way i can integrate it with marker clusterer

  • Hi, awesome work, it works very well. But I have a problem, in my map I have multiple markers of different categories, and I have a menu to hide and show the markers. But the Spiderfier thinks that they are there even when they are hiden. Is there any way to solve this problem?
    Thanks!
    PS:sorry for my engrish.
    PS2: here’s a picture of my problem so you can see what I’m talking about: http://img14.imageshack.us/img14/5722/picyaq.jpg

  • Rupendra: regarding clustering, I haven’t tried this myself, but I’ve been told it works: “I was able to get it working nicely actually, essentially the markerclusterer handles all clicks, etc until you pass it’s minimum threshold, and once it steps out of the way and displays the individual markers, the spiderfier then works as expected – a perfect solution!”

  • Emiliano: regarding showing/hiding markers, it sounds like what you need is a removeMarker method. You can use this to stop tracking the markers that aren’t visible. I’ve just added such a method to the code! Please download the latest version from GitHub, and let me know how you get on.

  • Beau

    How can I use this with a PHP MySQL database? Thanks

  • Beau: on the server you’d use PHP to query the database for the locations and associated data, and package that up in a JavaScript/JSON format to send to the client. On the client you’d write JavaScript to take that data and display it using the Google Maps API and OverlappingMarkerSpiderfier, in much the same way as shown in the documentation on GitHub. For the specifics, you probably need to look up a basic introduction to web mapping.

  • Hi George thnaks for your answer, I tried what you suggested, but I don’t know how to specify what markers to remove. I tried this with no luck:
    for (i in marcadores) {
    if(marcadores[i].getVisible()== false){
    oms.removeMarker(marcadores[i]);
    }
    else{
    oms.addMarker(marcadores[i]);
    }
    }
    Any ideas?

  • Emiliano: OK, I looked at the source of your website, and though you could use removeMarker to achieve what you want, this probably isn’t the easiest way. I’ve now updated the script to ignore markers that aren’t visible, and to be resilient to changes to the position and visibility of spiderfied markers. So you should be fine to do what you were doing before, and ignore the removeMarker method.

  • Hi, I found a way to make it work with removeMarker:
    for (i in marcadores){
    oms.removeMarker(marcadores[i]);
    if(marcadores[i].getVisible()==true){
    oms.addMarker(marcadores[i]);
    }
    }
    First I remove all the markers and then I add only the visible ones, maybe a method to remove all the markers could become handy.

    I’ll try your new code an let you know. Thanks a lot for your help and hard work!

  • Emiliano: I just edited my comment above to note that you can now do what you were doing originally and it should just work. (But note that if you ever do want to remove all the markers, I have added a method called clearMarkers(), which is much quicker than looping over them calling removeMarker()).

  • Thanks George, you are awesome! The new script works flawlessly!

  • thank you for the awesome work! 2650 points shows without any problem!

  • Hello George – your method is hands down the best one available at the moment. I am successfully using for a project of mine, but I found a small problem (not directly related to OMS I suspect). I’d like to hear you opinion if possible.
    For some reason Opera does not show tooltips for the map Markers. In order to get around this (and the delay in displaying the tooltip) I took a look at several custom tooltip libraries for markers and found out that none of them play nicely with OMS (or I don’t know how do do it the right way). The specific one I was looking at is http://koti.mbnet.fi/ojalesa/boundsbox/tiptool_trains.htm
    Is there a way to make it work alongside with OMS, or even better – is this feature something that could be built in OMS without too much trouble? Thanks!

  • Riba: I had a look at this, and the problem with your tooltip library seems likely to be down to a bug in OMS. OMS was removing all mouseover/mouseout listeners on markers when they were unspiderfied, not only its own. This is now fixed (version 0.2.2), so please download the latest JS file and try again. Longer term I may think about adding tooltips to OMS — it’s true that it would be good to be able to distinguish between the spiderfied markers without having to wait ages for the tooltips to pop up.

  • This is great.  I’m thinking of using OMS as an upgrade to my mapping site for times when there are multiple markers at one exact location.  

    I’ve setup a test page, but am having an issue with getting some options working.  http://www.yourmapper.com/demo/spiderfy.htm

    Here are the 6 options.  

    oms = new OverlappingMarkerSpiderfier(map, {markersWontMove: true, markersWontHide: true, keepSpiderfied: true, nearbyDistance: 1, legWeight: 4, circleSpiralSwitchover: Infinity});

    The first 3 work, and the last 3 (nearby, weight, switchover) seem to have no effect on the map.  Any ideas what I’m doing wrong?

    Thanks for Spiderfier!

  • The only issue with this is that markercluster shows a cluster at even at max zoom if there are multiple markers at the exact same lat/lon.  So markercluster never goes away in this case, and therefore spiderfier can’t be accessed.  Maybe there’s an markercluster option I don’t know about that can disable clustering in this case though.

  • In V3 API, there are 2 libraries you can use that will parse KML behind the scenes and let you access their individual elements in arrays. They basically get the data out of the KML, then render the points on the map as markers, which then you might be able to use spiderfier on.

    http://code.google.com/p/geoxml-v3/
    http://code.google.com/p/geoxml/

  • Anonymous

    Download the latest version (0.2.4) and this should now work.

    Previously, the first 3 options you mention were the only ones that could be passed to the constructor, while the others had to be set directly on the OverlappingMarkerSpiderfier instance after it was created (e.g. oms.legWeight = 2).

    There was no good reason for this, so I’ve now tweaked the code so these other options can all be passed to the constructor too.

  • Ok, cleared cache, got it loaded to the page, works great. 

    One question though.  I think I misunderstood what nearbyDistance does.  It seems like it is the number of pixels that the markers spread out.  I thought it was more like a sensitivity setting that would only make them spread if their centers were at less than X pixels away from each other.  I’m wanting to use spiderfier only when markers are at the exact same lat/lon.

  • Oh, nevermind, I was just confusing the final behavior.  When I increased the number, it made a bigger fan of a circle, but that’s just because more markers were activating. Works great, nothing else needed!

  • George,

    I thought you’d like to see the beta version of our V3 maps, which is now up for the public.  

  • This is awesome, is there anyway I can tell it to simply spiderfy the markers from the beginning?  (no click required)

  • Anonymous
  • fly2279

    I am also trying to use OMS with a ‘label’ for markers. I’m using the code from: 
    http://www.tdmarketing.co.nz/blog/2011/03/09/create-marker-with-custom-labels-in-google-maps-api-v3/ The class I’m using updates the position of the label if the marker changes position but not when it changes position using OMS. I’ve tried manually updating the position of the label using the spiderfy and unspiderfy callback but it seems like the new spiderfied position of the marker isn’t set yet during hte spiderfy callback. Any ideas?

  • jawj

    Since the labels bind themselves to the marker positions, this should just work. And indeed, for me it seems to: see http://mackerron.com/temp/oms-label-test/ (obviously this test isn’t a finished product — label z-indices are screwed up, and the label text isn’t clickable — but it shows that the position updating can work).

    If you can make a complete example available, I’ll have another look. It might be easier to handle as a Github issue, though.

  • Mike Randall

    Loving this library, thank you for making it available.

    And my question is….. Is there a way to add a mouseover listener?

  • jawj

    You’re welcome.

    You should be able to add mouseover listeners to markers in the usual way. Let me know if that’s not what you mean, or if it’s not working — preferably with a usable code sample.

  • Mike

    Sorry, got really busy last week. 

    Not having any luck just doing this: // Handle overlapping instrument markers.
    var iw = new gm.InfoWindow();
    var oms = new OverlappingMarkerSpiderfier(map,{markersWontMove: true, keepSpiderfied: true});
    oms.addListener(‘mouseover’, function(marker) {
    iw.setContent(marker.desc);
    iw.open(map, marker);
    });

    for ( var i in markers ) oms.addMarker(markers[i]); // Add markers to oms———-Do I add the mouseover event somewhere else?You can see an older version of this code in action here:http://volcanoes.usgs.gov/volcanoes/yellowstone/yellowstone_monitoring_47.html And the older code is here (about 1/3 down the page): http://volcanoes.usgs.gov/vsc/js/monitoring/monitoring.jsThanks again.Mike

  • jawj

    Ah. It’s only ‘click’, ‘spiderfy’ and ‘unspiderfy’ events that the Spiderfier handles, because those are the only ones where it’s needed. For ‘mouseover’ events you should add the listener in the standard Maps API way — i.e. google.maps.event.addListener(marker, ‘mouseover’, listenerFunc).

    If you then want to deal with spiderfied markers differently in the event listener, you can check a marker for the presence of the ._omsData property, which signifies that it’s currently spiderfied.

  • Mike Randall

    Had time this weekend to look at this again, still not getting it to work. I’m probably all around the solution.

    So, what do I do with my marker to have it instruct oms to ‘spiderify’ a group of markers when mouseover happens?

    Here’s where I’m trying to make it happen:

    google.maps.event.addListener(marker,’mouseover’,function(ev){ if( marker._omsData == undefined ){ // oms expands group??? }});

    I created a test page with a stripped down version of the code here:

    http://volcanoes.usgs.gov/volcanoes/yellowstone/yellowstone_map_test.html

    Again, thank you for your help. Much appreciated.

  • Mike

    Ok, figured it out, needed to bounce the mouseover event to the click event so oms would see the click: 

    google.maps.event.addListener(marker,’mouseover’,function(ev){ if( marker._omsData == undefined ){ google.maps.event.trigger(marker,’click’); }});

    My previous efforts were all about trying to interact with oms directly.

    ANOTHER Question? Is there a way to tell if a marker is in a group? It’s working too well, navigating the map is like walking through a minefield because all markers are getting fired on mouseovers.

  • jawj

    Glad you got this sorted. I had a look at your example, and auto-spiderfying on mouseover is less of a UX nightmare than I expected!

    I think your question is: is there a way to tell if a marker is close enough to one or more others that it will be spiderfied when clicked? I’m afraid the answer is no at present, but I’ll consider pull requests.

  • Mike Randall

    Again, thank you for a great piece of code, and for being responsive to questions.

    I’ve got it working as desired.

    To solve the issue of determining which markers will be spiderfied when clicked, I just loop through all the other markers and determine how many pixels the closest one is. If less than or equal to the oms nearby distance, then true. I was afraid it would be slow, but for 100 or so markers, it seems just fine.

    Here’s what my code looks like (for others that may need it):

    // Get pixels to nearest marker
    // For google maps v3
    // marker = google.maps.marker
    // markers = array of markers
    function nearestMarkerPx(marker){
    overlay = new google.maps.OverlayView(); overlay.draw = function() {}; overlay.setMap(map); p1 = overlay.getProjection().fromLatLngToDivPixel(marker.getPosition()); nmPx = 999; for ( var i in markers ){ var marker2 = markers[i]; if ( marker2 != marker ){ p2 = overlay.getProjection().fromLatLngToDivPixel(marker2.getPosition()); a = Math.abs(p2.x – p1.x); b = Math.abs(p2.y – p1.y); c = Math.sqrt( Math.pow(a,2) + Math.pow(b,2) ); if ( c < nmPx ) nmPx = c; } } return nmPx;}

  • Himanshu KHurana

    Excellent tool! Thanks for this.
    One question though: How can I animate spreading of the markers. They should spread slowly. 

  • jawj

    I agree that animation would be a really nice enhancement to the library. I haven’t done it (a) because I’m not certain whether performance will be good enough to make it nice and smooth and (b) owing to lack of time. Pull requests readily accepted on Github, though!

  • Hello
    I am using your Spiderfier and it works like a charm. I have one question: I am using it in combination with the MarkerClusterer provided by Google, works fine. Spiderfier is adjusted in a way, that only Markers on the same location will be spiderfied. I now want to have a custom marker to signal to the user, that there are more than one marker. Is there a way to do it with your api or not?

  • jawj

    No, though it sounds like a good idea for an enhancement. Why not add it as an issue on GitHub? (At present, there is a very subtle signal to users, in the form of a darker shadow).

  • Josh

    Hello
    I am using Google Fusion Tables to create a map of contacts and I have run into the same problem as this guy (http://code.google.com/p/fusion-tables/issues/detail?id=1142). I have multiple records at the same location yet only one entry is showing up.

    Is there a way to add your Spiderfier feature to the Fusion Tables? Or a trick to getting multiple entries at the same location to show up? Any help is appreciated.

  • Glen Stobbs

    I like it, but cannot get it to work using the mapping I set up to draw the info in from our Database. My guess is the script I have been using won’t accept the added scripting. Locations are loaded into an array at the top and called in once the map is set up. [Classic ASP & MySQL Db]

  • CaptH

    Hi,
    Thanks for implementing this great spiderfier! I’m using it with Google Maps v3 and a jQuery accordion – all worked fine.
    Today when I loaded the app, some parts do not work as usual, so I was wondering whether there have been any recent changes (either to the spiderfier or on the google maps side) that may have an effect on the functionality? Some details:
    – In Firefox the dialog window that it used to be associated with the ‘click’ event listener won’t appear at all at the first marker click and it will appear but without data at any successive clicks.
    – In IE the dialog window won’t appear at all.
    – Accordion functionality all still working fine (and contains same data as those shown in the dialog window).
    Firebug shows these errors:
    – “TypeError: d is undefined” (at the first marker click after spiderfying)
    – “TypeError: a.curCSS is not a function” (at successive marker clicks after spiderfying)
    I didn’t get any of these previously
    Cheers

  • Guest

    Thanks. This works great but I notice in your demo as well as in my own page that for some overlapped markers, there’s 2 or more slightly offset markers drawn from the start before they spiderfy. You mentioned in a reply that an overlapping marker has a darker shadow. Can it just display 2 sligthly offset markers instead? That makes it clearer there are overlapping markers, I think.

  • Pete

    First of all, thanks for this brilliant work.

    I got a little problem with the spidering.
    I have numbers in my markers and different colors (depends on the district).

    If got now markers who must be spidered, i got this little effect, that the numbers and colors are not the same as before the spidering.

    I managed to get numbers working, but then i cant open the description.

    Is there chance to get this working?

  • jawj

    @94bb32b2b5bfa7801942c5b0f54a9d20:disqus: I’ve now added methods (since version 0.2.8) that do this for you. See the GitHub README for details of oms.willSpiderfy(marker) and oms.markersThatWillAndWontSpiderfy().

  • jawj

    I suspect this issue is down to something you’re doing in the spiderfy and unspiderfy listeners. The library itself doesn’t do anything to change the marker icons.

    If you can provide a working code sample (e.g. on jsFiddle) I’ll take a look, though.

  • jawj

    Before they spiderfy, markers are shown in their original positions. If the original positions are identical, it won’t be obvious that there are several markers overlapping.

    As of version 0.2.8, I’ve added a function oms.markersThatWillAndWontSpiderfy(): see the GitHub README for details. You can call this to establish which markers are overlapping one or more others, and you could then use that to display a different marker icon.

  • jawj

    I’m not aware of any changes. I’d need to see the code up and (not) working to investigate further, but I suspect the problem is outside the library.

  • jawj

    I’m afraid there’s not much I can do without seeing your code. And I don’t know ASP, so there might not be much I could do even then.