Calculate bearing based on two sets of geographic coordinates

I’m building a scenario that works with geographic coordinates. The data that comes in contains a Latitude/Longitude pair that I want to compare to a constant lat/lon pair. I use the GPS Tools module to calculate distance, but I need a way to calculate the bearing of the incoming lat/lon pair in relation to the constant. The goal is to output a cardinal direction, as in “South” or “Northwest”.

Well there are well tried trigonometric formulas to do this. I believe Make’s Math app will help you as it does a variety of trigonometric functions

This will give you the direction in radiants

radiants = arctan((latB-latA)/(lonB-LonA))

You can convert it to degrees with

degrees = (radiants*180)/π

And then you can convert the degrees into cardinal direction based on ranges of degrees

3 Likes

Ok… does it matter if latB-latA or lonB-lonA turns out to be a negative value? In other words, does the larger lat or lon value need to subtract the other? Forgive my questions - I never did very well in trigonometry.

Thanks

A and B are relative sets of GPS latitude and longitude values for a particular location A and B. So you’re always taking the latitude of B minus Latitude of A dividing by longitude of B minus longitude of A and taking the arctangent of the result which gives you radiants.

I have some python code that does this as well and even calculates the cardinal bearing from 8 bearings by simply adding 22.5 degrees and taking the modulo of 360 and then dividing that into 45 to get the bearing index 0-7 which relates to N, NE, E etc…

import math

def calcBearing (lat1, long1, lat2, long2):
    dLon = (long2 - long1)
    x = math.cos(math.radians(lat2)) * math.sin(math.radians(dLon))
    y = math.cos(math.radians(lat1)) * math.sin(math.radians(lat2)) - math.sin(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.cos(math.radians(dLon))
    bearing = math.atan2(x,y)   # use atan2 to determine the quadrant
    bearing = math.degrees(bearing)

    return bearing

def calcNSEW(lat1, long1, lat2, long2):
    points = ["north", "north east", "east", "south east", "south", "south west", "west", "north west"]
    bearing = calcBearing(lat1, long1, lat2, long2)
    bearing += 22.5
    bearing = bearing % 360
    bearing = int(bearing / 45) # values 0 to 7
    NSEW = points [bearing]

    return NSEW

# White house 38.8977° N, 77.0365° W
lat1 = 38.8976763
long1 = -77.0365298
# Lincoln memorial 38.8893° N, 77.0506° W
lat2 = 38.8893
long2 = -77.0506

points = calcNSEW(lat1, long1, lat2, long2)
print ("The Lincoln memorial is " + points + " of the White House")
print ("Actually bearing of 231.88 degrees")

print ("Output says: The Lincoln memorial is south west of the White House ")
3 Likes

Here’s a scenario that does the math for you and evaluates the cardinal bearing. Just copy and paste this into your scenario editor

{
    "subflows": [
        {
            "flow": [
                {
                    "id": 2,
                    "module": "util:SetVariables",
                    "version": 1,
                    "parameters": {},
                    "mapper": {
                        "variables": [
                            {
                                "name": "latitude1",
                                "value": "38.8976763"
                            },
                            {
                                "name": "longitude1",
                                "value": "-77.0365298"
                            },
                            {
                                "name": "latitude2",
                                "value": "38.8893"
                            },
                            {
                                "name": "longitude2",
                                "value": "-77.0506"
                            }
                        ],
                        "scope": "roundtrip"
                    },
                    "metadata": {
                        "designer": {
                            "x": 0,
                            "y": 0,
                            "name": "set latitudes and longitudes"
                        },
                        "restore": {
                            "expect": {
                                "variables": {
                                    "items": [
                                        null,
                                        null,
                                        null,
                                        null
                                    ]
                                },
                                "scope": {
                                    "label": "One cycle"
                                }
                            }
                        },
                        "expect": [
                            {
                                "name": "variables",
                                "type": "array",
                                "label": "Variables",
                                "spec": [
                                    {
                                        "name": "name",
                                        "label": "Variable name",
                                        "type": "text",
                                        "required": true
                                    },
                                    {
                                        "name": "value",
                                        "label": "Variable value",
                                        "type": "any"
                                    }
                                ]
                            },
                            {
                                "name": "scope",
                                "type": "select",
                                "label": "Variable lifetime",
                                "required": true,
                                "validate": {
                                    "enum": [
                                        "roundtrip",
                                        "execution"
                                    ]
                                }
                            }
                        ],
                        "interface": [
                            {
                                "name": "latitude1",
                                "label": "latitude1",
                                "type": "any"
                            },
                            {
                                "name": "longitude1",
                                "label": "longitude1",
                                "type": "any"
                            },
                            {
                                "name": "latitude2",
                                "label": "latitude2",
                                "type": "any"
                            },
                            {
                                "name": "longitude2",
                                "label": "longitude2",
                                "type": "any"
                            }
                        ]
                    }
                },
                {
                    "id": 6,
                    "module": "math:EvaluateExpression",
                    "version": 1,
                    "parameters": {},
                    "mapper": {
                        "expression": "atan2(cos({{2.latitude2}}*pi/180) * sin(({{2.longitude2}}-{{2.longitude1}})*pi/180),cos({{2.latitude1}}*pi/180) * sin({{2.latitude2}}*pi/180) - sin({{2.latitude1}}*pi/180) * cos({{2.latitude2}}*pi/180) * cos(({{2.longitude2}}-{{2.longitude1}})*pi/180))*180/pi"
                    },
                    "metadata": {
                        "designer": {
                            "x": 300,
                            "y": 0,
                            "name": "calculate bearing"
                        },
                        "restore": {},
                        "expect": [
                            {
                                "name": "expression",
                                "type": "text",
                                "label": "Expression",
                                "required": true
                            }
                        ]
                    }
                },
                {
                    "id": 8,
                    "module": "util:SetVariable2",
                    "version": 1,
                    "parameters": {},
                    "mapper": {
                        "name": "bearing_in_degrees",
                        "scope": "roundtrip",
                        "value": "{{if(6.result >= 0; 6.result; 6.result + 360)}}"
                    },
                    "metadata": {
                        "designer": {
                            "x": 600,
                            "y": 0,
                            "name": "bearing in degrees"
                        },
                        "restore": {
                            "expect": {
                                "scope": {
                                    "label": "One cycle"
                                }
                            }
                        },
                        "expect": [
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Variable name",
                                "required": true
                            },
                            {
                                "name": "scope",
                                "type": "select",
                                "label": "Variable lifetime",
                                "required": true,
                                "validate": {
                                    "enum": [
                                        "roundtrip",
                                        "execution"
                                    ]
                                }
                            },
                            {
                                "name": "value",
                                "type": "any",
                                "label": "Variable value"
                            }
                        ],
                        "interface": [
                            {
                                "name": "bearing_in_degrees",
                                "label": "bearing_in_degrees",
                                "type": "any"
                            }
                        ]
                    }
                },
                {
                    "id": 7,
                    "module": "util:SetVariable2",
                    "version": 1,
                    "parameters": {},
                    "mapper": {
                        "name": "bearing",
                        "scope": "roundtrip",
                        "value": "{{switch(floor((8.bearing_in_degrees + 22.5 % 360) / 45); 0; \"north\"; 1; \"north east\"; 2; \"east\"; 3; \"south east\"; 4; \"south\"; 5; \"south west\"; 6; \"west\"; 7; \" north west\")}}"
                    },
                    "metadata": {
                        "designer": {
                            "x": 900,
                            "y": 0,
                            "name": "generate cardinal bearing"
                        },
                        "restore": {
                            "expect": {
                                "scope": {
                                    "label": "One cycle"
                                }
                            }
                        },
                        "expect": [
                            {
                                "name": "name",
                                "type": "text",
                                "label": "Variable name",
                                "required": true
                            },
                            {
                                "name": "scope",
                                "type": "select",
                                "label": "Variable lifetime",
                                "required": true,
                                "validate": {
                                    "enum": [
                                        "roundtrip",
                                        "execution"
                                    ]
                                }
                            },
                            {
                                "name": "value",
                                "type": "any",
                                "label": "Variable value"
                            }
                        ],
                        "interface": [
                            {
                                "name": "bearing",
                                "label": "bearing",
                                "type": "any"
                            }
                        ]
                    }
                }
            ]
        }
    ],
    "metadata": {
        "version": 1
    }
}
5 Likes

