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

- Nastartujeme lokalního hosta:
$ cd my-app $ npm start
Po napsání localhost adresy projektu http://localhost:5173 do webového prohlížeče se nám ukáže úvodní OpenLayers mapa.

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.

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>
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%;
}
<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.

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

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'
});
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.
