在Google Maps API v3中使用单个删除按钮绘制圆/多边形
问题描述:
这是为了帮助那些试图实现此目标的人,或者像我一直在寻找这种解决方案的人一样。没有成功找到现有的解决方案,我最终决定去做。在Google Maps API v3中使用单个删除按钮绘制圆/多边形
如何将删除按钮或X标记添加到绘制的形状(圆形/多边形),以便可以使用此按钮删除单个形状?
答
这是Github Link查看图书馆,也是一个完整的工作示例。
在绘制圆圈时,删除按钮(X标记)将放置在圆周上45度(东北)。 对于多边形,由于它们可能是不可预测的,我将删除按钮(X标记)放置在多边形的第一个顶点旁边。
您可以删除单个圆/多边形或清除所有绘制的图形。
直接调用库在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() {
\t setInitialMapOptions();
\t map = getMapObject(mapOptions);
\t drawingManager = getDrawingManagerObject();
\t google.maps.event.addListener(drawingManager, 'overlaycomplete', onOverlayComplete);
\t 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
\t };
var polygonOptions = {
\t \t editable: true,
\t \t fillColor: "#e20000",
fillOpacity: 0,
\t \t strokeColor: "#e20000",
\t \t strokeWeight: 4,
\t strokeOpacity: 1,
\t suppressUndo: true,
\t zIndex: 999
\t };
function setInitialMapOptions() {
\t mapOptions = {
\t \t \t zoom: 4,
\t \t \t center: center,
\t \t \t styles: [
\t \t \t \t {"featureType":"road", elementType:"geometry", stylers: [{visibility:"off"}]}, \t //turns off roads geometry
\t \t \t \t {"featureType":"road", elementType:"labels", stylers: [{visibility:"off"}]}, \t //turns off roads labels
\t \t \t \t {"featureType":"poi", elementType:"labels", stylers: [{visibility:"off"}]}, //turns off points of interest lines
\t \t \t \t {"featureType":"poi", elementType:"geometry", stylers: [{visibility:"off"}]}, //turns off points of interest geometry
\t \t \t \t {"featureType":"transit", elementType:"labels", stylers: [{visibility:"off"}]}, //turns off transit lines labels
\t \t \t \t {"featureType":"transit", elementType:"geometry", stylers: [{visibility:"off"}]}, \t //turns off transit lines geometry
\t \t \t \t {"featureType":"administrative.land_parcel", elementType:"labels", stylers: [{visibility:"off"}]}, //turns off administrative land parcel labels
\t \t \t \t {"featureType":"administrative.land_parcel", elementType:"geometry", stylers: [{visibility:"off"}]}, //turns off administrative land parcel geometry
\t \t \t \t {"featureType":"water", elementType:"geometry", stylers: [{color: '#d1e1ff'}]}, //sets water color to a very light blue
\t \t \t \t {"featureType":"landscape", elementType:"geometry", stylers: [{color: '#fffffa'}]}, //sets landscape color to a light white color
\t \t \t \t ],
\t \t \t \t mapTypeControl: false,
\t \t \t \t panControl: true,
\t \t \t \t panControlOptions: {
\t \t \t \t \t position: google.maps.ControlPosition.RIGHT_CENTER
\t \t \t \t },
\t \t \t \t streetViewControl: false,
\t \t \t \t scaleControl: false,
\t \t \t \t zoomControl: true,
\t \t \t \t zoomControlOptions: {
\t \t \t \t \t style: google.maps.ZoomControlStyle.SMALL,
\t \t \t \t \t position: google.maps.ControlPosition.RIGHT_BOTTOM
\t \t \t \t },
\t \t \t \t minZoom: 2
\t };
}
function getMapObject(mapOptions) {
var map = new google.maps.Map(document.getElementById('map'), mapOptions);
return map;
}
function getDrawingManagerObject(drawingManagerOptions) {
\t var drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: null,
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
\t google.maps.drawing.OverlayType.CIRCLE,
\t google.maps.drawing.OverlayType.POLYGON
\t ]
},
circleOptions: circleOptions,
polygonOptions: polygonOptions
});
drawingManager.setMap(map);
return drawingManager;
}
/* -- Overlay Functions Begin Here -- */
function onOverlayComplete(shape) {
\t addDeleteButtonToOverlay(shape);
\t addOverlayListeners(shape);
\t if(listenerFiltersApplied) {
\t \t listenerFiltersApplied = false;
\t }
}
function addOverlayListeners(shape) {
\t // Filters already applied.
\t if(listenerFiltersApplied) {
\t \t return;
\t }
\t if (shape.type == google.maps.drawing.OverlayType.POLYGON) {
\t \t setBoundsChangedListener(shape);
\t } \t
\t if (shape.type == google.maps.drawing.OverlayType.CIRCLE) {
\t \t setCenterChangedListener(shape);
\t \t setRadiusChangedListener(shape);
\t }
}
function setBoundsChangedListener(shape) {
\t // Add listeners for each path of the polygon.
\t shape.overlay.getPaths().forEach(function(path, index){
\t \t // New point
\t \t google.maps.event.addListener(path, 'insert_at', function(){
\t \t \t listenerFiltersApplied = true;
\t \t \t onOverlayComplete(shape);
\t \t });
\t \t // Point was removed
\t \t google.maps.event.addListener(path, 'remove_at', function(){
\t \t \t listenerFiltersApplied = true;
\t \t \t onOverlayComplete(shape);
\t \t });
\t \t // Point was moved
\t \t google.maps.event.addListener(path, 'set_at', function(){
\t \t \t listenerFiltersApplied = true;
\t \t \t onOverlayComplete(shape);
\t \t });
\t });
}
function setCenterChangedListener(shape) {
\t google.maps.event.addListener(shape.overlay, 'center_changed', function() {
\t \t listenerFiltersApplied = true;
\t \t onOverlayComplete(shape);
\t });
}
function setRadiusChangedListener(shape) {
\t google.maps.event.addListener(shape.overlay, 'radius_changed', function() {
\t \t listenerFiltersApplied = true;
\t \t onOverlayComplete(shape);
\t });
}
function addDeleteButtonToOverlay(shape) {
\t var deleteOverlayButton = new DeleteOverlayButton();
\t if(("deleteButton" in shape) && (shape.deleteButton != null)) {
\t \t shape.deleteButton.div.remove();
\t \t shape.deleteButton = deleteOverlayButton;
\t } else {
\t \t shape.deleteButton = deleteOverlayButton;
\t }
\t if(shape.type == google.maps.drawing.OverlayType.CIRCLE) {
\t \t var radiusInKms = convertDistance(Math.round(shape.overlay.getRadius()), "metres", "kms");
\t \t var circleCenter = new google.maps.LatLng(shape.overlay.getCenter().lat(), shape.overlay.getCenter().lng());
\t \t var deleteOverlayButtonPosition = circleCenter.destinationPoint(30, radiusInKms);
\t \t deleteOverlayButton.open(map, deleteOverlayButtonPosition, shape);
\t } else if (shape.type == google.maps.drawing.OverlayType.POLYGON) {
\t \t deleteOverlayButton.open(map, shape.overlay.getPath().getArray()[0], shape);
\t }
if (!('uid' in shape)) {
\t \t shape.uid = Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
\t }
\t overlays[shape.uid] = shape;
}
function clearAllOverlays() {
\t for(var shapeId in overlays) {
\t \t if(overlays.hasOwnProperty(shapeId)) {
\t \t \t var shape = overlays[shapeId];
\t \t \t if(("deleteButton" in shape) && (shape.deleteButton != null)) {
\t \t \t \t shape.deleteButton.div.remove();
\t \t \t }
\t \t \t shape.overlay.setMap(null);
\t \t }
\t }
\t 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) {
\t if(shape['uid'] in overlays) {
\t \t delete overlays[shape['uid']];
\t }
}
/* -- Overlay Functions End Here -- */
function convertDistance(distanceValue, actualDistanceUnit, expectedDistanceUnit) {
\t var distanceInKms = 0;
\t switch(actualDistanceUnit) {
\t \t case "miles":
\t \t \t distanceInKms = distanceValue/0.62137;
\t \t \t break;
\t \t case "kms":
\t \t \t distanceInKms = distanceValue;
\t \t \t break;
\t \t case "metres":
\t \t \t distanceInKms = distanceValue/1000;
\t \t \t break;
\t \t default:
\t \t \t distanceInKms = undefined;
\t }
\t
\t switch(expectedDistanceUnit) {
\t \t case "miles":
\t \t \t return distanceInKms * 0.62137;
\t \t case "kms":
\t \t \t return distanceInKms;
\t \t case "metres":
\t \t \t return distanceInKms * 1000;
\t \t default:
\t \t \t return undefined;
\t }
}
/* ***** Custom Library for Delete Overlay Button (Start) ***** */
\t
\t /**
\t * A HTML Button that lets a user delete a component.
\t * @constructor
\t * @author: Loy Alvares
\t */
\t function DeleteOverlayButton() {
\t \t this.div = document.createElement('div');
\t \t this.div.id = 'deleteOverlayButton';
\t \t this.div.className = 'deleteOverlayButton';
\t \t this.div.title = 'Delete';
\t \t this.div.innerHTML = '<span id="x">X</span>';
\t \t var button = this;
\t \t google.maps.event.addDomListener(this.div, 'click', function() {
\t \t button.removeShape();
\t \t \t button.div.remove();
\t \t });
\t }
\t
\t function initializeDeleteOverlayButtonLibrary() {
\t \t
/* This needs to be initialized by initMap() */
\t \t DeleteOverlayButton.prototype = new google.maps.OverlayView();
\t \t
\t \t /**
\t \t * Add component to map.
\t \t */
\t \t DeleteOverlayButton.prototype.onAdd = function() {
\t \t \t var deleteOverlayButton = this;
\t \t \t var map = this.getMap();
\t \t \t this.getPanes().floatPane.appendChild(this.div);
\t \t };
\t \t /**
\t \t * Clear data.
\t \t */
\t \t DeleteOverlayButton.prototype.onRemove = function() {
\t \t \t google.maps.event.removeListener(this.divListener_);
\t \t \t this.div.parentNode.removeChild(this.div);
\t \t \t // Clear data
\t \t \t this.set('position');
\t \t \t this.set('overlay');
\t \t };
\t \t /**
\t \t * Deletes an overlay.
\t \t */
\t \t DeleteOverlayButton.prototype.close = function() {
\t \t \t this.setMap(null);
\t \t };
\t \t /**
\t \t * Displays the Button at the position(in degrees) on the circle's circumference.
\t \t */
\t \t DeleteOverlayButton.prototype.draw = function() {
\t \t \t var position = this.get('position');
\t \t \t var projection = this.getProjection();
\t \t \t if (!position || !projection) {
\t \t \t \t return;
\t \t \t }
\t \t \t var point = projection.fromLatLngToDivPixel(position);
\t \t \t this.div.style.top = point.y + 'px';
\t \t \t this.div.style.left = point.x + 'px';
\t \t \t if(this.get('overlay').type == google.maps.drawing.OverlayType.POLYGON) {
\t \t \t \t this.div.style.marginTop = '-16px';
\t \t \t \t this.div.style.marginLeft = '0px';
\t \t \t }
\t \t };
\t \t /**
\t \t * Displays the Button at the position(in degrees) on the circle's circumference.
\t \t */
\t \t DeleteOverlayButton.prototype.open = function(map, deleteOverlayButtonPosition, overlay) {
\t \t \t this.set('position', deleteOverlayButtonPosition);
\t \t \t this.set('overlay', overlay);
\t \t \t this.setMap(map);
\t \t \t this.draw();
\t \t };
\t \t /**
\t \t * Deletes the shape it is associated with.
\t \t */
\t \t DeleteOverlayButton.prototype.removeShape = function() {
\t \t \t var position = this.get('position');
\t \t \t var shape = this.get('overlay');
\t \t \t if (shape != null) {
\t \t \t \t shape.overlay.setMap(null);
/* Add any cleanup code or any other events in the below method. */
\t \t \t \t callOnDelete(shape);
\t \t \t \t return;
\t \t \t }
\t \t \t this.close();
\t \t };
\t \t
\t \t Number.prototype.toRadians = function() {
\t \t \t return this * Math.PI/180;
\t \t }
\t \t Number.prototype.toDegrees = function() {
\t \t \t return this * 180/Math.PI;
\t \t }
\t \t /* Based the on the Latitude/Longitude spherical geodesy formulae & scripts
\t \t at http://www.movable-type.co.uk/scripts/latlong.html
\t \t (c) Chris Veness 2002-2010
\t \t */
\t \t google.maps.LatLng.prototype.destinationPoint = function(bearing, distance) {
\t \t \t distance = distance/6371;
\t \t \t bearing = bearing.toRadians();
\t \t \t var latitude1 = this.lat().toRadians(), longitude1 = this.lng().toRadians();
\t \t \t var latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(distance) + Math.cos(latitude1) * Math.sin(distance) * Math.cos(bearing));
\t \t \t var longitude2 = longitude1 + Math.atan2(Math.sin(bearing) * Math.sin(distance) * Math.cos(latitude1), Math.cos(distance) - Math.sin(latitude1) * Math.sin(latitude2));
\t \t \t if (isNaN(latitude2) || isNaN(longitude2)) return null;
\t \t \t return new google.maps.LatLng(latitude2.toDegrees(), longitude2.toDegrees());
\t \t }
\t }
/* ***** 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 \t src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD7MXQvcn_gskiZeZGhhXekqN1zjUX9fVM&libraries=drawing&callback=initMap" async defer></script>