• Non ci sono risultati.

Capitolo 5: Realizzazione

N/A
N/A
Protected

Academic year: 2021

Condividi "Capitolo 5: Realizzazione"

Copied!
28
0
0

Testo completo

(1)

29

Capitolo 5: Realizzazione

Nel primo paragrafo di questo capitolo si descrivono gli aspetti più importanti dei dati originali per realizzare il progetto.

Poi vengono descritti gli aspetti più importanti della fase di caricamento dei dati e configurazione del database su PostgreSQL. Inoltre per molte delle sue entità sono state create con Laravel le classi Model per facilitare le operazioni di estrazione dei dati.

I paragrafi successivi sono dedicati alla descrizione dei metodi creati in due classi, MapController e AjaxController, per estrarre i dati dal database, elaborarli e passarli alle viste create per l’applicativo: map.blade.php e bus_stops.blade.php.

Gli ultimi paragrafi del capitolo sono dedicati a queste due visualizzazioni che sono mappe web della Regione Toscana in cui vengono visualizzati alcuni dati presenti sul database.

5.1 Descrizione dei dati e delle sorgenti

Per il progetto sono stati ricercati e studiati dati provenienti da queste sorgenti pubbliche: 1. dati.toscana.it [33] - Sito della Regione Toscana, dove sono raccolti tutti i dati reperibili, dal quale sono stati scaricati le tabelle degli orari del trasporto pubblico, musei e luoghi dello spettacolo.

2. DatiOpen.it [34] - Lo scopo del progetto di questo sito è quello di cercare di dare una spinta decisa al movimento dei dati aperti, detti Open Data [35], in Italia. Sono stati scaricati i dataset dei monumenti e stazioni ferroviarie della Toscana.

3. Geonames [36] - un database geografico.

Invece la tabella contenente la lista delle scuole della Toscana è stata fornita dall’azienda Oimmei.

5.1.1 Le tabelle del trasporto pubblico locale

Il dataset contiene i dati previsionali relativi alle corse, agli orari e alle fermate del trasporto pubblico (treni, traghetti, tram, bus urbani ed extraurbani) in tutta la Regione Toscana. Sono

(2)

30

presenti 13 file, ognuno relativo ad una specifica modalità di trasporto pubblico e a determinate aziende esercenti il servizio. Me per il progetto sono stati utilizzati solo i GTFS [18] degli autobus e dei tram:

1. GEST.gtfs: servizio tramviario svolto da Gest S.p.A. sulla linea “Firenze - Scandicci”. 2. CTTNORDMASSACARRA.gtfs: servizio bus svolto da CTT Nord e CLUB sulle linee

urbane ed extraurbane di Massa Carrara.

3. CTTNORDLUCCA.gtfs: servizio bus svolto da CTT Nord e CLUB sulle linee urbane ed extraurbane di Lucca e Pistoia.

4. CTTNORDPISA.gtfs: servizio bus svolto da CTT Nord, SEQUI e 3MT sulle linee urbane ed extraurbane di Pisa.

5. CTTNORDLIVORNO.gtfs: servizio bus svolto da CTT Nord sulle linee urbane ed extraurbane di Livorno ed Isola d’Elba.

6. COPIT.gtfs: servizio bus svolto da COPIT sulle linee urbane ed extraurbane di Pistoia e Circondario Empolese.

7. CAP.gtfs: servizio bus svolto da CAP, TRASPORTI TOSCANI, PUCCIONI e RENIERI sulle linee urbane ed extraurbane di Prato, Circondario Empolese, Arezzo, Chianti, Valdarno Fiorentino, Mugello, Val di Sieve, Lucca e Pistoia.

8. ATAF.gtfs: servizio bus svolto da ATAF e LINEA sulle linee urbane dell’area metropolitana di Firenze.

9. BUSITALIA.gtfs: servizio bus svolto da BUSITALIA, AUTOLINEE TOSCANE, MAGHERINI, ALA e ALTERINI sulle linee extraurbane nel territorio del Chianti, Valdarno Fiorentino, Mugello, Valdisieve, Siena e Circondario Empolese.

10. TIEMME.gtfs: servizio bus svolto da TIEMME, BYBUS, FABBRI e BASCHETTI sulle linee urbane ed extraurbane di Arezzo, Grosseto, Piombino, Val di Cornia, Siena, Certaldo e San Gimignano.

Questi dataset sono stati scaricati dal sito il 23 luglio 2018 e coprono il periodo temporale dal 19 giugno 2018 al 31 dicembre 2018. Probabilmente quelli compresi tra giugno e luglio coincidono abbastanza fedelmente con i viaggi realmente effettuati dagli autobus in tale periodo, ma possono esserci delle differenze, nonostante il sito aggiorni i dataset abbastanza spesso.

(3)

31

Comunque i dati sono conformi alle General Transit Feed Specification Reference [18]. Ogni risorsa è un file in formato GTFS denominato con il nome dell’azienda di trasporto e contenente i seguenti file:

1. agency.txt: dati dell’azienda di trasporto.

2. stops.txt: elenco dei punti di salita/discesa dei passeggeri (fermate, stazioni, porti). 3. ruotes.txt: elenco delle linee di trasporto.

4. trips.txt: elenco delle corse per ogni linea di trasporto.

5. stop_times.txt: orari di arrivo e partenza ad ogni fermata di ciascuna corsa. 6. calendar.txt: calendario di servizio.

7. shapes.txt: elenco delle coordinate (latitudine, longitudine) per disegnare le corse su mappa.

Di seguito sono riportate le figure dei file .txt presenti all’interno dell’archivio di ATAF e alcune sono accompagnate da descrizione degli attributi più importanti per il progetto. La struttura è la stessa anche per le altre compagnie.

Figura 10: Il file agency.txt

