Display Vector data on a Map using vector sources.

100% © OpenLayers

The VectorSource accepts all formats that OpenLayers handle (such as GeoJSON, KML, GPX...). The VectorSource accepts either a URL to the remote data file, or the text content of the file, or an array of OpenLayers features. The style of the source is expressed using the OpenLayers Style object.

index.js
import { Color } from "three";
import GeoJSON from "ol/format/GeoJSON.js";
import { Fill, Stroke, Style } from "ol/style.js";

import { MapControls } from "three/examples/jsm/controls/MapControls.js";
import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer.js";

import ColorLayer from "@giro3d/giro3d/core/layer/ColorLayer.js";
import Extent from "@giro3d/giro3d/core/geographic/Extent.js";
import Instance from "@giro3d/giro3d/core/Instance.js";
import Map from "@giro3d/giro3d/entities/Map.js";
import VectorSource from "@giro3d/giro3d/sources/VectorSource.js";
import Inspector from "@giro3d/giro3d/gui/Inspector.js";
import Coordinates from "@giro3d/giro3d/core/geographic/Coordinates.js";

const extent = new Extent(
  "EPSG:3857",
  -20037508.342789244,
  20037508.342789244,
  -20037508.342789244,
  20037508.342789244,
);

let time = 0;

const instance = new Instance({
  target: "view",
  crs: extent.crs,
  backgroundColor: "white",
});

instance.view.camera.position.set(0, 0, 10000000);

const controls = new MapControls(instance.view.camera, instance.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
instance.view.setControls(controls);

const map = new Map({ extent, backgroundColor: "#135D66" });

instance.add(map);

const ecoRegionLayerStyle = (feature) => {
  const brightness = Math.sin((time / 1000) * 6) * 0.2;
  const featureColor = new Color(feature.get("COLOR") || "#eeeeee");
  const highlight = feature.get("highlight");

  const color = highlight
    ? new Color(featureColor).offsetHSL(0, 0, brightness)
    : featureColor;

  const stroke = highlight
    ? new Stroke({
        color: "white",
        width: 2,
      })
    : undefined;

  return new Style({
    zIndex: highlight ? 1 : 0,
    fill: new Fill({
      color: `#${color.getHexString()}`,
    }),
    stroke,
  });
};

const ecoRegionSource = new VectorSource({
  data: {
    url: "https://openlayers.org/data/vector/ecoregions.json",
    format: new GeoJSON(),
  },
  dataProjection: "EPSG:4326",
  style: ecoRegionLayerStyle,
});

const ecoRegionLayer = new ColorLayer({
  name: "ecoregions",
  extent,
  source: ecoRegionSource,
});

map.addLayer(ecoRegionLayer);

// Creates the country layer
const countryLayerStyle = new Style({
  stroke: new Stroke({
    color: "black",
    width: 1,
  }),
});

const countryLayer = new ColorLayer({
  name: "countries",
  extent,
  source: new VectorSource({
    data: {
      url: "https://openlayers.org/en/v5.3.0/examples/data/geojson/countries.geojson",
      format: new GeoJSON(),
    },
    dataProjection: "EPSG:4326",
    style: countryLayerStyle,
  }),
});

map.addLayer(countryLayer);

// Creates a custom vector layer
const geojson = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      geometry: {
        type: "Point",
        coordinates: [102.0, 0.5],
      },
      properties: {
        prop0: "value0",
      },
    },
    {
      type: "Feature",
      geometry: {
        type: "LineString",
        coordinates: [
          [102.0, 0.0],
          [103.0, 1.0],
          [104.0, 0.0],
          [105.0, 1.0],
        ],
      },
      properties: {
        prop0: "value0",
        prop1: 0.0,
      },
    },
    {
      type: "Feature",
      geometry: {
        type: "Polygon",
        coordinates: [
          [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0],
          ],
        ],
      },
      properties: {
        prop0: "value0",
        prop1: { this: "that" },
      },
    },
  ],
};

