OpenDistributoriCarburantiBot: come è fatto “dentro”


In questo post vedo di fornire qualche dettaglio su quelle che sono le caratteristiche e soluzioni tecniche che stanno alla base di OpenDistributoriCarburantiBot.

Non mi dilungo qui a spiegare nel dettaglio la piattaforma Telegram e la basi dei bot: rimando coloro che fossero interessati ad approfondire ad attingere dalle innumerevoli fonti presenti in rete.

Schematicamente l’architettura della soluzione è la seguente:

OpenDistributoriCarburantiBot

Nel diagramma sono citati i vari componenti del sistema che:

Si tratta di una soluzione che utilizza tutti software open source e attinge da servizi disponibili in rete.

Per una soluzione più scalabile è possibile passare ad utilizzare POSTGRESQL con la sua estensione POSTGIS, non cambiando la logica di funzionamento.

Nel diagramma sono anche illustrati i passi delle diverse operazioni coinvolte e precisamente:

  • giornalmente il sistema scarica dal MISE (0a) i dati e li memorizza sul db (0b)
    l’utente invia una richiesta dal proprio client (1)
  • il sistema recupera le richieste con un’operazione di getUpdates (2)
  • effettua le ricerche su DB (3)
  • nell’elaborazione della risposta invoca i servizi di Google Shortlink per abbreviare le url (4)
  • con un’operazione di sendMessage risponde (5)
  • Telegram inoltra la risposta al client (6)
  • l’utente può fruire dei diversi link contenuti nella risposta (7, 7a, 7b e 7c)

Il “cuore” del sistema sono i dati: questi sono tratti giornalmente dagli open del Ministero dello Sviluppo Economico (MISE).

I dati sono forniti nella forma di due file .csv che riportano le anagrafiche dei distributori ed i prezzi dei carburanti. Tra i dati delle anagrafiche dei distributori, informazione importante, sono disponibili anche le coordinate, espresse in latitudine e longitudine.

NOTA: negli esempi si suppone che il sw sia installato in /var/www/html/Telegram per cui la variabile <base_path> = /var/www/html/Telegram

Tutte le mattine, con un crontab schedulato, i dati sono prelevati via wget,

wget -U "Opera" -O <base_path>/OpenDistributoriCarburantiBot/Dati/prezzo_alle_8.csv http://www.sviluppoeconomico.gov.it/images/exportCSV/prezzo_alle_8.csv

wget -U "Opera" -O <base_path>/OpenDistributoriCarburantiBot/Dati/anagrafica_impianti_attivi.csv http://www.sviluppoeconomico.gov.it/images/exportCSV/anagrafica_impianti_attivi.csv

Si provvede ad eliminare la prima riga dei files

sed '1d' <base_path>/OpenDistributoriCarburantiBot/Dati/anagrafica_impianti_attivi.csv > <base_path>/OpenDistributoriCarburantiBot/Dati/anagrafica-temp.csv

sed '1d' <base_path>/OpenDistributoriCarburantiBot/Dati/prezzo_alle_8.csv > <base_path>/OpenDistributoriCarburantiBot/Dati/prezzo.csv

Nel file delle anagrafiche è necessario eliminare il carattere “doppi apici” che influisce negativamente nell’import in Spatialite

sed 's/"//g' <base_path>/OpenDistributoriCarburantiBot/Dati/anagrafica-temp.csv > <base_path>/OpenDistributoriCarburantiBot/Dati/anagrafica.csv

A questo punto è possibile creare il data base Spatialite ….

/usr/local/bin/spatialite <base_path>/OpenDistributoriCarburantiBot/Dati/PrezziCarburanti < <base_path>/OpenDistributoriCarburantiBot/Dati/CreaPrezziCarburanti.txt

….. con i seguenti dettagli …

CREATE TABLE anagrafica_impianti_attivi( idImpianto TEXT,
Gestore TEXT,
Bandiera TEXT,
Tipo Impianto TEXT,
Nome Impianto TEXT,
Indirizzo TEXT,
Comune TEXT,
Provincia TEXT,
Latitudine DOUBLE,
Longitudine DOUBLE );
CREATE TABLE prezzo_alle_8( idImpianto TEXT,
descCarburante TEXT,
prezzo TEXT,
isSelf TEXT,
dtComu TEXT );
.mode csv

.separator ;

.import <BASE_PATH>/OpenDistributoriCarburantiBot/Dati/anagrafica.csv anagrafica_impianti_attivi

SELECT AddGeometryColumn('anagrafica_impianti_attivi','geometry',4326,'POINT',2); UPDATE anagrafica_impianti_attivi SET geometry = GeomFromText('POINT('||"Longitudine"||' '||"Latitudine"||')',4326);

.import <BASE_PATH>/OpenDistributoriCarburantiBot/Dati/prezzo.csv prezzo_alle_8

.quit

La logica di business è stata implementata in php sebbene questa non sia una scelta esclusiva od obbligata. E’ possibile scegliere linguaggi di programmazione diversi: nel mio caso è stata una scelta di opportunità visto che in PHP avevo la possibilità di trovare maggiore esempi e supporto.

Di potenziale interesse riporto come sono implementate le ricerche, ad esempio quella per i distributori di un comune

SELECT distr.Comune, distr.Gestore, distr.Indirizzo, distr.Bandiera, distr.Latitudine, distr.Longitudine, prz.descCarburante, prz.prezzo, prz.dtComu FROM anagrafica_impianti_attivi as distr JOIN prezzo_alle_8 as prz ON (prz.idImpianto = distr.IdImpianto) WHERE distr.Comune = :Comune ORDER BY prz.prezzo ASC

