Files
uni-fans-score/utils/mapUtils.js
T
2025-11-28 09:21:37 +08:00

735 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 地图工具函数 - 内联腾讯地图SDK核心代码
const DEFAULT_LOCALE = 'zh-CN'
const permissionTexts = {
'zh-CN': {
locationTitle: '位置信息授权',
locationNeed: '需要获取您的位置信息以展示附近设备,请在“设置-权限管理”中开启定位权限。',
locationDenied: '未授权定位,将无法展示附近设备。您可以稍后在“设置-权限管理”中重新开启定位权限。',
goToSettings: '去设置',
later: '暂不',
gotIt: '知道了'
},
'en-US': {
locationTitle: 'Location Permission',
locationNeed: 'We need your location to show nearby devices. Please enable location in “Settings > Permissions”.',
locationDenied: 'Location access is disabled. You can re-enable it later in “Settings > Permissions”.',
goToSettings: 'Set',
later: 'Skip',
gotIt: 'OK'
}
}
const getCurrentLocale = () => {
try {
const saved = uni.getStorageSync('language')
if (saved && permissionTexts[saved]) {
return saved
}
} catch (_) {}
return DEFAULT_LOCALE
}
const getPermissionText = (key) => {
const locale = getCurrentLocale()
const texts = permissionTexts[locale] || permissionTexts[DEFAULT_LOCALE]
return texts[key] || permissionTexts[DEFAULT_LOCALE][key] || ''
}
// 腾讯地图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) => {
// 小程序端优先通过 getSetting 判断权限状态
// 这里只考虑 mp-weixin 场景,其它平台回退到直接调用 getLocation
// #ifdef MP-WEIXIN
uni.getSetting({
success: (settingRes) => {
const authSetting = settingRes.authSetting || {};
// 兼容多种定位 scope(微信近几次版本变更比较多)
const hasUserLocation = Object.prototype.hasOwnProperty.call(authSetting, 'scope.userLocation');
const userLocationAuth = authSetting['scope.userLocation'];
const fuzzyLocationAuth = authSetting['scope.userFuzzyLocation'];
const bgLocationAuth = authSetting['scope.userLocationBackground'];
// 已明确拒绝定位
if (
userLocationAuth === false ||
fuzzyLocationAuth === false ||
bgLocationAuth === false
) {
uni.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationNeed'),
confirmText: getPermissionText('goToSettings'),
cancelText: getPermissionText('later'),
success: (modalRes) => {
if (modalRes.confirm) {
uni.openSetting({});
}
}
});
reject({
code: 'LOCATION_DENIED',
errMsg: 'user denied location permission'
});
return;
}
const doGetLocation = () => {
wx.getLocation({
type: 'gcj02',
success: (res) => {
const longitude = parseFloat(res.longitude.toFixed(5));
const latitude = parseFloat(res.latitude.toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
};
// 未显式授权时,主动申请一次权限(老版本 scope 为 scope.userLocation
if (!hasUserLocation || userLocationAuth === undefined) {
uni.authorize({
scope: 'scope.userLocation',
success: () => {
doGetLocation();
},
fail: (authErr) => {
console.error('定位授权失败:', authErr);
uni.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationDenied'),
confirmText: getPermissionText('gotIt'),
showCancel: false
});
reject({
code: 'LOCATION_AUTH_FAIL',
errMsg: authErr.errMsg || 'authorize location fail'
});
}
});
} else {
// 已授权,直接获取定位
doGetLocation();
}
},
fail: (err) => {
console.warn('获取授权设置失败,直接尝试定位:', err);
wx.getLocation({
type: 'gcj02',
success: (res) => {
const longitude = parseFloat(res.longitude.toFixed(5));
const latitude = parseFloat(res.latitude.toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
}
});
// #endif
// 非微信小程序平台:使用 uni.getLocation 做一个尽量兼容的兜底
// #ifndef MP-WEIXIN
uni.getLocation({
type: 'gcj02',
success: (res) => {
const longitude = parseFloat(res.longitude.toFixed(5));
const latitude = parseFloat(res.latitude.toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
// #endif
});
}
// 逆地理编码 - 根据经纬度获取地址信息
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;
}