Data Science Programming

L3D Cube visualizations Part 2: real-time worldwide weather

Overview

We will make use of the OpenWeatherMap API to retrieve the temperature from cities around the world and displays the result on the cube. The result is a “real-time” (actually the free API key only gives access to hourly updates) visualization of the earth’s weather.

A Python script is used to select which cities are displayed: we start with a json file provided by OpenWeatherMap that contains every city accessible from the API as well as their ID and coordinates. The json is parsed and casted as a panda dataframe. The latitude and longitude of each city are transformed in voxel coordinates over a sphere of 4 voxel radius. The cities that fall on the same voxel are grouped and a random one is picked up from each group to represent that voxel.

The result is saved in a csv file that is loaded in Processing and used to query the API. The temperatures of each city are then shown on the cube using a gradient of color.

Walkthrough

Initial set-up

OpenWeatherMap

Create a new OpenWeatherMap account in order to get an API key.

Then download the JSON file containing the list of cities with their IDs and coordinates over here. If you cloned my repository, it is already included in the data/ subfolder of the corresponding Processing sketch.

Next use this Python script in order to select the cities that will be displayed. It will save a csv file containing the selected cities, their coordinates and IDs. Note that it can take quite some time to run.

Libraries

Our main goal is similar to the last example. We want to send a get request to a third party, retrieve the result, parse it and display it on the cube.

Thus we will use the same two libraries:

The code

Link to the repository.

Server: Processing sketch

Link to the sketch.

Retrieve and parse data

Import the Request library and set-up the variables that will be used to request the temperature data from OpenWeatherMap and to store the result.

import http.requests.*;

// First set-up the request parameters and url
GetRequest get;
String API_KEY = "YOUR_API_KEY"; // Your OpenWeatherMap API key to authenticate your requests
String requestStart = "http://api.openweathermap.org/data/2.5/group?id="; // base url for group requests
String requestEnd = "&units=metric&appid=" + API_KEY; // request arguments
// used to store the ids of the cities that we will query. 
// there are two strings because we will pass 2 requests
// (a group call is limited to 100 ids/call which is why we need to pass 2)
String subQueries[] = {"", ""}; 
String request;
String response;

Table table; // will store the csv info
JSONArray results; // will store the result of the query
color[] colors = new color[194]; // will store the colors corresponding to each city's temperature

Load the csv file in setup().

void setup() {
  table = loadTable("openweathermap_cities.csv", "header");
}

Create a new getData() function that will be in charge of reading the input csv file, creating two queries using the IDs from the table, make the request and parse the result.

void getData() {
  int i = 0;
  for (TableRow row : table.rows()) {   
    polarCoord[i] = new PVector(row.getFloat("lat"), row.getFloat("long"));

    // Create query
    if (i<100) {
      subQueries[0] += row.getInt("api_id") + ",";
    } else {
      subQueries[1] += row.getInt("api_id") + ",";
    }
    i++;
  }
  
  // Two requests are made because results are limited at 100 per query (we need 200)s
  for (int j=0; j<2; j++){
    // Make request
    request = requestStart + subQueries[j] + requestEnd;
    get = new GetRequest(request);
    get.send();
    response = get.getContent();
    
    // Parse result
    JSONObject jsonObject = parseJSONObject(response);
    results = jsonObject.getJSONArray("list");
    for (int k=0; k

You'll notice that we use a function called processColor() that we haven't defined yet. The role of this function is to take a temperature, compare its position on a pre-defined range and return the corresponding color from a gradient ranging from orange (warm) to blue (cold).

color processColor(float value) {
  color c1 = color(0,145, 255); // color for cold temperatures
  color c2 = color(255, 94, 0); // color for warm temperatures
  // set temperature range used to map a value to a color 
  int maxTemp = 45; 
  int minTemp = -30;
  
  // map temperature value between [0 ; 1]
  float inter = map(value, minTemp, maxTemp, 0, 1);
  
  // map the value to a color
  color c = lerpColor(c1, c2, inter);
   
  return c;
}

Transform polar coordinates in cartesian's

We define a few global variables that will be used for our transformation.

int radius = 4; // radius of the output sphere in voxel
PVector center;
PVector[] polarCoord = new PVector[194];
PVector[] cartesCoord = new PVector[194];

The latitude and longitude are transformed from degrees to radians. We then apply the following formula to get the Cartesian coordinates (x, y, z). We apply an eventual rotation on the resulting sphere (so that the sphere appears to be rotating over time) and add an offset of 4 to very resulting coordinate because the center of our referential lies at (4, 4, 4) and not (0, 0, 0).

PVector projectCoordinates(float latitude, float longitude, float r, int rotation) {
  // convert degrees in radians
  float theta = (latitude*PI)/180;
  float phi = (longitude*PI)/180;
  float rad = (rotation*PI)/180;

  // transform polar coordinates into cartesian ones 
  float x = (sin(phi)*cos(theta)*r);
  float y = (sin(theta)*r)+4;
  float z = (cos(phi)*cos(theta)*r);
  
  // apply rotation
  float x_p = x*cos(rad) + z*sin(rad) + 4;
  float z_p = -sin(rad)*x + cos(rad)*z + 4;
  
  return new PVector(floor(x_p), floor(y), floor(z_p));
}

Let's add a function that will apply the transformation to every city's coordinates.

void processCoordinates(int radius, int rotation) {
  for (int i=0; i

Putting it all together

We declare the cube as a global variable, set-up the rendering window, initialize a new cube object and start streaming the voxel's values on port 2000.

...
// Instanciate cube object
L3D cube;
...	
void setup() {
...
  size(512, 512, P3D);  // start simulation with 3d renderer

  cube = new L3D(this);
  cube.enableMulticastStreaming(2000);
}

We define a new function plotCoordinates() that takes the transformed coordinates and the corresponding color and displays them on the cube.

void plotCoordinates() {
  cube.background(0); // clear cube voxels
  
  for (int i=0; i

We define global variables storing the rate at which the cube should be rotated as well as the delay between each data update. Since the temperatures from OpenWeatherMap are updated every hour, we set the update rate at 1/h.

int dataUpdateRate = 60; // rate at which to update data in mn
int rotationRate = 2; // rate at which to rotate the sphere in seconds

// used to keep track of events
long nextDataUpdate = 0;
long nextRotation = 0;
int rotationAngle = 0;

Finally we fill-in the draw() loop.

void draw() {
  background(0); // set background to black
  lights(); // turn on light
  
  // update data if delay elapsed
  if (millis() > nextDataUpdate ) {
    getData();
    nextDataUpdate = millis() + dataUpdateRate*60*60*1000; // reset time for next update
  }

  // rotate sphere if delay elapsed
  if (millis() > nextRotation ) {
    processCoordinates(radius, rotationAngle); // re-process coordinates according to new rotation angle
    plotCoordinates(); // send new coordinates to the cube
    
    rotationAngle += 45; // update rotation angle, resetting to 0 if greater than 360 degrees
    if (rotationAngle > 360) {
      rotationAngle = 0;
    }
    nextRotation = millis() + (rotationRate * 1000); // reset time for next rotation
  }
}

Client: Photon firmware

Upload the following code to your device.

Sources

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.