dove ovviamente il parametro :Comune sarà poi valorizzato dal nome del comune corrente indicato dall’utente.

Analogamente, una ricerca spaziale che individua i distributori di un certo carburante, nell’intorno di una certa distanza da un punto, caratterizzato da una determinata coppia di coordinate, sarà

SELECT distr.Comune, distr.Gestore, distr.Indirizzo, distr.Bandiera, distr.Latitudine, distr.Longitudine, prz.descCarburante, prz.prezzo, prz.dtComu, ST_Distance(distr.geometry, MakePoint('.$lon.', '.$lat.', 4326), 1) AS dist FROM anagrafica_impianti_attivi as distr JOIN prezzo_alle_8 as prz ON (prz.idImpianto = distr.IdImpianto) WHERE dist <= '.$dist.' AND prz.descCarburante = \''.$carburante.'\' ORDER BY prz.prezzo ASC, dist ASC

Ovviamente i risultati delle query sono poi parsificati opportunamente per andare a costruire il messaggio finale che arriverà sul dispositivo dell’utente finale.

Ecco come …..

try {
$stmt = $db->prepare($q);
$results = $stmt->execute();
$count = 0;
while (($row = $results->fetchArray(SQLITE3_ASSOC)) AND ($count < $limit_search)){
$data .= "Marca: ".$row['Bandiera'];
$data .= "\n";
$data .= "Carburante: ".$row['descCarburante'];
$data .= "\n";
$data .= "Prezzo: ".$row['prezzo'];
$data .= "\n";
$data .= "Data: ".$row['dtComu'];
$data .= "\n";

$longUrl = "http://www.openstreetmap.org/?mlat=".$row['Latitudine']."&mlon=".$row['Longitudine']."&zoom=18";
$shortUrl = CompactUrl($longUrl);
$data .= "Mappa: ".$shortUrl;
$data .= "\n";

$longUrl = $base_url."/Telegram/DistributoriCarburanti/RenderRoute.php?lat_from=".$lat."&lon_from=".$lon."&lat_to=".$row['Latitudine']."&lon_to=".$row['Longitudine']."&map_type=0";
$shortUrl = CompactUrl($longUrl);
$data .= "Descrizione Percorso: ".$shortUrl;
$data .= "\n";

$longUrl = $base_url."/Telegram/DistributoriCarburanti/RenderRoute.php?lat_from=".$lat."&lon_from=".$lon."&lat_to=".$row['Latitudine']."&lon_to=".$row['Longitudine']."&map_type=2";
$shortUrl = CompactUrl($longUrl);
$data .= "Percorso su mappa 2D: ".$shortUrl;
$data .= "\n";

$longUrl = $base_url."/Telegram/DistributoriCarburanti/RenderRoute.php?lat_from=".$lat."&lon_from=".$lon."&lat_to=".$row['Latitudine']."&lon_to=".$row['Longitudine']."&map_type=3";
$shortUrl = CompactUrl($longUrl);
$data .= "Percorso su mappa 3D: ".$shortUrl;
$data .= "\n";

$data .= "\n";
$count = $count + 1;
}
if ($count >= $limit_search){
$data .= "La ricerca ha prodotto troppi risultati !!! Si presentano solo i primi ".$limit_search.". Per maggiori dettagli provare a cambiare comune, tipo di carburante, posizione o raggio di ricerca";
$data .= "\n";
}
return $data;
}
catch(PDOException $e) {
print "Something went wrong or Connection to database failed! ".$e->getMessage();
}
}
}

I messaggi di Telegram non possono superare i 4096 caratteri e dovendo fornire le url per la visualizzazione dei dettagli del percorso o della sua rappresentazione su mappa, ho dovuto “restringere” le url stesse avvalendomi del servizio di Google URL Shortener.

Il tutto avviene invocando un’apposita funzione …

function CompactUrl($longUrl)
{
$apiKey = API;

$postData = array('longUrl' => $longUrl);
$jsonData = json_encode($postData);

$curlObj = curl_init();

curl_setopt($curlObj, CURLOPT_URL, 'https://www.googleapis.com/urlshortener/v1/url?key='.$apiKey.'&fields=id');
curl_setopt($curlObj, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curlObj, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curlObj, CURLOPT_HEADER, 0);
curl_setopt($curlObj, CURLOPT_HTTPHEADER, array('Content-type:application/json'));
curl_setopt($curlObj, CURLOPT_POST, 1);
curl_setopt($curlObj, CURLOPT_POSTFIELDS, $jsonData);

$response = curl_exec($curlObj);

// Change the response json string to object
$json = json_decode($response);

curl_close($curlObj);
$shortLink = get_object_vars($json);

return $shortLink['id'];
}

Siccome tutte le comunicazioni sono “stateless”, mentre per alcuni passaggi necessitavo di mantenere traccia delle opzioni selezionate ai passi precedenti, ho implementato una piccola gestione di sessione sul db SQLite 3.

In questo modo aggiorno i dati quando necessario …

