Visualizing the Linked Brazilian Amazon Dataset using the NASA World Wind Java API

by Jim Jones

This Tutorial is one of the many tutorials available at NASA World Wind SDK Tutorial, developed by the Institute for Geoinformatics from the University of Münster, Germany. If you are not familiar with NASA World Wind and Apache Jena, mind to take a look at  Getting Started With NASA World Wind and at Getting Started with Apache Jena to learn how to set up your environment and how to build your first application using the NASA World Wind and Apache Jena API. For a detailed step-by-step tutorial on how to build polygons from Linked Open Data datasets, you can also visit Building Basic Polygons from LOD Datasets.

Getting Started

In this tutorial we are going to build an application to link to the LinkedScience.org triple store and explore the Linked Brazilian Amazon Rainforest Dataset. The Linked Brazilian Amazon Rainforest Dataset, created using the Open Linked Amazon Vocabulary (OLA) and the Time Space Core Vocabulary (TISC), contains deforestation statistics and several socio-economic variables. These data are subdivided in a grid of 8441 cells of 25km x 25km each. A complete sample of the statistical and socio-economical data attached to each cell can be seen here. The variables we are going to use for this tutorial are DEFOR_2007 (deforestation values on the year 2007), PRECO_PASTAGEM_2007 (Pasture price on the year 2007) and, of course, GEOMETRY (Coordinates to draw the cells). If you wish to do more detailed statistical analysis with this data set, take a look at all variables available.

We are going to create three classes to query, store and visualize our datasets. Firstly, we are going to see step-by-step how these classes are written, by short code snippets and afterwards a complete version of it (Copy & Paste ready). If you prefer, you can also download them clicking here: Download – Exploring the Linked Brazilian Amazon Dataset Tutorial.

For better manipulating our data, let’s create a data type to attend the informations above mentioned, namely municipality name (String), area size (Long) and geometry (String), with all its getters and setters. Let’s call it GeometryRecordLinkedScience.

(Copy & Paste ready)

package de.ifgi.lodnasa.tutorial;

public class GeometryRecordLinkedScience  {

	private double deforestation;
	private double pasturePrice;
	private String geometry;

	public double getDeforestation() {
		return deforestation;
	}
	public void setDeforestation(double deforestation) {
		this.deforestation = deforestation;
	}
	public double getPasturePrice() {
		return pasturePrice;
	}
	public void setPasturePrice(double pasturePrice) {
		this.pasturePrice = pasturePrice;
	}
	public String getGeometry() {
		return geometry;
	}
	public void setGeometry(String geometry) {
		this.geometry = geometry;
	}

	public GeometryRecordLinkedScience(double deforestation,
			double pasturePrice, String geometry) {
		super();
		this.deforestation = deforestation;
		this.pasturePrice = pasturePrice;
		this.geometry = geometry;
	}

}

Once we have our data type ready, it’s time to construct the class that is going to retrieve and provide the data from the LinkedScience.org triple store. Let’s call this class DataLoaderLinkedScience. Having it said, let’s create a method returning an array from the data type we defined above.

We start this method defining the array which will collect all retrieved data.

ArrayList<GeometryRecordLinkedScience> geometryRecord = new ArrayList<GeometryRecordLinkedScience>();

Now let’s write our SPARQL Query. This query simply lists all cells available in the triple store, together with their pasture price and amount of deforestation. In this sample we are retrieving only the year 2007, but more years are available.

String sparqlQuery =
"SELECT ?deforestation_2007 ?pasture_2007 ?polygon " +
"WHERE { "+
" ?cell a <http://linkedscience.org/lsv/ns#Item> ; " +
" <http://spatial.linkedscience.org/context/amazon/PRECO_PASTAGEM_2007> ?pasture_2007 ; " +
" <http://observedchange.com/tisc/ns#geometry> ?polygon ;" +
" <http://spatial.linkedscience.org/context/amazon/DEFOR_2007> ?deforestation_2007} ";

Now it is time to connect to the LinkedScience.org triple store and execute this query. For this we are going to need to create a Query object.

Query query = QueryFactory.create(sparqlQuery);

Now we need to connect to the triple store and execute our query.

QueryExecution qexec =
       QueryExecutionFactory.sparqlService("http://spatial.linkedscience.org/sparql", query);

The data retrieved from our SPARQL Query needs to be temporally stored in a ResultSet.

ResultSet results = qexec.execSelect();

With the dataset already loaded into our ResultSet, we now only need to iterate over it and add its records to our GeometryRecordLinkedScience array.

while (results.hasNext()) {

        QuerySolution soln = results.nextSolution();

        GeometryRecordLinkedScience tmpLODRecord = new GeometryRecordLinkedScience(
                        soln.getLiteral("deforestation_2007").getDouble(),
                        soln.getLiteral("pasture_2007").getDouble(),
                        soln.getLiteral("polygon").getString());

        geometryRecord.add(tmpLODRecord);
        System.out.println(soln.getLiteral("deforestation_2007").getDouble());
}

In the end, your DataLoaderLinkedScience class might look like this. (Copy & Paste ready)

