Figuring Out the Implementation of a Math Function Using Grapher on Mac OS X

In a side project I’m working on right now in Django/Python, I need to do some work with latitudes and longitudes on a map. What follows is a description of how I tackled on of the specific challenges I faced.

In a nutshell, I’m going to be working with some data sets that are grouped into grid squares on a map. The grid squares are based on the integer values of latitudes and longitudes, which I have arbitrarily chosen to identify by the coordinates at their southwest corner.

So let’s put this in a picture to make it a bit easier to visualize. Basically, just try to visualize a flat map of the Earth. Longitude runs E/W along the X axis, with 0 degrees being the central horizontal point and 180/-180 degrees being the edges (which are actually the same thing, since the Earth wraps around in a sphere), while latitude runs N/S (along the Y axis), with 90 being the north pole and -90 being the south.

Note the following map isn’t really to “scale,” in the sense that the coordinate system I am using doesn’t perfectly match up in the real world with the map behind it. I’m just using the map to help visualize the grid:

I’ve divvied up the map into “squares”, each of which is 1 degree of latitude tall and 1 degree longitude wide. The points are identified by their southwest corner coordinates, so the bottom left grid square on the map would be (-180,-90). I also plotted another random grid square at (-120,40).

So this is just a long-winded introduction to the problem at hand. I am going to be creating larger regions composed of these grid squares, which will be expanding outwards from a single grid square. There’s a chance that some of these regions will expand outwards at the edges of the map, so that the longitude and latitude points will have to be able to flip around and come back to the other side of the map.

In order to accomplish this, I basically need to be able to perform some simple math operations on the longitude and latitude integers that identify the grid squares. Take the grid square at (-180,-90). If I were to move three squares to the east, I would be at (-177,-90). If I were to move three squares to the west, though, I would be at (177,-90), because the longitude flips around to the other end of the scale at that point. Latitude is slightly different. If I go “up” three squares I’m at (-180, -87), whereas if I go “down” three squares, I’m at (0,-87)…in other words, the same latitude either direction.

Now we finally get to some programming.

To make sure that latitude and longitude behaved correctly in the calculations I would perform on them, I decided to encapsulate each of them in their own class. For my purposes (since the grid squares are spaced at integer values of latitude and longitude), I decided to subclass int, since the only behaviors I really needed to override were addition and subtraction.

Here’s a look at the final classes for both Longitude and Latitude:

class Lng(int):

    def __add__(self, number):
        return self.__bound(int.__add__(self, number))

    def __sub__(self, number):
        return self.__bound(int.__sub__(self, number))

    def __bound(self, num):
        return int(num - 360*math.floor(num/360.0 + .5))
class Lat(int):

    def __add__(self, number):
        return self.__bound(int.__add__(self, number))

    def __sub__(self, number):
        return self.__bound(int.__sub__(self, number))

    def __bound(self, num):
        return int((num - 180*math.floor((num/180.0) + .5))*2*(math.floor(math.sin((math.pi*num)/180 + math.pi/1.9999)) + .5))

What I really wanted to discuss here was my process for coming up with the “bound” functions in each class. “Bound” is what I named the function which takes the result of an addition or subtraction function and ensures that it stays within the boundaries defined by the map, according to the rules of that class.

Now, the last time I took a math class was in college (calculus), so what do I know about defining an oscillating function that repeats an upward movement in a linear fashion, or in the case of latitude, reverses that direction? In short, not much.

So how did I come up with a solution? Simple. I whipped out the excellent application called Grapher, which comes pre-installed on Mac OS X. This tool allows me to devise complex mathematical functions and see them plotted immediately. And that’s what I did.

Here’s how that process worked. Let’s start with longitude.

For longitude, I knew I needed a function that would start at (-180,-180) and proceed linearly until it reached (179,179). At x=180 though, the function would have to flip back down and start back at -180, since 180 and -180 are actually the same longitudinal line.

I basically started “brainstorming” at the Grapher function input, and finally stumbled upon:

Function 1. y=x-floor(x)

A good start, but the the lines are running from 0 to 1 on the y axis; we want the middle of the y axis to be 0. This should do the trick:

Function 2. y=x-floor(x+.5)

