16 Uvod u izradu klijentske web kartografske aplikacije

Najčešće se klijentske Web kartografske aplikacije izrađuju koristeći HTML, JavaScript, CSS uz neku od specijalizovanih JavaScript kartografskih biblioteka kao što su OpenLayers, Leafeat, Google Maps API ili druge. Ono što je zajedničko za sve biblioteke je da se one najčešće distribuiraju u formi API (Application Programming Interface) pristupa. API je okruženje koje se koristi za pisanje aplikacija. API obezbeđuje set klasa i metoda (funkcionalnosti) koje olakšavaju pisanje aplikacije, tako da su standardne alatke kao što je zumiranje, panovanje, dodavanje lejera na karti, dodavanje razmernika i sl. već unapred dostupne koristeći napisane klase i metode koje API obezbeđuje.

Pre nego što čitalac pređe na OpenLayers API, preporuka je da se upozna sa najosnovnijim principima HTML, CSS i JavaScript jezika. Mnogo je dostupnih resursa za učenje navedenih jezika, a ovde su preporučeni praktikumi sa portala https://www.w3schools.com/ :

  • HTML Tutorial

  • CSS Tutorial

  • JavaScript Tutorial

16.1 Uvod u OpenLayers API

OpenLayers API (u daljem tekstu OpenLayers) je biblioteka otvorenog koda, pisana u programskom jeziku JavaScript i jedna je od najsveobuhvatnijih biblioteka za razvoj interaktivnih Web karata, tj. Web kartografskih aplikacija. Karakterišu je velike mogućnosti u pogledu vizualizacije i manipulacije geoprostornim podacima, kao i velika podrška različitih formata i izvora geoprostornih podataka. Posebno je važno naglasiti da su u biblioteci integrisane procedure, koje omogućavaju olakšano korišćenje OGC Web servisa poput WMS-a, WFS-a i WMTS-a, što pokazuje da OpenLayers ima jaku orijentaciju ka razvoju Web GIS aplikacija.

Mada se programski jezik JavaScript, ne pridržava u potpunosti principa objektno-orijentisanog programiranja, OpenLayers je u svojoj implementaciji većinu nedostataka otklonio i definisao jasnu klasnu strukturu, što Web programerima omogućava pisanje kvalitetnog i čitljivog koda.

Za svaku biblioteku potrebno je uložiti vreme i trud, kako bi se na pravi način shvatila filosofija funkcionisanja biblioteke, a OpenLayers raspolaže veoma bogatom dokumentacijom i brojnim primerima korišćenja, koji u velikoj meri, pomažu “početnicima” da na brz i efikasan način izrade svoju prvu Web kartu.

OpenLayers projekat je započet od strane nekoliko entuzijastičnih programera, a danas je projekat u velikoj meri podržan od strane open-source zajednice i na zvaničnom repozitorijumu se može primetiti da više od 100 programera, aktivno doprinosi poboljšanjima i unapređenjima biblioteke. O aktivnim doprinosima, najbolje govori činjenica da uz biblioteku postoji i veliki broj takozvanih 3rd party dodataka, koji OpenLayers obogaćuju različitim novim funkcionalnostima poput:

  • Integracija Cesium globusa - OL-Cesium dodatak,
  • OpenLayers komponenta za React biblioteku - React OpenLayers dodatak,
  • Komponenta za kontrolu prikaza slojeva - OL-LayerSwitcher dodatak,
  • i drugi, https://openlayers.org/3rd-party/ .

Nedavno je objavljena nova verzija OpenLayers biblioteke (v5), što pokazuje da je potreba za razvojem Web kartografskih aplikacija u konstantnom porastu.

16.2 Prostorni podaci u OpenLayers biblioteci

Kao što je prethodno rečeno, OpenLayers podržava različite izvore prostornih podataka, kroz definisane klase od kojih se najčešće koriste :

  • Rasterski izvori
    • ImageStatic - koristi se za prikaz statičke georeferencirane slike.
    • Raster - koristi se za pristup i manipulaciju pikselskim vrednostima ulaznih rastera i vizualizaciju izlaznog rastera.
    • TileImage - koristi se za tajlovanu vizualizaciju ulazne slike.
    • TileWMS - koristi se za vizualizaciju rasterskih podataka, uspostavljanjem veze sa serverom geoprostornih podataka, poput GeoServer-a i definisanjem odgovarajućih WMS parametara.
    • Predefinisani WMS izvori - OSM, CartoDB, OpenStreetMap i Stamen, koji se najčešće koriste kao podloge, osnovne karte.
  • Vektorski izvori - s obzirom da JavaScript može da “razume” isključivo podatke u text/json formatu, OpenLayers je prevazišao i taj nedostatak i omogućio učitavanje vektorskih podataka različitih formata, korišćenjem njihove tekstualne reprezentacije, kao što su EsriJSON, GeoJSON, GML, GPX, KML, MVT, OSMXML, TopoJSON, WKT i drugi.

  • Vector - koristi se za vizualizaciju vektorskih podataka na nekoliko načina:

    • Definisanjem putanje ka fajlu, koji se nalazi u lokalnom fajl sistemu.

    • Definisanjem WFS putanje sa odgovarajućim parametrima, ka Web kartografskom serveru.

