Skip to main content

drawing-manager

Description​

This component helps you to drawing shapes on over the map.

Drawing Library removal

Google deprecated the Maps JavaScript API Drawing Library and removed google.maps.drawing.DrawingManager in Maps JavaScript API v3.65+. GmapDrawingManager is kept only for legacy API versions where Google still serves the Drawing Library. For current Maps API versions, build drawing workflows with editable shapes, the Data layer/GeoJSON, or a third-party drawing library such as Terra Draw.

For more information read the Google Maps documentation for drawing manager.

It is exported with the name GmapDrawingManager.

Variables​

This component save the original drawing manager object provided by Google Maps in a property called $drawingManagerObject, as the example below.

this.$drawingManagerObject = new google.maps.drawing.DrawingManager(...);

Source code​

Click to se the source code of drawing-manager.vue component
<template>
<div>
<slot
v-bind:setDrawingMode="setDrawingMode"
v-bind:deleteSelection="deleteSelection"
/>
</div>
</template>

<script>
import MapElementMixin from '../mixins/map-element';
import { drawingManagerMappedProps } from '../utils/mapped-props-by-map-element';
import { bindProps, getPropsValues } from '../utils/helpers';

export default {
mixins: [MapElementMixin],
props: {
circleOptions: {
type: Object,
},
markerOptions: {
type: Object,
},
polygonOptions: {
type: Object,
},
polylineOptions: {
type: Object,
},
rectangleOptions: {
type: Object,
},
position: {
type: String,
},
shapes: {
type: Array,
required: true,
},
},
data() {
return {
selectedShape: null,
drawingModes: [],
options: {
drawingMode: null,
drawingControl: true,
drawingControlOptions: {},
},
};
},
methods: {
setDrawingMode(mode) {
this.$drawingManagerObject.setDrawingMode(mode);
},
drawAll() {
const shapeType = {
circle: google.maps.Circle,
marker: google.maps.Marker,
polygon: google.maps.Polygon,
polyline: google.maps.Polyline,
rectangle: google.maps.Rectangle,
};

const self = this;
this.shapes.forEach((shape) => {
const shapeDrawing = new shapeType[shape.type](shape.overlay);
shapeDrawing.setMap(this.$map);
shape.overlay = shapeDrawing;
google.maps.event.addListener(shapeDrawing, 'click', () => {
self.setSelection(shape);
});
});
},
clearAll() {
this.clearSelection();
this.shapes.forEach((shape) => {
shape.overlay.setMap(null);
});
},
clearSelection() {
if (this.selectedShape) {
this.selectedShape.overlay.set('fillColor', '#777');
this.selectedShape.overlay.set('strokeColor', '#999');
this.selectedShape.overlay.setEditable(false);
this.selectedShape.overlay.setDraggable(false);
this.selectedShape = null;
}
},
setSelection(shape) {
this.clearSelection();
this.selectedShape = shape;
shape.overlay.setEditable(true);
shape.overlay.setDraggable(true);
this.selectedShape.overlay.set('fillColor', '#555');
this.selectedShape.overlay.set('strokeColor', '#777');
},
deleteSelection() {
if (this.selectedShape) {
this.selectedShape.overlay.setMap(null);
const index = this.shapes.indexOf(this.selectedShape);
if (index > -1) {
this.shapes.splice(index, 1);
}
}
},
addShape(shape) {
this.setDrawingMode(null);
this.shapes.push(shape);
const self = this;
google.maps.event.addListener(shape.overlay, 'click', () => {
self.setSelection(shape);
});
google.maps.event.addListener(shape.overlay, 'rightclick', () => {
self.deleteSelection();
});
this.setSelection(shape);
},
},
watch: {
position(position) {
if (this.$drawingManagerObject) {
const drawingControlOptions = {
position:
position && google.maps.ControlPosition[position]
? google.maps.ControlPosition[position]
: google.maps.ControlPosition.TOP_LEFT,
drawingModes: this.drawingModes,
};
this.$drawingManagerObject.setOptions({ drawingControlOptions });
}
},
circleOptions(circleOptions) {
if (this.$drawingManagerObject) {
this.$drawingManagerObject.setOptions({ circleOptions });
}
},
markerOptions(markerOptions) {
if (this.$drawingManagerObject) {
this.$drawingManagerObject.setOptions({ markerOptions });
}
},
polygonOptions(polygonOptions) {
if (this.$drawingManagerObject) {
this.$drawingManagerObject.setOptions({ polygonOptions });
}
},
polylineOptions(polylineOptions) {
if (this.$drawingManagerObject) {
this.$drawingManagerObject.setOptions({ polylineOptions });
}
},
rectangleOptions(rectangleOptions) {
if (this.$drawingManagerObject) {
this.$drawingManagerObject.setOptions({ rectangleOptions });
}
},
},
provide() {
// Infowindow needs this to be immediately available
const promise = await this.$mapPromise
.then((map) => {
this.$map = map;

// Initialize the maps with the given options
const initialOptions = {
...this.options,
map,
...getPropsValues(this, drawingManagerMappedProps),
};

const { options: extraOptions, ...finalOptions } = initialOptions;

this.drawingModes = Object.keys(finalOptions).reduce((modes, opt) => {
const val = opt.split('Options');

if (val.length > 1) {
modes.push(val[0]);
}

return modes;
}, []);

const position =
this.position && google.maps.ControlPosition[this.position]
? google.maps.ControlPosition[this.position]
: google.maps.ControlPosition.TOP_LEFT;

finalOptions.drawingMode = null;
finalOptions.drawingControl = !this.$scopedSlots.default;
finalOptions.drawingControlOptions = {
drawingModes: this.drawingModes,
position,
};

// https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
this.$drawingManagerObject = new google.maps.drawing.DrawingManager(
finalOptions
);

bindProps(this, this.$drawingManagerObject, drawingManagerMappedProps);

this.$drawingManagerObject.addListener('overlaycomplete', (e) =>
this.addShape(e)
);

this.$map.addListener('click', this.clearSelection);

if (this.shapes.length) {
this.drawAll();
}

return this.$drawingManagerObject;
})
.catch((error) => {
throw error;
});

this.$drawingManagerPromise = promise;
return { $drawingManagerPromise: promise };
},
destroyed() {
this.clearAll();

// Note: not all Google Maps components support maps
if (this.$drawingManagerObject && this.$drawingManagerObject.setMap) {
this.$drawingManagerObject.setMap(null);
}
},
};
</script>

