在 Google 地图 API v3 中使用单独的删除按钮绘制圆/多边形

Drawing Circles / Polygons With Individual Delete Buttons in Google Maps API v3

这是为了帮助那些一直在尝试实现这个或者像我一样一直在寻找这个解决方案的人。由于未能成功找到现有的解决方案,我最终决定这样做。

如何向绘制的形状(圆形/多边形)添加删除按钮或 X 标记,以便可以使用此按钮删除单个形状?

这是查看库的 Github Link,也是一个完整的工作示例。

在绘制圆时,删除按钮(X 标记)将放置在圆周上 45 度(东北)的位置。 对于多边形,因为它们是不可预测的,所以我将删除按钮(X 标记)放在多边形的第一个顶点旁边。

您可以删除单个 circles/polygons 或清除所有绘制的形状。

要直接调用 HTML 文件中的库,请使用以下脚本,
<script src="https://gist.github.com/loyalvares/c4ba7420b1eb055b309ab48bdcd34219.js"></script>

这跟JSFiddle Link一样。

/*
     * Method that is called when Google Maps API is loaded.
     */
    function initMap() {
     setInitialMapOptions();
     map = getMapObject(mapOptions);
     drawingManager = getDrawingManagerObject();
     google.maps.event.addListener(drawingManager, 'overlaycomplete', onOverlayComplete);
     initializeDeleteOverlayButtonLibrary();
    }

    // Get Map Geo Center Denver, USA Coordinates
    var center = {lat: 39.810866, lng: -104.990347};
    var map, drawingManager, mapOptions = {};
    var listenerFiltersApplied = false;
    var overlays = {};
    var circleOptions = {
            fillColor: "#e20000",
            fillOpacity: 0,
            strokeColor: "#e20000",
            strokeWeight: 4,
            strokeOpacity: 1,
            clickable: false,
            editable: true,
            suppressUndo: true,
            zIndex: 999
       };
    var polygonOptions = {
      editable: true,
      fillColor: "#e20000",
            fillOpacity: 0,
      strokeColor: "#e20000",
      strokeWeight: 4,
         strokeOpacity: 1,
         suppressUndo: true,
         zIndex: 999
     };

    function setInitialMapOptions() {
     mapOptions = {
       zoom: 4,
       center: center,
       styles: [
        {"featureType":"road", elementType:"geometry", stylers: [{visibility:"off"}]}, //turns off roads geometry
        {"featureType":"road", elementType:"labels", stylers: [{visibility:"off"}]}, //turns off roads labels
        {"featureType":"poi", elementType:"labels", stylers: [{visibility:"off"}]},  //turns off points of interest lines
        {"featureType":"poi", elementType:"geometry", stylers: [{visibility:"off"}]},  //turns off points of interest geometry
        {"featureType":"transit", elementType:"labels", stylers: [{visibility:"off"}]},  //turns off transit lines labels
        {"featureType":"transit", elementType:"geometry", stylers: [{visibility:"off"}]}, //turns off transit lines geometry
        {"featureType":"administrative.land_parcel", elementType:"labels", stylers: [{visibility:"off"}]},  //turns off administrative land parcel labels
        {"featureType":"administrative.land_parcel", elementType:"geometry", stylers: [{visibility:"off"}]},  //turns off administrative land parcel geometry
        {"featureType":"water", elementType:"geometry", stylers: [{color: '#d1e1ff'}]},  //sets water color to a very light blue
        {"featureType":"landscape", elementType:"geometry", stylers: [{color: '#fffffa'}]},  //sets landscape color to a light white color
        ],
        mapTypeControl: false,
        panControl: true,
        panControlOptions: {
         position: google.maps.ControlPosition.RIGHT_CENTER
        },
        streetViewControl: false,
        scaleControl: false,
        zoomControl: true,
        zoomControlOptions: {
         style: google.maps.ZoomControlStyle.SMALL,
         position: google.maps.ControlPosition.RIGHT_BOTTOM
        },
        minZoom: 2
     };
    }

    function getMapObject(mapOptions) {
        var map = new google.maps.Map(document.getElementById('map'), mapOptions);
        return map;
    }

    function getDrawingManagerObject(drawingManagerOptions) {
     var drawingManager = new google.maps.drawing.DrawingManager({
            drawingMode: null,
            drawingControl: true,
            drawingControlOptions: {
              position: google.maps.ControlPosition.TOP_CENTER,
              drawingModes: [
               google.maps.drawing.OverlayType.CIRCLE,
               google.maps.drawing.OverlayType.POLYGON
           ]
            },
            circleOptions: circleOptions,
            polygonOptions: polygonOptions
          });
          drawingManager.setMap(map);
          return drawingManager;
    }

    /* -- Overlay Functions Begin Here -- */
    function onOverlayComplete(shape) {
     addDeleteButtonToOverlay(shape);
     addOverlayListeners(shape);
     if(listenerFiltersApplied) {
      listenerFiltersApplied = false;
     }
    }

    function addOverlayListeners(shape) {
     // Filters already applied.
     if(listenerFiltersApplied) {
      return;
     }
     if (shape.type == google.maps.drawing.OverlayType.POLYGON) {
      setBoundsChangedListener(shape);
     } 
     if (shape.type == google.maps.drawing.OverlayType.CIRCLE) {
      setCenterChangedListener(shape);
      setRadiusChangedListener(shape);
     }
    }

    function setBoundsChangedListener(shape) {
     // Add listeners for each path of the polygon.
     shape.overlay.getPaths().forEach(function(path, index){
      // New point
      google.maps.event.addListener(path, 'insert_at', function(){
       listenerFiltersApplied = true;
       onOverlayComplete(shape);
      });
      // Point was removed
      google.maps.event.addListener(path, 'remove_at', function(){
       listenerFiltersApplied = true;
       onOverlayComplete(shape);
      });
      // Point was moved
      google.maps.event.addListener(path, 'set_at', function(){
       listenerFiltersApplied = true;
       onOverlayComplete(shape);
      });
     });
    }

    function setCenterChangedListener(shape) {
     google.maps.event.addListener(shape.overlay, 'center_changed', function() {
      listenerFiltersApplied = true;
      onOverlayComplete(shape);
     });
    }

    function setRadiusChangedListener(shape) {
     google.maps.event.addListener(shape.overlay, 'radius_changed', function() {
      listenerFiltersApplied = true;
      onOverlayComplete(shape);
     });
    }

    function addDeleteButtonToOverlay(shape) {
     var deleteOverlayButton = new DeleteOverlayButton();
     if(("deleteButton" in shape) && (shape.deleteButton != null)) {
      shape.deleteButton.div.remove();
      shape.deleteButton = deleteOverlayButton;
     } else {
      shape.deleteButton = deleteOverlayButton;
     }
     if(shape.type == google.maps.drawing.OverlayType.CIRCLE) {
      var radiusInKms = convertDistance(Math.round(shape.overlay.getRadius()), "metres", "kms");
      var circleCenter = new google.maps.LatLng(shape.overlay.getCenter().lat(), shape.overlay.getCenter().lng());
      var deleteOverlayButtonPosition = circleCenter.destinationPoint(30, radiusInKms);
      deleteOverlayButton.open(map, deleteOverlayButtonPosition, shape);
     } else if (shape.type == google.maps.drawing.OverlayType.POLYGON) {
      deleteOverlayButton.open(map, shape.overlay.getPath().getArray()[0], shape);
     }
      
      if (!('uid' in shape)) {
      shape.uid = Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
     }
     overlays[shape.uid] = shape;
    }

    function clearAllOverlays() {
     for(var shapeId in overlays) {
      if(overlays.hasOwnProperty(shapeId)) {
       var shape = overlays[shapeId];
       if(("deleteButton" in shape) && (shape.deleteButton != null)) {
        shape.deleteButton.div.remove();
       }
       shape.overlay.setMap(null);
      }
     }
     overlays = {};
    }

    /*
     * Add any code that needs to be run or cleaned up in this method. 
     * This method is called in DeleteOverlayButton.removeShape().
     */
    function callOnDelete(shape) {
     if(shape['uid'] in overlays) {
      delete overlays[shape['uid']];
     }
    }
    /* -- Overlay Functions End Here -- */

    function convertDistance(distanceValue, actualDistanceUnit, expectedDistanceUnit) {
     var distanceInKms = 0;
     switch(actualDistanceUnit) {
      case "miles":
       distanceInKms = distanceValue/0.62137;
       break;
      case "kms":
       distanceInKms = distanceValue;
       break;
      case "metres":
       distanceInKms = distanceValue/1000;
       break;
      default:
       distanceInKms = undefined;
     }
     
     switch(expectedDistanceUnit) {
      case "miles":
       return distanceInKms * 0.62137;
      case "kms":
       return distanceInKms;
      case "metres":
       return distanceInKms * 1000;
      default:
       return undefined;
     }
    }


    /* ***** Custom Library for Delete Overlay Button (Start) ***** */
     
     /**
      * A HTML Button that lets a user delete a component.
      * @constructor
      * @author: Loy Alvares
      */
     function DeleteOverlayButton() {
      this.div = document.createElement('div');
      this.div.id = 'deleteOverlayButton';
      this.div.className = 'deleteOverlayButton';
      this.div.title = 'Delete';
      this.div.innerHTML = '<span id="x">X</span>';
      var button = this;
      google.maps.event.addDomListener(this.div, 'click', function() {
          button.removeShape();
       button.div.remove();
      });
     }
     
     function initializeDeleteOverlayButtonLibrary() {
      
            /* This needs to be initialized by initMap() */
      DeleteOverlayButton.prototype = new google.maps.OverlayView();
      
      /**
       * Add component to map.
       */
      DeleteOverlayButton.prototype.onAdd = function() {
       var deleteOverlayButton = this;
       var map = this.getMap();
       this.getPanes().floatPane.appendChild(this.div);
      };

      /**
       * Clear data.
       */
      DeleteOverlayButton.prototype.onRemove = function() {
       google.maps.event.removeListener(this.divListener_);
       this.div.parentNode.removeChild(this.div);
       // Clear data
       this.set('position');
       this.set('overlay');
      };

      /**
       * Deletes an overlay.
       */
      DeleteOverlayButton.prototype.close = function() {
       this.setMap(null);
      };

      /**
       * Displays the Button at the position(in degrees) on the circle's circumference.
       */
      DeleteOverlayButton.prototype.draw = function() {
       var position = this.get('position');
       var projection = this.getProjection();
       if (!position || !projection) {
        return;
       }
       var point = projection.fromLatLngToDivPixel(position);
       this.div.style.top = point.y + 'px';
       this.div.style.left = point.x + 'px';
       if(this.get('overlay').type == google.maps.drawing.OverlayType.POLYGON) {
        this.div.style.marginTop = '-16px';
        this.div.style.marginLeft = '0px';
       }
      };

      /**
       * Displays the Button at the position(in degrees) on the circle's circumference.
       */
      DeleteOverlayButton.prototype.open = function(map, deleteOverlayButtonPosition, overlay) {
       this.set('position', deleteOverlayButtonPosition);
       this.set('overlay', overlay);
       this.setMap(map);
       this.draw();
      };

      /**
       * Deletes the shape it is associated with.
       */
      DeleteOverlayButton.prototype.removeShape = function() {
       var position = this.get('position');
       var shape = this.get('overlay');
       if (shape != null) {
        shape.overlay.setMap(null);
            /* Add any cleanup code or any other events in the below method. */
        callOnDelete(shape);
        return;
       }
       this.close();
      };
      
      Number.prototype.toRadians = function() {
       return this * Math.PI / 180;
      }

      Number.prototype.toDegrees = function() {
       return this * 180 / Math.PI;
      }

      /* Based the on the Latitude/Longitude spherical geodesy formulae & scripts
         at http://www.movable-type.co.uk/scripts/latlong.html
         (c) Chris Veness 2002-2010
      */
      google.maps.LatLng.prototype.destinationPoint = function(bearing, distance) {
       distance = distance / 6371;  
       bearing = bearing.toRadians();
       var latitude1 = this.lat().toRadians(), longitude1 = this.lng().toRadians();
       var latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(distance) + Math.cos(latitude1) * Math.sin(distance) * Math.cos(bearing));
       var longitude2 = longitude1 + Math.atan2(Math.sin(bearing) * Math.sin(distance) * Math.cos(latitude1), Math.cos(distance) - Math.sin(latitude1) * Math.sin(latitude2));
       if (isNaN(latitude2) || isNaN(longitude2)) return null;
       return new google.maps.LatLng(latitude2.toDegrees(), longitude2.toDegrees());
      }
     }

    /* ***** Custom Library for Delete Overlay Button (End) ***** */
