« Back to Processing

Tutorial #5: The IP-mapping globe

| 3 Comments | Published on April 24, 2008
The content that follows was originally published on the Don Havey website at http://donhavey.com/blog/tutorials/tutorial-5-the-ip-mapping-globe/

Earth previewHumans love maps. They love the mapping of any type of information, not strictly geographical. Maps are bursting with information. They transcend the boundaries of language. Maps are good.

I’m going to make a map today. Well, a globe. It’s going to be a very basic extension of our icosahedron-based sphere. We’ll translate a bunch of latitude/longitude pairs into three-dimensional coordinates to visually define the countries, then we’ll add a single marker on the globe that represents the user’s location, based on his or her IP address.

What it looks like

Here’s our completed Earth class: The final result

And here are the classes you’ll need: Earth classes

What you’ll learn

  • Translating latitude/longitude pairs to Points
  • Mapping an IP address to a physical location via Geobytes and PHP

Sounds like fun, right?

Everybody has some great idea that relies on a globe. I’m working on one that warps the globe according to internet usage statistics. I’m sure your idea is better.

Caveats

I usually add my list of imperfections at the end. Today, I’m all about being upfront with you. Here’s a list of needed improvements:

  • This globe is a sphere, not an oblate spheroid, the actual shape of our planet. Mapping coordinates to an oblate spheroid is harder than to a sphere… but I think you could use a modification of my Face class’ is_segment_intersecting() method to project spherical coordinates onto any shape composed of Faces. Of course, that would screw up the accuracy of the coordinates.
    Actually, we’re talking about radial symmetry here… so translating/scaling the coordinates would be a piece of cake if you knew the aspect ratio of the planet. Just imagine a vector perpendicular to the Earth’s minor axis (Y) that connects to the point (in the XZ plane). Scale that according to the aspect ratio, then in turn scale all Y-coordinates to “squish” the thing. Brilliant!
  • The countries are represented by clusters of points on the globe. They’re not connected as shapes. That’s on my “maybe someday” list. The points were extracted from simplified GIS data. If you want the best way to map an accurate world image onto a sphere, try image mapping. Much faster than rendering shapes.
  • We’re only fetching a single IP address right now. It’s much more interesting to compare an IP address location to a database full of similar points, but that’ll be up to you to figure out. I’ve done it before. And if I’ve done it, it’s not hard.
  • This Earth class is dependent upon the Icosahedron class. That’s silly. There is no need to use a complicated sphere of points for such a simple application. Use Processing’s optimized sphere() function if all you need to do is render a sphere, then make the Earth class standalone. This example is structured to allow for future modifications to the globe… warping its shape, for instance.

Now we can get started…

Translating a user’s IP address

There are a handful of IP-locating services on the web. You have to use a service for this type of thing because IP addresses do not have a strict relationship to geography. I use Geobytes. A service like Geobytes compares a given IP address against a huge database of pre-mapped IPs and spits out results based on a closest-match algorithm, along with a “certainty” factor. I don’t know where they got their original data, but you can bet it wasn’t easy.

Geobytes allows up to 20 queries per hour from a single server. I’ve got an account with them so I’m not going to address that limitation here. Their accounts are super cheap if you have a low- to medium-traffic website and want to do some IP mapping. Of course, your Google Analytics account maps IPs automatically.

Here’s how we request an IP translation via Geobytes and PHP (watch out for line breaks!):

