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.

Usage

ol-feature component (GeoJSON)

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

vue
<template>
  <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="fillColor"></ol-style-fill>
              <ol-style-stroke
                :color="strokeColor"
                :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 strokeColor = ref("red");
const fillColor = ref("white");
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>
  <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">
      <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 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="count" />
  <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
        ref="vectorsource"
        :features="features"
        @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>
import { computed, ref } from "vue";
import { Point } from "ol/geom";
import Feature from "ol/Feature";
import markerIcon from "@/assets/marker.png";
import { arrayWith500Points } from "./points";

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

const features = computed(() => {
  return Array.from({ length: count.value }, (_, i) => {
    return new Feature({
      geometry: new Point(arrayWith500Points[index - 1]),
      index: i,
    });
  });
});

const overrideStyleFunction = (feature, 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) => {
  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>