If you need to know what are mappedProps please read the general concepts of this application here.

Mapped Props of GmapDrawingManager component

export const drawingManagerMappedProps = {
circleOptions: {
type: Object,
twoWay: false,
noBind: true,
},
markerOptions: {
type: Object,
twoWay: false,
noBind: true,
},
polygonOptions: {
type: Object,
twoWay: false,
noBind: true,
},
polylineOptions: {
type: Object,
twoWay: false,
noBind: true,
},
rectangleOptions: {
type: Object,
twoWay: false,
noBind: true,
},
};

How to use it​

<template>
<gmap-map
ref="mapRef"
:center="mapCenter"
:zoom="7"
map-type-id="roadmap"
style="width: 100%; height: 800px"
:options="{
zoomControl: true,
mapTypeControl: true,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
disableDefaultUi: false,
draggable: mapDraggable,
draggableCursor: mapCursor
}"
>
<!-- use the default slot to pass markers to it -->
<gmap-drawing-manager></gmap-drawing-manager>
</gmap-map>
</template>
<template>
<gmap-map
ref="mapRef"
:center="mapCenter"
:zoom="7"
map-type-id="roadmap"
style="width: 100%; height: 800px"
:options="{
zoomControl: true,
mapTypeControl: true,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
disableDefaultUi: false,
draggable: mapDraggable,
draggableCursor: mapCursor
}"
>
<!-- or with an slot -->
<gmap-drawing-manager>
<template v-slot="on">
<your-toolbar-component
@drawingmode_changed="on.setDrawingMode($event)"
@delete_selection="on.deleteSelection()"
@save="mapMode='ready'"
/>
</template>
</gmap-drawing-manager>
</gmap-map>
</template>

If you need to know the API of this component please read it here.

HTML examples​

HTML example
<body>
<div id="root">
<h1>Drawing Manager Example</h1>
<div style="width:100%; display: flex; justify-content: center;">
<span style="width:auto" />
{{ mapMode }} - {{ toolbarPosition }}
<span style="width:auto" />
<button @click="setPos">Position</button>
<button @click="mapMode='edit'">Edit</button>
</div>
<br />
<gmap-map
ref="mapRef"
:center="mapCenter"
:zoom="7"
map-type-id="roadmap"
style="width: 100%; height: 800px"
:options="{
zoomControl: true,
mapTypeControl: true,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
disableDefaultUi: false,
draggable: mapDraggable,
draggableCursor: mapCursor
}"
>
<template #visible>
<gmap-drawing-manager
v-if="mapMode==='edit'"
:position="toolbarPosition"
:rectangle-options="rectangleOptions"
:circle-options="circleOptions"
:polyline-options="polylineOptions"
:shapes="shapes"
/>
</template>
</gmap-map>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gmap-vue@1.2.2/dist/gmap-vue.min.js"></script>

<script>
Vue.use(GmapVue, {
load: {
key: 'AIzaSyDf43lPdwlF98RCBsJOFNKOkoEjkwxb5Sc',
libraries: 'places,drawing'
}
});

document.addEventListener('DOMContentLoaded', function() {
new Vue({
el: '#root',
data: {
mapCenter: { lat: 4.5, lng: 99 },
mapMode: null,
toolbarPosition: 'TOP_CENTER',
mapDraggable: true,
mapCursor: null,
shapes: [],
rectangleOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
},
circleOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
},
polylineOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
}
},
watch: {
mapMode(newMode, oldMode) {
if (newMode === 'ready') {
if (oldMode === 'edit') {
this.mapDraggable = true;
this.mapCursor = null;
return;
}
}

if (newMode === 'edit') {
this.mapDraggable = false;
this.mapCursor = 'default';
}
}
},
mounted() {
this.mapMode = 'ready';
},
methods: {
setPos() {
const posTypes = [
'TOP_CENTER',
'TOP_LEFT',
'TOP_RIGHT',
'LEFT_TOP',
'RIGHT_TOP',
'LEFT_CENTER',
'RIGHT_CENTER',
'LEFT_BOTTOM',
'RIGHT_BOTTOM',
'BOTTOM_CENTER',
'BOTTOM_LEFT',
'BOTTOM_RIGHT'
];

this.toolbarPosition =
posTypes[Math.floor(Math.random() * posTypes.length)];
}
}
});
});
</script>
</body>
HML examples with slot
<body>
<div id="root">
<h1>Drawing Manager Example</h1>
<div style="width:100%; display: flex; justify-content: center;">
<span style="width: auto;" />
{{ mapMode }}
<span style="width: auto;" />
<button @click="mapMode='edit'">Edit</button>
</div>
<br />
<gmap-map
ref="mapRef"
:center="mapCenter"
:zoom="7"
map-type-id="roadmap"
style="width: 100%; height: 800px; display:flex; justify-content: center; align-items: flex-start;"
:options="{
zoomControl: true,
mapTypeControl: true,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
disableDefaultUi: false,
draggable: mapDraggable,
draggableCursor: mapCursor
}"
>
<template #visible>
<gmap-drawing-manager
v-if="mapMode==='edit'"
:rectangle-options="rectangleOptions"
:circle-options="circleOptions"
:polyline-options="polylineOptions"
:polygon-options="polygonOptions"
:shapes="shapes"
>
<template v-slot="on">
<drawing-toolbar
@drawingmode_changed="on.setDrawingMode($event)"
@delete_selection="on.deleteSelection()"
@save="mapMode='ready'"
/>
</template>
</gmap-drawing-manager>
</template>
</gmap-map>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gmap-vue@1.2.2/dist/gmap-vue.min.js"></script>

<script>
// Only for example purposes
// In a real application you would simply create a separate component
const toolbarTemplate =
'<div style="background-color: #040404; display: flex; position: absolute; padding: 8px">' +
" <div><button @click=\"$emit('drawingmode_changed', 'rectangle')\">Rectangle</button></div>" +
" <div><button @click=\"$emit('drawingmode_changed', 'circle')\">Circle</button></div>" +
" <div><button @click=\"$emit('drawingmode_changed', 'polyline')\">Line</button></div>" +
" <div><button @click=\"$emit('drawingmode_changed', 'polygon')\">Polygon</button></div>" +
" <div><button @click=\"$emit('delete_selection')\">Delete</button></div>" +
" <div><button @click=\"$emit('save')\">Save</button></div>" +
"</div>";

Vue.use(GmapVue, {
load: {
key: 'AIzaSyDf43lPdwlF98RCBsJOFNKOkoEjkwxb5Sc',
libraries: 'places,drawing'
}
});

document.addEventListener('DOMContentLoaded', function() {
new Vue({
el: '#root',
components: {
drawingToolbar: {
template: toolbarTemplate
}
},
data: {
mapCenter: { lat: 4.5, lng: 99 },
mapMode: null,
mapDraggable: true,
mapCursor: null,
shapes: [],
rectangleOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
},
circleOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
},
polylineOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
},
polygonOptions: {
fillColor: '#777',
fillOpacity: 0.4,
strokeWeight: 2,
strokeColor: '#999',
draggable: false,
editable: false,
clickable: true
}
},
watch: {
mapMode(newMode, oldMode) {
if (newMode === 'ready') {
if (oldMode === 'edit') {
this.mapDraggable = true;
this.mapCursor = null;
return;
}
}

if (newMode === 'edit') {
this.mapDraggable = false;
this.mapCursor = 'default';
}
}
},
mounted() {
this.mapMode = 'ready';
}
});
});
</script>
</body>
warning

If you are using the runtime-only build the template on toolbarTemplate will not work, instead of that you need to pre-compile the templates into render functions, or use the compiler-included build.

Test the component​

Click to see the HTML example in action
Click to see the HTML example with slot in action