Incom ist die Kommunikations-Plattform der Fachhochschule Potsdam

In seiner Funktionalität auf die Lehre in gestalterischen Studiengängen zugeschnitten... Schnittstelle für die moderne Lehre

Incom ist die Kommunikations-Plattform der Fachhochschule Potsdam mehr erfahren

UsingUnfolding – Geo visualization with Processing and the unfolding Library

UsingUnfolding – Geo visualization with Processing and the unfolding Library

This is a tutorial about using the Unfolding library for Processing by Till Nagel.

GeoJSONApp-basic

GeoJSONApp-basic-01.pngGeoJSONApp-basic-01.png

Data

We need to give our program the chance to know, if the mouse is inside a country or not. So, we need the shapes of the countries. A nice file wich serves the purpose could be found here: countries.geo.json. The shapes in this file are a little rough, but this provides better performance for our app. Then we need the locations of all airports. Let's take a look at GeoCommons. Type into the search field „world airports“ and hit the first result. As you can see, there is no option for downloading a valid geo.json version of this data set, but I have a gift for you:

„javascript:(function(){var currURL=document.URL;var dataSetID=currURL.match('([0-9]+)');var dataSetURL='http://geocommons.com/overlays/'+dataSetID[0]+'/features.json?geojson=1';dataSetJSON=window.open(dataSetURL,'GeoCommonsJSON');}());“

Copy & paste this javascript into a new bookmark to create a bookmarklet.

Now when you are back on the data-set at GeoCommons you only need to hit this bookmarklet and you will be led to a valid geo.json version of this data-set. Save it as WorldAirports.geo.json

Now, we have two files: countries.geo.json and WorldAirports.json. So, let's write some code.

Note: While writing this tutorial, the world airports data-set disappeared on GeoCommons. But there is a version in the download files.

Code

Let's start with opening the GeoJsonApp.pde from the examples folder of the Unfolding library, save it as GeoJSONApp-basic.pde and add the files to your project (for lazy guys is this sketch already included in the download folder).

Countries & Interaction

Now, let's see how the countries will be displayed by changing the following:

// CHANGE
List<Feature> countries = GeoJSONReader.loadData(this, "countries-simple.geo.json");

// INTO
List<Feature> countries = GeoJSONReader.loadData(this, "countries.geo.json");

Run!

Yes, I told you the shapes are rough, but the performance is quite good. Please don't mind the gray, you can take care about that later. The bigger problem for now is: we don't want the countries to be displayed permanently. Just when the mouse hovers it, we want to see the shape.

GeoJSONApp-ext01

GeoJSONApp-ext01-1a.pngGeoJSONApp-ext01-1a.png
GeoJSONApp-ext01-1b.pngGeoJSONApp-ext01-1b.png
GeoJSONApp-ext01-2a.pngGeoJSONApp-ext01-2a.png
GeoJSONApp-ext01-2b.pngGeoJSONApp-ext01-2b.png
GeoJSONApp-ext01-3a.pngGeoJSONApp-ext01-3a.png
GeoJSONApp-ext01-3b.pngGeoJSONApp-ext01-3b.png
GeoJSONApp-ext01-4a.pngGeoJSONApp-ext01-4a.png
GeoJSONApp-ext01-4b.pngGeoJSONApp-ext01-4b.png

That means we don't want the countryMarkers to be added to the map and for further use we need those Markers globaly. So please make following changes:

// FIND & DELETE
map.addMarkers(countryMarkers);

// INSERT AT TOP
List<Marker> countryMarkers;

// FIND & CHANGE
List<Marker> countryMarkers = MapUtils.createSimpleMarkers(countries);

// INTO
countryMarkers = MapUtils.createSimpleMarkers(countries);

RUN THE PROGRAM!

If you get no error and you see no shape – everthing is fine!

Remember, that we only want to see the country's shape when we hover it. Therefore we need to ask every country, if the mouse is inside its borders and then tell the country to draw itself. Please move to your draw() function and edit it like this:

void draw() {
  map.draw();
  for (int i = 0; i < countryMarkers.size(); i++){
    Marker country = countryMarkers.get(i);
    if(country.isInside(map, mouseX, mouseY)){
      country.draw(map);
    }
  }
}

RUN!

Ah! Nice! We have a hover effect. At this point I'd like to talk a little bit about the use of the properties of a geo.json feature. Often you will find really useful content inside it. For example every feature (country) of our countries.geo.json contains the full name of its country. You can use this to eventually display it.

The properties are stored in a HashMap, though you can get a value by looking for the wanted key, in this case „name“.

Please edit your if-statement like this:

if(country.isInside(map, mouseX, mouseY)){
  country.draw(map);
  HashMap countryProps = country.getProperties();
  String countryName = countryProps.get("name").toString();
  println(countryName);
}

RUN!

Now you should have the hover effect and a string output in your console. Good Job! If you should have contrary to expectations any trouble, then you can now compare your code:

import de.fhpotsdam.unfolding.mapdisplay.*;
import de.fhpotsdam.unfolding.utils.*;
import de.fhpotsdam.unfolding.marker.*;
import de.fhpotsdam.unfolding.tiles.*;
import de.fhpotsdam.unfolding.interactions.*;
import de.fhpotsdam.unfolding.ui.*;
import de.fhpotsdam.unfolding.*;
import de.fhpotsdam.unfolding.core.*;
import de.fhpotsdam.unfolding.data.*;
import de.fhpotsdam.unfolding.geo.*;
import de.fhpotsdam.unfolding.texture.*;
import de.fhpotsdam.unfolding.events.*;
import de.fhpotsdam.utils.*;
import de.fhpotsdam.unfolding.providers.*;
import processing.opengl.*;
import codeanticode.glgraphics.*;

UnfoldingMap map;
List<Marker> countryMarkers;

void setup() {
  size(800, 600, GLConstants.GLGRAPHICS);
  smooth();

  map = new UnfoldingMap(this);
  MapUtils.createDefaultEventDispatcher(this, map);

  List<Feature> countries = GeoJSONReader.loadData(this, "countries.geo.json");
  countryMarkers = MapUtils.createSimpleMarkers(countries);
}

void draw() {
  map.draw();
  for (int i = 0; i < countryMarkers.size(); i++){
    Marker country = countryMarkers.get(i);
    if(country.isInside(map, mouseX, mouseY)){
      country.draw(map);
      HashMap countryProps = country.getProperties();
      String countryName = countryProps.get("name").toString();
      println(countryName);
    }
  }
}

GeoJSONApp-ext02

GeoJSONApp-ext02-1.pngGeoJSONApp-ext02-1.png
GeoJSONApp-ext02-2.pngGeoJSONApp-ext02-2.png
GeoJSONApp-ext02-3.pngGeoJSONApp-ext02-3.png
GeoJSONApp-ext02-4.pngGeoJSONApp-ext02-4.png
GeoJSONApp-ext02-5.pngGeoJSONApp-ext02-5.png

Now let's lay hand on the airports. To get an easy entry please insert these lines at the end of your setup() function:

List<Feature> airports = GeoJSONReader.loadData(this, "WorldAirports.geo.json");
List<Marker> airportMarkers = MapUtils.createSimpleMarkers(airports);
map.addMarkers(airportMarkers);

And run!

There are quite a lot of airports to display and it seems to me like a little loss of performance. And remember that we just want to see the airports of the country we are hovering. First of all we have to make the same changes on the airports stuff, that we made on the countries stuff: make the Markers global and don't add them to the map.

If you are done, let's think about, how we should handle the airports. My suggestion is to sort them into ArrayLists for each country one. Then we only have to call for that ArrayList and tell each airport inside to draw itself. Would be nice if this sorting could be done in advance, but the WorldAirports.geo.json lacks of meta information (properites), and the Unfolding library needs to draw a map to compare the locations of two features. Therefore we must do the sorting one time during runtime. Please, make a global boolean an set it on false:

boolean airportListsBuild = false;

Then go into your draw() function and make an if-else statement under the map.draw() that executes when the boolean is false.

if(!airportListsBuild){

}
else{  

}

And put all code below this statement between the else brackets.

Run!

You should neither have an error nor any hover effect or output. Because all this will only work when airportsListsBuild is true. Meanwhile we'll do the sorting and we are going to to this with an own function.

So please insert a global ArrayList<ArrayList> airportLists, then edit your if-statement like this:

if(!airportListsBuild){
  airportLists = makeAirportLists(map, countryMarkers, airportMarkers);
}

this calls the function with the necessary parameters. Then make this new function under your draw() function:

ArrayList <ArrayList> makeAirportLists(

    UnfoldingMap map, 

    List<Marker> countryMarkers, 

    List<Marker> airportMarkers){

      ArrayList <ArrayList> lists = new ArrayList();

      airportListsBuild = true;

      return lists;

}

This function receives the parameters, makes a new ArrayList, sets our boolean to true and returns the ArrayList.

Run!

As you can see, our known interactions are back again, because when the function is called our boolean is set to true and the else part of our if-statement is executed.

Now let's start sorting. For every country we want to check if there are any airports and put them into a list for each country.

Let's see:

ArrayList <ArrayList> makeAirportLists(

  UnfoldingMap map, 

  List<Marker> countryMarkers, 

  List<Marker> airportMarkers){



  ArrayList <ArrayList> lists = new ArrayList();



  // iterating through the countryMarkers

  for (Marker country : countryMarkers){



    // a new ArrayList for each country

    ArrayList currentMarkerList = new ArrayList();



    // iterating through the airportMarkers

    for (Marker airport : airportMarkers){



      // we need to get the Loction of the airport

      Location airportLocation = airport.getLocation();



      // we need do convert it into a ScreenPosition

      ScreenPosition airportScreenPos = map.getScreenPosition(airportLocation);



      // and if this ScreenPosition is inside the country

      if(country.isInside(map, airportScreenPos.x, airportScreenPos.y)){



        // we add the Marker to this ArrayList

        currentMarkerList.add(airport);

      }

    }

    // we add this ArrayList of Markers to our ArrayList of ArrayLists

    lists.add(currentMarkerList);



      // to see if the program is running we generate some output

      println(lists.size());

    }

  airportListsBuild = true;

  return lists;

}

Now we have a good structure, so let's play with it. We need to edit the else section in our draw() function after the println(countryName):

// we need to get right the airport list for the current country
// because airportLists is sorted after the coutries i calls the right one
ArrayList<Marker> currentAirportList = airportLists.get(i);

// iterating through this airport list
for (Marker currentAirport : currentAirportList ){

  // tell airport to draw itself
  currentAirport.draw(map);

  // check for mouse hover
  if (currentAirport.isInside(map, mouseX, mouseY)){

    // you should know this from the countries part
    HashMap currentAirportProps = currentAirport.getProperties();

    // Note: in this case "NAME" is written in uppercase,
    // this depends on the given geo.json file
    // it's better to know your data
    String currentAirportName = currentAirportProps.get("NAME").toString();

    // string output into the console
    println(currentAirportName);
  }
}

Style

Now it's time to get rid of the gray and add a little style, but you should do this on your own. For more tutorials concerning this and other topics, please visit Unfolding tutorials.

Fachgruppe

Interfacedesign

Art des Projekts

Studienarbeit im zweiten Studienabschnitt

Betreuung

foto: Dr. Till Nagel

Entstehungszeitraum

Sommersemester 2012