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:
- L3D Library.
- HTTP Requests.
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
- Polar and Cartesian coordinates from Math is fun.
- Polar coordinate system from Wikipedia.
- Converting from longitude latitude to Cartesian coordinates from Stackoverflow.