feat:新增地图模块,用于查找附近设备场地
This commit is contained in:
-163
@@ -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()
|
||||
@@ -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
@@ -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 += `¢er=${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()
|
||||
Reference in New Issue
Block a user