<?php
header("Content-type: text/plain");
if(!isset($_GET["ip"])){
  if(!empty($_SERVER['HTTP_CLIENT_IP'])){
    $ip = $_SERVER['HTTP_CLIENT_IP'];
  }else if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
  }else{
    $ip = $_SERVER['REMOTE_ADDR'];
  }
}else{
  $ip = $_GET["ip"];
}
if(isset($ip)){
  $tags = get_meta_tags('http://www.geobytes.com/IpLocator.htm?GetLocation&
                         template=php3.txt&IpAddress='.$ip);
}
if(isset($tags)&&count($tags)>0){
  print $ip."n".$tags['known']."n".$tags['certainty']."n".$tags['latitude']."n".
        $tags['longitude']."n".$tags['city']."n".$tags['region']."n".$tags['country'];
}
?>

See what’s happening here?

First, we’re finding the user’s IP address or using a passed IP address. Note that the only safe way to grab an IP address (if you’re doing something serious with it, like user verification) is through the $_SERVER[“remote_addr”] variable. The other variables listed above are often relayed to our server when a user is connected behind a proxy, and so are occasionally more helpful, but they can be spoofed by malicious users. In our case, we don’t care. Spoof away. You’ll get a different location on our globe.

Secondly, we’re passing the above IP address to a file on the Geobytes server. In return, we’ll be sent a very basic html file with no content and a list of meta tags, each named according the variable it carries, and each relating to the geographic location of the IP address we sent. We’re parsing those variables via PHP’s built-in get_meta_tags() function.

Lastly, we print out the information we need on separate lines, to be parsed with Processing’s loadStrings() function.

Mapping coordinates to a globe

Let’s look at the Earth constructor (sounds pretty impressive, right?):

Earth(String $earth_file,String $ip_file){
  super(); //initialize the icosahedron
  subdivide(4); //subdivide the icosahedron
  data = loadStrings($earth_file);
  for(int i=0;i<data.length;i++){
    String[] temp = split(data[i],' ');
    dots[ndots] = latlon_to_point(float(temp[2]),float(temp[1]));
    ndots++;
  }
  ip_data = loadStrings($ip_file);
  println(ip_data);
  if(match(ip_data[1],"true")!=null&&int(ip_data[2])>50){
    dots[ndots] = latlon_to_point(float(ip_data[3]),float(ip_data[4]));
    ndots++;
  }else{
    println("Could not locate IP address!");
  }
}

So we’re loading our file of country-defining points, translating them to xyz coordinates via the latlon_to_point() function, and then doing the same for our single IP address, which will be tacked on to our “dots” array for convenience. We initiate the Earth via this line in our applet’s setup() function:
e = new Earth("countries.txt","http://donhavey.com/x/ip/ip.php");
If you’re going to use this script for your own uses, swap out the address of the ip.php file for one on your own server, please.

The only other unique function is our latlon_to_point() function:

Point latlon_to_point(float $lat,float $lon){
  float theta = radians(-$lat); //negated to orient the globe correctly in our xyz space
  float gamma = radians(-$lon); //negated to orient the globe correctly in our xyz space
  //"radius" refers to the radius variable defined in our icosahedron class
  //x = radius*cos(theta)*cos(gamma)
  //y = radius*sin(theta)
  //z = radius*cos(theta)*sin(gamma)
  return new Point(radius*cos(theta)*cos(gamma),radius*sin(theta),radius*cos(theta)*sin(gamma));
}

Pretty simple, right? A little bit of trigonometry (that I won’t get into), but that’s the conversion formula for you. Now we’ll just render the final Point in the “dots” array as a marker:
void render(){
  //render the country outline dots
  strokeWeight(1);
  stroke(255,150,150,150);
  for(int i=0;i<ndots-1;i++){
    line(0,0,0,dots[i].x*1.01,dots[i].y*1.01,dots[i].z*1.01);
  }
  //render the IP location marker
  strokeWeight(3);
  stroke(255);
  line(0,0,0,dots[ndots-1].x*1.1,dots[ndots-1].y*1.1,dots[ndots-1].z*1.1);
  fill(255);
  pushMatrix();
  translate(dots[ndots-1].x*1.1,dots[ndots-1].y*1.1,dots[ndots-1].z*1.1);
  sphere(3);
  popMatrix();
  //render the icosahedron
  fill(50,0,0,200);
  noStroke();
  super.render();
}

As I mentioned, we’re doing a lot of unnecessary work by rotating and rendering our custom-built Icosahedron sphere. Obviously, a true sphere does not need to be rotated: it looks the same from any angle. Again, I’d recommend using Processing’s built-in sphere() function if you don’t need to manipulate the actual Points defining the globe.

All done

There you have it. A sexy globe. Enjoy!

One last look: The final result

And in case you didn’t get them up top: Earth classes

Categories: Processing / Tutorials

Tags: / / / / / / / /

3 Comments

matt ditton says:

Hi Don

I was curious about the countries.txt file that you are loading the points from. Each line has three numbers. I understand that the second and third number relate to the lat and long of the location. But am I right in thinking that the first number groups the points into a shape (like a country boundary). It’s seams like a great little file and I was wondering where it came from.

BTW, I really like the site. Nice work on the tutorials.

MattD

Don says:

Yep. Correct. That list of points was generated from GIS data using ArcView GIS… which is a (now outdated) mapping program. I’m a bit of a map geek and tend to amass gigabytes of mapping data, but I think I originally got the stuff in question here:

http://geodata.grid.unep.ch

or here:

http://data.geocomm.com/

I found some worldwide data set of country boundaries, simplified them, and then exported them as points. You can find user-generated plugins for that type of thing on the ArcView website. Converting geospacial data to lat/lon pairs was not very fun, but maybe there’s a better way to do it?

GIS data is grouped by polygons, so I think that was what the first number signified.

Hope that helps.

Tom Gonzalez says:

Don,

Thanks so much for posting the source. For us non math/map types understanding the projection details is invaluable. Have you posted this on the processing discourse? I could not find a link via their search.

– Tom

Leave a Response

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>