(4)

32

Ogni service_id è un identificativo univoco di un insieme di date. Ogni coppia (service_id, date) può apparire solo una volta in calendar_dates.txt.

Figura 12: Il file routes.txt

In questo file, route_uid identifica univocamente un record del file. E’ presente anche l’identificativo della compagnia (agency_id) e il nome della linea (route_short_name).

Figura 13: Il file shapes.txt

Questo file contiene i percorsi fisici fatti da un veicolo. Ogni percorso shape è identificato dall’attributo shape_id e contiene una sequenza di coordinate geografiche. Le shapes appartengono alla tabella trips.

(5)

33

Questo file contiene gli orari di arrivo e partenza ad ogni fermata di ciascuna corsa e ha come chiavi esterne trip_id e stop_id; nessun singolo attributo è chiave primaria. L’arrival_time, espresso in HH: MM: SS, specifica l'orario di arrivo ad una fermata di una corsa di una linea. Il tempo viene misurato da "mezzogiorno meno 12 ore" (effettivamente a mezzanotte, eccetto per i giorni in cui si verificano i cambiamenti dell'ora legale) all'inizio del giorno di servizio. Per i tempi che si verificano dopo la mezzanotte del giorno di servizio, ma che si riferiscono ad uno stesso viaggio, l'ora ha come valore 24:MM:SS. Siccome il dataset non ha orari separati per l'arrivo e la partenza a una fermata, per entrambi gli attributi il valore è uguale.

Figura 15: Il file stops.txt

La tabella degli stops contiene l’elenco delle fermate della compagnia con i seguenti campi: un identificativo, il nome della fermata, le coordinate geografiche e un codice.

(6)

34

In questa tabella la chiave primaria è trip_id, invece le chiavi esterne sono route_id per le linee degli autobus della compagnia, service_id per il calendario e shape_id per il percorso fisico.

5.1.3 I dati dei luoghi d’interesse

Per il progetto sono stati utilizzati anche i dati forniti dal database geografico di GeoNames che contiene oltre 25 milioni di nomi geografici, almeno 11 milioni di caratteristiche uniche di 4,8 milioni di posti popolati e 13 milioni di nomi alternativi [36]. Il progetto di GeoNames prevedere di integrare i dati geografici come nomi di luoghi in varie lingue, elevazione, popolazione e altri da varie fonti. Le coordinate di latitudine e longitudine sono in WGS84 (World Geodetic System 1984) [16].

Geonames copre tutti i paesi, per ognuno dei quali è presente un file di testo con estensione .txt. Riportiamo la descrizione dei campi di un file presente sul sito:

«geonameid : integer id of record in geonames database name: name of geographical point (utf8) varchar(200)

asciiname : name of geographical point in plain ascii characters, varchar(200) alternatenames: alternatenames, comma separated, ascii names automatically transliterated, convenience attribute from alternatename table, varchar(10000) latitude: latitude in decimal degrees (wgs84)

longitude : longitude in decimal degrees (wgs84)

feature class: see http://www.geonames.org/export/codes.html, char(1) feature code: see http://www.geonames.org/export/codes.html, varchar(10) country code: ISO-3166 2-letter country code, 2 characters

cc2: alternate country codes, comma separated, ISO-3166 2-letter country code, 200 characters+

admin1 code: fipscode (subject to change to iso code), see exceptions below, see file admin1Codes.txt for display names of this code; varchar(20)

admin2 code: code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)

(7)

35

admin4 code: code for fourth level administrative division, varchar(20) population: bigint (8 byte int)

elevatio: in meters, integer

dem: digital elevation model, srtm3 or gtopo30, average elevation of 3''x3'' (ca 90mx90m) or 30''x30'' (ca 900mx900m) area in meters, integer. srtm processed by cgiar/ciat.

timezone : the iana timezone id (see file timeZone.txt) varchar(40) modification date: date of last modification in yyyy-MM-dd format»

I più importanti per la realizzazione del progetto sono stati: la chiave primaria (geonameid), il nome del punto geografico (name), le coordinate geografiche, feature_class e feature code. L’attributo feature_class categorizza i luoghi presenti su GeoNames in queste categorie:

 A: paese, stato, regione, ecc.;  H: fiumi, laghi, ecc.;

 L: parchi, ecc.;  P: città, villaggi, ecc.;  R: strade, autostrade, ecc.;  S: costruzione, fattoria, ecc.;  T: montagna, collina, roccia, ecc.;  U: undersea;

 V: foresta, brughiera, ecc.;

Ognuna di queste presenta a sua volta delle sottocategorie all’interno dell’attributo feature_code. Per esempio, la feature_class S presenta nel feature_code i codici STDM per gli stadi, SQR per le piazze e molti altri. Si preferisce non fornire un elenco delle modalità dell’attributo feature_code perché sono moltissime, ma è stato importante per selezionare i luoghi d’interesse visualizzabili nella mappa dell’applicativo.

Per il progetto è stato scelto il file riguardante l’Italia, ma nel database è stata caricata solo la parte contenete i luoghi della Toscana.

(8)

36

Figura 17: Archivio delle strutture ricettive

La figura sopra si riferisce al file di testo con estensione .csv e contiene i nomi e i dati anagrafici (indirizzo, telefono, e-mail, sito web) di tutte le strutture ricettive della Toscana, codificate secondo i codici ISTAT [37] e distinte per tipologia (alberghi, agriturismi, ...) e stabilimenti balneari. Gli attributi più utilizzati per il progetto sono stati la chiave primaria id, le coordinate geografiche, il nome e la tipologia.

Figura 18: I luoghi dello spettacolo della regione

Anche i dati relativi ai luoghi dello spettacolo (teatri, cinema, ed altro) toscani sono stati scaricati dal sito dati.toscana.it [20]. La figura sopra mostra un’anteprima del file originale.

