Display Vector Tiles on a Map using OpenLayers.

100% © Mapbox
index.js
import { MapControls } from "three/examples/jsm/controls/MapControls.js";

import { Fill, Icon, Stroke, Style, Text } from "ol/style.js";

import VectorTileSource from "@giro3d/giro3d/sources/VectorTileSource.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 Inspector from "@giro3d/giro3d/gui/Inspector.js";

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

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

const map = new Map({ extent, maxSubdivisionLevel: 15 });
instance.add(map);

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);

// Adds a color layer from a VectorTile source
const key =
  "pk.eyJ1IjoibWljaGFlbG51bm4iLCJhIjoiY2pudTk4czFuMDJ6NDNrcG56bG5xeWk4byJ9.R5dzdzAM5kaxa1gXjOQQrw";

const vectorTileSource = new VectorTileSource({
  url: `${
    "https://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/" +
    "{z}/{x}/{y}.vector.pbf?access_token="
  }${key}`,
  style: createMapboxStreetsV6Style(),
  backgroundColor: "hsl(47, 26%, 88%)",
});

function createMapboxStreetsV6Style() {
  const fill = new Fill({ color: "" });
  const stroke = new Stroke({ color: "", width: 1 });
  const polygon = new Style({ fill });
  const strokedPolygon = new Style({ fill, stroke });
  const line = new Style({ stroke });
  const text = new Style({
    text: new Text({
      text: "",
      fill,
      stroke,
    }),
  });
  const iconCache = {};
  function getIcon(iconName) {
    let icon = iconCache[iconName];
    if (!icon) {
      icon = new Style({
        image: new Icon({
          src: `https://cdn.rawgit.com/mapbox/maki/master/icons/${iconName}-15.svg`,
          size: [15, 15],
          crossOrigin: "anonymous",
        }),
      });
      iconCache[iconName] = icon;
    }
    return icon;
  }
  const styles = [];

  return (feature, resolution) => {
    let length = 0;
    const layer = feature.get("layer");
    const cls = feature.get("class");
    const type = feature.get("type");
    const scalerank = feature.get("scalerank");
    const labelrank = feature.get("labelrank");
    const adminLevel = feature.get("admin_level");
    const maritime = feature.get("maritime");
    const disputed = feature.get("disputed");
    const maki = feature.get("maki");
    const geom = feature.getGeometry().getType();
    if (layer === "landuse" && cls === "park") {
      fill.setColor("#d8e8c8");
      styles[length++] = polygon;
    } else if (layer === "landuse" && cls === "cemetery") {
      fill.setColor("#e0e4dd");
      styles[length++] = polygon;
    } else if (layer === "landuse" && cls === "hospital") {
      fill.setColor("#fde");
      styles[length++] = polygon;
    } else if (layer === "landuse" && cls === "school") {
      fill.setColor("#f0e8f8");
      styles[length++] = polygon;
    } else if (layer === "landuse" && cls === "wood") {
      fill.setColor("rgb(233,238,223)");
      styles[length++] = polygon;
    } else if (
      layer === "waterway" &&
      cls !== "river" &&
      cls !== "stream" &&
      cls !== "canal"
    ) {
      stroke.setColor("#a0c8f0");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "waterway" && cls === "river") {
      stroke.setColor("#a0c8f0");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "waterway" && (cls === "stream" || cls === "canal")) {
      stroke.setColor("#a0c8f0");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "water") {
      fill.setColor("#a0c8f0");
      styles[length++] = polygon;
    } else if (layer === "aeroway" && geom === "Polygon") {
      fill.setColor("rgb(242,239,235)");
      styles[length++] = polygon;
    } else if (
      layer === "aeroway" &&
      geom === "LineString" &&
      resolution <= 76.43702828517625
    ) {
      stroke.setColor("#f0ede9");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "building") {
      fill.setColor("#f2eae2");
      stroke.setColor("#dfdbd7");
      stroke.setWidth(1);
      styles[length++] = strokedPolygon;
    } else if (layer === "tunnel" && cls === "motorway_link") {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "tunnel" && cls === "service") {
      stroke.setColor("#cfcdca");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "tunnel" &&
      (cls === "street" || cls === "street_limited")
    ) {
      stroke.setColor("#cfcdca");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "tunnel" &&
      cls === "main" &&
      resolution <= 1222.99245256282
    ) {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "tunnel" && cls === "motorway") {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "tunnel" && cls === "path") {
      stroke.setColor("#cba");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "tunnel" && cls === "major_rail") {
      stroke.setColor("#bbb");
      stroke.setWidth(2);
      styles[length++] = line;
    } else if (layer === "road" && cls === "motorway_link") {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "road" &&
      (cls === "street" || cls === "street_limited") &&
      geom === "LineString"
    ) {
      stroke.setColor("#cfcdca");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "road" &&
      cls === "main" &&
      resolution <= 1222.99245256282
    ) {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "road" &&
      cls === "motorway" &&
      resolution <= 4891.96981025128
    ) {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "road" && cls === "path") {
      stroke.setColor("#cba");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "road" && cls === "major_rail") {
      stroke.setColor("#bbb");
      stroke.setWidth(2);
      styles[length++] = line;
    } else if (layer === "bridge" && cls === "motorway_link") {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "bridge" && cls === "motorway") {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "bridge" && cls === "service") {
      stroke.setColor("#cfcdca");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "bridge" &&
      (cls === "street" || cls === "street_limited")
    ) {
      stroke.setColor("#cfcdca");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "bridge" &&
      cls === "main" &&
      resolution <= 1222.99245256282
    ) {
      stroke.setColor("#e9ac77");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "bridge" && cls === "path") {
      stroke.setColor("#cba");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "bridge" && cls === "major_rail") {
      stroke.setColor("#bbb");
      stroke.setWidth(2);
      styles[length++] = line;
    } else if (layer === "admin" && adminLevel >= 3 && maritime === 0) {
      stroke.setColor("#9e9cab");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "admin" &&
      adminLevel === 2 &&
      disputed === 0 &&
      maritime === 0
    ) {
      stroke.setColor("#9e9cab");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (
      layer === "admin" &&
      adminLevel === 2 &&
      disputed === 1 &&
      maritime === 0
    ) {
      stroke.setColor("#9e9cab");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "admin" && adminLevel >= 3 && maritime === 1) {
      stroke.setColor("#a0c8f0");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "admin" && adminLevel === 2 && maritime === 1) {
      stroke.setColor("#a0c8f0");
      stroke.setWidth(1);
      styles[length++] = line;
    } else if (layer === "country_label" && scalerank === 1) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('bold 11px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#334");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(2);
      styles[length++] = text;
    } else if (
      layer === "country_label" &&
      scalerank === 2 &&
      resolution <= 19567.87924100512
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('bold 10px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#334");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(2);
      styles[length++] = text;
    } else if (
      layer === "country_label" &&
      scalerank === 3 &&
      resolution <= 9783.93962050256
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('bold 9px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#334");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(2);
      styles[length++] = text;
    } else if (
      layer === "country_label" &&
      scalerank === 4 &&
      resolution <= 4891.96981025128
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('bold 8px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#334");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(2);
      styles[length++] = text;
    } else if (
      layer === "marine_label" &&
      labelrank === 1 &&
      geom === "Point"
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('italic 11px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#74aee9");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "marine_label" &&
      labelrank === 2 &&
      geom === "Point"
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('italic 11px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#74aee9");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "marine_label" &&
      labelrank === 3 &&
      geom === "Point"
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('italic 10px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#74aee9");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "marine_label" &&
      labelrank === 4 &&
      geom === "Point"
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('italic 9px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#74aee9");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "place_label" &&
      type === "city" &&
      resolution <= 1222.99245256282
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('11px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#333");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "place_label" &&
      type === "town" &&
      resolution <= 305.748113140705
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('9px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#333");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "place_label" &&
      type === "village" &&
      resolution <= 38.21851414258813
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('8px "Open Sans", "Arial Unicode MS"');
      fill.setColor("#333");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "place_label" &&
      resolution <= 19.109257071294063 &&
      (type === "hamlet" || type === "suburb" || type === "neighbourhood")
    ) {
      text.getText().setText(feature.get("name_en"));
      text.getText().setFont('bold 9px "Arial Narrow"');
      fill.setColor("#633");
      stroke.setColor("rgba(255,255,255,0.8)");
      stroke.setWidth(1);
      styles[length++] = text;
    } else if (
      layer === "poi_label" &&
      resolution <= 19.109257071294063 &&
      scalerank === 1 &&
      maki !== "marker"
    ) {
      styles[length++] = getIcon(maki);
    } else if (
      layer === "poi_label" &&
      resolution <= 9.554628535647032 &&
      scalerank === 2 &&
      maki !== "marker"
    ) {
      styles[length++] = getIcon(maki);
    } else if (
      layer === "poi_label" &&
      resolution <= 4.777314267823516 &&
      scalerank === 3 &&
      maki !== "marker"
    ) {
      styles[length++] = getIcon(maki);
    } else if (
      layer === "poi_label" &&
      resolution <= 2.388657133911758 &&
      scalerank === 4 &&
      maki !== "marker"
    ) {
      styles[length++] = getIcon(maki);
    } else if (
      layer === "poi_label" &&
      resolution <= 1.194328566955879 &&
      scalerank >= 5 &&
      maki !== "marker"
    ) {
      styles[length++] = getIcon(maki);
    }
    styles.length = length;
    return styles;
  };
}

const vectorTileLayer = new ColorLayer({
  name: "osm",
  extent,
  source: vectorTileSource,
});

map.addLayer(vectorTileLayer);

Inspector.attach("inspector", instance);
index.html
<!doctype html>
<html lang="en">
  <head>
    <title>Vector Tiles With OpenLayers</title>
    <meta charset="UTF-8" />
    <meta name="name" content="vector_tile" />
    <meta
      name="description"
      content="Display Vector Tiles on a Map using OpenLayers."
    />
    <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": "vector_tile",
    "dependencies": {
        "@giro3d/giro3d": "0.39.0"
    },
    "devDependencies": {
        "vite": "^3.2.3"
    },
    "scripts": {
        "start": "vite",
        "build": "vite build"
    }
}