Building awesome Google Maps Polygon Manager with Drawing Library

single-image

In this tutorial I will show You how to build Google Maps Polygon Manager with Google Maps and Drawing Library. Keep on reading!

Preview results

How to start

Recently, as part of building my own portal, about which I will tell you in the next posts, I dreamed of such a builder so that users can mark areas where they will perform certain activities.

First, let’s start by adding html code with very simple buttons and a map marker (according to Google Maps documentation)

<div class="container">
    <div class="form-group">
        <label for="inp-polygons" class="">polygons</label>
        <textarea name="polygons" id="inp-polygons" class="form-control"></textarea>
    </div>

    <button id="circle-draw" class="btn btn-primary" type="button">
        Okrąg
    </button>

    <button id="polygon-draw" class="btn btn-primary" type="button">
        Dowolny kształt
    </button>

    <button id="back-draw" style="display: none;" class="btn btn-outline-primary" type="button">
        Cofnij ostatnie rysowanie
    </button>

    <div id="map" style="height: 70vh;" class="mt-3"></div>
</div>

Connecting and initializing the Library

It’s simple as you see

<script>
  var map

  const mapOptions = {
    zoom: 8,
    center: { lat: 52, lng: 22 }
  }

  const drawingManagerOptions = {
    drawingControl: false,
    polygonOptions: {
      draggable: true,
      editable: true
    },
    circleOptions: {
      draggable: true,
      editable: true
    }
  }

  const circle_btn = document.getElementById('circle-draw')
  const poly_btn = document.getElementById('polygon-draw')
  const back_btn = document.getElementById('back-draw')
  const save_state_btn = document.getElementById('save-map-state')

  var polygons = []

  window.initMap = () => {
    map = new google.maps.Map(document.getElementById('map'), mapOptions)
    const drawingManager = new google.maps.drawing.DrawingManager(drawingManagerOptions)

    drawingManager.setMap(map)
  }
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=HEREGOESYOURTOKKEN&callback=initMap&libraries=drawing"
        async defer></script>

Defining the drawing function

Our manager will allow you to draw circles and polylines, so we will create javascript functions that will be responsible for it. They will listen for button presses.

  window.circleButton = drawingManager => {
    circle_btn.addEventListener('click', (() => {
      // closure handles local toggle variables
      let toggled = false
      const originalHTML = circle_btn.innerHTML
      return e => {
        if (toggled) {
          drawingManager.setDrawingMode(null)
          e.target.innerHTML = originalHTML
          circle_btn.dataset.clicked = 'false'
        } else {
          if (poly_btn.dataset.clicked === 'true') {
            poly_btn.click()
          }
          drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE)
          e.target.innerHTML = 'Finish'
          circle_btn.dataset.clicked = 'true'
        }
        toggled = !toggled
      }
    })())
  }
  window.polylineButton = drawingManager => {
    poly_btn.addEventListener('click', (() => {
      // closure handles local toggle variables
      let toggled = false
      const originalHTML = poly_btn.innerHTML
      return e => {
        if (toggled) {
          drawingManager.setDrawingMode(null)
          e.target.innerHTML = originalHTML
          poly_btn.dataset.clicked = 'false'
        } else {
          if (circle_btn.dataset.clicked === 'true') {
            circle_btn.click()
          }
          drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON)
          e.target.innerHTML = 'Finish'
          poly_btn.dataset.clicked = 'true'
        }
        toggled = !toggled
      }
    })())
  }

As you can see, these functions are very similar. Both perform the same task – they turn on or off depending on the state of drawing on the map and change the text on the button.

Polygon to JSON function

Every time we change something (add or remove polygon) we will serialize the objects to the input in our specific way that we can successfully transfer to the backend.

  window.polygonsToString = () => {
    var json = []

    polygons.forEach(polygon => {
      var coords = []
      var type

      if (polygon instanceof google.maps.Polygon) {
        polygon.getPath().forEach(coord => {
          coords.push(coord.toJSON())
        })
        type = 'polygon'
      }

      if (polygon instanceof google.maps.Circle) {
        coords = {
          radius: polygon.getRadius(),
          center: polygon.getCenter().toJSON()
        }
        type = 'circle'
      }

      json.push({
        type: type,
        coords: coords
      })

    })
    document.getElementById('inp-polygons').value = JSON.stringify(json)
  }

Adding back button function

We will register a listener who will remove the last polygon from the global table. If array is empty hide back button.

  window.backButton = drawingManager => {
    back_btn.addEventListener('click', (() => {
      return e => {
        let polygon = polygons.pop()
        polygon.setMap(null)
        if (polygons.length === 0) {
          back_btn.style.display = 'none'
        }
        polygonsToString()
      }
    })())
  }

Changes to the initMap function

We need to change the initMap function so that it registers listeners to add (and edit!) Shapes on our map.

The second part of this function is to start the function that registers the operation of the buttons

window.initMap = () => {
    map = new google.maps.Map(document.getElementById('map'), mapOptions)
    const drawingManager = new google.maps.drawing.DrawingManager(drawingManagerOptions)

    drawingManager.setMap(map)

    google.maps.event.addListener(drawingManager, 'overlaycomplete', function (event) {
        back_btn.style.display = 'inline-block'
        polygons.push(event.overlay)

        if (event.type === 'polygon') {
          google.maps.event.addListener(event.overlay.getPath(), 'set_at', function () {
            polygonsToString()
          })

          google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function () {
            polygonsToString()

          })
        }

        if (event.type === 'circle') {
          google.maps.event.addListener(event.overlay, 'radius_changed', function () {
            polygonsToString()
          })

          google.maps.event.addListener(event.overlay, 'center_changed', function () {
            polygonsToString()
          })
        }

        polygonsToString()

      }
    )

    polylineButton(drawingManager)
    circleButton(drawingManager)
    backButton(drawingManager)
  }

Final full JS code

 var map

  const mapOptions = {
    zoom: 8,
    center: { lat: 52, lng: 22 }
  }

  const drawingManagerOptions = {
    drawingControl: false,
    polygonOptions: {
      draggable: true,
      editable: true
    },
    circleOptions: {
      draggable: true,
      editable: true
    }
  }

  const circle_btn = document.getElementById('circle-draw')
  const poly_btn = document.getElementById('polygon-draw')
  const back_btn = document.getElementById('back-draw')
  const save_state_btn = document.getElementById('save-map-state')

  var polygons = []

  window.initMap = () => {
    map = new google.maps.Map(document.getElementById('map'), mapOptions)
    const drawingManager = new google.maps.drawing.DrawingManager(drawingManagerOptions)

    drawingManager.setMap(map)

    google.maps.event.addListener(drawingManager, 'overlaycomplete', function (event) {
        back_btn.style.display = 'inline-block'
        polygons.push(event.overlay)

        if (event.type === 'polygon') {
          google.maps.event.addListener(event.overlay.getPath(), 'set_at', function () {
            polygonsToString()
          })

          google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function () {
            polygonsToString()

          })
        }

        if (event.type === 'circle') {
          google.maps.event.addListener(event.overlay, 'radius_changed', function () {
            polygonsToString()
          })

          google.maps.event.addListener(event.overlay, 'center_changed', function () {
            polygonsToString()
          })
        }

        polygonsToString()

      }
    )

    polylineButton(drawingManager)
    circleButton(drawingManager)
    backButton(drawingManager)
  }

  window.circleButton = drawingManager => {
    circle_btn.addEventListener('click', (() => {
      // closure handles local toggle variables
      let toggled = false
      const originalHTML = circle_btn.innerHTML
      return e => {

        if (toggled) {
          drawingManager.setDrawingMode(null)
          e.target.innerHTML = originalHTML
          circle_btn.dataset.clicked = 'false'
        } else {
          if (poly_btn.dataset.clicked === 'true') {
            poly_btn.click()
          }
          drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE)
          e.target.innerHTML = 'Back'
          circle_btn.dataset.clicked = 'true'
        }
        toggled = !toggled
      }
    })())
  }

  window.polylineButton = drawingManager => {
    poly_btn.addEventListener('click', (() => {
      // closure handles local toggle variables
      let toggled = false
      const originalHTML = poly_btn.innerHTML
      return e => {
        if (toggled) {
          drawingManager.setDrawingMode(null)
          e.target.innerHTML = originalHTML
          poly_btn.dataset.clicked = 'false'
        } else {
          if (circle_btn.dataset.clicked === 'true') {
            circle_btn.click()
          }
          drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON)
          e.target.innerHTML = 'Back'
          poly_btn.dataset.clicked = 'true'
        }
        toggled = !toggled
      }
    })())
  }

  window.backButton = drawingManager => {
    back_btn.addEventListener('click', (() => {
      return e => {
        let polygon = polygons.pop()
        polygon.setMap(null)
        if (polygons.length === 0) {
          back_btn.style.display = 'none'
        }
        polygonsToString()
      }
    })())
  }

  window.polygonsToString = () => {
    var json = []

    polygons.forEach(polygon => {
      var coords = []
      var type

      if (polygon instanceof google.maps.Polygon) {
        polygon.getPath().forEach(coord => {
          coords.push(coord.toJSON())
        })
        type = 'polygon'
      }

      if (polygon instanceof google.maps.Circle) {
        coords = {
          radius: polygon.getRadius(),
          center: polygon.getCenter().toJSON()
        }
        type = 'circle'
      }

      json.push({
        type: type,
        coords: coords
      })

    })
    document.getElementById('inp-polygons').value = JSON.stringify(json)
  }

Summary

That’s all for now. Our code works very well.

You can change it as you like to suit your needs. For example, add an additional type of shape from the library or change the way objects are serialized.


Please leave me comment if you have any question. If you liked this post, go to my instagram and follow me to stay up to date!

You may like