Browser APIs
JavaScript Geolocation API: Complete Location Services Guide
Master the Geolocation API in JavaScript for location-based features. Learn positioning, tracking, and building location-aware applications.
By JavaScriptDoc Team•
geolocationgpslocationjavascriptmaps
JavaScript Geolocation API: Complete Location Services Guide
The Geolocation API provides access to geographical location information, enabling you to build location-aware web applications with features like maps, local search, and location tracking.
Understanding Geolocation
The Geolocation API allows web applications to access the user's geographical position with their permission.
// Check if Geolocation is supported
if ('geolocation' in navigator) {
console.log('Geolocation is supported');
} else {
console.log('Geolocation is not supported');
}
// Basic position request
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('Latitude:', position.coords.latitude);
console.log('Longitude:', position.coords.longitude);
console.log('Accuracy:', position.coords.accuracy, 'meters');
},
(error) => {
console.error('Error getting location:', error.message);
}
);
// Position object structure
/*
position = {
coords: {
latitude: 37.7749,
longitude: -122.4194,
altitude: null,
accuracy: 20,
altitudeAccuracy: null,
heading: null,
speed: null
},
timestamp: 1634567890123
}
*/
Getting Current Position
Advanced Position Options
class LocationService {
constructor() {
this.lastPosition = null;
this.watchId = null;
}
// Get current position with options
getCurrentPosition(options = {}) {
const defaultOptions = {
enableHighAccuracy: false,
timeout: 5000,
maximumAge: 0,
};
const finalOptions = { ...defaultOptions, ...options };
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
this.lastPosition = position;
resolve(this.formatPosition(position));
},
(error) => {
reject(this.handleError(error));
},
finalOptions
);
});
}
// Format position data
formatPosition(position) {
return {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
altitudeAccuracy: position.coords.altitudeAccuracy,
heading: position.coords.heading,
speed: position.coords.speed,
timestamp: new Date(position.timestamp),
// Additional calculated fields
coordinates: [position.coords.longitude, position.coords.latitude],
accuracyLevel: this.getAccuracyLevel(position.coords.accuracy),
isMoving: position.coords.speed > 0,
};
}
// Determine accuracy level
getAccuracyLevel(accuracy) {
if (accuracy <= 5) return 'excellent';
if (accuracy <= 10) return 'good';
if (accuracy <= 25) return 'moderate';
if (accuracy <= 50) return 'fair';
return 'poor';
}
// Handle geolocation errors
handleError(error) {
const errorMessages = {
[error.PERMISSION_DENIED]: 'User denied location permission',
[error.POSITION_UNAVAILABLE]: 'Location information unavailable',
[error.TIMEOUT]: 'Location request timed out',
};
return {
code: error.code,
message: errorMessages[error.code] || error.message,
timestamp: new Date(),
};
}
// Request high accuracy position
async getHighAccuracyPosition(timeout = 10000) {
try {
// First try with high accuracy
return await this.getCurrentPosition({
enableHighAccuracy: true,
timeout: timeout,
maximumAge: 0,
});
} catch (error) {
console.warn('High accuracy failed, falling back:', error);
// Fallback to lower accuracy
return await this.getCurrentPosition({
enableHighAccuracy: false,
timeout: timeout / 2,
maximumAge: 30000,
});
}
}
// Get position with retry logic
async getPositionWithRetry(maxRetries = 3, delay = 1000) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await this.getCurrentPosition();
} catch (error) {
lastError = error;
if (error.code === 1) {
// Permission denied, no point retrying
throw error;
}
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
}
// Usage
const locationService = new LocationService();
// Get current position
locationService
.getCurrentPosition()
.then((position) => {
console.log('Current position:', position);
})
.catch((error) => {
console.error('Location error:', error);
});
// Get high accuracy position
locationService.getHighAccuracyPosition().then((position) => {
console.log('Accurate position:', position);
});
Watching Position Changes
Continuous Position Tracking
class PositionTracker {
constructor() {
this.watchId = null;
this.positions = [];
this.callbacks = new Map();
this.isTracking = false;
}
// Start watching position
startTracking(options = {}) {
if (this.isTracking) {
console.warn('Already tracking position');
return;
}
const defaultOptions = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
};
const finalOptions = { ...defaultOptions, ...options };
this.watchId = navigator.geolocation.watchPosition(
(position) => this.handlePositionUpdate(position),
(error) => this.handleError(error),
finalOptions
);
this.isTracking = true;
}
// Stop watching position
stopTracking() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
this.isTracking = false;
}
}
// Handle position updates
handlePositionUpdate(position) {
const formattedPosition = this.formatPosition(position);
// Add to history
this.positions.push(formattedPosition);
// Calculate additional metrics
if (this.positions.length > 1) {
const prevPosition = this.positions[this.positions.length - 2];
formattedPosition.distance = this.calculateDistance(
prevPosition,
formattedPosition
);
formattedPosition.bearing = this.calculateBearing(
prevPosition,
formattedPosition
);
}
// Notify callbacks
this.notifyCallbacks('update', formattedPosition);
}
// Calculate distance between two points
calculateDistance(pos1, pos2) {
const R = 6371e3; // Earth's radius in meters
const φ1 = (pos1.latitude * Math.PI) / 180;
const φ2 = (pos2.latitude * Math.PI) / 180;
const Δφ = ((pos2.latitude - pos1.latitude) * Math.PI) / 180;
const Δλ = ((pos2.longitude - pos1.longitude) * Math.PI) / 180;
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // Distance in meters
}
// Calculate bearing between two points
calculateBearing(pos1, pos2) {
const φ1 = (pos1.latitude * Math.PI) / 180;
const φ2 = (pos2.latitude * Math.PI) / 180;
const Δλ = ((pos2.longitude - pos1.longitude) * Math.PI) / 180;
const y = Math.sin(Δλ) * Math.cos(φ2);
const x =
Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
const θ = Math.atan2(y, x);
return ((θ * 180) / Math.PI + 360) % 360; // Bearing in degrees
}
// Subscribe to position updates
subscribe(event, callback) {
if (!this.callbacks.has(event)) {
this.callbacks.set(event, []);
}
this.callbacks.get(event).push(callback);
// Return unsubscribe function
return () => {
const callbacks = this.callbacks.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
};
}
// Notify all callbacks
notifyCallbacks(event, data) {
const callbacks = this.callbacks.get(event) || [];
callbacks.forEach((callback) => callback(data));
}
// Get movement statistics
getStatistics() {
if (this.positions.length < 2) {
return null;
}
let totalDistance = 0;
let maxSpeed = 0;
let totalSpeed = 0;
let speedCount = 0;
for (let i = 1; i < this.positions.length; i++) {
const pos1 = this.positions[i - 1];
const pos2 = this.positions[i];
const distance = this.calculateDistance(pos1, pos2);
totalDistance += distance;
if (pos2.speed !== null) {
maxSpeed = Math.max(maxSpeed, pos2.speed);
totalSpeed += pos2.speed;
speedCount++;
}
}
const duration =
this.positions[this.positions.length - 1].timestamp -
this.positions[0].timestamp;
return {
totalDistance,
duration,
averageSpeed: speedCount > 0 ? totalSpeed / speedCount : 0,
maxSpeed,
pointCount: this.positions.length,
};
}
formatPosition(position) {
return {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
speed: position.coords.speed,
timestamp: new Date(position.timestamp),
};
}
handleError(error) {
this.notifyCallbacks('error', error);
}
}
// Usage
const tracker = new PositionTracker();
// Subscribe to updates
const unsubscribe = tracker.subscribe('update', (position) => {
console.log('Position update:', position);
if (position.distance) {
console.log(`Moved ${position.distance.toFixed(2)} meters`);
}
});
// Start tracking
tracker.startTracking({
enableHighAccuracy: true,
maximumAge: 0,
});
// Get statistics after some time
setTimeout(() => {
const stats = tracker.getStatistics();
console.log('Movement statistics:', stats);
}, 30000);
Geofencing
Location-Based Triggers
class Geofence {
constructor(id, latitude, longitude, radius, options = {}) {
this.id = id;
this.center = { latitude, longitude };
this.radius = radius; // in meters
this.options = {
enterCallback: null,
exitCallback: null,
dwellCallback: null,
dwellTime: 30000, // 30 seconds
...options,
};
this.isInside = false;
this.enterTime = null;
this.dwellTimer = null;
}
// Check if position is inside geofence
contains(position) {
const distance = this.calculateDistance(this.center, {
latitude: position.latitude,
longitude: position.longitude,
});
return distance <= this.radius;
}
// Update geofence state with new position
update(position) {
const wasInside = this.isInside;
this.isInside = this.contains(position);
if (!wasInside && this.isInside) {
// Entered geofence
this.handleEnter(position);
} else if (wasInside && !this.isInside) {
// Exited geofence
this.handleExit(position);
}
}
handleEnter(position) {
this.enterTime = Date.now();
if (this.options.enterCallback) {
this.options.enterCallback({
geofenceId: this.id,
position,
timestamp: new Date(),
});
}
// Start dwell timer
if (this.options.dwellCallback) {
this.dwellTimer = setTimeout(() => {
this.options.dwellCallback({
geofenceId: this.id,
position,
dwellTime: this.options.dwellTime,
timestamp: new Date(),
});
}, this.options.dwellTime);
}
}
handleExit(position) {
const duration = this.enterTime ? Date.now() - this.enterTime : 0;
if (this.options.exitCallback) {
this.options.exitCallback({
geofenceId: this.id,
position,
duration,
timestamp: new Date(),
});
}
// Clear dwell timer
if (this.dwellTimer) {
clearTimeout(this.dwellTimer);
this.dwellTimer = null;
}
this.enterTime = null;
}
calculateDistance(pos1, pos2) {
const R = 6371e3;
const φ1 = (pos1.latitude * Math.PI) / 180;
const φ2 = (pos2.latitude * Math.PI) / 180;
const Δφ = ((pos2.latitude - pos1.latitude) * Math.PI) / 180;
const Δλ = ((pos2.longitude - pos1.longitude) * Math.PI) / 180;
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
}
class GeofenceManager {
constructor() {
this.geofences = new Map();
this.tracker = new PositionTracker();
this.isMonitoring = false;
}
// Add a geofence
addGeofence(geofence) {
this.geofences.set(geofence.id, geofence);
// Check current position if available
if (this.tracker.positions.length > 0) {
const currentPosition =
this.tracker.positions[this.tracker.positions.length - 1];
geofence.update(currentPosition);
}
}
// Remove a geofence
removeGeofence(id) {
this.geofences.delete(id);
}
// Start monitoring
startMonitoring(options = {}) {
if (this.isMonitoring) return;
this.tracker.subscribe('update', (position) => {
this.checkGeofences(position);
});
this.tracker.startTracking({
enableHighAccuracy: true,
...options,
});
this.isMonitoring = true;
}
// Stop monitoring
stopMonitoring() {
this.tracker.stopTracking();
this.isMonitoring = false;
}
// Check all geofences
checkGeofences(position) {
for (const geofence of this.geofences.values()) {
geofence.update(position);
}
}
// Get nearby geofences
getNearbyGeofences(position, maxDistance) {
const nearby = [];
for (const geofence of this.geofences.values()) {
const distance = geofence.calculateDistance(geofence.center, {
latitude: position.latitude,
longitude: position.longitude,
});
if (distance <= maxDistance) {
nearby.push({
geofence,
distance,
isInside: geofence.contains(position),
});
}
}
return nearby.sort((a, b) => a.distance - b.distance);
}
}
// Usage
const geofenceManager = new GeofenceManager();
// Create a geofence for a store
const storeGeofence = new Geofence(
'store-123',
37.7749, // latitude
-122.4194, // longitude
100, // radius in meters
{
enterCallback: (event) => {
console.log('Entered store area:', event);
// Send notification
},
exitCallback: (event) => {
console.log('Left store area:', event);
console.log('Duration:', event.duration / 1000, 'seconds');
},
dwellCallback: (event) => {
console.log('Dwelling in store area:', event);
// Offer special promotion
},
}
);
geofenceManager.addGeofence(storeGeofence);
geofenceManager.startMonitoring();
Location Utilities
Geocoding and Reverse Geocoding
class LocationUtilities {
constructor(apiKey = null) {
this.apiKey = apiKey;
}
// Convert address to coordinates (using Nominatim)
async geocode(address) {
const url =
`https://nominatim.openstreetmap.org/search?` +
`q=${encodeURIComponent(address)}&format=json&limit=1`;
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'MyApp/1.0',
},
});
const data = await response.json();
if (data.length > 0) {
return {
latitude: parseFloat(data[0].lat),
longitude: parseFloat(data[0].lon),
displayName: data[0].display_name,
boundingBox: data[0].boundingbox.map(parseFloat),
};
}
throw new Error('No results found');
} catch (error) {
console.error('Geocoding error:', error);
throw error;
}
}
// Convert coordinates to address
async reverseGeocode(latitude, longitude) {
const url =
`https://nominatim.openstreetmap.org/reverse?` +
`lat=${latitude}&lon=${longitude}&format=json`;
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'MyApp/1.0',
},
});
const data = await response.json();
return {
displayName: data.display_name,
address: data.address,
placeId: data.place_id,
osmType: data.osm_type,
osmId: data.osm_id,
};
} catch (error) {
console.error('Reverse geocoding error:', error);
throw error;
}
}
// Format coordinates for display
formatCoordinates(latitude, longitude, format = 'decimal') {
switch (format) {
case 'decimal':
return `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`;
case 'dms':
return `${this.toDMS(latitude, 'lat')} ${this.toDMS(longitude, 'lon')}`;
case 'url':
return `https://www.google.com/maps?q=${latitude},${longitude}`;
default:
return { latitude, longitude };
}
}
// Convert decimal to degrees, minutes, seconds
toDMS(decimal, type) {
const absolute = Math.abs(decimal);
const degrees = Math.floor(absolute);
const minutesDecimal = (absolute - degrees) * 60;
const minutes = Math.floor(minutesDecimal);
const seconds = ((minutesDecimal - minutes) * 60).toFixed(1);
const direction =
type === 'lat' ? (decimal >= 0 ? 'N' : 'S') : decimal >= 0 ? 'E' : 'W';
return `${degrees}°${minutes}'${seconds}"${direction}`;
}
// Calculate bounds for a set of coordinates
calculateBounds(coordinates) {
if (coordinates.length === 0) return null;
let minLat = Infinity,
maxLat = -Infinity;
let minLng = Infinity,
maxLng = -Infinity;
for (const coord of coordinates) {
minLat = Math.min(minLat, coord.latitude);
maxLat = Math.max(maxLat, coord.latitude);
minLng = Math.min(minLng, coord.longitude);
maxLng = Math.max(maxLng, coord.longitude);
}
return {
southwest: { latitude: minLat, longitude: minLng },
northeast: { latitude: maxLat, longitude: maxLng },
center: {
latitude: (minLat + maxLat) / 2,
longitude: (minLng + maxLng) / 2,
},
};
}
// Find nearest location from a list
findNearest(position, locations) {
let nearest = null;
let minDistance = Infinity;
for (const location of locations) {
const distance = this.calculateDistance(position, location);
if (distance < minDistance) {
minDistance = distance;
nearest = location;
}
}
return {
location: nearest,
distance: minDistance,
};
}
calculateDistance(pos1, pos2) {
const R = 6371e3;
const φ1 = (pos1.latitude * Math.PI) / 180;
const φ2 = (pos2.latitude * Math.PI) / 180;
const Δφ = ((pos2.latitude - pos1.latitude) * Math.PI) / 180;
const Δλ = ((pos2.longitude - pos1.longitude) * Math.PI) / 180;
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
}
// Usage
const utils = new LocationUtilities();
// Geocode an address
utils.geocode('1600 Amphitheatre Parkway, Mountain View, CA').then((result) => {
console.log('Coordinates:', result);
});
// Reverse geocode
navigator.geolocation.getCurrentPosition(async (position) => {
const address = await utils.reverseGeocode(
position.coords.latitude,
position.coords.longitude
);
console.log('Current address:', address);
});
Location Privacy
Privacy-Aware Location Handling
class PrivacyAwareLocation {
constructor() {
this.permissionState = 'prompt';
this.privacyLevel = 'precise'; // precise, approximate, city
}
// Check permission state
async checkPermission() {
if ('permissions' in navigator) {
try {
const result = await navigator.permissions.query({
name: 'geolocation',
});
this.permissionState = result.state;
// Listen for permission changes
result.addEventListener('change', () => {
this.permissionState = result.state;
this.onPermissionChange(result.state);
});
return result.state;
} catch (error) {
console.error('Permission check failed:', error);
}
}
return 'unknown';
}
// Request location with privacy options
async requestLocation(privacyLevel = 'precise') {
this.privacyLevel = privacyLevel;
// Check permission first
const permission = await this.checkPermission();
if (permission === 'denied') {
throw new Error('Location permission denied');
}
// Get location based on privacy level
switch (privacyLevel) {
case 'precise':
return this.getPreciseLocation();
case 'approximate':
return this.getApproximateLocation();
case 'city':
return this.getCityLocation();
default:
throw new Error('Invalid privacy level');
}
}
// Get precise location
async getPreciseLocation() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
privacyLevel: 'precise',
});
},
reject,
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
}
);
});
}
// Get approximate location (reduced precision)
async getApproximateLocation() {
const precise = await this.getPreciseLocation();
// Round to ~1km precision
return {
latitude: Math.round(precise.latitude * 100) / 100,
longitude: Math.round(precise.longitude * 100) / 100,
accuracy: 1000, // 1km
privacyLevel: 'approximate',
};
}
// Get city-level location
async getCityLocation() {
const precise = await this.getPreciseLocation();
// Use reverse geocoding to get city
const utils = new LocationUtilities();
const address = await utils.reverseGeocode(
precise.latitude,
precise.longitude
);
// Return city center coordinates
if (address.address.city) {
const cityCoords = await utils.geocode(
`${address.address.city}, ${address.address.country}`
);
return {
latitude: cityCoords.latitude,
longitude: cityCoords.longitude,
accuracy: 10000, // 10km
city: address.address.city,
country: address.address.country,
privacyLevel: 'city',
};
}
// Fallback to very approximate location
return this.getApproximateLocation();
}
// Store location with privacy considerations
storeLocation(location, options = {}) {
const {
encrypt = false,
expiry = 3600000, // 1 hour
} = options;
const data = {
location,
timestamp: Date.now(),
expiry: Date.now() + expiry,
};
if (encrypt) {
// Simple obfuscation (use proper encryption in production)
const encrypted = btoa(JSON.stringify(data));
localStorage.setItem('user_location', encrypted);
} else {
localStorage.setItem('user_location', JSON.stringify(data));
}
}
// Retrieve stored location
getStoredLocation(decrypt = false) {
const stored = localStorage.getItem('user_location');
if (!stored) return null;
try {
const data = decrypt ? JSON.parse(atob(stored)) : JSON.parse(stored);
// Check expiry
if (data.expiry && Date.now() > data.expiry) {
localStorage.removeItem('user_location');
return null;
}
return data.location;
} catch (error) {
console.error('Failed to retrieve stored location:', error);
return null;
}
}
// Clear location data
clearLocationData() {
localStorage.removeItem('user_location');
}
onPermissionChange(state) {
console.log('Location permission changed:', state);
if (state === 'denied') {
this.clearLocationData();
}
}
}
// Usage
const privacyLocation = new PrivacyAwareLocation();
// Check permission
privacyLocation.checkPermission().then((state) => {
console.log('Permission state:', state);
});
// Request with privacy options
async function getUserLocation() {
try {
// Try precise first
const location = await privacyLocation.requestLocation('precise');
console.log('Precise location:', location);
} catch (error) {
// Fall back to approximate
try {
const location = await privacyLocation.requestLocation('approximate');
console.log('Approximate location:', location);
} catch (error) {
console.error('Location request failed:', error);
}
}
}
Map Integration
Working with Map Services
class MapIntegration {
constructor(mapContainer) {
this.mapContainer = mapContainer;
this.markers = new Map();
this.userLocation = null;
}
// Create a simple map visualization
createMap(center, zoom = 13) {
// Create map iframe (using OpenStreetMap)
const iframe = document.createElement('iframe');
iframe.width = '100%';
iframe.height = '400';
iframe.frameBorder = '0';
iframe.scrolling = 'no';
iframe.marginHeight = '0';
iframe.marginWidth = '0';
const bbox = this.calculateBoundingBox(center, zoom);
iframe.src =
`https://www.openstreetmap.org/export/embed.html?` +
`bbox=${bbox.join(',')}&layer=mapnik`;
this.mapContainer.appendChild(iframe);
return iframe;
}
// Calculate bounding box for zoom level
calculateBoundingBox(center, zoom) {
const scale =
(156543.03392 * Math.cos((center.latitude * Math.PI) / 180)) /
Math.pow(2, zoom);
const halfWidth = scale * 400;
const halfHeight = scale * 300;
const metersPerDegLat = 111320;
const metersPerDegLon =
metersPerDegLat * Math.cos((center.latitude * Math.PI) / 180);
return [
center.longitude - halfWidth / metersPerDegLon,
center.latitude - halfHeight / metersPerDegLat,
center.longitude + halfWidth / metersPerDegLon,
center.latitude + halfHeight / metersPerDegLat,
];
}
// Generate static map URL
generateStaticMapUrl(center, options = {}) {
const { zoom = 13, width = 600, height = 400, markers = [] } = options;
// Using MapQuest static API (requires API key)
const baseUrl = 'https://www.mapquestapi.com/staticmap/v5/map';
const params = new URLSearchParams({
key: 'YOUR_API_KEY',
center: `${center.latitude},${center.longitude}`,
zoom: zoom,
size: `${width},${height}`,
type: 'map',
});
// Add markers
markers.forEach((marker, index) => {
params.append(
'locations',
`${marker.latitude},${marker.longitude}|marker-${index + 1}`
);
});
return `${baseUrl}?${params.toString()}`;
}
// Show user location on map
async showUserLocation() {
try {
const locationService = new LocationService();
const position = await locationService.getCurrentPosition();
this.userLocation = position;
// Create or update map
if (!this.map) {
this.map = this.createMap({
latitude: position.latitude,
longitude: position.longitude,
});
}
// Add user marker (visual indicator)
this.addUserLocationIndicator(position);
return position;
} catch (error) {
console.error('Failed to show user location:', error);
throw error;
}
}
// Add visual indicator for user location
addUserLocationIndicator(position) {
const indicator = document.createElement('div');
indicator.className = 'user-location-indicator';
indicator.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
background: #4285f4;
border: 3px solid white;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
z-index: 1000;
`;
// Add pulsing animation
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(66, 133, 244, 0.7); }
70% { box-shadow: 0 0 0 20px rgba(66, 133, 244, 0); }
100% { box-shadow: 0 0 0 0 rgba(66, 133, 244, 0); }
}
.user-location-indicator {
animation: pulse 2s infinite;
}
`;
document.head.appendChild(style);
this.mapContainer.style.position = 'relative';
this.mapContainer.appendChild(indicator);
}
// Calculate route between points
async calculateRoute(start, end) {
// Using OpenRouteService (requires API key)
const url = 'https://api.openrouteservice.org/v2/directions/driving-car';
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
coordinates: [
[start.longitude, start.latitude],
[end.longitude, end.latitude],
],
}),
});
const data = await response.json();
if (data.routes && data.routes.length > 0) {
const route = data.routes[0];
return {
distance: route.summary.distance,
duration: route.summary.duration,
geometry: route.geometry,
};
}
throw new Error('No route found');
}
}
Best Practices
-
Always handle errors gracefully
navigator.geolocation.getCurrentPosition(success, (error) => { // Provide fallback or user guidance showLocationError(error); });
-
Respect user privacy
// Ask for permission context showLocationPermissionDialog({ title: 'Enable Location', message: 'We need your location to show nearby stores', });
-
Use appropriate accuracy
// Don't use high accuracy unless needed const options = { enableHighAccuracy: false, // Better battery life maximumAge: 300000, // 5 minutes cache };
-
Clean up watchers
// Always clear watch when done const watchId = navigator.geolocation.watchPosition(success); // Later... navigator.geolocation.clearWatch(watchId);
Conclusion
The Geolocation API enables powerful location features:
- Position tracking for real-time updates
- Geofencing for location-based triggers
- Privacy controls for user trust
- Map integration for visualization
- Location utilities for geocoding
- Cross-platform support
Key takeaways:
- Always handle permissions properly
- Provide privacy options to users
- Use appropriate accuracy levels
- Handle errors gracefully
- Test on real devices
- Consider battery impact
Master the Geolocation API to build engaging location-aware applications!