Chapter 46: The HTML5 Geolocation API

Distance & Geofencing Math

Implement the Haversine spherical trigonometry formula in JavaScript to calculate great-circle distances, determine geographic bearings, and build circular geofencing triggers.

LEARNING OBJECTIVES
  • Understand why Euclidean flat-plane geometry ($a^2 + b^2 = c^2$) fails for planetary GPS calculations.
  • Implement the mathematical Haversine Formula in pure JavaScript to compute great-circle distance in kilometers, meters, and miles.
  • Calculate the forward azimuth (initial bearing angle) between two geographic coordinate pairs.
  • Architect circular Geofences that trigger entry and exit events when a user moves across spatial boundaries.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

If you take a flat sheet of grid paper, the distance between coordinate $(2, 3)$ and $(6, 7)$ is a simple Pythagorean calculation ($d = \sqrt{\Delta x^2 + \Delta y^2}$).

Flat Euclidean Plane (Wrong for Earth)        Spherical Great-Circle Arc (Haversine)
         (x2, y2)                                     Point B
            /|                                        /
           / |                                      /   (Curved Surface Arc)
        d /  | dy                                 /
         /   |                                  /
        /____|                                Point A 
    (x1, y1)  dx

However, the Earth is not a flat sheet of paper—it is an oblate spheroid.

  • Lines of longitude converge at the North and South Poles.
  • One degree of longitude at the Equator is approximately 111.32 km, but at the Arctic Circle (66° N), one degree of longitude shrinks to just 45.24 km.

If you fly from London to Tokyo, the shortest path on a curved globe is not a straight line across a Mercator map, but a curved Great-Circle arc soaring over Scandinavia and Siberia. To calculate spatial distances accurately between two GPS points on Earth, we must use spherical trigonometry: The Haversine Formula.


Technical Deep Dive & Specifications

The Haversine Formula Derivation

The Haversine formula calculates the great-circle distance between two points on a sphere given their longitudes and latitudes:

$$\Delta\varphi = (\text{lat}_2 - \text{lat}_1) \times \frac{\pi}{180}$$

$$\Delta\lambda = (\text{lon}_2 - \text{lon}_1) \times \frac{\pi}{180}$$

$$a = \sin^2\left(\frac{\Delta\varphi}{2}\right) + \cos\left(\text{lat}_1 \times \frac{\pi}{180}\right) \cdot \cos\left(\text{lat}_2 \times \frac{\pi}{180}\right) \cdot \sin^2\left(\frac{\Delta\lambda}{2}\right)$$

$$c = 2 \cdot \text{atan2}\left(\sqrt{a}, \sqrt{1-a}\right)$$

$$d = R \cdot c$$

Where:

  • $R$ is the mean volumetric radius of Earth:
    • $R = 6,371\text{ km}$ ($6,371,000\text{ meters}$)
    • $R = 3,958.8\text{ statute miles}$
    • $R = 3,440.0\text{ nautical miles}$
  • $\varphi$ is latitude in radians.
  • $\lambda$ is longitude in radians.
  • $d$ is the shortest distance over the Earth's surface.

Pure JavaScript Haversine Implementation

/**
 * Calculates great-circle distance between two points using the Haversine formula.
 * @param {number} lat1 Latitude of point 1 (decimal degrees)
 * @param {number} lon1 Longitude of point 1 (decimal degrees)
 * @param {number} lat2 Latitude of point 2 (decimal degrees)
 * @param {number} lon2 Longitude of point 2 (decimal degrees)
 * @param {'km' | 'm' | 'miles'} unit Desired output unit (default: 'm')
 * @returns {number} Distance in chosen units
 */
