This is Part 5 in a multi-part series bout using the location functions in FileMaker Go.
In my last post, I described how to calculate geographic distances between two locations. That’s fine for knowing how close you are to something, but it’s only half of the picture for knowing your relative location. Is the party 10 km north of your current location, or west?
Bearing
The calculation of the initial bearing of the straight line “greatest circle” path between two points, like the haversine formula, involves spherical trigonometry that I wont bother you with except to show you the calculation:
Let (
[
// Convert angles to radians
~longitudeDifference = Radians ( endLongitude – startLongitude ) ;
~latitudeDifference = Radians ( endLatitude – startLatitude ) ;
~startLatitude = Radians ( startLatitude ) ;
~startLongitude = Radians ( startLongitude ) ;
~endLatitude = Radians ( endLatitude ) ;
~endLongitude = Radians ( endLongitude ) ;
// calculate forward bearing of path at start point
~bearingY = Sin ( ~longitudeDifference ) * Cos ( ~latitudeDifference ) ;
~bearingX =
Cos ( ~startLatitude )
* Sin ( ~endLatitude )
– Sin ( ~startLatitude )
* Cos ( ~endLatitude )
* Cos ( ~longitudeDifference ) ;
~bearing = // Atan2 ( ~bearingY ; ~bearingX ) in range [0,2*Pi)
Case (
~bearingX < 0 ; Atan ( ~bearingY / ~bearingX ) + Pi ; ~bearingX > 0 and ~bearingY < 0 ; Atan ( ~bearingY / ~bearingX ) + 2 * Pi ; ~bearingX ≠ 0 ; Atan ( ~bearingY / ~bearingX ) ; ~bearingY > 0 ;
Pi / 2 ;
~bearingY < 0 ;
3 / 2 * Pi ;
/* Else, ~bearingX = ~bearingY = 0 */
0
)
] ;
Degrees ( ~bearing )
)
Cardinal Direction
The calculation above will give us a compass bearing, a number of degress between 0 and 360. That’s a fine start, but what am I as a user supposed to do with 143.490867°? Is that left or right? Converting that bearing to a cardinal direction is the natural next step. Since cardinal directions correspond to particular numerical ranges of bearing, we can just test for each range using the Case function to select the matching nominal direction:
Case ( $bearing < 45 ; "N" ; $bearing < 135 ; "E" ; $bearing < 225 ; "S" ; $bearing < 315 ; "W" ; /* Else */ "N" )
If we want to use more precise cardinal directions, we can just specify smaller ranges and more of them:
Case ( $bearing < 22.5 ; "N" ; $bearing < 67.5 ; "NE" ; $bearing < 112.5 ; "E" ; $bearing < 157.5 ; "SE" ; $bearing < 202.5 ; "S" ; $bearing < 247.5 ; "SW" ; $bearing < 292.5 ; "W" ; $bearing < 337.5 ; "NW" ; /* Else */ "N" )
This works fine, but something bugs me about it. We’re performing a test of the same variable several times, and the only difference is what number it’s being compared to each time. The ranges being checked are all exactly the same size, but you wouldn’t know that without subtracting each from the next one by one. I hate doing arithmetic myself. This is almost an example of the magic number code smell (but not exactly, since the calculation effectively defines what each numerical range represents). If we let FileMaker figure out what the ranges should be for us, we get an equally effective calculation with some differences that are worth contemplating:
Let ( [ ~sliceAngleSize = 360 / 8 ; // divide circle into equal slices ~adjustedBearing = // shift values so north doesn't straddle 360°/0° Mod ( $bearing[$i] + ~sliceAngleSize / 2 ; 360 ) ; ~bearingSlice = // which slice is the bearing in? Floor ( ~adjustedBearing / ~sliceAngleSize ) ] ; Choose ( ~bearingSlice ; "N" ; "NE" ; "E" ; "SE" ; "S" ; "SW" ; "W" ; "NW" ) )
This version is longer and more complicated, but it better expresses the meaning of what the calculation is doing: splitting a circle into equal slices and figuring out which slice the compass bearing falls in. Nobody reading this calculation in the future has to wonder what’s so special about the number 157.5. By using the Choose function instead of Case, I’m not comparing the $bearing variable against pre-computed values 8 times. I also don’t have to calculate 16 new critical values when I decide I want to distinguish secondary-intercardinal directions like WSW; I can just replace the 8 with 16 when calculating the ~sliceAngleSize value and insert the additional direction names in the Choose function. Let FileMaker do the arithmetic. I hear computers are supposed to be pretty good at that, especially when compared to developers.
That said, there are good reasons to use pre-computed values in code. Arithmetic is work, and doing some of it in advance can help a calculation execute faster when users really need it. Many of the functions in a modern calculator start by doing a table lookup, and the first several coefficients used for any Taylor series calculations are usually pre-computed.
So is there a performance justification to using the first calculation instead of the second? I didn’t know, so I wrote a test script to compare the execution times of the two versions. If the Case version of this calculation is faster, I’ll have to weigh the magnitude of the performance difference against the readability advantage of the Choose version, which can be a difficult decision.
As it turns out, the Choose method is no slower than performing 4 comparisons for the Case method, and the extra arithmetic is actually faster than 8 or 16 Case comparisons, though not by much. Choose wins! No difficult decisions today!
How Do I Get There?
Compass bearing and cardinal direction are great for orienteering and navigation for boats and aircraft. However, I usually find myself walking or driving along roads, which almost never offer a straight-line path to my destination. I need directions, not just my relative position, if I want to actually get to a destination.
I could write a script for FileMaker to just ask the Google Directions API how to get to my destination, along similar lines to how we handled geocoding and transit distance in previous posts. I could even skip the steps of parsing the JSON response and making a user interface to display the results by just opening Google’s directions in a web viewer, as Douglas Alder described. But other folks have already invested a lot of work into building apps specifically designed for giving you directions, like Navigon and Waze, and those apps are really good. Anything I could whip together in FileMaker in a day is going to be lame in comparison. I’d rather just send my destination to the other app and let it do the work — use the right tool for the job.
In iOS, apps can send each other instructions with custom URI schemes. By building a URI and “opening” it with FileMaker’s Open URL script step, we can launch another app and give it something to do, like displaying directions to a location. There are a few directories of apps and their URI schemes that you can browse for ideas, like handleOpenURL and the Akosma URL Scheme wiki page. Here’s how to get directions from the handful of navigation apps I already have:
Apple Maps
Unfortunately, following Apple’s documentation on the Maps app URL scheme to the letter will actually launch Google Maps in a web page — very embarrassing! To launch the Maps app on iOS from FileMaker Go, we have to start our URLs with “maps:/” instead of “http://maps.apple.com/”. On the flip side, using the URL scheme as documented works just fine on the desktop from FileMaker Pro; it even launches the Maps app on Macs running OS X 10.9.
If ( Get ( SystemPlatform ) = 3 ; // iOS "maps:/" ; /* Else */ "http://maps.apple.com/" ) & "?daddr=" // destination address & GetAsURLEncoded ( $address )
Google Maps
The Google Maps URL scheme is very similar to the Apple Maps scheme. You can just substitute “comgooglemaps:/” for “maps:/” and be done. But the Google Maps scheme also lets you specify that you want directions via walking, bicycling, and public transit instead of only driving directions. (The Apple Maps app lets the user select driving, walking, or transit, but only inside the app.)
"comgooglemaps:/?daddr=" // destination address & GetAsURLEncoded ( $address ) & If ( not IsEmpty ( $directionsMode ) ; "&directionsmode=" & $directionsMode )
Waze
Waze is a navigation app that lets you share handy information like traffic conditions and gas prices, and see the information shared by other users. The Waze URL scheme is a little different from the Apple and Google schemes, but it’s still pretty straightforward.
"waze://?q=" & GetAsURLEncoded ( $address ) & "&navigate=yes" // start navigation automatically when switching to the app
Unfortunately, Waze appears not to respect the “navigate=yes” parameter instructing it to start navigation automatically when you give it an address. However, it works fine when you send it geographic coordinates instead.
"waze://?ll=" & $latitude & "," & $longitude & "&navigate=yes" // start navigation automatically when switching to the app
(2013-11-19 Update: Waze has confirmed for me that they do not support navigate=yes for locations specified by an address. I wasn’t just doing something wrong. Bummer. You’ll have to geocode your addresses first and use latitude and longitude if you want to start navigation automatically with Waze.)
Navigon
The Navigon URL scheme is slightly more obtuse. You have to specify the parameters of an address separately in a specific order rather than relying on the app to parse it out. Navigon also needs you to give it the street name and house number separately; most of us store those details in a single field, so we either have to parse those out from the combined field, or use the extra data returned from the Google geocoding API.
"navigon://address/" & GetAsURLEncoded ( $placeName ) & "/" & $countryCode & If ( not IsEmpty ( $state ) ; "-" & $state ) & "/" & $postCode & "/" & GetAsURLEncoded ( $city ) & "/" & GetAsURLEncoded ( $street ) & "/" & $houseNumber
If you want to use any of these techniques in your own applications, you’re welcome to copy from the demo file for this post. Location.fmp12