const customVectorLayerStyle = new Style({
  fill: new Fill({
    color: "cyan",
  }),
  stroke: new Stroke({
    color: "orange",
    width: 1,
  }),
});

const customVectorLayer = new ColorLayer({
  name: "geojson",
  extent,
  source: new VectorSource({
    data: {
      content: geojson,
      format: new GeoJSON(),
    },
    dataProjection: "EPSG:4326",
    style: customVectorLayerStyle,
  }),
});

map.addLayer(customVectorLayer);

const labelElement = document.createElement("span");
labelElement.classList.value = "badge rounded-pill text-bg-light";
labelElement.style.marginTop = "2rem";
const label = new CSS2DObject(labelElement);

label.visible = false;
instance.add(label);

let previousFeature;

function pickFeatures(mouseEvent) {
  const pickResult = instance.pickObjectsAt(mouseEvent);

  const picked = pickResult[0];

  function resetPickedFeatures() {
    if (previousFeature) {
      previousFeature.set("highlight", false);
      ecoRegionSource.updateFeature(previousFeature);
    }
    if (label.visible) {
      label.visible = false;
    }
    previousFeature = null;
  }

  if (picked) {
    const { x, y } = picked.point;
    const features = ecoRegionLayer.getVectorFeaturesAtCoordinate(
      new Coordinates(instance.referenceCrs, x, y),
    );

    if (features.length > 0) {
      const firstFeature = features[0];

      previousFeature?.set("highlight", false);
      firstFeature.set("highlight", true);

      if (previousFeature !== firstFeature) {
        ecoRegionSource.updateFeature(previousFeature, firstFeature);
        previousFeature = firstFeature;
      }

      label.position.set(x, y, 100);
      label.visible = true;
      label.element.innerText = firstFeature.get("ECO_NAME");
      label.updateMatrixWorld(true);
    } else {
      resetPickedFeatures();
    }
  } else {
    resetPickedFeatures();
  }
}

function update(t) {
  time = t;
  if (previousFeature != null) {
    ecoRegionSource.updateFeature(previousFeature);
  }
  requestAnimationFrame(update);
}

update(0);

instance.domElement.addEventListener("mousemove", pickFeatures);

Inspector.attach("inspector", instance);
index.html
<!doctype html>
<html lang="en">
  <head>
    <title>Vector sources</title>
    <meta charset="UTF-8" />
    <meta name="name" content="ol_vector" />
    <meta
      name="description"
      content="Display Vector data on a Map using vector sources."
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="icon" href="https://giro3d.org/images/favicon.svg" />
    <link
      href="https://giro3d.org/assets/bootstrap-custom.css"
      rel="stylesheet"
    />
    <script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
    <link
      rel="stylesheet"
      type="text/css"
      href="https://giro3d.org/latest/css/example.css"
    />
  </head>

  <body>
    <div id="view" class="m-0 p-0 w-100 h-100"></div>
    <div
      id="inspector"
      class="position-absolute top-0 start-0 mh-100 overflow-auto"
    ></div>

    <script type="module" src="index.js"></script>
    <script>
      /* activate popovers */
      const popoverTriggerList = [].slice.call(
        document.querySelectorAll('[data-bs-toggle="popover"]'),
      );
      popoverTriggerList.map(
        // bootstrap is used as script in the template, disable warning about undef
        // eslint-disable-next-line no-undef
        (popoverTriggerEl) =>
          new bootstrap.Popover(popoverTriggerEl, {
            trigger: "hover",
            placement: "left",
            content: document.getElementById(
              popoverTriggerEl.getAttribute("data-bs-content"),
            ).innerHTML,
            html: true,
          }),
      );
    </script>
  </body>
</html>
package.json
{
    "name": "ol_vector",
    "dependencies": {
        "@giro3d/giro3d": "0.39.0"
    },
    "devDependencies": {
        "vite": "^3.2.3"
    },
    "scripts": {
        "start": "vite",
        "build": "vite build"
    }
}