DEV Community

Cover image for Integrating an OpenLayers map in Vue.js, a step-by-step guide

Integrating an OpenLayers map in Vue.js, a step-by-step guide

Olivia Guyot on September 08, 2020

Cover art by Donato Giacola. Introduction Hi! Chances are, you have at least heard of Vue.js, the very popular frontend JavaScript fram...
Collapse
 
rowild profile image
Robert Wildling

Thank you for this article! Very interesting and a great introduction into how to use OpenLayers with Vue! – Question: OpenLayers has several async functions, too, like "geolocation.on('change' ...". How can those function be used in a Vue component?

Collapse
 
jahow profile image
Olivia Guyot

Hi, thanks for reading!

It might be tempting to put all map-related logic in the MapContainer component, and for example do:

geolocation.on('change', (event) => {
  // we have direct access to the OpenLayers map here
  this.olMap.getView().setCenter(...)
})
Enter fullscreen mode Exit fullscreen mode

I would personally try to split concerns a bit more and maybe create a specific component for the geolocation logic for example. Event if this component does not output any HTML, it will be responsible for encapsulating the logic and simply output for example a center and radius (or a geometry).

IMO the main pitfall when integrating a library such as OpenLayers in a JS framework would be to end up with a large "black box" component which ends up being almost impossible to split up as complexity grows.

My 2 cents!

Collapse
 
rowild profile image
Robert Wildling

First of all: Thank you s much for your reply! Highly appreciated!

I am still not sure how you think of implementing it. When you say "in the MapContainer component" - where exactly do you mean? All the Vue logic live within the "export default {}" object. Usually, methods are put in the "methods" parameter of that object, but I cannot just put a function with an event handler there. At least I didn't get it to work. And even if: they won't cause any reactions since they are, at that point, simply methods that aren't called from anywhere...
I was thinking of using watchers, but watchers – as far as I know – are fired on each $tick and therefore not very performant if there is a huge number of events to be observed...
So I am still in the dark about using OL's event system in Vue...

Thread Thread
 
jahow profile image
Olivia Guyot

Ok, I think I see what you mean. Event handlers should be defined in the mounted callback, which is also where the OL map is initialized.

See vuejs.org/v2/guide/instance.html#I... for more info on Vue components lifecycle. Hope that helps!

Thread Thread
 
rowild profile image
Robert Wildling

Thank you again! I tried your solution already, but I unfortunately I did not achieve a "beautiful" result. The whole code organisation soon gets clunky and things need to be duplicated etc.

I only get results when implementing watchers and, since there are more components involved, managing several parameters using vuex (e.g. the current lon-lat values, which a needed in the map component as well as in the "trackNewRoute" component as well as in a sidebar component, where there is a button that zooms into the current location, which is necessary in case the user changed the map's center and zoom manually...)

I wonder if I could motivate you to write another tutorials, where you show how to implement a simple tracker? Something like bikemap, where the app updates you position and draws a line of your route?

Anyway, thank you again for your article and your valuable feedback!

Thread Thread
 
jahow profile image
Olivia Guyot

Hmm, that is a very nice idea for a follow-up tutorial, which I was planning to write sooner than later. Thanks, I'll keep that in mind!

Thread Thread
 
rowild profile image
Robert Wildling

Great! – And I just wanted to let you know that you have provided the path to y solutions. A bit of refactoring, removing the watchers and setting up the listeners in the mounted() cycle already helped me a lot! That's great! Thank you for making this Sunday evening a successful one!
Looking forward to your tutorial!

Collapse
 
jduncanradblue profile image
Jen Duncan

I am having trouble with the new vue3 composition API and getting this to work. The source is not allowing me to store it in the component instance. If I do, I get the following error:

events.js?781a:63 Uncaught (in promise) TypeError: target.addEventListener is not a function
    at listen (events.js?781a:63:1)
    at LayerGroup.registerLayerListeners_ (Group.js?4337:182:1)
    at LayerGroup.handleLayersChanged_ (Group.js?4337:172:1)
    at LayerGroup.dispatchEvent (Target.js?363c:113:1)
    at LayerGroup.notify (Object.js?1b38:176:1)
    at LayerGroup.set (Object.js?1b38:215:1)
    at LayerGroup.setLayers (Group.js?4337:271:1)
    at new LayerGroup (Group.js?4337:141:1)
    at createOptionsInternal (Map.js?985a:1752:1)
    at new Map (Map.js?985a:259:1)
Enter fullscreen mode Exit fullscreen mode

Here's the code:

<template>
  <div id="map-root"
       style="width:100%; height:100%">
  </div>
</template>

<script setup>
import { onMounted, ref } from "vue";
import View from 'ol/View'
import Map from 'ol/Map'
import TileLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import GeoJSON from 'ol/format/GeoJSON'
import 'ol/ol.css'

const olMap = ref(null);
const vectorLayer = ref(null);

const data = {
  type: 'Feature',
  properties: {},
  geometry: {
    type: 'Polygon',
    coordinates: [
      [
        [
          -27.0703125,
          43.58039085560784
        ],
        [
          -28.125,
          23.563987128451217
        ],
        [
          -10.8984375,
          32.84267363195431
        ],
        [
          -27.0703125,
          43.58039085560784
        ]
      ]
    ]
  }
};
const updateSource = () => {
  const view = olMap.value.getView();
  const source = vectorLayer.value.getSource();
  const features = new GeoJSON({
    featureProjection: 'EPSG:3857',
  }).readFeatures(data);
  source.clear();
  source.addFeatures(features);
  view.fit(source.getExtent())
}
const initMap = () => {
  // a new vector layer is created with the feature
  vectorLayer.value = new VectorLayer({
    source: new VectorSource({
      features: [],
    }),
  })
  olMap.value = new Map({
    target: "map-root",
    layers: [
      new TileLayer({
        source: new OSM() // tiles are served by OpenStreetMap
      }),
        vectorLayer
    ],
    view: new View({
      center: [0, 0],
      zoom: 2
    })
  })
  updateSource();
}

onMounted(() => {
  initMap();

})

</script>
Enter fullscreen mode Exit fullscreen mode

If I change :

vectorLayer.value = new VectorLayer({
    source: new VectorSource({
      features: [],
    }),
Enter fullscreen mode Exit fullscreen mode

to

const vectorLayer = new VectorLayer({
    source: new VectorSource({
      features: [],
    }),
  })
Enter fullscreen mode Exit fullscreen mode

it works, but then I can not access the source in the updateSource function to add features, modify, delete, etc.

Any ideas?

Collapse
 
jahow profile image
Olivia Guyot

Looks like you're missing a .value when using vectorLayer in the map initialization:

olMap.value = new Map({
    // ...
    layers: [
      new TileLayer({
        source: new OSM()
      }),
        vectorLayer.value // <-- here!
    ],
    // ...
Enter fullscreen mode Exit fullscreen mode

Does that change anything?

Collapse
 
jduncanradblue profile image
Jen Duncan

YES! Thank you. I can't believe I missed that!

Collapse
 
melihaltintas profile image
Melih Altıntaş

I wrote an openlayers wrapper for vue 3.x. You can easily use it.
Github: github.com/MelihAltintas/vue3-open...
Doc: vue3openlayers.netlify.app/

Collapse
 
jahow profile image
Olivia Guyot

Thanks for sharing! Very interesting use of the Vue composition API, great work. How much of the OpenLayers API does this cover?

Wrappers definitely have their use in some contexts. Their downside is, in my opinion, that they need additional maintenance work compared to just using the core library (OpenLayers in this case).