Da un po’ di mesi sono passato da utente a “sviluppatore in erba” di algoritmi con la potente libreria di Sextante GIS (qualcuno la conoscerà come integrazione di gvSIG) interamente scritta in Java e che ha un papà attivissimo: Victor Olaya
. Per chi non la conoscesse, Sextante è una libreria di geoalgoritmi in grado di interfacciarsi con i dati geografici attraverso specifici Bindings (tra cui per gvSIG, OpenJump ed anche le GeoTools). Non entro nel merito su come funzionano i bindings, ma possiamo intenderli come delle interfacce tra Sextante e le applicazioni desktop GIS.
Credo che molti di noi utenti GIS abbiano una volta almeno desiderato sviluppare un algoritmo di analisi spaziale per uno scopo ben preciso
A volte ci viene in aiuto la possibilità di costruire dei flussi di lavoro (workflow) possibili anche con Sextante attraverso il suo modelbuilder, ma in questo post proveremo a sviluppare ex novo un geoalgoritmo basato sulla libreria di Sextante.
Ciò che troverete scritto è sostanzialmente tratto e basato sull’ottimo manuale di programmazione Sextante scritto da V. Olaya. Lo scenario applicativo è il seguente: abbiamo installato sul nostro calcolatore un software GIS Open Source (es. gvSIG) esteso con Sextante. Questo breve tutorial presuppone anche una conoscenza di base di Java e un ambiente di sviluppo, in questo caso mi baserò su Eclipse.
Per prima cosa scarichiamo i binari dal sito di Sextante. Appena aperta la cartella, troveremo delle sotto cartelle (bindings, core e help). Ciò di cui avremo bisogno sono i file .jar contenuti nella cartella “core” e che costituiranno le nostre librerie per lo sviluppo dei nostri geoalgoritmi.
A questo punto possiamo avviare Eclipse e creare un nuovo progetto nominandolo “Tutorial”. Adesso creaimo una cartella “lib” dove andremo a collocare i file .jar presenti nella cartella “core” di Sextante. Non ci resta che aggiungere al Build Path del nostro progetto i file presenti nella cartella “lib”.
Bene, adesso dovremo creare un nuovo package (es. it.mionome.sextante.testraster). La struttura di Sextante prevede che ciascun package contenga un algoritmo ed eventuali classi di supporto. Per questo motivo creeremo una nuova classe (senza metodo main) nominandola MoltplicaRasterAlgorithm – nota bene che il suffisso Algorithm è necessario per essere riconosciuto nel Framework di Sextante come nuovo algoritmo.
Tutti gli algoritmi estendono la classe GeoAlgorithm di Sextante e, pertanto, implementano due metodi:
- defineCharacteristics()
- processAlgorithm()
Il primo definisce i parametri dell’algoritmo che serviranno anche per la sua interfaccia grafica; il secondo definisce la struttura dell’algoritmo in termini di calcolo e processi. Procediamo con lo sviluppo di un semplice algoritmo che moltiplica ogni cella di un dato raster in ingresso per un dato valore e restituisce un nuovo raster. Questo esempio è tratto proprio dalla guida citata, ovvero la Programming Guide di Sextante scritta da Victor Olaya.
Ecco come si presenterà la nostra classe dopo averla estesa con GeoAlgorithm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package es.unex.sextante.multiplyRaster; import es.unex.sextante.core.GeoAlgorithm; import es.unex.sextante.exceptions.GeoAlgorithmExecutionException; public class MoltiplicaRasterAlgorithm extends GeoAlgorithm{ @Override public void defineCharacteristics() { // TODO Auto-generated method stub } @Override public boolean processAlgorithm() throws GeoAlgorithmExecutionException { // TODO Auto-generated method stub return false; } } |
Passiamo con il definire il contenuto del primo metodo e, dunque, le caratteristiche del nostro algoritmo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static final String RESULT = "RESULT"; public static final String INPUT = "INPUT"; public static final String VALUE = "VALUE"; public void defineCharacteristics() { this.setName("Multiply raster"); this.setGroup("My algorithm"); this.setGeneratesUserDefinedRasterOutput(false); try { m_Parameters.addInputRasterLayer("INPUT", "Raster layer", true); m_Parameters.addNumericalValue("VALUE", "Value", 1, AdditionalInfoNumericalValue.NUMERICAL_VALUE_DOUBLE); addOutputRasterLayer("RESULT", "Result"); } catch (RepeatedParameterNameException e) {e.printStackTrace(); } } |
Analizziamo il codice passo passo. Dalla riga 5 alla riga 6 vengono definiti il nome ed il gruppo di appartenenza del nuovo algoritmo riferito all’interfaccia grafica della toolbar di Sextante. La riga 7 è necessaria perchè creando un nuovo dato raster in uscita, dovremmo definire le estensioni di tale dato in termini di cella e regione. In questo caso impostiamo di default che il dato in uscita avrà le stesse caratteristiche del dato di ingresso e disattiviamo l’opzione con un false.
Le righe 10,11 e 12 rispondono alle semplici domande: quali sono i dati ed i parametri di input (definibili nella GUI del nostro algoritmo) e quali sono i dati in uscita prodotti. In questo caso necessitiamo di un raster in ingresso, un valore numerico come moltiplicatore ed un dato raster in uscita per cui si debba definire il path di scrittura.
Passiamo adesso a definire il processo di calcolo dell’algoritmo:
1 2 3 4 5 6 7 | public boolean processAlgorithm() throws GeoAlgorithmExecutionException { int x,y; int iNX, iNY; double dValue; double dConstant; IRasterLayer layer; IRasterLayer result; |
Per prima cosa definiamo le variabili. Come potrete approfondire nel manuale, Sextante si interfaccia con dati raster, vector e tabelle tramite classi apposite: IRasterLayer, IVectorLayer e ITable.
Adesso possiamo inizializzare le nostre variabili notando come definiamo, a livello di codice, anche l’estensione del raster in uscita (ricordiamo che abbiamo disattivato l’opzione tramite GUI).
8 9 10 11 | layer = m_Parameters.getParameterValueAsRasterLayer("INPUT"); dConstant = m_Parameters.getParameterValueAsDouble("VALUE"); GridExtent extent = new GridExtent(layer); result = getNewRasterLayer("RESULT", Sextante.getText("Result"), IRasterLayer.RASTER_DATA_TYPE_FLOAT, extent); |
Dalla riga 8 alla 11 impostiamo come parametri dell’algoritmo i dati di input immessi tramite GUI. Non ci resta che definire il processo di calcolo. Abbiamo bisogno di due semplici cicli for che scandisca cella per cella ne recuperi il value e lo moltiplichi per il nostro moltiplicatore restituendo il risultato come nuovo value.
12 13 14 15 16 17 18 19 20 21 22 23 | layer.setWindowExtent(extent); iNX = extent.getNX(); iNY = extent.getNY(); for (y = 0; y < iNY && setProgress(y, iNY); y++){ for(x = 0; x < iNX; x++){ dValue = layer.getCellValueAsDouble(x, y); result.setCellValue(x, y, dValue * dConstant); } } return !m_Task.isCanceled(); } } |
La riga 21 contiene il codice con cui terminare la procedura del nostro algoritmo.
Ricapitolando…il codice completo sarà il seguente:
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package es.unex.sextante.multiplyRaster; import es.unex.sextante.additionalInfo.AdditionalInfoNumericalValue; import es.unex.sextante.core.*; import es.unex.sextante.dataObjects.IRasterLayer; import es.unex.sextante.exceptions.RepeatedParameterNameException; import es.unex.sextante.exceptions.GeoAlgorithmExecutionException; import es.unex.sextante.rasterWrappers.GridExtent; public class MultiplyRasterAlgorithm extends GeoAlgorithm{ public static final String RESULT = "RESULT"; public static final String INPUT = "INPUT"; public void defineCharacteristics() { this.setName("Multiply raster"); this.setGroup("My algorithm"); this.setGeneratesUserDefinedRasterOutput(false); try { m_Parameters.addInputRasterLayer("INPUT", "Raster layer", true); m_Parameters.addNumericalValue("VALUE", "Value", 1, AdditionalInfoNumericalValue.NUMERICAL_VALUE_DOUBLE); addOutputRasterLayer("RESULT", "Result"); } catch (RepeatedParameterNameException e) {e.printStackTrace(); } } public boolean processAlgorithm() throws GeoAlgorithmExecutionException { int x,y; int iNX, iNY; double dValue; double dConstant; IRasterLayer layer; IRasterLayer result; layer = m_Parameters.getParameterValueAsRasterLayer("INPUT"); dConstant = m_Parameters.getParameterValueAsDouble("VALUE"); GridExtent extent = new GridExtent(layer); result = getNewRasterLayer("RESULT", Sextante.getText("Result"), IRasterLayer.RASTER_DATA_TYPE_FLOAT, extent); layer.setWindowExtent(extent); iNX = extent.getNX(); iNY = extent.getNY(); for (y = 0; y < iNY && setProgress(y, iNY); y++){ for(x = 0; x < iNX; x++){ dValue = layer.getCellValueAsDouble(x, y); result.setCellValue(x, y, dValue * dConstant); } } return !m_Task.isCanceled(); } } |
Siamo pronti per testare il nostro algoritmo. Esportiamo i nostri sorgenti come file .jar prestando attenzione al nome del file che dovrà contenere il prefisso “sextante”. Ad esempio potremmo nominare il nostro file: “sextante_test”.
Copiamo il nostro jar nella cartella “es.unex.sextante” presente nella directory “extensiones” di gvSIG. A questo punto troveremo nella toolbar di Sextante un nuovo gruppo di algoritmi “My Algorithm” con il geoalgoritmo “Multiply raster”.
Spero che possa avere stimolato la vostra curiosità e vi invito ancora una volta a consultare e approfondire questi argomenti nel manuale scritto da Olaya (a cui si riferisce questo post).
Spero presto di aggiungere la seconda parte con un algoritmo nel mondo vector (..tratto sempre dallo stesso manuale in inglese)



