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
+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;
}