/* Always set the map height explicitly to define the size of the div element that contains the map. */
    .map {
      height: 100%;
    }

    /* Optional: Makes the sample page fill the window. */
    html, body {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    /* CSS for the Delete Button. */
    .deleteOverlayButton {
      background: #dee0df;
      color: #000;
      /* font-family: 'Helvetica', 'Arial', sans-serif; */
      font-size: 11.4px;
      font-weight: bold;
      text-align: center;
      width: 14px;
      height: 15px;
      border-radius: 8px;
      box-shadow: 1px 0px -1px rgba(0, 0, 0, .3);
      position: absolute;
      padding: 0px 0px 0px 0px;
      margin-top: 7px;
      margin-left: 8px;
      border: 1px solid #999;
      cursor: pointer;
    }

    .deleteOverlayButton:hover {
      background: #eee;
    }

    #clearOverlays {
      font-family: var(--websiteFont);
      top: 10%;
      position: absolute;
      right: 1%;
      background: rgb(34,55,65);
      border-radius: 4px;
      color: white;
      border: 1px solid rgb(34,55,65);
      padding: 2px 6px;
      cursor: pointer;
    }
<div id="map" class="map"></div>
    <input id='clearOverlays' onclick="clearAllOverlays();" type=button value="Clear Shapes" />

    <link rel="stylesheet" type="text/css" href="https://code.jquery.com/ui/1.10.4/themes/ui-lightness/jquery-ui.css" />
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD7MXQvcn_gskiZeZGhhXekqN1zjUX9fVM&libraries=drawing&callback=initMap" async defer></script>