Here’s another really good resource for calculating distance and bearing…

2 Likes

Alex,

Thanks for all of this. I’ll work this solution into my existing scenario and get back to you.

At the risk of showing my inexperience with advanced Make.com practices, how do I paste the JSON structure you provided into my Scenario editor?

Appreciate your help with this.

Just copy the scneario JSON using the copy tool on my post above (or select the JSON text manually and control/command-c). Then open a blank scenario or an existing scenario and do a right-click and select Paste from the Make context window, or type control/command-v to paste in the JSON. When you paste the scenario blueprint JSON into a scenario editor it just “parses” the pasted JSON and creates the blueprint as if you imported a blueprint file.

Video: Copying and Pasting Scenario JSON into a scenario

3 Likes

Ok, I got it. That’s what it sounded like you meant, but for some reason Chrome wasn’t working and playing nicely with the Scenario Editor. I had to use a different browser to paste your JSON in a new Scenario. Thanks for all the assistance. This will get my project on track.

Good stuff. I did test out the trigonometric calculation for 2 locations thousands of KM away from each other and compared it to the online baering calculator – the bearing in degrees is not exactly the same but the cardinal bearing seems to be accurate. It seems for locations that are reasonably close to each other it is fine but I believe the bearing does change for 2 locations many thousands of km away from each other and this formula provides the Initial bearing in degrees and a result a reasonable cardinal bearing as well.

3 Likes

try clearing cookies and cache – it will ask you to approve access to clipboard first time you try to paste.

2 Likes

It’s perfect. My use case involves posting recent earthquake information to a local social media feed, so precision isn’t such a big deal. I’m a big fan of Make as it allows me to automate many time-sensitive functions. I have a rather complex Scenario to automate NWS weather bulletins posting to the socials, and this Scenario will take care of nearby (or not-so-nearby) earthquakes.

Thanks so much for your help. I’ll try clearing the local browsing data as you suggest.

2 Likes

Oh that’s interesting – I was wondering why you needed to calculate bearings and had a few ideas in mind but I didn’t guess earthquake information.

The Math app does many many things but I had to externalize the conditional stuff like adding 360 when the bearing result is negative. You can probably combine the last 2 modules and save an operation by combining the conditional on negative bearings and nesting them into the cardinal bearing switch statement, not sure if operation count is important to you. I think the end result can be done in 2 operations - the Math module and the cardinal bearing calculation and if you really want to save operations you can take the expression on cardinal bearing and put it inside wherever you want the bearing to appear entirely reducing the whole calculation into 1 operation – the Math module.

3 Likes