5

Here an example of OverlappingMarkerSpiderfier by using Overlapping Marker Spiderfier which is overlap markers based on their Distance, Is there any way to achieve same effect based on regions, for example all marker of a country overlap and show as a group of marker and after click on the same make that expand.

Edited

i have search stack overflow and google for possibilities but didn't found any solution, if any one have idea/fiddle about how to manually do the clustering with the marker manager which use polygon/region is also helpful.

Shailendra Sharma
  • 6,976
  • 2
  • 28
  • 48
  • Check this [link](https://developers.google.com/maps/articles/toomanymarkers) there is a builtin clustering api. To bound the markers to a country you can make some kind of multipass for each country otherwise the api will cluster based in distance. – Carlos Aug 18 '16 at 12:23
  • no information there for regional based Marker clustering and also i need a Spider of markers. i have gone through **google.maps.geometry.poly** but after spending couple of days still not figure out how to use this in my fiddle – Shailendra Sharma Aug 18 '16 at 12:28
  • Without regional information I can't see how to cluster NY country in a reliable way – Carlos Aug 18 '16 at 13:01
  • possible duplicate of [Google Maps API V3 -> Utilize MarkerCluster but have the clusters themselves be specific to a drawn polygon/region?](http://stackoverflow.com/questions/12592079/google-maps-api-v3-utilize-markercluster-but-have-the-clusters-themselves-be) – geocodezip Aug 18 '16 at 13:01
  • @geocodezip could you please share some piece of code if you have any that would be helpful. – Shailendra Sharma Aug 18 '16 at 13:07
  • possible duplicate of [Marker cluster number in a polygon or/and infowindow](http://stackoverflow.com/questions/15415357/marker-cluster-number-in-a-polygon-or-and-infowindow) – geocodezip Aug 18 '16 at 13:59
  • @geocodezip that question is about number of markers or cluster inside the polygon – Shailendra Sharma Aug 19 '16 at 04:51
  • That is how region clustering would be done – geocodezip Aug 19 '16 at 09:38
  • @geocodezip any hope with this https://jsfiddle.net/L83z2kcs/4/ – Shailendra Sharma Aug 24 '16 at 13:04

1 Answers1

8

Previously on Stack Overflow ....

My approach to marker clustering

My approach to this challenge would be to follow @geocodezip suggestion. I would use the answer he provided in https://stackoverflow.com/a/15422677/1337392 to create a map of regions.

Each region would know which markers it has. Additionally, if you want to follow the principle of least carnality, you can also have each marker know to which region it belongs. To achieve that I would use MarkerLibs, or something similar to it.

Once we know where each marker belongs, it is straightforward to group them. Once again, @geocodezip suggested a good source https://developers.google.com/maps/articles/toomanymarkers , however, I advise caution as some of these libraries are old and discontinued (last time I checked a few months ago).

You can also go all Rambo mode in this one and code it yourself. Basically, you would need a listener for the zoom level and every time you zoom out you can calculate if you want to group all the makers inside a said region or not.

Grouping the said markers would mean to hide them, and in their place (you can calculate the geometrical center) place a new (custom) marker that symbolizes a group.

I have no code for the moment, but I hope that my detailed explanation helps you on this one.


The Illusion of ... Regional Marker Clustering

"Reality is that which, when you stop believing it, doesn't go away."

Philip K. Dick

The easiest solution for a user to see marker clustering ... is not to do marker clustering.

Instead of clustering markers, we can just hide and show them based on zoom levels - thus effectively creating the illusion that markers are being clustered.

Algorithm/Example

This approach, as you have seen, is based on giving the illusion of marker clustering.

In this example, I have all the of Portugal's districts (divided by colors) and each district has a set of POI's (Points of Interest).

On a personal note, I strongly recommend you to visit this beautiful country which was my home for many years, you won't regret it (specially the food!)

Now, each POI has a name, the coordinates where it is, and the district to where it belongs. This is all in the Data Set:

const POIS = [{
    district: 'LISBOA',
    name: 'Torre de Belem',
    coords: {
        lat: 38.69383759999999,
        lng: -9.2127885
    }
}, {
    district: 'LISBOA',
    name: 'Mosteiro dos Jeronimos',
    coords: {
        lat: 38.69703519999999,
        lng: -9.2068921
    }
},
...
];

PS: Torre de Belem and the Mosteiro are very nice!

As you can see, I have followed the principle of least cardinality previously explained and I added to each POI the district (region) where it belongs to.

What happens here is that when I initialize the map, I create a marker for each POI - but I don't show it.

Then, I calculate the centroid for each district (each district is no more than a polygon if you think about it!) and I create another marker for the center of each district as well (but again I don't show it).

When do I show the markers?

When the user either zooms in or out. To help me with this, I ended up using a library called markermanager which shows and hides markers based on the maximum and minimum zoom level!

The result is a map that gives you the illusion of marker clustering!

To add some finesse to the example, I also used dynamic pins to count the number of POIs in each region, thus furthering the illusion.

PS: dynamic pins is deprecated, but I used it as currently I am unaware of a better solution.

Code

The code seems very big, but if you dive into it, you will see it is quite easy.

First, the index.html. This file contains a div for the map and the JavaScript references:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>Simple Polygon</title>
    <link rel="stylesheet" type="text/css" href="mystyle.css">
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyA7ZtEnzC81C1TnUI1Ri44JgJJIh5KaltM"></script>
  </head>
  <body>
    <div id="map"></div>
    <script src="markermanager.js"></script>
    <script src="districtData.js"></script>
    <script src="poiData.js"></script>
    <script src="myscript.js"></script>
  </body>
</html>

Now, we have the heart of the application. The myscript.js file. This file uses the markermaner.js lib previously mentioned, and makes use of two data files, namely poiData.js and districtData.js.

This file deals with the innitializion of the map, draws the district polygons, calculates the centroid for each polygon, and innitializes the markermanager. If there was one function I could tell you is really important, that function would be setUpMarkerManager() where most of the heavy lifting is done.

"use strict";

/*global google*/
/*global DISTRICTS*/
/*global POIS*/
/*global MarkerManager*/

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 5,
    center: {
      lat: 38.184,
      lng: -7.117
    },
    mapTypeId: 'terrain'
  });

  setUpMarkerManager(map);
  drawDistricts(map);
}

//Check https://github.com/googlemaps/v3-utility-library/blob/master/markermanager/docs/reference.html
function setUpMarkerManager(aMap) {
  let mgr = new MarkerManager(aMap);

  let poisPerDistrict = new Map();
  let allPOISArray = [];


  POIS.forEach(element => {

    let poiDistrict = element.district;
    if (poisPerDistrict.has(poiDistrict)) {
      let poiCount = poisPerDistrict.get(poiDistrict) + 1;
      poisPerDistrict.set(poiDistrict, poiCount);
    }
    else
      poisPerDistrict.set(poiDistrict, 1);

    allPOISArray.push(new google.maps.Marker({
      position: new google.maps.LatLng(element.coords.lat, element.coords.lng),
      title: element.name
    }));
  });

  let inverseCenter;
  let disctrictsCenter = [];
  DISTRICTS.forEach(element => {
    inverseCenter = getPolygonCenter(element.coords.coordinates[0]);

    if (poisPerDistrict.get(element.id))

      //For cool markers check https://developers.google.com/chart/image/docs/gallery/dynamic_icons#scalable_pins
      disctrictsCenter.push(new google.maps.Marker({
        position: new google.maps.LatLng(inverseCenter[1], inverseCenter[0]),
        icon: "https://chart.googleapis.com/chart?chst=d_map_spin&chld=0.6|0|FFFFFF|12|_|" + poisPerDistrict.get(element.id),
        title: element.name
      }));
  });

  google.maps.event.addListener(mgr, 'loaded', function() {
    mgr.addMarkers(allPOISArray, 9);
    mgr.addMarkers(disctrictsCenter, 0, 8);
    mgr.refresh();
  });
}

// https://stackoverflow.com/a/37472218/1337392
function getRandomColor() {
  return '#' + Math.random().toString(16).slice(2, 8);
}

function drawDistricts(aMap) {
  let randomColor;

  DISTRICTS.forEach(element => {
    randomColor = getRandomColor();

    new google.maps.Polygon({
      paths: getPolygonCoordinates(element.coords),
      strokeColor: randomColor,
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: randomColor,
      fillOpacity: 0.35,
      map: aMap
    });
  });
}

function getPolygonCoordinates(polygon) {
  let coords = polygon.coordinates[0];
  let result = [];

  coords.forEach(element => {
    result.push({
      lng: element[0],
      lat: element[1]
    });
  });

  return result;
}

//Check https://stackoverflow.com/a/16282685/1337392
//Check https://en.wikipedia.org/wiki/Centroid
function getPolygonCenter(coords) {
  var minX, maxX, minY, maxY;
  for (var i = 0; i < coords.length; i++) {
    minX = (coords[i][0] < minX || minX == null) ? coords[i][0] : minX;
    maxX = (coords[i][0] > maxX || maxX == null) ? coords[i][0] : maxX;
    minY = (coords[i][1] < minY || minY == null) ? coords[i][1] : minY;
    maxY = (coords[i][1] > maxY || maxY == null) ? coords[i][1] : maxY;
  }
  return [(minX + maxX) / 2, (minY + maxY) / 2];
}

initMap();

Next is the Library itself, the markermanager.js. I could post the code here, but this is already quite long, so I will simply refer that I am using v1.1 and that you can download it at the official place or by checking the URL of the project I previously posted.

With this in mind, I will also give you my MANUALLY MADE and MANUALLY CORRECTED huge data files. Yes, if you were wondering why it took me so long, now you have an idea.

Normally I would copy and paste the values here, but since StackOverflow has a character limit, I will link you to my GitHub account where they are.

poiData.js is first, the file with all the places you should see! Link: https://github.com/Fl4m3Ph03n1x/marker-cluster-polygon/blob/master/poiData.js

And now, the districtData.js with the data for each district polygon. Link: https://github.com/Fl4m3Ph03n1x/marker-cluster-polygon/blob/master/districtData.js

There is also CSS file, but it truly is nothing special.

You can check the full demo/project at my GitHub account here: https://github.com/Fl4m3Ph03n1x/marker-cluster-polygon

Personal Notes

When I first started this, I could not have imagined where it was going to take me.

One thing I have to say is that when I started coding I didn't realize how close to my initial theory I was! In fact, as an easter egg, I can tell you that I originally made a different post for this answer because I thought it was very different from the initial algorithm I had predicted :P

I guess it goes to show you that is does pay off to think code before writing code!

I overall find this approach acceptably efficient, and it sure was a lot of fun and (a lot of work) to create. Most of the work you will have using this approach will be with crunching data, but once that is done, the rest is fast - and easy.

Part of me whishes I could have done it earlier to get the bounty, but since I like to see myself as someone who keeps his word, I ended up doing it and posting it anyway!

I hope you now understand why it took me so long, but I truly hope this helps you!

Community
  • 1
  • 1
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266