try {
$base_path = BASE_PATH;
//## Preparo di dati di sessione da memorizzare nel data base per il'id di chat ...
$session_data = array(
array('Chat_id' => $chat_id,
'Comune' => $comune,
'My_Lat' => $lat,
'My_Lon' => $lon,
'Search_Distance' => $search_distance
)
);
//## Accedo al db di sessione per memorizzare i dati per il'id di chat ...
$db_data_sessions = new SQLite3($base_path.'/DistributoriCarburanti/DataSessionsDB');
//## Accedo al db di sessione per eliminare i dati di sessione per di interesse per l'id di chat ...
$q="DELETE FROM data_sessions WHERE Chat_id = :Chat_id";
try {
$stmt = $db_data_sessions->prepare($q);
$stmt->bindvalue(':Chat_id', $chat_id, SQLITE3_TEXT);
$results = $stmt->execute();
}
catch(PDOException $e) {
print "Something went wrong or Connection to database failed! ".$e->getMessage();
}

//## Prepare INSERT statement to SQLite3 file db ...
$insert = "INSERT INTO data_sessions (Chat_id, Comune, My_Lat, My_Lon, Search_Distance) VALUES (:Chat_id, :Comune, :My_Lat, :My_Lon, :Search_Distance)";
$stmt = $db_data_sessions->prepare($insert);

//## Bind parameters to statement variables ...
$stmt->bindParam(':Chat_id', $Chat_id);
$stmt->bindParam(':Comune', $Comune);
$stmt->bindParam(':My_Lat', $My_Lat);
$stmt->bindParam(':My_Lon', $My_Lon);
$stmt->bindParam(':Search_Distance', $Search_Distance);

//## Loop thru all messages and execute prepared insert statement ...
foreach ($session_data as $data) {
//## Set values to bound variables ...
$Chat_id = $data['Chat_id'];
$Comune = $data['Comune'];
$My_Lat = $data['My_Lat'];
$My_Lon = $data['My_Lon'];
$Search_Distance = $data['Search_Distance'];

//## Execute statement ...
$stmt->execute();

$db_data_sessions = null;
}

}
catch(PDOException $e) {
print "Something went wrong or Connection to database failed! ".$e->getMessage();
}

ed in questo modo recupero i dati quando necessario ….

$db_data_sessions = new SQLite3($base_path.’/DistributoriCarburanti/DataSessionsDB’);
$q="SELECT ds.Chat_id, ds.Comune, ds.My_Lat, ds.My_Lon, ds.Search_Distance
FROM data_sessions as ds
WHERE ds.Chat_id = :Chat_id";
try {
$stmt = $db_data_sessions->prepare($q);
$stmt->bindvalue(':Chat_id', $chat_id, SQLITE3_TEXT);
$results = $stmt->execute();
while ($row = $results->fetchArray(SQLITE3_ASSOC)){
$comune = $row['Comune'];
$my_lat = $row['My_Lat'];
$my_lon = $row['My_Lon'];
$search_distance = $row['Search_Distance'];
}
}
catch(PDOException $e) {
print "Something went wrong or Connection to database failed! ".$e->getMessage();
}

Per il calcolo dei percorsi utilizzo il servizio di routing di MapQuest (rif. https://developer.mapquest.com/products/directions): tale servizio, gratuito sino a 15.000 chiamate al giorno (non credo di superare tali limite …. ne sarei piacevolmente stupito ….), si basa sui dati di OpenStreetMap e permette di ritornare sia la semplice descrizione testuale del percorso, sia i suoi dettagli geografici in formato JSON.

Ecco un esempio di chiamata ….

http://www.mapquestapi.com/directions/v2/route?key=YOUR_KEY_HERE&from=Lancaster,PA&to=York,PA&callback=renderNarrative

Il richiamo del servizio avviene via CURL

$url = 'http://open.mapquestapi.com/directions/v2/route?key=&outFormat=json&routeType=fastest&timeType=1&narrativeType=html&enhancedNarrative=true&shapeFormat=raw&generalize=0&locale=it_IT&unit=k&from='.$lat_from.','.$lon_from.'&to='.$lat_to.','.$lon_to.'&drivingStyle=2&highwayEfficiency=21.0';

//#Set CURL parameters
$ch = curl_init();
curl_setopt($ch, CURLOPT_AUTOREFERER, TRUE);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_PROXY, '');
$data = curl_exec($ch);
curl_close($ch);

//#Convert to string (json) the route ...
$json = json_decode($data);

ed ecco come aggiungere il json del percorso sulla mappa Leaflet …..

var json_route = <?php echo json_encode($json); ?>;

//# Calculate the new map center based on the route bounding box ...
lng_ul = json_route.route.boundingBox.ul.lng;
lat_ul = json_route.route.boundingBox.ul.lat;
lng_lr = json_route.route.boundingBox.lr.lng;
lat_lr = json_route.route.boundingBox.lr.lat;
lng_center = (lng_lr - lng_ul)/2 + lng_ul;
lat_center = (lat_ul - lat_lr)/2 + lat_lr;


var map = L.map('map').setView([lat_center, lng_center], 15);

L.tileLayer('https://api.tiles.mapbox.com/v4/{id}
/{z}/{x}/{y}.png?access_token=’, {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.light'
}).addTo(map);


the_coords = '';

for (i = 0; i < json_route.route.shape.shapePoints.length;
i++) {

if (i == 0) {
the_coords = the_coords + '[' + json_route.route.shape.shapePoints[i+1] + ',' + json_route.route.shape.shapePoints[i] + ']';
}
else {
if ((i % 2) == 0) {
the_coords = the_coords + ',[' + json_route.route.shape.shapePoints[i+1] + ',' + json_route.route.shape.shapePoints[i] + ']';
}
}
}

//# Create the lineString json (text) of the route ...
var my_lineString = '{\"type\": \"Feature\", \"properties\": {
}, \”geometry\”: {\”type\”: \”LineString\”, \”coordinates\”: [‘ + the_coords + ‘]}}’;

