Cartografie electorală în linie de comandă - Partea 6: Crearea hărții interactive

6.1. Găsirea unui format eficient pentru publicarea datelor

Există mai multe modalități a publica datele noastre prin intermediul unei aplicații cartografice web. Una foarte la îndemînă este să le publicăm prin intermediul unui serviciu WMS pe care să-l accesăm apoi prin intermediul unui client web, ajutați de o bibliotecă JavaScript precum OpenLayers sau Leaflet. Necazul cu WMS este că stă prost la interactivitate în mediul web. E drept, are o metodă, intitulată getFeatureInfo, cu ajutorul căreia putem interoga tabela de atribute asociată geometriilor. Acesta este un mecanism prea lent pentru genul nostru de hartă, unde ne dorim o interactivitate foarte mare (rezultatele să fie afișate și actualizate instantaneu pe măsură ce mouse-ul se plimbă peste suprafața hărții). Un format foarte popular, ce permite încărcarea directă a datelor în aplicațiile web, este GeoJSON. Conversia în format GeoJSON o putem face cu comanda ogr2ogr din GDAL/OGR. Vom face un test folosind datele la nivel de secție pentru turul 2:

        
ogr2ogr -f "GeoJSON" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
test.geojson "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, localitate, uat, judet, g1, g2, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), castigator from tur2.sectii_voronoi_pv_tur2"
        
      

Avem însă o problemă cu dimensiunea. Fișierul rezultat are 13 MB, cam mult pentru a putea fi transferat eficient către utilizator și din perspectiva faptului că avem de integrat și alte date, mai complexe. Înainte însă de a abandona complet acest format, mai facem o încercare cu TopoJSON. Acesta este o extensie a GeoJSON care beneficiază de suport pentru topologie și stochează o singură dată muchiile comune (arce). Din păcate, GDAL/OGR nu are suport pentru scrierea de date în format TopoJSON (le poate doar citi). De aceea vom face acest lucru în doi pași:

Comenzile de test arată așa:
        
ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
test.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, localitate, uat, judet, id_sectii, g1, g2, cast(g1p as numeric(6,2)), 
cast(g2p as numeric(6,2)), castigator from tur2.sectii_voronoi_pv_tur2"

mapshaper -i test.shp -o format=topojson test.topojson
        
      

Aceleași date, în format TopoJSON, au ajuns la 5.5 MB. Suficient cît să se încarce într-un timp rezonabil în aplicația client.

6.2. Aducerea datelor în harta web

Am ales să utilizăm biblioteca Leaflet pentru a reprezenta interactiv datele noastre într-o aplicație cartografică interactivă web. Aceasta poate manipula date TopoJSON dacă este utilizată împreună cu biblioteca topojson-client. După cum ziceam, nu vom intra în detaliile realizării propriu-zise a hărții (programare JavaScript). Vom prezenta însă pașii importanți.

6.2.1. Crearea hărții de bază

Pentru început vom crea o hartă și vom aduce acolo un strat de bază:

        
var map = new L.Map('harta',
{
  center: new L.LatLng(46, 25),
  zoom: 7
});

var lightOSM = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
  maxZoom: 18,
  zIndex: 1,
  attribution: '© OpenStreetMap'
});

map.addLayer(lightOSM);
        
      
6.2.2. Încărcarea datelor

Apoi, vom încărca fișierul TopoJSON în hartă:

        
var req = new XMLHttpRequest();
var url = 'data/test.topojson'

req.open('GET', url, true);
req.onreadystatechange = handler;
req.send();
var topoob = {};
getsections = {};

function handler(){
  if(req.readyState === XMLHttpRequest.DONE){
    topoob = JSON.parse(req.responseText)
    neighbors = topojson.neighbors(topoob.objects.test.geometries);
    getsections = topojson.feature(topoob, topoob.objects.test)
    getsections.features = getsections.features.map(function(fm,i){
      var ret = fm;
        ret.indie = i;
        return ret
      });
    sectii1 = L.geoJson(getsections, {});
  }

  map.addLayer(sectii);
}
        
      
6.2.3. Simbolizarea datelor

