4. Zobrazení vektorových dlaždic

Následující řádky popisují vývoj aplikace na MS Windows, až na rozdílnou adresářovou strukturu je vývoj na operačním systému postaveném na Linux obdobný.

Vytvoření úvodního OpenLayers projektu

1) V cestě C:\ProgramData\GeoServer\www si založíme adresář, ve kterém budeme nastavovat OpenLayers prostředí a zároveň v něm budeme i vývíjet aplikaci. Adresář pojmenujeme planek_svatba. Na Unix operačním systému se jedná o cestu /usr/share/geoserver/www.

2) V příkazové řádce se přesuneme do GeoServer adresáře www a vytvoříme počáteční OpenLayers projekt:

$ cd C:\ProgramData\GeoServer\www
$ npm create ol-app planek_svatba
Úvodní OpenLayers projekt, který jsme vytvořili je v cestě C:\ProgramData\GeoServer\www\planek_svatba a vypadá následovně:

Datový adresář s úvodním projektem

  1. Nastartujeme lokalního hosta:
    $ cd my-app
    $ npm start
    
    Localhost OpenLayers

Po napsání localhost adresy projektu http://localhost:5173 do webového prohlížeče se nám ukáže úvodní OpenLayers mapa.

Úvodní OpenLayers mapová aplikace

Zobrazení podkladové vrstvy parcel a ladění

Aby aplikace běžící ve webovém prohlížeči mohli přistupovat k dlaždicím z GeoServeru, je nutné nejprve povolit CORS ve webovém prohlížeči.

1) Přidáme CORS rozšíření do našeho webového prohlížeče. V případě webového prohlížeče Google Chrome se řídíme postupem na adrese https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf. Po stažení CORS rozšíření klikneme na CORS ikonku a zašrtneme Toogle ON v levém dolním rohu.

Zapnutí CORS rozšíření

Pokud nám běží localhost (spustili jsme npm start), tak veškeré změny, které uděláme ve strukturě projektu automaticky po uložení uvidíme na adrese localhostu: http://localhost:5173/.

Podíváme se na strukturu projektu. Vizualizaci vektorových dlaždic a dalších vrstev budeme řešit v souboru main.js. Uspořádání jednotlivých komponent na webové stránce pak budeme definovat v souboru index.html, který v našem případě zaznamená pouze minimum úprav. Styly html komponent budeme definovat v souboru style.css.

2) V souboru index.html pod tagem title změníme název projektu.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/x-icon" href="https://openlayers.org/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Plánek svatba</title>
  </head>
  <body>
    <div id="map"></div>
    <script type="module" src="./main.js"></script>
  </body>
</html>
Můžeme si všimnout, že se po uložení této změny se po aktualizaci záložky http://localhost:5173/, změnil název záložky na Plánek svatba.

3) Upravíme styly pro mapu v souboru style.css:

@import "node_modules/ol/ol.css";

html, body {
  margin: 0;
  height: 100%;
}
#map {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
}
Vektorové dlaždice budeme získávat přes WMTS službu. Můžeme se podívat na obsah dotazu GetCapabilities. Jako URL adresu zadáme http://localhost:8080/geoserver/gwc/service/wmts?SERVICE=WMTS&request=GetCapabilities a najdeme si vrstvu parcely_km. Můžeme si všimnout, že k vrstvě je přiřazen gridset SJTSK-512 a že jeden z možných formátů vrstvy je application/vnd.mapbox-vector-tile.