//# Create le lineString json (object) of the route ...
var lineString = JSON.parse(my_lineString);

//# add the lineString json (object) of the route to the map ...
L.geoJson(lineString).addTo(map);

Per la rappresentazione su mappa 3D l’approccio è del tutto analogo, con una unica eccezione: non essendo possibile sovrapporre alla mappa OSM Buildings un oggetto Linestring il percorso viene trasformato in un oggetto poligonale calcolandone un piccolo buffer di 1 metro. Il calcolo del buffer avviene avvalendosi della libreria Turf.js (rif. http://turfjs.org/).

Ecco come avviene il tutto ….

//# Extract the route coordinates ...
the_coords = '';

for (i = 0; i < json_route.route.shape.shapePoints.length; i++) {
if (i == 0) {
the_coords = the_coords + '[' + json_route.route.shape.shapePoints[i+1] + ',' + json_route.route.shape.shapePoints[i] + ']';
}
else {
if ((i % 2) == 0) {
the_coords = the_coords + ',[' + json_route.route.shape.shapePoints[i+1] + ',' + json_route.route.shape.shapePoints[i] + ']';
}
}
}

//# Create the lineString json (text) of the route ...
var my_lineString = '{\”type\”: \”Feature\”, \”properties\”: {}, \”geometry\”: {\”type\”: \”LineString\”, \”coordinates\”: [‘ + the_coords + ‘]}}’;

//# Create le lineString json (object) of the route ...
var lineString = JSON.parse(my_lineString);

//# Set the unit measure and calculate the buffer using Turf.js ....
var unit = 'meters';
var buffered = turf.buffer(lineString, 1, unit);
var result = turf.featurecollection([buffered]);

//# Convert to string the buffered json ....
var my_json = JSON.parse(JSON.stringify(result));

//# Create an empty Polygon json to put in OSM Building map ....
var geojson_route = {
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {
color: '#FF0080',
roofColor: '#ff99cc',
height: 0,
minHeight: 0
},
geometry: {
type: 'Polygon',
coordinates: [
[
]
]
}
}]
};

//# Add the coordinates at the Polygon json bringing them from the buffered json ....
for (var i = 0; i < my_json.features[0].features[0].geometry.coordinates[0].length; i++) {
geojson_route.features[0].geometry.coordinates[0][i] = my_json.features[0].features[0].geometry.coordinates[0][i];
}

//# Add the Polygon json to the map ....
osmb.addGeoJSON(geojson_route);

//# Refresh of the map to show the labels ...
map.setPosition(map.getPosition());

Ovviamente per maggiori dettagli mi potete contattare direttamente.

Annunci

Test automation su web gis application “OpenLayers based” usando Selenium Web Driver


Per ragioni lavorative mi sono occupato di valutare l’utilizzo della suite Selenium HQ per il test automation di web application.

Tale suite risulta essere uno dei principali prodotti open source di test automation per web application: sostanzialmente è caratterizzato da due tool, Selenium IDE e Selenium Web Driver (ve ne sono poi altri due, Selenium Grid e Selenium Remote Control che non ho analizzato).

Selenium IDE è un plugin per Firefox (non esiste omologo per altri browser), che permette di catturare una sessione utente di una web application (comprese le azioni di imputazione di dati ….), produrre automaticamente uno script che può poi essere rieseguito come tale o modificato.

Il tool è adatto per test semplici (sostanzialmente adatto per l’entry level …), non richiede alcuna conoscenza tecnica pregressa (sebbene un profilo tecnico, tipo programmatore, probabilmente riesce ad ottenere il meglio dallo strumento ….), ed è molto facile da utilizzare: ha tuttavia dei limiti che, anche con le personalizzazioni degli script ottenuti automaticamente, difficilmente possono essere superati.

La maggior parte della comunità che utilizza la suite Selenium utilizza per i propri scopi Selenium Web Driver.

Selenium Web Driver permette di creare delle vere e proprie suite di test programmando i test cases nei principali linguaggi di programmazione (Java, Ruby, Python, ecc …): nel mio caso ho utilizzato Java ma questa non è la scelta esclusiva anche se è quella per cui forse si trovano maggiori esempi, documentazione e supporto.

Sostanzialmente Web Driver “emula” una sessione browser ma lo fà server-side.

La sua installazione (è necessario usare Eclipse …), è molto semplice e ben documentata (nel senso adatta anche a chi non ha grosse conoscenza tecniche, si tratta di un tutorial veramente descritto “passo – passo” ….), sul sito di Selenium o in vari altri blog.

A differenza di Selenium IDE è quindi rivolto ad una utenza decisamente più tecnica, diciamo figure professionali con profili da programmatore (e la cosa si evidenzia andando a vedere il supporto che si ottiene on-line sui vari StackExchange, StackOverflow, ecc …, nel senso che chi risponde sono tutte figure di questo genere, lo si intende dal livello di risposte fornite).

Fatta questa esperienza professionale ho deciso di provare a vedere come il tool si comportava con web gis application che sono tipicamente caratterizzate da un abbondante uso di librerie javascript, anche “verticali” quali OpenLayers o Leaflet, e un livello di interattività (intendo quello sulla mappa ….), decisamente superiore ad una tipica web application gestionale.

