Skip to content

ol-source-vector

ol-source-vector can be used together with ol-vector-layer to draw any vector data on the map.

Demo

Setup

Plugin usage

This component is part of the Sources plugin. If not installed globally, you need to import and use the plugin in your main.ts or use the explicit component import (see section below).

Import and use the Sources plugin
ts
import { createApp } from "vue";
import App from "./App.vue";

import {

  Map,
  Layers,
  Sources,
} from "vue3-openlayers";

const app = createApp(App);
// ...
app.use(Sources); 
// ...

Explicit import

If you don't want to install a plugin, you can import the component explicitly. It's available as a child of the named export Sources.

NOTE

The following documentation refers to the plugin usage. Please make sure to adopt the component names, when you decided to use explicit component imports (e. g. <ol-map> becomes <Map.OlMap> etc.).

Usage

Plugin UsageExplicit Import
<ol-source-vector><Sources.OlSourceVector>

ol-feature component (GeoJSON)

Static features with the help of ol-feature, should be used only for tiny static layers.

vue
<template>
  <form>
    <fieldset>
      <label for="fill">Fill:</label>
      <input type="color" id="fill" v-model="fill" />
      <label for="stroke">Stroke:</label>
      <input type="color" id="stroke" v-model="stroke" />
    </fieldset>
    <fieldset>
      <label for="strokeWidth">StrokeWidth:</label>
      <input
        type="number"
        id="strokeWidth"
        step="1"
        min="0"
        v-model="strokeWidth"
      />
      <label for="radius">Radius:</label>
      <input type="number" id="radius" step="1" min="1" v-model="radius" />
    </fieldset>
  </form>
  <button class="btn-default" @click="changeCoordinate" type="button">
    change coordinates
  </button>
  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="height: 400px"
  >
    <ol-view
      ref="view"
      :center="center"
      :zoom="zoom"
      :projection="projection"
    />

    <ol-tile-layer>
      <ol-source-osm />
    </ol-tile-layer>

    <ol-vector-layer>
      <ol-source-vector>
        <ol-feature>
          <ol-geom-point :coordinates="coordinate"></ol-geom-point>
          <ol-style>
            <ol-style-circle :radius="radius">
              <ol-style-fill :color="fill"></ol-style-fill>
              <ol-style-stroke
                :color="stroke"
                :width="strokeWidth"
              ></ol-style-stroke>
            </ol-style-circle>
          </ol-style>
        </ol-feature>
      </ol-source-vector>
    </ol-vector-layer>
  </ol-map>
</template>

<script setup lang="ts">
import { ref } from "vue";

const center = ref([40, 40]);
const projection = ref("EPSG:4326");
const zoom = ref(8);
const radius = ref(40);
const strokeWidth = ref(10);
const stroke = ref("#ff0000");
const fill = ref("#ffffff");
const coordinate = ref([40, 40]);

function changeCoordinate() {
  coordinate.value = coordinate.value.map((a) => a + 0.01);
}
</script>

<style scoped>
button {
  border: 1px solid black;
  margin: 0.5rem 0;
  padding: 0.5rem;
}
button:hover,
button:focus {
  background-color: lightgray;
}
</style>

url property

Load features simply by providing url value and format GeoJSON

vue
<template>
  <form>
    <fieldset>
      <label for="opacity">Layer Opacity</label>
      <input
        type="range"
        id="opacity"
        min="0"
        max="1"
        step="0.1"
        v-model.number="opacity"
      />
      <span class="description">{{ opacity }}</span>
    </fieldset>
  </form>

  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="height: 400px"
  >
    <ol-view
      ref="view"
      :center="center"
      :zoom="zoom"
      :projection="projection"
      :constrainRotation="16"
    />

    <ol-vector-layer
      background="#1a2b39"
      ref="vectorSourceRef"
      :opacity="opacity"
    >
      <ol-source-vector :url="url" :format="geoJson">
        <ol-style :overrideStyleFunction="styleFn"></ol-style>
      </ol-source-vector>
    </ol-vector-layer>

    <ol-interaction-dragbox
      :condition="shiftKeyOnly"
      @boxstart="log('boxstart', $event)"
      @boxdrag="log('boxdrag', $event)"
      @boxend="log('boxend', $event)"
      @boxcancel="log('boxcancel', $event)"
    ></ol-interaction-dragbox>
  </ol-map>
</template>

<script setup lang="ts">
import type { Feature } from "ol";
import { Fill, Style } from "ol/style";
import { shiftKeyOnly } from "ol/events/condition";
import { ref, inject } from "vue";
import type { DragBoxEvent } from "ol/interaction/DragBox";

const opacity = ref(1);
const center = ref([0, 0]);
const projection = ref("EPSG:4326");
const zoom = ref(0);
const url = ref("https://openlayers.org/data/vector/ecoregions.json");
const format = inject("ol-format");
const geoJson = new format.GeoJSON();