This function cycles through from -.5 to .5. We’re almost there, now we just need it to cycle through -180 to 180. A bit more fumbling around and we stumble upon:

Function 3. y=x-360*floor(x/360 + .5)

There you go, and that’s exactly what’s coded up in the __bound() method of Lng cited above.

Latitude was a bit trickier. In fact, I’m not thrilled with the implementation, but it works, so I’ll leave it alone until someone advises me of a better solution.

So our work with longitude sets things up nicely for latitude. We’ve already got a function that we can use to get us part of the way there:

Function 4. y=x-180*floor(x/180 + .5)

The only problem is, for latitude we need every other one of these upward sloping to be downward sloping (i.e. negative), since when latitude hits 90 degrees (the north pole) it then retreats back down rather than starting from the bottom again at -90 degrees.

What we need then is a function that oscillates back and forth between 1 and -1. If we can get that function to do its flip every 180 units, and we align it right, then we can apply it as a multiplier to Function 4 above.

I’m not very bright; the only function I know that oscillates is sin(x).

Function 5. y=sin(x)

Looks great. It’s flipping positive and negative at regular intervals. Now if we can only coax it to doing it at the intervals we want. Let’s see, it appears to be flipping signs in units of 3.14… hey, this is ringing a bell:

Function 6. y=sin(pi*x)

Sure enough, when we multiply x by a factor of π we get a graph that oscillates between positive and negative every one unit. For the next step, let’s get it to truly flip from 1 to -1 and get rid of the curving line. To do that let’s apply a floor() function, which will cause the line to flip between 0 and -1. Then all we need to do is add 0.5 (so that it flips between -.5 and .5) and then multiply the whole thing by 2. Et voila:

Function 7. y=2*(floor(sin(pi*x))+.5)

Getting closer. Now, let’s see if we can’t get this to stretch across 180 units for each sign switch. Some messing around a bit gets us here:

Function 8. y=2*(floor(sin((pi*x)/180))+.5)

Almost there! Now the only problem is that we have to shift everything to the left, since right now each range starts at 0 and ends at 180 on the X axis, whereas latitude starts at -90 and ends at 90 on the X axis. To be honest this required a feat of minor inspiration, which yielded the following:

Function 9. y=2*(floor(sin((pi*x)/180) + (pi/2))+.5)

By adding pi/2 to the sin function, we shift everything over by half the range, which yields exactly the result we are looking for!* Now, all we need to do is apply our multiplier to Function 4:

Function 10. y=(x-180*floor(x/180 + .5))*2*(floor(sin((pi*x)/180) + (pi/2))+.5)

And as you can see, this is pretty much exactly what’s coded as the __bound() function for Latitude above.

So how does all of this at long last play out in our code? Well, since we’ve overridden the addition and subtraction functions for each of these and applied our __bound() function to the results of these calculations, it means we can add and subtract numbers from these objects and they will stay within the defined boundaries of our grid.

>>> import grid
>>> x = grid.Lat(10)
>>> x
10
>>> x + 60
70
>>> x + 80
90
>>> x + 85
85
>>> x + 250
-80
>>> x + 270
-80
>>> y = grid.Lat(10)
>>> y
10
>>> y + 60
70
>>> y + 80
90
>>> y + 85
85
>>> y + 250
-80
>>> y + 270
-80
>>> y - 270
80
>>> x = grid.Lng(10)
>>> x
10
>>> x + 120
130
>>> x + 169
179
>>> x + 170
-180
>>> x + 350
0
>>> x - 350
20

What’s the moral of the story here? I guess it’s that you don’t always have to know what you’re doing to figure out how to do something. My math and trigonometry skills are rusty at best, and yet with a little futzing around with Grapher I was able to whip together a pretty tidy little function that did just what I wanted. I hope this post inspires you to make use of Grapher if you ever find yourself in need of some serious math back-up.

On a final note, if anyone reading this has a better implementation of the mathematical functions I whipped up here, I’d love to hear about it.

*Update: Re-reading my statement above that sin() was the only oscillating function I knew, I chuckled to myself that I also know cos()…at which point it dawned on me that I could replace out sin(x + pi/2) with cos(x), which I have since done.

Post a comment

Contact Info
will not be published

include http://

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