Harta vă permite să faceți zoom și pan dar nu prezintă alte funcționalități. Putem observa însă că Leaflet se mișcă destul de repede în randarea on-the-fly a unor geometrii relativ complexe. Ca să înțelegem ce este cu aceste geometrii, haideți să le simbolizăm în funcție de candidatul ce a cîștigat în fiecare secție. Pentru aceasta construim o funcție care returnează culoarea roșie (#e31a1c) sau albastră (#1f78b4), în funcție de atributul castigator. Secțiile unde s-a înregistrat egalitate le vom reprezenta cu verde (#33a02c). Toate poligoanele vor avea opeacitatea culorii de umplere setată la 50%. Astfel, se pot observa detaliile din harta de bază.

        
function styleSectii(feature) {
  return {
    fillColor: getColor(feature.properties.castigator),
    weight: 0.9,
    opacity: 1,
    color: getColor(feature.properties.castigator),
    dashArray: '0.5',
    fillOpacity: 0.5
  };
}

function getColor(d) {
  return d == 1 ? '#1f78b4' :
  d == 2  ? '#e31a1c':
  '#33a02c';
}
        
      
6.2.4. Interacțiunea cu datele

Lucrurile încep să arate din ce în ce mai bine. Mai rămîne să adăugăm interactivitate și să afișăm atributele asociate cu aceste geometrii. Prin interactivitate înțelegem "răspuns" în hartă la următoarele evenimente la nivel de secție:

La crearea unui strat, Leaflet are o opțiune, numită onEachFeature, ce permite atașarea unei funcții (am numit-o tot onEachFeature) ce poate fi apelată atunci cînd interacționăm cu fiecare din geometriile încărcate. Aceasta este perfectă pentru a apela alte funcții, în funcție de tipurile de interacțiune descrise mai sus:

        
function onEachFeature(feature, layer){
  layer.on({
    mouseover: highlightFeature,
    mouseout: resetHighlight, click: zoomToFeature})
}
        
      

De aici, apelăm funcția highlightFeature atunci cînd cursorul se găsește pe suprafața secției:

        
function highlightFeature(e){
  var layer = e.target;
  layer.setStyle({
    weight: 2,
    color: '#ffffff',
    dashArray: '',
    fillOpacity: .7
  })
  if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
    layer.bringToFront();
  }
  info.update(layer.feature.properties);
}
        
      

Secția unde se înregistrează evenimentul mouseover își va crește gradul de opacitate (70%) și își va schimba grosimea (2 pixeli) și culoarea (alb) liniei de contur. O dată ce se înregistrează evenimentul mouseout, se apelează funcția resetHighlight, care readuce secția la modul implicit de simbolizare:

        
function resetHighlight(e){
  sectii.resetStyle(e.target);
  info.update();
}
        
      

În sfîrșit, în cazul în care se înregistrează evenimentul click (stînga) se apelează funcția zoomToFeature:

        
function zoomToFeature(e) {
    map.fitBounds(e.target.getBounds());
}
        
      

Ultima funcționalitate de care avem nevoie ține de afișarea rezultatelor înregistrate în cadrul secției precum și a informațiilor despre secție (indicativ, localitate, județ). În caz că nu s-a observat, ultima instrucțiune din funcția onEachFeature se prezenta ca: info.update(layer.feature.properties);. Prin această instrucțiune se apelează o nouă funcție ce injectează într-un container HTML de tip div informațiile relevante pentru secția peste care se găsește cursorul mouse-ului:

        
var info = L.control();
info.onAdd = function(map) {
  this._div = L.DomUtil.create('div', 'info');
  this.update();
  return this._div;
}