Ho quindi provato a vedere cosa succedeva ad utilizzare Web Driver inizialmente con alcuni degli esempi consultabili sul sito di OpenLayers 2.13, ed infine con una web gis application “vera” Java based ed utilizzante OpenLayers ed ExtJS (non ho provato per ragioni di tempo con Leaflet ma credo che, rispetto alle conclusioni che se ne possono trarre, la cosa sia piuttosto invariante).

I .jar che vi potete scaricare dovrebbero essere eseguibili direttamente dando il seguente comando:

java -jar <nome_esempio>.jar

e richiedono come minimo la jdk 1.6.

Vi condivido comunque il codice sorgente in modo che, chi interessato, una volta installato e configurato Eclipse con le diverse librerie che servono per Web Driver (per l’installazione e configurazione vi rimando alla documentazione ufficiale …).

In merito al codice sorgente solo una nota: il codice è puramente a scopo didattico e pensato per poter essere compilato ed eseguito nel maggior numero di situazioni, non và preso come codice di “produzione”, per cui i “puristi” o i programmatori veri (cosa che io oramai non sono più ….), non me ne vogliano per le diverse System.out che troveranno nel codice, sò da me che avrei dovuto usare, come deve essere fatto correttamente, Log4J e compilare di conseguenza, ma lascio il tutto a coloro che vorranno poi scrivere dei test cases veri.

Altra cosa è che i test case NON analizzano come trattare eventuali situazioni di errore: il loro focus è provare se si riesce a simulare una sessione utente che comprenda le principali interazioni con la mappa. Trattandosi di test case realizzati in modalità “programmatica” la gestione dell’errore viene lasciata alla modalità organizzativa ritenuta più efficace dall’organizzazione: raccogliere i dati su un database, scatenare eventi, inviare messaggi, tracciare su file di log, ecc … Ovviamente tutto questo era, al momento, out-of-scope di queste prove.

Vediamo allora il primo test case sull’esempio base di OpenLayers effettuando uno zoom in interattivo sulla mappa

Il codice sorgente che implementa questo test è il seguente:

package myTests;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.firefox.internal.ProfilesIni;
import org.openqa.selenium.interactions.Actions;

public class interactiveZoomInOpenLayerstest_01 {

private static WebDriver driver = null;

public static void main(String[] args) throws InterruptedException {

//Create a new profile and load my Firefox default profile
System.out.println("Creo un nuovo profilo e vi carico il profilo di default di Firefox ...");
Thread.sleep(3000L);
ProfilesIni profile = new ProfilesIni();
FirefoxProfile ffProfile = profile.getProfile("default");

// Create a new instance of the Firefox driver using my new Firefox profile
System.out.println("Creo una nuova sessione del browser Firefox ...");
Thread.sleep(3000L);
driver = new FirefoxDriver(ffProfile);

//Put a Implicit wait, this means that any search for elements on the page could take the time the implicit wait is set for before throwing exception
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

// It is always advisable to Maximize the window before performing DragNDrop action
System.out.println("Massimizzo la finestra del browser ...");
Thread.sleep(3000L);
driver.manage().window().maximize();

//Launch the OpenLayers 2.x sample
System.out.println("Mi collego all'esempio di OpenLayers 2.x ...");
Thread.sleep(3000L);
driver.get("http://dev.openlayers.org/releases/OpenLayers-2.13.1/examples/example.html");

// Find the viewport inside in witch there is the map
System.out.println("Individuo il viewport al cui interno c'è la mappa ...");
Thread.sleep(3000L);
WebElement el = driver.findElement(By.id("OpenLayers_Map_2_OpenLayers_ViewPort"));

// Create a new Action instance
System.out.println("Creo un oggetto di tipo \"Action\" ...");
Actions act = new Actions(driver);

// Moves to 2nd location
System.out.println("Moves to 1st location: 200, 50 ...");
Thread.sleep(3000L);

// Draw rectangle and execute zoom in
System.out.println("Moves to 1st location: 300, 150 ...");
act.moveToElement(el,200,50).click().keyDown(Keys.SHIFT).clickAndHold().moveToElement(el, 300, 150).click().release().keyUp(Keys.SHIFT).build().perform();

// Print TEST = OK!!
System.out.println("TEST = OK !!");
//driver.quit();

}

}

Il codice sorgente è liberamente scaricabile.

E’ possibile anche scaricare lo zip dell’eseguibile Java: una volta decompresso posizionarsi in

InterActiveZoomInTest\InterActiveZoomIn\jar

e poi riprodurlo in locale con il seguente comando

java -jar InterActiveZoomIn.jar

Vediamo ora il  secondo test case sull’esempio del calcolo della misura di un’area disegnata interattivamente di OpenLayers

Per brevità non si riporta il codice sorgente che comunque liberamente scaricabile.

E’ possibile anche scaricare lo zip dell’eseguibile Java: una volta decompresso posizionarsi in

MeasureAreaOpenTest\MeasureAreaOpenLayers\jar

e poi riprodurlo in locale con il seguente comando

java -jar MeasureAreaOpenLayers.jar

Vediamo ora il  terzo test case sull’esempio di OpenLayers dell’interrogazione interattiva di un marker presente sulla mappa

Per brevità non si riporta il codice sorgente che comunque liberamente scaricabile.

E’ possibile anche scaricare lo zip dell’eseguibile Java: una volta decompresso posizionarsi in

IdentifyOpenLayersTest\IdentifyOpenLayers\jar

e poi riprodurlo in locale con il seguente comando

java -jar IdentifyOpenLayers.jar

Vediamo ora il quarto test case sull’esempio di OpenLayers dell’interrogazione interattiva di geometrie di un servizio WMS presente sulla mappa

