Geolocation API in JavaScript
Introduction
The Geolocation API allows web applications to access the user’s geographical location. This enables location-based services like maps, local search, and location tracking. Understanding how to use the Geolocation API responsibly is important for building modern web applications.
Geolocation Basics
Checking Support
// Check if Geolocation API is supported
if ('geolocation' in navigator) {
console.log('Geolocation is supported');
} else {
console.log('Geolocation is not supported');
}
// Practical example: Feature detection
function isGeolocationSupported() {
return 'geolocation' in navigator;
}
if (isGeolocationSupported()) {
// Use geolocation
} else {
// Fallback or show message
console.log('Please enable location services');
}
Getting User Location
getCurrentPosition()
// Get current position
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
console.log(`Latitude: ${latitude}`);
console.log(`Longitude: ${longitude}`);
console.log(`Accuracy: ${accuracy}m`);
},
(error) => {
console.error('Error getting location:', error.message);
}
);
// With options
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('Location:', position.coords);
},
(error) => {
console.error('Error:', error);
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
Position Object
// Position object structure
navigator.geolocation.getCurrentPosition((position) => {
const coords = position.coords;
console.log(coords.latitude); // Latitude in degrees
console.log(coords.longitude); // Longitude in degrees
console.log(coords.accuracy); // Accuracy in meters
console.log(coords.altitude); // Altitude in meters (may be null)
console.log(coords.altitudeAccuracy); // Altitude accuracy (may be null)
console.log(coords.heading); // Direction of travel (0-360 degrees)
console.log(coords.speed); // Speed in m/s (may be null)
console.log(position.timestamp); // Timestamp when position was acquired
});
Watching Position
watchPosition()
// Watch position changes
const watchId = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude } = position.coords;
console.log(`Current position: ${latitude}, ${longitude}`);
},
(error) => {
console.error('Error:', error.message);
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
// Stop watching
navigator.geolocation.clearWatch(watchId);
// Practical example: Track movement
let previousPosition = null;
const watchId = navigator.geolocation.watchPosition((position) => {
const { latitude, longitude } = position.coords;
if (previousPosition) {
const distance = calculateDistance(
previousPosition.latitude,
previousPosition.longitude,
latitude,
longitude
);
console.log(`Moved ${distance}m`);
}
previousPosition = { latitude, longitude };
});
Error Handling
Error Types
// Handle different error types
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('Location:', position.coords);
},
(error) => {
switch (error.code) {
case error.PERMISSION_DENIED:
console.error('User denied geolocation permission');
break;
case error.POSITION_UNAVAILABLE:
console.error('Position information is unavailable');
break;
case error.TIMEOUT:
console.error('The request to get user location timed out');
break;
default:
console.error('An unknown error occurred');
}
}
);
Comprehensive Error Handling
// Robust error handling
function getLocation() {
return new Promise((resolve, reject) => {
if (!('geolocation' in navigator)) {
reject(new Error('Geolocation not supported'));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
resolve(position.coords);
},
(error) => {
let message = 'Unknown error';
if (error.code === error.PERMISSION_DENIED) {
message = 'Location permission denied';
} else if (error.code === error.POSITION_UNAVAILABLE) {
message = 'Location unavailable';
} else if (error.code === error.TIMEOUT) {
message = 'Location request timed out';
}
reject(new Error(message));
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 300000 // 5 minutes
}
);
});
}
// Usage
getLocation()
.then(coords => console.log('Location:', coords))
.catch(error => console.error(error.message));
Practical Examples
Example 1: Display Location on Map
// Display user location on map
function displayLocationOnMap() {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
// Create map (using Leaflet or similar)
const map = L.map('map').setView([latitude, longitude], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// Add marker for user location
L.marker([latitude, longitude])
.addTo(map)
.bindPopup('Your location')
.openPopup();
},
(error) => {
console.error('Error getting location:', error);
}
);
}
Example 2: Distance Calculator
// Calculate distance between two points
function calculateDistance(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; // Distance in km
}
// Find nearby locations
function findNearbyLocations(locations, maxDistance = 5) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
const nearby = locations.filter(location => {
const distance = calculateDistance(
latitude,
longitude,
location.latitude,
location.longitude
);
return distance <= maxDistance;
});
resolve(nearby);
},
reject
);
});
}
// Usage
const locations = [
{ name: 'Coffee Shop', latitude: 40.7128, longitude: -74.0060 },
{ name: 'Restaurant', latitude: 40.7580, longitude: -73.9855 }
];
findNearbyLocations(locations, 10)
.then(nearby => console.log('Nearby:', nearby))
.catch(error => console.error(error));
Example 3: Location Tracking
// Track user movement
class LocationTracker {
constructor(options = {}) {
this.watchId = null;
this.positions = [];
this.options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
...options
};
}
start() {
if (!('geolocation' in navigator)) {
console.error('Geolocation not supported');
return;
}
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.positions.push({
coords: position.coords,
timestamp: position.timestamp
});
this.onPositionUpdate(position);
},
(error) => {
this.onError(error);
},
this.options
);
}
stop() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
getPositions() {
return this.positions;
}
getTotalDistance() {
let total = 0;
for (let i = 1; i < this.positions.length; i++) {
const prev = this.positions[i - 1].coords;
const curr = this.positions[i].coords;
total += calculateDistance(
prev.latitude,
prev.longitude,
curr.latitude,
curr.longitude
);
}
return total;
}
onPositionUpdate(position) {
// Override in subclass
}
onError(error) {
console.error('Tracking error:', error);
}
}
// Usage
const tracker = new LocationTracker();
tracker.start();
// Stop after 5 minutes
setTimeout(() => {
tracker.stop();
console.log('Total distance:', tracker.getTotalDistance(), 'km');
}, 5 * 60 * 1000);
Example 4: Geofencing
// Simple geofencing
class Geofence {
constructor(latitude, longitude, radiusKm) {
this.latitude = latitude;
this.longitude = longitude;
this.radiusKm = radiusKm;
this.isInside = false;
}
check(userLat, userLon) {
const distance = calculateDistance(
this.latitude,
this.longitude,
userLat,
userLon
);
const wasInside = this.isInside;
this.isInside = distance <= this.radiusKm;
if (!wasInside && this.isInside) {
this.onEnter();
} else if (wasInside && !this.isInside) {
this.onExit();
}
}
onEnter() {
console.log('Entered geofence');
}
onExit() {
console.log('Exited geofence');
}
}
// Usage
const geofence = new Geofence(40.7128, -74.0060, 1); // 1km radius
navigator.geolocation.watchPosition((position) => {
const { latitude, longitude } = position.coords;
geofence.check(latitude, longitude);
});
Privacy and Permissions
Requesting Permission
// Permission is requested automatically on first use
// User sees a permission prompt
// Check permission status (if available)
if ('permissions' in navigator) {
navigator.permissions.query({ name: 'geolocation' })
.then(permission => {
console.log('Permission status:', permission.state);
// 'granted', 'denied', or 'prompt'
});
}
// Handle permission changes
if ('permissions' in navigator) {
navigator.permissions.query({ name: 'geolocation' })
.then(permission => {
permission.addEventListener('change', () => {
console.log('Permission changed:', permission.state);
});
});
}
Best Practices
// Best practices for geolocation
function useGeolocationResponsibly() {
// 1. Check support
if (!('geolocation' in navigator)) {
console.log('Geolocation not available');
return;
}
// 2. Explain why you need location
console.log('We need your location to find nearby stores');
// 3. Use appropriate options
const options = {
enableHighAccuracy: false, // Use false unless necessary
timeout: 10000,
maximumAge: 300000 // Cache for 5 minutes
};
// 4. Handle errors gracefully
navigator.geolocation.getCurrentPosition(
(position) => {
// Use location
},
(error) => {
// Provide fallback
console.log('Using default location');
},
options
);
// 5. Allow users to opt-out
// Provide a way to disable location tracking
}
Common Mistakes to Avoid
Mistake 1: Not Checking Support
// โ Wrong - Assumes geolocation exists
navigator.geolocation.getCurrentPosition(...);
// โ
Correct - Check first
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(...);
}
Mistake 2: Ignoring Errors
// โ Wrong - No error handling
navigator.geolocation.getCurrentPosition((position) => {
console.log(position.coords);
});
// โ
Correct - Handle errors
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position.coords);
},
(error) => {
console.error('Error:', error.message);
}
);
Mistake 3: Using High Accuracy Unnecessarily
// โ Wrong - High accuracy drains battery
const options = { enableHighAccuracy: true };
// โ
Correct - Use only when needed
const options = { enableHighAccuracy: false };
Mistake 4: Not Cleaning Up Watches
// โ Wrong - Watch continues indefinitely
navigator.geolocation.watchPosition(...);
// โ
Correct - Clean up when done
const watchId = navigator.geolocation.watchPosition(...);
// Later...
navigator.geolocation.clearWatch(watchId);
Summary
Geolocation API enables location-based features:
- Check support before using
- Use getCurrentPosition() for one-time location
- Use watchPosition() for continuous tracking
- Handle errors appropriately
- Respect user privacy
- Use appropriate accuracy settings
- Clean up watches when done
- Provide fallbacks for unsupported browsers
- Explain why you need location
- Allow users to opt-out
Related Resources
- MDN: Geolocation API
- MDN: getCurrentPosition()
- MDN: watchPosition()
- Privacy Considerations
- Leaflet Maps Library
Next Steps
Continue your learning journey:
- LocalStorage and SessionStorage
- Cookies: Creation, Reading, Deletion
- Fetch API: Making HTTP Requests
- Notification API
Comments