function styleFn(feature: Feature) {
  return new Style({
    fill: new Fill({
      color: feature.get("COLOR_BIO") || "#eeeeee",
    }),
  });
}

function log(eventType: string, event: DragBoxEvent) {
  console.log(eventType, event);
}
</script>

features property

vue
<template>
  <label for="count">Marker:</label>
  <input type="number" id="count" v-model.number="count" max="50000" />
  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    renderer="webgl"
    style="height: 400px"
  >
    <ol-view
      ref="view"
      :center="center"
      :rotation="rotation"
      :zoom="zoom"
      :projection="projection"
    />

    <ol-tile-layer>
      <ol-source-osm />
    </ol-tile-layer>

    <ol-interaction-clusterselect @select="featureSelected" :pointRadius="20">
      <ol-style>
        <ol-style-stroke color="green" :width="5"></ol-style-stroke>
        <ol-style-fill color="rgba(255,255,255,0.5)"></ol-style-fill>
        <ol-style-icon :src="markerIcon" :scale="0.05"></ol-style-icon>
      </ol-style>
    </ol-interaction-clusterselect>

    <ol-animated-clusterlayer :animationDuration="500" :distance="40">
      <ol-source-vector
        :features="geoJsonFeatures"
        :format="geoJson"
        @featuresloadstart="featuresloadstart"
        @featuresloadend="featuresloadend"
        @featuresloaderror="featuresloaderror"
      />

      <ol-style :overrideStyleFunction="overrideStyleFunction">
        <ol-style-stroke color="red" :width="2"></ol-style-stroke>
        <ol-style-fill color="rgba(255,255,255,0.1)"></ol-style-fill>

        <ol-style-circle :radius="20">
          <ol-style-stroke
            color="black"
            :width="15"
            :lineDash="[]"
            lineCap="butt"
          ></ol-style-stroke>
          <ol-style-fill color="black"></ol-style-fill>
        </ol-style-circle>

        <ol-style-text>
          <ol-style-fill color="white"></ol-style-fill>
        </ol-style-text>
      </ol-style>
    </ol-animated-clusterlayer>
  </ol-map>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";
import markerIcon from "@/assets/marker.png";
import { arrayWith50000Points } from "./points";
import { GeoJSON } from "ol/format";
import type { FeatureLike } from "ol/Feature";
import type { SelectEvent } from "ol/interaction/Select";

const center = ref([40, 40]);
const projection = ref("EPSG:4326");
const zoom = ref(5);
const rotation = ref(0);
const count = ref(1000);

const geoJson = new GeoJSON();

const geoJsonFeatures = computed(() => {
  const features = Array.from({ length: count.value }, (_, i) => {
    return {
      type: "Feature",
      properties: {},
      geometry: {
        type: "Point",
        coordinates: arrayWith50000Points[i],
      },
    };
  });

  const providerFeatureCollection = {
    type: "FeatureCollection",
    features,
  };

  return geoJson.readFeatures(providerFeatureCollection);
});

const overrideStyleFunction = (feature: FeatureLike, style) => {
  const clusteredFeatures = feature.get("features");
  const size = clusteredFeatures.length;

  const color = size > 20 ? "192,0,0" : size > 8 ? "255,128,0" : "0,128,0";
  const radius = Math.max(8, Math.min(size, 20));
  const dash = (2 * Math.PI * radius) / 6;
  const calculatedDash = [0, dash, dash, dash, dash, dash, dash];

  style.getImage().getStroke().setLineDash(dash);
  style
    .getImage()
    .getStroke()
    .setColor("rgba(" + color + ",0.5)");
  style.getImage().getStroke().setLineDash(calculatedDash);
  style
    .getImage()
    .getFill()
    .setColor("rgba(" + color + ",1)");

  style.getImage().setRadius(radius);

  style.getText().setText(size.toString());
  return style;
};

const featureSelected = (event: SelectEvent) => {
  console.log(event);
};
function featuresloadstart() {
  console.log("features load start");
}
function featuresloaderror() {
  console.log("features load error");
}
function featuresloadend() {
  console.log("features load end");
}
</script>

<style scoped>
input {
  margin: 0.5rem;
  padding: 0.25rem 0.5rem;
  font-size: 1rem;
  border: 1px solid black;
  width: 100px;
}
</style>

urlFunction

Next example loads features from remote WFS service by viewport BBOX. With format and strategy you can define custom vector source format and loading strategy.