16.3 Komponente OpenLayers biblioteke

Najveći izazov kod učenja biblioteka jeste razumevanje filosofije funkcionisanja, odnosno na koji način ispravno koristiti biblioteku. Struktura na slici (Slika 16.1), delimično objašnjava hijerarhiju najvažnijih komponenti OpenLayers biblioteke.

Hijerarhijska struktura najvažnijih komponenti OpenLayers biblioteke.

Slika 16.1: Hijerarhijska struktura najvažnijih komponenti OpenLayers biblioteke.

Kao što se može videti na slici, osnovna komponenta jeste Map i ona sadrži celokupnu strukturu jedne interaktivne Web karte. U njoj se mogu definisati:

  • Layers - gde se svakom dodatom sloju (eng. layer), mora definisati izvor podataka (eng. source)
  • Controls - predstavljaju kontrolnu dugmad na samoj karti, kojima se izvršavaju određene interakcije na karti poput uvećanja/umanjenja, pomeranja, promene rotacije i slično.
  • Interactions container - pored podrazumevanih interakcija, mogu se dodati interakcije za crtanje, izmenu i selekciju geometrije na karti - Draw, Modify, Select.
  • Overlays - predstavljaju bilo kakvu vrstu elemenata, koji se mogu postaviti kao pokrivajući sloj, lejer. Najčešće se koriste markeri, na Web-u su najzastupljenije jednostavne karte koje pokazuju tačke od interesa na nekoj od dostupnih pozadina (osnovnih karti, npr OpenStreetMaps i dr.).
  • View - definiše početni prikaz karte uz pomoć parametara, kao što su uvećanje karte, pozicija centra, kartografska projekcija i slično.

16.4 Primer izrade jednostavne Web karte

OpenLayers može delovati dosta apstraknto u tehničkom smislu, kada se vidi koliko sadržajno izgleda rezultujuća Web karta. Međutim, bez obzira na složenost JavaScript koda, celokupna Web karta generiše se u okviru HTML elementa - canvas.

Canvas predstavlja HTML element, koji se koristi za grafička iscrtavanja u letu (egn. on the fly), korišćenjem JavaScript jezika. Svi rasterski i vektorski podaci, iscrtavaju se u okviru canvas elementa. Na primer, ako imamo WMS-T podlogu, poput OpenStreetMap-a, u canvas elementu će se izvršiti renderovanje niza susednih slika u formi img HTML elemenata, čije su pozicije definisane pikselskim koordinatama i gde svaki img element predstavlja jedan tajl. S druge strane, ako se radi o vektorskim podacima, renderovanje geometrije izvršava se korišćenjem JavaScript Canvas DOM metoda za iscrtavanje tačaka i linija.

Kao što je već napomenuto, OpenLayers ima jasno definisanu klasnu strukturu i u većini slučajeva, kod instanciranja objekata svake od klasa, prosleđuje se jedan parametar - options, koji predstavlja JavaScript objekat u formi key-value parova.

var options={
key1: value1,
key2: value2,
key3: value3
}

U daljem tekstu, biće objašnjeno u koracima, kako se formira jednostavna Web karta, vodeći se koncpetualnom strukturom i načinom funkcionisanja OpenLayers biblioteke. Celokupna aplikacija koja sadrži OpenStreetMap kao osnovnu kartu i centrirana je na područje Beograda zadata je sledećim kodom koji sadrži HTML, CSS i JavaScript.

<!doctype html>
<html lang="en">
  <head>
    <link rel="stylesheet" 
href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/css/ol.css" 
type="text/css">
    <style>
      .map {
        height: 400px;
        width: 100%;
      }
    </style>
    <script 
src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/build/ol.js">
  </script>
    <title>OpenLayers primer</title>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 16
        })
      });
    </script>
  </body>