info.update = function(props){
  this._div.innerHTML = "
" + "

Secții de votare

" + (props ? 'Localitate: ' + titleCase(props.localitate) + '
' : "") + (props ? 'Județul: ' + titleCase(props.judet) + '
' : "") + (props ? 'Număr secție (secții): ' + props.id_sectii + '
' : "") + (props ? 'Voturi Klaus Iohannis: ' + props.g1 + ' (' + props.g1p +' %)
' : "") + (props ? 'Voturi Viorica Dăncilă: ' + props.g2 + ' (' + props.g2p +' %)
' : "") + "
" } info.addTo(map);

Observăm că viteza de reacție este foarte bună în afișarea interactivă combinată a geometriilor și a atributelor.

6.3. Exportul datelor

Am văzut că TopoJSON oferă o performanță bună în termeni de dimensiune a fișierelor și viteză de afișare. Pe lîngă aceasta, putem stoca mai multe straturi într-un sigur astfel de fișier. Vom crea două astfel de fișiere, cîte unul pentru fiecare tur, intitulate data_tur1.topojson, respectiv data_tur2.topojson. În acestea vom importa datele la nivel de secție (inclusiv locația punctuală a secțiilor), UAT și județ pentru fiecare tur:

        
ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
sectii_voronoi_pv_tur1.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, localitate, uat, judet, id_sectii, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), cast(g3p as numeric(6,2)), cast(g4p as numeric(6,2)), cast(g5p as numeric(6,2)), cast(g6p as numeric(6,2)), cast(g7p as numeric(6,2)), cast(g8p as numeric(6,2)), cast(g9p as numeric(6,2)), cast(g10p as numeric(6,2)), cast(g11p as numeric(6,2)), cast(g12p as numeric(6,2)), cast(g13p as numeric(6,2)), cast(g14p as numeric(6,2)), castigator from tur1.sectii_voronoi_pv_tur1"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
uat_pv_cumulat_tur1.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, name, county, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), cast(g3p as numeric(6,2)), cast(g4p as numeric(6,2)), cast(g5p as numeric(6,2)), cast(g6p as numeric(6,2)), cast(g7p as numeric(6,2)), cast(g8p as numeric(6,2)), cast(g9p as numeric(6,2)), cast(g10p as numeric(6,2)), cast(g11p as numeric(6,2)), cast(g12p as numeric(6,2)), cast(g13p as numeric(6,2)), cast(g14p as numeric(6,2)), castigator from tur1.uat_pv_cumulat_tur1"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
judete_pv_cumulat_tur1.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, county, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), cast(g3p as numeric(6,2)), cast(g4p as numeric(6,2)), cast(g5p as numeric(6,2)), cast(g6p as numeric(6,2)), cast(g7p as numeric(6,2)), cast(g8p as \
numeric(6,2)), cast(g9p as numeric(6,2)), cast(g10p as numeric(6,2)), cast(g11p as numeric(6,2)), cast(g12p as numeric(6,2)), cast(g13p as numeric(6,2)), cast(g14p as numeric(6,2)), castigator from tur1.judete_pv_cumulat_tur1"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
sectii_voronoi_pv_tur1_clip_pop.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" \
-sql "select geom, castigator from tur1.sectii_voronoi_pv_tur1_clip_pop" 

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
sectii_punct_tur1.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom from tur1.sectii_pv_tur1"

mapshaper -i judete_pv_cumulat_tur1.shp uat_pv_cumulat_tur1.shp sectii_voronoi_pv_tur1.shp \
sectii_punct_tur1.shp sectii_voronoi_pv_tur1_clip_pop.shp combine-files -o format=topojson data_tur1.topojson


ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
sectii_voronoi_pv_tur2.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, localitate, uat, judet, id_sectii, g1, g2, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), castigator from tur2.sectii_voronoi_pv_tur2"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
uat_pv_cumulat_tur2.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, name, county, g1, g2, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), castigator from tur2.uat_pv_cumulat_tur2"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
judete_pv_cumulat_tur2.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom, county, g1, g2, cast(g1p as numeric(6,2)), cast(g2p as numeric(6,2)), castigator from tur2.judete_pv_cumulat_tur2"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
sectii_voronoi_pv_tur2_clip_pop.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" \
-sql "select geom, castigator from tur2.sectii_voronoi_pv_tur2_clip_pop"

ogr2ogr -f "ESRI Shapefile" -lco resize=yes -lco ENCODING=UTF-8 -s_srs EPSG:3857 -t_srs EPSG:4326 \
sectii_punct_tur2.shp "PG:host=localhost dbname=alegeri user=user password=user port=5432" -sql "select geom from tur2.sectii_pv_tur2"

mapshaper -i judete_pv_cumulat_tur2.shp uat_pv_cumulat_tur2.shp sectii_voronoi_pv_tur2.shp \
sectii_punct_tur2.shp sectii_voronoi_pv_tur2_clip_pop.shp combine-files -o format=topojson data_tur2.topojson
        
      
6.4. Aducerea datelor mari în harta web

