Academic Block

Geolocation API Get Certified

The Geolocation API lets a web page ask the browser for the device’s current geographic position. Because this tutorial runs in a sandbox, the examples use a small mock that behaves like the real navigator.geolocation object.

Checking for Support

Always test whether the API exists before using it. Older browsers and pages that are not served over HTTPS may not provide it.

What this code does: Checks whether the Geolocation API is available on the current environment.

if ("geolocation" in navigator) {
    console.log("Geolocation is supported");
} else {
    console.log("Geolocation is not supported");
}

console.log("Always check support before using the API.");

Getting the Current Position

getCurrentPosition() asks for a single reading. It takes a success callback and an optional error callback.

What this code does: Requests a one-time location and logs the latitude and longitude.

function createMockGeolocation() {
    function makePosition(lat, lon) {
        return {
            coords: { latitude: lat, longitude: lon, accuracy: 25 },
            timestamp: Date.now()
        };
    }
    return {
        getCurrentPosition(onSuccess, onError) {
            // Call immediately so the editor captures the output
            onSuccess(makePosition(40.7128, -74.0060));
        }
    };
}

const geolocation = createMockGeolocation();

console.log("Requesting current position...");

geolocation.getCurrentPosition(
    (position) => {
        console.log("Latitude:", position.coords.latitude);
        console.log("Longitude:", position.coords.longitude);
        console.log("Accuracy:", position.coords.accuracy, "meters");
    },
    (error) => console.log("Error:", error.message)
);

Reading Accuracy and Other Fields

The coords object also contains accuracy (in meters) and, when available, altitude, speed, and heading.

What this code does: Displays a friendly message that includes both the coordinates and the reported accuracy.

function createMockGeolocation() {
    function makePosition(lat, lon) {
        return {
            coords: { latitude: lat, longitude: lon, accuracy: 25 },
            timestamp: Date.now()
        };
    }
    return {
        getCurrentPosition(onSuccess) {
            onSuccess(makePosition(51.5074, -0.1278)); // London
        }
    };
}

const geolocation = createMockGeolocation();

console.log("Getting position...");

geolocation.getCurrentPosition((position) => {
    const { latitude, longitude, accuracy } = position.coords;
    console.log("You are near", latitude.toFixed(4), longitude.toFixed(4));
    console.log("Accuracy: ±" + accuracy + " meters");
});

Passing Options

A third, optional argument configures the request: enableHighAccuracy asks for a more precise (but more battery-intensive) reading, timeout sets how long to wait, and maximumAge allows a recently cached position to be reused.

What this code does: Passes options such as enableHighAccuracy, timeout, and maximumAge to control how the position is obtained.

function createMockGeolocation() {
    function makePosition(lat, lon) {
        return {
            coords: { latitude: lat, longitude: lon, accuracy: 5 },
            timestamp: Date.now()
        };
    }
    return {
        getCurrentPosition(onSuccess, onError, geoOptions) {
            console.log("Requested with options:", JSON.stringify(geoOptions));
            onSuccess(makePosition(35.6762, 139.6503)); // Tokyo
        }
    };
}

const geolocation = createMockGeolocation();

console.log("Requesting high-accuracy position...");

geolocation.getCurrentPosition(
    (position) => {
        console.log("Latitude:", position.coords.latitude);
        console.log("Longitude:", position.coords.longitude);
        console.log("Accuracy:", position.coords.accuracy, "meters");
    },
    (error) => console.log("Error:", error.message),
    {
        enableHighAccuracy: true,
        timeout: 5000,
        maximumAge: 0
    }
);

Tracking Movement with watchPosition()

watchPosition() works like getCurrentPosition(), but keeps calling the success callback every time the device’s location changes.

What this code does: Continuously tracks the user’s movement with watchPosition, receiving updates as the location changes.

function createMockGeolocation() {
    let watchIdCounter = 0;
    const activeWatches = {};

    function makePosition(lat, lon) {
        return {
            coords: { latitude: lat, longitude: lon, accuracy: 15 },
            timestamp: Date.now()
        };
    }

    return {
        watchPosition(onSuccess) {
            const id = ++watchIdCounter;
            activeWatches[id] = true;

            let lat = 40.7128;
            let lon = -74.0060;

            // Fire a few updates immediately so the editor can capture them
            for (let i = 0; i < 3; i++) {
                if (!activeWatches[id]) break;
                lat += 0.001;
                lon += 0.001;
                onSuccess(makePosition(lat, lon));
            }
            return id;
        },
        clearWatch(id) {
            delete activeWatches[id];
        }
    };
}

const geolocation = createMockGeolocation();

console.log("Starting to watch position...");

geolocation.watchPosition((position) => {
    console.log(
        "Updated position:",
        position.coords.latitude.toFixed(4),
        position.coords.longitude.toFixed(4)
    );
});

Stopping a Watch with clearWatch()

watchPosition() returns a watch ID. clearWatch(id) stops receiving updates, similar to how clearInterval() stops a repeating timer.

What this code does: Stops an active watch by calling clearWatch with the watch ID returned earlier.

