Geolocation API允许Web应用获取用户的地理位置信息。本章将介绍Geolocation API的基本概念和使用方法。
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('纬度:', position.coords.latitude);
console.log('经度:', position.coords.longitude);
console.log('精度:', position.coords.accuracy);
console.log('海拔:', position.coords.altitude);
console.log('海拔精度:', position.coords.altitudeAccuracy);
console.log('方向:', position.coords.heading);
console.log('速度:', position.coords.speed);
console.log('时间戳:', new Date(position.timestamp));
},
(error) => {
console.error('获取位置失败:', error.message);
console.error('错误码:', error.code);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
const errorCodes = {
1: 'PERMISSION_DENIED - 用户拒绝了位置请求',
2: 'POSITION_UNAVAILABLE - 位置信息不可用',
3: 'TIMEOUT - 请求超时'
};
function handlePositionError(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
console.error('用户拒绝了位置请求');
break;
case error.POSITION_UNAVAILABLE:
console.error('位置信息不可用');
break;
case error.TIMEOUT:
console.error('请求超时');
break;
default:
console.error('未知错误');
}
}
const positionOptions = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 60000
};
const optionsDescription = {
enableHighAccuracy: {
description: '是否使用高精度模式',
default: false,
note: '高精度模式耗电更多,响应更慢'
},
timeout: {
description: '请求超时时间(毫秒)',
default: Infinity,
note: '超时后会触发错误回调'
},
maximumAge: {
description: '缓存位置的最大时间(毫秒)',
default: 0,
note: '0表示不使用缓存,Infinity表示始终使用缓存'
}
};
const watchId = navigator.geolocation.watchPosition(
(position) => {
console.log('位置更新:', position.coords);
},
(error) => {
console.error('监听位置失败:', error);
},
{
enableHighAccuracy: true,
maximumAge: 30000
}
);
navigator.geolocation.clearWatch(watchId);
class LocationTracker {
constructor(options = {}) {
this.options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
...options
};
this.watchId = null;
this.listeners = [];
this.lastPosition = null;
}
start() {
if (this.watchId !== null) return;
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.lastPosition = position;
this.notifyListeners(position);
},
(error) => {
this.notifyError(error);
},
this.options
);
}
stop() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
onPosition(callback) {
this.listeners.push({ type: 'position', callback });
return () => this.off(callback);
}
onError(callback) {
this.listeners.push({ type: 'error', callback });
return () => this.off(callback);
}
off(callback) {
this.listeners = this.listeners.filter(l => l.callback !== callback);
}
notifyListeners(position) {
this.listeners
.filter(l => l.type === 'position')
.forEach(l => l.callback(position));
}
notifyError(error) {
this.listeners
.filter(l => l.type === 'error')
.forEach(l => l.callback(error));
}
getLastPosition() {
return this.lastPosition;
}
}
const tracker = new LocationTracker();
tracker.onPosition((pos) => console.log('位置:', pos));
tracker.onError((err) => console.error('错误:', err));
tracker.start();
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function toRad(deg) {
return deg * (Math.PI / 180);
}
const distance = calculateDistance(39.9042, 116.4074, 31.2304, 121.4737);
console.log(`北京到上海的距离: ${distance.toFixed(2)} km`);
function calculateBearing(lat1, lon1, lat2, lon2) {
const dLon = toRad(lon2 - lon1);
const lat1Rad = toRad(lat1);
const lat2Rad = toRad(lat2);
const y = Math.sin(dLon) * Math.cos(lat2Rad);
const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLon);
let bearing = Math.atan2(y, x);
bearing = toDeg(bearing);
bearing = (bearing + 360) % 360;
return bearing;
}
function toDeg(rad) {
return rad * (180 / Math.PI);
}
class Geofence {
constructor(center, radius, onEnter, onExit) {
this.center = center;
this.radius = radius;
this.onEnter = onEnter;
this.onExit = onExit;
this.isInside = false;
}
checkPosition(position) {
const distance = calculateDistance(
position.coords.latitude,
position.coords.longitude,
this.center.lat,
this.center.lng
);
const wasInside = this.isInside;
this.isInside = distance <= this.radius;
if (!wasInside && this.isInside) {
this.onEnter?.(position, distance);
} else if (wasInside && !this.isInside) {
this.onExit?.(position, distance);
}
return this.isInside;
}
}
class GeofenceManager {
constructor() {
this.geofences = new Map();
this.tracker = new LocationTracker();
}
addGeofence(id, center, radius, callbacks) {
const geofence = new Geofence(
center,
radius,
callbacks.onEnter,
callbacks.onExit
);
this.geofences.set(id, geofence);
}
removeGeofence(id) {
this.geofences.delete(id);
}
start() {
this.tracker.onPosition((position) => {
this.geofences.forEach((geofence, id) => {
geofence.checkPosition(position);
});
});
this.tracker.start();
}
stop() {
this.tracker.stop();
}
}
const geofenceManager = new GeofenceManager();
geofenceManager.addGeofence('office',
{ lat: 39.9042, lng: 116.4074 },
0.1,
{
onEnter: () => console.log('进入办公室区域'),
onExit: () => console.log('离开办公室区域')
}
);
geofenceManager.start();
function initMap(position) {
const map = new google.maps.Map(document.getElementById('map'), {
center: {
lat: position.coords.latitude,
lng: position.coords.longitude
},
zoom: 15
});
new google.maps.Marker({
position: {
lat: position.coords.latitude,
lng: position.coords.longitude
},
map: map,
title: '当前位置'
});
}
async function showOnMap() {
try {
const position = await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
initMap(position);
} catch (error) {
console.error('无法获取位置:', error);
}
}
📍 位置隐私
- 用户必须明确授权才能获取位置
- 只在需要时请求位置权限
- 提供不依赖位置的备选方案
- 清晰说明位置数据的用途
⚡ 性能建议
- 使用maximumAge减少GPS请求
- 不需要高精度时关闭enableHighAccuracy
- 及时clearWatch停止监听
- 考虑使用IP定位作为备选方案
下一章将探讨 [Drag and Drop](file:///e:/db-w.cn/md_data/javascript/79_Drag and Drop.md),学习如何在Web应用中实现拖放功能。