Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to perform Google Maps regional based clustering or Marker Overlapping

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.

like image 603
Shailendra Sharma Avatar asked Aug 18 '16 12:08

Shailendra Sharma


People also ask

How do I use marker clusters in Google Maps?

To see how marker clustering works, view the map below. To navigate, press the arrow keys. The number on a cluster indicates how many markers it contains. Notice that as you zoom into any of the cluster locations, the number on the cluster decreases, and you begin to see the individual markers on the map.

How is clustering represented on a map?

Each cluster is represented by a circle with a diameter that is directly proportional to the number of markers it represents which, in turn, is also numerically represented. Clicking on a cluster will show you the individual markers or sub-clusters.

What is Google map overlay?

Overlays are objects on the map that are tied to latitude/longitude coordinates, so they move when you drag or zoom the map. For information on predefined overlay types, see Drawing on the map.


1 Answers

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!

like image 186
Flame_Phoenix Avatar answered Nov 01 '22 12:11

Flame_Phoenix