feat:新增地图模块,用于查找附近设备场地

This commit is contained in:
2025-08-18 14:52:39 +08:00
parent c5b8026fba
commit 38eb05fefd
122 changed files with 8317 additions and 1768 deletions
-163
View File
@@ -1,163 +0,0 @@
// 高德地图工具类
const AMAP_KEY = '4c513a688938fd89b88b296e867f66ec'
class AmapUtil {
constructor() {
this.key = AMAP_KEY
}
// 逆地理编码 - 根据经纬度获取地址信息
async regeocode(longitude, latitude) {
try {
const res = await uni.request({
url: 'https://restapi.amap.com/v3/geocode/regeo',
method: 'GET',
data: {
key: this.key,
location: `${longitude},${latitude}`,
poitype: '',
radius: 1000,
extensions: 'base',
batch: false,
roadlevel: 0
}
})
if (res.statusCode === 200 && res.data.status === '1') {
return {
success: true,
data: res.data.regeocode
}
} else {
return {
success: false,
message: res.data.info || '逆地理编码失败'
}
}
} catch (error) {
console.error('逆地理编码异常:', error)
return {
success: false,
message: '网络异常'
}
}
}
// 地理编码 - 根据地址获取经纬度
async geocode(address, city = '') {
try {
const res = await uni.request({
url: 'https://restapi.amap.com/v3/geocode/geo',
method: 'GET',
data: {
key: this.key,
address: address,
city: city
}
})
if (res.statusCode === 200 && res.data.status === '1' && res.data.geocodes.length > 0) {
return {
success: true,
data: res.data.geocodes[0]
}
} else {
return {
success: false,
message: res.data.info || '地理编码失败'
}
}
} catch (error) {
console.error('地理编码异常:', error)
return {
success: false,
message: '网络异常'
}
}
}
// 搜索POI
async searchPOI(keywords, location = '', radius = 3000, city = '') {
try {
const res = await uni.request({
url: 'https://restapi.amap.com/v3/place/text',
method: 'GET',
data: {
key: this.key,
keywords: keywords,
location: location,
radius: radius,
city: city,
citylimit: true
}
})
if (res.statusCode === 200 && res.data.status === '1') {
return {
success: true,
data: res.data.pois || []
}
} else {
return {
success: false,
message: res.data.info || '搜索失败'
}
}
} catch (error) {
console.error('POI搜索异常:', error)
return {
success: false,
message: '网络异常'
}
}
}
// 路径规划
async getRoute(origin, destination, strategy = 0) {
try {
const res = await uni.request({
url: 'https://restapi.amap.com/v3/direction/driving',
method: 'GET',
data: {
key: this.key,
origin: origin,
destination: destination,
strategy: strategy,
extensions: 'base'
}
})
if (res.statusCode === 200 && res.data.status === '1') {
return {
success: true,
data: res.data.route
}
} else {
return {
success: false,
message: res.data.info || '路径规划失败'
}
}
} catch (error) {
console.error('路径规划异常:', error)
return {
success: false,
message: '网络异常'
}
}
}
// 计算两点间距离
calculateDistance(lat1, lng1, lat2, lng2) {
const radLat1 = lat1 * Math.PI / 180.0
const radLat2 = lat2 * Math.PI / 180.0
const a = radLat1 - radLat2
const b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0
let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))
s = s * 6378.137
s = Math.round(s * 10000) / 10000
return s
}
}
export default new AmapUtil()
+590
View File
@@ -0,0 +1,590 @@
// 地图工具函数 - 内联腾讯地图SDK核心代码
// 腾讯地图Key
const QQMAP_KEY = 'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35';
// 内联腾讯地图SDK核心代码
const QQMapWX = (function() {
// 错误配置
const ERROR_CONF = {
KEY_ERR: 311,
KEY_ERR_MSG: 'key格式错误',
PARAM_ERR: 310,
PARAM_ERR_MSG: '请求参数信息有误',
SYSTEM_ERR: 600,
SYSTEM_ERR_MSG: '系统错误',
WX_ERR_CODE: 1000,
WX_OK_CODE: 200
};
// API基础URL
const BASE_URL = 'https://apis.map.qq.com/ws/';
const URL_SEARCH = BASE_URL + 'place/v1/search';
const URL_SUGGESTION = BASE_URL + 'place/v1/suggestion';
const URL_GET_GEOCODER = BASE_URL + 'geocoder/v1/';
const URL_DISTANCE = BASE_URL + 'distance/v1/';
// 工具函数
const Utils = {
// 获取location参数
getLocationParam(location) {
if (typeof location == 'string') {
const locationArr = location.split(',');
if (locationArr.length === 2) {
location = {
latitude: location.split(',')[0],
longitude: location.split(',')[1]
};
} else {
location = {};
}
}
return location;
},
// 验证location值
checkLocation(param) {
const location = this.getLocationParam(param.location);
if (!location || !location.latitude || !location.longitude) {
const errconf = this.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' location参数格式有误');
param.fail(errconf);
param.complete(errconf);
return false;
}
return true;
},
// 构造错误数据结构
buildErrorConfig(errCode, errMsg) {
return {
status: errCode,
message: errMsg
};
},
// 回调函数默认处理
polyfillParam(param) {
param.success = param.success || function () { };
param.fail = param.fail || function () { };
param.complete = param.complete || function () { };
},
// 处理用户参数是否传入坐标进行不同的处理
locationProcess(param, locationsuccess, locationfail, locationcomplete) {
const that = this;
locationfail = locationfail || function (res) {
res.statusCode = ERROR_CONF.WX_ERR_CODE;
param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
};
locationcomplete = locationcomplete || function (res) {
if (res.statusCode == ERROR_CONF.WX_ERR_CODE) {
param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
}
};
if (!param.location) {
wx.getLocation({
type: 'gcj02',
success: locationsuccess,
fail: locationfail,
complete: locationcomplete
});
} else if (that.checkLocation(param)) {
const location = Utils.getLocationParam(param.location);
locationsuccess(location);
}
},
// 构造微信请求参数
buildWxRequestConfig(param, options, feature) {
const that = this;
options.header = { "content-type": "application/json" };
options.method = 'GET';
options.success = function (res) {
const data = res.data;
if (data.status === 0) {
that.handleData(param, data, feature);
} else {
param.fail(data);
}
};
options.fail = function (res) {
res.statusCode = ERROR_CONF.WX_ERR_CODE;
param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
};
options.complete = function (res) {
const statusCode = +res.statusCode;
switch(statusCode) {
case ERROR_CONF.WX_ERR_CODE: {
param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
break;
}
case ERROR_CONF.WX_OK_CODE: {
const data = res.data;
if (data.status === 0) {
param.complete(data);
} else {
param.complete(that.buildErrorConfig(data.status, data.message));
}
break;
}
default:{
param.complete(that.buildErrorConfig(ERROR_CONF.SYSTEM_ERR, ERROR_CONF.SYSTEM_ERR_MSG));
}
}
};
return options;
},
// 数据处理函数
handleData(param, data, feature) {
if (feature == 'search') {
const searchResult = data.data;
const searchSimplify = [];
for (let i = 0; i < searchResult.length; i++) {
searchSimplify.push({
id: searchResult[i].id || null,
title: searchResult[i].title || null,
latitude: searchResult[i].location && searchResult[i].location.lat || null,
longitude: searchResult[i].location && searchResult[i].location.lng || null,
address: searchResult[i].address || null,
category: searchResult[i].category || null,
tel: searchResult[i].tel || null,
adcode: searchResult[i].ad_info && searchResult[i].ad_info.adcode || null,
city: searchResult[i].ad_info && searchResult[i].ad_info.city || null,
district: searchResult[i].ad_info && searchResult[i].ad_info.district || null,
province: searchResult[i].ad_info && searchResult[i].ad_info.province || null
});
}
param.success(data, {
searchResult: searchResult,
searchSimplify: searchSimplify
});
} else if (feature == 'suggest') {
const suggestResult = data.data;
const suggestSimplify = [];
for (let i = 0; i < suggestResult.length; i++) {
suggestSimplify.push({
adcode: suggestResult[i].adcode || null,
address: suggestResult[i].address || null,
category: suggestResult[i].category || null,
city: suggestResult[i].city || null,
district: suggestResult[i].district || null,
id: suggestResult[i].id || null,
latitude: suggestResult[i].location && suggestResult[i].location.lat || null,
longitude: suggestResult[i].location && suggestResult[i].location.lng || null,
province: suggestResult[i].province || null,
title: suggestResult[i].title || null,
type: suggestResult[i].type || null
});
}
param.success(data, {
suggestResult: suggestResult,
suggestSimplify: suggestSimplify
});
} else if (feature == 'reverseGeocoder') {
const reverseGeocoderResult = data.result;
const reverseGeocoderSimplify = {
address: reverseGeocoderResult.address || null,
latitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lat || null,
longitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lng || null,
adcode: reverseGeocoderResult.ad_info && reverseGeocoderResult.ad_info.adcode || null,
city: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.city || null,
district: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.district || null,
nation: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.nation || null,
province: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.province || null,
street: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street || null,
street_number: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street_number || null,
recommend: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.recommend || null,
rough: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.rough || null
};
param.success(data, {
reverseGeocoderResult: reverseGeocoderResult,
reverseGeocoderSimplify: reverseGeocoderSimplify
});
} else if (feature == 'calculateDistance') {
const calculateDistanceResult = data.result.elements;
const distance = [];
for (let i = 0; i < calculateDistanceResult.length; i++){
distance.push(calculateDistanceResult[i].distance);
}
param.success(data, {
calculateDistanceResult: calculateDistanceResult,
distance: distance
});
} else {
param.success(data);
}
}
};
// QQMapWX类
class QQMapWX {
constructor(options) {
if (!options.key) {
throw Error('key值不能为空');
}
this.key = options.key;
}
// 逆地址解析
reverseGeocoder(options) {
const that = this;
options = options || {};
Utils.polyfillParam(options);
const requestParam = {
coord_type: options.coord_type || 5,
get_poi: options.get_poi || 0,
output: 'json',
key: that.key
};
if (options.poi_options) {
requestParam.poi_options = options.poi_options;
}
const locationsuccess = function (result) {
requestParam.location = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_GET_GEOCODER,
data: requestParam
}, 'reverseGeocoder'));
};
Utils.locationProcess(options, locationsuccess);
}
// POI周边检索
search(options) {
const that = this;
options = options || {};
Utils.polyfillParam(options);
if (!options.keyword) {
const errconf = Utils.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' keyword参数格式有误');
options.fail(errconf);
options.complete(errconf);
return;
}
const requestParam = {
keyword: options.keyword,
orderby: options.orderby || '_distance',
page_size: options.page_size || 10,
page_index: options.page_index || 1,
output: 'json',
key: that.key
};
if (options.address_format) {
requestParam.address_format = options.address_format;
}
if (options.filter) {
requestParam.filter = options.filter;
}
const distance = options.distance || "1000";
const auto_extend = options.auto_extend || 1;
const locationsuccess = function (result) {
requestParam.boundary = "nearby(" + result.latitude + "," + result.longitude + "," + distance + "," + auto_extend + ")";
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_SEARCH,
data: requestParam
}, 'search'));
};
Utils.locationProcess(options, locationsuccess);
}
// sug模糊检索
getSuggestion(options) {
const that = this;
options = options || {};
Utils.polyfillParam(options);
if (!options.keyword) {
const errconf = Utils.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' keyword参数格式有误');
options.fail(errconf);
options.complete(errconf);
return;
}
const requestParam = {
keyword: options.keyword,
region: options.region || '全国',
region_fix: options.region_fix || 0,
policy: options.policy || 0,
page_size: options.page_size || 10,
page_index: options.page_index || 1,
get_subpois: options.get_subpois || 0,
output: 'json',
key: that.key
};
if (options.address_format) {
requestParam.address_format = options.address_format;
}
if (options.filter) {
requestParam.filter = options.filter;
}
if (options.location) {
const locationsuccess = function (result) {
requestParam.location = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION,
data: requestParam
}, "suggest"));
};
Utils.locationProcess(options, locationsuccess);
} else {
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION,
data: requestParam
}, "suggest"));
}
}
// 距离计算
calculateDistance(options) {
const that = this;
options = options || {};
Utils.polyfillParam(options);
if (!options.to) {
const errconf = Utils.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' to参数格式有误');
options.fail(errconf);
options.complete(errconf);
return;
}
const requestParam = {
mode: options.mode || 'walking',
to: options.to,
output: 'json',
key: that.key
};
if (options.from) {
options.location = options.from;
}
const locationsuccess = function (result) {
requestParam.from = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
url: URL_DISTANCE,
data: requestParam
}, 'calculateDistance'));
};
Utils.locationProcess(options, locationsuccess);
}
}
return QQMapWX;
})();
// 全局QQMap实例
let qqmapInstance = null;
// 初始化腾讯地图SDK
function initQQMap() {
if (!qqmapInstance) {
try {
qqmapInstance = new QQMapWX({
key: QQMAP_KEY
});
console.log('腾讯地图SDK初始化成功');
} catch (err) {
console.error('初始化腾讯地图SDK失败:', err);
}
}
return qqmapInstance;
}
// 获取腾讯地图SDK实例
function getQQMapInstance() {
return qqmapInstance || initQQMap();
}
// 获取用户位置(使用微信的接口获取位置)
function getUserLocation() {
return new Promise((resolve, reject) => {
wx.getLocation({
type: 'gcj02',
success: (res) => {
resolve({
longitude: res.longitude,
latitude: res.latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
});
}
// 逆地理编码 - 根据经纬度获取地址信息
function getRegeo(longitude, latitude) {
return new Promise((resolve, reject) => {
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
return;
}
qqmap.reverseGeocoder({
location: {
latitude,
longitude
},
success: (data, result) => {
// 官方SDK返回的数据结构:data是原始数据,result是简化数据
const reverseGeocoderSimplify = result.reverseGeocoderSimplify;
resolve({
success: true,
data: {
formatted_address: reverseGeocoderSimplify.address,
addressComponent: {
city: reverseGeocoderSimplify.city,
district: reverseGeocoderSimplify.district,
province: reverseGeocoderSimplify.province,
street: reverseGeocoderSimplify.street,
street_number: reverseGeocoderSimplify.street_number
}
}
});
},
fail: (error) => {
console.error('逆地理编码失败:', error);
reject({ success: false, message: error.message || '逆地理编码失败' });
}
});
});
}
// 搜索周边POI
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
return new Promise((resolve, reject) => {
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
return;
}
qqmap.search({
keyword: keyword,
location: {
latitude,
longitude
},
distance: radius,
success: (data, result) => {
const searchSimplify = result.searchSimplify;
resolve({
success: true,
data: searchSimplify
});
},
fail: (error) => {
console.error('搜索POI失败:', error);
reject({ success: false, message: error.message || '搜索POI失败' });
}
});
});
}
// 计算距离(异步)
function calculateDistance(from, to) {
return new Promise((resolve, reject) => {
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
return;
}
qqmap.calculateDistance({
from: from,
to: to,
mode: 'walking',
success: (data, result) => {
const distance = result.distance;
resolve({
success: true,
data: distance
});
},
fail: (error) => {
console.error('计算距离失败:', error);
reject({ success: false, message: error.message || '计算距离失败' });
}
});
});
}
// 计算距离(同步,使用球面距离公式)
function calculateDistanceSync(lat1, lng1, lat2, lng2) {
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 Math.round(R * c); // 返回距离,单位为米
}
// 关键词提示
function getSuggestion(keyword, region = '全国') {
return new Promise((resolve, reject) => {
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
return;
}
qqmap.getSuggestion({
keyword: keyword,
region: region,
success: (data, result) => {
const suggestSimplify = result.suggestSimplify;
resolve({
success: true,
data: suggestSimplify
});
},
fail: (error) => {
console.error('关键词提示失败:', error);
reject({ success: false, message: error.message || '关键词提示失败' });
}
});
});
}
// 导出函数
export {
getUserLocation,
getRegeo,
getPoiAround,
calculateDistance,
calculateDistanceSync,
getSuggestion,
initQQMap,
getQQMapInstance
};
// 测试距离计算函数(开发调试用)
export function testDistanceCalculation() {
// 测试用例:北京天安门到故宫的距离(约1.5公里)
const tiananmen = { lat: 39.908823, lng: 116.397470 };
const gugong = { lat: 39.916527, lng: 116.397128 };
const distance = calculateDistanceSync(tiananmen.lat, tiananmen.lng, gugong.lat, gugong.lng);
console.log('天安门到故宫的距离:', distance, '米');
console.log('转换为公里:', (distance / 1000).toFixed(2), '公里');
return distance;
}
File diff suppressed because it is too large Load Diff
+78 -90
View File
@@ -1,10 +1,10 @@
// 高德静态地图API工具类
const AMAP_KEY = '4c513a688938fd89b88b296e867f66ec'
// 腾讯静态地图API工具类
const QQMAP_KEY = 'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35'
class StaticMapUtil {
constructor() {
this.key = AMAP_KEY
this.baseUrl = 'https://restapi.amap.com/v3/staticmap'
this.key = QQMAP_KEY
this.baseUrl = 'https://apis.map.qq.com/ws/staticmap/v2/'
}
/**
@@ -14,54 +14,40 @@ class StaticMapUtil {
*/
generateMapUrl(options = {}) {
const defaultOptions = {
location: '116.397128,39.916527', // 默认中心点(北京)
zoom: 13, // 缩放级别
size: '750*500', // 图片尺寸
scale: 2, // 高清显示
markers: [], // 标记点
labels: [], // 文字标注
paths: [], // 路径
traffic: 0, // 交通路况 0-不显示 1-显示
format: 'png' // 图片格式
center: '39.916527,116.397128', // 默认中心点(北京)
zoom: 13, // 缩放级别
size: '750*500', // 图片尺寸
scale: 2, // 高清显示
markers: [], // 标记点
labels: [], // 文字标注
paths: [], // 路径
traffic: 0, // 交通路况 0-不显示 1-显示
format: 'png' // 图片格式
}
const config = { ...defaultOptions, ...options }
let url = `${this.baseUrl}?key=${this.key}`
url += `&location=${config.location}`
url += `&center=${config.center}`
url += `&zoom=${config.zoom}`
url += `&size=${config.size}`
url += `&scale=${config.scale}`
url += `&traffic=${config.traffic}`
url += `&format=${config.format}`
// 添加标记点
// 腾讯地图添加标记点
if (config.markers && config.markers.length > 0) {
const markersStr = config.markers.map(marker => {
let markerStr = ''
if (marker.size) markerStr += `size:${marker.size}|`
if (marker.color) markerStr += `color:${marker.color}|`
if (marker.label) markerStr += `label:${marker.label}|`
markerStr += `${marker.longitude},${marker.latitude}`
return markerStr
}).join('|')
url += `&markers=${encodeURIComponent(markersStr)}`
config.markers.forEach((marker, index) => {
url += `&markers=size:${marker.size || 'medium'}|color:${marker.color || '0x2196F3'}|label:${marker.label || ' '}|${marker.latitude},${marker.longitude}`
})
}
// 添加文字标注
if (config.labels && config.labels.length > 0) {
const labelsStr = config.labels.map(label => {
let labelStr = ''
if (label.content) labelStr += `content:${label.content}|`
if (label.font) labelStr += `font:${label.font}|`
if (label.bold) labelStr += `bold:${label.bold}|`
if (label.fontSize) labelStr += `fontSize:${label.fontSize}|`
if (label.fontColor) labelStr += `fontColor:${label.fontColor}|`
if (label.background) labelStr += `background:${label.background}|`
labelStr += `${label.longitude},${label.latitude}`
return labelStr
// 添加路径
if (config.paths && config.paths.length > 0) {
const pathsStr = config.paths.map(path => {
let pathStr = `color:${path.color || '0xFF0000'}|weight:${path.weight || 5}|`
pathStr += path.points.map(point => `${point.latitude},${point.longitude}`).join(';')
return pathStr
}).join('|')
url += `&labels=${encodeURIComponent(labelsStr)}`
url += `&path=${encodeURIComponent(pathsStr)}`
}
return url
@@ -86,13 +72,13 @@ class StaticMapUtil {
const markers = positions.map((position, index) => ({
longitude: parseFloat(position.longitude),
latitude: parseFloat(position.latitude),
size: 'mid',
color: position.status === 'online' ? 'green' : 'red',
size: 'medium',
color: position.status === 'online' ? '0x4CAF50' : '0xF44336',
label: String.fromCharCode(65 + (index % 26)) // A, B, C...
}))
return this.generateMapUrl({
location: `${center.longitude},${center.latitude}`,
center: `${center.latitude},${center.longitude}`,
zoom: zoom,
markers: markers,
...options
@@ -114,14 +100,23 @@ class StaticMapUtil {
return { longitude: 116.397128, latitude: 39.916527 }
}
const sum = validPositions.reduce((acc, pos) => ({
longitude: acc.longitude + parseFloat(pos.longitude),
latitude: acc.latitude + parseFloat(pos.latitude)
}), { longitude: 0, latitude: 0 })
if (validPositions.length === 1) {
return {
longitude: parseFloat(validPositions[0].longitude),
latitude: parseFloat(validPositions[0].latitude)
}
}
let sumLat = 0
let sumLng = 0
validPositions.forEach(position => {
sumLat += parseFloat(position.latitude)
sumLng += parseFloat(position.longitude)
})
return {
longitude: (sum.longitude / validPositions.length).toFixed(6),
latitude: (sum.latitude / validPositions.length).toFixed(6)
latitude: sumLat / validPositions.length,
longitude: sumLng / validPositions.length
}
}
@@ -132,63 +127,56 @@ class StaticMapUtil {
* @returns {Number} 缩放级别
*/
calculateOptimalZoom(positions, center) {
if (positions.length <= 1) return 15
if (!positions || positions.length <= 1) {
return 13 // 默认缩放级别
}
const validPositions = positions.filter(p => p.longitude && p.latitude)
if (validPositions.length <= 1) return 15
if (validPositions.length <= 1) {
return 13
}
// 计算最大距离
let maxDistance = 0
validPositions.forEach(pos => {
const distance = this.getDistance(
validPositions.forEach(position => {
const distance = this.calculateHaversineDistance(
center.latitude, center.longitude,
parseFloat(pos.latitude), parseFloat(pos.longitude)
parseFloat(position.latitude), parseFloat(position.longitude)
)
maxDistance = Math.max(maxDistance, distance)
})
// 根据最大距离确定缩放级别
if (maxDistance < 1) return 16 // 1km
if (maxDistance < 2) return 15 // 2km
if (maxDistance < 5) return 14 // 5km
if (maxDistance < 10) return 13 // 10km
if (maxDistance < 20) return 12 // 20km
if (maxDistance < 50) return 11 // 50km内
return 10 // 50km以
// 基于距离计算缩放级别
if (maxDistance > 10) return 10 // 10km以上
if (maxDistance > 5) return 11 // 5-10km
if (maxDistance > 2) return 12 // 2-5km
if (maxDistance > 1) return 13 // 1-2km
if (maxDistance > 0.5) return 14 // 500m-1km
if (maxDistance > 0.2) return 15 // 200-500m
return 16 // 200m以
}
/**
* 计算两点间距离(公里)
* @param {Number} lat1 纬度1
* @param {Number} lng1 经度1
* @param {Number} lat2 纬度2
* @param {Number} lng2 经度2
* @param {Number} lat1 第一点纬度
* @param {Number} lng1 第一点经度
* @param {Number} lat2 第二点纬度
* @param {Number} lng2 第二点经度
* @returns {Number} 距离(公里)
*/
getDistance(lat1, lng1, lat2, lng2) {
const radLat1 = lat1 * Math.PI / 180.0
const radLat2 = lat2 * Math.PI / 180.0
const a = radLat1 - radLat2
const b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0
let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))
s = s * 6378.137
s = Math.round(s * 10000) / 10000
return s
}
/**
* 预加载地图图片
* @param {String} mapUrl 地图URL
* @returns {Promise} 图片加载Promise
*/
preloadMapImage(mapUrl) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => resolve(mapUrl)
img.onerror = reject
img.src = mapUrl
})
calculateHaversineDistance(lat1, lng1, lat2, lng2) {
const R = 6371 // 地球半径(公里)
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
}
}
export default new StaticMapUtil()
// 导出实例
module.exports = new StaticMapUtil()