Files
uni-fans-score/utils/mapUtils.js
T

1330 lines
45 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';
// 高德地图配置(支付宝小程序专用)
const AMAP_KEY = 'a07af802ff0a04f012954ff4e69b36d0';
const AMAP_SECURITY_KEY = '119f36535ab42b8b2c857f29f9320b06';
const AMAP_BASE_URL = 'https://restapi.amap.com/v3';
const AMAP_PATH_PREFIX = '/v3';
// 运行环境标识(编译期指令)
const isAlipayEnv = (() => {
let flag = false;
// #ifdef MP-ALIPAY
flag = true;
// #endif
return flag;
})();
// 轻量级 MD5 实现(用于生成高德安全 sig)
function md5(string) {
function RotateLeft(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
}
function AddUnsigned(lX, lY) {
const lX4 = lX & 0x40000000;
const lY4 = lY & 0x40000000;
const lX8 = lX & 0x80000000;
const lY8 = lY & 0x80000000;
const lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
if (lX4 & lY4) {
return lResult ^ 0x80000000 ^ lX8 ^ lY8;
}
if (lX4 | lY4) {
if (lResult & 0x40000000) {
return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
}
return lResult ^ 0x40000000 ^ lX8 ^ lY8;
}
return lResult ^ lX8 ^ lY8;
}
function F(x, y, z) { return (x & y) | (~x & z); }
function G(x, y, z) { return (x & z) | (y & ~z); }
function H(x, y, z) { return x ^ y ^ z; }
function I(x, y, z) { return y ^ (x | ~z); }
function FF(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function GG(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function HH(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function II(a, b, c, d, x, s, ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
}
function ConvertToWordArray(str) {
const lWordArray = [];
let lMessageLength = str.length;
let lNumberOfWordsTempOne = lMessageLength + 8;
const lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;
const lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
let lBytePosition = 0;
let lByteCount = 0;
while (lByteCount < lMessageLength) {
const lWordCount = (lByteCount - (lByteCount % 4)) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = (lWordArray[lWordCount] || 0) | (str.charCodeAt(lByteCount) << lBytePosition);
lByteCount++;
}
const lWordCount = (lByteCount - (lByteCount % 4)) / 4;
lBytePosition = (lByteCount % 4) * 8;
lWordArray[lWordCount] = lWordArray[lWordCount] || 0;
lWordArray[lWordCount] |= 0x80 << lBytePosition;
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
return lWordArray;
}
function WordToHex(lValue) {
let WordToHexValue = '';
for (let lCount = 0; lCount <= 3; lCount++) {
const lByte = (lValue >>> (lCount * 8)) & 255;
const WordToHexValueTemp = '0' + lByte.toString(16);
WordToHexValue += WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
}
return WordToHexValue;
}
function Utf8Encode(str) {
str = str.replace(/\r\n/g, '\n');
let utftext = '';
for (let n = 0; n < str.length; n++) {
const c = str.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if (c > 127 && c < 2048) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
let x = [];
let k, AA, BB, CC, DD, a, b, c, d;
const S11 = 7, S12 = 12, S13 = 17, S14 = 22;
const S21 = 5, S22 = 9, S23 = 14, S24 = 20;
const S31 = 4, S32 = 11, S33 = 16, S34 = 23;
const S41 = 6, S42 = 10, S43 = 15, S44 = 21;
string = Utf8Encode(String(string));
x = ConvertToWordArray(string);
a = 0x67452301;
b = 0xefcdab89;
c = 0x98badcfe;
d = 0x10325476;
for (k = 0; k < x.length; k += 16) {
AA = a; BB = b; CC = c; DD = d;
a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
a = AddUnsigned(a, AA);
b = AddUnsigned(b, BB);
c = AddUnsigned(c, CC);
d = AddUnsigned(d, DD);
}
const temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
return temp.toLowerCase();
}
// 将参数按字典序拼装为查询字符串
function buildSortedQuery(params) {
const sortedKeys = Object.keys(params || {}).sort();
return sortedKeys.map(key => `${key}=${params[key] !== undefined ? params[key] : ''}`).join('&');
}
// 调试开关:如需打印签名原串和 sig,临时改为 true
const DEBUG_AMAP_SIG = false;
// 构造高德 API 签名(正确规则:参数字符串 + 私钥,不含路径)
function buildAmapSig(params) {
const query = buildSortedQuery(params);
const raw = `${query}${AMAP_SECURITY_KEY}`;
if (DEBUG_AMAP_SIG) {
console.log('[AMap Sig Debug] query:', query, 'raw:', raw, 'sig:', md5(raw));
}
return md5(raw);
}
// 过滤掉 undefined / null 参数,避免签名与请求不一致
function sanitizeParams(obj = {}) {
const cleaned = {};
Object.keys(obj).forEach((k) => {
const v = obj[k];
if (v !== undefined && v !== null) {
cleaned[k] = v;
}
});
return cleaned;
}
// 统一的高德 API 请求
function requestAmap(path, params = {}) {
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
const baseParams = sanitizeParams({
key: AMAP_KEY,
...params
});
const sig = buildAmapSig(baseParams);
const queryParams = sanitizeParams({
...baseParams,
sig
});
return new Promise((resolve, reject) => {
uni.request({
url: `${AMAP_BASE_URL}${normalizedPath}`,
method: 'GET',
data: queryParams,
success: (res) => {
if (res.data && res.data.status === '1') {
resolve(res.data);
} else {
reject(res.data || { message: 'AMap request failed' });
}
},
fail: reject
});
});
}
// 将 WGS84 转 GCJ02(支付宝 my.getLocation 返回 WGS84
async function convertToGcj02(longitude, latitude) {
if (!isAlipayEnv) {
return { longitude, latitude };
}
try {
const data = await requestAmap('/assistant/coordinate/convert', {
locations: `${longitude},${latitude}`,
coordsys: 'gps' // gps 即 WGS84
});
const locStr = (data.locations || '').split(';')[0] || '';
const [lngStr, latStr] = locStr.split(',');
const lng = Number(lngStr);
const lat = Number(latStr);
if (!isNaN(lng) && !isNaN(lat)) {
return { longitude: lng, latitude: lat };
}
} catch (err) {
console.warn('坐标转换失败,使用原始坐标:', err);
}
return { longitude, latitude };
}
// 内联腾讯地图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
// #ifdef MP-WEIXIN
wx.getLocation({
type: 'gcj02',
success: locationsuccess,
fail: locationfail,
complete: locationcomplete
});
// #endif
// 支付宝小程序使用 my.getLocation(不支持 type
// #ifdef MP-ALIPAY
my.getLocation({
success: (res) => {
// 确保经纬度是数字类型
const result = {
...res,
longitude: Number(res.longitude),
latitude: Number(res.latitude)
};
locationsuccess(result);
},
fail: locationfail,
complete: locationcomplete
});
// #endif
// 其他平台使用 uni.getLocation
// #ifndef MP-WEIXIN
// #ifndef MP-ALIPAY
uni.getLocation({
type: 'gcj02',
success: locationsuccess,
fail: locationfail,
complete: locationcomplete
});
// #endif
// #endif
} else if (that.checkLocation(param)) {
const location = Utils.getLocationParam(param.location);
locationsuccess(location);
}
},
// 构造腾讯地图API请求参数(跨平台兼容)
buildWxRequestConfig(param, options, feature) {
const that = this;
// GET请求不需要设置content-typeuni.request会自动处理
options.header = options.header || {};
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;
// 调用腾讯地图API - 逆地理编码接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_GET_GEOCODER, // 腾讯地图API地址:https://apis.map.qq.com/ws/geocoder/v1/
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 + ")";
// 调用腾讯地图API - POI周边检索接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SEARCH, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/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;
// 调用腾讯地图API - 地点搜索建议接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/suggestion
data: requestParam
}, "suggest"));
};
Utils.locationProcess(options, locationsuccess);
} else {
// 调用腾讯地图API - 地点搜索建议接口(无位置信息)
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION, // 腾讯地图API地址:https://apis.map.qq.com/ws/place/v1/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;
// 调用腾讯地图API - 距离计算接口
// 使用 uni.request 跨平台API,在支付宝小程序中自动转换为 my.request
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_DISTANCE, // 腾讯地图API地址:https://apis.map.qq.com/ws/distance/v1/
data: requestParam
}, 'calculateDistance'));
};
Utils.locationProcess(options, locationsuccess);
}
}
return QQMapWX;
})();
// 全局QQMap实例
let qqmapInstance = null;
// 初始化腾讯地图SDK
function initQQMap() {
if (isAlipayEnv) {
// 支付宝端改用高德地图服务
return null;
}
if (!qqmapInstance) {
try {
qqmapInstance = new QQMapWX({
key: QQMAP_KEY
});
console.log('腾讯地图SDK初始化成功');
} catch (err) {
console.error('初始化腾讯地图SDK失败:', err);
}
}
return qqmapInstance;
}
// 获取腾讯地图SDK实例
function getQQMapInstance() {
if (isAlipayEnv) {
return null;
}
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(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(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(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
}
});
// #endif
// 支付宝小程序:使用 my.getLocation API,不支持 type 参数
// #ifdef MP-ALIPAY
// 支付宝小程序权限检查
my.getSetting({
success: (settingRes) => {
const authSetting = settingRes.authSetting || {};
const hasLocationAuth = Object.prototype.hasOwnProperty.call(authSetting, 'location');
const locationAuth = authSetting['location'];
// 已明确拒绝定位
if (locationAuth === false) {
my.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationNeed'),
confirmText: getPermissionText('goToSettings'),
cancelText: getPermissionText('later'),
success: (modalRes) => {
if (modalRes.confirm) {
my.openSetting({});
}
}
});
reject({
code: 'LOCATION_DENIED',
errMsg: 'user denied location permission'
});
return;
}
const doGetLocation = () => {
// 支付宝小程序使用 my.getLocation,不支持 type 参数
my.getLocation({
success: (res) => {
// 支付宝小程序返回的经纬度可能是字符串,需要先转换为数字
// 注意:支付宝返回的是 WGS84 坐标系,如需 GCJ02 需要转换
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('支付宝地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
};
// 未显式授权时,主动申请一次权限
if (!hasLocationAuth || locationAuth === undefined) {
my.authorize({
scope: 'scope.userLocation',
success: () => {
doGetLocation();
},
fail: (authErr) => {
console.error('定位授权失败:', authErr);
my.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationDenied'),
confirmText: getPermissionText('gotIt'),
showCancel: false
});
reject({
code: 'LOCATION_AUTH_FAIL',
errMsg: authErr.errorMessage || 'authorize location fail'
});
}
});
} else {
// 已授权,直接获取定位
doGetLocation();
}
},
fail: (err) => {
console.warn('获取授权设置失败,直接尝试定位:', err);
// 支付宝小程序使用 my.getLocation,不支持 type 参数
my.getLocation({
success: (res) => {
// 支付宝小程序返回的经纬度可能是字符串,需要先转换为数字
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('支付宝地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
}
});
// #endif
// 其他非微信、非支付宝小程序平台
// #ifndef MP-WEIXIN
// #ifndef MP-ALIPAY
uni.getLocation({
type: 'gcj02',
success: (res) => {
// 确保经纬度是数字类型
const longitude = parseFloat(Number(res.longitude).toFixed(5));
const latitude = parseFloat(Number(res.latitude).toFixed(5));
console.log('地址获取成功');
resolve({
longitude,
latitude
});
},
fail: (error) => {
console.error('获取位置失败:', error);
reject(error);
}
});
// #endif
// #endif
});
}
// 逆地理编码 - 根据经纬度获取地址信息
function getRegeo(longitude, latitude) {
return new Promise((resolve, reject) => {
// 支付宝端使用高德逆地理编码
if (isAlipayEnv) {
(async () => {
try {
const gcj = await convertToGcj02(longitude, latitude);
const data = await requestAmap('/geocode/regeo', {
location: `${gcj.longitude},${gcj.latitude}`,
extensions: 'all',
batch: 'false'
});
const info = data.regeocode || {};
const comp = info.addressComponent || {};
resolve({
success: true,
data: {
formatted_address: info.formatted_address || '',
addressComponent: {
city: comp.city || comp.province || '',
district: comp.district || '',
province: comp.province || '',
street: comp.streetNumber?.street || comp.street || '',
street_number: comp.streetNumber?.number || comp.streetNumber?.street_number || ''
}
}
});
} catch (error) {
console.error('高德逆地理编码失败:', error);
reject({ success: false, message: error.message || '逆地理编码失败' });
}
})();
return;
}
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) => {
// 支付宝端使用高德周边搜索
if (isAlipayEnv) {
(async () => {
try {
const gcj = await convertToGcj02(longitude, latitude);
const data = await requestAmap('/place/around', {
location: `${gcj.longitude},${gcj.latitude}`,
keywords: keyword,
radius,
offset: 20,
page: 1,
extensions: 'base',
sortrule: 'distance'
});
const pois = data.pois || [];
const normalized = pois.map((item, idx) => ({
id: item.id || idx + 1,
title: item.name || '',
latitude: Number(item.location?.split(',')[1] || item.location?.lat || 0),
longitude: Number(item.location?.split(',')[0] || item.location?.lng || 0),
address: item.address || '',
category: item.type || '',
tel: item.tel || '',
city: item.cityname || '',
district: item.adname || '',
province: item.pname || ''
}));
resolve({
success: true,
data: normalized
});
} catch (error) {
console.error('高德周边搜索失败:', error);
reject({ success: false, message: error.message || '搜索POI失败' });
}
})();
return;
}
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) => {
// 支付宝端使用高德距离测量接口
if (isAlipayEnv) {
// to 支持数组
const targets = Array.isArray(to) ? to : [to];
if (!targets.length) {
reject({ success: false, message: '缺少目标坐标' });
return;
}
const doRequest = async (originPoint) => {
if (!originPoint || originPoint.longitude === undefined || originPoint.latitude === undefined) {
reject({ success: false, message: '缺少起点坐标' });
return;
}
try {
const originGcj = await convertToGcj02(originPoint.longitude || originPoint.lng, originPoint.latitude || originPoint.lat);
const convertedTargets = await Promise.all(targets.map(async (item) => {
const lng = item.longitude || item.lng;
const lat = item.latitude || item.lat;
const res = await convertToGcj02(lng, lat);
return `${res.longitude},${res.latitude}`;
}));
const origins = convertedTargets.join('|');
const destination = `${originGcj.longitude},${originGcj.latitude}`;
const data = await requestAmap('/distance', {
origins,
destination,
type: 3 // 3:步行距离
});
const results = data.results || [];
const distance = results.map(r => Number(r.distance || 0));
resolve({
success: true,
data: distance
});
} catch (error) {
console.error('高德距离计算失败:', error);
reject({ success: false, message: error.message || '计算距离失败' });
}
};
if (from && typeof from === 'object') {
doRequest(from);
} else {
// 未传入起点时,使用用户当前定位
getUserLocation().then(loc => {
doRequest({
longitude: loc.longitude,
latitude: loc.latitude
});
}).catch(err => {
reject({ success: false, message: err.message || '获取起点位置失败' });
});
}
return;
}
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) => {
// 支付宝端使用高德输入提示
if (isAlipayEnv) {
requestAmap('/assistant/inputtips', {
keywords: keyword,
city: region === '全国' ? undefined : region,
datatype: 'all'
}).then((data) => {
const tips = data.tips || [];
const normalized = tips.map((item, idx) => ({
id: item.id || idx + 1,
title: item.name || '',
adcode: item.adcode || '',
address: item.address || '',
category: item.type || '',
city: item.city || '',
district: item.district || '',
latitude: item.location ? Number(item.location.split(',')[1]) : null,
longitude: item.location ? Number(item.location.split(',')[0]) : null,
province: item.province || ''
}));
resolve({
success: true,
data: normalized
});
}).catch((error) => {
console.error('高德关键词提示失败:', error);
reject({ success: false, message: error.message || '关键词提示失败' });
});
return;
}
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;
}