vue
<template>
  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="height: 400px"
  >
    <ol-view
      ref="view"
      :center="center"
      :zoom="zoom"
      :projection="projection"
    />

    <ol-tile-layer>
      <ol-source-osm />
    </ol-tile-layer>

    <ol-vector-layer>
      <ol-source-vector
        :url="urlFunction"
        :strategy="bbox"
        :format="GeoJSON"
        :projection="projection"
      >
      </ol-source-vector>
      <ol-style>
        <ol-style-stroke color="red" :width="5"></ol-style-stroke>
      </ol-style>
    </ol-vector-layer>
  </ol-map>
</template>

<script setup>
import { ref, inject } from "vue";

const center = ref([-8908887.277395891, 5381918.072437216]);
const projection = ref("EPSG:3857");
const zoom = ref(14);

const urlFunction = (extent, resolution, projection) => {
  const proj = projection.getCode();
  const url =
    "https://ahocevar.com/geoserver/wfs?service=WFS&" +
    "version=1.1.0&request=GetFeature&typename=osm:water_areas&" +
    "outputFormat=application/json&srsname=" +
    proj +
    "&" +
    "bbox=" +
    extent.join(",") +
    "," +
    proj;
  return url;
};

const strategy = inject("ol-loadingstrategy");
const bbox = strategy.bbox;
const format = inject("ol-format");
const GeoJSON = new format.GeoJSON();
</script>

TopoJSON

You can also use other Vector formats like TopoJSON.

vue
<template>
  <ol-map
    :loadTilesWhileAnimating="true"
    :loadTilesWhileInteracting="true"
    style="height: 400px"
  >
    <ol-view
      ref="view"
      :center="center"
      :zoom="zoom"
      :projection="projection"
    />

    <ol-tile-layer>
      <ol-source-osm />
    </ol-tile-layer>

    <ol-vector-layer>
      <ol-source-vector :url="url" :format="TopoJSON" :projection="projection">
      </ol-source-vector>
      <ol-style>
        <ol-style-stroke color="red" :width="2"></ol-style-stroke>
      </ol-style>
    </ol-vector-layer>
  </ol-map>
</template>

<script setup>
import { ref, inject } from "vue";

const center = ref([4.4764595, 50.5010789]);
const projection = ref("EPSG:4326");
const zoom = ref(7.5);

const url =
  "https://raw.githubusercontent.com/bmesuere/belgium-topojson/master/belgium.json";

const format = inject("ol-format");
const TopoJSON = new format.TopoJSON({
  // don't want to render the full world polygon (stored as 'land' layer),
  // which repeats all countries
  layers: ["arrondissements", "provinces"],
});
</script>

Performance hints

When rendering a large number of markers on a map using OpenLayers, there can be performance differences between requesting data from a URL as GeoJSON and passing the features directly through the source vector.

Data Transfer: When requesting data from a URL, the GeoJSON data is transferred over the network as needed, rather than loading the entire dataset upfront. This can be advantageous when dealing with large datasets, as it minimizes the initial data transfer and reduces the time required to load the markers onto the map.

Data Streaming: Requesting data from a URL allows for streaming the GeoJSON data in smaller chunks, which can significantly improve performance when rendering a large number of markers. The data can be fetched progressively and rendered incrementally, providing a smoother user experience and reducing the strain on system resources.

Memory Management: By requesting data from a URL, the GeoJSON data is loaded on-demand and can be efficiently managed by OpenLayers. When passing the features directly through the source vector, all the data is loaded into memory at once, which can cause performance issues and potentially lead to out-of-memory errors when dealing with extremely large datasets.

Data Filtering and Caching: Requesting data from a URL allows for server-side data filtering, which means you can request only the necessary markers based on certain criteria. Additionally, the server can cache the GeoJSON data, enabling faster subsequent requests and reducing the load on the server and the network.

Dynamic Data Updates: If your data is dynamic and frequently updated, requesting data from a URL provides a convenient way to fetch the latest information without having to reload the entire dataset. This can be particularly useful when working with real-time data or when updates occur frequently.

Properties

Props from OpenLayers

Properties are passed-trough from OpenLayers directly. Their types and default values can be checked-out in the official OpenLayers docs. Only some properties deviate caused by reserved keywords from Vue / HTML. This deviating props are described in the section below.

Deviating Properties

None.

Events

You have access to all Events from the underlying source. Check out the official OpenLayers docs to see the available events tht will be fired.

html
<ol-source-vector :url="url" @error="handleEvent" />

Methods

You have access to all Methods from the underlying source. Check out the official OpenLayers docs to see the available methods.

To access the source, you can use a ref() as shown below:

vue
<template>
  <!-- ... -->
  <ol-source-vector :url="url" ref="sourceRef" />
  <!-- ... -->
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import type VectorSource from "ol/source/vector";

const sourceRef = ref<{ source: VectorSource }>(null);

onMounted(() => {
  const source: VectorSource = sourceRef.value?.source;
  // call your method on `source`
});
</script>