Este evident că varianta cu TopoJSON funcționează doar pentru seturile de date cu dimensiuni medii și mici. Nu am putea încărca eficient în hartă seturile de date la nivel de procent în cadrul secției sau voturile individuale. Dimensiunile respectivelor seturi sînt de ordinul sutelor de MB și n-ar fi practic să mergem pe această soluție. Pentru acestea avem nevoie de o componentă server-side iar cel mai simplu ar fi să publicăm datele prin intermediul unui serviciu de tip WMS/WMST. În felul acesta, se rezolvă problema transferului eficient a datelor în hartă dar soluția vine și cu un cost, pierzîndu-se interactivitatea. Alternativa, pentru păstrarea interactivității, ar fi publicarea datelor printr-un serviciu de transfer vectorial al datelor, cum este WFS sau utilizarea uneia din soluțiile de tip vector tiles. Evident, și aceste soluții au minusuri, legate în special de viteza de transfer. Pentru a le utiliza eficient, sînt necesare o serie de optimizări:

Pentru că volumul de date utilizat de noi este semnificativ, vom merge pe soluția vector tiles, datorită posibilității de a le pregenera sau face cache on-the-fly. Pentru vector tiles există mai multe specificații (probabil, cea mai cunoscută este Mapbox Vector Tile Specification) și mai multe aplicații (ex: TileStache, tilelive-vector, geojson-vt, GeoServer) care pot produce aceste date, atît dinamic cît și static. Dintre acestea am ales să lucrăm cu GeoServer, deoarece se găsește deja preinstalat pe mașina virtuală OSGeo Live.

6.3.1. Configurarea GeoServer