Per brevità non si riporta il codice sorgente che comunque liberamente scaricabile.

E’ possibile anche scaricare lo zip dell’eseguibile Java: una volta decompresso posizionarsi in

GetFeatureInfoWMSTest\GetFeatureInfoWMSTest\jar

e poi riprodurlo in locale con il seguente comando

java -jar GetFeatureInfoWMSTest.jar

Vediamo ora il  quinto test case sull’esempio di OpenLayers dell’editing interattivo su mappa 

Per brevità non si riporta il codice sorgente che comunque liberamente scaricabile.

E’ possibile anche scaricare lo zip dell’eseguibile Java: una volta decompresso posizionarsi in

EditingOpenLayersTest\EditingOpenLayers\jar

e poi riprodurlo in locale con il seguente comando

java -jar EditingOpenLayers.jar

Ma il tutto è applicabile anche a progetti reali? La risposta è sì: ho provato a costruire una navigazione interattiva su un progetto di web mapping reale di Regione Piemonte ad accesso libero realizzato con MapStore (prodotto open source realizzato partendo da GeoExplorer e quindi basato su ExtJS e OpenLayers).

Per brevità non si riporta il codice sorgente che comunque liberamente scaricabile.

E’ possibile anche scaricare lo zip dell’eseguibile Java: una volta decompresso posizionarsi in

AeraTest\AeraProject\jar

e poi riprodurlo in locale con il seguente comando

java -jar AeraProject.jar

Occorre precisare che la realizzazione di test suite richiede l’impegno di risorse con un profilo tecnico (programmatori) medio-alto, e non è banale per cui anche i tempi di realizzazione non sono bassi: non è quindi ammortizzabile, in termini di costi, per progetti “piccoli”, mentre per progetti medio – grandi, in particolare se critici o sensibili per aspetti diversi, sicuramente, una volta redatti ed automatizzati i test, si ha la possibilità di eseguirli automaticamente in tempi molto rapidi e tutte le volte he lo si ritiene necessario.

Essendo in questo caso le suite di test “codice”, le si possono (anzi le si devono ….), trattare come tali e quindi possono (devono), essere mantenuti su repository da cui, con tools di software automation, agganciare a processi di building automatici che permettano di eseguire i test automaticamente ad ogni rilascio significativo del codice di business dell’applicazione.

Non occorre però scordare due aspetti fondamentale che esulano dall’automazione: il codice delle suite di test deve essere manutenuto allo stesso modo del codice di business applicativo e, altro aspetto fondamentale, anche la base dati su cui si eseguono i test deve essere mantenuta (o replicata), coerente.

Concludendo quindi si può dire che per l’automazione di test di web mapping application si possono usare senza problemi i medesimi tools che si utilizzano per le web application gestionale e che quindi, in questo caso …. “spatial is not special” !!!

Numeri civici Open Data in Italia: disponibile la release 3.0 della raccolta

27 settembre 2015 14 commenti

A febbraio 2015 ho pubblicato la seconda versione della raccolta di quelli che sono i numeri civici georiferiti open data disponibili nel panorama italiano.

E’ giunta quindi l’ora di un ulteriore aggiornamento, per cui ora è rendo disponibile la release 3.0 della raccolta stessa.

La raccolta è resa disponibile con licenza CC0 1.0 Universal: ovviamente per i singoli dataset restano valide le licenze originali che sono riportate nella raccolta stessa.

Con questo aggiornamento siamo arrivati a 188 dataset diversi : ci sono aree ancora completamente vuote, intere regioni in cui mancano dati, regioni per cui vi sono dati ridondanti, ecc … ma i numeri stanno crescendo

Ecco quali sono “release notes” di questa versione della raccolta:

  • verificati, per tutti i dataset già esistenti, i link di pubblicazione e download: nel tempo alcuni erano stati cambiati dagli Enti
  • verificate tutte le licenze d’uso: anche qui vi sono casi in cui le licenze sono state modificate
  • aggiunti nuovi dataset, in particolare:
    • Comune di Storo
    • Provincia Autonoma di Bolzano
    • Regione Piemonte
    • Regione Lombardia

La percentuale dei numeri civici resi disponibili come tali, e non “derivati” da altri tematismi,  sale dal 10% al 25 % sul totale delle informazioni raccolte e quindi un buon risultato in termini di crescita  (la prima versione vedeva una percentuale del 5%)

DistribuzioneCivici-V03

Come consueto i dati sono consultabili via web grazie ad un piccolo esempio in web mapping basato su HTML, Javascript e Leaflet.

CiviciItalia2

I dati sono anche resi consultabili, da un punto di vista geografico, usando il software open source QGIS:

A differenza delle versioni precedenti della raccolta, viste le dimensioni raggiunte, non mi è più stato possibile rendere disponibile anche i dati in formato ESRI shapefile insieme al progetto QGIS di consultazione. Resta comunque possibile, per chi interessato, poter scaricare i singoli dataset in formato ESRI shapefile e convertiti un WGS84 utilizzando i link di download resi disponibili all’interno del file della raccolta stessa

Ricordo una breve nota tecnica: per rendere operativo lo sfondo OpenStreetMap è necessario che, qualora si operi su una rete locale, si verifichi la corretta configurazione del proxy, e che, nel QGIS utilizzato, sia presente ed attivo il plugin OpenLayers.

I progetti QGIS sono stati realizzati con la versione 2.6.1 (Brigthon).

Segnalazioni di dati mancanti per aggiornare la raccolta sono particolarmente gradite …. grazie sin da ora!