<Layer>
    <ows:Title>parcely_km</ows:Title>
    <ows:Identifier>planek_svatba:parcely_km</ows:Identifier>
    <Style isDefault="true">
        <ows:Identifier>polygon</ows:Identifier>
        <LegendURL format="image/png" xlink:href="http://localhost:8080/…_km" width="20" height="20"/>
    </Style>
    <Format>application/vnd.mapbox-vector-tile</Format>
    <Format>application/json;type=geojson</Format>
    <Format>application/json;type=topojson</Format>
    <InfoFormat>text/plain</InfoFormat>
    <InfoFormat>application/vnd.ogc.gml</InfoFormat>
    <InfoFormat>text/xml</InfoFormat>
    <InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
    <InfoFormat>text/xml</InfoFormat>
    <InfoFormat>text/html</InfoFormat>
    <InfoFormat>application/json</InfoFormat>
    <TileMatrixSetLink>
        <TileMatrixSet>SJTSK-512</TileMatrixSet>
        <TileMatrixSetLimits>
            <TileMatrixLimits>
                <TileMatrix>SJTSK-512:4</TileMatrix>
                <MinTileRow>45</MinTileRow>
                <MaxTileRow>46</MaxTileRow>
                <MinTileCol>73</MinTileCol>
                <MaxTileCol>75</MaxTileCol>
            </TileMatrixLimits>
            <TileMatrixLimits>
                <TileMatrix>SJTSK-512:5</TileMatrix>
                <MinTileRow>89</MinTileRow>
                <MaxTileRow>91</MaxTileRow>
                <MinTileCol>147</MinTileCol>
                <MaxTileCol>150</MaxTileCol>
            </TileMatrixLimits>
            <TileMatrixLimits>
                <TileMatrix>SJTSK-512:6</TileMatrix>
                <MinTileRow>177</MinTileRow>
                <MaxTileRow>182</MaxTileRow>
                <MinTileCol>295</MinTileCol>
                <MaxTileCol>301</MaxTileCol>
            </TileMatrixLimits>
            <TileMatrixLimits>
                <TileMatrix>SJTSK-512:7</TileMatrix>
                <MinTileRow>445</MinTileRow>
                <MaxTileRow>457</MaxTileRow>
                <MinTileCol>739</MinTileCol>
                <MaxTileCol>753</MaxTileCol>
            </TileMatrixLimits>
        </TileMatrixSetLimits>
    </TileMatrixSetLink>
    <ResourceURL format="application/vnd.mapbox-vector-tile" resourceType="tile" template="http://localhost:8080/…e"/>
    <ResourceURL format="application/json;type=geojson" resourceType="tile" template="http://localhost:8080/…n"/>
    <ResourceURL format="application/json;type=topojson" resourceType="tile" template="http://localhost:8080/…n"/>
    <ResourceURL format="text/plain" resourceType="FeatureInfo" template="http://localhost:8080/…n"/>
    <ResourceURL format="application/vnd.ogc.gml" resourceType="FeatureInfo" template="http://localhost:8080/…l"/>
    <ResourceURL format="text/xml" resourceType="FeatureInfo" template="http://localhost:8080/…l"/>
    <ResourceURL format="application/vnd.ogc.gml/3.1.1" resourceType="FeatureInfo" template="http://localhost:8080/…1"/>
    <ResourceURL format="text/xml" resourceType="FeatureInfo" template="http://localhost:8080/…l"/>
    <ResourceURL format="text/html" resourceType="FeatureInfo" template="http://localhost:8080/…l"/>
    <ResourceURL format="application/json" resourceType="FeatureInfo" template="http://localhost:8080/…n"/>
</Layer>

Následující změny už budeme provádět v souboru main.js. Ukážeme si minimální fungující obsah main.js zobrazující vektorové dlaždice v souřadnicovém systému S-JTSK (EPSG:5514) a gridsetu uloženém v GeoServeru pod názvem SJTSK-512.

Jak můžeme vidět, přidání vektorových dlaždic do mapy se skládá z několika kroků. Nejprve je třeba vytvořit projekci EPSG:5514. Jelikož WMTS služba bere definici EPSG:5514 z jiného zdroje než knihovna OpenLayers, je třeba obě definice nastavit, že jsou ekvivaletní. Pokud bychom tento krok neudělali, budeme při vytváření nového WMTS objektu dostávat chybu axis orientation undefined.

