Sunday, 3rd March 2013
This post describes my highly convoluted method of making an animated globe using Khan Academy's computer science framework.
A few weeks ago I found a set of coordinates for a world map in JSON format. I can't remember where exactly, but I think it was via a StackOverflow question. The first thing I did was to convert it into an SVG world map, because I like SVG and I've been meaning to get a world map for a while.
Then, since I've been using Khan Academy's computer science section a lot recently, I made a KA version using Processing. In both cases, I scaled the data to useful dimensions and then rounded to sensible precision; since KA's canvas only accepts integers, there was no point have coordinates to eight decimal places. I then striped out any points that were identical to preceding points after rounding to reduce redundancy. I could have reduced redundancy further by removing multiple points lying on a straight line, but it turned out to be trickier than I expected to do well.
And then, I started wondering how I could make draw an image of a globe using this information.
Diversion into Pygame
My first idea was to draw the image of world map off screen and use get(x, y) to find the colour of pixels on the off-screen map, but that didn't seem to work and I thought might be a bit slow anyway. So, I converted the data into yet another format: pygame. So I wrote a Python program that would write a Pygame program that would draw the world on its screen as a series of black polygons on a white background. Then it looped through all the pixels on the screen and recorded whether they were white or black. It then outputted this information to a text file and a series of 0s and 1s, in which each line represented a row of the screen and each character represented a pixel of the screen. The text file actually looked like a world map when I zoomed out.
The point of all this was to get 2D grid that could be used to determine whether there should be land or sea. The alternative would have been to try and work it out myself based on the coordinates for the outlines of the landmasses, but I don't know how to do that (though it would be interesting to work it out). I chose to save the data (in a variable called land) as an array of strings, which is more compact than an array of arrays and simpler than an array of binary or some other type of number. I could then find out if there should be land at coordinate (x, y) with:
land[y].charAt(x) === "1";
The problem then was to convert from a 2D rectangular array into a 2D spherical surface in three dimension and to the 2D circular projection of that surface onto the screen. I don't know much about the various ways to project the surface of a sphere onto two (flat) dimensions, other than there are a lot of them. My naïve approach was to draw a filled circle of pixels using information from a square of the landmass data. I assumed that the y-value, or lattitude, would correspond directly and so it was just a matter of working out how the x-coordinate mapped from the circle to the square. As it happens, I think the map is an equirectangular projection, so my approach turns out to be correct.
For a given y value on the circle, I could calculate the width of the circle (2 × dx above) with a bit of trigonometry as radius × sin(θ) where θ = arccos(y / radius). This gets the maximum width of the circle at that latitude, so to map that to the maximum extent of the square, you just divide the radius (also the width of the square) by that value. This allows you to scale an x value on the circle to an x value on the square.
Then it was just as a matter of changing where the square started on the map (and wrapping values appropriately) to create the spinning effect and rotating the globe by 23 degrees to get it in the plane of the sun. It might be nice to shade the globe so it looks like it is light from the side, which shouldn't be too hard as I'm already colouring each pixel individually.