</html>

Pre nego što se počne sa razvojem Web karte, potrebno je učitati biblioteku u glavni HTML dokument. Učitavanje podrazumeva smeštanje script elementa sa putanjom ka izvornom JavaScript fajlu biblioteke, kao i smeštanje link elementa sa putanjom ka izvornom CSS fajlu biblioteke.

Zatim je potrebno definisati div element, u okviru kojeg će se iscrtavati (renderovati) karta i canvas element sa rezultujućom Web kartom. Div element mora imati definisan id za koji će se vezati glavni objekat OpenLayers biblioteke. Pored toga potrebno je izvršiti osnovnu stilizaciju div elementa korišćenjem CSS jezika. Sve to je sadržano u zaglavlju HTML dokumenta.

  <head>
    <link rel="stylesheet" 
href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/css/ol.css" 
type="text/css">
    <style>
      .map {
        height: 400px;
        width: 100%;
      }
    </style>
    <script 
src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/build/ol.js">
  </script>
    <title>OpenLayers primer</title>
  </head>

Sama karta je kreirana u okviru JavaScript jezika. Pre nego što se izvrši instanciranje objekta glavne klase Map, poželjno je pre toga pripremiti neophodne parametre i podatke:

  • Potrebno je instancirati objekat klase Map i proslediti mu definisane objekte i parametre:
    • target - neophodan parametar koji predstavlja id elementa u kojem će se renderovati Web karta. U ovom primeru, id je "map".
    • layers - predstavljaju niz slojeva. Za svaki sloj, definiše se obavezni source podataka. Na ovom primeru biće prikazano kako se koristi predefinisani izvor podataka - OSM.
    • view - predstavlja instancu objekta klase View, u okviru kojoj se definišu pozicija centra prikaza, početno uvećanje i kartografska projekcija
  <script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 16
        })
      });
    </script>

Kao rezultat, dobijamo jednostavnu Web kartu (Slika 16.2), sa podrazumevanim kontrolama i interakcijama:

Primer jednostavne Web karte kreirane u OpenLayers-u.

Slika 16.2: Primer jednostavne Web karte kreirane u OpenLayers-u.

16.5 Lejeri

U narednom poglavlju će biti opisano kako kreirati Web kartu koja sadrži dodatne lejere, ne samo kao u predhonom primeru osnovnu kartu. Lejeri su najčešće rasterske slike prostornih podataka u formi WMS sloja ili mogu biti i sirovi vektorski podaci u GeoJSON formatu i sl. Ono što je specifično kod OpenLayers je da se jasno razdvaja lejer i deo koji se odnosi na izvor podataka (source).

Naznačajniji lejeri koji se koriste u OpenLayers biblioteci su:

  • WMS lejer,
  • Tajlovani lejer,
  • Vektorski lejer.

16.6 WMS lejer

Najčešće u klasičnim Web kartografskim aplikacijama podaci su servirani koristeći neka od rešenja koja pružaju OGC web servise kao što je WMS. U narednom primeru je dat deo koda koji se odnosi na kreiranje karte sa WMS lejerom. Kao primer uzet je DEM za Srbiju dostupan kroz OGC servise na kartografskom serveru OSGL Beograd, opis podataka dat je na sledećem linku.

Svaki tajlovani lejer se zadaje sa ol.layer.Tile konstruktorom, u ovom slučaju (naredni blok koda) je zadat sa WMS izvorom koristeći konstruktor ol.source.TileWMS, pored izvora ol.layer.Tile objekat sadrži i naslov tajlovanog lejera, koji se obično zadaje nekim asocijativnim imenom, u ovom slučaju “DEM”. Ovom objektu je moguće definisati i još neke osobine kao što su: opacity, visibility, zIndex, extent …. Objekat ol.source.TileWMS ima osobine (parametre), url i params, koje su definisane u navedenom primeru. Osim ovih osobina, url do servisa i parametra za WMS zahtev, mogle bi se dodatno definisati i osobine kao što su: attributions, cacheSize, projection, tileGrid,… Ako bi se želeo dobiti WMS koji nije tajlovan, koristio bi se ol.source.ImageWMS konstruktor.

Rezultat je prikazan na sledećoj Slici 16.3.

<!doctype html>
<html lang="en">
  <head>
    <link rel="stylesheet" 
href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/css/ol.css" 
type="text/css">
    <style>
      .map {
        height:100%;
        width: 100%;
      }
    </style>
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/build/ol.js">
    </script>
    <title>OpenLayers primer</title>
  </head>
  <body>
    <div id="map" class="map"></div>
   <script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            title: 'DEM',
            source: new ol.source.TileWMS({
              url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wms',
              params: {LAYERS: 'osgl_2:dem25', VERSION: '1.1.1'}
            })
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 8
        })
      });
    </script>

  </body>
</html>
Prikaz WMS lejera.

Slika 16.3: Prikaz WMS lejera.

Naredni primer kreira sličan rezultat kao predhodni osim što uključuje OSM kartu, kao osnovnu kartu, a WMS lejeru je definisana providnost na 50%, Slika 16.4.

<script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          }),

          new ol.layer.Tile({
            title: 'DEM',
            opacity:0.5,
            source: new ol.source.TileWMS({
              url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wms',
              params: {LAYERS: 'osgl_2:dem25', VERSION: '1.1.1'}
            })
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 8
        })
      });
    </script>
Prikaz WMS lejera sa OSM osnovnom kartom.

Slika 16.4: Prikaz WMS lejera sa OSM osnovnom kartom.

16.7 Tajlovani lejer

Kao što je već opisano u poglavlju 4, tajlovani lejeri su vrlo česti kod Web kartografskih aplikacija. Najčešće klijent šalje zahteve za delove slike koji su 256 x 256 (piksela), i to tako da se u zavisnosti od interakcije korisnika sa kartom tajlovi dinamički iscrtavaju. Obično se na serverskoj strani dinamički generišu tajlovi, ali i keširaju za područja koja se često “posećuju” na karti. Tako se dobija brza interakcija sa korisnikom, tj. efikasno iscrtavanje lejera. Praktično u prethodnom primeru je već korišćen tajlovani lejer jer OSM isporučuje podatke koristeći ovaj pristup.

Objekat iz prethodnog primera napravljen ol.source.OSM konstruktorom obezbeđuje OSM podatke koji su prethodno renderovani (iscrtani), keširani tajlovi. Takvi tajlovi su organizovani u standardan grid sa XYZ notacijom za svaki tajl.

Konstrutor ol.source.XYZ može biti korišćen za bilo koji tajlovani lejer. Takav lejer je unapred pripremljen i u zavisnosti od veličine uvećanja na klijentskoj stani isporučuju se delovi kartografskog prikaza (tajlovi). Notacija tajlova je data u obliku XYZ, gde su X i Y kolone i vrste pretajlovanog prikaza, a Z prestavlja nivo detalja (nivo uvećanja, eng. zoom level). Slika 16.5 prikazuje model iscrtavanja tajlova u zavisnosti od nivoa uvećanja.