Ops …. io e la mia vecchia bici siamo finiti sul sito di Mapillary

11 settembre 2015 2 commenti

Oggi il mio post su come farsi il proprio StreetView con Mapillary è stato ripreso nella sezione Community di Mapillary ….

MapillaryCommunityMember

Un minimo di soddisfazione ed orgoglio non lo nego … 🙂

 

Categorie:Open Data, Progetti Tag:

GFOSS DAY 2015 a Lecco il 28-29 Settembre 2015


Il GFOSS DAY 2015 si terrà a Lecco il 28-29 Settembre 2015

Il GFOSS DAY è un evento promosso dall’Associazione Italiana per l’Informazione Geografica Libera (GFOSS.it) della durata di due giorni, durante il quale vengono presentati i migliori lavori sull’utilizzo, lo sviluppo e la diffusione delle applicazioni libere e a codice aperto (Free and Open Source Software) in ambito GIS.

Vengono inoltre trattati argomenti sui dati aperti (Open Data) geografici. Durante la conferenza ci sarà anche la possibilità di conoscere nuovi software attraverso workshop, tenuti dai più importanti contributori italiani al software libero geografico. Lo scopo principale della manifestazione, giunta quest’anno alla settima edizione, è quello di coinvolgere imprese, enti pubblici, scuole, università, centri di ricerca, sviluppatori, cittadini, operatori del settore ed appassionati ai temi del software libero geografico e degli open data.

Il 25 Settembre si sarà la chiusura delle registrazioni!!

Per i contatti info@gfoss.it

Categorie:Conferenze Tag:

Una vecchia bici (da donna …), un tablet (in prestito …), un pò di tempo ed ecco come farsi lo StreetView del proprio Comune

30 agosto 2015 7 commenti

Quanti di voi già conoscono Mapillary? Quanti lo hanno già usato? Quanti vi hanno già contribuito? Se le risposte sono, “no”, “no” e “no” allora vi suggerisco di fare prima un giro informativo sul sito ufficiale del progetto, poi di tornare qui per capire come potete realizzare lo street view del vostro comune, quartiere, zona di interesse, ecc …

Citando Wikipedia riporto …. “… Mapillary (mapillary.com) è un servizio per condividere foto georeferenziate sviluppato da una startup, con sede a Malmö, Svezia. I suoi ideatori vogliono rappresentare il mondo intero (non solo le strade) con delle foto. Ritengono che per coprire tutti i posti interessanti nel mondo siano necessari un progetto crowd-sourced indipendente e un approccio sistematico alla copertura di aree interessanti. I servizi come Google, che utilizzano auto equipaggiate con speciali fotocamere, non riusciranno a coprire il mondo con dettaglio sufficiente. Secondo loro, la conoscenza locale è quasi imbattibile, e solo gli abitanti sanno cosa veramente sia importante quando si scatta una foto. Sono interessati alla copertura di qualunque posto all’aperto, e possono contribuiere a un sistema che rappresenta il mondo con un alto livello di dettaglio. La maggior parte della elaborazione di immagine è fatta lato server usando tecnologie Big data e di visione artificiale, rendendo la raccolta dei dati estremamente semplice per l’utente. Pertanto, Mapillary migliora con ciascuna nuova foto, perchè ogni nuova foto è messa in relazione con tutte le foto esistenti nel suo intorno. ……. I contributori possono installare la app Mapillary su smartphone Android o iPhone, sono noti casi di successo persino su dispositivi Kindle App Store, Jolla e Blackberry che possono eseguire app Android. Dopo la registrazione, l’utente può cominciare a scattare foto ….

Quindi in sostanza Mapillary è:

  • un sistema crowd-sourced per raccogliere fotografie georiferite nel mondo e realizzare un’alternativa al prodotto Street View di Google
  • un’app di semplice utilizzo con cui fare fotografie, o serie di fotografie, delle zone di interesse

Le immagini su Mapillary possono essere usate secondo la licenza internazionale Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA). Un permesso speciale è concesso per derivare dati dalle foto per contribuire ad OpenStreetMap. Le tracce GPX possono essere usate senza restrizioni. La licenza è stata cambiata il 29 aprile 2014 da CC-BY-NC a CC-BY-SA.

Il sistema di stà rapidamente diffondendo in giro per il mondo …

Mapillary-world

…. sicuramente in evidenza l’Europa, in particolare dalla Germania andando verso nord ….


mapillary-europe

mentre in Italia è ancora poco presente ed in modo non strutturato.

mapillary-italy

Tuttavia è un sistema che, con davvero poco sforzo e praticamente senza necessità di nessun background tecnico (ad esempio è molto più semplice che non mappare su OpenStreetMap con ID o con JOSM ….), permette di produrre risultati interessanti ed immediatamente visibili, anche perchè i tools che Mapillary stà pian piano mettendo a disposizione (o ha in sviluppo ….), stanno migliorando ed aumentando così come pure la disponibilità di API ne permette un uso anche a terzi (canale ancora poco utilizzato a dire la verità, forse legato al fatto che deve aumentare la disponibilità di zone completamente e ben mappate).

Nella sostanza è sufficiente saper fare delle foto usando uno smartphone / tablet ed usare qualche piccola accortezza: la app di Mapillary permette di fare foto “singole” o “sequenze” (queste si possono catturare ogni 2 secondi camminando, andando in bicicletta, andando in auto, ovviamente in questi ultimi due casi è necessario fissare in un qualche modo il dispositivo ….).

