3

I am drawing polygons that are adjacent and whose borders are shared. Looking at the example image below, if I were to draw a polygon for Montana, I'd like to be able to click on the nodes of the Idaho polygon for the part of the border that is the same to ensure the borders don't overlap or have holes. However, the nodes don't allow clicks directly on them, and if I make the nodes invisible then the accuracy is worsened and I'll probably end up with holes or overlaps between the two polygons.

Is there a way to snap a drawing to a node of an existing polygon? If not, is there at least a way to make it so that I can click directly where the nodes are? I've set the polygon's properties to editable: false and clickable: false, but the boundary of the polygon (and the nodes) still disallows clicks.

I haven't seen any documentation in the API for snapping.

polygon on google maps

Skitterm
  • 4,257
  • 7
  • 38
  • 54
  • 2
    Find the nearest vertex of the polygon to your mouse click. – geocodezip Nov 21 '13 at 22:34
  • @geocodezip: Thanks. I assume the best way to do that would be using the Geometry Library for google maps. I'll give that a try. – Skitterm Nov 21 '13 at 22:51
  • If you draw a rough border for Montana, you can then move the points and they'll snap to the Idaho points. – brouxhaha Nov 21 '13 at 22:54
  • This seems relevant http://stackoverflow.com/questions/10694378/confine-dragging-of-google-maps-v3-marker-to-polyline – geocodezip Nov 21 '13 at 23:32
  • I like [this example by Wolfgang Pichler](http://www.wolfpil.de/v3/snapable-lines.html), snaps to polylines and between vertices, but might be useful (would allow you to snap anywhere on the edge of the polygon, not just to the vertices). – geocodezip Nov 22 '13 at 00:16

1 Answers1

8

PolySnapper: easy polygon vertice snapping.

PolySnapper GIF Demo

I created this this github repo and complementary jsfiddle.

A brief example might look like:

var PS = PolySnapper({
      map: map,
      threshold: 20,
      key: 'shift',
      keyRequired: true,
      polygons: polygons,
      polystyle: polystyle,
      hidePOI: true,
      onEnabled: function(){
        console.log("enabled")
      },
      onDisabled: function(){
        console.log("disabled")
      },
      onChange: function(){
        console.log("a point was added, removed, or moved")
      }
});

//first enable the manager (enter drawing mode)
PS.enable();

//user draws the polygon, point by point, snapping when necessary.
//now, retrieve the polygon from the manager.
the_poly = PS.polygon();

//and disable the manager (exit drawing mode and clean up poly).
//you should now use the_poly as a polygon reference
PS.disable();

Note: By design, the western shape on jsfiddle is not set to snapable (see above polygons property) so only the eastern shape will snap :)

//the only global variable
//SM will become the SnapManager instance.
var SM = null;