Izgled predefinisanih tajlova u zavisnosti od nivoa uvećanja. (Izvor: https://www.e-education.psu.edu/geog585/node/519)

Slika 16.5: Izgled predefinisanih tajlova u zavisnosti od nivoa uvećanja. (Izvor: https://www.e-education.psu.edu/geog585/node/519)

Primer tajlovanog lejera dat je sledećim kodom. Generalno kod novih verzija OpenLayers biblioteke može se koristiti kod kojim se importuju klase i objekti u radno okruženje (samo moduli neophodni za aplikaciju), tako da nije neophodno učitavanje cele biblioteke. Naredni kod podrazumeva da je biblioteka preuzeta na disku i instalirana. Upustvo za instalaciju je dostupno na https://openlayers.org/en/latest/doc/tutorials/bundle.html. Alternativno može se koristiti codesandbox, sledeći primer je dat na linku https://codesandbox.io/s/9y2qwq66lp.

<script>
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import XYZ from 'ol/source/XYZ.js';

      // The tile size supported by the ArcGIS tile service.
      var tileSize = 512;

      var urlTemplate = 'https://services.arcgisonline.com/arcgis/rest/services/' +
          'ESRI_Imagery_World_2D/MapServer/tile/{z}/{y}/{x}';

      var map = new Map({
        target: 'map',
        layers: [
          new TileLayer({
            source: new XYZ({
              attributions: 'Copyright:© 2013 ESRI, i-cubed, GeoEye',
              maxZoom: 16,
              projection: 'EPSG:4326',
              tileSize: tileSize,
              tileUrlFunction: function(tileCoord) {
                return urlTemplate.replace('{z}', (tileCoord[0] - 1).toString())
                  .replace('{x}', tileCoord[1].toString())
                  .replace('{y}', (-tileCoord[2] - 1).toString());
              },
              wrapX: true
            })
          })
        ],
        view: new View({
          center: [0, 0],
          projection: 'EPSG:4326',
          zoom: 2,
          minZoom: 2
        })
      });
 </script>

Kod daje kartu sa Slike 16.6.

Primer tajlovanog lejera ESRI Imagery World 2D.

Slika 16.6: Primer tajlovanog lejera ESRI Imagery World 2D.

Na prethodnom kodu se automatski generišu tajlovi u zavisnosti od nivoa uvećanja. Primer koji dostupan na https://openlayers.org/en/latest/examples/canvas-tiles.html ilustruje iscrtavanje tajlova u zavisnosti od uvećanja, Slika 16.7. Kao i u prethodnom primeru ako je zadat warp parametar, onda se karta kontinualno proširuje u pravcu istok zapad.

Primer generisanja tajlova za nivo uvećanja 0,1,3,7.

Slika 16.7: Primer generisanja tajlova za nivo uvećanja 0,1,3,7.

16.8 Vektorski lejer

Vektorski lejer se definiše sa konstruktorom ol.layer.Vector, tada se vektorski podaci iscrtavaju na strani klijenta. Sledeći primer ilustruje iscrtavanje vektorskog lejera u GeoJSON formatu.

U narednom primeru je korišćena osobina zIndex kojom je zadat redosled iscrtavanja lejera na karti. Data je OSM karta kao podloga, potom WMS lejer i na kraju raspored klimatoloških stanica. Stanice su uvezene kao tačkasti lejer u GeoJSON formatu, koristeći ol.layer.Vector konstruktor. U ovom slučaju su one uzete direktno iz WFS linka, a generalno mogu biti isporučene i kao fajl na lokalnom disku ili se mogu zadati direktno kao JavaScript objekat. Stilizacija vektorskog lejera data je kroz osobinu ol.style.Style. Rezultat koda prikazan je na Slici 16.8.

<script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
        new ol.layer.Vector({
        title: 'TempTrends',
        zIndex:2,
        source: new ol.source.Vector({
          url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wfs?service=WFS&version=1.0.0'+ 
            '&request=GetFeature&typeName=osgl_2:tac_trends_annual' +  
            '&outputFormat=application/json&srsName=EPSG:3857',
          format: new ol.format.GeoJSON()
        }),
        style: new ol.style.Style({
          image: new ol.style.Circle({
            radius: 3,
            fill: new ol.style.Fill({color: 'black'})
          })
        })
      }),
          new ol.layer.Tile({
            title: 'DEM Srbija',
             zIndex: 1,
            opacity:0.5,
            source: new ol.source.TileWMS({
              url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wms',
              params: {LAYERS: 'osgl_2:dem25', VERSION: '1.1.1'}
            })
          }),
          new ol.layer.Tile({
            zIndex: 0,
            source: new ol.source.OSM()
          })

        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 8
        })
      });
    </script>
Vektorski lejer uz WMS i bazni lejer.

Slika 16.8: Vektorski lejer uz WMS i bazni lejer.

16.9 Elementi karte

Koristeći OpenLayers biblioteku mogu se kreirati elementi karte kao što je pozicija kursora na karti, kontrola zooma, razmernik, slajder i slično. Sledeći primer je dat na linku https://codesandbox.io/s/mjzmr0v0y. Na standardne kontrole dodate su još dve i to pozicija kursora na karti i razmernik. Slično bi se moglo proširiti i za ostale kontrole, Slika 16.9.

import Map from "ol/Map.js";
import View from "ol/View.js";
import { defaults as defaultControls, ScaleLine } from "ol/control.js";
import MousePosition from "ol/control/MousePosition.js";
import TileLayer from "ol/layer/Tile.js";
import OSM from "ol/source/OSM.js";
import { createStringXY } from "ol/coordinate.js";
var mousePositionControl = new MousePosition({
  coordinateFormat: createStringXY(4),
  projection: "EPSG:4326",
  undefinedHTML: "&nbsp;"
});
var scaleLineControl = new ScaleLine();
var map = new Map({
  target: "map",
  controls: defaultControls()
    .extend([mousePositionControl])
    .extend([scaleLineControl]),
  layers: [
    new TileLayer({
      source: new OSM()
    })
  ],
  view: new View({
    center: [21, 45],
    projection: "EPSG:4326",
    zoom: 10,
    minZoom: 2
  })
});
Karta sa pozicijom kursora, gornji desni ugao; i razmernikom donji levi ugao.

Slika 16.9: Karta sa pozicijom kursora, gornji desni ugao; i razmernikom donji levi ugao.