Poté je nutné nadefinovat HTTP požadavek, na jehož základě se dále postaví WMTS objekt. Gridset a URL funkce WMTS objektu jsou vstupem pro VectorTileSource objekt, na jehož základě můžeme už definovat vrstvu vektorových dlaždic VectorTileLayer. Kromě přípravy vrstvy (vrstev) je vždy v OpenLayers nutné připravit 2D pohled View. Ten z velké části definuje, jak se má mapa chovat. V dalším kroku proto v rámci View nastavíme počáteční střed a počáteční měřítko, ve kterém se mají dlaždice renderovat. V našem případě zvolíme měřítko 1:20 000. Pro zobrazení dlaždic nicméně potřebujeme znát rozlišení (velikost strany pixelu v metrech). To spočítáme díky znalosti měřítka a dále díky znalosti skutečné velikosti pixelu na obrazovce, což je konstanta, která se obvykle udává 0.28 mm. Vrstva vektorových dlaždic a 2D pohled jsou pak argumenty při tvorbě posledního objektu a tím je objekt mapy Map.

import './style.css';

import MVT from 'ol/format/MVT';
import Map from 'ol/Map';
import VectorTileLayer from 'ol/layer/VectorTile';
import VectorTileSource from 'ol/source/VectorTile';
import View from 'ol/View';

import { optionsFromCapabilities } from 'ol/source/WMTS';
import WMTSCapabilities from 'ol/format/WMTSCapabilities.js';
import WMTS from 'ol/source/WMTS.js';

import Projection from 'ol/proj/Projection';
import {addEquivalentProjections} from 'ol/proj';

/////////////////////// Defining vector tile source and projection //////////////////////

// Define projection 
const sjtsk_projection = new Projection({
  code: 'EPSG:5514',
  units: 'm',
});

// CRS from opengis
const sjtsk_projection_2 = new Projection({
  code: 'http://www.opengis.net/def/crs/EPSG/0/5514',
  units: 'm',
});

// Prevent from axis orientation undefined error when creating WMTS object
addEquivalentProjections([sjtsk_projection, sjtsk_projection_2]);

// Define HTTP request
function data() {
  var xhr = new XMLHttpRequest();
  xhr.overrideMimeType("application json");
  xhr.open("GET", 'http://localhost:8080/geoserver/gwc/service/wmts?SERVICE=WMTS&request=GetCapabilities', false);
  xhr.send();
  console.log(xhr.responseText);
  return xhr.responseText;
}
 // Get WMTS capabilities
var caps = new WMTSCapabilities().read(data());

// Create WMTS object
var wmts = new WMTS(optionsFromCapabilities(caps, {
  layer: 'planek_svatba:parcely_km',
  matrixSet: 'SJTSK-512',
  format: 'application/vnd.mapbox-vector-tile',

}));

// Create vector tile layer source
var mvtVectorSource = new VectorTileSource({
  projection: sjtsk_projection,
  format: new MVT({idProperty : 'ID'}),
  tileUrlFunction: wmts.getTileUrlFunction(),
  tileGrid: wmts.getTileGrid(),
  overlaps: false,
});

// Create vector tiles
const vectorTiles = new VectorTileLayer({
  source: mvtVectorSource
});

///////////////////// Creating 2D view with S-JTSK projection /////////////////////

// Coordinates of view center point
const center = [-737520,-1039600]; 

// Count resolution from scale
function getResolutionFromScale(scale) {
  const real_pixel_size = 0.00028; // standard display pixel size in meters
  var mpu = 1; // meter per unit
  return parseFloat(scale * real_pixel_size) / (mpu) ;
}

// Initial map resolution (scale 1:20000)
const resolution = getResolutionFromScale(20000);

// Create S-JTSK map view
const view = new View({
  projection: sjtsk_projection,
  center: center,
  resolutions : wmts.getTileGrid().resolutions_,
  resolution : resolution,
  constrainResolution : false,
  smoothResolutionConstraint : true,
});

// Add vector tile layer into map
const map = new Map({
target: 'map',
view: view,
layers: [vectorTiles],
units: 'm'
});

4) Nahradíme obsah main.js souboru minimálním fungujícím obsahem. Po aktualizaci záložky ve webovém prohlížeči se nám zobrazí srdce z parcel ve formě vektorových dlaždic.

Vektorové dlaždice pro svatební web

Statické stylování vektorových dlaždic

