📌 Practical Example: Calculating Distance Between Cities

In this tutorial, we’ll create a set of ACF functions that allow you to calculate the distance between two cities based on their names.

distancetest

We'll implement the following components:

🌍 Looking Up City Coordinates via API

To retrieve the geographic coordinates (latitude and longitude) of a city, we use a free public API: Nominatim, based on OpenStreetMap.

We go to the API function itself. It is called GetCityCoordinates

This will return a JSON object, containing the latitude and the longitude coordinates.

Function GetCityCoordinates ( string cityName ) // → float lat, float lon
    string baseUrl = "https://nominatim.openstreetmap.org/search";
    string url = format("%s?q=%s&format=json&limit=1", baseUrl, Url_Encode (cityName));

    string res = http_get(url,"User-Agent: MyACFapp/1.0");

    if (res == "" || HTTP_STATUS_CODE != 200) then
        throw "No response from Nominatim API";
    end if
     
    JSON js;
    js["a"] = res; 
   
    string latStr = js["a[1].lat"];
    string lonStr = js["a[1].lon"];

    if (latStr == "?" || lonStr == "?") then
        throw "City not found";
    end if
    float lat = float(latStr);
    float lon = float(lonStr);
    return json ("lat", lat, "lon", lon); 
End

📐 Calculating the Distance

Now that we can request coordinates for two cities, we can calculate the distance between them using the Haversine formula. You could use the Pythagorean theorem, but since we're not flat-earthers, we need a formula that accounts for the curvature of the Earth — and that's exactly what the Haversine formula does.

But before we implement it, we need a helper function: Atan2.

ℹ️ What is atan2?

atan2 is a special arctangent function that returns the angle (in radians) between the x-axis and the point (x, y) on a 2D plane. It’s a smarter version of atan(y / x) that also takes direction into account.

🔍 Why not just use atan(y / x)?

Because atan(y / x) loses quadrant information:

atan2(y, x) fixes this by:

Then we can make a function for the Haversine formula.

Function HaversineDistance:

Function HaversineDistance ( float lat1, float lon1, float lat2, float lon2 )

    // Earth's radius in kilometers
    float R = 6371;

    // Convert degrees to radians
    float fi1 = lat1 * pi / 180;
    float fi2 = lat2 * pi / 180;
    float dtfi = (lat2 - lat1) * pi / 180;
    float dtlb = (lon2 - lon1) * pi / 180;

    // Haversine formula
    float a = sin(dtfi / 2)^2 + cos(fi1) * cos(fi2) * sin(dtlb / 2)^2;
    float c = 2 * atan2(sqrt(a), sqrt(1 - a));

    return R * c;
End

🧩 Wrapping Up

Now that we’ve built all the pieces, let’s put them together.

We’ll create a function that takes two city names, looks up their coordinates, and returns the distance between them in kilometers.


Function: CityDistance

Function CityDistance ( string cityA, string cityB )

    JSON a = GetCityCoordinates(cityA);
    JSON b = GetCityCoordinates(cityB);
  
    float lat1 = float(a["lat"]);
    float lon1 = float(a["lon"]);
    float lat2 = float(b["lat"]);
    float lon2 = float(b["lon"]);

    return HaversineDistance(lat1, lon1, lat2, lon2);

End

💡 Bonus: Presenting the Result — the Nerdy Way

If we’re feeling a bit nerdy (and why not?), we can enhance the result by adding a descriptive text and calculating how long light would take to travel the same distance — in microseconds.

To do this, we use a value converter called m2lighty to convert meters into light-years. Then we multiply the result by the number of seconds in a year to get the time light would take, and finally multiply by 10^6 to express it in microseconds.

Function DisplayCityDistance:

Function DisplayCityDistance (string cityA, string cityB)
    float distkm = CityDistance(cityA, cityB); 
    float distmiles = distkm*0.621371; 
    float distlysec = m2lighty(distkm*1000.0)*365*24*60*60; 

    return format ("The distance between %s and %s is %.1f km, (%.1f miles)\n(%f us at lightspeed)", 
        cityA, cityB, distkm, distmiles, distlysec*10^6); 
end

Testing

In FileMaker, we run this script:

Set Field [test::result; ACF_Run("DisplayCityDistance"; test::FromCity; test::ToCity)]

For the cities of New York and Fredrikstad, it came up with

The distance between New York and Fredrikstad is 5948.5 km, (3696.2 miles)
(19828.351929 us at lightspeed)

Full listing:

This listing is for plugin version 1.7.5.2, which has the URL_Encode and Atan2 as standard functions.

Package CityDistances "Functions to calculate the distance between cities";


Function HaversineDistance ( float lat1, float lon1, float lat2, float lon2 )

    print format ("\n\n %f, %f, %f, %f", lat1, lon1, lat2, lon2); 

    // Earth's radius in kilometers
    float R = 6371;

    // Convert degrees to radians
    float fi1 = lat1 * pi / 180;
    float fi2 = lat2 * pi / 180;
    float dtfi = (lat2 - lat1) * pi / 180;
    float dtlb = (lon2 - lon1) * pi / 180;

    // Haversine formula
    float a = sin(dtfi / 2)^2 + cos(fi1) * cos(fi2) * sin(dtlb / 2)^2;
    float c = 2 * atan2(sqrt(a), sqrt(1 - a));

    return R * c;

End


function OsloNYCDist ()
    float d = HaversineDistance(59.9139, 10.7522, 40.7128, -74.0060); // Oslo to NYC
    Print format("Distance: %.1f km", d);
    return d; 
end

Function GetCityCoordinates ( string cityName ) // → float lat, float lon

    string baseUrl = "https://nominatim.openstreetmap.org/search";
    string url = format("%s?q=%s&format=json&limit=1", baseUrl, URL_Encode(cityName));

    string res = http_get(url,"User-Agent: MyACFapp/1.0");

    if (res == "") then
        throw "No response from Nominatim API";
    end if
    // print res; 
    JSON js;
    js["a"] = res; 
    print string(js); 
    string latStr = js["a[1].lat"];
    string lonStr = js["a[1].lon"];

    if (latStr == "" || lonStr == "") then
        throw "City not found";
    end if
// print latstr; 
    float lat = float(latStr);
    float lon = float(lonStr);
    return json ("lat", lat, "lon", lon); 

End



Function CityDistance ( string cityA, string cityB )

    JSON a = GetCityCoordinates(cityA);
    JSON b = GetCityCoordinates(cityB);
    //print string (a); 
    //print string (b); 

    float lat1 = float(a["lat"]);
    float lon1 = float(a["lon"]);
    float lat2 = float(b["lat"]);
    float lon2 = float(b["lon"]);

    return HaversineDistance(lat1, lon1, lat2, lon2);

End

Function DisplayCityDistance (string cityA, string cityB)

    float distkm = CityDistance(cityA, cityB); 
    float distmiles = distkm*0.621371; 

    float distlysec = m2lighty(distkm*1000.0)*365*24*60*60; 

    return format ("The distance between %s and %s is %.1f km, (%.1f miles)\n(%f us at lightspeed)",
        cityA, cityB, distkm, distmiles, distlysec*10^6); 

end