first commit

This commit is contained in:
2025-10-16 21:32:16 +08:00
commit c446df73b5
229 changed files with 499497 additions and 0 deletions

View File

@@ -0,0 +1,793 @@
// 地图服务 - 处理地图相关的功能,包括定位、路线规划、地理编码等
import apiService from './apiService';
import { isMockMode } from './apiService';
import { AMapRegeoResponse, RoutePlanResult, SearchResult, LocationData } from '../types';
/**
* 地图服务类
* 提供地图相关功能,包括定位、路线规划、地理编码等
*/
class MapService {
// 引入高德地图小程序SDK
private amapFile: any;
// 高德地图API密钥
private readonly MAP_KEY: string;
// 昆明中心点坐标
private readonly KUNMING_CENTER: { longitude: number; latitude: number };
// 高德地图实例
private amapInstance: any;
// 定时器引用,用于模拟实时位置更新
private locationUpdateTimer: number | null;
// 位置更新回调函数
private locationUpdateCallback: ((location: LocationData) => void) | null;
// 取消订阅函数
private unsubscribeFunction: (() => void) | null;
/**
* 构造函数,初始化地图实例
*/
constructor() {
this.amapFile = require('../libs/amap-wx.js');
this.MAP_KEY = '1fc5cafd570d9fbfd14c39359d41823d';
this.KUNMING_CENTER = {
longitude: 102.833722,
latitude: 24.880095
};
this.amapInstance = new this.amapFile.AMapWX({ key: this.MAP_KEY });
this.locationUpdateTimer = null;
this.locationUpdateCallback = null;
this.unsubscribeFunction = null;
}
/**
* 获取用户位置信息
* @returns 用户位置坐标
*/
async getLocation(): Promise<{ longitude: number; latitude: number }> {
if (isMockMode) {
// 模拟位置 - 昆明市中心
console.log('[MOCK] 获取用户位置信息');
return {
longitude: 102.7123,
latitude: 25.0409
};
}
return new Promise((resolve, reject) => {
console.log('开始调用高德地图SDK获取位置...');
// 添加超时机制
const timeout = setTimeout(() => {
console.error('高德地图SDK调用超时使用模拟位置');
resolve({
longitude: 102.7123,
latitude: 25.0409
});
}, 10000); // 10秒超时
try {
// 高德地图SDK的getWxLocation方法需要两个参数配置对象和回调函数
this.amapInstance.getWxLocation({
type: 'gcj02', // 使用国测局坐标系
success: (res: { longitude: number; latitude: number }) => {
clearTimeout(timeout);
console.log('高德地图SDK定位成功:', res);
resolve(res);
},
fail: (err: any) => {
clearTimeout(timeout);
console.error('高德地图SDK定位失败:', err);
// 如果高德SDK失败尝试使用微信原生API
console.log('尝试使用微信原生getLocation API...');
wx.getLocation({
type: 'gcj02',
success: (wxRes) => {
console.log('微信原生API定位成功:', wxRes);
resolve({
longitude: wxRes.longitude,
latitude: wxRes.latitude
});
},
fail: (wxErr) => {
console.error('微信原生API定位失败:', wxErr);
// 所有方法都失败,使用模拟位置
console.log('所有定位方法失败,使用模拟位置');
resolve({
longitude: 102.7123,
latitude: 25.0409
});
}
});
}
}, (locationString: string) => {
// 这是高德地图SDK的回调函数参数是格式化的位置字符串
console.log('高德地图SDK格式化位置:', locationString);
// 如果格式化位置回调被调用说明SDK已经获取到位置
// 尝试从格式化字符串中解析经纬度
if (locationString) {
const coords = locationString.split(',');
if (coords.length === 2) {
const longitude = parseFloat(coords[0]);
const latitude = parseFloat(coords[1]);
if (!isNaN(longitude) && !isNaN(latitude)) {
clearTimeout(timeout);
console.log('从格式化位置解析成功,使用解析的位置:', { longitude, latitude });
resolve({ longitude, latitude });
return;
}
}
}
// 如果无法解析继续等待success回调
console.log('格式化位置回调被调用但无法解析位置继续等待success回调...');
});
} catch (error) {
clearTimeout(timeout);
console.error('高德地图SDK调用异常:', error);
reject(error);
}
});
}
/**
* 获取当前位置信息(兼容格式)
* @returns 当前位置的详细信息
*/
async getCurrentLocation(): Promise<{
latitude: number;
longitude: number;
accuracy?: number;
speed?: number;
altitude?: number;
}> {
if (isMockMode) {
// 模拟位置 - 昆明市中心
console.log('[MOCK] 获取当前位置信息(兼容格式)');
return {
latitude: 25.0409,
longitude: 102.7123,
accuracy: 50,
speed: 0,
altitude: 1891
};
}
const location = await this.getLocation();
return {
latitude: location.latitude,
longitude: location.longitude,
accuracy: 50,
speed: 0,
altitude: 1891
};
}
/**
* 获取位置详细信息(逆地理编码)
* @param longitude 经度
* @param latitude 纬度
* @returns 逆地理编码结果
*/
async getLocationInfo(longitude: number, latitude: number): Promise<AMapRegeoResponse> {
if (isMockMode) {
// 模拟逆地理编码结果 - 完全符合AMapRegeoResponse接口
console.log('[MOCK] 获取位置详细信息(逆地理编码),坐标:', longitude, latitude);
return {
regeocode: {
addressComponent: {
province: '云南省',
city: '昆明市',
district: '五华区',
street: '',
township: ''
}
},
status: '1'
};
}
return new Promise((resolve, reject) => {
this.amapInstance.getRegeo({
location: `${longitude.toFixed(6)},${latitude.toFixed(6)}`,
success: (res: AMapRegeoResponse) => {
console.log('逆地理编码成功:', res);
resolve(res);
},
fail: (err: any) => {
console.log('逆地理编码失败:', err);
reject(err);
}
});
});
}
/**
* 获取昆明中心点坐标
* @returns 昆明中心点坐标
*/
getKunmingCenter(): { longitude: number; latitude: number } {
return this.KUNMING_CENTER;
}
/**
* 搜索地点
* @param keyword 搜索关键词
* @param city 城市名称
* @returns 搜索结果列表
*/
async searchPoi(keyword: string, city: string): Promise<SearchResult[]> {
if (isMockMode) {
// 模拟搜索结果
console.log('[MOCK] 搜索地点,关键词:', keyword, '城市:', city);
const mockResults: SearchResult[] = [
{
id: '1',
name: '昆明火车站',
address: '昆明市官渡区北京路',
longitude: 102.7222,
latitude: 25.0157,
phone: ''
},
{
id: '2',
name: '昆明长水国际机场',
address: '昆明市官渡区长水村',
longitude: 102.9292,
latitude: 25.1012,
phone: ''
}
];
return mockResults.filter(item =>
item.name.includes(keyword) || item.address.includes(keyword)
);
}
return new Promise((resolve, reject) => {
this.amapInstance.getPoiAround({
querykeywords: keyword,
city: city,
radius: 50000, // 搜索半径50公里
offset: 20, // 返回20条结果
success: (res: any) => {
console.log('搜索POI成功:', res);
if (res.pois && res.pois.length > 0) {
const results: SearchResult[] = res.pois.map((poi: any) => ({
id: poi.id,
name: poi.name,
address: poi.address,
longitude: parseFloat(poi.location.split(',')[0]),
latitude: parseFloat(poi.location.split(',')[1]),
phone: poi.tel || ''
}));
resolve(results);
} else {
resolve([]);
}
},
fail: (err: any) => {
console.error('搜索POI失败:', err);
reject(err);
}
});
});
}
/**
* 规划驾车路线
* @param origin 起点坐标字符串 (经度,纬度)
* @param destination 终点坐标字符串 (经度,纬度)
* @returns 路线规划结果
*/
async getDrivingRoute(origin: string, destination: string): Promise<RoutePlanResult> {
if (isMockMode) {
// 模拟路线规划结果
console.log('[MOCK] 规划驾车路线,起点:', origin, '终点:', destination);
const [originLng, originLat] = origin.split(',').map(Number);
const [destLng, destLat] = destination.split(',').map(Number);
const distance = this.calculateDistance(originLat, originLng, destLat, destLng);
const duration = Math.round(distance / 10); // 假设平均速度10米/秒
return {
polyline: '',
distance,
duration
};
}
return new Promise((resolve, reject) => {
this.amapInstance.getDrivingRoute({
origin,
destination,
strategy: '0', // 推荐路线策略
showtraffic: false, // 不显示交通状况
success: (res: any) => {
console.log('高德地图路线规划API调用成功');
if (res.paths && res.paths.length > 0) {
const path = res.paths[0];
const result: RoutePlanResult = {
polyline: path.polyline || '',
distance: path.distance || 0,
duration: path.duration || 0
};
console.log(`路线规划完成: 距离${result.distance}米, 预计${result.duration}`);
resolve(result);
} else {
resolve({
polyline: '',
distance: 0,
duration: 0
});
}
},
fail: (err: any) => {
console.error('高德地图路线规划API调用失败:', err);
reject(err);
}
});
});
}
/**
* 计算两点之间的距离(米)
* @param lat1 第一个点的纬度
* @param lng1 第一个点的经度
* @param lat2 第二个点的纬度
* @param lng2 第二个点的经度
* @returns 两点之间的距离(米)
*/
calculateDistance(
lat1: number,
lng1: number,
lat2: number,
lng2: number
): number {
const R = 6371000; // 地球半径(米)
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLng = (lng2 - lng1) * 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(dLng / 2) * Math.sin(dLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
/**
* 获取路线规划(通用版本)
* @param origin 起点坐标
* @param destination 终点坐标
* @param mode 交通方式
* @returns 路线规划结果
*/
async getRoute(
origin: { latitude: number; longitude: number },
destination: { latitude: number; longitude: number },
mode: 'driving' | 'walking' | 'bicycling' = 'driving'
): Promise<{
distance: number; // 距离(米)
duration: number; // 时间(秒)
polyline: { latitude: number; longitude: number }[];
}> {
if (isMockMode) {
// 模拟路线规划
console.log('[MOCK] 获取路线规划,起点:', origin, '终点:', destination, '模式:', mode);
const distance = this.calculateDistance(
origin.latitude, origin.longitude,
destination.latitude, destination.longitude
);
const duration = Math.round(distance / 10); // 假设平均速度10米/秒
// 简单的直线路径
const polyline = [
origin,
destination
];
return {
distance,
duration,
polyline
};
}
// 对于真实模式,目前只支持驾车路线
const originStr = `${origin.longitude},${origin.latitude}`;
const destStr = `${destination.longitude},${destination.latitude}`;
const result = await this.getDrivingRoute(originStr, destStr);
// 转换polyline格式
const polylinePoints: { latitude: number; longitude: number }[] = [];
if (result.polyline) {
const points = result.polyline.split(';');
for (const point of points) {
const [lng, lat] = point.split(',');
if (lng && lat) {
polylinePoints.push({
latitude: parseFloat(lat),
longitude: parseFloat(lng)
});
}
}
}
return {
distance: result.distance,
duration: result.duration,
polyline: polylinePoints
};
}
/**
* 地理编码 - 地址转坐标
* @param address 地址字符串
* @returns 坐标信息
*/
async geocode(address: string): Promise<{
latitude: number;
longitude: number;
formattedAddress: string;
}> {
if (isMockMode) {
// 模拟地理编码结果
console.log('[MOCK] 地理编码,地址:', address);
const mockLocations: { [key: string]: { latitude: number; longitude: number; formattedAddress: string } } = {
'昆明市五华区二环西路599号': {
latitude: 25.055281,
longitude: 102.705745,
formattedAddress: '云南省昆明市五华区二环西路599号'
},
'昆明市盘龙区北京路1188号': {
latitude: 25.042498,
longitude: 102.728421,
formattedAddress: '云南省昆明市盘龙区北京路1188号'
},
'昆明市西山区滇池路1234号': {
latitude: 25.028234,
longitude: 102.689190,
formattedAddress: '云南省昆明市西山区滇池路1234号'
}
};
return mockLocations[address] || {
latitude: 25.0409,
longitude: 102.7123,
formattedAddress: '云南省昆明市'
};
}
// 对于真实模式使用搜索API来模拟地理编码
const results = await this.searchPoi(address, '昆明');
if (results.length > 0) {
return {
latitude: results[0].latitude,
longitude: results[0].longitude,
formattedAddress: results[0].address
};
}
// 如果没有找到结果,返回默认位置
return {
latitude: 25.0409,
longitude: 102.7123,
formattedAddress: '云南省昆明市'
};
}
/**
* 逆地理编码 - 坐标转地址
* @param latitude 纬度
* @param longitude 经度
* @returns 地址信息
*/
async reverseGeocode(latitude: number, longitude: number): Promise<{
formattedAddress: string;
country: string;
province: string;
city: string;
district: string;
street: string;
}> {
if (isMockMode) {
console.log('[MOCK] 逆地理编码,坐标:', latitude, longitude);
return {
formattedAddress: '云南省昆明市五华区',
country: '中国',
province: '云南省',
city: '昆明市',
district: '五华区',
street: ''
};
}
const result = await this.getLocationInfo(longitude, latitude);
return {
// 修复不再使用不存在的formatted_address字段而是根据已有的addressComponent字段构建格式化地址
formattedAddress: `${(result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.province) || '云南省'}${(result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.city) || '昆明市'}${(result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.district) || '五华区'}` || '未知地址',
country: '中国',
province: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.province) || '云南省',
city: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.city) || '昆明市',
district: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.district) || '五华区',
street: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.township) || ''
};
}
/**
* 获取附近的地点
* @param latitude 纬度
* @param longitude 经度
* @param radius 搜索半径
* @param type 地点类型
* @returns 附近地点列表
*/
async getNearbyPlaces(
latitude: number,
longitude: number,
radius: number,
type?: string
): Promise<Array<{
id: string;
name: string;
latitude: number;
longitude: number;
address: string;
distance: number;
}>> {
if (isMockMode) {
// 模拟附近地点
console.log('[MOCK] 获取附近的地点,坐标:', latitude, longitude, '半径:', radius, '类型:', type);
const mockPlaces = [
{
id: '1',
name: '昆明火车站',
latitude: 25.0157,
longitude: 102.7222,
address: '昆明市官渡区北京路',
distance: this.calculateDistance(latitude, longitude, 25.0157, 102.7222)
},
{
id: '2',
name: '昆明长水国际机场',
latitude: 25.1012,
longitude: 102.9292,
address: '昆明市官渡区长水村',
distance: this.calculateDistance(latitude, longitude, 25.1012, 102.9292)
},
{
id: '3',
name: '翠湖公园',
latitude: 25.0486,
longitude: 102.7042,
address: '昆明市五华区翠湖公园',
distance: this.calculateDistance(latitude, longitude, 25.0486, 102.7042)
}
];
return mockPlaces.filter(place => place.distance <= radius);
}
// 对于真实模式使用搜索API
const keyword = type ? type : '';
const results = await this.searchPoi(keyword, '昆明');
return results.map(place => ({
id: place.id,
name: place.name,
latitude: place.latitude,
longitude: place.longitude,
address: place.address,
distance: this.calculateDistance(latitude, longitude, place.latitude, place.longitude)
})).filter(place => place.distance <= radius);
}
/**
* 获取位置历史记录
* @param userId 用户ID
* @param limit 记录数量限制
* @returns 位置历史记录列表
*/
async getLocationHistory(userId: number, limit: number = 50): Promise<Array<{
id: number;
latitude: number;
longitude: number;
timestamp: number;
accuracy?: number;
speed?: number;
altitude?: number;
}>> {
if (isMockMode) {
// 模拟位置历史记录
console.log('[MOCK] 获取位置历史记录用户ID:', userId, '限制:', limit);
const history: Array<{ id: number; latitude: number; longitude: number; timestamp: number; accuracy?: number; speed?: number; altitude?: number }> = [];
const baseLat = 25.0409;
const baseLng = 102.7123;
for (let i = 0; i < limit; i++) {
history.push({
id: i + 1,
latitude: baseLat + (Math.random() - 0.5) * 0.01,
longitude: baseLng + (Math.random() - 0.5) * 0.01,
timestamp: Date.now() - i * 3600000, // 每小时一个记录
accuracy: 20 + Math.random() * 30,
speed: Math.random() * 10,
altitude: 1891 + (Math.random() - 0.5) * 20
});
}
return history.sort((a, b) => b.timestamp - a.timestamp);
}
// 修复参数名不匹配问题并转换返回值类型以匹配mapService定义
const apiResults = await apiService.getLocationHistory(userId, limit);
return apiResults.map((result, index) => ({
...result,
id: index + 1, // 添加id字段
altitude: 1891 + (Math.random() - 0.5) * 20 // 添加默认海拔高度
})).sort((a, b) => b.timestamp - a.timestamp);
}
/**
* 批量更新货运人员位置
* @param locations 位置信息数组
*/
async batchUpdateDeliveryPersonLocations(
locations: Array<{ userId: number; latitude: number; longitude: number; timestamp: number }>
): Promise<void> {
if (isMockMode) {
console.log('[MOCK] 批量更新货运人员位置:', locations);
return;
}
return apiService.batchUpdateDeliveryPersonLocations(locations);
}
/**
* 订阅实时位置更新
* @param callback 位置更新回调函数
*/
async subscribeToRealTimeLocations(callback: (location: LocationData) => void): Promise<void> {
if (isMockMode) {
// 模拟模式下,定期生成随机位置更新
console.log('[MOCK] 订阅实时位置更新');
// 保存回调引用
this.locationUpdateCallback = callback;
// 启动模拟更新定时器
if (!this.locationUpdateTimer) {
this.locationUpdateTimer = setInterval(() => {
// 生成随机位置更新数据
const randomLocation: LocationData = {
userId: Math.floor(Math.random() * 100) + 100, // 模拟货运人员ID
longitude: 102.714585 + (Math.random() - 0.5) * 0.05, // 昆明附近随机经度
latitude: 25.046321 + (Math.random() - 0.5) * 0.05, // 昆明附近随机纬度
timestamp: Date.now()
};
// 调用回调函数
if (this.locationUpdateCallback) {
this.locationUpdateCallback(randomLocation);
}
}, 3000); // 每3秒更新一次
}
return;
}
// 真实环境中调用API服务的订阅方法
try {
// 获取当前用户信息
const userInfo = wx.getStorageSync('userInfo');
if (!userInfo || !userInfo.id) {
throw new Error('用户未登录,无法订阅位置更新');
}
// 初始化WebSocket连接
await apiService.initLocationWebSocket();
// 订阅位置更新
await apiService.subscribeToLocationUpdates(userInfo.id);
// 使用新的onLocationUpdate方法来注册回调
this.unsubscribeFunction = apiService.onLocationUpdate((location) => {
callback(location as LocationData);
});
} catch (error) {
console.error('订阅实时位置更新失败:', error);
throw error;
}
}
/**
* 取消订阅实时位置更新
*/
async unsubscribeFromRealTimeLocations(): Promise<void> {
if (isMockMode) {
// 模拟模式下,清除定时器
console.log('[MOCK] 取消订阅实时位置更新');
if (this.locationUpdateTimer) {
clearInterval(this.locationUpdateTimer);
this.locationUpdateTimer = null;
}
// 清除回调引用
this.locationUpdateCallback = null;
return;
}
// 真实环境中调用API服务的取消订阅方法
try {
// 获取当前用户信息
const userInfo = wx.getStorageSync('userInfo');
if (userInfo && userInfo.id) {
// 取消订阅
await apiService.unsubscribeFromLocationUpdates(userInfo.id);
}
// 调用取消订阅函数
if (this.unsubscribeFunction) {
this.unsubscribeFunction();
this.unsubscribeFunction = null;
}
// 关闭WebSocket连接
apiService.closeLocationWebSocket();
} catch (error) {
console.error('取消订阅实时位置更新失败:', error);
throw error;
}
}
/**
* 格式化路线距离
* @param distance 距离(米)
* @returns 格式化后的距离字符串
*/
formatRouteDistance(distance: number): string {
if (distance < 1000) {
return `${distance}`;
} else {
return `${(distance / 1000).toFixed(1)}公里`;
}
}
/**
* 格式化路线时间
* @param duration 时间(秒)
* @returns 格式化后的时间字符串
*/
formatRouteDuration(duration: number): string {
if (duration < 60) {
return `${Math.round(duration)}`;
} else if (duration < 3600) {
return `${Math.round(duration / 60)} 分钟`;
} else {
const hours = Math.floor(duration / 3600);
const minutes = Math.round((duration % 3600) / 60);
return `${hours}小时${minutes}分钟`;
}
}
}
/**
* 地图服务单例实例
* 导出供应用程序全局使用
*/
export default new MapService();