29

Google Maps has the Drawing library to draw Polylines and Polygons and other things.

Example of this functionality here: http://gmaps-samples-v3.googlecode.com/svn-history/r282/trunk/drawing/drawing-tools.html

I want, when drawing and editing the polygon, to be able to delete one point/vertex on the path. The API docs haven't seemed to hint at anything.

Kara
  • 6,115
  • 16
  • 50
  • 57
James F
  • 554
  • 1
  • 9
  • 19

6 Answers6

42

Google Maps now provides a "PolyMouseEvent" callback object on events that are triggered from a Polygon or Polyline.

To build on the other answers which suggested a solution involving a right click, all you would need to do is the following in the latest versions of the V3 API:

// this assumes `my_poly` is an normal google.maps.Polygon or Polyline
var deleteNode = function(mev) {
  if (mev.vertex != null) {
    my_poly.getPath().removeAt(mev.vertex);
  }
}
google.maps.event.addListener(my_poly, 'rightclick', deleteNode);

You'll notice that any complex calculations on whether or not we are near the point are no longer necesary, as the Google Maps API is now telling us which vertex we've clicked on.

Note: this will only work while the Polyline/Polygon is in edit mode. (Which is when the vertices you might want to delete are visible.)

As a final thought, you could consider using a click or double click event instead. "Click" is smart enough to not trigger on a drag, though using a single click trigger might still surprise some of your users.

Sean Ouimet
  • 429
  • 4
  • 5
  • 2
    You can switch `my_poly` in the callback for `this`. I went with `click` protected with a `confirm` dialog... I don't think users would guess to right click (context menu) or double click (zoom), but clicking is natural. Thanks! – Adrian Schneider Aug 08 '12 at 13:50
  • This doesn't work properly for polygons that have more than one path, such as donuts. See my answer for code that correctly handles multiple paths. –  Dec 17 '13 at 22:44
  • I agree with with @AdrianSchneider, click+dialog seems like a better UX option – sebasira Jul 29 '22 at 19:10
28

This is currently an outstanding feature request (acknowledged by Google), issue 3760.

Here's my solution: http://jsbin.com/ajimur/10. It uses a function that adds a delete button to the passed in polygon (below the undo button).


Alternatively, someone suggested this approach: right-click to delete closest vertex, which works fine but is somewhat lacking in UI finesse. I built on the code from the link to check if the click was inside (or within 1 pixel of) the node - in a JSBin here: http://jsbin.com/ajimur/.

EDIT: as Amr Bekhit pointed out - this approach is currently broken, as the events need to be attached to the polygon.

Community
  • 1
  • 1