Podkladovovu vrstvu vectorTiles dále nastylujeme. Nejdříve pouze staticky. Zaměříme se nicméně i na barevné zvýraznění parcel, na kterých stojí dvě důležitá svatební místa.

1) Nadefinujeme funkci vectorTilesStyle, která bude vracet hodnoty jednotlivých stylů v závislosti na typu prvku. Styly pro jednotlivé typy (obyčejná parcela, kostel, restaurace) definujeme v rámci vlastních proměnných. Definování stylů umístíme hned za importními deklaracemi.

// Style of normal polygons
var parcels = new Style({
  stroke: new Stroke({
    color: 'rgba(150,150,150,0.8)',
    width: 1,
  }),
  fill: new Fill({
    color: 'rgba(245,245,245,0.8)',
  }),
});

// Style of church (orange)
var churchPolygonStyle = new Style({
  stroke: new Stroke({
    color: 'gray',
    width: 1,
  }),
  fill: new Fill({
    color: 'rgba(30, 129, 176, 0.8)',
  }),
});

// Style of restaurant (blue)
var restaurantParcelaStyle = new Style({
  stroke: new Stroke({
    color: 'gray',
    width: 1,
  }),
  fill: new Fill({
    color: 'rgba(247, 163, 99, 0.8)',
  }),
});

// Vector tiles style function - style polygons accorging to ID_2 attribute
function vectorTilesStyle(feature) {
  if ((feature.get("ID_2") == 2238913101)) {
    return churchPolygonStyle;
  } else if ((feature.get("ID_2") == 2113941101)) {
    return restaurantPolygonStyle;
  }
  return parcels;
}

2) Jelikož jsme použili nové závislosti Fill, Stroke, Style, je třeba přidat na začátek souboru následující importní deklaraci:

import {Fill, Stroke, Style} from 'ol/style';
Pokud bychom tuto novou importní deklaraci nepřidali, webový prohlížeč nás upozorní, že jsme závislosti, které jsme použili, zapomněli v hlavičce skriptu main.js naimportovat. V našem případě by se objevila chyba Uncaught ReferenceError: Style is not defined. Importování nově zavedených závislostí je tedy třeba udělat vždy po přidání nových závislostí do main.js.

3) Funkci vectorTilesStyle dáme jako argument do konstruktoru vrstvy vektorových dlaždic. Kresba parcel má šedou barvu, pozadí je jenom lehce našedlé. Místa obřadu a hostiny byla obarvena výraznější barvou.

const vectorTiles = new VectorTileLayer({
  declutter: true,
  source: mvtVectorSource,
  style: vectorTilesStyle
});

Staticky nastylované vektorové dlaždice

Grafické měřítko

Můžeme si zkontrolovat, zda mapa bere v potaz měřítka z definovaného gridsetu SJTSK-512. Měřítka pravděpodobně budou lehce odlišná, což může být způsobeno tím, že OpenLayers počítá s rozlišeními, ne s měřítky. Ve chvíli kdy z rozlišení dopočítává měřítka, může se měřítkové číslo od měřítkové čísla v gridsetu nepatrně lišit.

Nadefinujeme si funkci scaleControl, která vrací objekt ScaleLine. Tento objekt hned vzápětí přidáme jako nový kontroler do mapy.

function scaleControl() {
  return new ScaleLine({
      units: 'metric',
      bar: true,
      steps: 5,
      text: true,
      minWidth: 140,
  });
}

// Add vector tile layer into map
const map = new Map({
controls: defaultControls().extend([scaleControl()]),
target: 'map',
view: view,
layers: [vectorTiles],
units: 'm'
});
Dále nezapomeneme na přidání nově vzniklé závislosti.

import {ScaleLine, defaults as defaultControls} from 'ol/control';

V levém dolním rohu mapové aplikace se nám zobrazí grafické měřítko i s číselným vyjádřením. Od číselného vyjádření se nicméně u mapových aplikací založených na vektorových dlaždicích spíše upouští, jelikož bude obvykle neceločíselné vzhledem k plynulosti v zoomování. Modernější cestou je uvést pouze grafické měřítko.

Grafické měřítko