package de.ifgi.lodnasa.tutorial;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import com.hp.hpl.jena.query.ARQ;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;

public class DataLoaderLinkedScience {

    public static void main(String[] args) throws FileNotFoundException {
            DataLoaderLinkedScience lodDataset = new DataLoaderLinkedScience();
            lodDataset.queryAmazonCells();
    }

    public ArrayList queryAmazonCells() throws FileNotFoundException {
        //Defining an array list of the type GeometryRecordLinkedScience.
        ArrayList<GeometryRecordLinkedScience> geometryRecord = new ArrayList<GeometryRecordLinkedScience>();
        //Defining SPARQL Query
        String sparqlQuery =
        "SELECT ?deforestation_2007 ?pasture_2007 ?polygon " +
        "WHERE { "+
        " ?cell a  ; " +
        "  ?pasture_2007 ; " +
        "  ?polygon ;" +
        "  ?deforestation_2007} ";

        //Executing query towards the LinkedScience.org SPARQL Endpoint.
        Query query = QueryFactory.create(sparqlQuery);
        QueryExecution qexec =
           QueryExecutionFactory.sparqlService("http://spatial.linkedscience.org/sparql", query);
        ResultSet results = qexec.execSelect();
        //Iterating over the ResultSet and adding each record into our
        //GeometryRecordLinkedScience array list.
		while (results.hasNext()) {

		        QuerySolution soln = results.nextSolution();

		        GeometryRecordLinkedScience tmpLODRecord = new GeometryRecordLinkedScience(
		                        soln.getLiteral("deforestation_2007").getDouble(),
		                        soln.getLiteral("pasture_2007").getDouble(),
		                        soln.getLiteral("polygon").getString());

		        geometryRecord.add(tmpLODRecord);
		}

        qexec.close();
        //Returning an array list with our query result elements.
        return geometryRecord;
    }
}

With our dataset properly loaded, we just need to plot it on a NASA World Wind application. To do so, let’s create the class PlotGeometriesLinkedScience! Let’s begin by setting the application elevation model to zero, to avoid having one of our cells partially displayed or even not displayed at all.

this.getWwd().getModel().getGlobe().setElevationModel(new ZeroElevationModel());

Creating the renderable layer, an array of the type GeometryRecordLinkedScience, an instance of our DataLoaderLinkedScience class and finally load the query result set.

RenderableLayer layer = new RenderableLayer();
DataLoaderLinkedScience dataLoader = new DataLoaderLinkedScience();
ArrayList geometryRecord = new ArrayList();
geometryRecord = dataLoader.queryAmazonCells();

Now that we’ve got the dataset loaded into our array list, we have to iterate over it to get each record it contains and create polygons for each cell. Since the coordinates are separated by “,” comma and every pair of latitude and longitude is separated by “;” semicolon (e.g. -35.01479,-8.05609; -34.99385,-8.04899;…), we have to parse these strings for providing the coordinates to the API in an understandable format. To do so, we can use the function split() and save its result into a Positions array.

for (int i = 0; i < geometryRecord.size(); i++) {

    ArrayList borderPositions = new ArrayList();
    //Splitting every coordinate, which in our dataset is separated by ";"
    String latlong[] = geometryRecord.get(i).getGeometry().split(";");
    //Getting the deforestation and pasture price of
    //each municipality for further use
    double deforestation = geometryRecord.get(i).getDeforestation();
    double pasture = geometryRecord.get(i).getPasturePrice();

    for(String str: latlong){

        //Splitting the latitude / longitude, which in our dataset is separated by ","
        String latlong2[] = str.split(",");
        //Adding the latitude and longitude to the Positions array
        borderPositions.add(Position.fromDegrees(Double.parseDouble(latlong2[1]),
                                                 Double.parseDouble(latlong2[0]),1e4));
    }
   ... (continue)

Still inside of the geometryRecord.size() loop, we have now to create our polygon cell objects and set their colour and altitude. The colour is going to be defined by the amount of deforestation and the altitude by the pasture price. And finally, we’re going to set a tooltip text with the amount of deforestation and pasture price of each cell and add them to the renderable layer.

    ... (continuation)
    gov.nasa.worldwind.render.airspaces.Polygon polygon = new Polygon(borderPositions);
    //Setting polygons altitude based on the pasture price
    polygon.setAltitudes(pasture*220,0);

    //Classifying the polygons due its amount of deforestation,
    //giving them different colors
    if (deforestation == 0.0) {
            polygon.getAttributes().setMaterial(Material.GREEN);
    } else if (deforestation > 0 && deforestation <= 0.005 ) {
            polygon.getAttributes().setMaterial(Material.BLUE);}
     else if (deforestation > 0.005  && deforestation <= 0.01 ) {
          polygon.getAttributes().setMaterial(Material.YELLOW);}
     else if (deforestation > 0.01 && deforestation < 0.02) {
          polygon.getAttributes().setMaterial(Material.ORANGE);}
     else if (deforestation > 0.02 ) {
            polygon.getAttributes().setMaterial(Material.RED);} 

    polygon.getAttributes().setDraw	

    //Creating a tooltip text for each polygon, containing their
    //amount of deforestation  and pasture price
    polygon.setValue(AVKey.DISPLAY_NAME," Deforestation: " +
                  geometryRecord.get(i).getDeforestation() +
                  "km2 \n Pasture Price: R$" + geometryRecord.get(i).getPasturePrice());

    //Adding the polygon to the renderable layer
        layer.addRenderable(polygon);
}

Bellow you can find the PlotGeometriesLinkedScience class final version with its code comments.

(Copy & Paste ready)

package de.ifgi.lodnasa.tutorial;

import gov.nasa.worldwind.Configuration;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.render.airspaces.Polygon;
import gov.nasa.worldwind.terrain.ZeroElevationModel;
import gov.nasa.worldwindx.examples.ApplicationTemplate;
import java.io.FileNotFoundException;
import java.util.ArrayList;

import de.ifgi.lodnasa.test.GeometryRecordLinkedScience;

public class PlotGeometriesLinkedScience extends ApplicationTemplate
{
	public static class AppFrame extends ApplicationTemplate.AppFrame
	{
	    public AppFrame() throws FileNotFoundException
	    {
	     super(true, true, false);
	     //Setting the elevation model to zero
	     this.getWwd().getModel().getGlobe().setElevationModel(new ZeroElevationModel());
	     //Creating the renderable layer
	     RenderableLayer layer = new RenderableLayer();
	     //Creating an instance of our class DataLoaderLinkedScience
	     DataLoaderLinkedScience dataLoader = new DataLoaderLinkedScience();
	     //Creating an array of our predefined type GeometryRecordLinkedScience
            ArrayList<GeometryRecordLinkedScience> geometryRecord = new ArrayList<GeometryRecordLinkedScience>();
            //Calling the function queryAmazonCells to execute our predefined SPARQL Query
            geometryRecord = dataLoader.queryAmazonCells();
            //Iterating the SPARQL result and adding its records into a Positions array
            for (int i = 0; i < geometryRecord.size(); i++) {

                ArrayList borderPositions = new ArrayList();
                //Splitting every coordinate, which in our dataset is separated by ";"
                String latlong[] = geometryRecord.get(i).getGeometry().split(";");
                //Getting the deforestation and pasture price of
                //each municipality for further use
                double deforestation = geometryRecord.get(i).getDeforestation();
                double pasture = geometryRecord.get(i).getPasturePrice();

                for(String str: latlong){

	                //Splitting the latitude / longitude, which in our dataset is separated by ","
	                String latlong2[] = str.split(",");
	                //Adding the latitude and longitude to the Positions array
	                borderPositions.add(Position.fromDegrees(Double.parseDouble(latlong2[1]),
	                                                         Double.parseDouble(latlong2[0]),1e4));
                }

	            //Creating a new renderable airspaces Polygon
	            gov.nasa.worldwind.render.airspaces.Polygon polygon = new Polygon(borderPositions);
	            //Setting polygons altitude based on the pasture price
	            polygon.setAltitudes(pasture*220,0);

	            //Classifying the polygons due its amount of deforestation,
	            //giving them different colors
	            if (deforestation == 0.0) {
	                    polygon.getAttributes().setMaterial(Material.GREEN);
	            } else if (deforestation > 0 && deforestation <= 0.005 ) {
	                    polygon.getAttributes().setMaterial(Material.BLUE);}
	             else if (deforestation > 0.005  && deforestation <= 0.01 ) {
	                  polygon.getAttributes().setMaterial(Material.YELLOW);}
	             else if (deforestation > 0.01 && deforestation < 0.02) {
	                  polygon.getAttributes().setMaterial(Material.ORANGE);}
	             else if (deforestation > 0.02 ) {
	                    polygon.getAttributes().setMaterial(Material.RED);} 

	            polygon.getAttributes().setDrawOutline(true);
	            polygon.getAttributes().setDrawInterior(true);

	            //Creating a tooltip text for each polygon, containing their name and area
	            polygon.setValue(AVKey.DISPLAY_NAME," Deforestation: " +
	                          geometryRecord.get(i).getDeforestation() +
	                          "km2 \n Pasture Price: R$" + geometryRecord.get(i).getPasturePrice());

	            //Adding the polygon to the renderable layer
	            layer.addRenderable(polygon);
	                    }

	        // Add the layer to the model.
	        insertBeforeCompass(getWwd(), layer);
	        // Update layer panel
	        this.getLayerPanel().update(this.getWwd());

	    }
	}

	public static void main(String[] args)
	{
	    Configuration.setValue(AVKey.INITIAL_LATITUDE, -9.16);
	    Configuration.setValue(AVKey.INITIAL_LONGITUDE, -56);
	    Configuration.setValue(AVKey.INITIAL_ALTITUDE, 500e4);

	    ApplicationTemplate.start("NASA World Wind Tutorial - " +
	                              "LinkedScience Amazon Rainforest Dataset", AppFrame.class);
	}
}

Execute the PlotGeometriesLinkedScience class and take a look at your first visualization of the Linked Brazilian Amazon Rainforest Dataset!