function createMockGeolocation() {
    let watchIdCounter = 0;
    const activeWatches = {};

    function makePosition(lat, lon) {
        return {
            coords: { latitude: lat, longitude: lon, accuracy: 15 },
            timestamp: Date.now()
        };
    }

    return {
        watchPosition(onSuccess) {
            const id = ++watchIdCounter;
            activeWatches[id] = true;

            let lat = 40.7128;
            let updates = 0;

            // Fire updates immediately
            while (activeWatches[id] && updates < 5) {
                updates++;
                lat += 0.001;
                onSuccess(makePosition(lat, -74.0060));
            }
            return id;
        },
        clearWatch(id) {
            delete activeWatches[id];
            console.log("clearWatch called – watching stopped");
        }
    };
}

const geolocation = createMockGeolocation();
let updateCount = 0;

console.log("Starting watch...");

const watchId = geolocation.watchPosition((position) => {
    updateCount++;
    console.log("Update #" + updateCount, position.coords.latitude.toFixed(4));

    if (updateCount >= 2) {
        geolocation.clearWatch(watchId);
        console.log("Stopped watching after 2 updates");
    }
});

Calculating Distance Between Two Coordinates

A common real-world task is calculating the distance between two latitude/longitude points using the Haversine formula, which accounts for the Earth’s curvature.

What this code does: Calculates the distance in kilometres between two sets of coordinates using the Haversine formula.

function getDistanceInKm(lat1, lon1, lat2, lon2) {
    const R = 6371; // Earth's radius in km
    const dLat = (lat2 - lat1) * Math.PI / 180;
    const dLon = (lon2 - lon1) * Math.PI / 180;
    const a =
        Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
}

// New York City to Los Angeles
const distance = getDistanceInKm(40.7128, -74.0060, 34.0522, -118.2437);
console.log("Distance from New York to Los Angeles:");
console.log(distance.toFixed(1), "km");

getCurrentPosition() vs. watchPosition()

Feature getCurrentPosition() watchPosition()
Number of readings One Continuous, until stopped
Needs to be manually stopped? No Yes — with clearWatch()
Typical use case “Find my location” button Live map, navigation tracking
Battery impact Low (one-time) Higher (ongoing)

Common Mistakes with Geolocation

  • Forgetting to provide an error callback, leaving permission denials or failures completely unhandled.
  • Calling watchPosition() without ever calling clearWatch(), continuing to drain battery after the location is no longer needed.
  • Assuming the API is available without checking — older browsers, and any page not served over HTTPS, may not support it at all.
  • Requesting enableHighAccuracy: true everywhere, even when a rough location would be enough — this increases battery usage.
  • Not planning for the user simply denying the permission prompt, which is a normal, expected outcome.

Best Practices for Geolocation

  • Always provide an error callback and show the user a clear message if location access is denied or unavailable.
  • Check "geolocation" in navigator before calling any geolocation method.
  • Always call clearWatch() once you no longer need continuous location updates.
  • Only request enableHighAccuracy when precision genuinely matters, to save battery.
  • Explain to the user why your app needs their location before triggering the permission prompt.

Quick Summary

  • navigator.geolocation.getCurrentPosition() requests a one-time location reading.
  • watchPosition() keeps calling its callback every time the location changes, and returns a watch ID.
  • clearWatch(id) stops a running watch.
  • The position object’s coords property holds latitude, longitude, accuracy, and other optional fields.
  • An options object can set enableHighAccuracy, timeout, and maximumAge.
  • Always handle the error callback — permission can be denied or the location can be unavailable.

Try It Yourself

Modify the code below. Try changing the starting coordinates, making the mock fail by setting fail: true, or calculating the distance to a different city.

What this code does: A practice exercise that fetches a mock position, handles errors, and computes the distance to another city.

function createMockGeolocation(options) {
    options = options || {};
    function makePosition(lat, lon) {
        return {
            coords: { latitude: lat, longitude: lon, accuracy: 10 },
            timestamp: Date.now()
        };
    }
    return {
        getCurrentPosition(onSuccess, onError) {
            if (options.fail) {
                onError({ code: 1, message: "User denied Geolocation" });
            } else {
                onSuccess(makePosition(48.8566, 2.3522)); // Paris
            }
        }
    };
}

function getDistanceInKm(lat1, lon1, lat2, lon2) {
    const R = 6371;
    const dLat = (lat2 - lat1) * Math.PI / 180;
    const dLon = (lon2 - lon1) * Math.PI / 180;
    const a =
        Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
}

const geolocation = createMockGeolocation(); // change to { fail: true } to test error

console.log("Getting current position...");

geolocation.getCurrentPosition(
    (position) => {
        const { latitude, longitude } = position.coords;
        console.log("Current position:", latitude, longitude);

        const distance = getDistanceInKm(latitude, longitude, 51.5074, -0.1278);
        console.log("Distance to London:", distance.toFixed(1), "km");
    },
    (error) => console.log("Error:", error.message)
);
Tip: Always pair a success callback with an error callback, remember to clearWatch() when you no longer need live updates, and only ask for high accuracy when your feature genuinely needs it.

Understanding the Geolocation API gives you the building block for location-aware features, and pairs naturally with the Fetch API for sending coordinates to a server or a mapping service.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close