Ian Grainger
  • 5,148
  • 3
  • 46
  • 72
  • Your demo page works for me in FF, IE and Chrome but not in Opera which seems to override the rightclick event. But Thx. – mtom Mar 16 '12 at 09:03
  • Right - Opera can be a bit mean in what it allows scripts to do by default. Does this help: http://groups.google.com/a/googleproductforums.com/forum/#!category-topic/maps/navigation-and-directions/4dy2iYzRKgk – Ian Grainger Mar 16 '12 at 15:22
  • 2
    Not to sound like expertsexchange or anything, but +1 me if it was helpful! :) – Ian Grainger Mar 16 '12 at 15:27
  • I've been using the "right click to delete" method for a while now and it's been working fine. However, it looks like a recent Google maps update has stopped this from working. I initially thought it was my code, but both your examples have stopped working too...annoying! – Amr Bekhit Apr 26 '12 at 14:51
  • I posted about this issue here (http://code.google.com/p/gmaps-api-issues/issues/detail?id=4112) and fortunately, have received a response. It looks like the editing handles now generate click events that are attached to the polygon itself. So, in order to modify the two samples in the answer, change the polygon's clickable attribute to true and attach the right click event listener to the polygon, not the map. – Amr Bekhit May 03 '12 at 21:47
  • @AmrBekhit my first sample (the /10) seems to still be working - but the right-click to delete one isn't. I'll edit my answer for now until I have chance to fix the example - unless you have a bin with the fix? – Ian Grainger Nov 03 '15 at 10:07
  • It's been such a long time since I looked at this! The main issue was that I wasn't specifying the API version when linking the Google maps js file, so it was always pulling the latest version, which could break things. Once I specified the version (using the v=x.x parameter), I could fix the problem using that version and leave it at that. Easiest thing for your demos will probably be to specify one of the old API versions. – Amr Bekhit Nov 03 '15 at 10:12
  • @AmrBekhit Sure, but it seems like pegging the version for one of these samples wouldn't be very productive as people writing this for the first time would always want to write against the latest version of the maps API (I should think!) It feels more correct to me to say: 'you could do something like this in the latest version,' rather than: 'I used to do this and it worked' ;) – Ian Grainger Nov 03 '15 at 11:44
  • In this example http://jsbin.com/ajimur/10 the polygon is already completed and we can undo vertex when polygon point is changed. But my requirement is to undo the last drawn vertex or point "during when I am drawing" polygon (Not already drawn polygon). For example "i am drawing polygon and by mistake I put point on wrong place and I want to undo it". I searched a lot but cannot find this feature. please help. – shiv Sep 11 '18 at 07:25
  • @shiv I have not attempted to solve undoing the addition of a point when first drawing a shape. Sorry. – Ian Grainger Sep 11 '18 at 15:37
21

I found Sean's code very simple and helpful. I just added a limiter to stop deleting when the user has only 3 nodes left. Without it, the user can get down to just one node, and can't edit anymore:

my_poly.addListener('rightclick', function(mev){
    if (mev.vertex != null && this.getPath().getLength() > 3) {
        this.getPath().removeAt(mev.vertex);
    }
});
Evil Red Robot
  • 331
  • 2
  • 3
  • 3
    Having 2 nodes left is still ok because there is that faded node between them to create a new one. But thanks for the hint, I used it. – Oliver Apr 25 '13 at 13:33
  • Elegant solution! One change needed. Polygons have vertices+1 points in the path because they need an extra point to close the polygon. So change the 3 to a 4. That works for me. – Brad Mathews Aug 28 '18 at 05:32
11

I ran into situations where I needed to delete nodes from polygons that contained multiple paths. Here's a modification of Sean's and Evil's code:

shape.addListener('rightclick', function(event){
  if(event.path != null && event.vertex != null){
    var path = this.getPaths().getAt(event.path);
    if(path.getLength() > 3){
      path.removeAt(event.vertex);
    }
  }
});
2

Just thought I'd contribute because I was looking for a solution for this too, here's my implementation:

if (m_event.hasOwnProperty('edge') && m_event.edge >= 0 &&
GeofenceService.polygon.getPath().getLength() > 3) {
    GeofenceService.polygon.getPath().removeAt(m_event.edge);
    return;
}

if (m_event.hasOwnProperty('vertex') && m_event.vertex >= 0 &&
GeofenceService.polygon.getPath().getLength() > 3) {
    GeofenceService.polygon.getPath().removeAt(m_event.vertex);
    return;
}

This allows for handling deletion of vertex nodes AND edge nodes, and maintains a minimum of a triangle formation polygon at all times by checking the path length > 3.

andro1d
  • 568
  • 2
  • 11
  • 20
1

2020 Update

Google provides a working demo of this in their documentation which demonstrates how a to delete a vertex, or a point on the line, by right-clicking on a vertex to show a "Delete" menu.

Check out Deleting a Vertex

Deleting a Vertex

And the code for completeness (see their Github repo);

function initialize() {
  const mapOptions = {
    zoom: 3,
    center: new google.maps.LatLng(0, -180),
    mapTypeId: "terrain",
  };
  const map = new google.maps.Map(document.getElementById("map"), mapOptions);
  const flightPlanCoordinates = [
    new google.maps.LatLng(37.772323, -122.214897),
    new google.maps.LatLng(21.291982, -157.821856),
    new google.maps.LatLng(-18.142599, 178.431),
    new google.maps.LatLng(-27.46758, 153.027892),
  ];
  const flightPath = new google.maps.Polyline({
    path: flightPlanCoordinates,
    editable: true,
    strokeColor: "#FF0000",
    strokeOpacity: 1.0,
    strokeWeight: 2,
    map: map,
  });

  /**
   * A menu that lets a user delete a selected vertex of a path.
   */
  class DeleteMenu extends google.maps.OverlayView {
    constructor() {
      super();
      this.div_ = document.createElement("div");
      this.div_.className = "delete-menu";
      this.div_.innerHTML = "Delete";
      const menu = this;
      google.maps.event.addDomListener(this.div_, "click", () => {
        menu.removeVertex();
      });
    }
    onAdd() {
      const deleteMenu = this;
      const map = this.getMap();
      this.getPanes().floatPane.appendChild(this.div_);
      // mousedown anywhere on the map except on the menu div will close the
      // menu.
      this.divListener_ = google.maps.event.addDomListener(
        map.getDiv(),
        "mousedown",
        (e) => {
          if (e.target != deleteMenu.div_) {
            deleteMenu.close();
          }
        },
        true
      );
    }
    onRemove() {
      if (this.divListener_) {
        google.maps.event.removeListener(this.divListener_);
      }
      this.div_.parentNode.removeChild(this.div_);
      // clean up
      this.set("position", null);
      this.set("path", null);
      this.set("vertex", null);
    }
    close() {
      this.setMap(null);
    }
    draw() {
      const position = this.get("position");
      const projection = this.getProjection();

      if (!position || !projection) {
        return;
      }
      const point = projection.fromLatLngToDivPixel(position);
      this.div_.style.top = point.y + "px";
      this.div_.style.left = point.x + "px";
    }
    /**
     * Opens the menu at a vertex of a given path.
     */
    open(map, path, vertex) {
      this.set("position", path.getAt(vertex));
      this.set("path", path);
      this.set("vertex", vertex);
      this.setMap(map);
      this.draw();
    }
    /**
     * Deletes the vertex from the path.
     */
    removeVertex() {
      const path = this.get("path");
      const vertex = this.get("vertex");

      if (!path || vertex == undefined) {
        this.close();
        return;
      }
      path.removeAt(vertex);
      this.close();
    }
  }
  const deleteMenu = new DeleteMenu();
  google.maps.event.addListener(flightPath, "rightclick", (e) => {
    // Check if click was on a vertex control point
    if (e.vertex == undefined) {
      return;
    }
    deleteMenu.open(map, flightPath.getPath(), e.vertex);
  });
}
Ralpharoo
  • 789
  • 9
  • 21