Skip to content

Instantly share code, notes, and snippets.

@loyalvares
Last active November 20, 2021 11:53
Show Gist options
  • Save loyalvares/c4ba7420b1eb055b309ab48bdcd34219 to your computer and use it in GitHub Desktop.
Save loyalvares/c4ba7420b1eb055b309ab48bdcd34219 to your computer and use it in GitHub Desktop.
This is a custom library to add a Delete Button ( x mark) to a Google Maps v3 Circle or Polygon when drawn. This is the working JSFiddle link [ https://jsfiddle.net/foobarbazz/vn763fu7/ ] to see it in action.
/**
* author: Loy Alvares
* This utility was written to handle deletion of circles and polygons in Google Maps V3.
*
* Also thanks to Chris Veness for the distance calculation formulae from pointA to pointB
* ( Latitude/Longitude spherical geodesy formulae & scripts )
at http://www.movable-type.co.uk/scripts/latlong.html
(c) Chris Veness 2002-2010
*/
/* ***************** 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();
});
}
/*
* Preferably, call this method in your initMap().
* The basic code that need to be initialized by initMap() is :
* DeleteOverlayButton.prototype = new google.maps.OverlayView();
* and the following function,
* google.maps.LatLng.prototype.destinationPoint = function(bearing, distance) { //code }.
* Otherwise, you will get a "google is not defined" error.
*/
function initializeDeleteOverlayButtonLibrary() {
/* This needs to be initialized by initMap() to be able to call DeleteOverlayButton's functions. */
DeleteOverlayButton.prototype = new google.maps.OverlayView();
/**
* Add component to map.
* @author: Loy Alvares
*/
DeleteOverlayButton.prototype.onAdd = function() {
var deleteOverlayButton = this;
var map = this.getMap();
this.getPanes().floatPane.appendChild(this.div);
};
/**
* Clear data.
* @author: Loy Alvares
*/
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.
* @author: Loy Alvares
*/
DeleteOverlayButton.prototype.close = function() {
this.setMap(null);
};
/**
* Displays the Button at the position(in degrees) on the circle's circumference.
* @author: Loy Alvares
*/
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.
* @author: Loy Alvares
*/
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.
* @author: Loy Alvares
*/
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) ******************* */
/**
* author: Loy Alvares
*/
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;
}
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Google Maps - Circle / Polygon Delete Button</title>
<style>
/* 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: 5%;
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;
}
</style>
</head>
<body>
<!-- This is the working JSFiddle link [ https://jsfiddle.net/foobarbazz/vn763fu7/ ] to see it in action. -->
<div id="map" class="map"></div>
<input id='clearOverlays' onclick="clearAllOverlays();" type=button value="Clear Shapes" />
<script type="text/javascript" src="DeleteOverlayButton.js"></script>
<script type="text/javascript" src="googleMaps.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD7MXQvcn_gskiZeZGhhXekqN1zjUX9fVM&libraries=drawing&callback=initMap" async defer></script>
</body>
</html>
@loyalvares
Copy link
Author

This is the working JSFiddle link to see it in action.

@esthonwood
Copy link

Hi @loyalvares! Thank you for this script. You have helped me a lot. Just a couple questions though in using this script: how can I save the drawn shapes to MySQL and how to draw the saved shapes back to the map? I would appreciate it very much if you could give me some pointers. Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment