Map projection 

When dealing with geographic data such as maps, GPS tracks and administrative boundaries, it is often necessary to combine data from different sources. Unfortunately, the way in which geographic location is related to coordinate values in such sources can vary between datasets. This is largely because there is no single best way of projecting three-dimensional global data onto a flat plane ready for data visualization. These examples show how you can project data using a number of common coordinate systems.

 

Using projections in Processing 

To apply map projections in your sketches, you should import the spatial package into your sketch with the line

 
import org.gicentre.utils.spatial.*;
 

There are a range of map projection class available that control precisely how geographical coordinate values are converted from one system to another. The following map projections are currently supported:

For a full details, see the Spatial package API reference. See also, the mapProjectionExample sketch supplied in the examples folder of the giCentre Utilities library.

Each of the classes above can convert to and from global 'geographical' coordinates - in other words, longitude/latitude coordinate pairs that use the 'WGS84' datum. This is a de facto standard for storing locations around the globe before they are projected onto a plane for drawing. Longitude (east-west value) is represented as an angle between -180 degrees and +180 degrees where 0 is the Greenwich meridian, negative values are to the west of the meridian and positive ones to the east. Latitude (north-south value) is scaled between -90 degrees (south pole), through 0 (equator) to +90 degrees (north pole).

To use one of the projection classes, you simply provide a location in one coordinate system and get one of the projection classes to convert it to another one. For example, the sketch below is a minimal example showing how to convert a longitude/latitude pair into the French NTF system:

 
import org.gicentre.utils.spatial.*;    // For map projections.

// Minimal example that converts from lat/long to French NTF coordinates.

void setup()
{
  FrenchNTF proj = new FrenchNTF();
  PVector geo = new PVector(2.3, 48.8);  // Paris longitude/latitude
  PVector projCoords = proj.transformCoords(geo);
  
  println(geo.x+","+geo.y+" -> "+projCoords.x+","+projCoords.y);  
}
 

To keep things tidy, coordinate pairs are stored inside Processing's PVector class. Passing a PVector to a projection class's transform() method, as above, will return a new PVector containing the projected coordinates. If you need to convert from a projected coordinate pair back to longitude/latitude, call the class's invTransform() method.

Here is a more useful sketch that displays a GPS track on top of an OpenStreetMap tile using the WebMercator system. In this case the mapping provided by OpenStreetMap is already using the WebMercator system, so we simply need to convert the longitude/latitude coordinates of the GPS track to match the mapping coordinate system:

GPS track in lat/long coordinates reprojected to web Mercator

import org.gicentre.utils.spatial.*;    // For map projections.

// Displays a GPS track and map using WebMercator projection.
// Version 1.1, 5th November, 2013.
// Author Jo Wood, giCentre, City University London.

ArrayList<PVector>coords;    // Projected GPS coordinates.
PImage backgroundMap;        // OpenStreetMap.
PVector tlCorner,brCorner;   // Map corners in WebMercator coordinates.

void setup()
{
  size(781,548);
  noLoop();
  readData();
}

void draw()
{
  // Background map.
  image(backgroundMap,0,0,width,height);
  
  // Projected GPS coordinates
  noFill();
  stroke(150,50,50,150);
  strokeWeight(6);
  
  beginShape();
  for (PVector coord : coords)
  {
    PVector scrCoord = geoToScreen(coord);
    vertex(scrCoord.x,scrCoord.y);  
  }
  endShape();
}

void readData()
{
  // Read the GPS data and background map.
  String[] geoCoords = loadStrings("gpsTrack.txt");
  backgroundMap = loadImage("background.png");
  
  WebMercator proj = new WebMercator();
  
  // Convert the GPS coordinates from lat/long to WebMercator
  coords = new ArrayList<PVector>();  
  for (String line: geoCoords)
  {
    String[] geoCoord = split(line.trim()," ");
    float lng = float(geoCoord[0]);
    float lat = float(geoCoord[1]);
    coords.add(proj.transformCoords(new PVector(lng,lat)));
  } 
  
  // Store the WebMercator coordinates of the corner of the map.
  // The lat/long of the corners was provided by OpenStreetMap
  // when exporting the map tile.
  tlCorner = proj.transformCoords(new PVector(-0.07,52.28));
  brCorner = proj.transformCoords(new PVector( 1.64,51.54));
}

// Convert from WebMercator coordinates to screen coordinates.
PVector geoToScreen(PVector geo)
{
  return new PVector(map(geo.x,tlCorner.x,brCorner.x,0,width),
                     map(geo.y,tlCorner.y,brCorner.y,0,height));
}
 

Most of the code above is for reading the GPS data from a file and storing the coordinates. The projection of longitude/latitude coordinates into a common WebMercator projection happens with the transformCoords method using the proj object created at the start of the readData() method.

While the background map tile produced by OpenStreetMap is already using the WebMercator projection, we still need to know what the coordinates of its corners are so we can relate the background image position to the GPS coordinates that are used to display a line on top of it. The longitude/latitude of the corners (provided by OpenStreetMap) are converted into WebMercator coordinates at the end of readData().

While the WebMercator projection is planar (i.e. there is a direct correspondence between coordinate values and screen pixel locations), the numbers themselves are not in pixel units. So the method geoToScreen() is used to convert from WebMercator to pixel units using Processing's own linear map() method. This uses the coordinates of the corners of the background map to rescale all WebMercator coordinates to fit within the screen space.