(9)

37

Figura 19: Elenco dei musei della Toscana

Un altro dataset interessante offerto da dati.toscana.it è l’elenco dei musei e nella figura sopra è mostrata un’anteprima. Contiene diversi campi come le coordinate geografiche e la categoria.

Altri dataset sono stati scaricati dal sito DatiOpen.it che mette a disposizione diversi archivi. Per il progetto sono stati presi in considerazione la sorgente che contiene l'elenco, non esaustivo, di oltre 3000 stazioni ferroviarie e i dati relativi a 5000 monumenti e sacrari, nel territorio italiano. Ma sono stati scaricati solo i dati relativi alla regione Toscana come mostrano le figure sotto.

(10)

38

Figura 21: Elenco di alcuni monumenti e sacrari della regione Toscana

5.2 Caricamento e trasformazione dei dataset sul database

Dopo aver scaricato i file elencati nella sezione precedente, è stato creato e configurato un database su PostgreSQL [11] dove sono stati importati tutti i file, alcuni dei quali “puliti” da errori di formattazione. Durante l’importazione, è stato necessario impostare i tipi degli attributi che formeranno le tabelle, ad esempio per l’attributo lat delle strutture ricettive della regione è stato impostato il tipo double precision.

Per ogni dataset che presentava gli attributi relativi alle coordinate geografiche, latitudine e longitudine, sono state eseguite due query per aggiungere e riempire la colonna geom contenente la geometria delle coordinate del luogo. Inoltre sono state fatte operazioni importanti alle tabelle della scuola e degli autobus.

Il file del dataset delle scuole non aveva le coordinate geografiche, ma solo gli indirizzi. Perciò, dopo aver creato l’attributo geom di tipo geometry nella tabella sul database, è stata creata una Route con Laravel alla quale è stato associato il metodo school_coordinate del MapboxAPIController di cui si riporta il frammento di codice:

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB; use Illuminate\Http\Request;

(11)

39

use Illuminate\Support\Facades\View;

