Introduzione
Se parliamo di applicazioni desktop tradizionali, Google Maps non è un'opzione dal momento che il suo contesto è quello della pagine web dinamiche. Google Earth, invece, non è open source e non possiamo metterci le mani dentro. NASA World Wind for Java, o WWJ in breve, è un prodotto alternativo (in .NET esiste un reale competiore di Google Earth, mentre in Java per ora c'è solo un SDK corredato da alcuni esempi) e open source, che possiamo usare liberamente. In questo articolo ne introduciamo gli aspetti principali, effettuando una carrellata su alcune demo distribuite con i sorgenti.
Alcune note preliminari sul deployment
Per prendere velocemente confidenza con WWJ è opportuno dare un'occhiata alla decina di piccole applicazioni d'esempio contenute nella distribuzione. Partiamo quindi con lo scaricare lo .zip con i sorgenti da http://worldwind.arc.nasa.gov/java (nel momento in cui scrivo è disponibile la versione 0.3.0). Chi ha particolarmente fretta può anche lanciare la versione in Java WebStart, ma in questo caso vedrà solo una demo generica e non quelle che sto per descrivere.
Mentre scaricate, ne approfitto per ricordare che WWJ è attualmente in versione preliminare (come suggerisce quello "zero" nel numero di versione principale). Da un punto di vista qualitativo è robusto, ma l'ingegnerizzazione non è completata e può capitare che, da una versione all'altra, ci siano un po' di cambiamenti di nomi di classi e di package; pertanto, ogni tanto ci toccherà eseguire un refactoring sui sorgenti. Ma il fatto che sia l'US Navy che la Marina Italiana stiano usando WWJ per alcune applicazioni di navigazione (come pure alcune compagnie private che distribuiscono prodotti per la navigazione civile) è comunque una buona testimonianza della qualità del prodotto, del commitment di NASA nel portarlo avanti e della buona reputazione che si è già creato.
L'unica precauzione da valutare, prima di utilizzarlo seriamente in un progetto reale, è la compatibilità con l'hardware. WWJ usa JOGL, le API di Java per usare OpenGL, che sono portabili su diverse piattaforme (io non ho avuto grossi problemi su tutti i Mac OS X e Windows che girano su macchine non troppo vecchie e gran parte dei Linux), ma richiedono una scheda grafica con accelerazione 3D e dotata del driver opportuno (nel qual caso, le prestazioni sono eccellenti). Questo è un problema su certe installazioni di Linux anche su macchine moderne, anche se generalmente è risolvibile scaricando un driver alternativo. Su macchine vecchie non dotate di schede con accelerazione 3D, o se non avete un driver aggiornato, le performance sono scadenti (si riescono a visualizzare pochi fotogrammi al secondo).
Le classi principali
Una volta scompattato lo .zip dei sorgenti, è già possibile eseguire le applicazioni di esempio usando lo script run-demo.bash e passando come argomento gov.nasa.worldwind.examples.NOME_ESEMPIO. Gli esempi che stiamo per prendere in considerazione sono i seguenti:
● ViewIteration
● GlobalGridAboveSurface ● Shapes ● DraggingShapes ● SurfaceImages ● TexturedSurfaceShape ● Tracks ● AnaglyphStereo
Gli utenti Windows non troveranno un .bat pronto per l'uso, ma potranno costruirlo a partire dalle informazioni in run-demo.bash.
Prima di partire con il primo esempio conviene dare un'occhiata alla classe gov.nava.worldwind.examples.ApplicationTemplate, che è usata come contenitore da tutte le demo ed effettua il setup di base. Questo ci permette di prendere conoscenza delle poche classi fondamentali necessarie per mettere in piedi WWJ
Configuration. È un contenitore di proprietà che permette di prendere il controllo di alcune specifiche modalità di esecuzione, come il settaggio delle cache su disco, il threading, il controllore di rendering, eccetera. In generale, questa classe si usa settando solo le proprietà che intendiamo modificare rispetto al default:
ovviamente come prima operazione prima di toccare altre componenti di WWJ. Nel 90% dei casi i settaggi di default vanno bene così come sono (vedremo un esempio di modifica nella demo AnaglyphStereo).
WorldWindowGLCanvas
È il componente grafico dove avviene il rendering dello scenario tridimensionale. Si tratta di un componente heavyweight di AWT, che può essere integrato in un'applicazione Swing con qualche precauzione (attualmente non è possibile usare una versione lightweight a causa di alcuni bug). È sufficiente istanziarlo e inserirlo in un layout come si fa per un qualsiasi componente grafico di Java.
Model
Rappresenta il modello dell'oggetto che vogliamo renderizzare, che è sempre una sfera (o meglio un'ellissoide tenendo presente dello schiacciamento polare) modificata per tenere conto dell'elevazione del terreno. In tutto il resto dell'articolo si farà riferimento a un modello della Terra, ma è possibile utilizzare al suo posto la Luna od alcuni satelliti di Giove e Saturno per i quali la NASA mette a disposizione i dati raccolti dalle sonde spaziali inviate negli ultimi trent'anni.
Layers
I layer sono uno dei concetti più importanti di WWJ. In pratica si tratta di diversi strati che vengono "avvolti" intorno al Model (ad esempio la Terra) e permettono di disegnare varie informazioni. I layer principali sono quelli che disegnano mappe o fotorilevamenti satellitari, ma noi possiamo aggiungere i nostri layer customizzati. Più avanti nell'articolo vengono discussi i tipi di layer principali.
View, OrbitView
Questa classe rappresenta la posizione corrente della "telecamera" e i suoi settaggi, come ad esempio l'ampiezza dell'angolo di vista. OrbitView è una particolare View che può essere controllata mediante latitudine, longitudine ed altezza dal suolo e, pertanto, si presta più facilmente ad essere usata per la visualizzazione di dati geospaziali alla Google Earth (alternativamente, View potrebbe essere invece usata per implementare un simulatore di volo dove la telecamera è solidale con un oggetto che si muove con leggi proprie, ad esempio un aereo).
SceneController
È la parte "C" del pattern MVC usato da WWJ (ovviamente la "M" è il Model e la "V" la View appena descritti) e, come tale, si occupa di controllare vari aspetti del rendering e dei movimenti della View attraverso i comandi dell'utente, per mezzo di mouse e tastiera.
Detto questo, l'inizializzazione di WWJ è facilmente eseguibile con le seguenti righe di codice:
I Layer predefiniti
L'ApplicationTemplate istanzia e configura per default una collezione di layer per eseguire il "rendering standard" della Terra (noi ovviamente possiamo scegliere di usarli tutti od una parte, o aggiungere i nostri a piacimento). Questi layer standard sono:
Stars
Renderizza un po' di stelle visibili per mettere la Terra nel suo contesto naturale.
Fog
Implementa un effetto "foschia" sulle distanze per migliorare l'effetto tridimensionale del rendering.
SkyGradient
Disegna la sottile fascia d'atmosfera che si assottiglia verso lo spazio esterno.
BMNGSurface
Disegna il modello grafico noto come "BlueMarble", un insieme di foto che disegnano tutto il pianeta, armonizzate tra di loro (i mosaici di foto satellitari "grezzi" hanno dei salti di colore qua e là dovuti al fatto che non sono stati ripresi tutti nello stesso momento), ma con risoluzione piuttosto bassa (quindi non sono adatti a zoom oltre ad un certo livello). Tuttavia, il loro ingombro ridotto fa sì che le bitmap siano contenute dentro la stessa distribuzione di WWJ, quindi non è necessario essere collegati in rete per visualizzarli.
LandsatI3
Una serie di bitmap derivate dai dati Landsat (oggi chiamati iCube) a più alta risoluzione. Questo dato dipende grandemente dall'area geografica; in generale, in Europa non si va sotto i 30m per pixel, quindi una qualità decisamente più bassa di quanto non si ottenga da Google Earth. Il vantaggio, però, è che i dati di iCube possono essere utilizzati liberamente (a patto di citarne la fonte), mentre Google Earth è usabile solo per scopi personali (a meno che non si stipuli un contratto commerciale con Google). Ovviamente è necessario un collegamento in rete per visualizzare questi dati.
USGSDigitalOrtho e USGSUrbanAreaOrtho
Sono due livelli di dati ancora a maggior dettaglio che coprono l'area degli USA e in particolare quella delle grandi città.
CountryBoundaries
Questo layer disegna i confini degli stati.
EarthNASAPlaceName
Questo layer disegna i nomi degli stati, dei mari e di altri oggetti geografici di rilievo.
ScaleBar
Questo layer disegna la barra di scala.
WorldMap
Un layer che disegna una piccola proiezione rettificata della terra con una crocetta che rappresenta la posizione corrente della telecamera, utile per capire dove siamo quando abbiamo zoomato notevolmente.
Compass
Una bussola che indica l'orientamento attuale della zona renderizzata.
Per default, lo SceneController permette di zoomare e ruotare il modello attraverso il mouse, la rotellina del mouse, i tasti freccia e varie combinazione di alt, shift o ctrl. Per visualizzare un layer, bisogna aggiungerlo ad una LayerList presente nel Model, ad esempio:
specificando la posizione di inserimento (prevLayer). Infatti, la maggior parte dei layer non è trasparente e quindi l'ordine con cui vengono disegnati è importante.
Siamo ora in grado di analizzare il codice significativo dei vari programmi di esempio
ViewIteration
Questa semplice demo ci mostra come muovere la View dalla posizione corrente ad una nuova con un'animazione morbida. È sufficiente estendere una AbstractAction standard (che quindi può essere collegata ad un menù o ad un bottone) ed usare uno ScheduledOrbitViewStateIterator specificando le nuove coordinate su cui posizionare la View.
class GoToLatLonFromCurrent extends AbstractAction {
private final LatLon latlon; GoToLatLonFromCurrent(String name, LatLon latlon) {
super(name);
this.latlon = latlon;
} public void actionPerformed(ActionEvent actionEvent) {
OrbitView view = (OrbitView) wwjPanel.getWwd().getView();
ScheduledOrbitViewStateIterator vsi = ScheduledOrbitViewStateIterator.createLatLonIterator(view, this.latlon);
wwjPanel.getWwd().getView().applyStateIterator(vsi);
}
}
RenderableLayer shapeLayer = new RenderableLayer();
// Generate meridians
ArrayList<Position> positions = new ArrayList<Position>(3);
for (double lon = -180; lon < 180; lon += 10)
{
Angle longitude = Angle.fromDegrees(lon);
positions.clear();
positions.add(new Position(Angle.NEG90, longitude, 10e3));
positions.add(new Position(Angle.ZERO, longitude, 10e3));
positions.add(new Position(Angle.POS90, longitude, 10e3));
Polyline polyline = new Polyline(positions);
polyline.setFollowTerrain(false);
polyline.setNumSubsegments(30);
polyline.setColor(new Color(1f, 1f, 1f, 0.5f));
shapeLayer.addRenderable(polyline);
}
this.getWwd().addSelectListener(new SelectListener() { private BasicDragger dragger = new BasicDragger(getWwd()); public void selected(SelectEvent event) {
if (event.getEventAction().equals(SelectEvent.HOVER)) {
// manage tool tips }
else if (event.getEventAction().equals(SelectEvent.ROLLOVER) && !this.dragger.isDragging()) {
AppFrame.this.highlight(event.getTopObject());
} // Have drag events drag the selected object. else if (event.getEventAction().equals(SelectEvent.DRAG_END) || event.getEventAction().equals(SelectEvent.DRAG)) {
// Delegate dragging computations to a dragger. this.dragger.selected(event); if (event.getEventAction().equals(SelectEvent.DRAG_END)) {
PickedObjectList pol = getWwd().getObjectsAtCurrentPosition();
if (pol != null) {
AppFrame.this.highlight(pol.getTopObject());
AppFrame.this.getWwd().repaint();
}
}
}
}
} );
public void selected (SelectEvent event);
il cui evento descrive cosa sta succedendo con quattro valori specifici:
● SelectEvent.HOVER: indica che il mouse si è fermato da qualche decimo di secondo sopra un certo oggetto (nell'esempio, si usa questa informazione per mostrare un tooltip se l'oggetto in questione è un'icona). ● SelectEvent.ROLLOVER: indica che il mouse è sopra un certo oggetto (e quindi, ad esempio, si può evidenziarlo cambiandone colore o forma). ● SelectEvent.DRAG: indica che è iniziato un trascinamento. ● SelectEvent.DRAG_END: indica che è terminato un trascinamento.
Dall'oggetto SelectEvent si possono recuperare le reference relative agli oggetti coinvolti nell'evento (che in generale possono essere più di uno, se sono sovrapposti):
● event.getTopObject() restituisce l'oggetto selezionato che sta sopra gli altri ● event.getObjectsAtCurrentPosition() li restituisce tutti, incapsulandoli in una speciale PickedObjectList.
Tutto il "lavoro sporco" per il trascinamento viene delegato ad una classe chiamata BasicDragger:
private BasicDragger dragger = new BasicDragger(getWwd());
... this.dragger.selected(event);
Alla fine dell'evento di trascinamento, gli oggetti coinvolti sono stati spostati in modo definitivo (cioé le loro coordinate sono state aggiornate).
Tracks
Una delle cose più tipiche che si può visualizzare su una mappa è un percorso. La demo Tracks ci mostra come sia possibile, per esempio, leggere una traccia registrata da un navigatore GPS (nel caso, in formato .GPX) e visualizzarla su un layer specifico.
Nota: la versione 0.3.0 ha in questo caso un file mancante, tuolumne.gpx, che dovrebbe contenere i dati GPS da visualizzare. Per lanciare la demo, dovrete quindi modificare il nome del file da usare:
private static final String TRACK_FILE = "/tmp/PipeTracks2.gpx";
GpxReader reader = new GpxReader();
reader.readFile(TRACK_FILE);
List<Track> tracks = reader.getTracks();
TrackMarkerLayer layer = new TrackMarkerLayer(tracks);
layer.setMaterial(Material.WHITE);
layer.setMarkerShape("Cylinder");
AnaglyphSceneController.class.getName());
= (AnaglyphSceneController)this.getWwd().getSceneController(); asc.setDisplayMode(AnaglyphSceneController.DISPLAYMODESTEREO)); asc.setFocusAngle(this.focusAngle);
http://forum.worldwindcentral.com/forumdisplay.php?f=37
