Mystery Incorporated


Programming, photography, news, culture, and cartoons

home | rss | atom

Add to Google

Circle Selection for Google Maps


January 30th, 2013 [programming]

selectionHow best to allow users to select multiple markers on a map? I wanted to let them draw a circle and have all markers within be selected. Using the Google Maps JavaScript API, I used the circle object for my solution.

I tested the circle selection in my Cabi Trip Visualizer. Clicking on an individual CaBi station draws arrows to all the other stations in its network. But the program becomes more interesting when you look at a network formed by multiple stations. I added some keyboard shortcuts to select clusters of my own design, such as G for Georgetown and V for Virginia. But those clusters were in arbitrary boundaries. I needed a way to let the user design their own clusters.

The first option was to simply select every station visible in the viewport. You can hit the “1” key to try this out.

To give the user the option of selecting stations within a circle, I used the API’s circle object. One of the CircleOptions properties is “editable.” When set to true, the circle is drawn with control boxes in the center and on the sides. The user can click and drag these control points to move or re-size the circle. Hit the “2” key to try this out.

Coding the Circle Object in JavaScript

Declaring a new circle object can be done in one step, such as:

userCircle = new google.maps.Circle({
  strokeColor: "#0000ff",
  strokeOpacity: 0.75,
  fillOpacity: 0,
  strokeWeight: 5,
  map: map,
  editable: true,
  visible: false });   
google.maps.event.addListener(userCircle, 'bounds_changed', 
  function() {selectFromCircle();});

That “editable: true” part will give the user the ability to change the circle. The second line adds an event listener, so that whenever the circle is moved or re-sized I call a function to recalculate the selected markers.

circleWhen the circle first appears, I want it to be half the size of the viewport. I wish Google had a fitBounds() method that would take latitude and longitude coordinates; that would be easy to get from the viewport. Instead, I need to figure out where to place the center and how big to make it. The center is easily available from the map’s getCenter method, but for the radius I need a number in meters. I want the initial diameter of my circle to be half the length of the viewport, using either the width or height, whichever is smaller.

To calculate distances from the coordinates of the map, I use a library of functions that Google added to the API in 2011 (see A little help with spherical geometry from our first Maps API library). The geometry library has a “computeDistanceBetween” method that takes two coordinates and returns the distance in meters. The method is in the “sphericalnamespace, where they group functions that assume locations are on a spherical object. But default they assume you are talking about the Earth, but you could adjust for other planets if you know their radii. The code below gets three corners of the viewport, then measures the horizontal (northeast to northwest) and vertical (southwest to northwest) distances. I calculate the smaller of the two, then divide by 2 to get half, and divide by 2 once more to go from diameter to radius.

function toggleCircle() {
  if (userCircle.getVisible()) 
    userCircle.setVisible(false)
  else {
    var bounds = map.getBounds();
    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();
    var nw = new google.maps.LatLng(ne.lat(), sw.lng())
    var x = google.maps.geometry.spherical.computeDistanceBetween(ne, nw);
    var y = google.maps.geometry.spherical.computeDistanceBetween(sw, nw); 
    userCircle.setOptions({center: map.getCenter(), 
      radius: Math.min(x, y)/4, 
      visible: true}); 
    }
  }

You might be surprised to learn that selectFromCircle() is called at the end of this function. If you look at the previous chunk of code, you’ll see an event listener set up to call selectFromCircle() whenever the “bounds_changed” event is triggered. The setOptions call here does in fact trigger that event.

Here’s where I decide which markers fit within the circle. The API has a containsLocation() method for polygons, but not for circles. The circle object has only a getBounds() method, which of course returns a rectangle, and thus would return false positives for spots outside the circle but within the bounding box. So once again I have to resort to computeDistanceBetween() to get how far a marker is from the circle’s center. I’m still going to use the circle’s bounding box, but only because mathematically it’s faster to see if a spot is within a rectangle than a circle. Because JavaScript uses short-circuit evaluation, I’ll put my radius test in the second half of the and expression.

function selectFromCircle() {
  var bounds = userCircle.getBounds();
  if (bounds != null) {
    var groupList = []; 
    var center = userCircle.getCenter(); 
    var r = userCircle.getRadius(); 
    for (var i=0; i < marker.length; i++) {
      var pos = marker[i].getPosition();
      if (bounds.contains(pos) &&
        google.maps.geometry.spherical.computeDistanceBetween(pos, center) <= r)
        groupList.push(i);
      }  
    clusterName = "custom area";  
    drawArrows(groupList, false); 
    }
  }

You can see the editable circle in action in my Cabi Trip Visualizer. Hit the "2" key to toggle the circle.

More resources at Make your map interactive with shape editing and drawing tools, and another Drawing Tools Library Example.

For more information about the CaBi Trip Visualizer, see Capital Bikeshare’s 4th Quarter of 2012.

One Response to “Circle Selection for Google Maps”

  • SonT says:

    Hey thanks for the tip! This helps me see the CaBi trip patterns within a given area. It’s certainly a much more user-friendly approach than clicking between individual stations.

    Thanks for the code share too!

  • Leave a reply



Site by M.V. Jantzen
mvs202 "at" gmail.com
twitter.com/mvs202