Pervasive Touristic Location Based Service (LBS)

Transcript

Pervasive Touristic Location Based Service (LBS)
Università degli studi di Trento
Facoltà di Scienze Matematiche, Fisiche e Naturali
Corso di Laurea Magistrale in Computer Science
TESI
Pervasive Touristic Location Based Service (LBS)
App With Social Perspective
RELATORE:
LAUREANDO:
Raffaele De Amicis
Umberto Di Staso
CORRELATORI:
Gabrio Girardi
Anno Accademico 2010 - 2011
ABSTRACT
Nel corso degli ultimi anni, i mercati mondiali hanno reso accessibili alle masse dispositivi
dotati di ottime prestazioni uniti a prezzi davvero contenuti. L’introduzione di questo
tipo di strumenti, sopratutto nel settore mobile, unito a tariffe sempre più convenienti
per l’accesso ad internet tramite operatori telefonici, ha creato un nuovo tipo di mercato,
il mercato delle applicazioni mobile, la cui espansione viaggia di pari passo alla diffusione
di smartphone e tablet.
La necessità e la possibilità da parte dell’utente finale di disporre di informazioni dettagliate riguardanti un qualsiasi argomento, indipendentemente dalla locazione in cui esso si
trova, è quindi aumentata, ed un settore che ha particolarmente beneficiato di tale tecnologia è quello del turismo.
La giovinezza di tale mercato è tale da fornire applicazioni turistiche ad uno stato primordiale di sviluppo: l’obiettivo di questo documento è quello di fornire le specifiche
necessarie per l’implementazione di un un software che costituisca una nuova generazione
di programmi riguardanti la visualizzazione di informazioni georeferenziate su modelli
tridimensionali creati in tempo reale (on-the-fly).
‘Non è forte chi non cade mai,
ma colui che cadendo
ha la forza di rialzarsi’
Johann Wolfgang Von Goethe
Indice
1 Introduzione
1
2 Il Progetto
2.1 Application Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Middleware Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Data Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
4
5
5
3 Lo Stato dell’Arte
3.1 Unity3D Game Engine . . . . . . . . . . . . .
3.2 NASA World Wind Server . . . . . . . . . . .
3.2.1 GetCapabilities . . . . . . . . . . . . .
3.2.2 GetMap . . . . . . . . . . . . . . . . .
3.3 PostGIS GeoDatabase . . . . . . . . . . . . .
3.4 PHP: Hypertext Preprocessor . . . . . . . . .
3.4.1 PHP: Cenni Storici . . . . . . . . . . .
3.4.2 Perchè PHP, le limitazioni di Unity3D
4 Il modello basato su Tile
4.0.3 Il Zero Level Tile Delta . .
4.1 Struttura delle Tile . . . . . . . .
4.2 Level Of Detail . . . . . . . . . .
4.2.1 Frustum Culling . . . . . .
4.2.2 Divisibilità . . . . . . . . .
4.2.3 L’algoritmo di LOD . . . .
4.3 Costruzione Dinamica delle Mesh
4.3.1 Le Skirts . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
8
10
10
11
14
17
17
18
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
20
23
23
24
24
26
28
5 Contenuti Aggiuntivi
5.1 I Layers . . . . . . . . . . . . . . . . . . . .
5.1.1 Il Z-Fighting . . . . . . . . . . . . . .
5.2 Punti Di Interesse . . . . . . . . . . . . . . .
5.2.1 Approccio Tramite Immagini Raster
5.2.2 Approccio Tramite GameObject . . .
5.2.3 Approccio Tramite BillBoard . . . .
5.2.4 Il Picking . . . . . . . . . . . . . . .
5.2.5 Il Fattore di Scala . . . . . . . . . . .
5.2.6 La Precisione dei Float . . . . . . . .
5.2.7 Inserimento/Aggiornamento PDI . .
5.3 Le Informazioni Meteoreologiche . . . . . . .
5.3.1 I Particle Systems . . . . . . . . . . .
5.4 Il GeoCoding . . . . . . . . . . . . . . . . .
5.5 I Percorsi Guidati . . . . . . . . . . . . . . .
5.5.1 Il formato GPX . . . . . . . . . . . .
5.5.2 Migrazione dei Dati . . . . . . . . . .
5.5.3 Rappresentazione del Percorso . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
31
31
32
32
33
33
34
35
37
38
40
41
41
42
44
44
45
47
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5.5.4
5.5.5
5.5.6
5.5.7
L’Algoritmo di Douglas Peucker .
Proiezione in Altezza . . . . . . .
Visualizzazione delle Informazioni
Attraversamento Aereo . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
53
53
55
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
57
57
58
58
60
7 Il Sistema di Navigazione
7.1 Camera Navigation Basata su Target . . . . . . . . . . . . . . . . . . . .
63
64
8 Natural User Interface
8.1 L’Evoluzione delle Interfacce Utente: da CLI a NUI . . . . . . . . . . . .
8.2 Struttura della NUI utilizzata . . . . . . . . . . . . . . . . . . . . . . . .
8.2.1 Menù Contestuale . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
67
68
69
9 Conclusioni
9.1 Campi di Impiego . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.2 Sviluppi Futuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
71
71
10 Ringraziamenti
73
6 Funzionalità Sociali
6.1 Flickr . . . . . . . . . . . . . . . .
6.1.1 Integrazione . . . . . . . . .
6.1.2 API di Sviluppo . . . . . . .
6.1.3 Filtraggio Basato sul Colore
.
.
.
.
.
.
.
.
.
.
.
.
Appendices
A Tile Generation
75
B BIL Reading
87
C Mesh Generation
89
D Flickr Photo Downloader
101
8
Elenco delle figure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Features offerte dal progetto SMART-ISLANDS. . . . . . . . . . . . . . .
Architettura del progetto SMART-ISLANDS. . . . . . . . . . . . . . . .
Editor fornito da Unity3D Game Engine. . . . . . . . . . . . . . . . . . .
Esempio di una richiesta relativa alle funzionalità esposte dal server WMS.
File in formato XML in risposta alla richiesta di GetCapabilities. Come si
può notare sono presenti tag atti a descrivere nel dettaglio il tipo di dato
esposto dal servizio, come ad esempio: il formato dell’immagine che può
essere generato, una descrizione, il tipo di proiezione e l’area di validità
della richiesta (Bounding Box) . . . . . . . . . . . . . . . . . . . . . . . .
Esempio di una richiesta relativa ad un’immagine in formato PNG del layer
‘mapName’ della porzione di territorio nel bounding box 45-11/46-12. . .
Esempio di visualizzazione 3D del modello digitale di elevazione realizzato
attraverso l’interpolazione di isoipse. . . . . . . . . . . . . . . . . . . . .
Esempio di una richiesta relativa ad un file di altezze in formato Bil16 della
porzione di territorio nel bounding box 45-11/46-12. . . . . . . . . . . . .
Esempio di un file BIL mappato su una porzione di territorio. . . . . . .
Esempio di geometria. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Esempio di rappresentazione spaziale. . . . . . . . . . . . . . . . . . . . .
Raffigurazione dello strato intermedio rappresentato da PHP. . . . . . . .
Class Diagram riguardante la classe Tile. . . . . . . . . . . . . . . . . . .
Esempio di struttura gerarchica delle Tile. . . . . . . . . . . . . . . . . .
Frustum culling relativo ad una generica camera. . . . . . . . . . . . . .
Frustum culling, caso reale. . . . . . . . . . . . . . . . . . . . . . . . . .
Funzionamento dell’algoritmo di LOD. . . . . . . . . . . . . . . . . . . .
Dettaglio del macro blocco riguardante la visualizzazione delle Tile. . . .
Esempio di Tile senza skirts. . . . . . . . . . . . . . . . . . . . . . . . . .
Esempio di Tile con skirts. . . . . . . . . . . . . . . . . . . . . . . . . . .
Costruzione di un layer: si parte dalla duplicazione della mesh base con
applicazione della texture, sucessivamente si passa alla sovrapposizione
degli oggetti. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sequenza di azioni effettuate per la visualizzazione di PDI in formato Raster.
Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando
GameObject. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando
BillBoard. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Esempio a runtime di visualizzazione di PDI utilizzando BillBoard. . . .
Esempio di Particle Emitter. . . . . . . . . . . . . . . . . . . . . . . . . .
3D-Meteo: sole, pioggia e neve. . . . . . . . . . . . . . . . . . . . . . . .
Esempio di una richiesta di GeoCoding dell’indirizzo: via Sommarive 14,
Povo (TN). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
XML in risposta alla richiesta effettuata in figura 28. . . . . . . . . . . .
Esempio rappresentazione di un percorso secondo il formato GPX. . . . .
Esempio LINESTRING di tipo semplice. . . . . . . . . . . . . . . . . . .
Esempio LINESTRING di tipo complesso. . . . . . . . . . . . . . . . . .
9
3
4
8
11
11
12
13
13
14
15
15
18
20
22
23
24
25
27
29
29
32
34
35
36
37
42
42
43
43
45
46
47
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Vertici ausiliari per la costruzione della mesh riguardante un percorso. . .
Triangolarizzazione dei vertici ausiliari ottenuti in precedenza. . . . . . .
Algoritmo di Douglas-Peucker in esecuzione. . . . . . . . . . . . . . . . .
Esempio di costruzione dinamica della mesh di un percorso: i triangoli verdi costituiscono il collider generato sui punti filtrati, in blu il LineRendereer
utilizzante il dataset originale. . . . . . . . . . . . . . . . . . . . . . . . .
Esempio di costruzione dinamica della proiezione in altezza del percorso
‘Altopiano delle Pale’. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Template di riferimento per la visualizzazione delle informazioni riguardanti i percorsi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Esempio a runtime di visualizzaione delle informazioni riguardanti un percorso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Percorso completo di tragitto e punti di interesse. . . . . . . . . . . . . .
Fotogramma dell’animazione riguardante l’attraversamento aereo di un
percorso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
In rosso il perimetro della zona effettiva delle Tile, il restante spazio nero
rappresenta le zone in ombra facente parte del bounding box . . . . . . .
Punti di interesse fotografici posizionati su zone in ombra . . . . . . . . .
Punti di interesse fotografici filtrati in base al colore . . . . . . . . . . . .
Interfacce per l’acquisizione dati di movimento. . . . . . . . . . . . . . .
Esempio di struttura gerarchica. . . . . . . . . . . . . . . . . . . . . . . .
Struttura gerarchica del sistema di navigazione utilizzato. . . . . . . . . .
Evoluzione delle interfacce utente. . . . . . . . . . . . . . . . . . . . . . .
Screenshot raffigurante la struttura della NUI progettata in modalità all-off.
Screenshot raffigurante la struttura della NUI progettata in modalità all-on.
Menù contestuale riferito ad un percorso. . . . . . . . . . . . . . . . . . .
10
48
49
51
52
53
54
55
55
56
60
61
61
63
64
65
67
69
69
70
1
Introduzione
Il corso di laurea in Informatica, dell’Università degli studi di Trento, prevede che, prima
del conseguimento del titolo scelto, lo studente effettui uno stage formativo all’interno
della facoltà stessa o presso un’azienda esterna. A differenza di quanto ho scelto ormai
due anni fà per il conseguimento della Laurea Triennale in Informatica, per la laurea
Magistrale, ho deciso di affidarmi ad una fondazione di ricerca che si è fatta notare negli
anni, per gli splendidi lavori realizzati nel campo della Computer Grafica, settore che da
sempre mi ha affascinato.
La Fondazione GraphiTech, Center for Advanced Computer Graphics Tecnologies, è stata costituita nel 2002 come
una joint venture fra la Fondazione INI-GraphicsNet1 , l’Istituto Trentino di Cultura2 e l’Università degli studi di Trento3 al fine di condurre attività di ricerca e sviluppo su ‘Advanced Computer Graphics’ (ACG), ‘Virtual Reality’ (VR),
‘Mixed Reality’ (MR) ed infine dalle tecnologie dell’informazione e comunicazione ‘Information and Communication
Systems’ (ICS), allo scopo di favorire il trasferimento tecnologico e lo sviluppo della
cultura imprenditoriale in questo settore disciplinare, adattando il Know-How acquisito
all’area della Provincia Autonoma di Trento, ma senza che ciò comporti limitazioni territoriali.
A tal fine, Graphitech partecipa anche a progetti finanziati dall’Unione Europea (EU),
dalla Repubblica Italiana (RI) e da altri enti ed organizzazioni, sia pubblici che privati,
che mirano a favorire il trasferimento tecnologico alle imprese favorendone avviamento e
sviluppo.
1
http://www.inigraphics.net/
http://www.fbk.eu/
3
http://www.unitn.it/
2
1
2
2
Il Progetto
Il progetto sviluppato e oggetto di questo documento, ha come aree di dominio due scienze perfettamente integrate fra loro: l’Human Computer Iterperfaction, scienza che studia
le tecniche di iterazione fra uomo e macchina, Computer Graphics.
Il lavoro svolto, infatti, costituisce una parte molto importante del progetto Europeo chiamato Smart-Islands4 , in
cui la Fondazione Graphitech ha assunto un ruolo leader, che
tratta lo sviluppo di un applicazione per dispositivi mobili
per la visualizzazione di informazioni territoriali, disposte su
opportuni modelli tridimensionali, delle principali isole del Mar Mediterraneo, in maniera
tale da offrire informazioni e supporto in un contesto prevalentemente turistico.
I servizi offerti spaziano dalla conformazione territoriale dell’isola stessa, meteo, punti di
interesse, mappe e tutto ciò che potrebbe essere utile ad un qualsiasi turista, con l’aggiunta di una serie di funzionalità dedicate agli addetti ai lavori, quali: statistiche territoriali,
particelle catastali e via dicendo.
Figura 1: Features offerte dal progetto SMART-ISLANDS.
Un’isola è una particolare tipologia di terreno con confini ben definiti, chiusi, con una
capitale ed un network di piccole città e paesi. La scarsa disponibilità di informazioni
4
http://www.smart-islands.eu
3
spaziali riguardanti le sue infrastrutture, l’ambiente urbano e socio-economico sono fattori critici per un qualsiasi luogo che fa del turismo la sua ragion d’essere.
Le isole del Mar Mediterraneo sono solitamente numerose, di piccole dimensioni e con
infrastrutture che non sono rese note facilmente al visitatore.
Il progetto Europeo Smart-Islands ha come punto chiave quello di offrire supporto a tali
attività, in maniera tale da migliorare l’economia dell’intera zona interessata.
L’architettura di Smart-Islands si basa sull’utilizzo di informazioni reperite online da diversi server appositamente creati, i quali offriranno informazioni in tempo reale su svariate
categorie, le quali andranno poi visualizzate sul dispositivo mobile ove l’applicazione è in
esecuzione.
La figura 2 mostra una chiara visione dell’architettura a tre livelli del progetto SmartIslands.
Figura 2: Architettura del progetto SMART-ISLANDS.
2.1
Application Level
Il primo strato consiste nel client Smart-Islands. Il livello applicazione sarà composto da
una serie di componenti modulari, uniti da un interfaccia avanzata il cui ruolo è quello di
facilitare l’iterazione con le funzionalità offerte dallo strato inferiore. La scelta di decetrare
parte della business logic dal livello preso in esame a quello inferiore garantisce flessibilità
4
e scalabilità, infatti, in questo modo risulterà molto semplice creare le stesse versioni del
software per diverse piattaforme, limitando al minimo i cambiamenti necessari.
2.2
Middleware Level
Il livello intermedio, chiamato middleware level, è a sua volta formato da due insiemi di
componenti: il primo è composto da una lista di componenti statici accessibili da remoto
in base alle features abilitate dall’utente. Il secondo blocco contiene un insieme di script
di gestione delle richieste in arrivo dall’application level, che devono essere smistate al
livello sottostante.
2.3
Data Level
Al livello inferiore il framework Smart-Islands offre una serie di repository di dati e informazioni aggiornate in tempo reale, o in maniera piuttosto frequente, da provider esterni.
L’obiettivo di tale livello è quello di offrire supporto per l’accesso ai database principali
dei provider dei servizi lasciando a loro il mantenimento dei dati.
5
6
3
Lo Stato dell’Arte
Con la locuzione stato dell’arte si intende il più alto livello di sviluppo o conoscenza
finora raggiunto da una tecnologia o da un campo d’indagine scientifico, sinonimo di
‘all’avanguardia’, ‘dell’ultima generazione’, ‘eccellenza’.
In questo capitolo verranno elencati e descritti tutte le componenti allo stato dell’arte
che sono state utilizzate per il raggiungimento dell’obiettivo prefissato. Le tecnologie che
sono stati impiegate sono quattro:
1. Un Game Engine di alto livello per lo sviluppo delle componenti tridimensionali
(Unity3D);
2. un WMS Server utilizzato per la trasmissione di dati DTM5 , Digital Terrain
Model ed immagini georeferenziate (NASA World Wind Server);
3. un GeoDatabase per il contenimento e trasmissione di dati georeferenziati;
4. un linguaggio di scripting, utilizzato per la programmazione Web, ovvero la
realizzazione di pagine web dinamiche (PHP).
5
http://it.wikipedia.org/wiki/Modello_digitale_di_elevazione
7
3.1
Unity3D Game Engine
Unity3D6 è un authoring tool sviluppato per la creazione
di videogames in tre dimensioni ed applicazioni multicontenuto interattive. Unity ha la particolarità di poter essere
eseguito sia su macchine equipaggiate con Microsoft Windows7 che Mac OS X8 e consiste essenzialmente in un editor
grafico di sviluppo/design accoppiato ad un Game Engine
di alto livello.
Le ragioni per le quali Unity è stato scelto, come abiente di
sviluppo, a discapito di altri Game Engine concorrenti sono
le seguenti:
• Ambiente di sviluppo integrato con gerarchie di oggetti, editor grafico visuale,
inspector dettagliato dei vari elementi instanziati e preview del prodotto finale live;
Figura 3: Editor fornito da Unity3D Game Engine.
• Possibilità di compilazione dello stesso progetto per varie piattaforme:
1. Microsoft Windows;
2. Mac OS X;
6
http://unity3d.com/
http://www.microsoft.com/
8
http://www.apple.com/it/macosx/
7
8
3. Web Player via Unity Web Player plugin disponibile per Internet Explorer,
Firefox, Safari, Mozilla, Netscape, Opera, Google Chrome e Camino;
4. Nintendo Wii;
5. iPhone/iPad;
6. Google Android;
7. Microsoft Xbox 360;
8. Sony playstation 3.
• Compatibilità con i più comuni formati di modelli 3D/2D (3ds, obj, fbx, psp, tiff);
• Supporto di speciali effetti grafici quali particle engine, bump, reflection mapping,
dynamic shadows e shaderLab language (Renderer Engine);
• Motore fisico integrato, basato su NVIDIA PhysX;
• GUI System, chiamato UnityGUI. UnityGUI è un sistema di sviluppo integrato
utilizzato per la creazione di interfaccie utente;
• Engine per la costruzione di terreni e relativa vegerazione;
• Scripting sviluppato via Mono, l’implementazione open source del framework .NET.
Gli sviluppatori possono programmare tramite UnityScript (un linguaggio di scripting molto simile al JavaScript), C# oppure Boo (Python-inspired syntax);
• Motore grafico utilizzante Direct3D (Windows), OpenGL (Mac, Windows), OpenGL
ES (iOS, Android).
• Versione ‘Educational’ di Unity3D scaricabile gratuitamente dal sito di riferimento.
Per questa serie di motivi tecnico/tecnologici, uniti alla semplicità di apprendimento
all’uso del Game Engine stesso e alla velocità di sviluppo, Unity3D è stato scelto come
componente fondamentale per lo sviluppo del progetto finale.
9
3.2
NASA World Wind Server
NASA World Wind Server9 è la versione server del celebre
NASA World Wind, globo virtuale Open Source sviluppato dalla NASA e dalla comunità open source per l’uso su
personal computers, per la trasmissione di informazioni geografiche secondo protocollo WMS10 , acronimo che stà per
Web Map Service. Un server WMS produce dinamicamente mappe di dati spazialmente
riferiti a partire da informazioni geografiche. Questo standard definisce una mappa come
rappresentazione di informazioni geografiche restituendo un’immagine digitale idonea.
Generalmente le mappe prodotte da un servizio WMS sono restituite in un formato
immagine quale PNG, GIF o JPEG.
Lo standard WMS definisce tre operazioni:
1. Restituisce metadati a livello di servizio;
2. Restituisce una mappa dai parametri geografici e dimensionali definiti;
3. Restituisce informazioni sugli oggetti della cartografia visualizzata (opzionale).
Le operazioni del Web Map Service vengono invocate usando un client che supporti il
protocollo HTTP, in forma di Uniform Resource Locators (URL). Le operazioni richieste
al server vengono specificate nell’URL. Nel dettaglio, sono due i tipi di richieste che sono
state utilizzate nel contesto del progetto di tesi descritto: GetCapabilities e GetMap.
3.2.1
GetCapabilities
La prima delle due richieste che verranno analizzate riguarda quella denominata ‘GetCapabilities’, la quale serve a fornire al richiedente la lista delle funzionalità abilitate sul
server WMS interrogato. Attraverso questa lista, formattata secondo lo standard XML11
è possibile ottenere la lista dei layers disponibili, il formato delle immagini in output supportate, le aree ove i dati sono disponibili (bounding box) e le descrizioni in linguaggio
naturale delle informazioni esposte.
Un esempio di richiesta GetCapabilities è mostrata in figura numero 4 mentre la figura 5
mostra il file in formato XML in risposta ad una richiesta.
9
http://worldwind.arc.nasa.gov/
http://it.wikipedia.org/wiki/Web_Map_Service
11
http://it.wikipedia.org/wiki/XML
10
10
Figura 4: Esempio di una richiesta relativa alle funzionalità esposte dal server WMS.
Figura 5: File in formato XML in risposta alla richiesta di GetCapabilities. Come si può
notare sono presenti tag atti a descrivere nel dettaglio il tipo di dato esposto dal servizio,
come ad esempio: il formato dell’immagine che può essere generato, una descrizione, il
tipo di proiezione e l’area di validità della richiesta (Bounding Box)
3.2.2
GetMap
La seconda richiesta che andremo ad analizzare riguarda quella denominata ‘GetMap’,
utilizzata quando si vogliono ottenere immagini di una porzione di territorio. In questo
caso, il richiedente specifica nella chiamata HTTP una serie di parametri che verranno
successivamente processati dal server WMS in maniera tale da ottenere la corretta immagine in risposta. Le informazioni base che devono essere specificate sono: la porzione
dei terreno che deve essere rappresentata, il sistema di coordinate desiderato, il formato e
le dimensioni dell’immagine di output. L’immagine 6 mostra una tipica chiamata HTTP
al server WMS chiedendo in risposta un’immagine in formato PNG.
Qualora due o più mappe siano prodotte con gli stessi parametri geografici e di dimensione dell’immagine, i risultati possono essere sovrapposti per produrre una mappa
composita. L’uso di formati che supportano la trasparenza (GIF o PNG per esempio)
permette di visualizzare le parti di mappa sottostanti; inoltre mappe diverse possono es11
Figura 6: Esempio di una richiesta relativa ad un’immagine in formato PNG del layer
‘mapName’ della porzione di territorio nel bounding box 45-11/46-12.
sere richieste a differenti server. In questa maniera il Web Map Service abilita la creazione
di una rete di server cartografici che l’utente può utilizzare per costruire mappe personalizzate.
Parallelamente alla distribuzione (streaming) di immagini, Nasa World Wind Server
permette l’esposizione di risorse chiamate ‘elevation files’ o DTM che, come il nome stesso suggerisce, riguardano le altezze di una particolare zona, specificata nella chiamata
HTTP nella stessa maniera in cui vengono richieste le immagini.
Un modello digitale di elevazione (anche noto come DEM, dall’inglese Digital Elevation
Model) è la rappresentazione della distribuzione delle quote di un territorio, o di un’altra
superficie, in formato digitale. Il modello digitale di elevazione viene in genere prodotto
in formato raster associando a ciascun pixel in un’immagine satellitare l’attributo relativo
alla quota assoluta.
Il DTM può essere prodotto con tecniche diverse. I modelli più raffinati sono in genere
realizzati attraverso tecniche di telerilevamento che prevedono l’elaborazione di dati acquisiti attraverso un sensore montato su un satellite, un aeromobile o una stazione a terra.
Ad esempio, analizzando il segnale di fase registrato da un Radar ad Apertura Sintetica
(SAR: Synthetic Aperture Radar) installato su un satellite, è possibile produrre un modello digitale di elevazione. Una tecnica delle tecniche per la produzione di DEM consiste
nell’interpolazione delle curve di livello che possono essere prodotte anche attraverso il
rilevamento diretto sul terreno.
Nell’ immagine 7 è possibile osservare un esempio di visualizzazione 3D del modello
digitale di elevazione realizzato attraverso l’interpolazione di isoipse.
L’immagine 8 mostra una tipica chiamata HTTP al server WMS richiedendo un file
DTM in risposta in formato Bil16.
Il formato file BIL è un formato non-standard per la trasmissione di dati DEM (Digital
Elevation Model) utilizzato in Nasa World Wind il cui scopo è quello di offrire informazioni
riguardanti le altezze di una specifica porzione di territorio. Ogni file BIL è composto
12
Figura 7: Esempio di visualizzazione 3D del modello digitale di elevazione realizzato
attraverso l’interpolazione di isoipse.
Figura 8: Esempio di una richiesta relativa ad un file di altezze in formato Bil16 della
porzione di territorio nel bounding box 45-11/46-12.
da un bitmap la cui risoluzione dipende dai parametri ‘altezza’ e ‘larghezza’ specificati
nella chiamata al server WMS Nasa World Wind. Ogni pixel che compone l’immagine
in risposta, codificato opportunamente come intero a 16 bit (bil16), descrive l’altezza in
metri sul livello del mare di un preciso punto nello spazio.
Per comprendere al meglio come utilizzare un file BIL per la rappresentazione dei
dati relativi alle altezze, facciamo riferimento all’immagine 8: come possiamo osservare,
in questa chiamata viene specificata una zona che va da 45 a 46 gradi di longitudine e
da 11 a 12 gradi di latitudine. La porzione di territorio viene descritta da una matrice di
pixel 8x8, che andrà mappata sul terreno come mostrato in figura 9.
Richiedendo al server WMS file BIL più fitti in termini di densità di punti è possibile
rappresentare porzioni di territtorio con maggiore fedeltà ripetto al mondo reale.
13
Figura 9: Esempio di un file BIL mappato su una porzione di territorio.
3.3
PostGIS GeoDatabase
Un database spaziale (Spatial Database Management System o SDBMS) è un database ottimizzato per memorizzare e
interrogare i dati relativi agli oggetti nello spazio, includendo punti, linee e poligoni. A differenza dei tipici database
relazionali, in grado di analizzare vari tipi di caratteri numerici e di dati, in questo nuovo tipo di database sono necessarie funzionalità aggiuntive, per processare i nuovi tipi di
dati, chiamate geometrie o funzionalità.
Gli indici che i database tradizionali usano per velocizzare la
ricerca di dati e di valori, nel caso dei database spaziali sono
chiamati indicispaziali (le tipologie più usate sono gli R-tree). Oltre alle classiche tipologie di query (come ad esempio quelle di SELECT), in questo caso ne sono presenti anche
delle altre come, ad esempio, funzioni di misurazione delle distanze, e di calcolo delle aree.
Il termine database spaziale è entrato in uso solo negli ultimi anni in seguito ad un
ciclo di conferenze, chiamato ‘Symposium on Large Spatial Databases (SSD)’, a partire
dal 1989; e sta proprio ad indicare che tale database sia un contenitore di oggetti nello
spazio invece che di immagini o figure di uno spazio.
Nel corso degli anni, con lo sviluppo di applicazioni sempre più complesse, le quali sono
14
andate avanti di pari passo con la disponibilità di componenti hardware più performanti
ad un prezzo sempre più contenuto, si è sentita la necessità di creare questa nuova tipologia
di database, in quanto ci si era resi conto di avere un’ altra tipologia di dati da dover
rappresentare che non poteva essere gestita con i metodi tradizionali, in particolare:
1. Oggetti nello spazio: interessati a diverse entità distinte fra loro, ognuna delle quali
ha la proprio descrizione geometrica. La figura 10 mostra un chiaro esempio di
descrizione geometrica.
Figura 10: Esempio di geometria.
2. Spazio: per descrivere lo spazio stesso, che sarebbe a dire essere in grado di descrivere ogni suo punto, esempio in figura 11.
Figura 11: Esempio di rappresentazione spaziale.
Un GeoDatabase è un particolare tipo di database spaziale progettato per immagazzinare, interrogare e manipolare informazioni geografiche con bassa dimensionalità. Costituisce infatti un database spaziale particolarmente ottimizzato per informazioni in due
o tre dimensioni, dati di tipo raster e distanze Euclidee.
15
Utilizzando un Geodatabase, possono essere salvate strutture dati Vettoriali come
punti, linee o poligoni, opportunamente georeferenziati. Un qualsiasi record di un GeoDatabase può utilizzare geometrie come tipo di dato, per la rappresentazione di luoghi o
oggetti appartenenti al mondo fisico, oltre che mantenere le classiche funzionalità di un
database relazionale.
La maggior parte dei GeoDatabase uniscono infatti le funzionalità per le interrogazioni
spaziali al classico motore SQL utilizzato dalla maggior parte dei database relazionali, in
questo modo è possibile effettuare query del tipo ‘trova tutti i residenti in una determinata
area che sono nati dal 1984’.
Ll GeoDatabase utilizzato è stato PostGIS, una particolare estensione al database
relazione PostgreSQL, che lo abilita all’interrogazione e manipolazione di strutture dati
spaziali (GIS).
16
3.4
PHP: Hypertext Preprocessor
PHP: Hypertext Preprocessor, comunemente chiamato PHP, è un linguaggio di scripting
interpretato, originariamente concepito per la programmazione Web, ovvero la realizzazione di pagine web dinamiche. Attualmente è utilizzato principalmente per sviluppare
applicazioni web lato server ma può essere usato anche per script a riga di comando o
applicazioni stand-alone con interfaccia grafica. L’elaborazione di codice PHP sul server
produce codice HTML da inviare al browser dell’utente che ne fa richiesta. Il vantaggio
dell’uso di PHP rispetto al classico HTML deriva dalle differenze profonde che sussistono
tra Web dinamico e Web statico.
3.4.1
PHP: Cenni Storici
Nato nel 1994 ad opera del danese Rasmus Lerdorf, PHP era in origine una raccolta
di script CGI che permettevano una facile gestione delle pagine personali. Il significato
originario dell’acronimo era Personal Home Page. Il pacchetto originario venne in seguito
esteso e riscritto dallo stesso Lerdorf in C, aggiungendo funzionalità quali il supporto al
database mSQL e prese a chiamarsi PHP/FI, dove FI stava per Form Interpreter (interprete di form), prevedendo la possibilità di integrare il codice PHP nel codice HTML in
modo da semplificare la realizzazione di pagine dinamiche. In quel periodo, 50.000 domini
Internet annunciavano di aver installato PHP. A questo punto il linguaggio cominciò a
godere di una certa popolarità tra i progetti open source del web, e venne così notato
da due giovani programmatori: Zeev Suraski e Andi Gutmans. I due collaborarono nel
1998 con Lerdorf allo sviluppo della terza versione di PHP (il cui acronimo assunse il
significato attuale) riscrivendone il motore che fu battezzato Zend da una contrazione
dei loro nomi. Le caratteristiche chiave della versione PHP 3.0, frutto del loro lavoro,
erano la straordinaria estensibilità, la connettività ai database e il supporto iniziale per
il paradigma a oggetti. Verso la fine del 1998 PHP 3.0 era installato su circa il 10%
dei server web presenti su Internet. La versione 4 di PHP venne rilasciata nel 2000 e
prevedeva notevoli migliorie. Attualmente siamo alla quinta versione, sviluppata da un
team di programmatori, che comprende ancora Lerdorf, oltre a Suraski e Gutmans. La
popolarità del linguaggio PHP è in costante crescita grazie alla sua flessibilità: nel Giugno
2001, ha superato il milione di siti che lo utilizzano. Nell’ottobre 2002, più del 45% dei
server Apache usavano PHP. Nel 2005 la configurazione LAMP (Linux, Apache, MySQL,
PHP) supera il 50% del totale dei server sulla rete mondiale. Nel 2008 PHP 5 è diventata
l’unica versione stabile in fase di sviluppo. A partire da PHP 5.3.0, PHP implementa una
funzione chiamata ‘late static binding’ che può essere utilizzata per fare riferimento alla
classe chiamata in un contesto di eredità statica. A partire dal 5 febbraio 2008, a causa
17
dell’iniziativa GoPHP5, sostenuta da una serie di sviluppatori PHP, molti dei progetti
open-source di alto profilo cessano di supportare PHP 4 nel nuovo codice e promuovono
il passaggio da PHP 4 a PHP 5.
3.4.2
Perchè PHP, le limitazioni di Unity3D
Come accennato in sezione 3.1, uno degli enormi punti di forza che hanno fatto sì che
la scelta del GameEngine da utilizzare sia caduta su Unity3D, stà nella possibilità di
compilare lo stesso codice per una varietà molto ampia di piattaforme. Purtroppo però,
è garantita la compatibilità di esecuzione delle funzioni per le sole operazioni incluse nel
Core stesso del GameEngine. Ciò vuol dire che, se volessimo utilizzare una libreria esterna, o una funzione base della classe System di C Sharp, sulla versione per PC Desktop il
software prodotto potrebbe non avere problemi in esecuzione, ma ciò potrebbe non essere
vero per le versioni eseguite su dispositibi mobili.
E’ stata quindi una delle maggiori criticità quella di generare codice multipiattaforma che
potesse essere eseguito senza problemi su qualsiasi dispositivo.
Uno dei limiti attualmente esistenti del sistema Unity3D, è quello di non offrire funzionalità all’interno del suo Core per la connessione ed interrogazione ai Database, sia
in locale che in remoto. Per questo motivo è stato scelto di creare uno strato intermedio fra dati e client, del quale abbiamo già avuto modo di parlare in sezione
sec:phpl evel, dedicatoall0 interscambiodiinf ormazionif ral0 applicazionef inaleedildatalevel.Inaltrepar
Loschemadellastrutturaprogettatarappresentatainf igura12.
Figura 12: Raffigurazione dello strato intermedio rappresentato da PHP.
18
4
Il modello basato su Tile
Il primo problema che bisogna affrontare quando si progettano applicazioni che producono
in tempo reale terreni tridimensionali è strettamente legato alla generazione dinamica, ed
efficiente, degli elementi costituenti il mondo virtuale ottenuto come risultato finale.
Il modello più efficiente per la generazione di modelli basati su informazioni geografiche
è quello a Tile, dove Tile è un termine in lingua Inglese che, in Italiano, potrebbe essere
tradotto con buona fedeltà dalla parola ‘tassellazione’. Il modello a Tile propone quindi
una suddivisione del territorio, dove ogni componente, o tassello, è chiamato appunto
Tile.
4.0.3
Il Zero Level Tile Delta
Prima di iniziare con la definizione della struttura a Tile, è necessario che il lettore
padroneggi alcuni concetti base, fra i quali quello di Zero Level Tile Delta, che da qui
in avanti sarà abbreviato con l’acronimo ZLTD. Il ZLTD è il valore che risponde alla
seguente domanda ‘Che area in gradi viene coperta da una Tile di livello zero?’.
Detta in questa maniera, è normale che ci si possa sentire un pò spaesati, facciamo
ulteriore chiarezza, definendo inanzitutto il significato di Tile di livello zero: esse non
sono altro che le foto aeree di una porzione di territorio alla minor risoluzione disponibile,
in altre parole, quelle che, scattate da una maggior altitudine, corrispondono ad un livello
di dettaglio definito per conformità a zero.
Avendo ora compreso cosa si intende per Tile di livello zero passiamo ora con la definizione
dei gradi che esse ricoprono, per avere una completa comprensione del concetto di ZLTD.
Com’è facilmente intuibile, la dimensione di ogni tassello non è impostata casualmente,
ma secondo una rigorosa logica, che si basa sui gradi che essi vanno a coprire della
superficie terrestre.
Tile molto piccole e quindi molto definite, copriranno zone di dimensioni ridotte mentre
quelle ad una minor risoluzione rappresenteranno spazi più ampi.
Il ZLTD non è altro che il numero di gradi della superficie terrestre delle Tile di livello
zero.
E’ possibile avere un’idea del chilometraggio della superficie coperta da ogni orthophoto
utilizzando la seguente formula matematica:
P
360
zltd
Dove P è il perimetro della terra (40.000km circa se preso all’equatore, non essendo il
globo perfettamente sferico), 360 sono i gradi totali e ZLTD è il zero level Tile Delta. Per
19
fare un esempio, supponendo di avere Tile di livello zero ad un grado (come quelle del
dataset di test utilizzato), ogni immagine alla sua minor definizione coprirà circa 111km
del perimetro terrestre all’ equatore.
4.1
Struttura delle Tile
Il Tiling, è quindi il concetto fondamentale sul quale tutto l’algoritmo di visualizzazione
ruota, ed è stata creata una classe apposita per l’immagazzinamento delle informazioni
riguardanti ogni tassello.
Figura 13 mostra il Class Diagram riguardante la classe Tile.
Figura 13: Class Diagram riguardante la classe Tile.
Come abbiamo avuto modo di osservare, ogni Tile contiene una grande quantità di
informazioni, le più importanti definite nell’elenco sottostante:
• public int level : rappresenta il livello in cui la Tile risiede.
• public int tileX : rappresenta il codice della Tile in X, calcolato con
(int)
longitude + 180
zltd
2l
• public int tileY : rappresenta il codice della Tile in Y, calcolato con
(int)
latitude + 90
zltd
2l
20
• public float GLOBALSCALE : rappresenta il fattore di scala utilizzato, parametro
del quale parleremo ampiamente in sezione 5.2.5.
• public float latitude : esprime la latitudine del punto in basso a sinistra della Tile.
• public float longitude : esprime la longitudine del punto in basso a sinistra della
Tile.
• public float size : dimensione della Tile, calcolata in base a livello e zltd. La
dimensione di ogni Tile viene calcolata con
zltd
∗ GLOBALSCALE
2l
• public float shift : parametro identico a size, duplicato per una miglior lettura del
codice.
• public float zltd : contiene il valore riguardante il Zero Level Tile Delta, parametro
del quale abbiamo parlato in sezione 4.0.3
• public Vector3 tileLLPoint : indica il punto in basso a sinistra della Tile, espresso
come vettore in tre dimensioni.
• public Vector3 tileULPoint : indica il punto in alto a sinistra della Tile, espresso
come vettore in tre dimensioni.
• public Vector3 tileLRPoint : indica il punto in basso a sinistra della Tile, espresso
come vettore in tre dimensioni.
• public Vector3 tileURPoint : indica il punto in alto a sinistra della Tile, espresso
come vettore in tre dimensioni.
• public Vector3 tileMidPoint : indica il punto centrale della Tile, espresso come
vettore in tre dimensioni.
• public float boundingSphereRadius : rappresenta il raggio della sfera di contenimento della Tile, l’utilizzo di questo dato verrà approfondito in sezione 4.2.1.
• public float normalizedTileX : indica la posizione della Tile nella sua Tile di livello
0 in X. Questo dato viene calcolato tramite la formula
tileX
∗ zltd ∗ GLOBALSCALE
2l
21
• public float normalizedTileY : indica la posizione della Tile nella sua Tile di livello
0 in Y. Questo dato viene calcolato tramite la formula
tileY
∗ zltd ∗ GLOBALSCALE
2l
• public string tileCode : codice identificativo della Tile, costituito dalla concetenazione delle stringhe riguardanti livello, tileX e tileY.
• public string parentTile : codice della Tile padre.
Il Tiling si basa fondamentalmente su un concetto molto semplice ma performante:
ogni tassello componente la porzione di mondo rappresentata può essere suddiviso in
quattro Tile figlie ogni volta che si richiede una maggior fedeltà di riproduzione visiva.
Ogni tile risulta essere quindi indipendente, avendo la possibilità di suddividersi a piacimento qualora ce ne fosse la necessità.
La figura numero 14 mostra come una Tile viene suddivisa ricorsivamente in quattro Tile
figlie.
Figura 14: Esempio di struttura gerarchica delle Tile.
Il Tiling risulta essere una tecnica molto potente per la gestione di informazioni visive riguardanti il territtorio, tant’è che i più famosi geoVisualizzatori, come per esempio
Google Earth12 o lo stesso Nasa World Wind versione Client basano i loro algoritmi proprio su questo principio.
Avendo acquisito le nozioni basilati riguardanti la tassellazione ed il suo funzionamento, la domanda che nasce è la seguente: ‘In base a che parametri ogni Tile deve essere
12
http://www.google.com/intl/it/earth/
22
suddivisa, dando vita quindi ai suoi quattro figli, che a loro volta possono suddividersi o
meno?’.
Il problema sopra esposto prende il nome di Level Of Detail, o, usando un acronimo,
LOD.
4.2
Level Of Detail
Prima di ragionare sulla risposta al problema del LOD, i concetti fondamentali che il
lettore deve necessariamente padroneggiare sono due: il concetto di Frustum Culling e
quello di Divisibilità.
4.2.1
Frustum Culling
Il frustum culling è una tecnica di culling basata sul volume visivo della matrice di
proiezione utilizzata per la trasformazione delle coordinate. Una matrice di proiezione
prospettica genera un frustum, che può essere definito da un’insieme di sei piani relativi
alla camera interessata: Left, Right, Top, Bottom, Near, Far.
Figura 15: Frustum culling relativo ad una generica camera.
Conoscendo i sei piani che definiscono il frustum è possibile stabilire se un qualsiasi
punto nello spazio è all’interno del frustum stesso, e quindi visibile o meno, semplicemente
controllando la distanza fra l’oggetto interessato e i sei piani definiti precedentemente.
Un esempio reale del risultato di questa tecnica è visibile in immagine .
23
Figura 16: Frustum culling, caso reale.
4.2.2
Divisibilità
Il concetto di divisibilità è strettamente legato alla distanza che intercorre fra il centro
di ogni Tile e il nostro punto di vista (la camera utilizzata). Conoscere la distanza che
separa questi due elementi è fondamentale per decidere quando suddividere ogni elemento per aumentarne la risoluzione o, al contrario, compattare più figli in caso ci si stia
allontanando dal nostro oggetto di riferimento.
4.2.3
L’algoritmo di LOD
Avendo acquisito un background necessario per la completa comprensione dell’algoritmo
di LOD, passiamo ora alla visualizzazione di un semplice diagramma che ne schematizza
il funzionamento, visibile in figura 17.
Come la stessa immagine suggerisce, l’algoritmo che si prende in carico la gestione del
Level Of Detail è gestito ricorsivamente, secondo i seguenti steps:
1. In input viene passata una qualsiasi Tile, una struttura dati contenente i parametri
fondamentali per la gestione della stessa quali latitudine, longitudine, codice, zltd,
ecc;
2. Successivamente la Tile in input viene sottoposta al check di visibilità, tramite la
tecnica del Frustum Culling analizzata nella sezione 4.2.1. A questo punto le strade
da prendere sono le seguenti:
24
Figura 17: Funzionamento dell’algoritmo di LOD.
(a) Se la Tile non è visibile allora non ha senso procedere con ulteriori passi,
l’algoritmo viene interrotto;
(b) Altrimenti passa allo step successivo, ossia il test di divisibilità.
3. Test di divisibilità, basato su quanto detto in sezione 4.2.2. Anche in questo caso,
l’esito del test garantisce il proseguo dell’algoritmo su due strade differenti:
(a) Se la distanza che intercorre fra la Tile e la camera non richiede una visualizzazione di dati ad una più alta risoluzione, allora visualizza la Tile in
input;
(b) Altrimenti (passo ricorviso) dividi l’input in quattro figli (Lower_Left - Lower_Right - Upper_Left - Upper_Right) e, su ciascuno di essi, chiama l’algoritmo di LOD passando come input ciascun figlio creato.
In questo modo la sola visualizzazione di elementi ad una corretta risoluzione è garantita. Inoltre è bene evidenziare che non tutte le Tile possono essere mostrate alla medesima
definizione: pensiamo per esempio ad una porzione di terreno esplorata ad una qualsiasi
angolazione, in questo caso le Tile più vicine avranno una maggior definizione, mentre
quelle più lontane saranno visualizzate in minor qualità.
25
Lo scopo dell’algoritmo di LOD è quindi quello di ottimizzare le performance in base
alla percezione della definizione che si ha guardando oggetti posti a distanze differenti:
sarebbe inutile renderizzare una geometria posta molto lontano ad una risoluzione pari
a quella degli oggetti in primo piano, in quando l’occhio umano non ne percepirebbe la
differenza. Costruendo modelli tridimensionali distanti a minor qualità si ottimizzano
le performance mantenendo lo stesso risultato estetico finale. Concludendo la sezione
riguardante l’algoritmo di LOD, è molto importante comprendere che esso viene eseguito
ad ogni frame, in maniera tale da avere una situazione costantemente aggiornata di ciò
che si deve renderizzare.
4.3
Costruzione Dinamica delle Mesh
‘Una mesh poligonale è una collezione di vertici, spigoli e facce che definiscono la forma di un oggetto poliedrico nella computer grafica 3D e nella modellazione solida. Le
facce consistono solitamente di triangoli, quadrilateri od altri semplici poligoni convessi, dal momento che ciò semplifica il rendering, ma possono essere composti anche da
poligoni concavi più generici, o poligoni con buchi. Le mesh sono primitive grafiche che
consentono di risolvere con grande efficienza i procedimenti di visualizzazione delle forme
modellate: sono strisce di triangoli o maglie di quadrilateri con cui rappresentiamo un
poliedro qualsiasi o con cui approssimiamo superfici curve.’ 13
Uno dei punti di forza del sistema di geoVisualizzazione creato è quello di adattarsi
completamente, e in tempo reale, alle mappe che identificano la porzione di territorio
da visualizzare. Sarebbe un lavoro lunghissimo ed estremamente poco flessibile creare
ad-hoc i modelli tridimensioni di ogni Tile in base alla mappa in input, considerando che,
passando da un livello all’altro, il numero di tasselli cresce secondo potenza di 2. La cosa
migliore da fare è quindi creare on-the-fly le mesh necessarie, partendo dalle informazioni
appartenenti ai dataset in ingresso.
Facendo riferimento all’immagine 17, ecco come si presenta nel dettaglio, in figura 18, il
macro-blocco riguardante la visualizzazione di ogni Tile e conseguente creazione di ogni
mesh.
Nel dettaglio, ecco i passi che compongono la visualizzazione di ogni Tile:
1. Un primo controllo viene effettuato sull’esistenza o meno del modello tridimensionale di ciascuna Tile da visualizzare in memoria: un sistema di indicizzazione basato
su HashMap garantisce un’efficace e rapida reperibilità degli oggetti precedente13
http://it.wikipedia.org/wiki/Mesh_poligonale
26
Figura 18: Dettaglio del macro blocco riguardante la visualizzazione delle Tile.
mente creati. In questo modo è possibile tenere in memoria ciò che è stato già
creato, utilizzandolo nuovamente se necessario.
2. In base al controllo precedente, vengono prese due strade: la prima, consiste semplicemente nella renderizzazione dell’elemento precedentemente creato, la seconda
viene spiegata successivamente.
3. Un altro controllo stabilisce se la texture da applicare sulla mesh è presente in memoria o meno. In caso il check fallisca l’immagine viene scaricata automaticamente da
un apposito server.
4. Verificata la presenza della texture in cache si passa alla verifica o download del file
BIL relativo alle altezze. La formattazione del file BIL e della richiesta HTTP sono
state abbondantemente approfondite in sezione 3.2.2.
5. L’ultimo step riguarda la crezione vera e propria del modello tridimesionale appartenente alla Tile oggetto.
27
4.3.1
Le Skirts
Creando dinamicamente mesh appartenenti ad un terreno, è assai probabile che la superficie rappresentata non sia perfettamente piana. A questo punto, una componente di
fondamentale importanza per un ottimo risultato finale, è rappresentata dalla precisione
dei dati DEM: utilizzando dati ad alta risoluzione, infatti, vengono ridotti i problemi
che sorgono naturalmente quando due Tile vengono accostati. Supponiamo per esempio di avere punti con risoluzione a 90 metri (da un elemento all’altro ci sono quindi
90 metri), la stessa offerta dai server NASA per l’intero globo terrestre (dataset dataset
SRTM30plus). Supponiamo inoltre di avvicinarci molto al terreno, in prossimità della
linea di congiunzione fra due Tile, chiedendo quindi una buona fedeltà di riproduzione
del terreno. Cosa succede a questo punto? La risposta è molto semplice: le altezze dei
punti collocati sulla linea di congiunzione delle due Tiles saranno distanziati da 90 metri,
risoluzione che potrebbe creare molti problemi se si stà visualizzando una zona montuosa
(in tale distanza potrebbe esserci un dislivello importante). Un tipico esempio è mostrato
in figura 19.
Un altro problema noto riguarda l’accostamento di Tile a diversa risoluzione, in quanto l’algoritmo di LOD, del quale abbiamo parlato in sezione 4.2.3, gestisce l’aumento o la
diminuzione della risoluzione in base alla distanza di ciascun elemento dal punto di vista
in uso.
Prendiamo nuovamente come esempio la figura 14: come è possibile osservare, l’elemento
numero 1 genera i figli numero 2-3-4-5. Anche se la dimensione di ogni mesh figlia è la
metà, sia in altezza che in larghezza, rispetto a quella padre, il numero di punti che la
costituisce rimane invariato, indipendente dalla risoluzione in uso.
Quindi, quando ogni Tile viene suddivisa, il numero di punti che definiscono la stessa
porzione di territorio viene elevato al quadrato rispetto al tassello padre.
Pare quindi chiaro che, se due elemementi a diversa risoluzione vengono accostati, verranno evidenziati problemi sulla linea di congiunzione delle due mesh, dovuti alla differenza
di risoluzione in punti (doppia) di un elemento rispetto ai due adiacenti.
Utilizzare dati ad alta risoluzione potrebbe quindi attenuarne l’effetto, ma non sempre questa tipologia di dataset sono disponibili. Esistono comunque varie soluzioni al
problema, quello che è stato scelto in questo contesto è rappresentato dalle skirts.
Le skirts non sono altro che delle geometrie aggiuntive collocate ai bordi di ciascun
modello tridimensionale, come se formassero una gonna. Utilizzando dei poligoni aggiuntivi, collocati opportunamente, è possibile coprire i buchi che vanno a formarsi quando
la coordinata Y sui bordi delle Tile non coincide perfettamente. Un esempio di skirt è
mostrato in figura 20.
28
Figura 19: Esempio di Tile senza skirts.
Figura 20: Esempio di Tile con skirts.
In appendice, sezione B (BilReader.cs), è possibile analizzare la parte di codice riguardante
la lettura dei files DEM in formato BIL16. In appendice, sezione C (MeshGenerator.cs),
è possibile analizzare la parte di codice riguardante la costruzione della mesh riguardante
ciascuna Tile.
29
30
5
Contenuti Aggiuntivi
Il sistema così progettato permette quindi la visualizzazione di un qualsiasi terreno esistente in tre dimensioni. Ma, spesso, questo non è sufficiente: la conoscenza che l’utente
riceve si limita alla sola conformazione territoriale, quindi è sorta la necessità di mostrare
altri tipi di informazioni riguardanti lo spazio rappresentato, in maniera da incrementare
l’user-experience.
Per questo sono stati introdotti i seguenti componenti aggiuntivi:
1. I layers;
2. i punti di interesse, PDI;
3. informazioni meteo;
4. il geoCoding;
5. visualizzatore di percorsi.
5.1
I Layers
Un tipo di informazione molto utile, e molto gradevole da vedere, è costituita dai layers.
Un layer non è altro che un tematismo applicato sul terreno, capace di mostrare le informazioni di più svariate tipologie (dai fiumi, ai confini territoriali, i parchi, le strade e
così via).
Come abbiamo avuto modo di vedere nella sezione 3.2.2, NASA World Wind Server permette la sovrapposizione di più immagini immagazzinate nella sua memoria ma, per un
discorso di cashing (i dati scaricati vengono infatti salvati in memoria, un’immagine formata da più livelli sovrapposti risulta essere poco flessibile per utilizzi futuri), è stato
scelto un altro tipo di approccio per l’ottenimento del medesimo risultato.
La soluzione utilizzata si basa sulla duplicazione delle sole mesh appartenenti alle sole
Tile visibili ad un determinato istante di tempo, l’applicazione della texture del layer alla
mesh duplicata e la sovrapposizione dei risultati finali.
Il numero di layer che possono essere sovrapposti è potenzialmente infinito, l’unica limitazione è costituita dalle prestazioni della macchina ove il software è in esecuzione.
La figura numero 21 mostra un esempio di applicazione di un layer sulla mesh base.
In appendice, sezione C (Layer.cs), è possibile analizzare la parte di codice riguardante
la duplicazione della mesh di ciascuna Tile.
31
Figura 21: Costruzione di un layer: si parte dalla duplicazione della mesh base con
applicazione della texture, sucessivamente si passa alla sovrapposizione degli oggetti.
5.1.1
Il Z-Fighting
La sovrapposizione di uno o più layer comporta, nativamente, un problema a cui bisogna
far fronte, chiamato in gergo tecnico flicker. Il flicker è un termine proveniente dal mondo dell’elettrotecnica che, tradotto letteralmente, significa ‘sfarfallio’. Lo shader in uso
infatti, quando due elementi sono perfettamente sovrapposti, non riesce a capire quale
elemento debba essere visualizzato prima o dopo, di conseguenza l’effetto finale sarà appunto quello di uno sfarfallio di texture, dovuto al non corretto ordine di renderizzazione.
Per risolvere il problema l’approccio è stato il seguente:
1. Per ogni livello viene creato un nuovo materiale;
2. Per ogni Tile duplicata appartenente ad un livello viene impostato il relativo materiale;
3. Per ogni materiale viene impostata la relativa rendererQueue, una proprietà che
indica alla scheda grafica l’ordine in cui gli oggetti devono essere renderizzati.
In questa maniera il problema del flickering è stato completamente risolto.
5.2
Punti Di Interesse
Un’altro tipo di informazione molto importante riguardante il territorio è costituita dai
punti di interesse, che da qui in avanti chiameremo PDI/POI. Un punto di interesse non
è altro che marker posizionato sul terreno indicante un qualsiasi oggetto reale, collocato
in quella esatta posizione, che potrebbe essere rilevante per l’utente: un monumento, un
edificio pubblico, un luogo di culto ma anche un hotel, un parco o una fontana. Generalmente i PDI vengono suddivisi per categorie e caricati da un GeoDatabase remoto, ove
sono opportunamente georeferenziati.
32
L’implementazione dei punti di interesse potrebbe sembrare molto semplice, ma in
realtà è stata una dei punti più critici dell’intero progetto, in quanto la visualizzazione
contemporanea di migliaia di gameObject sul terreno (uno per punto di interesse) ha un
pesante effetto negativo sulle prestazioni globali.
Gli approcci testati sono stati tre, alla fine l’ultimo è stato scelto come definitivo.
5.2.1
Approccio Tramite Immagini Raster
Il primo test è stato eseguito utilizzando, per la rappresentazione di ciascun PDI, una
normalissima immagine in due dimensioni proiettata a schermo come se fosse una componente della GUI. Anche se la visualizzazione un oggetto GUI in due dimensioni potrebbe
sembrare un’operazione molto leggera, è stato comprovato che così in effetti non è, e la
motivazione è la seguente: la funzione OnGui() di Unity3D, nella quale deve essere collocato tutto il codice per la generazione di elementi grafici dell’interfaccia utente, è stata
implementata nativamente per essere eseguita due volte per ogni frame. Questa tipologia
di approccio, sebbene sia la più corretta da un punto di vista ‘ideale’, è risultata inefficiente una volta implementata: la scelta di eseguire due volte per frame il codice, la cui
sequenza di azioni è mostrata in figura 22, della GUI di Unity3D penalizza fortemente le
prestazioni generali.
Inoltre, gli elementi GUI vengono mostrati in primo piano, quindi risultava impossibile
nascondere un punto di interesse che, per esempio, si trovava dietro una montagna, e
quindi non visibile se la visuale in uso non è perpendicolare al terreno.
5.2.2
Approccio Tramite GameObject
Il secondo test è stato effettuato utilizzando dei normalissimi gameObject tridimensionali, per i quali è stato costruito un apposito modello in formato FBX rappresentante
un marker. Sebbene l’utilizzo di oggetti tridimensionali, formati da mesh complesse e
provvisti di collider potrebbe sembrare un processo molto più pesante dal punto di vista
computazionale, così in realtà non è stato riscrontrato: a differenza dell’approccio precedente, il gameObject una volta che è stato instanziato viene esclusivamente acceso/spento
all’occorrenza. Lo scaling inoltre è automatico.
La figura 23 mostra la sequenza di azioni che viene eseguita una volta per ogni frame
relativa all’implementazione dei PDI tramite GameObject.
Sebbene dal punto di vista prestazionale questa soluzione non presentava particolari
problemi, è stata scartata da un punto di vista del risultato estetico finale: le icone relative
33
Figura 22: Sequenza di azioni effettuate per la visualizzazione di PDI in formato Raster.
ai PDI sono state modellizate per essere in due dimensioni, quindi la loro applicazione su
un modello 3D produceva risultati deludenti.
5.2.3
Approccio Tramite BillBoard
La terza ed ultima soluzione testata, che poi si è rivelata la migliore in base ad un rapporto
tra qualità visiva e prestazioni, è stata quella che ha introdotto l’utilizzo delle BillBoard.
Una BillBoard non è altro che un elemento tridimensionale per la rappresentazione di
immagini in due dimensioni nello spazio. Utilizzando questo tipo di componente quindi, si
hanno i vantaggi del GameObject (profondità, collider, scaling, riproduzione una volta per
frame) ma anche quelli dell’elemento GUI, in quanto l’icona 2D viene automaticamente
ruotata a favore della telecamera. L’immagine numero 24 mostra la sequenza di azioni
che viene eseguita una volta per ogni frame relativa all’implementazione dei PDI tramite
BillBoard.
La figura numero 25 mostra un esempio a runtime dell’implementazione dei punti di
interesse: com’è possibile vedere, i PDI più vicini alla camera appaiono con uno scaling
maggiore rispetto a quelli più distanti; gli elementi parzialmente visibili sono parzialmente
visualizzati mentre alcun elemento nascosto da montagne è visualizzato.
34
Figura 23: Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando
GameObject.
5.2.4
Il Picking
Con il termine Picking (dall’inglese, raccolta) vengono indicate la serie di operazioni che
vengono effettuate quando, con l’utilizzo di un qualsiasi tipologia di puntatore a video,
che può essere sia un mouse che un tocco su un display touchscreen, vengono raccolte o
visualizzate informazioi riguardanti l’oggetto che il puntatore ha selezionato.
Elemento fondamentale per l’implementazione del picking è uno strumento messo a disposizione del GameEngine Unity3D, chiamato RayCast.
Come, dal nome stesso dell’oggetto, è facilmente intuibile, il RayCast è, letteralmente, un
raggio, che restituisce informazioni riguardante l’eventuale oggetto che colpisce durante
il suo tragitto. Per rapportarlo ad un oggetto reale, pensiamo ad un sensore ad infrarossi
per la determinazione della chiusura sicura delle porte di un ascensore: quando il raggio
viene interrotto dal passaggio di un qualsiasi oggetto, scatta un evento, la riapertura delle
porte.
Nel dettaglio, ecco i parametri necessari per instanziare un RayCast, presi direttamente
dallo script reference di Unity3D:
• origin: rappresenta l’origine del raggio. Nel contesto di utilizzo, esso è rappresentato
35
Figura 24: Sequenza di azioni effettuate per la visualizzazione di PDI utilizzando
BillBoard.
dal centro della camera.
• direction: la direzione del raggio.
• distance: la sua lunghezza, impostata per comodità ad infinito.
• hitInfo: struttura contenente le informazioni riguardanti l’oggetto o il punto colpito
(nome, posizione, distanza, ecc)
• layerMask: rappresenta l’insieme degli oggetti che devono essere coinvolti nella collisione. Ogni componente ha la possibilità di essere associato ad un layer numerico,
con il parametro layerMask è possibile specificare il gruppo di oggetti che devono
essere coinvolti nella collisione con il raggio.
Risulta quindi di facile comprensione come i RayCast siano stati fondamentali nell’implementazione del Picking sui PDI: generato un raggio, avente come layerMask l’intero
rappresentante il gruppo dei punti di interesse, su un quasiasi punto a video, in caso di
collisione con un GameObject PDI viene reperito il suo nome (codice) tramite la strut36
Figura 25: Esempio a runtime di visualizzazione di PDI utilizzando BillBoard.
tura hitInfo generata come output.
Grazie ad esso è molto semplice acquisire le informazioni aggiuntive associate.
5.2.5
Il Fattore di Scala
Una delle problematiche che sono state affrontate durante lo sviluppo del progetto è legato alla dimensione stessa di ciascun grigliato componente ogni mesh. Sappiamo che, il
modello a Tile definito in sezione 4 prevede che ogni tile figlia sia esattamente 1/4 di quella padre. La domanda che, a questo punto, dovrebbe venire spontanea dovrebbe essere
riferita alla dimensione delle primissime Tile visualizzate, chiamate anche Tile di livello
zero. La dimensione delle Tile di livello zero è definita da un parametro strettamente
legato alle mappe che vogliono essere rappresentate, chiamato Zero Level Tile Delta, del
quale abbiamo già discusso in sezione 4.0.3.
Il dataset utilizzato per il testing dell’applicazione prevedeva l’uso di immagini aeree del
Trentino, con ZLTD fissato ad 1ř e sei Tile a livello zero. Partendo quindi dal ZLTD, è
stato scelto, per un discorso riguardante la rappresentazione in scala 1:1 del territorio, di
utilizzare questo parametro come dimensione iniziale delle Tile di livello 0.
Come spesso accade, la fase di testing ha evidenziato un preciso problema riguardante
questo tipo di scelta: partendo da Tile di livello zero molto piccole, il mondo virtuale
creato avrà dimensioni ridotte, e questo ha creato un problema di stabilità numerica. I
due componenti infatti, venivano a contatto prima di aver suddiviso le Tile in maniera
tale da visualizzare i dati alla massima risoluzione disponibile.
Un ulteriore problema era legato all’utilizzo dei RayCast su oggetti di dimensioni ridotte:
è stato constatato che le collisioni non vengono più determinate sotto una determinata soglia. La soluzione più immediata tale da comportare un aumento della distanza
37
fra camera e terreno, e aumento della dimensione generale di ogni oggetto disposto, è
stata quella di immettere un fattore di scala. In questo modo è possibile ingrandire (o
rimpicciolire) il mondo virtuale in base set di dati da visualizzare.
5.2.6
La Precisione dei Float
L’introduzione di un fattore di scala ha risolto il problema indicato in sezione 5.2.5, ma
ha evidenziato la presenza di un ulteriore bug, la cui risoluzione è prevista solo negli
sviluppi futuri del progetto, in quanto prevede una completa riprogettazione del sistema
di navigazione e di creazione delle Tile.
L’intero sistema è stato progettato in maniera da collocare, nello spazio virtuale offerto
da Unity3D, i GameObject delle Tile ad una proiezione della loro effettiva posizione nel
mondo reale. In questo modo, è possibile caricare più mappe corrispondenti a porzioni
di territtorio differenti, senza che esse vengano sovrapposte.
Facciamo un esempio: considerando un fattore di scala settato ad 1, la posizione della
Tile contenente la città di Trento sarà a circa X=195 e Y=136 nel mondo di Unity3D,
corrispondente a longitudine 195 e latitudine 136 del mondo reale (coordinate normalizzate).
Supponiamo ora l’utilizzo di un fattore di scala impostato a 10: la posizione della Tile
precedente sarà in X=1950 e Y=1360 nel mondo virtuale di Unity3D.
Più grande è il fattore di scala utilizzato, più importanti sono i numeri che vengono coinvolti per la determinazione della esatta collocazione degli oggetti di scena.
Questo ha sollevato un problema nuovo: la precisione dei numeri float a singola precisione
che Unity3D utilizza per la collocazione degli elementi nel suo mondo. Per una migliore
comprensione, analizziamo la struttura dei numeri float a 32 bit, secondo lo standard
IEEE 75414 :
Un numero in virgola mobile, secondo lo standard IEEE è rappresentato su parole di 32,
64 o 128 bit divisi in tre parti:
1. un bit di segno s;
2. un campo di esponente e;
3. un campo di mantissa m.
Gli n bit di una parola sono indicizzati in modo decrescente con numeri interi da 0 a
n-1. In un numero in questo standard, l’importanza del bit decresce col suo indice.
Di seguito è rappresentato un numero in una parola di 32 bit:
14
http://en.wikipedia.org/wiki/IEEE_754
38
1
8
23
+−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+
S Esp .
Mantissa
+−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+
31 30
22
0
lunghezza in bi t
indice dei bit
Proviamo ora a convertire un numero 118.625 secondo lo standard IEEE_754: dobbiamo determinarne il segno, l’esponente e la mantissa. Poiché è un numero positivo, il
primo bit è ‘0’. Poi scriviamo il numero in forma binaria: 1110110.101. Successivamente
spostiamo la virgola verso sinistra, lasciando solo un 1 alla sua sinistra: 1110110,101 =
1,110110101 per 2 elevato alla 6. La mantissa è la parte a destra della virgola, riempita
con zeri a destra fino a riempire i 23 bit: 11011010100000000000000. L’esponente è pari a
6, ma dobbiamo convertirlo in forma binaria e adattarlo allo standard. Per la precisione
singola, dobbiamo aggiungere 127. Quindi 6 + 127 = 133. In forma binaria: 10000101.
Assemblando il tutto:
1
8
23
+−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+
S Exp
Fraction
0 10000101 11011010100000000000000
+−+−−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−+
31 30
22
0
Capiamo immediatamente come, al crescere della parte intera del numero da convertire, aumentino i bit utilizzati per rappresentare tale valore nella mantissa. Nell’esempio
precedente, per rappresentare il numero 118 sono stati occupati 6 bit, su 23. Utilizzando
un fattore di scala pari a 10, i bit della mantissa vanno utilizzati per la rappresentazione della parte intera di ciascuna posizione sono 10; con fattore a 100, essi diventano
14. Diminuiscono quindi i bit utilizzati per la conversione della parte decimale della posizione, assai più importanti quando il livello di dettaglio sale, e la dimensione di ciascuna
Tile diminuisce. Con fattori di scala e di dettaglio elevati, sono quindi sorti problemi di
sovrapposizione degli elementi, dovuti ad arrotondamenti dovuti alla precisione dei numeri float 32.
La soluzione a tale problema, la cui implementazione è attesa nei prossimi sviluppi del
software creato, citata all’inizio di questa sezione, stà nella traslazione dell’intero mondo
creato nell’origine dello spazio virtuale offerto da Unity3D.
Durante un qualsiasi movimento, non viene in realtà spostata la camera in funzione del
39
modello tridimensionale, ma esattamente il contrario: la camera rimarrà fissa nei movimento longitudinali, il terreno si sposterà in funzione di essa. In questo modo, essendo la
Tile che si stà osservando, e quelle adiacenti, in prossimità della coordinata (0,0,0) in tre
dimensioni, aumenterà la precisione della parte decimale di ogni Tile figlia, in quanto non
vengono ‘sprecati’ bit per la sua parte intera (che è, appunto, in prossimità dello zero),
risolvendo quindi in gran parte il problema che l’introduzione di un fattore di scala ha
sollevato e dell’utilizzo dei numeri float a 32 bit.
5.2.7
Inserimento/Aggiornamento PDI
Uno dei punti critici dell’intera applicazione, che vanno al di là dei puri limiti tecnologici
affrontati, è rappresentato dalla reperibilità e qualità dei dati da visualizzare. I punti di
interesse sono infatti molteplici, un numero molto elevato di oggetti per ogni categoria
e, soprattutto, la cui descrizione potrebbe variare da utente ad utente, in base all’esperienza passata: prendiamo per esempio un PDI riferito ad un hotel, potrebbe accadere
che la recensione di due utenti possa essere diametralmente opposta. Oppure, potrebbe
accadere la nascita di un nuovo punto di interesse, come per esempio la costruzione di un
nuovo museo.
Si è ritenuto quindi necessario offrire funzionalità per l’inserimento o l’aggiornamento di
un nuovo punto di interesse.
Il problema principale sorto nell’implementazione di questo tipo di feature è costituito
dal fatto che, in fase di design, è stato scelto di costruire tutte le funzionalità offerte come
blocchi a sè stanti: in questa maniera, aggiornamento ed estensione dei vari moduli in
futuro risulterà più semplice ed efficiente.
Visualizzazione ed aggiornamento/inserimento di nuovi punti di interesse costituiscono
due blocchi di codice completamente separati, facendo però riferimento alla stessa struttura dati.
In fase di inserimento di nuove informazioni sarà quindi il sistema stesso, lato server, a
decidere se accorpare i dati ad un punto di interesse già esistente, o crearne uno completamente nuovo.
Il principio secondo il quale la scelta precedentemente illustrata viene evasa si basa sul
risultato di una particolare tipologia di interrogazione che un GeoDatabase offre, chiamata query di prossimità: acquisite latitudine e longitudine tramite il sistema di input
utilizzato sulla geometria tridimensionale del territorio visualizzato, categoria e titolo del
nuovo punto di interesse, vengono cercati PDI le cui informazioni sono simili in un intorno
molto piccolo delle coordinate inserite e, in base al risultato di questa operazione, viene
deciso se il PDI è nuovo o già esistente.
40
5.3
Le Informazioni Meteoreologiche
All’interno dei requisiti funzionali del progetto SmartIslands, una parte fondamentale
riguardava la rappresentazione di informazioni metereologiche sul territorio rappresentato nel modello tridimensionale virtuale. Fortunatamente, è stata lasciata carta bianca sul
modo in cui esse potessero essere visualizzate, così è stato deciso di utilizzare dei veri e
propri modelli tridimensionali per ciascuna condizione da rappresentare.
Un informazione meteo è acquisita nella medesima maniera in cui sono scaricati i punti
di interesse, oggetti dei quali abbiamo parlato in sezione 5.2, ovviamente, la differenza
sostanziale stà che i PDI rappresentano informazioni del territorio, quindi sono collocati
su esso, così non è per i dati meteo.
Il meteo in tre dimensioni mi ha dato la possibilità di lavorare con una tipologia di
GameObject offerto da Unity3D molto interessante, chiamato Particle System, ossia
sistema di particelle.
5.3.1
I Particle Systems
I Particle Systems sono essenzialmente immagini in due dimensioni renderizzate in uno
spazio tridimensionale. Sono utilizzati principalmente per la rappresentazione di effetti
tipo fumo, fuoco, acqua, gocce, foglie ed effetti atmosferici. Un Particle completo è
formato da diversi componenti: Particle Emitter, Particle Animator ed infine un Particle
Renderer.
Il Particle Emitter genera i sistemi di particelle, l’Animator genera il movimento ed il
Renderer le visualizza a schermo. Il funzionamento di base è il seguente: visualizzando
l’immagine passata come input più volte, secondo dei parametri impostabili nell’Emitter,
è possibile creare effetti caotici utili per la rappresentazione di forme irregolari.
I parametri utilizati precedentemente, che determinano il comportamento del sistema di
particelle generato, sono i seguenti:
• Emit: parametro booleano, determina l’accensione/spegnimento del Particle.
• Min Size: rappresenta la dimensione minima di ogni particella.
• Max Size: rappresenta la dimensione massima di ogni particella.
• Min Energy: indica il tempo di vita minimo di ogni particella generata, in secondi.
• Max Energy: indica il tempo di vita massimo di ogni particella generata, in secondi.
• Min Emission: è il numero minimo di particelle emesso ogni secondo.
• Max Emission: è il numero massimo di particelle emesso ogni secondo.
41
• World Velocity: Vector3, indica la direzione, espressa come vettore, in cui le particelle sono generate.
• Ellipsoid: dimensione del sistema di particelle.
Figura 26: Esempio di Particle Emitter.
Settando questi parametri, ed associando ai Particle creati le opportune texture, sono
stati creati i tre principali eventi metereologici, le nuvole, la pioggia e la neve, la cui
intensità può essere facilmente modificata come spiegato precedentemente. Per il Sole,
invece è stata utilizzata una normalissima sfera.
In immagine 27 è possibile visionare il risultato finale dell’implementazione del meteo in
tre dimensioni.
Figura 27: 3D-Meteo: sole, pioggia e neve.
5.4
Il GeoCoding
Per geocoding si intende l’attribuzione a un dato di un’informazione relativa alla sua
dislocazione geografica o viceversa. Tale posizione è espressa in un particolare sistema
geodetico di riferimento, normalmente via latitudine e longitudine. La georeferenziazione
42
è usata nei sistemi GIS, tanto da essere applicata sostanzialmente ad ogni elemento presente: pixel componenti un’immagine raster, elementi vettoriali come punti, linee o poligoni e persino annotazioni.
Una delle funzionalità che sono state implementate ed aggiunte al Core delle operazioni
disponibili, stà nella conversione di un qualsiasi indirizzo abitativo in coordinate di latitudine e longitudine, con conseguente zoom della camera nella zona interessata.
Per effettuare questo tipo di servizio sono state utilizzate delle API fornite da Google che,
tramite una richiesta HTTP formattata come figura 28 mostra, restituiscono in risposta
un documento XML, contenente le informazioni richieste.
Un esempio XML ottenuto è mostrato in figura 29.
Figura 28: Esempio di una richiesta di GeoCoding dell’indirizzo: via Sommarive 14, Povo
(TN).
Figura 29: XML in risposta alla richiesta effettuata in figura 28.
Effettuato il parsing dell’output ottenuto, ottenute le informazioni di latitudine e
longitudine relative all’indirizzo immesso, è quindi molto semplice convertire tali dati nel
sistema di coordinate utilizzate nel mondo virtuale sviluppato, e puntare su tale locazione.
P osX = longitude + 180 ∗ GLOBALSCALE
P osZ = latitude + 90 ∗ GLOBALSCALE
43
5.5
I Percorsi Guidati
Uno dei contenuti aggiuntivi che offrono una tipologia di informazione di importanza
strategica elevatissima per un qualsiasi turista è costituito dall’implementazione di un
sistema di visualizzazione di percorsi, la cui tipologia può essere la più varia: da un
sentiero montano ad un tour enogastronomico passando per un tragitto culturale.
Un percorso è fondamentalmente costituito da due componenti:
1. Una lista di punti che identificano il tragitto;
2. una lista di tappe sensibili.
5.5.1
Il formato GPX
GPX o GPS eXchange Format15 è uno schema XML progettato per il trasferimento di dati
GPS tra applicazioni software. Può essere usato per descrivere waypoint (punti), tracce
e percorsi (routes). I suoi tag contengono queste tipologie di informazioni: location (luogo), elevation (elevazione), e time (tempo). Questo fa sì che possa essere utilizzato come
formato di interscambio fra ricevitori GPS e pacchetti software.
Il formato GPX presenta un insieme di punti che può essere senza alcuna relazione sequenziale, oppure ordinati. Nel primo caso si parla di waypoints, nel secondo di tracks
(tracce) o routes (percorsi). I waypoints sono dei singoli punti di cui si conoscono le
coordinate di latitudine e longitudine più l’elevazione (es. i numeri civici di una strada).
Tracce e routes invece si presentano come un insieme di punti sequenziali, nel primo caso
vengono archiviate informazioni spazio-temporali (= il percorso fatto dal dispositivo gps
attraverso le coordinate, l’elevazione e la marcatura temporale - timestamp), il secondo
invece si occupa solo di registrare le informazioni geografiche al fine di fornire percorsi
utili a terzi (es. sentieri o percorsi stradali o ...). Le proprietà indispensabili presenti in
un file GPX sono la latitudine e la longitudine di un singolo waypoint. Tutte le altre
variabili sono opzionali.
Di seguito è possibile visionare un estratto di file GPX contenente alcuni waypoints
riguardanti un percorso di test.
<gpx xmlns : x s i ="http : / /www. w3 . o rg /2001/XMLSchema−i n s t a n c e "
xmlns : xsd="http : / /www. w3 . or g /2001/XMLSchema" v e r s i o n ="1.1"
c r e a t o r ="Base3 . GeoTools "
xmlns="htt p : / /www. t o p o g r a f i x . com/GPX/1/1">
<trk >
<name>TNCSOLT−01</name>
15
http://www.topografix.com/gpx.asp, http://it.wikipedia.org/wiki/GPS_eXchange_Format
44
<desc>Giro Test Via S o l t e r i </desc>
<t r k s e g >
<t r k p t l a t ="46.086418870836496" l o n ="11.119476584717631" >
<e l e >219.3179931640625 </ e l e >
</t r k p t >
<t r k p t l a t ="46.086375117301941" l o n ="11.119383377954364" >
<e l e >218.8372802734375 </ e l e >
</t r k p t >
</t r k s e g >
</trk >
</gpx>
In immagine 30 è possibile vedere come un percorso viene rappresentato utilizzando i
waypoints.
Figura 30: Esempio rappresentazione di un percorso secondo il formato GPX.
5.5.2
Migrazione dei Dati
Una delle limitazioni che i file in formato gpx possiedono è che essi offrono informazioni
che non possono essere interrogate in alcun modo.
Ciò vuol dire che, una volta acquisito il file, il programmatore non può, utilizzando metodi
standard, effettuare qualsiasi tipo di query su esso.
Data questa importantissima motivazione, è stato scelto di migrare tutti i contenuti dei file
45
spaziali gpx su un database PostGreSQL al quale è stata installata l’estensione PostGIS16
per l’elaborazione dei dati georeferenziati. In questo modo, è possibile l’implementazione
di query spaziali del calibro di:
• Ottenimento di tutti i percorsi nel raggio di X metri da un punto inserito dall’utente;
• Ottenimento della distanza spaziale da un percorso ad un altro.
Per la rappresentazione di un tragitto, formato da una lista di elementi espressi in
latitudine e longitudine, PostGis mette a disposizione un’opportuna struttura dati, chiamata LINESTRING, che prende in input una lista di punti in due o tre dimensioni, senza
alcun vincolo sulla corrispondenza fra primo ed ultimo dato immesso.
LINESTRING(0 0,1 1,1 2)
Esistono fondamentalmente due tipi di LINESTRING: semplice o complessa.
1. Una LINESTRING si dice semplice se passa per ogni punto che la costituisce una
ed una sola volta;
2. una LINESTRING si dice complessa nel caso opposto, ossia quello in cui un punto
può essere percorso più volte in un singolo tragitto.
Figura 31 mostra una serie di LINESTRING semplici, figura 32 mostra LINESTRING
complesse.
Figura 31: Esempio LINESTRING di tipo semplice.
Associato ad ogni file gpx contenente i dati di ciascun percorso è stato fornito anche
un documento in formato XML di descrizione del relativo tragitto.
Le informazioni ad associate ad ogni elemento sono:
16
http://postgis.refractions.net/
46
Figura 32: Esempio LINESTRING di tipo complesso.
1. code: codice identificativo del percorso. Tramite questo codice è possibile risalire
al relativo file in formato gpx;
2. title: nome del percorso;
3. subtitle: sottotitolo del percorso;
4. length: distanza espressa in chilometri;
5. ttime: durata del tragitto effettuato a piedi, espresso in ore;
6. description: descrizione del percorso;
7. altitude: link ad un’immagine contenente la visualizzazione grafica del dislivello da
affrontare;
8. img: link ad un’immagine fotografica significativa del percorso;
9. logo: logo del comprensorio di appartenenza.
10. pdi: lista di punti di interesse, georeferenziati, associati al tragitto.
Anche questa tipologia di dati sono stati migrati su un database di tipo relazionale,
in maniera tale da poterle reperire in maniera più veloce e funzionale.
5.5.3
Rappresentazione del Percorso
La rappresentazione del percorso è stata una delle parti più complesse dell’intero progetto, in quanto la mesh identificante ogni tragitto, da costruire on-the-fly, non è basata su
una forma regolare come è stato per la costruzione dinamica delle delle Tile 4.3, dove sono
tutte di forma regolare (quadrate), ma su un percorso, con tutte le sue curve e variazioni
di altezza.
47
Il problema principale che è emerso nella costruzione dinamica di questo tipo di mesh è
nato dalla necessità di triangolizzare ciò che in realtà non è altro che una serie di segmenti, come spiegato in sezione 5.5.2. E’ stato quindi necessario sviluppare una funzione
che, presa in ingresso la lista di punti, restituisca un’altra lista, di dimensione doppia
rispetto all’originale, contenente una serie di punti che, presi due a due, includono l’elemento originale sul segmento che li unisce. In questo modo è stato possibile simulare una
sorta di ‘spessore’ della carreggiata, utilizzato per triangolizzare i vertici che compogono
la geometria del percorso da rappresentare.
In figura 33 è possibile osservare il tipo di risultato che la funzione sviluppata deve offrire
mentre il listato sottostante mostra come i nuovi dati sono stati calcolati: P0 e P1 rappresentano i punti in ingresso mentre V0, V1, V2, V3 costituiscono i quattro vertici restituiti.
Figura 33: Vertici ausiliari per la costruzione della mesh riguardante un percorso.
Vector2 d i r e c t i o n = P1 . xy − P0 . xy
Vector2 normal = n o r m a l i z e ( Vector2 ( d i r e c t i o n . y , −d i r e c t i o n . x ) )
Vector2
Vector2
Vector2
Vector2
V0
V1
V2
V3
=
=
=
=
P0 . xy
P1 . xy
P0 . xy
P1 . xy
−
−
+
+
( normal
( normal
( normal
( normal
∗
∗
∗
∗
meshDinstance )
meshDinstance )
meshDinstance )
meshDinstance )
Successivamente ogni vertice creato è stato ricollocato all’opportuna altezza, utilizzando lo strumento RayCast del quale è stato parlato in sezione 5.2.4. Ottenuti, per ogni
elemento costituente il percorso, la nuova lista di vertici, collocati all’opportuna altezza,
48
è stato possibile creare l’opportuna mesh trangolarizzando gli elementi in output, come
mostrato in figura 34.
Figura 34: Triangolarizzazione dei vertici ausiliari ottenuti in precedenza.
L’utilizzo di una mesh è dovuto al fatto che a questo tipo di struttura è associato un
gestore di collisioni calcolato sui vertici creati, utile per il picking.
5.5.4
L’Algoritmo di Douglas Peucker
Lo sviluppo ed il successivo testing dell’algoritmo di generazione automatica della mesh di
ogni percorso ha sollevato un problema molto comune nel mondo della computer grafica:
la generazione on-the-fly dei numerosi vertici necessari comporta una serie di conseguenze che limitano le prestazioni della macchina sulla quale il programma sviluppato è in
esecuzione. Se si tratta di un computer Desktop i rallentamenti risultano essere impercettibili ma, se lo stesso percorso è visualizzato su un tablet per esempio, allora i cali
di performance risultano essere significativi.
Il maggior sforzo computazionale è richiesto per calcolo delle componenti che stanno dietro la generazione di un collider, l’elemento base che permette il picking su un qualsiasi
oggetto tridimensionale tramite raycast: un collider assume la stessa identica forma della
mesh, ovviamente, quindi riducendo il numero di triangoli che compongono un percorso vengono automaticamente ridotti gli sforzi computazionali necessari per il calcolo del
collider.
E’ stato quindi necessario trovare un algoritmo in grado di filtrare, in base ad un parametro
di accuratezza, la lista dei punti componente ciascun percorso, e successivamente utilizzare il set nuovo filtrato per il calcolo dei vertici ausiliari per la costruzione della mesh:
l’algoritmo perfetto per tale scopo è quello di Douglas-Peucker.
L’algoritmo di Douglas-Peucker è un algoritmo utilizzato per la riduzione del numero
49
di punti che compongono una curva o retta, approssimandola ad una serie di segmenti. La forma iniziale di questo processo fù suggerita da Urs Ramer nel 1972, nel 1973
fù pubblicata la forma definitiva da David Douglas e Thomas Peucker. L’algoritmo è
conosciuto anche con le seguenti nominazioni: Ramer-Douglas-Peucker algorithm, iterative end-point fit algorithm o split-and-merge algorithm.
L’obiettivo di tale algoritmo è: data in ingresso una curva composta da un insieme di
segmenti, trovare una curva simile con un numero di punti minore. Il punto chiave è
basato sulla distanza che intercorre fra la curva originale e quella semplificata.
La curva semplificata consiste in un subset di punti di quell originale dove un fattore definisce il grado di approssimazione.
Di seguito è possibile visionare lo pseudocodice dell’algoritmo di Douglas-Peucker per il
campionamento dei punti17 .
f u n c t i o n DouglasPeucker ( P o i n t L i s t [ ] , e p s i l o n )
// Find th e p o i n t with t h e maximum d i s t a n c e
dmax=0
i n d e x=0
f o r i =2 t o ( l e n g t h ( P o i n t L i s t ) −1)
d=P e r p e n d i c D i s t ( P o i n t L i s t [ i ] , Line ( P o i n t L i s t [ 1 ] , P o i n t L i s t [ end ] ) )
i f d>dmax
i n d e x=i
dmax=d
end
end
// I f max d i s t a n c e i s g r e a t e r than e p s i l o n , r e c u r s i v e l y s i m p l i f y
i f dmax>=e p s i l o n
// R e c u r s i v e c a l l
r e c R e s u l t s 1 [ ] = DouglasPeucker ( P o i n t L i s t [ 1 . . . i n d e x ] , e p s i l o n )
r e c R e s u l t s 2 [ ] = DouglasPeucker ( P o i n t L i s t [ i n d e x . . . end ] , e p s i l o n )
// B u i l d t he r e s u l t l i s t
R e s u l t L i s t [ ] = { r e c R e s u l t s 1 [ 1 . . . end −1] r e c R e s u l t s 2 [ 1 . . . end ] }
else
R e s u l t L i s t [ ] = { P o i n t L i s t [ 1 ] , P o i n t L i s t [ end ] }
end
17
http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
50
// Return t he r e s u l t
return ResultList [ ]
end
Avendo avuto modo di visionare lo pseudocodice, vediamo l’algoritmo di DouglasPeucker per la riduzione dei punti all’opera, prendendo come esempio la curva visibile in
figura 35.
Figura 35: Algoritmo di Douglas-Peucker in esecuzione.
La curva con indice 0 rappresenta la curva originale mentre quella approssimta è visionabile in indice 4.
L’algoritmo, in primo luogo, marca il primo ed ultimo punto della lista in input, in
maniera tale che essi siano mantenuti nell’output finale. Successivamente viene cercato
il punto più lontano fra i due marcati nella precedente operazione.
Se la distanza fra il punto trovato e i due endpoints è maggiore del fattore , esso viene
salvato e l’algoritmo chiamato, ricorsivamente, sulla curva che va dal primo punto a quello
trovato, e dal punto trovato all’ultimo punto della curva.
Se la distanza fra il punto e gli endpoints è minore di , esso viene scartato.
Il risultato finale di tale procedura sarà un’insieme di punti, approssimanti la curva in
input tramite il fattore .
Concludendo con un cenno sulla complessità, essa è stimata per il caso peggiore con
O[n2 ].
51
Utilizzando l’algoritmo di Douglas Peucker è stato quindi possibile ridurre, con una
buona approssimazione, il numero di punti che descrivono l’andamento di un percorso:
prendiamo per esempio il percorso turistico chiamato ‘Altopiano delle Pale’, in Val di
Fiemme, nei dintorni di San Martino di Castrozza: il numero di punti del file gpx, migrato successivamente su GeoDatabase, associato a tale escursione presenta 918 punti di
controllo. Ipotizzando ora che venga utilizzato il set originale per la costruzione della
mesh, il numero di vertici finale corrisponderà al doppio del numero delle componenti
in input, quindi 1836 punti di controllo, un numero decisamente elevato. Filtrando il
dataset originale tramite l’algoritmo di Douglas Peucker, con un fattore impostato a ,
è stato possibile passare dai 918 elementi originali a soli 15 punti più significativi.
L’affermazione che ora dovrebbe nascere naturalmente deriva dalla qualità visiva della
rappresentazione nel mondo virtuale di un qualsiasi percorso: prendendo sempre per esempio l’‘Altopiano delle Pale’, sarebbe impensabile credere che la qualità di 918 punti
originali e i 15 filtrati sia la stessa. Per ovviare a questo problema è stato scelto l’utilizzo
di una componente fornita da Unity3D chiamata LineRendereer, uno strumento molto
leggero utilizzato per disegnare linee nello spazio tridimensionale. Disegnando una linea
tramite il LineRendereer utilizzando il dataset originale riguardante ciascun percorso e
collocando il risultato ottenuto sopra la mesh costruita utilizzando i vertici filtrati, alla
quale verrà applicata ua texture trasparente, è stato raggiunto un perfetto equilibrio tra
performance e gradimento visivo.
In immagine 36 è possibile vedere il risultato finale di questa serie di operazioni
applicate al percorso ‘Altopiano delle Pale’.
Figura 36: Esempio di costruzione dinamica della mesh di un percorso: i triangoli verdi
costituiscono il collider generato sui punti filtrati, in blu il LineRendereer utilizzante il
dataset originale.
In appendice, sezione C (WalkGenerator.cs), è possibile analizzare la parte di codice
riguardante la creazione della mesh di ciascun percorso e relativo LineRendereer.
52
5.5.5
Proiezione in Altezza
In determinate situazioni, sebbene ciascun percorso sia disegnato direttamente sul modello tridimensionale del terreno, potrebbe accadere che la planimetria del percorso non
sia immediatamente recepita dall’utente, quindi è stato deciso di fornire uno strumento
aggiuntivo per meglio comprendere questo tipo di informazione.
Il problema precedentemente illustrato può essere facilmente risolto costruendo una mesh
tridimensionale del percorso interessato provvisto di un fattore di altezza, ed elevata
rispetto al terreno.
Per effettuare questo tipo di operazione è stato utilizzato in buona parte il codice scritto
per la costruzione della mesh base del percorso, il cui procedimento è stato ampiamente
illustrato in sezione 5.5.3, ovviamente, con un livello di dettaglio maggiore, indicato dal
fattore impostato durante la procedura di decimazione dei punti.
Per ottenere il fattore ‘altezza’ del modello tridimensionale, è bastato duplicare ciascun
punto con un offset sommato all’asse Y di ciascun vertice duplicato.
In figura 37 è possibile vedere il risultato della proiezione in altezza del percorso ‘Altopiano
delle Pale’.
Figura 37: Esempio di costruzione dinamica della proiezione in altezza del percorso
‘Altopiano delle Pale’.
In appendice, sezione C (WalkAltitudeGenerator.cs), è possibile analizzare la parte di
codice riguardante la creazione della mesh di ciascuna proiezione in altezza.
5.5.6
Visualizzazione delle Informazioni
In sezione 5.5.2 è stato affrontato il problema della migrazione dei dati da file in formato
gpx ed xml ad un database di tipo relazionale al quale è stato installato il relativo plugin
per la gestione di dati georeferenziati. In sezione 5.5.3, è stato spiegata la costruzione
della mesh relativa ad ogni percorso, con relativo gestore delle collisioni, utile per il picking, del quale abbiamo parlato in 5.2.4.
53
In questa sezione verrà mostrato come le informazioni relative a ciascuna escusione sono
rese disponibili all’utilizzatore, unendo le conoscenze apprese in precedenza.
Considerando il fatto che il software sviluppato dovrà essere platform-indipendent è stato scelto di abilitare la visualizzazione delle informazioni aggiuntive tramite un evento
associabile sia ad un single mouseclick che ad un single touch su un display touchscreen.
Utilizzando la coordinata acquisita tramite uno dei due metodi indicati precedentemente,
verrà controllato se l’elemento corrispondente a tale posizione è uno dei percorsi abilitati (picking) e, in caso affermativo, un popup la cui struttura è mostrata in figura 38 è
visualizzato.
Figura 38: Template di riferimento per la visualizzazione delle informazioni riguardanti i
percorsi.
Interfacciandosi ai database utilizzando delle pagine PHP create appositamente per
tale scopo, è stato possibile reperire i dati relativi a ciascun percorso cui è richiesta la
visualizzazione di informazioni aggiuntive.
Prendendo sempre come esempio l’escusione utilizzata nelle precedenti sezioni, chiamata ‘Altopiano delle Pale’ ecco in figura 39 come si presenta la visualizzazione delle
informazioni complete a runtime.
Per quanto concerne la rappresentazione della lista riguardante i checkpoints, è stato
scelto di utilizzare nuovamente il materiale prodotto per i PDI, essendo le tappe sensibili
nient’altro che dei punti di interesse sul percorso, che sono stati ampiamente descritti in
sezione 5.2.
Il risultato finale dell’unione delle due componenti è mostrato in figura 40.
54
Figura 39: Esempio a runtime di visualizzaione delle informazioni riguardanti un percorso.
Figura 40: Percorso completo di tragitto e punti di interesse.
5.5.7
Attraversamento Aereo
L’ultima funzionalità che è stata sviluppata per la rappresentazione dei percorsi riguarda
l’attraversamento aereo del tragitto creato. Visualizzando questo tipo di animazione, l’utente si troverà immerso nella ricostruzione tridimensionale del territorio che circonda il
percorso selezionato, rendendosi conto del panorama che andrà a vedere affrontando tale
escursione.
Fortunatamente, Unity3D mette a disposizione uno script, il cui nome è iTween, la cui
esecuzione produce esattamente l’effetto deriderato: dato come input una serie di punti
e dei parametri quali velocità e tipo di interpolazione, è possibile muovere un qualsiasi
GameObject, in questo caso la Camera principale, lungo la curva che unisce i vertici del
path specificato.
55
Anche in questo caso, l’efficiente decimazione dei punti offerta dall’algoritmo di DouglasPeucker, impostato con un parametro molto elevato, ha permesso che la lista di checkpoint fosse composta dai soli punti più significativi del percorso, rendendo il risultato
finale più fluido.
In immagine 41 è possibile vedere un fotogramma dell’animazione effettuara sul percorso
‘Altopiano delle Pale’.
Figura 41: Fotogramma dell’animazione riguardante l’attraversamento aereo di un
percorso.
56
6
Funzionalità Sociali
I social network ormai sono divetati strumenti preziosissimi, che ogni designer di software
deve tenere in considerazione durante la progettazione del proprio prodotto. E’ infatti
chiaro che, la presenza di funzionalità per poter interagire con i principali social network,
Facebook, Twitter e Flickr, aiuta l’imposizione di un prodotto sul mercato.
Nel corso dello sviluppo del software oggetto di questa tesi, è stato temporaneamente
coinvolto solo uno dei tre social network citati precedentemente, quello che permette
l’utilizzatore di ottenere una quantità maggiore di informazioni sul territorio: Flickr.
6.1
Flickr
Flickr18 è un sito web multilingua che permette agli iscritti di condividere fotografie
personali con chiunque abbia accesso a Internet, in un ambiente web 2.0. Il sito, di
proprietà del gruppo Yahoo!19 , ha una libreria in continua espansione contando ogni
minuto più di duemila nuove foto inserite da parte dei suoi sette milioni di utenti.
Flickr è stato sviluppato dalla Ludicorp, una compagnia canadese di Vancouver fondata
nel 2002.
Nel marzo del 2005, sia la Ludicorp che Flickr sono stati comprati da Yahoo!: i server,
quindi, sono stati trasferiti dal Canada agli Stati Uniti. Il 16 maggio 2006 Flickr conclude
la fase di sviluppo beta, definendosi in stato ‘Gamma’, ossia non più in fase di prova ma
in stato di perpetua evoluzione. Il 29 dicembre 2006 sono stati ritoccati i limiti portando
a 100 MB al mese (dai precedenti 20 MB) per gli account gratuiti ed a 2 GB al mese
per gli utenti a pagamento. Nel gennaio 2007 Flickr ha completato la migrazione alla
piattaforma Yahoo! facendo trasferire forzatamente i vecchi utenti con account aperto
prima dell’acquisizione del 2005 ad un account della piattaforma Yahoo!. L’utilizzo del
servizio negli anni è cambiato parecchio. Inizialmente nato come strumento per ospitare le
proprie immagini da pubblicare su altri siti, ha avuto grande successo grazie al fenomeno
dei blog. In seguito si è evoluto, diventando esso stesso una comunità virtuale grazie ai
gruppi tematici ed ai forum; anche grazie alla crescita tecnologica ed al largo numero
di strumenti fotografici, viene utilizzato per raccogliere la digigrafia della propria vita e
rimanere aggiornati su quella dei propri conoscenti ed amici20 .
18
http://www.flickr.com/
http://www.yahoo.com/
20
http://it.wikipedia.org/wiki/Flickr
19
57
6.1.1
Integrazione
Avendo ormai compreso il funzionamento e lo scopo di Flickr passiamo a descrivere
l’integrazione del social network con il software sviluppato.
6.1.2
API di Sviluppo
Il social network Flickr mette a disposizione un completo pacchetto di API per tutti gli
sviluppatori che hanno la necessità di interagire con il loro database fotografico. Le query
vengono effettuate in maniera molto semplice ed intuitiva, contattando un loro server con
una normalissima chiamata HTTP, nella quale vengono specificati i parametri tramite
i quali la risposta ottenuta sarà filtrata. Nel dettaglio, le API che sono state utilizzate
sono le seguenti.
flickr.photos.search:
Restituisce una lista di foto le quali corrispondono a determinati criteri di filtraggio. Le
foto restituite saranno solo ed esclusivamente quelle i cui permessi in lettura sono pubblici.
Della lista di argomenti disponibili, visionabile su http://www.flickr.com/services/
api/flickr.photos.search.html, solo i seguenti parametri sono stati utilizzati in questo
contesto:
• api_key (Obbligatorio): codice numerico associato a ciascun account sviluppatore;
• min_taken_date (Facoltativo): specifica la data minima tramite la quale una foto
viene presa in considerazione. In questo modo è possibile effettuare un filtro basato
sulla data;
• bbox (Facoltativo): bounding box per le foto georeferenziate;
• has_geo (Facoltativo): abilita la ricerca per le sole immagini georeferenziate;
• page (Facoltativo): numero della pagina, in quanto le foto in risposta vengono
offerte a blocchi di 250, e spesso il loro numero totale è di gran lunga superiore a
questo limite.
Questo metodo ritorna lŠXML della lista di foto standard:
<photos page ="2" pages ="89" perpage ="10" t o t a l ="881">
<photo i d ="2636" owner ="47058503995@N01"
s e c r e t ="a123456 " s e r v e r ="2" t i t l e ="test_04 "
i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" />
58
<photo i d ="2635" owner ="47058503995@N01"
s e c r e t ="b123456 " s e r v e r ="2" t i t l e ="test_03 "
i s p u b l i c ="0" i s f r i e n d ="1" i s f a m i l y ="1" />
<photo i d ="2633" owner ="47058503995@N01"
s e c r e t ="c123456 " s e r v e r ="2" t i t l e ="test_01 "
i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" />
<photo i d ="2610" owner ="12037949754@N01"
s e c r e t ="d123456 " s e r v e r ="2" t i t l e ="00 _ t a l l "
i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" />
</photos>
Concatenando opportunamente gli attributi di ciascun elemento, secondo questa forma:
http://farmfarm-id.staticflickr.com/server-id/id_secret.jpg
è possibile ottenere l’url di ciascuna immagine che corrisponde ai criteri di ricerca specificati.
flickr.photos.getInfo:
Con questo metodo è possibile ottenere i dettagli di una foto, utilizzando il suo id come
parametro di ricerca, ottenuto dalla query precedente.
L’esempio di risposta ottenuta è presente nel listato sottostante
<photo i d ="2733" s e c r e t ="123456" s e r v e r ="12">
<owner n s i d ="12037949754@N01" username="Bees"/>
< t i t l e >o r f o r d _ c a s t l e _ t a s t e r </ t i t l e >
<d e s c r i p t i o n >h e l l o !</ d e s c r i p t i o n >
< v i s i b i l i t y i s p u b l i c ="1" i s f r i e n d ="0" i s f a m i l y ="0" />
<d a t e s p o s t e d ="1100897479" taken ="2004−11−19 12:51:19"/ >
<p e r m i s s i o n s permcomment="3" permaddmeta="2" />
< e d i t a b i l i t y cancomment="1" canaddmeta="1" />
<comments>1</comments>
<l o c a t i o n >
<l a t i t u d e ></l a t i t u d e >
<l o n g i t u d e ></l o n g i t u d e >
</ l o c a t i o n >
</photo>
59
Come è possibile osservare, per ciascuna immagine è possible acquisire una serie di
informazioni che, trattate oppurtunamente, trasformano ogni singolo elemento ottenuto
in un particolare punto di interesse, dei quali è già stato parlato in sezione 5.2. E’
stato quindi scelto di rappresentare ogni elemento scaricato come un PDI, e posizionarlo
direttamente sul territorio.
6.1.3
Filtraggio Basato sul Colore
Essendo i dati forniti da Flickr non strutturati ad-hoc per il progetto oggetto di questa
tesi, sono sorti problemi inevitabili riguardanti la quantità di dati reperibili dai server
del servizio. Infatti, il parametro principale tramite il quale le foto in risposta vengono
filtrate è costituito dal bounding box. Per comprendere meglio il problema, facciamo
riferimento all’immagine 42: come è possibile osservare, il terreno utilizzato non occupa
l’intero spazio offerto dalle Tile, lasciando numerosi spazi inutilizzati, il cui colore è rappresentato dal nero.
Figura 42: In rosso il perimetro della zona effettiva delle Tile, il restante spazio nero
rappresenta le zone in ombra facente parte del bounding box
Sebbene la porzione di territorio in nero non sia utilizzata, essa va a far parte del
bounding box di livello zero per la mappa caricata quindi, quando questi dati vengono
utilizzati per la richiesta preliminare della lista di foto georeferenziate, vengono restituiti
codici di immagini che, in effetti, andrebbero a collocarsi su zone in ombra. La quantità
di informazioni lette potrebbe essere ben superiore a quella necessarie e, sopratutto, il
rischio maggiore è quello di vedere posizionati punti di interesse fotografici su zone nere,
60
fenomeno visibile in figura .
Figura 43: Punti di interesse fotografici posizionati su zone in ombra
Per ovviare a questo tipo di problema è stata scelta una soluzione che fa del colore
posto al di sotto di ciascun PDI la sua forza: se il pixel disposto sotto ogni elemento è
di colore nero, il punto di interesse si trova al di fuori della zona di iteresse, quindi può
essere distrutto.
In figura 44, è possibile osservare il risultato finale del filtraggio basato sul colore.
Figura 44: Punti di interesse fotografici filtrati in base al colore
61
62
7
Il Sistema di Navigazione
Il secondo punto critico, cui è stato speso buona parte del tempo di realizzazione del
progetto, è stato lo studio di un sistema di navigazione che accordasse più sistemi di
input, profondamente diversi fra loro, quali mouse, tablet multitouch capacitivo e display
multitouch basato su sensori.
La decisione presa, anche tenendo conto di possibili sviluppi futuri, è stata quella di
isolare il più possibile il codice relativo alla navigazione da quello che prende in ingresso
gli input in base all’hardware utilizzato tramite l’utilizzo di varie interfacce.
La figura 45 mostra la soluzione adottata in maniera grafica.
Figura 45: Interfacce per l’acquisizione dati di movimento.
Com’è possibile vedere, un primo controllo viene effettuato in quanto il controller della
camera deve necessariamente conoscere a priori da quale sistema di input i dati verranno
passati. Questo per questioni di performace: non avrebbe senso creare collegamenti
virtuali con hardware non utilizzati. Una volta conosciuto il sistema di riferimento, inizia
l’acquisizione dei dati di navigazione, con il sistema che attende i relativi input per poi
riprodurli a video.
63
7.1
Camera Navigation Basata su Target
Unity3D, come tutti i GameEngine di ultima generazione, mette a disposizione dello
sviluppatore una struttura chiamata albero di scena. Un albero di scena rappresenta tutti gli oggetti instanziati e tutti gli oggetti che verranno instanziati a runtime, con relativo
grado di parentela. E’ proprio basato sul grado di parentela fra oggetti che il sistema di
navigazione è stato progettato e sviluppato.
Per comprendere al meglio il concetto di grado di parentela fra oggetti facciamo un esempio utilizzando oggetto che esula dal mondo della Computer Graphics e coinvolge quello
della meccanica: una macchina. Tutti conosciamo la struttura di una macchina, visionabile in maniera molto molto semplificata in figura 46: l’elemento portante, padre quindi,
dell’intera struttura è il telaio, componente fondamentale al quale verranno collegati i
restanti componenti dell’autovettura, come per esempio il motore, il gruppo di sospensioni anteriori e posteriori.
Agli ultimi due elementi elencati nel precedente periodo andrano poi attaccate la coppia
di ruote anteriori e posteriori.
Figura 46: Esempio di struttura gerarchica.
L’utilizzo di una struttura di questo tipo fornisce un importantissimo vantaggio nella
gestione dell’oggetto in questione: muovendo l’elemento padre, il telaio appunto, tutti i
restanti componenti a sè collegati, la cui posizione è referenziata al rispettivo elemento
padre, vengono spostati di conseguenza, in quanto legati gerarchicamente alla radice
dell’albero di componenti.
E’ proprio su questo principio che è stato costruito il sistema di navigazione dell’intero
software: una gerarchia di componenti, dove l’ultimo risulta essere proprio la camera in
uso, permette i movimenti di:
1. Spostamento longitudinale;
2. Rotazione secondo l’asse Y;
64
3. Rotazione secondo l’asse X;
4. Rotazione su pivot.
L’albero riferito alla camera è mostrato nel dettaglio in figura 47.
Figura 47: Struttura gerarchica del sistema di navigazione utilizzato.
Ma, purtroppo, tutto questo non si è rivelato sufficiente per l’ottenimento di risultati soddisfacenti, quindi partendo dalla struttura base descritta, si è creata un’ulteriore
struttura ad albero, identica alla precedente, denominata struttura target. La necessità
di avere una struttura target è stata data dalla possibilità di utilizzare una delle funzioni
messe a disposizione dal core di Unity3D, chiamata Lerp, della classe Vector3. Lerp è un
acronimo che stà per ‘Linear-Interpolation’, in quanto, preso in ingresso un parametro
iniziale, uno finale ed una unità di misura temporale, permette al parametro iniziale di
raggiungere quello finale nella unità di tempo indicata, con andamento lineare.
65
66
8
Natural User Interface
In Computer Science, la Natural User Interface, da qui in avanti chiamata NUI, è il
termine utilizzato da designer e sviluppatori di interfacce per software per fare riferimento
ad una interfaccia utente che è completamente invisibile, oppure lo diventa con successive
interazioni, ai suoi utenti. La parola ‘naturale’ è usata perché la maggior parte dei
computer usa dispositivi di controllo artificiale il cui funzionamento deve essere appreso.
Una NUI è rivolta ad un utente in grado di effettuare movimenti relativamente naturali,
azioni e gesti (gestures) che devono controllare lŠapplicazione del computer o manipolare
contenuti sullo schermo. La principale caratteristica di una NUI è la mancanza di una
tastiera fisica e/o mouse.
8.1
L’Evoluzione delle Interfacce Utente: da CLI a NUI
Negli anni 70, 80 e 90 furono sviluppate una serie di strategie per l’ottimizzazione di
interfacce utente che avrebbero sostituito le allora in voga, command line user interface,
CLI, e graphical user interface, GUI. Furono proprio questi gli anni in cui vennero coniati
termini Natural User Interfaces, Direct User Interfaces e Metaphor-Free Computing. Ma
fù solo il 2006 l’anno in cui Christian Moore creò una comunità di ricerca dedita all’espansione delle discussioni in merito alle tecnologie NUI. Nel 2008 invece, durante la
conferenza chiamata Predicting the Past, Microsoft dichiarò in via ufficiale come le NUI
avrebbero costituito la terza generazione di user interface.
Figura 48: Evoluzione delle interfacce utente.
Nelle command line interface, gli utenti dovevano necessariamente imparare una serie
di comandi, l’utilizzo della tastiera e di input codificati, che restringevano molto l’utilizzo
di un personal computer alle persone meno esperte e meno propense all’apprendimento.
Con l’avvento del mouse naquero le GUI, i requisiti minimi per l’apprendimento dell’uso
di un qualsiasi programma diminuirono molto, in quanto l’utilizzo di interfacce grafiche
permetteva una migliore e facile esplorazione dei contenuti. Strumenti quali il ‘Desktop’
67
o azioni tipo ‘Drag’, diventarono familiari al mondo intero.
Una NUI è rivolta ad un utente in grado di effettuare movimenti relativamente naturali,
azioni e gesti che devono controllare l’applicazione del computer o manipolare contenuti
sullo schermo.
Poiché la NUI è così veloce da imparare, l’aggettivo ’intuitivo‘ viene usato da molti per
descrivere come gli utenti interagiscono con esso. Il Consumer Electronics Show del 2010
ha dimostrato diversi nuovi prodotti che utilizzano interfacce NUI. Ora una nuova ondata
di prodotti è destinata ad essere dotata di interfacce naturali utente, dispositivi elettronici
rivolti a un pubblico sempre più ampio.
8.2
Struttura della NUI utilizzata
Il fatto che, il software progettato, debba necessariamente essere platform-indipendent,
ha comportato un inevitabile progettazione ad-hoc dell’interfaccia di gestione, tenendo
conto delle seguenti problematiche:
1. Tutte le operazioni devono essere gestite usando il single mouseclick/single touch;
2. La precisione di un mouse e di un dito è differente;
3. Un touch prolungato genera una serie di punti nell’intorno di quello ottenuto nel
primo istante il cui l’evento è stato lanciato, è quasi impossibile ottenere due volte
la stessa coordinata in frame immediatamente successivi;
4. Gli elementi sensibili dell’interfaccia devono essere sufficientemente grandi e distanti
fra loro, tale da non permettere sovrapposizione di eventi;
5. L’interfaccia deve essere naturale, il tempo dedicato all’apprendimento nell’utilizzo
minimo.
In base ai concetti precedentemente espressi, figura 49 mostra la struttura della NUI
utilizzata per la gestione delle features offerte dal sistema progettato: come è possibile osservare, le operazioni disponibili vengono attivate/disabilitate tramite l’utilizzo di
‘linguette’ disposte ai bordi del display attivabili con un semplice tocco su di esse.
Acquisito l’evento che determina l’attivazione della features indicata, verranno mostrate,
tramite un pannello scorrevole, le opzioni disponibili. Figura 50 mostra la struttura della
NUI utilizzata in modalità completamente attiva.
68
Figura 49: Screenshot raffigurante la struttura della NUI progettata in modalità all-off.
Figura 50: Screenshot raffigurante la struttura della NUI progettata in modalità all-on.
8.2.1
Menù Contestuale
Alcune features implementate, includono una serie di sotto-funzionalità che vanno oltre
alla semplice creazione di un oggetto tridimensionale a video, quindi è sorta la necessità
di creare un sistema grazie al quale l’utente possa scegliere in maniera intuitiva quale
delle funzionalità offerte abilitare o disabilitare.
Un menù che possa offrire opzioni differenti in base al contesto di utilizzo prende il nome di
‘menù contestuale’. Nell’applicazione descritta in questo documento è presente un singolo
menù di questo genere, per la gestione delle sotto-funzionalità offerte dalla visualizzazione
di percorsi:
• proiezione in altezza;
• punti di interesse;
• visuale aerea;
• informazioni generali,
delle quali abbiamo parlato in sezione 5.5.
Le opzioni vengono abilitate nel momento in cui viene effettuato il picking su una qualsiasi
69
parte del percorso. Questo tipo di evento genera il completo bloccaggio della gestione
della navigazione, in questo modo viene anche disabilitato il RayCast generato dalla
pressione effettuata su un qualsiasi bottone, in quanto esso confonderebbe l’evento come
un tentativo di picking.
Figura 51: Menù contestuale riferito ad un percorso.
70
9
9.1
Conclusioni
Campi di Impiego
Nel corso di questo elaborato abbiamo avuto modo di vedere come il sistema progettato
e realizzato abbia basato il suo intero ciclo di vita su un concetto molto importante:
flessibilità.
Nato come componente fondamentale del progetto Europeo Smart-Islands, il visualizzatore di informazioni territoriali può elaborare qualsiasi tipo di mappa passata in ingresso,
semplicemente configurando i parametri fondamentali, legati al dataset in input, necessari ad una sua corretta riproduzione.
Avendo dunque un mondo virtuale autogenerato, i possibili impieghi sono pressochè infiniti: si può passare dall’applicazione turistica che sfutta le funzionalità già implementate,
aggiornando solamente i dati da esporre, alla realizzazione di un videogioco di strategia
in tempo reale basato su scenari realmente esistenti.
Si ha quindi a disposizione un mondo, vuoto.
L’unica limitazione sul come riempirlo deriva dalla fantasia del team che ne proseguirà lo
sviluppo.
9.2
Sviluppi Futuri
Il progetto, come già detto, è pressochè allo stadio iniziale del suo sviluppo e, la flessibilità
intorno alla quale esso è stato progettato, permette una sua personalizzazione in base al
contesto in cui lo si vorrà inserire. Essendo stato scritto utilizzado un GameEngine molto
conosciuto dagli addetti ai lavori, il cui sviluppo continua inesorabile ormai da anni, è
possibile implementare aggiornamenti in maniera molto funzionale, lasciando carta bianca su possibili personalizzazioni.
Come risultato di un lavoro di tesi il risultato finale può considerarsi pienamente soddisfacente ma il software è lungi dall’essere definito perfetto: sono tutt’ora presenti problemi
non risolti.
Il primo risulta essere legato alla precisione dei numeri float a 32 bit, discusso in sezione
5.2.6. In fase di testing è stato trovata una soluzione a tale problema, ma che comporterebbe un intera riprogettazione del sistema di collocazione delle Tile, e del sistema
di navigazione. La soluzione più comoda è quella di attendere una versione di Unity3D a
64 bit.
Il secondo, la cui risoluzione è molto più semplice rispetto al problema illustrato precedentemente, è legato alla visibilità di ciascuna Tile: nella versione corrente del software
viene creata una sfera di contenimento avente raggio pari a metà diagonale di ciascun
71
tassello. Questa approssimazione non tiene conto dell’elemento altezza: è stato provato
in fase di testing infatti, che i rilievi montuosi aventi altitudini importanti fuoriescono
dalla sfera virtuale creata. Questo, in alcuni casi limite (come l’attraversamento aereo)
potrebbe comportare un errato calcolo della visibilità della Tile. La soluzione a tale problema consiste nell’effettuare il check di visilità non più su una sfera, ma su un cilindro
avente raggio pari a metà della diagonale di ogni Tile e come altezza quella massima della
geometria contenuta.
72
10
Ringraziamenti
Con questo ultimo capitolo, siamo giunti alla fine, queste sono le ultime righe che andrò
a scrivere come studente, e le volevo dedicare ai ringraziamenti.
Ai miei genitori, perchè senza di loro non avrei mai potuto essere ciò che oggi sono, anche
se in certe occasioni potrebbero non prendere questa mia frase come un complimento.
Ai miei amici, vecchi e nuovi, che mi sono stati vicini nei momenti belli e brutti, grazie
di cuore ragazzi.
Ai miei colleghi di facoltà, che ho imparato a conoscere sotto un diverso punto di vista
solo nell’ultimo anno, e di questo mi rammarico un pò.
Ai ragazzi di GraphiTech, che sono sempre stati molto gentili, ma sopratutto pazienti,
nei miei confronti.
...e ultima, ma non per importanza, vorrei ringraziare la mia professoressa di Italiano
delle scuole medie (G.G.): se avessi seguito ciò che volevi impormi, con la minaccia di una
bocciatura, adesso non sarei dove sono. Come vedi a 13 anni avevo le idee già piuttosto
chiare sul mio futuro.
73
Appendices
74
A
Tile Generation
Main.cs: costruzione ricorsiva delle Tile.
public c l a s s Main : MonoBehaviour {
private b o o l c h e c k V i s i b i l i t y ( Vector3 midPoint , f l o a t r a d i u s ) {
for ( int i =0; i <t h i s . f r u s t u m P l a n e s . Length ; i ++){
f l o a t d i n s = t h i s . f r u s t u m P l a n e s [ i ] . GetDistanceToPoint ( midPoint ) ;
i f ( d in s <−r a d i u s ) return f a l s e ;
}
return true ;
}
private b o o l c h e c k D i n s t a n c e ( T i l e t i l e ) {
Vector3 midPoint = t i l e . t i l e M i d P o i n t ;
Vector3 p o s i t i o n = Camera . main . t r a n s f o r m . p o s i t i o n ;
f l o a t d i n s = Vector3 . D i s t a n c e ( p o s i t i o n , midPoint ) ;
f l o a t logDinstanceFromcam = Mathf . Log10 ( d i n s ) ;
f l o a t l o g C e l l S i z e = Mathf . Log10 ( t i l e . s i z e ) ;
return ( l o g C e l l S i z e >= ( logDinstanceFromcam − t h i s .K) ) ;
}
private b o o l haveChildrenBIL ( T i l e p a r e n t T i l e ) {
int n e x t L e v e l = p a r e n t T i l e . l e v e l + 1 ;
/∗ F i g l i o i n b a s s o a s i n i s t r a ∗/
int c h i l d L L T i l e X = ( int ) ( ( p a r e n t T i l e . l o n g i t u d e +180)/
( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ;
int c h i l d L L T i l e Y = ( int ) ( ( p a r e n t T i l e . l a t i t u d e +90)/
( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ;
/∗ F i g l i o i n b a s s o a d e s t r a ∗/
int childLRTileX = c h i l d L L T i l e X +1;
int childLRTileY = c h i l d L L T i l e Y ;
75
/∗ F i g l i o i n a l t o a s i n i s t r a ∗/
int childULTileX = c h i l d L L T i l e X ;
int childULTileY = c h i l d L L T i l e Y +1;
/∗ F i g l i o i n a l t o a d e s t r a ∗/
int childURTileX = c h i l d L L T i l e X +1;
int childURTileY = c h i l d L L T i l e Y +1;
string
string
string
string
childLLCode
childLRCode
childULCode
childURCode
=
=
=
=
childLLTileY
childLRTileY
childULTileY
childURTileY
+
+
+
+
"_"
"_"
"_"
"_"
+
+
+
+
childLLTileX ;
childLRTileX ;
childULTileX ;
childURTileX ;
s t r i n g childLLDownloaderCode = " B i l ␣" + c h i l d L L T i l e X + "−" +
c h i l d L L T i l e Y + "−" + n e x t L e v e l ;
s t r i n g childLRDownloaderCode = " B i l ␣" + childLRTileX + "−" +
childLRTileY + "−" + n e x t L e v e l ;
s t r i n g childULDownloaderCode = " B i l ␣" + childULTileX + "−" +
childULTileY + "−" + n e x t L e v e l ;
s t r i n g childURDownloaderCode = " B i l ␣" + childURTileX + "−" +
childURTileY + "−" + n e x t L e v e l ;
s t r i n g childLLBilPath = this . bilPath
childLLTileY
s t r i n g c hi ld LR Bi lP at h = t h i s . b i l P a t h
childLRTileY
s t r i n g childULBilPath = t h i s . b i l P a t h
childULTileY
s t r i n g childURBilPath = t h i s . b i l P a t h
childURTileY
+
+
+
+
+
+
+
+
"/"
"/"
"/"
"/"
"/"
"/"
"/"
"/"
+
+
+
+
+
+
+
+
n e x t L e v e l + "/"
childLLCode ;
n e x t L e v e l + "/"
childLRCode ;
n e x t L e v e l + "/"
childULCode ;
n e x t L e v e l + "/"
childURCode ;
/∗ C o n t r o l l o s e i l BIL r i c h i e s t o Ãĺ d i s p o n i b i l e ∗/
i f ( t h i s . a v a i l a b l e B I L s . Contains ( childLLCode ) &&
t h i s . a v a i l a b l e B I L s . Contains ( childLRCode ) &&
t h i s . a v a i l a b l e B I L s . Contains ( childULCode ) &&
t h i s . a v a i l a b l e B I L s . Contains ( childURCode ) )
return true ;
76
+
+
+
+
/∗ Se non Ãĺ d i s p o n i b i l e , s e non Ãĺ i n coda d e i download ma i l
∗ f i l e e s i s t e v u o l d i r e che Ãĺ s t a t o appena s c a r i c a t o ∗/
i f ( ! t h i s . dQueue . ContainsKey ( childLLDownloaderCode ) &&
! t h i s . dQueue . ContainsKey ( childLRDownloaderCode ) &&
! t h i s . dQueue . ContainsKey ( childULDownloaderCode ) &&
! t h i s . dQueue . ContainsKey ( childURDownloaderCode ) ) {
bool
bool
bool
bool
childLLExists
childLRExists
childULExists
childURExists
=
=
=
=
File
File
File
File
. Exists ( childLLBilPath ) ;
. E x i s t s ( c hi ld LR Bi lP at h ) ;
. E x i s t s ( childULBilPath ) ;
. E x i s t s ( childURBilPath ) ;
i f ( c h i l d L L E x i s t s&&c h i l d L R E x i s t s&&c h i l d U L E x i s t s&&c h i l d U R E x i s t s ) {
a v a i l a b l e B I L s . Add( childLLCode ) ;
a v a i l a b l e B I L s . Add( childLRCode ) ;
a v a i l a b l e B I L s . Add( childULCode ) ;
a v a i l a b l e B I L s . Add( childURCode ) ;
return true ;
}
else {
i f (HAVEINTERNET) {
/∗ c o n t r o l l o d i r L e U∗/
s t r i n g dirLPath = t h i s . b i l P a t h + "/" + n e x t L e v e l +
"/" + c h i l d L L T i l e Y + "/" ;
s t r i n g dirUPath = t h i s . b i l P a t h + "/" + n e x t L e v e l +
"/" + childULTileY + "/" ;
i f ( ! D i r e c t o r y . E x i s t s ( dirLPath ) )
D i r e c t o r y . C r e a t e D i r e c t o r y ( dirLPath ) ;
i f ( ! D i r e c t o r y . E x i s t s ( dirUPath ) )
D i r e c t o r y . C r e a t e D i r e c t o r y ( dirUPath ) ;
/∗ F i g l i o i n b a s s o a s i n i s t r a ∗/
i f ( ! childLLExists )
t h i s . downloadBIL ( childLLDownloaderCode , childLLCode ) ;
77
/∗ F i g l i o i n b a s s o a d e s t r a ∗/
i f ( ! childLRExists )
t h i s . downloadBIL ( childLRDownloaderCode , childLRCode ) ;
/∗ F i g l i o i n a l t o a s i n i s t r a ∗/
i f ( ! childULExists )
t h i s . downloadBIL ( childULDownloaderCode , childULCode ) ;
/∗ F i g l i o i n a l t o a d e s t r a ∗/
i f ( ! childURExists )
t h i s . downloadBIL ( childURDownloaderCode , childURCode ) ;
}
}
}
return f a l s e ;
}
private b o o l haveChildrenIMG ( T i l e p a r e n t T i l e ) {
int n e x t L e v e l = p a r e n t T i l e . l e v e l + 1 ;
/∗ F i g l i o i n b a s s o a s i n i s t r a ∗/
int c h i l d L L T i l e X = ( int ) ( ( p a r e n t T i l e . l o n g i t u d e +180)/
( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ;
int c h i l d L L T i l e Y = ( int ) ( ( p a r e n t T i l e . l a t i t u d e +90)/
( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ;
/∗ F i g l i o i n b a s s o a d e s t r a ∗/
int childLRTileX = c h i l d L L T i l e X +1;
int childLRTileY = c h i l d L L T i l e Y ;
/∗ F i g l i o i n a l t o a s i n i s t r a ∗/
int childULTileX = c h i l d L L T i l e X ;
int childULTileY = c h i l d L L T i l e Y +1;
/∗ F i g l i o i n a l t o a d e s t r a ∗/
int childURTileX = c h i l d L L T i l e X +1;
int childURTileY = c h i l d L L T i l e Y +1;
78
string
string
string
string
childLLCode
childLRCode
childULCode
childURCode
=
=
=
=
childLLTileY
childLRTileY
childULTileY
childURTileY
+
+
+
+
"_"
"_"
"_"
"_"
+
+
+
+
childLLTileX ;
childLRTileX ;
childULTileX ;
childURTileX ;
s t r i n g childLLDownloaderCode="Img␣"+t h i s . mapName+"−"+c h i l d L L T i l e X+
"−"+c h i l d L L T i l e Y+"−"+n e x t L e v e l ;
s t r i n g childLRDownloaderCode="Img␣"+t h i s . mapName+"−"+childLRTileX+
"−"+childLRTileY+"−"+n e x t L e v e l ;
s t r i n g childULDownloaderCode="Img␣"+t h i s . mapName+"−"+childULTileX+
"−"+childULTileY+"−"+n e x t L e v e l ;
s t r i n g childURDownloaderCode="Img␣"+t h i s . mapName+"−"+childURTileX+
"−"+childURTileY+"−"+n e x t L e v e l ;
s t r i n g c h i l d L L B i l P a t h = t h i s . mapPath
childLLTileY
s t r i n g c hi ld LR Bi lP at h = t h i s . mapPath
childLRTileY
s t r i n g childULBilPath = t h i s . mapPath
childULTileY
s t r i n g childURBilPath = t h i s . mapPath
childURTileY
+
+
+
+
+
+
+
+
"/"
"/"
"/"
"/"
"/"
"/"
"/"
"/"
+
+
+
+
+
+
+
+
nextLevel +
childLLCode
nextLevel +
childLRCode
nextLevel +
childULCode
nextLevel +
childURCode
"/" +
+ " . jpg " ;
"/" +
+ " . jpg " ;
"/" +
+ " . jpg " ;
"/" +
+ " . jpg " ;
/∗ C o n t r o l l o s e i l BIL r i c h i e s t o Ãĺ d i s p o n i b i l e ∗/
i f ( t h i s . a v a i l a b l e I M G s . Contains ( childLLCode ) &&
t h i s . a v a i l a b l e I M G s . Contains ( childLRCode ) &&
t h i s . a v a i l a b l e I M G s . Contains ( childULCode ) &&
t h i s . a v a i l a b l e I M G s . Contains ( childURCode ) )
return true ;
/∗ Se non Ãĺ d i s p o n i b i l e , s e non Ãĺ i n coda d e i download ma i l
∗ e s i s t e v u o l d i r e che Ãĺ s t a t o appena s c a r i c a t o ∗/
i f ( ! t h i s . dQueue . ContainsKey ( childLLDownloaderCode ) &&
! t h i s . dQueue . ContainsKey ( childLRDownloaderCode ) &&
! t h i s . dQueue . ContainsKey ( childULDownloaderCode ) &&
79
file
! t h i s . dQueue . ContainsKey ( childURDownloaderCode ) ) {
bool
bool
bool
bool
childLLExists
childLRExists
childULExists
childURExists
=
=
=
=
File
File
File
File
. Exists ( childLLBilPath ) ;
. E x i s t s ( c hi ld LR Bi lP at h ) ;
. E x i s t s ( childULBilPath ) ;
. E x i s t s ( childURBilPath ) ;
i f ( c h i l d L L E x i s t s&&c h i l d L R E x i s t s&&c h i l d U L E x i s t s&&c h i l d U R E x i s t s ) {
a v a i l a b l e I M G s . Add( childLLCode ) ;
a v a i l a b l e I M G s . Add( childLRCode ) ;
a v a i l a b l e I M G s . Add( childULCode ) ;
a v a i l a b l e I M G s . Add( childURCode ) ;
return true ;
}
else {
i f (HAVEINTERNET) {
/∗ c o n t r o l l o d i r L e U∗/
s t r i n g dirLPath = t h i s . mapPath + "/" + n e x t L e v e l + "/" +
c h i l d L L T i l e Y + "/" ;
s t r i n g dirUPath = t h i s . mapPath + "/" + n e x t L e v e l + "/" +
childULTileY + "/" ;
i f ( ! D i r e c t o r y . E x i s t s ( dirLPath ) )
D i r e c t o r y . C r e a t e D i r e c t o r y ( dirLPath ) ;
i f ( ! D i r e c t o r y . E x i s t s ( dirUPath ) )
D i r e c t o r y . C r e a t e D i r e c t o r y ( dirUPath ) ;
/∗ F i g l i o i n b a s s o a s i n i s t r a ∗/
i f ( ! childLLExists )
t h i s . downloadIMG ( childLLDownloaderCode , childLLCode ) ;
/∗ F i g l i o i n b a s s o a d e s t r a ∗/
i f ( ! childLRExists )
t h i s . downloadIMG ( childLRDownloaderCode , childLRCode ) ;
/∗ F i g l i o i n a l t o a s i n i s t r a ∗/
i f ( ! childULExists )
80
t h i s . downloadIMG ( childULDownloaderCode , childULCode ) ;
/∗ F i g l i o i n a l t o a d e s t r a ∗/
i f ( ! childURExists )
t h i s . downloadIMG ( childURDownloaderCode , childURCode ) ;
}
}
}
return f a l s e ;
}
private void c r e a t e C h i l d r e n ( T i l e p a r e n t T i l e ) {
int n e x t L e v e l = p a r e n t T i l e . l e v e l +1;
/∗ F i g l i o i n b a s s o a s i n i s t r a ∗/
int c h i l d L L T i l e X = ( int ) ( ( p a r e n t T i l e . l o n g i t u d e +180)/
( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ;
int c h i l d L L T i l e Y = ( int ) ( ( p a r e n t T i l e . l a t i t u d e +90)/
( ( p a r e n t T i l e . z l t d ) / Mathf . Pow( 2 , n e x t L e v e l ) ) ) ;
f l o a t childLLLon = ( ( c h i l d L L T i l e X / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −180;
f l o a t chi ldLLLat = ( ( c h i l d L L T i l e Y / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −90;
T i l e c h i l d L L = new T i l e ( childLLLat , childLLLon , n e x t L e v e l ,
p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ;
a d e s t r a ∗/
c h i l d L L T i l e X +1;
childLLTileY ;
( ( childLRTileX / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −180;
f l o a t childLRLat = ( ( childLRTileY / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −90;
/∗ F i g l i o i n b a s s o
int childLRTileX =
int childLRTileY =
f l o a t childLRLon =
T i l e childLR = new T i l e ( childLRLat , childLRLon , n e x t L e v e l ,
p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ;
81
/∗ F i g l i o i n a l t o a s i n i s t r a ∗/
int childULTileX = c h i l d L L T i l e X ;
int childULTileY = c h i l d L L T i l e Y +1;
f l o a t childULLon = ( ( childULTileX / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −180;
f l o a t childULLat = ( ( childULTileY / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −90;
T i l e childUL = new T i l e ( childULLat , childULLon , n e x t L e v e l ,
p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ;
/∗ F i g l i o i n a l t o a d e s t r a ∗/
int childURTileX = c h i l d L L T i l e X +1;
int childURTileY = c h i l d L L T i l e Y +1;
f l o a t childURLon = ( ( childURTileX / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −180;
f l o a t childURLat = ( ( childURTileY / Mathf . Pow( 2 , n e x t L e v e l ) ) ∗
p a r e n t T i l e . z l t d ) −90;
T i l e childUR = new T i l e ( childURLat , childURLon , n e x t L e v e l ,
p a r e n t T i l e . z l t d , p a r e n t T i l e . t i l e C o d e , GLOBALSCALE) ;
/∗ A t t i v o r i c o r s i o n e su f i g l i ∗/
this . checkRecursion ( childLL ) ;
t h i s . c h e c k R e c u r s i o n ( childLR ) ;
t h i s . c h e c k R e c u r s i o n ( childUL ) ;
t h i s . c h e c k R e c u r s i o n ( childUR ) ;
}
private void c h e c k R e c u r s i o n ( T i l e t i l e ) {
/∗
s e c h e c k d i n s t a n c e && h o t e x t u r e d i l i v e l l o
s e ho i f i l e BIL d e i f i g l i
1) r i m u o v i da a r r a y v i s i b i l i
2) d i v i d i
82
altrimenti
1) a s s o c i a t e x t u r e
2) a c c e n d i ( d i d e f a u l t s p e n t o )
3) i n s e r i s c i n e l v e t t o r e d e i r e n d e r
altrimenti
s e NON Ãĺ i n h a s h t a b l e
a g g i u n g i i n cima l i s t a o g g e t t i
aggiungi hastable
aggiorna g l i a l t r i elementi d e l l a l i s t a s c h i f t a n d o l i in giu
aggiorno g l i a l t r i i n d i c i d e l l ’ hashtable incrementandoli
d i uno ( t r a n n e q u e l l o che ho a g g i u n t o )
1) a s s o c i a t e x t u r e
2) a c c e n d i ( d i d e f a u l t s p e n t o )
3) i n s e r i s c i n e l v e t t o r e d e i r e n d e r
∗/
b o o l i s V i s i b l e=c h e c k V i s i b i l i t y ( t i l e . t i l e M i d P o i n t , t i l e . boundingSphereRad
i f ( i s V i s i b l e ){
int n e x t L e v e l = t i l e . l e v e l + 1 ;
i f ( t h i s . c h e c k D i n s t a n c e ( t i l e ) && ( n e x t L e v e l<=t h i s . m a x A v a i l a b l e L e v e l ) ) {
i f ( haveChildrenIMG ( t i l e ) ) {
i f ( t h i s . haveChildrenBIL ( t i l e ) ) {
this . removeOnVisibles ( t i l e . tileCode ) ;
this . createChildren ( t i l e ) ;
}
else
visualizeTile ( tile );
}
else
visualizeTile ( tile );
}
else {
/∗ Se a r r i v o q u i v u o l d i r e che i l t i l e deve e s s e r e r e n d e r i z z a t o
∗
1) c o n t r o l l o s e c ’ Ãĺ n e l l a mappa
∗
2) s e non c ’ Ãĺ l o c r e o
83
∗
3) a g g i o r n o mappa , o b j e c t e v i s i b l e s
∗/
visualizeTile ( tile );
i f ( t i l e . l e v e l > this . maxVisibleLevel )
this . maxVisibleLevel = t i l e . l e v e l ;
}
}
}
private void v i s u a l i z e T i l e ( T i l e t i l e ) {
i f ( ! t h i s . map . ContainsKey ( t i l e . t i l e C o d e ) ) {
GameObject t e r r a i n ;
Quaternion ID = Quaternion . i d e n t i t y ;
t e r r a i n = I n s t a n t i a t e ( t h i s . t i l e P r e f a b , t i l e . t i l e M i d P o i n t , ID ) ;
t e r r a i n . t r a n s f o r m . l o c a l S c a l e=new Vector3 ( t i l e . s i z e , 1 , t i l e . s i z e ) ;
t e r r a i n . name = " T i l e ␣" + t i l e . t i l e C o d e ;
t e r r a i n . transform . parent = this . Layer0Container . transform ;
this . insertOnBuffer ( t e r r a i n ) ;
t h i s . insertOnMap ( t i l e . t i l e C o d e ) ;
}
i f ( ! t h i s . v i s i b l e s . Contains ( t i l e . t i l e C o d e ) ) {
t h i s . applyTexture ( t i l e ) ;
this . i n s e r t O n V i s i b l e s ( t i l e . tileCode ) ;
t h i s . GizmosTilesArray . Add( t i l e ) ;
}
}
// Update i s c a l l e d once per frame
void Update ( ) {
f r u s t u m P l a n e s = G e o m e t r y U t i l i t y . C a l c u l a t e F r u s t u m P l a n e s ( Camera . main ) ;
int l v 0 = 0 ;
for ( int i =0; i <t h i s . L e v e l 0 T i l e s . Count ; i ++){
s t r i n g name = ( s t r i n g ) t h i s . L e v e l 0 T i l e s [ i ] ;
int t i l e X = System . Convert . ToInt32 ( ( s t r i n g ) ( name . S p l i t ( ’_ ’ ) ) [ 1 ] ) ;
84
int t i l e Y = System . Convert . ToInt32 ( ( s t r i n g ) ( name . S p l i t ( ’_ ’ ) ) [ 0 ] ) ;
f l o a t l o n = ( ( t i l e X / Mathf . Pow( 2 , l v 0 ) ) ∗ t h i s . z l t d ) −180;
f l o a t l a t = ( ( t i l e Y / Mathf . Pow( 2 , l v 0 ) ) ∗ t h i s . z l t d ) −90;
string
string
string
string
string
code = t i l e Y + "_" + t i l e X ;
b i l C o d e = " B i l ␣" + t i l e X + "−" + t i l e Y + "−" + l v 0 ;
imgCode = "Img␣"+t h i s . mapName+"−"+t i l e X+"−"+t i l e Y+"−"+l v 0 ;
b i l P a t h= t h i s . b i l P a t h+"/"+l v 0+"/"+t i l e Y+"/"+code ;
imgPath = t h i s . mapPath+"/"+l v 0+"/"+t i l e Y+"/"+code+" . j p g " ;
i f ( t h i s . a v a i l a b l e I M G s . Contains ( code ) ) {
i f ( t h i s . a v a i l a b l e B I L s . Contains ( code ) ) {
T i l e t i l e = new T i l e ( l a t , lon , lv0 , t h i s . z l t d , null ,GLOBALSCALE) ;
this . checkRecursion ( t i l e ) ;
}
else {
i f ( ! t h i s . dQueue . ContainsKey ( b i l C o d e ) )
i f ( File . Exists ( bilPath ))
t h i s . a v a i l a b l e B I L s . Add( code ) ;
else {
t h i s . downloadBIL ( bilCode , code ) ;
}
}
}
else {
i f ( ! t h i s . dQueue . ContainsKey ( imgCode ) )
i f ( F i l e . E x i s t s ( imgPath ) )
t h i s . a v a i l a b l e I M G s . Add( code ) ;
else {
t h i s . downloadIMG ( imgCode , code ) ;
}
}
}
}
}
85
86
B
BIL Reading
BilReader.cs: lettura dei file DTM in formato BIL.
public c l a s s MeshGenerator : MonoBehaviour {
private void l o a d A L t i t u d e s ( ) {
t h i s . a l t i t u d e s = new f l o a t [ h e i g h t ∗ width ] ;
byte [ ] byteArray = t h i s . r e a d F i l e ( t h i s . BILpath ) ; ;
// Questo c o n t r o l l o s e r v e s o l o per l e t i l e d i l i v e l l o 0 ,
// i n quanto non hanno padre , d i c o n s e g u e n z a i c o n t r o l l i
// che vengono f a t t i s u i f i g l i i n HavechildBIL vengono s a l t a t i
// e c ’ Ãĺ i l r i s c h i o che s i acceda a l f i l e BIL prima che e s s o
// s i a s c a r i c a t o completamente
while ( byteArray==null ) {
System . Threading . Thread . S l e e p ( 1 0 ) ;
byteArray = t h i s . r e a d F i l e ( t h i s . BILpath ) ;
}
f l o a t [ ] a l t i t u d e A r r a y = new f l o a t [ h e i g h t ∗ width ] ;
// R i f l e t t o i n X per a v e r e c o r r i s p o n d e n z a 1 : 1 con l a mia g r i g l i a
int i n d e x =0;
for ( int i =( byteArray . Length /2) −1; i >=0; i −−){
a l t i t u d e A r r a y [ i ]= System . B i t C o n v e r t e r . ToInt16 ( byteArray , i n d e x ) ;
i n d e x +=2;
}
// R i f l e t t o i n Y per a v e r e c o r r i s p o n d e n z a 1 : 1 con l a mia g r i g l i a
for ( int y=0;y<h e i g h t ; y++){
for ( int x=0;x<width ; x++){
a l t i t u d e s [ y∗ width+x]= a l t i t u d e A r r a y [ y∗ width+(width−1−x ) ] ;
}
}
}
87
public byte [ ] r e a d F i l e ( s t r i n g f i l e P a t h ) {
byte [ ] b u f f e r ;
int count ;
int sum = 0 ;
try {
FileStream f i l e S t r e a ;
f i l e S t r e a m=new F i l e S t r e a m ( f i l e P a t h , FileMode . Open , F i l e A c c e s s . Read ) ;
int l e n g t h = ( int ) f i l e S t r e a m . Length ;
b u f f e r = new byte [ l e n g t h ] ;
while ( ( count=f i l e S t r e a m . Read ( b u f f e r , sum , l e n g t h −sum)) >0)
sum += count ;
f i l e S t re a m . Close ( ) ;
return b u f f e r ;
} catch ( IOException e ) { Debug . Log ( e . Message ) ; }
return null ;
}
}
88
C
Mesh Generation
MeshGenerator.cs: costruzione Tile mesh.
public c l a s s MeshGenerator : MonoBehaviour {
private void generateHeightmap ( ) {
// A c q u i s i z i o n e d e l l a mesh d a l component
Mesh mesh = GetComponent<M e s h F i l t e r > ( ) . mesh ;
int i n d e x=h e i g h t ∗ width ;
// C o s t r u i s c o i v e r t i c i e l e uv ( per l e t e x t u r e )
Vector3 [ ] v e r t i c e s ;
Vector2 [ ] uv ;
i f ( a c t i v a t e S k i r t s ){
v e r t i c e s=new Vector3 [ ( h e i g h t ∗ width )+(2∗ h e i g h t +2∗width ) ] ;
uv=new Vector2 [ ( h e i g h t ∗ width )+(2∗ h e i g h t +2∗width ) ] ;
}
else {
v e r t i c e s=new Vector3 [ ( h e i g h t ∗ width ) ] ;
uv=new Vector2 [ ( h e i g h t ∗ width ) ] ;
}
int [ ] s k i r t=new int [ h e i g h t ∗ width ] ;
// C o s t r u z i o n e d e i v e r t i c i d e l piano
for ( int y=0;y<h e i g h t ; y++){
for ( int x=0;x<width ; x++){
// Normalizzo X ed Y i n maniera t a l e da f a r l i e n t r a r e
// i n un range compreso f r a 0 ed 1 , c o s i ho i l piano 1 x1
f l o a t s c a l e d X =( f l o a t ) x / ( f l o a t ) ( width −1);
f l o a t s c a l e d Y =( f l o a t ) y / ( f l o a t ) ( h e i g h t −1);
f l o a t a l t i t u d e =( t h i s . a l t i t u d e s [ y∗ width + x ] / 1 0 0 0 ) / a l t S c a l e ;
89
// Se vado o l t r e i l bbox i l s e r v e r mi r e s t i t u i s c e
// a l t e z z a −3000 mt
i f ( a l t i t u d e <0) a l t i t u d e =0.0 f ;
v e r t i c e s [ y∗ width+x]=new Vector3 ( scaledX , a l t i t u d e , s c a l e d Y )+
new Vector3 ( −0.5 f , 0 , − 0 . 5 f ) ;
uv [ y∗ width+x]=new Vector2 ( scaledX , s c a l e d Y ) ;
i f ( a c t i v a t e S k i r t s ){
// C o s t r u z i o n e d e l l a s k i r t
i f ( ( x ==0)||( x==width − 1 ) | | ( y ==0)||( y==h e i g h t −1)){
v e r t i c e s [ i n d e x ]=new Vector3 ( scaledX ,− s k i r t S i z e , s c a l e d Y )+
new Vector3 ( −0.5 f , 0 f , −0.5 f ) ;
uv [ i n d e x ]=new Vector2 ( scaledX , s c a l e d Y ) ;
s k i r t [ y∗ width+x]= i n d e x ;
i n d e x++;
}
else
s k i r t [ y∗ width+x]= −1;
}
}
}
// B u i l d t r i a n g l e i n d i c e s : 3 i n d i c e s i n t o
// v e r t e x a r r a y f o r each t r i a n g l e
int [ ] t r i a n g l e s ;
if ( activateSkirts )
t r i a n g l e s=new int [ ( ( h e i g h t −1)∗( width −1)∗6)+
( ( 2 ∗ ( h e i g h t −1)+2∗( width − 1 ) ) ∗ 6 ) ] ;
else
t r i a n g l e s=new int [ ( ( h e i g h t −1)∗( width − 1 ) ∗ 6 ) ] ;
i n d e x =0;
for ( int y=0;y<h e i g h t −1;y++){
for ( int x=0;x<width −1;x++){
90
// For each g r i d c e l l o u t p u t two t r i a n g l e s
t r i a n g l e s [ i n d e x ++]=(y∗ width)+ x ;
t r i a n g l e s [ i n d e x ++]=((y+1)∗ width)+x ;
t r i a n g l e s [ i n d e x ++]=(y∗ width)+x+1;
t r i a n g l e s [ i n d e x ++]=((y+1)∗ width)+x ;
t r i a n g l e s [ i n d e x ++]=((y+1)∗ width)+x+1;
t r i a n g l e s [ i n d e x ++]=(y∗ width)+x+1;
}
}
i f ( a c t i v a t e S k i r t s ){
// S k i r t prima ed u l t i m a r i g a
i n d e x =(( h e i g h t −1)∗( width − 1 ) ∗ 6 ) ;
for ( int x=0;x<width −1;x++){
// S k i r t prima r i g a
t r i a n g l e s [ i n d e x++]= s k i r t [ x ] ;
t r i a n g l e s [ i n d e x++]=x ;
t r i a n g l e s [ i n d e x++]= s k i r t [ x + 1 ] ;
t r i a n g l e s [ i n d e x++]=x ;
t r i a n g l e s [ i n d e x++]=x+1;
t r i a n g l e s [ i n d e x++]= s k i r t [ x + 1 ] ;
// S k i r t u l t i m a r i g a
t r i a n g l e s [ i n d e x++]=x+(width ∗( h e i g h t − 1 ) ) ;
t r i a n g l e s [ i n d e x++]= s k i r t [ x+(width ∗( h e i g h t − 1 ) ) ] ;
t r i a n g l e s [ i n d e x++]=x+1+(width ∗( h e i g h t − 1 ) ) ;
t r i a n g l e s [ i n d e x++]= s k i r t [ x+(width ∗( h e i g h t − 1 ) ) ] ;
t r i a n g l e s [ i n d e x++]= s k i r t [ x+1+(width ∗( h e i g h t − 1 ) ) ] ;
t r i a n g l e s [ i n d e x++]=x+1+(width ∗( h e i g h t − 1 ) ) ;
}
// S k i r t prima ed u l t i m a c o l o n n a
for ( int y=0;y<=(width ∗( h e i g h t − 2 ) ) ; y+=width ) {
// S k i r t prima c o l o n n a
91
t r i a n g l e s [ i n d e x++]= s k i r t [ y ] ;
t r i a n g l e s [ i n d e x++]= s k i r t [ y+width ] ;
t r i a n g l e s [ i n d e x++]=y ;
t r i a n g l e s [ i n d e x++]= s k i r t [ y+width ] ;
t r i a n g l e s [ i n d e x++]=y+width ;
t r i a n g l e s [ i n d e x++]=y ;
// S k i r t u l t i m a c o l o n n a
t r i a n g l e s [ i n d e x++]=y+width −1;
t r i a n g l e s [ i n d e x++]=y+2∗width −1;
t r i a n g l e s [ i n d e x++]= s k i r t [ y+width − 1 ] ;
t r i a n g l e s [ i n d e x++]=y+2∗width −1;
t r i a n g l e s [ i n d e x++]= s k i r t [ y+2∗width − 1 ] ;
t r i a n g l e s [ i n d e x++]= s k i r t [ y+width − 1 ] ;
}
}
mesh . v e r t i c e s=v e r t i c e s ;
mesh . uv=uv ;
mesh . t r i a n g l e s=t r i a n g l e s ;
mesh . R e c a l c u l a t e N o r m a l s ( ) ;
GetComponent<M e s h C o l l i d e r > ( ) . sharedMesh=mesh ;
}
}
92
Layer.cs: duplicazione della mesh di ciascuna Tile.
public c l a s s Layer : MonoBehaviour {
// Update i s c a l l e d once per frame
void Update ( ) {
t h i s . v i s i b l e s B u f f e r=main . v i s i b l e s B u f f e r ;
t h i s . v i s i b l e s=main . v i s i b l e s ;
for ( int i =0; i <v i s i b l e s . Count ; i ++){
s t r i n g code=( s t r i n g ) t h i s . v i s i b l e s [ i ] ;
i f ( ! t h i s . map . ContainsKey ( code ) ) {
int i n d e x =( int ) main . map [ code ] ;
GameObject t i l e =(GameObject ) main . b u f f e r [ i n d e x ] ;
MeshGenerator meshT ;
meshT= t i l e . GetComponent ( " MeshGenerator " )
as MeshGenerator ;
b o o l isDone=meshT . isDone ;
i f ( isDone ) {
i f ( t h i s . haveImage ( code ) ) {
GameObject l a y e r ;
l a y e r=I n s t a n t i a t e ( t h i s . l a y e r P r e f a b ,
t i l e . transform . position ,
Quaternion . i d e n t i t y ) as GameObject ;
l a y e r . t r a n s f o r m . l o c a l S c a l e= t i l e . t r a n s f o r m . l o c a l S c a l e ;
l a y e r . name=" Layer ␣"+code ;
l a y e r . t r a n s f o r m . p a r e n t=t h i s . L a y e r C o n t a i n e r . t r a n s f o r m ;
Mesh mesh=t e r r a i n . GetComponent<M e s h F i l t e r > ( ) . mesh ;
l a y e r . GetComponent<M e s h F i l t e r > ( ) . sharedMesh=mesh ;
this . insertOnBuffer ( l a y e r ) ;
t h i s . insertOnMap ( code ) ;
t h i s . applyTexture ( l a y e r ) ;
}
}
}
}
}
}
93
WalkGenerator.cs: generazione collider per ciascun percorso e LineRendereer.
public c l a s s WalkGenerator : MonoBehaviour {
private A r r a y L i s t c a l c u l a t e W i d e L i n e ( A r r a y L i s t d e c i m a t e d P o i n t s ) {
int s i z e = d e c i m a t e d P o i n t s . Count ;
A r r a y L i s t r e s u l t = new A r r a y L i s t ( ) ;
for ( int i =1; i <s i z e ; i ++){
Vector2 p1=( Vector2 ) d e c i m a t e d P o i n t s [ i − 1 ] ;
Vector2 p2=( Vector2 ) d e c i m a t e d P o i n t s [ i ] ;
Vector2 d i r=p2−p1 ;
Vector2 normal=(new Vector2 ( d i r . y,− d i r . x ) ) . n o r m a l i z e d ;
Vector2
Vector2
Vector2
Vector2
result
result
result
result
v0=p1−(normal ∗walkWidth ) ;
v1=p2−(normal ∗walkWidth ) ;
v2=p1+(normal ∗walkWidth ) ;
v3=p2+(normal ∗walkWidth ) ;
. Add( v0 ) ;
. Add( v2 ) ;
. Add( v1 ) ;
. Add( v3 ) ;
}
return r e s u l t ;
}
private void g e n e r a t e C o l l i d e r ( ) {
// A c q u i s i z i o n e d e l l a mesh d a l component
Mesh mesh = GetComponent<M e s h F i l t e r > ( ) . mesh ;
A r r a y L i s t c u r r e n t = new A r r a y L i s t ( ) ;
// C o s t r u i s c o i v e r t i c i e l e uv ( per l e t e x t u r e )
L i s t <Vector3> v e r t i c e s = new L i s t <Vector3 > ( ) ;
L i s t <Vector2> uv = new L i s t <Vector2 > ( ) ;
for ( int i =0; i <c o l l i d e r P o i n t s . Count ; i ++){
Vector2 p o i n t = ( Vector2 ) c o l l i d e r P o i n t s [ i ] ;
94
float l a t i t u d e = point . x ;
float longitude = point . y ;
f l o a t posX=( l o n g i t u d e +180)∗GLOBALSCALE;
f l o a t posZ=( l a t i t u d e +90)∗GLOBALSCALE;
Vector3 o r i g i n = new Vector3 ( posX , 1 0 f , posZ ) ;
Vector3 d i r e c t i o n = −Vector3 . up ;
Ray ray = new Ray ( o r i g i n , d i r e c t i o n ) ;
RaycastHit h i t ;
float altitude = 0.0 f ;
int layerMask = ( 1 << 1 0 ) ;
i f ( P h y s i c s . Raycast ( ray , out h i t , Mathf . I n f i n i t y , layerMask ) ) {
a l t i t u d e = hit . point . y ;
}
v e r t i c e s . Add(new Vector3 ( posX , a l t i t u d e , posZ ) ) ;
uv . Add(new Vector2 ( posX , posZ ) ) ;
}
L i s t <int> t r i a n g l e s = new L i s t <int > ( ) ;
for ( int i =0; i <c o l l i d e r P o i n t s . Count −2; i ++){
// For each g r i d c e l l o u t p u t two t r i a n g l e s
t r i a n g l e s . Add( i ) ;
t r i a n g l e s . Add( i +2);
t r i a n g l e s . Add( i +1);
}
mesh . v e r t i c e s = v e r t i c e s . ToArray ( ) ;
mesh . uv = uv . ToArray ( ) ;
mesh . t r i a n g l e s = t r i a n g l e s . ToArray ( ) ;
mesh . R e c a l c u l a t e N o r m a l s ( ) ;
GetComponent<M e s h C o l l i d e r > ( ) . sharedMesh = mesh ;
i f ( ! walk . c u r r e n t T i l e s M a p . ContainsKey ( code ) )
95
walk . c u r r e n t T i l e s M a p . Add( code , c u r r e n t ) ;
else
walk . c u r r e n t T i l e s M a p [ code ] = c u r r e n t ;
}
private void g e n e r a t e L i n e ( ) {
L i n e R e n d e r er l i n e = GetComponent<LineRenderer > ( ) ;
A r r a y L i s t a l t i t u d e s = new A r r a y L i s t ( ) ;
int s i z e = t h i s . l i n e P o i n t s . Count ;
l i n e . SetVertexCount ( s i z e ) ;
for ( int i =0; i <s i z e ; i ++){
Vector2 p o i n t = ( Vector2 ) t h i s . l i n e P o i n t s [ i ] ;
float
float
float
float
l a t i t u d e = point . x ;
longitude = point . y ;
posX=( l o n g i t u d e +180)∗GLOBALSCALE;
posZ=( l a t i t u d e +90)∗GLOBALSCALE;
Vector3 o r i g i n = new Vector3 ( posX , 1 0 f , posZ ) ;
Vector3 d i r e c t i o n = −Vector3 . up ;
Ray ray = new Ray ( o r i g i n , d i r e c t i o n ) ;
RaycastHit h i t ;
float altitude = 0.0 f ;
int layerMask = ( 1 << 1 0 ) ;
i f ( P h y s i c s . Raycast ( ray , out h i t , Mathf . I n f i n i t y , layerMask ) ) {
a l t i t u d e = hit . point . y ;
l i n e . S e t P o s i t i o n ( i , new Vector3 ( posX , a l t i t u d e , posZ ) ) ;
}
}
}
}
96
WalkAltitudeGenerator.cs: generazione mesh riguardante la proiezione in altezza di
ciascun percorso.
public c l a s s WalkAltitudeGenerator : MonoBehaviour {
private A r r a y L i s t c a l c u l a t e W i d e L i n e ( A r r a y L i s t d e c i m a t e d P o i n t s ) {
int s i z e = d e c i m a t e d P o i n t s . Count ;
A r r a y L i s t r e s u l t = new A r r a y L i s t ( ) ;
for ( int i =1; i <s i z e ; i ++){
Vector2 p1 = ( Vector2 ) d e c i m a t e d P o i n t s [ i − 1 ] ;
Vector2 p2 = ( Vector2 ) d e c i m a t e d P o i n t s [ i ] ;
Vector2 d i r = p2−p1 ;
Vector2 normal = (new Vector2 ( d i r . y , −d i r . x ) ) . n o r m a l i z e d ;
Vector2
Vector2
Vector2
Vector2
result
result
result
result
v0
v1
v2
v3
=
=
=
=
p1
p2
p1
p2
−
−
+
+
( normal
( normal
( normal
( normal
∗
∗
∗
∗
walkWidth ) ;
walkWidth ) ;
walkWidth ) ;
walkWidth ) ;
. Add( v0 ) ;
. Add( v2 ) ;
. Add( v1 ) ;
. Add( v3 ) ;
}
return r e s u l t ;
}
private void generateMesh ( ) {
// A c q u i s i z i o n e d e l l a mesh d a l component
Mesh mesh = GetComponent<M e s h F i l t e r > ( ) . mesh ;
ArrayList a l t i t u d e s = walkInfo . a l t i t u d e s ;
// C o s t r u i s c o i v e r t i c i e l e uv ( per l e t e x t u r e )
L i s t <Vector3> v e r t i c e s = new L i s t <Vector3 > ( ) ;
L i s t <Vector2> uv = new L i s t <Vector2 > ( ) ;
int i n d e x = 0 ;
97
for ( int i =0; i <meshPoints . Count ; i ++){
Vector2 p o i n t = ( Vector2 ) meshPoints [ i ] ;
float
float
float
float
float
l a t i t u d e = point . x ;
longitude = point . y ;
posX=( l o n g i t u d e +180)∗GLOBALSCALE;
posZ=( l a t i t u d e +90)∗GLOBALSCALE;
a l t i t u d e = ( f l o a t ) a l t i t u d e s [ i n d e x ]+ a l t i t u d e O f f s e t ;
v e r t i c e s . Add(new Vector3 ( posX , a l t i t u d e , posZ ) ) ;
uv . Add(new Vector2 ( posX , posZ ) ) ;
v e r t i c e s . Add(new Vector3 ( posX , a l t i t u d e+walkHeight , posZ ) ) ;
uv . Add(new Vector2 ( posX , posZ ) ) ;
i f ( i %4==0)i n d e x++;
}
L i s t <int> t r i a n g l e s = new L i s t <int > ( ) ;
/∗
∗ 1−−−−3
∗ |
|
∗ 0−−−−2
∗/
for ( int i =0; i <(meshPoints . Count ∗2) −6; i +=4){
// Lato 0−2
t r i a n g l e s . Add( i ) ;
t r i a n g l e s . Add( i +2);
t r i a n g l e s . Add( i +4);
t r i a n g l e s . Add( i +2);
t r i a n g l e s . Add( i +6);
t r i a n g l e s . Add( i +4);
// Lato 1−3
t r i a n g l e s . Add( i +1);
t r i a n g l e s . Add( i +5);
t r i a n g l e s . Add( i +3);
t r i a n g l e s . Add( i +3);
t r i a n g l e s . Add( i +5);
98
t r i a n g l e s . Add( i +7);
// Lato 0−1
t r i a n g l e s . Add( i ) ;
t r i a n g l e s . Add( i +4);
t r i a n g l e s . Add( i +1);
t r i a n g l e s . Add( i +4);
t r i a n g l e s . Add( i +5);
t r i a n g l e s . Add( i +1);
// Lato 2−3
t r i a n g l e s . Add( i +2);
t r i a n g l e s . Add( i +3);
t r i a n g l e s . Add( i +6);
t r i a n g l e s . Add( i +6);
t r i a n g l e s . Add( i +3);
t r i a n g l e s . Add( i +7);
// Lato d a v a n t i
t r i a n g l e s . Add( i ) ;
t r i a n g l e s . Add( i +1);
t r i a n g l e s . Add( i +2);
t r i a n g l e s . Add( i +2);
t r i a n g l e s . Add( i +1);
t r i a n g l e s . Add( i +3);
// d i e t r o
t r i a n g l e s . Add( i +4);
t r i a n g l e s . Add( i +6);
t r i a n g l e s . Add( i +5);
t r i a n g l e s . Add( i +5);
t r i a n g l e s . Add( i +6);
t r i a n g l e s . Add( i +7);
}
mesh . v e r t i c e s = v e r t i c e s . ToArray ( ) ;
mesh . uv = uv . ToArray ( ) ;
mesh . t r i a n g l e s = t r i a n g l e s . ToArray ( ) ;
mesh . R e c a l c u l a t e N o r m a l s ( ) ;
}
}
99
100
D
Flickr Photo Downloader
getFlickr.php: download informazioni riguardanti le immagini fotografiche su Flickr.
<?php
f u n c t i o n getImgs ( $apiKey , $bbox , $page , $ c a t e g o r y , $ c o m p r e n s o r i o ) {
$ u r l = " h ttp : / / a p i . f l i c k r . com/ s e r v i c e s / r e s t /? method=
␣␣␣␣ f l i c k r . photos . s e a r c h&api_key=" . $apiKey . "&bbox="
. $bbox . "&page=" . $page . "&has_geo=1&s o r t=r e l e v a n c e
␣␣␣␣&min_taken_date =2005 −01 −01%2000:00:00 " ;
$xml = s i m p l e x m l _ l o a d _ f i l e ( $ u r l ) ;
$photo = $xml−>xpath ( ’ // photo ’ ) ;
$count = count ( $photo ) ;
$ s t r i n g = "" ;
for ( $ i =0; $ i <$count ; $ i ++){
$ s e r v e r = $photo [ $ i ] [ " s e r v e r " ] ;
$farm = $photo [ $ i ] [ " farm " ] ;
$code = $photo [ $ i ] [ " i d " ] ;
$ s e c r e t = $photo [ $ i ] [ " s e c r e t " ] ;
$img = " http : / / farm " . $farm . " . s t a t i c . f l i c k r . com/"
. $ s e r v e r . "/" . $code . "_" . $ s e c r e t . " . j p g " ;
$icon = getIcon ( $category , $comprensorio ) ;
$ s t r i n g .= $code . " | " ;
$ s t r i n g .= $ i c o n . " | " ;
$ s t r i n g .= $img . "\n" ;
}
$ s t r i n g = substr ( $ s t r i n g , 0 , s t r l e n ( $ s t r i n g ) −1);
echo $ s t r i n g ;
}
101
f u n c t i o n g e t I n f o s ( $apiKey , $code ) {
$ u r l = " h ttp : / / a p i . f l i c k r . com/ s e r v i c e s / r e s t /? method=
␣␣␣␣ f l i c k r . photos . g e t I n f o&api_key=" . $apiKey .
"&photo_id=" . $code ;
$info = simplexml_load_file ( $url ) ;
$ t i t = $ i n f o −>xpath ( ’ // t i t l e ’ ) ;
$ d e s c = $ i n f o −>xpath ( ’ // d e s c r i p t i o n ’ ) ;
$ t i t l e = s t r _ r e p l a c e ( " | " , "−" , $ t i t [ 0 ] ) ;
$ d e s c r i p t i o n = s t r _ r e p l a c e ( " | " , "−" , $ d e s c [ 0 ] ) ;
i f ( $ t i t l e == "" ) $ t i t l e = " Nessun ␣ T i t o l o " ;
i f ( $ d e s c r i p t i o n == "" ) $ d e s c r i p t i o n = " Nessuna ␣ D e s c r i z i o n e " ;
$ l o c a t i o n = $ i n f o −>xpath ( ’ // l o c a t i o n ’ ) ;
$latitude = $location [ 0 ] [ " latitude " ] ;
$longitude = $location [ 0 ] [ " longitude " ] ;
$string
$string
$string
$string
.=
.=
.=
.=
$latitude . " | " ;
$longitude . " | " ;
$title . "|";
$description ;
$ s t r i n g = substr ( $ s t r i n g , 0 , s t r l e n ( $ s t r i n g ) −1);
echo $ s t r i n g ;
}
?>
102