class MapboxAPIController extends Controller { public function school_coordinate() {

$schools=DB::table('toscana_scuole')->get();

if($schools == null) {//se la query NON ha restituito niente throw new NotFoundHttpException();

} else {

foreach($schools as $school) {

if(isset($school->Indirizzo) && ($school->Indirizzo) ) {

$id_adress=$school->Indirizzo ." ". $school->Comune ." ". $school->provincia; $url="api.mapbox.com/geocoding/v5/mapbox.places/%s.json?language=it&types=region,pos tcode,place,address&country=it&access_token=pk.eyJ1IjoiYWdpc3BhIiwiYSI6ImNqYXc1eHMyM TVwajcyd25xcmo1ejZleGwifQ.KBjt8t1gADlGqhzq5reAyg";

$url = sprintf($url, urlencode($id_adress)); $ch = curl_init();

curl_setopt($ch, CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_ENCODING, "UTF-8"); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); ini_set('max_execution_time', '300'); $curl_response=curl_exec($ch); curl_close($ch); if($curl_response === false) { echo "No response"."<br>";

echo 'Curl error: ' . curl_error($ch); } else { $obj=json_decode($curl_response); if(array_key_exists(0, $obj->features)) { $coord=$obj->features[0]->center;

(12)

40

//affected indica il numero di record aggiornati dall'interrogazione $affected=DB::update("

update toscana_scuole

set geom=ST_SetSRID(ST_Point($coord[0], $coord[1]), 4326)

where codice=?", [$school->codice] ); } } } } } }

Frammento 3: Aggiornamento del campo geom della tabella delle scuole

Evitando di scendere nei dettagli delle funzioni di PHP, prima di tutto il metodo recupera i record della tabella delle scuole salvandoli nella variabile $schools e mappandoli come oggetti PHP. Poi, con il ciclo foreach, per ogni record vengono determinate le coordinate geografiche in base all’indirizzo della scuola connettendosi al servizio Mapbox Geocoing API [28].

Sapendo le coordinate di una scuola, viene calcolata la geometry e aggiornato il relativo attributo sul database con il metodo update della classe DB di Laravel. Purtroppo per diverse scuole non è stato possibile determinare le loro coordinate per riempire il campo della geometria perché non avevano l’indirizzo. Per altre invece le coordinate non sono state calcolate correttamente, ad esempio perché l’indirizzo era sbagliato oppure mancava il numero civico.

Invece per quanto riguarda i dataset del trasporto pubblico locale, per ognuna delle dieci compagnie della regione sono stati importati sul databaset i file

del calendario degli autobus, delle linee, delle fermate, delle corse, degli orari di salita e discesa dalle fermate e shapes.txt, cioè 60 file di testo. Siccome erano troppi, per facilitare la scrittura del codice relativo alle operazioni di estrazione, è stato preferito unirle, con delle interrogazioni in linguaggio SQL [19], ottenendo le tabelle elencate nella Figura 22.

Figura 22: Le tabelle del trasporto pubblico locale

(13)

41

L’unione delle tabelle del trasporto pubbliclo locale ha presentato delle difficoltà perché era possibile che alcuni record di tabelle dello stesso tipo, ma appartenenti a diverse compagnie, avessero gli stessi valori di chiave primaria. Perciò è stato necessario creare nuovi attributi come chiavi primarie e chiave esterne nelle tabelle agency, stops, routes, stop_times e trips.

Per esempio, dopo aver creato nella tabella agency l’attributo agency_uid (unique identifer) come chiave prima di tipo intero, sono state unite le tabelle stops di tutte le compagnie ottenendo alla tabella all_stops. A quest’ultima è stato aggiungo l’attributo stop_uid come chiave primaria di tipo intero perché era possibile che il valore di stop_id di diverse compagnie fosse lo stesso. Quindi è stata creata e aggiornata una nuova chiave esterna nelle tabelle stop_time delle varie compagnie attraverso query come queste:

alter table ataf_stop_times add column stop_uid integer; update ataf_stop_times as a

set stop_uid = t.stop_uid from all_stops as t

where t.agency_uid = 1 and t.stop_id = a.stop_id;

dove viene aggiunto il nuovo attributo stop_uid nella tabella stop_times della compagnia ATAF, avente 1 come identificativo agency_uid, quindi viene fatto il suo update prendendo i valori dalla tabella all_stops.

La tabella all_shapes_pt_seq nasce dall’unione delle tabelle shapes delle varie compagnie e da questa è stata generata la tabella all_shapes_geom della quale si riporta un’anteprima nella figura a fianco. In essa sono presenti gli attributi shapes_id, come chiave primaria, e geom contenente la geometria della sequenza di punti della

(14)

42

shapes. In questo modo è stato possibile, con funzioni e metodi su Laravel, convertire la geometria in GeoJSON e disegnarla sulla mappa.

La tabella all_sections è stata popolata con un comando Artisan creato nel file

PopulateSections.php e ogni suo record rappresenta una sezione di una corsa di una linea degli

autobus compresa tra due fermate. Infatti, oltre alla chiave primaria id, la tabella contiene gli attributi stop_uid_start e stop_uid_end per identificare le fermate d’inizio e di fine di una sezione. Altri attributi sono la shape_sequence che rappresenta l’ordine della sezione in una corsa, la geometria della sezione e le coordinate delle fermate d’inizio e di fine. Questa tabella è stata realizzata per rendere abbastanza rapide e affidabili alcune funzionalità dell’applicativo.

Figura 24: La tabella all_sections

5.3 I Model delle tabelle

Dopo aver popolato il database, sono state create delle classi PHP sul framework Laravel chiamate Model (estensione della classe Eloquent) per ogni tabella da cui si vuole estrarre i dati. L’elenco completo delle classi, all’interno della cartella app, è mostrato nella Figura 24. I Model consentono di interagire con il database su PostgreSQL. Si preferisce non analizzare tutti i Model creati perché presentano la stessa struttura; comunque si fornisce una breve descrizione di qualcuno di essi per cercare di spiegare le potenzialità di questo strumento di Laravel.

(15)

43

Dalla figura si nota che sono state create le classi per tutte le tabelle del trasporto pubblico locale: Agency per la tabella agency,

CalendarDate per la tabella all_calendar, PointShape per la tabella

all_shapes_pt_seq, Route per la tabella all_routes, Section per la tabella all_sections, Shape per la tabella all_shapes_geom, Stop per la tabella all_stops, StopTime per la tabella all_stop_times e Trip per la tabella all_trips. Queste tabelle sono in relazione l’una con l’altra, per esempio tra all_routes e all_trips c’è una relazione uno a molti perché una linea o route può essere associata a diverse corse o trip.

Il componente Eloquent permette di definire dei metodi all’interno dei Model per i vari tipi di relazioni. Prendiamo come esempio, la classe Route in questo frammento di codice:

class Route extends Model

{ protected $table = 'all_routes'; protected $primaryKey = 'route_uid'; public $timestamps = false;

public function trips() {

return $this->hasMany('App\Trip', 'route_uid'); }

public function stops() {

return $this->belongsToMany('App\Stop', 'pivot_stops_routes', 'route_uid', 'stop_uid');

} }

Frammento 4: La classe Route e i suoi metodi

Le prime due proprietà della classe Route sono il nome della tabella e la chiave primaria, poi viene definita la funzione trips che chiama il metodo hasMany e restituisce il suo risultato. Come suggerisce il nome, serve per definire una relazione uno a molti e ha tre parametri: il nome del Model con cui Route deve essere in relazione, la chiave esterna e la chiave primaria. Se si volesse recuperare i record delle corse associate a una linea, avente uno specifico route_id, basterebbe questo comando in PHP:

Figura 25:I Model delle tabelle

(16)

44

$trips = Route::find($route_uid)->trips;

Le proprietà dinamiche di Eloquent consentono di accedere ai metodi di relazione come se fossero proprietà definite sul Model. Ma è possibile definire anche la relazione inversa creando questo metodo all’interno della classe Trips:

public function route(){

return $this->belongsTo('App\Route', 'route_uid', 'route_uid');

}

Invocando route a una specifica corsa o trip è possibile recuperare i dati della linea associata a tale corsa nella tabella all_routes.

Invece il metodo belongsToMany, presente all’interno della funzione stops del Frammento 4, serve per definire una relazione molti a molti tra le entità all_routes e all_stops. Infatti, una linea può passare da più fermate e una fermata può essere servita da più linee. Il metodo ha come primo parametro la tabella pivot_stops_routes, creata nel database, che contiene le coppie di chiavi primarie delle due tabelle. Invocando il metodo stops è possibile recuperare le linee che servono una fermata.

Anche per le altre tabelle del trasporto pubblico locale sono state definite le relazioni con i rispettivi metodi di Eloquent; invece per quelle relative ai luoghi d’interesse è stato sufficiente definire solamente le proprietà nelle rispettive classi, come il nome della tabella e l’attributo chiave primaria, perché non erano in relazione con nessun’altra tabella.

5.4 Il comando PopulateSections

E’ una classe che estende la superclasse Command di Laravel dove vanno definite, come proprietà, il nome del comando e la sua descrizione. Il metodo handle viene chiamato quando il comando viene eseguito, allora vengono estratti dei dati dal datset con una query e viene invocato il metodo createSections che, dopo averli elaborati, li inserisce nella tabella all_sections. Anche createSections è stato definito dentro la classe PopulateSectionis insieme

(17)

45

ad altri metodi necessari per definire le geometrie delle sezioni che popoleranno la tabella. Verrà descritto brevemente il suo funzionamento senza dilungarci troppo sui dettagli.

Si riporta il codice di handle: public function handle(){

date_default_timezone_set("Europe/Rome"); $this->info("Inizio alle ". date("h:i:s"));

Trip::with(['stoptimes' => function ($query) {

$query->select('trip_uid', 'stop_uid', 'stopsequence') ->orderBy('stopsequence', 'asc');

},

'stoptimes.stop:stop_uid,stop_lat,stop_lon,stop_name', 'pointshapes' => function ($query) {

$query->select('shape_id', 'shape_pt_lat', 'shape_pt_lon', 'shape_pt_sequence') ->orderBy('shape_pt_sequence', 'asc'); } ]) ->chunkById(60, function($trips) { self::createSections($trips); }, 'trip_uid');

$this->info("Ora di fine: " . date("h:i:s")); }

Frammento 5: Il metodo handle del comando PopulateSections

Dopo aver impostato il fuso orario, viene invocato il metodo info che visualizza sulla console l’ora in cui viene eseguito il comando. Questo metodo viene invocato anche a fine esecuzione per stimare il suo tempo di esecuzione.

Viene eseguita una query che utilizza il Model Trip e il metodo with per caricare le relazioni

stoptime, stop e pointshapes. La notazione stoptime.stop permette di concatenare queste due

relazioni, cioè per ogni fermata appartenente alla sequenza di una corsa avremo i suoi dati: l’identificativo della fermata, il suo nome e le sue coordinate geografiche. Le relazioni stoptime e pointshapes presentano delle funzioni anonime PHP con le quali si specifica di selezionare col metodo select solo alcuni attributi e di ordinare le sequenze col metodo orderBy.

(18)

46

Questa query crea una struttura dati interessante in cui ogni corsa è mappata come un oggetto dentro al quale le proprietà stoptimes e pointshapes sono array di oggetti, contenenti rispettivamente la sequenza ordinata delle fermate (stoptimes) e la sequenza ordinata dei punti del percorso (pointshapes) associati a una corsa. A sua volta ogni fermata avrà associata, sotto la proprietà stop¸ i suoi dati precedentemente elencati.

Siccome la query lavora su dataset con milioni di record, è stato utilizzato il metodo

chunkById che recupera una piccola porzione di risultati alla volta, in questo caso 60, in base

alla chiave primaria trip_uid della relazione e poi gli passa a una chiusura che invoca il metodo

createSections. Quest’ultimo, per ogni corsa della porzione, legge le sequenze ordinate

stoptimes e pointshapes per costruire le diverse geometrie delle sezioni comprese tra due fermate, le quali vengono mappate come GeoJSON di tipo LineString [25]. Una volta che è stata costruita la sezione, viene inserita nella tabella all_sections insieme ad altri dati come gli identificativi della corsa, della fermata d’inizio sezione e della fermata di fine sezione.

5.5 Il MapController

Questa classe di Laravel contiene tutti i metodi riguardanti la view principale dell’applicativo chiamata map. In essa è definito il metodo queries che effettua, attraverso delle interrogazioni al database, diverse operazioni di estrazione di dati utili per visualizzare i luoghi d’interesse sulla mappa e restituisce la vista map insieme a un array con i risultati delle precedenti interrogazioni. Si riporta il frammento di codice contenente una sezione del metodo:

$parks = Geoname::query()

->select('name as nome', 'admin2_code as Provincia', 'geonameid as id', 'lat', 'lon')

->whereIn('feature_code', ['AMUS', 'PRK', 'RESN', 'GDN']) ->get();

$sport = Geoname::query()

->select('name as nome', 'admin2_code as Provincia', 'geonameid as id', 'lat', 'lon')

(19)

47

if ($schools != null && $places != null && $mus != null && $monuments != null && $ports != null && $shows != null && $stations != null && $strutt_ric != null) {

$data = array( 'schools' => $schools, 'places' => $places, 'mus' => $mus, 'monuments' => $monuments, 'airports' => $airports, 'ports' => $ports, 'shows' => $shows, 'stations' => $stations, 'strutt_ric' => $strutt_ric, 'fortscastles' => $fortscastles, 'histSites' => $histSites, 'parks' => $parks, 'sport' => $sport );

return view('map', $data);

} else { throw new NotFoundHttpException(); }

Frammento 6: Sezione di codice del metodo queries

Le variabili $parks e $sports memorizzano i risultati di due interrogazioni alla tabella Geonames, cioè alcuni parchi e luoghi sportivi della regione. Il metodo whereIn del query builder di Laravel verifica che i valori di un attributo, in questo caso feature_code, siano uguali a dei valori dentro un array dati come parametri al metodo. Quindi è un metodo che realizza gli stessi effetti di un particolare tipo di espressioni condizionali della clausola WHERE dell’SQL [19].

Si preferisce non mostrare tutte le interrogazioni fatte nel metodo perché sono abbastanza simili. I loro risultati vengono memorizzati nell’array associativo di PHP chiamato $data per poi essere passati alla vista map. Se però le interrogazioni non riescono a recuperare alcun dato, allora viene lanciata l’eccezione chiamata NotFoundHttpException, una classe di Symfony che gestisce gli errori HTTP [10] di risorsa non trovata. In questo caso l’utente sarà indirizzato a una pagina web che lo informa di non aver trovato alcuna risorsa.

Gli ultimi due metodi del MapController sono find_point_of_interest e nearest_stops. Il primo riceve come parametri la tipologia (es. scuola, museo) e l’identificativo del punto

(20)

48

d’interesse; ha lo scopo di restituire la vista chiamata bus_stops dove sarà visualizzata una mappa con il marker del punto circondato, se ci sono, dalle fermate dell’autobus entro un raggio di 500 metri. Il suo funzionamento può essere riassunto in queste fasi:

1. Interrogazione del database per cercare i dati del punto d’interesse, scelto dall’utente, sapendo la sua tipologia e il suo identificativo. La tipologia del luogo d’interesse serve per sapere quale tabella del database interrogare, invece l’identificativo serve per recuperare il record giusto.

2. Sapendo i dati geografici del punto, in particolare la sua geometria, viene invocato il metodo

nearest_stops per cercare nelle tabelle del trasporto pubblico locale i dati delle fermate e

linee dell’autobus più vicine.

3. Restituzione della vista bus_stops passando i dati del punto d’interesse e delle fermate più vicine.

public function nearest_stops($geometry) { $stops = Stop::with('routes')

->selectRaw('stop_uid, stop_id, stop_name, stop_lat, stop_lon , st_distance(st_transform( ?::geometry, 3003), st_transform(geom, 3003)) as

distance', [$geometry])

->whereRaw('st_dwithin( geography(?::geometry), geography(geom), 500)', [$geometry]) ->get(); foreach($stops as $stop) { $stop->url_all_routes = url("/all_routes/{$stop->stop_uid}"); foreach($stop->routes as $route) { $route->url_route = url("/shapes_of_route/{$route->route_uid}"); $route->url_sections = url("/sections/{$stop->stop_uid}/{$route->route_uid}"); } } return $stops; }

(21)

49

Leggendo il frammento si nota che nearest_stops riceve come parametro $geometry, cioè la geometria delle coordinate geografiche del punto d’interesse recuperata dal metodo

find_point_of_interest in seguito all’interrogazione al database. Inizialmente viene eseguita una

query e il suo risultato viene salvato dentro la variabile $stops. La query, che sfrutta la classe

Stop e il query builder di Laravel, restituisce i dati (nome, coordinate e identificativo) delle

fermate dell’autobus, presenti nella tabella all_stops del database, che distano dal punto d’interesse meno di 500 metri. Per ogni fermata vengono restituiti anche i dati, presenti nella tabella all_routes, delle linee che la servono. Questo avviene utilizzando il metodo with di Eloquent che riceve come parametro il metodo routes, racchiuso tra apici, presente all’interno della classe Stop per definire la relazione molti a molti tra le entità all_stops e all_routes.

La query chiama due funzioni di PostGIS abbastanza interessanti:

st_distance: riceve come parametri i valori delle due geometrie della fermata e del luogo

d’interesse, che vengono trasformate con la funzione st_transform nel tipo geografia di PostGIS [17], e restituisce la distanza minima sferoidale [16] tra queste aree geografiche in metri;

st_dwithin: riceve come parametri i valori delle geometrie della fermata e del luogo

d’interesse, che vengono trasformate con la funzione geography nel tipo geografia di PostGIS [17], e una distanza in metri. Quindi restituisce true se i due punti sono a una distanza minore o uguale a 500 metri, false altrimenti.

Poi vengono eseguiti due cicli foreach annidati utilizzando i risultati salvati in $stops perché Eloquent, essendo un Object-Relational Mapping (ORM) [13], permette di mappare i record di un database in oggetti PHP. Per ogni fermata e per ogni linea vengono aggiunti degli URL che indirizzano ai percorsi registrati nel file web.php associati ai metodi presenti nell’AjaxController.

Infine, nearest_stops restituisce i risultati trovati.

5.6 L’AjaxController

Come suggerisce il nome, questa classe contiene i metodi riguardanti richieste HTTP in AJAX. Infatti, nella vista bus_stop.blade.php, la cui pagina web s’intitola “Fermate e percorsi

(22)

50

delle linee”, i popup delle fermate contengono dei pulsanti che, se premuti dall’utente, lanciano richieste HTTP che eseguono i metodi dell’AjaxController descritti in questo paragrafo.

Il primo metodo creato in questa classe è obtainShapes di cui si riporta un frammento di codice:

public function obtainShapes($route_uid) { $result = Shape::query()

->selectRaw('shape_id, length_shape, st_asgeojson(line_geom) as geojson') ->whereRaw('route_uid_fk = ?', [$route_uid])

->get();

if (isset($result) && $result && count($result) != 0) { return ['result' => $result, 'control' => 1]; } else {

return ['result' => null, 'control' => 0]; }

}

Frammento 8: Il metodo obtainShapes

Questa funzione prende come parametro l’identificativo di una linea ed esegue una query per il Model Shape, associato alla tabella all_shapes_geom. La query:

 recupera tutte le geometrie dei percorsi associati alla linea;

 converte le geometrie nel tipo GeoJSON di tipo LineString [25] con la funzione st_geojson di PostGIS.

Se la query ha restituito qualcosa, allora il suo risultato viene memorizzato nella variabile

$result e restituito in un array associativo insieme alla chiave control con valore uguale a 1 alla

vista bus_stops. Altrimenti la vista riceverà una chiave uguale a zero per lanciare un messaggio di allerta all’utente che l’avvisa di non aver trovato alcun percorso nel database.

Un altro metodo di questo Controller è getSections di cui si riporta il frammento di codice:

public function getSections($stop_uid, $route_uid) { date_default_timezone_set("Europe/Rome"); $hourTime = (int)date("H");

$tripsIds = StopTime::query() ->select('trip_uid') ->where([

(23)

51

['stop_uid', '=', $stop_uid], ['route_uid', '=', $route_uid], ['hour_arrival', '=', $hourTime] ])

->whereHas('calendars', function ($query) { $today = date("Y-m-d"); $query->where('date', '=', $today); }) ->distinct() ->get(); $sections = Section::query()

->selectRaw('stop_uid_start, stop_uid_end, start_lat, start_lon, end_lat, end_lon, ST_AsGeoJSON(geom), count(*) as freq') ->whereIn('trip_uid', $tripsIds)

->groupBy('stop_uid_start', 'stop_uid_end', 'start_lat', 'start_lon', 'end_lat', 'end_lon', 'geom') ->get();

if (isset($sections) && $sections) { if(count($sections) == 0) { return ['control' => 0]; } else {

return['sections' => $sections, 'control' => 1, 'tripIds' => $tripsIds]; } } else { return ['control' => 0]; } }

Frammento 9: Il metodo getSections

Esso deve restituire le sezioni, comprese tra due fermate, delle corse effettuate da una certa linea che passa per una certa fermata nell’arco dell’ora del giorno in cui l’utente clicca su un pulsante che fa partire la chiamata di questo metodo. A ognuna delle sezioni è associato il numero di volte che la linea passa per il tratto compreso tra le fermate in questo arco temporale, cioè il numero delle corse. Le sezioni di tutti i viaggi sono presenti nella tabella all_sections.

Il metodo prende come parametri gli identificativi della fermata e della linea, rispettivamente $stop_uid e $route_uid.

(24)

52

Dopo aver impostato il fuso orario, viene eseguita una query sfruttando il Model StopTime che recupera gli identificativi delle corse (trip_uid) di una linea che passano per uno stop ad una certa ora di un certo giorno. Quest’ultimi coincidono con il momento temporale in cui viene invocato questo metodo. Questi identificativi sono usati nel metodo whereIn dell’ultima query per recuperare le sezioni e calcolare le loro frequenze con la funzione di aggregazione count.

Prima di restituire i risultati dell’interrogazione viene fatto un controllo perché è possibile che certe linee in certi momenti temporali non passino per una fermata. Se la variabile $trip non contiene alcun risultato sarà restituito un array associativo con la chiave control uguale a zero, altrimenti avrà valore 1 e alla vista saranno restituiti i risultati delle precedenti interrogazioni.

Altri metodi di questa classe, il cui codice si omette perché abbastanza simile ai precedenti, sono i seguenti:

 drawPoints: deve restituire le coordinate delle fermate e dei punti dei percorsi delle corse effettuate da una certa linea che, nell’arco dell’ora del giorno in cui l’utente clicca sul pulsante nella pagina “Fermate e percorsi delle linee”, passa e si ferma su una fermata;  getAllRoutes: deve restituire le sezioni e le frequenze dei percorsi di tutte le linee che

nell’arco di una fascia temporale, cioè l’ora in cui viene invocato questo metodo, passano per una determinata fermata.

5.6 Le visualizzazioni

Il codice sorgente in HTML e Javascript, delle due visualizzazioni, è contenuto nei seguenti file:

 map.blade.php per la mappa dei luoghi d’interesse della Toscana. Appena viene caricata la pagina web, è visibile la mappa della regione con in alto a destra un checkbox con varie categorie di luoghi visualizzabili sulla mappa;

 bus_stop.blade.php per la mappa del luogo d’interesse circondato dalle fermate entro 500 metri, ognuna delle quali ha un popup con dei bottoni che permettono di disegnare le corse su mappa.

(25)

53

5.6.1 La mappa dei luoghi d’interesse

Figura 26: La mappa dei punti d'interesse della Toscana

La figura mostra la zona della Toscana in provincia di Pisa, ma ovviamente è possibile visualizzare altre zone della regione perché è una mappa interattiva. Le icone gialle rappresentano le scuole, invece i cerchi verdi e gialli con all’interno i numeri sono i Marker

Cluster [29] che contengono i marker dei luoghi d’interesse. Aumentando il livello dello zoom,

i Marker Custer tendono a sparire e al loro posto vengono disegnati sulla mappa un numero di marker pari al numero dentro i cerchi.

Si notano anche le stazioni ferroviarie rappresentate dalle icone blu, infatti ogni tipologia di luogo d’interesse ha un marker distinto per facilitare la sua individuazione sulla mappa.

Nella figura, in basso a sinistra, è presente la scala grafica di default di Leaflet con all’interno la misura della distanza. Questa misura varia ed è correlata al livello dello zoom; in questo modo rappresenterà sempre la distanza reale tra i punti della mappa indipendentemente dallo zoom.

(26)

54

Cliccando su un marker, si apre un popup con diverse informazioni del luogo d’interesse. Nella figura è aperto il popup di una scuola dove sono elencati il nome dell’istituto, la tipologia, le coordinate geografiche e l’identificativo nel database. Importante è il link blu perché permette di aprire una nuova pagina web.

In alto a destra della figura è presente un checkbox, presente nella libreria di Leaflet e chiamato Layer Control, contenente le tipologie di luoghi d’interesse visualizzabili sulla mappa: 1. places. Questa categoria contiene i luoghi d’interesse appartenenti al database geografico

GeoNames, più precisamente le città, paesi e villaggi o altri agglomerati di edifici in cui le persone vivono e lavorano. Sono stati inseriti come Layer visualizzabile nella mappa perché è interessante scoprire se eventuali paesi o villaggi fossero raggiungibili da autobus extraurbani dalle città limitrofe.

2. musei. E’ stato deciso di inserirli in questa mappa dei luoghi d’interesse perché sono punti

d’attrazione della domanda di mobilità a livello locale. Quindi è interessante scoprire se sono abbastanza accessibili con gli autobus.

3. statue o strutture commemorative.

4. forti e castelli. Sono stati inseriti nella mappa perché alcuni di questi luoghi sono

importanti da un punto di vista turistico.

5. luoghi storici. Questa categoria contiene chiese storiche e luoghi monumentali come ad

esempio la Basilica di Santa Maria del Fiore e la Torre di Pisa. 6. parchi e giardini pubblici.

7. luoghi dello spettacolo.

8. strutture ricettive.

9. sport. Contiene un elenco non esaustivo di luoghi sportivi della regione come i campi

d’atletica.

10. scuole. Layer che contiene un elenco ricco, ma non esaustivo, di scuole primarie, medie e superiori della regione.

11. aeroporti. Contiene i luoghi con piste di decollo per aeromobili come l’aeroporto di Pisa o l’Aereo Club di Arezzo.

12. porti. Layer con alcuni porti per l’approdo o l’ormeggio di navi o altre imbarcazioni, come il porto Mediceo di Livorno o il porto a Marina di Pisa.

(27)

55

13. stazioni ferroviarie.

Sono state scelte tali categorie perché raggruppano punti d’attrazione della domanda di mobilità, cioè un cittadino potrebbe volerli raggiungere utilizzando l’autobus. La sua propensione a utilizzare il trasporto pubblico sarà tanto maggiore quanto il luogo d’interesse è favorevolmente accessibile con questo mezzo. Si ritiene che questa accessibilità territoriale possa essere valutata tramite la pagina web che viene descritta nella sezione seguente.

5.6.2 La mappa delle fermate e percorsi delle linee

Figura 27: La mappa delle fermate e percorsi degli autobus

La figura sopra mostra la mappa di un punto d’interesse con il marker nero, la Basilica di Santa Maria Novella di Firenze, circondato dalle fermate dell’autobus entro 500 metri.

Il punto è circondato anche da diversi cerchi colorati con diverse graduazioni di rosso e la differenza tra i loro raggi è multipla di 125 metri. Quindi il cerchio centrale, più piccolo rispetto agli altri, ha un raggio di 125 metri e quello più grosso ha un raggio di 500 metri. In questo modo è possibile individuare diversi gruppi di fermate in base alla loro distanza dal punto d’interesse, ad esempio quelle che distano dal punto d’interesse al massimo 125 metri da quelle che distano al massimo 250 metri.

(28)

56

Ogni fermata è rappresentata da un cerchio blu con un raggio proporzionale al numero di linee che ci passano nell’arco temporale coperto dal dataset e cliccando sul cerchio si apre un popup contenete delle informazioni e dei pulsanti. Le informazioni sono: il nome della fermata, la distanza in linea d’aria dal punto d’interesse e il numero di linee che servono la fermata.

Il primo pulsante presene in ciascun popup delle fermate si chiama “I percorsi delle linee attuali”. Quando l’utente ci clicca viene invocato il metodo getAllRoutes e sulla mappa vengono disegnati i percorsi e le fermate di tutte le linee che, nell’ora in cui viene premuto tale pulsante, servono la fermata. Questi percorsi sono divisi in diverse sezioni comprese tra due fermate, ognuna avente uno spessore proporzionale alla frequenza della linea in tale ora. Se nessuna linea serve la fermata, viene aperto sul browser un popup box con questo messaggio: “In quest’ora

non è in programma alcuna corsa che passa per questa fermata”.

Invece, per ogni linea che serve una fermata presente nei cerchi rossi sono presenti tre pulsanti con questi nomi:

I punti delle sue corse attuali: quando premuto viene invocato il metodo drawPoints e sulla

mappa vengono disegnati i punti dei percorsi e delle fermate in cui passa la linea in un certo momento.

Tutti i suoi percorsi: quando premuto viene invocato il metodo getShapes e sulla mappa

vengono disegnati tutti i percorsi che la linea effettua nell’arco temporale coperto dal database, cioè tra il 19 giugno e il 31 dicembre del 2018.

Le sue corse attuali con frequenze: viene invocato il metodo getSections che disegna sulla

mappa i percorsi della linea effettuati nell’ora e nel giorno in cui viene premuto questo pulsane. I percorsi sono divisi in sezioni di spessore proporzionale alla frequenza delle corse della linea in quest’ora. Vengono disegnate anche le fermate servite dalle corse di questi percorsi. Se la linea non passa dalla fermata in questo momento temporale, viene aperto sul browser un popup box con questo messaggio: “In questo momento non è in programma

Riferimenti

Documenti correlati

1. Ogni progetto presentato, completato di tutte le necessarie autorizzazioni, viene esaminato e valutato dal Responsabile del Procedimento, nominato dal Comune secondo quanto

Per l’ottavo anno consecutivo, il 12 maggio 2017, la Scuola Politecnica di Ingegneria ed Architettura dell’Università degli Studi di Genova organizza il Convegno

A questo proposito, sono state ripartite e inizieranno ad essere erogate nel 2021: le risorse ordinarie e del Piano Strategico Nazionale per la mobilità sostenibile (3.885 mln

INVESTIMENTI PER IL BENESSERE DELLE PERSONE E PER LA RIDUZIONE DELLE DISUGUAGLIANZE: INTERCONNESSIONE E MOBILITÀ SOSTENIBILE. Estensione

L’ATS Brianza in esecuzione della DGR XI/2720/2019 e ss.mm.ii, nell’ambito della misura B1, intende raccogliere manifestazioni d’Interesse di Enti Erogatori di servizi socio

In questo caso sì ho utilizzato un diverso dal sei di sconto per ogni flusso di cassa, quando la curva dei rendimenti è relativamente piatta questa differenza tra i tassi di

Preso atto che le aziende di trasporto provvedono al rinnovo delle tessere alla scadenza, agli utenti che ne fanno richiesta, gratuitamente e che pertanto è necessario procedere

3 secondo il programma di esercizio analitico annuale (Allegato 1), assicurando anche i servizi connessi al trasporto di cui all’Allegato 2, nel rispetto delle condizioni