Publicarea datelor în format vector tiles necesită cîteva activități de configurare. Țineți cont că instanța locală a GeoServer nu pornește o dată cu sistemul de operare. Trebuie să facem manual acest lucru. Să facem însă mai întîi configurările despre care vorbeam. Vom începe cu suportul pentru vector tiles. Acesta nu există în distribuția de bază dar se poate adăuga prin intermediul unui plugin. Vom descărca acest plugin (cu grijă să selectăm versiunea compatibilă cu instanța GeoServer de pe mașina virtuală) și îl vom instala (cuvîntul instalare e pretențios, aceasta se face prin simpla dezarhivare și copiere a fișierelor ce alcătuiesc pluginul în directorul cu biblioteci a GeoServer:

        
wget \ http://sourceforge.net/projects/geoserver/files/GeoServer/2.15.1/extensions/geoserver-2.15.1-vectortiles-plugin.zip

sudo unzip -n geoserver-2.15.1-vectortiles-plugin.zip -d /usr/local/lib/geoserver-2.15.1/lib/
        
      

Următorul pas este activarea mecanismului Cross-Origin Resource Sharing (CORS) pentru a permite accesarea resurselor GeoServer din afara domeniului pe care acesta rulează implicit. Pe mașina virtuală, GeoServer a fost configurat să funcționeze pe portul 8082. Tot aici, serverul web rulează folosind portul 80. Din cauza politicii restrictive implicite de securitate, aplicațiile web publicate pe portul 80 nu vor putea accesa resursele GeoServer de pe 8082. Pentru a permite acest lucru este necesar să modificăm fișierul web.xml din rădăcina GeoServer. Deschidem acest fișier cu editorul nano:

        
sudo nano /usr/local/lib/geoserver-2.15.1/webapps/geoserver/WEB-INF/web.xml
        
      

Navigăm la final și adăugăm liniile de mai jos, fix înainte de linia unde se închide tag-ul </web-app>:

        
<filter>
  <filter-name>cross-origin</filter-name>
  <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>cross-origin</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
        
      

Salvăm modificările apăsînd combinația de taste ctrl+O și părăsim fișierul cu combinația ctrl+X. Cu acestea făcute, din consolă, vom porni instanța locală de GeoServer:

        
sudo /usr/local/lib/geoserver-2.15.1/bin/startup.sh
        
      

În funcție de resursele alocate mașinii virtuale, instanța de GeoServer ar trebui să pornească în 20-50 de secunde. Dacă totul merge cum trebuie, la final ar trebui să se afișeze un mesaj de genul INFO:oejs.Server:main: Started @38617ms.

6.3.2. Găsirea unui flux pentru publicarea datelor în GeoServer

GeoServer dispune de o interfață web prietenoasă și intuitivă ce permite cu ușurință publicarea datelor stocate în PostGIS. Totuși, pentru că ne găsim la un tutorial în linie de comandă, vom face aceste lucruri folosind API-ul REST al GeoServer. Prima dată vom crea un workspace dedicat scopului nostru, workspace intitulat alegeri:

        
curl -u admin:geoserver -v -XPOST -H "accept: application/xml" -H "content-type: application/xml" \
-d "<workspace><name>alegeri</name></workspace>" http://localhost:8082/geoserver/rest/workspaces
        
      

Dacă totul a funcționat bine ar trebui să primiți un mesaj asemănător cu cel de mai jos:

        
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8082 (#0)
* Server auth using Basic with user 'admin'
> POST /geoserver/rest/workspaces HTTP/1.1
> Host: localhost:8082
> Authorization: Basic YWRtaW46Z2Vvc2VydmVy
> User-Agent: curl/7.58.0
> accept: application/xml
> content-type: application/xml
> Content-Length: 46
>
* upload completely sent off: 46 out of 46 bytes
< HTTP/1.1 201 Created
< Set-Cookie: JSESSIONID=node01h703590m5rp9pesues9niqd85.node0;Path=/geoserver
< Expires: Thu, 01 Jan 1970 00:00:00 GMT
< Location: http://localhost:8082/geoserver/rest/workspaces/geospatial
< Content-Type: application/xml;charset=ISO-8859-1
< Content-Length: 10
< Server: Jetty(9.4.12.v20180830)
<
* Connection #0 to host localhost left intact
        
      

În continuare, vom crea un depozit de date nou (data store), corespunzător schemei tur1 din PostGIS:

        
curl -u admin:geoserver -v -XPOST -H "accept: application/xml" -H "content-type: application/xml" \
-d "
<dataStore>
  <name>tur1</name>
  <type>PostGIS</type>
  <description>Store PostGIS pentru turul 1</description>
  <connectionParameters>
    <entry key='host'>localhost</entry>
    <entry key='port'>5432</entry>
    <entry key='database'>alegeri</entry>
    <entry key='user'>user</entry>
    <entry key='passwd'>user</entry>
    <entry key='schema'>tur1</entry>
    <entry key='dbtype'>postgis</entry>
    <entry key='validate connections'>true</entry>
    <entry key='Connection timeout'>20</entry>
    <entry key='min connections'>1</entry>
    <entry key='max connections'>10</entry>
    <entry key='Loose bbox'>true</entry>
    <entry key='fetch size'>1000</entry>
    <entry key='Max open prepared statements'>50</entry>
    <entry key='Estimated extends'>true</entry>
    <entry key='Expose primary keys'>true</entry>
  </connectionParameters>
  <__default>false</__default>
  </dataStore>" \
  http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores
        
      

Depozitul fiind creat, se pot publica straturile individuale. Vom începe cu stratul corespunzător procentelor la nivel de secție, voronoi_procente_sectie pentru turul 1:

        
curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>voronoi_procente_sectie</nativeName>
  <name>fiecare_procent_sectie_tur1</name>
  <title>Fiecare procent din turul 1 sectie</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur1/featuretypes
        
      

Stratul este de acum disponibil pentru accesare prin intermediul serviciilor de la adresa http://localhost:8082/geoserver, în workspace-ul alegeri, sub numele fiecare_procent_tur1. Totuși, informația nu poate fi accesată în format vector tiles deoarece nu face parte din setările implicite, după cum se poate observa din imaginea de mai jos:

Setările implicite din GeoServer cu privire la livrarea datelor în format application/vnd.mapbox-vector-tile

Setările implicite din GeoServer cu privire la livrarea datelor în format application/vnd.mapbox-vector-tile.

Pentru a modifica această setare, vom folosi metoda GET din API-ul REST GeoServer pentru a descărca, în format XML lista actuală de setări a stratului:

        
curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_sectie_tur1.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_procent_sectie_tur1.xml
        
      

Vom deschide fișierul respectiv cu nano:

        
sudo nano fiecare_procent_sectie_tur1.xml
        
      

Rezultatul este prezentat mai jos:

        

<GeoServerLayer>
  <id>LayerInfoImpl-12776bc0:16fd8e57986:-3872</id>
  <enabled>true</enabled>
  <inMemoryCached>true</inMemoryCached>
  <name>alegeri:fiecare_procent_tur1</name>
  <mimeFormats>
    <string>application/json;type=utfgrid</string>
    <string>image/png</string>
    <string>image/vnd.jpeg-png</string>
    <string>image/jpeg</string>
    <string>image/gif</string>
    <string>image/png8</string>
  </mimeFormats>
  <gridSubsets>
    <gridSubset>
      <gridSetName>EPSG:3857</gridSetName>
    </gridSubset>
    <gridSubset>
      <gridSetName>EPSG:900913</gridSetName>
    </gridSubset>
    <gridSubset>
      <gridSetName>EPSG:4326</gridSetName>
    </gridSubset>
  </gridSubsets>
  <metaWidthHeight>
    <int>4</int>
    <int>4</int>
  </metaWidthHeight>
  <expireCache>0</expireCache>
  <expireClients>0</expireClients>
  <parameterFilters>
    <styleParameterFilter>
      <key>STYLES</key>
      <defaultValue></defaultValue>
    </styleParameterFilter>
  </parameterFilters>
  <gutter>0</gutter>
</GeoServerLayer>
        
      

Pentru a activa suportul pentru vector tiles vom insera linia <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats> și vom salva modificările apăsînd combinația de taste ctrl+O. Vom părăsi fișierul cu combinația ctrl+X. Apoi, vom rula, folosind metoda PUT, o comandă de actualizare prin intermediul API-ului REST al GeoServer:

        
curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_procent_sectie_tur1.xml \
"http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_sectie_tur1.xml"
        
      
6.3.3. Încărcarea datelor vector tile în harta web

Structura aceasta de tip tile vectorial este semnificativ diferită de un strat normal și prezintă mult mai multe provocări în utilizare comparativ cu fișierele tiled de tip raster (ex: un element dintr-un strat se poate întinde pe mai multe tile-uri dar el trebuie să poată fi simbolizat și interogat ca și cum ar avea o geometrie unitară, nu una spartă în mai multe bucăți). Leaflet (prin intermediul pluginului VectorGrid) este capabil să ofere utilizatorului o experiență unitară dar cele două tipuri de straturi vectoriale se încarcă și se manipulează diferit. De exemplu, pentru încărcarea în hartă folosim ceva de genul:

        
fiecare_procent = L.vectorGrid.protobuf('http://localhost:8082/geoserver/gwc/service/tms/1.0.0/alegeri:fiecare_procent_tur1@EPSG%3A3857@pbf/{z}/{x}/{-y}.pbf', {
  rendererFactory: L.svg.tile,
  interactive: true,
  maxNativeZoom: 16,
  minZoom: 11,
  pane: 'fiecare_procent',
  getFeatureId: function(f) {
    return f.properties.id;
  },
  vectorTileLayerStyles: {
    fiecare_procent_tur1: properties => {
      return styleFiecareProcent(properties.castigator, false)
    }
  }
});
        
      

Interacțiunea la evenimentele mouseover și mouseout o facem după cum este ilustrat în continuare:

        
fiecare_procent.on('mouseover', function(e) {
  var properties = e.layer.properties;
  clearHighlightVectorGrid();
  highlightVectorGrid = properties.id;
  var style = {
    fillColor: getColor(properties.castigator),
    weight: 1,
    opacity: 1,
    color: getColor(properties.castigator),
    fillOpacity: 1,
    fill: true
  };
  fiecare_procent.setFeatureStyle(properties.id, style);
  info.update(e.layer.properties, 'fiecare_procent');
});

fiecare_procent.on('mouseout', function(e) {
  var properties = e.layer.properties;
  clearHighlightVectorGrid();
  highlightVectorGrid = properties.id;
  var style = {
    fillColor: getColor(properties.castigator),
    weight: 0.9,
    opacity: 1,
    color: getColor(properties.castigator),
    dashArray: '0.5',
    fillOpacity: $("#slider").slider("value"),
    fill: true
  };
  fiecare_procent.setFeatureStyle(properties.id, style);
});
        
      
6.3.4. Publicarea tuturor datelor

Vom publica și restul datelor folosind procedura descrisă anterior. Vom începe cu crearea celui de al doilea datastore, corespunzător schemei tur2 din PostGIS:

        

curl -u admin:geoserver -v -XPOST -H "accept: application/xml" -H "content-type: application/xml" \
-d "
<dataStore>
  <name>tur2</name>
  <type>PostGIS</type>
  <description>Store PostGIS pentru turul 2</description>
  <connectionParameters>
    <entry key='host'>localhost</entry>
    <entry key='port'>5432</entry>
    <entry key='database'>alegeri</entry>
    <entry key='user'>user</entry>
    <entry key='passwd'>user</entry>
    <entry key='schema'>tur2</entry>
    <entry key='dbtype'>postgis</entry>
    <entry key='validate connections'>true</entry>
    <entry key='Connection timeout'>20</entry>
    <entry key='min connections'>1</entry>
    <entry key='max connections'>10</entry>
    <entry key='Loose bbox'>true</entry>
    <entry key='fetch size'>1000</entry>
    <entry key='Max open prepared statements'>50</entry>
    <entry key='Estimated extends'>true</entry>
    <entry key='Expose primary keys'>true</entry>
  </connectionParameters>
  <__default>false</__default>
  </dataStore>" \
  http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores
        
      

Apoi, unul cîte unul, vom publica seturile de date. Începem cu procentele la nivel de secție pentru turul 2:

        

curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>voronoi_procente_sectie</nativeName>
  <name>fiecare_procent_sectie_tur2</name>
  <title>Fiecare procent din turul 2 sectie</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur2/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_sectie_tur2.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_procent_sectie_tur2.xml

sudo nano fiecare_procent_sectie_tur2.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_procent_sectie_tur2.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_sectie_tur2.xml"
        
      

Continuăm cu cele la nivel de UAT pentru cele două tururi:

        

curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>voronoi_procente_uat</nativeName>
  <name>fiecare_procent_uat_tur1</name>
  <title>Fiecare procent din turul 1 UAT</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur1/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_uat_tur1.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_procent_uat_tur1.xml

sudo nano fiecare_procent_uat_tur1.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_procent_uat_tur1.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_uat_tur1.xml"


curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>voronoi_procente_uat</nativeName>
  <name>fiecare_procent_uat_tur2</name>
  <title>Fiecare procent din turul 2 UAT</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur2/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_uat_tur2.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_procent_uat_tur2.xml

sudo nano fiecare_procent_uat_tur2.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_procent_uat_tur2.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_uat_tur2.xml"
        
      

Apoi, județele:

        

curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>voronoi_procente_judete</nativeName>
  <name>fiecare_procent_judete_tur1</name>
  <title>Fiecare procent din turul 1 județe</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur1/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_judete_tur1.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_procent_judete_tur1.xml

sudo nano fiecare_procent_judete_tur1.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_procent_judete_tur1.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_judete_tur1.xml"


curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>voronoi_procente_judete</nativeName>
  <name>fiecare_procent_judete_tur2</name>
  <title>Fiecare procent din turul 2 județe</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur2/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_judete_tur2.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_procent_judete_tur2.xml

sudo nano fiecare_procent_judete_tur2.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_procent_judete_tur2.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_procent_judete_tur2.xml"
        
      

Toate voturile:

        

curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>fiecare_vot_tur1</nativeName>
  <name>fiecare_vot_tur1</name>
  <title>Fiecare vot din turul 1</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur1/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_vot_tur1.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_vot_tur1.xml

sudo nano fiecare_vot_tur1.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_vot_tur1.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_vot_tur1.xml"


curl -u admin:geoserver -v -XPOST -H "Content-type: text/xml" -d "
<featureType>
  <nativeName>fiecare_vot_tur2</nativeName>
  <name>fiecare_vot_tur2</name>
  <title>Fiecare vot din turul 2</title>
</featureType>" \
http://localhost:8082/geoserver/rest/workspaces/alegeri/datastores/tur2/featuretypes

curl -X GET "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_vot_tur2.xml" \
-H  "accept: application/xml" -H  "content-type: application/xml" > fiecare_vot_tur2.xml

sudo nano fiecare_vot_tur2.xml

#se adaugă <string>application/vnd.mapbox-vector-tile</string> în secțiunea <mimeFormats>

curl -v -u admin:geoserver -v -XPUT -H "Content-type: text/xml" -d @fiecare_vot_tur2.xml \
 "http://localhost:8082/geoserver/gwc/rest/layers/alegeri:fiecare_vot_tur2.xml"
        
      
6.5. Concluzii
Avem toate datele și funcționalitățile de bază dorite. Nu mai rămîne decît să împachetăm totul într-o aplicație coerentă.