Terminata la sessione di mapping, è possibile rivedere le fotografie, elminare quelle venute male e provvedere all’upload sul server: dopo qualche ora le vostre fotografie sono già disponibili ….. a chiunque!!

Mapillary fà in realtà molto di più ma non mi dilungo sui dettagli e, come detto in precedenza, vi rimando al sito.

Partendo da questi presupposti ho provato a vedere cose riuscivo a fare nel mio piccolo comune cosa che, per chi interessato, può tranquillamente replicare per la propria area di interesse.

Ecco cosa ho utilizzato:

  • una vecchia bici da donna
  • un tablet (in prestito, nello specifico un IPAD Mini …)
  • un supporto per il tablet da montare sulla bici (per evitare che cadesse): unica spesa fatta (circa 30 Euro)
  • qualche ora libera per girare in bici per le strade del comune (si può anche ottenere lo stesso risultato andando a piedi o in automobile)

Ecco alcune immagini del miscuglio di tecnologie 1.0 e 2.0 …. 🙂

SAMSUNG

SAMSUNG

Il risultato finale è questo (cliccare sull’immagine per aprire Mapillary sulla zona …) ….

Mapillary-Carignano

Ed ecco qualche dettaglio ….

Carignano-1

Carignano-2

Carignano-3

Quindi posso dire (con un pò di orgoglio ?!), che ora il mio comune è praticamente uno dei pochi comuni Italiani  completamente mappato su Mapillary!

Ovviamente ora si può migliorare aggiungendo immagini di dettaglio dei punti di maggiore interesse e chiunque, oltre me, lo può e lo potrà fare ed il sistema migliorerà da solo.

Qualche dettaglio / suggerimento per chi volesse replicare:

  • cercate, nell’ambito del possibile, di evitare di fotografare persone, targhe automobilistiche, ecc … In ogni caso, una volta fatto l’upload delle immagini su Mapillary, è possibile controllare le proprie immagini ed intervenire, in post processing, editando le immagini “oscurando” (blur) parti di esse per rendere irriconoscibili volti, targhe automobilistiche, particolari, ecc … (Mapillary cerca di farlo per voi in modo automatico ma è comunque sempre consigliabile una verifica ed un intervento umano ….)
  • cercate, nell’ambito del possibile, di catturare immagini o sequenze in orari con poco affollamento
  • percorrete le vie in entrambi i sensi di marcia: se possibile anche catturando immagini lateralmente quindi l’optimum sarebbe percorrere una via in entrambi i sensi di di marcia catturando immagini frontalmente e poi, nuovamente percorrere la via catturando immagini letaralmente. Ecco un esempio di quello che si ottiene ruotando a 360° sullo stesso punto

DettaglioCarignano1

DettaglioCarignano2

DettaglioCarignano3

DettaglioCarignano4

Particolare interessante presente da qualche mese, è che Mapillary prova a riconoscere i vari segnali stradali presenti sulle vostre fotografie, permettendo così di avere la georeferenziazione degli stessi (ovviamente è bene anche qui fare un post processing per verificare la bontà del riconoscimento automatico

CarignanoSegnali

Al netto di iniziative “singole” come la mia, fattibili su aree mediamente piccole, Mapillary si presta a “eventi” di mapping di gruppo, ad esempio quello recentemente svoltosi presso l’area archeologica di Pompei che ha permesso di portare sulla piattaforma le immagini georiferite di una parte significativa dell’area archeologica visitabile.

L’esperienza è quindi replicabile presso altre aree di particolare interesse.

Lo stesso principio di potrebbe applicare coinvolgendo le scuole, magari nei periodi delle loro gite scolatische o in aree di particolare interesse che si voglia valorizzare coinvogendo gli studenti, magari associandovi dei meccanismi di “gaming” per rendere la cosa più interessante.

Da provare ……. in fondo basta veramente poco!

We want also the National Catalogue for Spatial Data in the INSPIRE register!


A year and a half ago (precisely on February 11, 2014), based on the initiative of some Italian geomatics fans, a petition was launched to denounce the lack of presence of Italy in the INSPIRE register: italy4INSPIRE

rndt4inspire

This initiative, which has rapidly gathered more than 100 adhesions, not only by individuals but also by associations and communities of the geomatics sector, aimed to highlight the fact that it was enough just a grain of sand in the cumbersome and infernal bureaucratic machine (the failure to send an email by ghost responsible), to make yet another fool to our country in the international arena.

And the joke was that Italy had already fulfilled so far the sectoral legislation creating the “National Catalogue for Spatial Data” ( RNDT – Repertorio Nazionale dei Dati Territoriali) and therefore could even get us to the finish among the first in Europe. Among other things, the initiative was echoed in two parliamentary questions, submitted on 4 July and 12 November 2014 , remained unanswered.

What happened after so long?

That we took the usual “patch” Italian style, registering the only endpoint of the National Geoportal (which counts a few hundred metadata), continuing to ignore RNDT, despite European law allows each Member State to present more than one National access point.

Why?

Since the Decree 32/2010 (which transposed the INSPIRE Directive in Italy) refers explicitly to RNDT and that this, in consistency and continuity with the monitoring “envisaged by the Commission” 2014, signaled 6140 metadata and in 2015 more than 17940 metadata are reported, why it was not registered yet?

We wonder if someone with a name, a surname and a face, will never give an answer and, above all, will manage to achieve the goal of registering the RNDT endpoint in the INSPIRE register.

RNDT4INSPIRE_h

Categorie:Uncategorized Tag:,