google.maps.event.addDomListener(window, "load", function () {
  
  //we will center the map here
  var vancouver = {
      lat: 49.269858,
      lng: -123.137283
  }
  
  //granville island coordinates.
  //you should be fetching your coordinates from your server
  var granville_coords = [
        {lat: 49.27158485202591, lng: -123.13729763031006},
        {lat: 49.27277488695786, lng: -123.13691139221191},
        {lat: 49.27316689217891, lng: -123.13613891601562},
        {lat: 49.27319489243262, lng: -123.13474416732788},
        {lat: 49.27248088099777, lng: -123.13384294509888},
        {lat: 49.2696667352996,  lng: -123.13049554824829},
        {lat: 49.268546632648494,lng: -123.13055992126465},
        {lat: 49.268350612069995,lng: -123.13066720962524},
        {lat: 49.2684906268484,  lng: -123.13146114349365},
        {lat: 49.268546632648494,lng: -123.13249111175537},
        {lat: 49.26888266611402, lng: -123.13347816467285},
        {lat: 49.26889666745873, lng: -123.13401460647583},
        {lat: 49.2706328034105,  lng: -123.1368041038513 }
 ];
    
  //coordinates of blocks just east of burrard.
  var burrard_coords = [
  {lat: 49.267972570183545, lng: -123.145751953125},
  {lat: 49.2679445669656,   lng: -123.14085960388184},
  {lat: 49.27032478374826,  lng: -123.14077377319336},
  {lat: 49.27138884351881,  lng: -123.14176082611084},
  {lat: 49.27309689147504,  lng: -123.14356327056885},
  {lat: 49.27267688516586,  lng: -123.14467906951904},
  {lat: 49.27152884967477,  lng: -123.14553737640381},
  {lat: 49.269834748503946, lng: -123.1459450721740}
  ];
  
  //make the satellite view google map, center it in Vancouver.
  map = new google.maps.Map(document.getElementById("map_div"), {
      
    center: new google.maps.LatLng(vancouver.lat, vancouver.lng),
    zoom: 16,
    mapTypeId: google.maps.MapTypeId.HYBRID
      
  });
  
  //this style is easier on the eyes than the default black.
  //BADASS and COFFEE hex to the rescue.
  var polystyle = {
      strokeColor: '#BADA55',
      strokeOpacity: 0.8,
      strokeWeight: 2,
      fillColor: '#C0FFEE',
      fillOpacity: 0.35
  }
  
  //options for granville polygon.
  //SNAPABLE = TRUE
  var poly1_opts = $.extend({
      paths: granville_coords,
      map: map,
      snapable: true
  }, polystyle);
    
  //options for burrard polygon
  //SNAPABLE not present (false)
  var poly2_opts = $.extend({
   paths: burrard_coords,
    map: map
  }, polystyle);
  
  //let's make the polygons  
  var granville = new google.maps.Polygon(poly1_opts);
  var burrard   = new google.maps.Polygon(poly2_opts);
  
  /*
      For demo purposes, lets just put two gmaps Polys into the polygon array.
      For your application purposes, you would populate this array with
      all of the polygons you want to snap to - likely driven from the DB.
  */
  polygons = [granville, burrard];
  
  /*
   Now, we make the SnapManager.
    See http://stackoverflow.com/a/33338065/568884 for API
    Will be transferred to Github soon.
  */
    
  SM = PolySnapper({
      map: map,
      marker: new google.maps.Marker(),
      threshold: 20,
      keyRequired: false,
      polygons: polygons,
      polystyle: polystyle,
      hidePOI: true,
      onEnabled: function(){
  console.log("enabled")
      },
      onDisabled: function(){
  console.log("disabled")
      }
  });
  
  //add the buttons initial state on top of the map.
  renderCpanel(false);
 
});

//when user clicks log poly button, pull the poly out of the manager and console.log it.
$(document).on("click", "#query", function(){
   console.log( SM.poly().getPath().getArray() );
});

//just a small render function to re-draw the buttons whenever the enabled state is flipped on and off.
function renderCpanel(drawing){
    
    var t = $("#control-panel").html();
    var html = _.template(t, {drawing: drawing});
    $("#cp-wrap").html(html);
    
}

//attach the click handlers to the button. #cp-wrap is never added or removed
//from the DOM, so its safe to bind the listeners to it.
$("#cp-wrap").on("click", "button", function(){
 
    var action = $(this).data("action");
    
    if    (action == 'new')  SM.enable();
  else if(action == 'query')  console.log( SM.polygon() )
    else           SM.disable();
    
    renderCpanel( (action == 'new')  );
    
});
body {
  margin: 0;
  padding: 0;
  font: 12px sans-serif;
}
#cp-wrap{
    position: absolute;
    top: 10px;
    left:120px;
    background-color:white;
}

#cp-wrap button{
    font-size: 22px;
}
<script src="https://rawgit.com/jordanarseno/polysnapper/master/polysnapper.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maps.google.com/maps/api/js?.js"></script>
<div style='position:relative;'>
    <div id="map_div" style="height: 600px; width:100%;"></div>
    <div id='cp-wrap'></div>
</div>

<script id='control-panel' type='text/template'>
    <% if(drawing) { %>
        <button data-action='cancel' >cancel</button>
        <button data-action='query' >log poly</button>
    <% } else { %>
        <button data-action='new' >new poly</button>
    <% } %>
</script>
Jordan Arsenault
  • 7,100
  • 8
  • 53
  • 96
  • and how do you get the actual content of PS.polygon() once you're done? – Magor Menessy Feb 28 '18 at 18:12
  • 1
    I haven't used this in years, but from what I recall, `PS.polygon()` returns a Google Maps Polygon object. So, see the Google Maps API Docs how to obtain the polygon data. You could try `.toString()` to start. – Jordan Arsenault Mar 01 '18 at 22:14