function calculateDistance(lat1, lon1, lat2, lon2, unit = 'm') {
  const EARTH_RADII = {
    m: 6371000,
    km: 6371,
    miles: 3958.8
  };

  const R = EARTH_RADII[unit] || EARTH_RADII.m;
  const toRad = (deg) => (deg * Math.PI) / 180;

  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const rLat1 = toRad(lat1);
  const rLat2 = toRad(lat2);

  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(rLat1) * Math.cos(rLat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return R * c;
}

Calculating Initial Compass Bearing (Forward Azimuth)

To determine which direction (in degrees from True North) a user must face to travel toward a destination:

$$\theta = \text{atan2}\left(\sin(\Delta\lambda)\cdot\cos(\varphi_2), \cos(\varphi_1)\cdot\sin(\varphi_2) - \sin(\varphi_1)\cdot\cos(\varphi_2)\cdot\cos(\Delta\lambda)\right)$$

function calculateBearing(lat1, lon1, lat2, lon2) {
  const toRad = (deg) => (deg * Math.PI) / 180;
  const toDeg = (rad) => (rad * 180) / Math.PI;

  const y = Math.sin(toRad(lon2 - lon1)) * Math.cos(toRad(lat2));
  const x =
    Math.cos(toRad(lat1)) * Math.sin(toRad(lat2)) -
    Math.sin(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.cos(toRad(lon2 - lon1));

  const bearingRad = Math.atan2(y, x);
  const bearingDeg = (toDeg(bearingRad) + 360) % 360; // Normalize 0 - 360°

  return bearingDeg;
}

The Circular Geofencing State Machine

A Geofence is a virtual geographic boundary defined by a center coordinate $(lat_C, lon_C)$ and a radius $r$ in meters:

[ User Moves ] ──► Compute distance(userPos, fenceCenter)
                         │
         ┌───────────────┴───────────────┐
         ▼                               ▼
  distance <= radius              distance > radius
         │                               │
         ▼                               ▼
 [ INSIDE ZONE ]                  [ OUTSIDE ZONE ]
 (Trigger 'ENTER' if               (Trigger 'EXIT' if
  previous was OUTSIDE)             previous was INSIDE)

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Lines 114–128 (haversineDistance): Implements standard Haversine equations, explicitly converting inputs from decimal degrees into radians using deg * Math.PI / 180.
  • Lines 130–140 (calculateBearing): Computes trigonometric arctangent of spherical delta vectors to return a normalized azimuth between $0^\circ$ and $360^\circ$.
  • Lines 155–163: Evaluates whether distMeters <= radius to dynamically trigger inside/outside geofence status.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
📐 Haversine Distance & Geofencing Math
Point A: 37.7749, -122.4194  |  Point B: 37.7792, -122.4140

[ ⚡ Compute Spatial Metrics ]

DISTANCE (METERS)        DISTANCE (MILES)         COMPASS BEARING
671.4 m                  0.42 mi                  43.5°

[ 🚫 OUTSIDE GEOFENCE (671.4m > 500m) ]

🏋️ Hands-On Exercise

🎯 The Challenge: Build a Nearest Store Finder & Proximity Geofence

Instructions:

  1. Given an array of store locations (with name, lat, lon), write a function that queries the user's location via getCurrentPosition().
  2. Compute the distance from the user to each store using the Haversine formula.
  3. Sort the stores from closest to farthest.
  4. If the closest store is within 300 meters, flag it with an "Active In-Store Geofence VIP Discount" badge.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Passing Degrees Directly to Math.sin() and Math.cos(): JavaScript's Math functions expect angles in radians, not degrees. Forgetting to multiply by Math.PI / 180 produces completely broken distance values.
  2. Using Euclidean Flat Pythagorean Math: Over distances exceeding a few kilometers, flat Euclidean equations underestimate great-circle curve lengths significantly.
  3. Geofencing Boundary Flutter (Hysteresis): If a user is standing exactly on the 500m geofence border, GPS jitter will cause them to repeatedly trigger "Enter" and "Exit" events every few seconds. Implement a 20-meter deadband buffer (e.g. enter at 490m, exit at 510m).

💡 Pro Tips

  1. Server-Side Geofencing Verification: Always perform critical geofence checks (e.g. clocking into an employee work shift) on the server using verified backend databases (such as PostgreSQL with PostGIS ST_DWithin) to prevent client-side coordinate spoofing.
  2. Spatial Indexing with R-Trees / Geohash: If you are calculating distances against 50,000 retail points, do not iterate through all 50,000 points on the client. Use Geohashing or spatial indexing on the server to query only the nearest bounding box.

📌 Key Takeaways

  • The Earth's curved geometry requires spherical trigonometry; flat Pythagorean formulas fail for GPS.
  • The Haversine Formula calculates great-circle distances across the Earth's surface using mean radius $R = 6,371\text{ km}$.
  • Always convert decimal degree coordinates to radians ($\text{rad} = \text{deg} \times \pi / 180$) before calling trigonometric functions.
  • Compass bearing (azimuth) computes forward travel direction normalized between $0^\circ$ and $360^\circ$.
  • Circular geofences evaluate whether the Haversine distance between device and target is $\le \text{radius}$.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Why does the standard Pythagorean distance formula ($d = \sqrt{\Delta x^2 + \Delta y^2}$) fail when applied directly to latitude and longitude coordinates?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What is the volumetric mean radius of the Earth typically used in Haversine distance formulas for meter-based output?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

How can an engineer prevent rapid "flip-flopping" (enter/exit spam) when a user stands right on the boundary of a geofence?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP