Skip to main content

drawing-manager

Description

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

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