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:
- Vom exporta din PostGIS, cu GDAL/OGR, datele în format Shapefile;
- Cu ajutorul Mapshaper convertim fișierele Shapefile în format TopoJSON.
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:
mouseover
: evidențiem (prin modul de simbolizare) secția atunci cînd cursorul mouse-ului se găsește în interiorul acesteia;mouseout
: readucem secția la modul implicit de simbolizare atunci cînd cursorul mouse-ului părăsește suprafața acesteia;click
: facem zoom la nivel de secție cînd se execută click în interiorul acesteia.
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:
- Generalizarea datelor pentru afișarea la scări mici;
- Optimizarea tabelei de atribute (reprezentarea eficientă, eliminarea cîmpurilor mai puțin importante, etc.) sau chiar aplearea la o metrodă hibridă (ex.: încărcarea gemetriilor de pe server și unirea lor cu atribute la nivel de client - se aplică eficient doar cu anumite tipuri de informații);
- Pregenerarea geometriilor și stocarea pe server într-o zonă de tip
cache
. Această abordare este posibilă doar cu opțiuneavector tiles
, neffind posibilă și înWFS
.
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
.
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"