背景
百度地圖插件vue-baidu-map拾取的經(jīng)緯度在小程序上顯示時存在偏差。
小程序使用的是騰訊自家的地圖。兩個地圖用的是兩套坐標系,騰訊地圖、高德地圖用的是GCJ-02坐標,也就是國測局坐標系,而百度是自成一套,BD-09坐標系,所以相同地點在經(jīng)緯度在兩個坐標系是不一樣的,或者說有偏移。
解決方案如下:
對于后臺百度地圖傳過來的經(jīng)緯度,要做一次轉(zhuǎn)換,轉(zhuǎn)換成騰訊地圖的經(jīng)緯度,再傳給小程序顯示。
function convert2TecentMap(lng, lat) {
if (lng == '' && lat == '') {
return {
lng: '',
lat: ''
}
}
var x_pi = 3.14159265358979324 * 3000.0 / 180.0
var x = lng - 0.0065
var y = lat - 0.006
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi)
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi)
var qqlng = z * Math.cos(theta)
var qqlat = z * Math.sin(theta)
return {
lng: qqlng,
lat: qqlat
}
}
由于不想做轉(zhuǎn)換,所以基于騰訊地圖封裝了個vue組件。
- 實現(xiàn)的功能:
- 外部傳入經(jīng)緯度,地圖上顯示標記。
- 根據(jù)關(guān)鍵詞搜索地址。
- 點擊地圖,標記點,并顯示經(jīng)緯度、地址詳細信息。
- 點擊搜索結(jié)果的某一項,地圖上顯示標記。
- 效果圖:

ff7a5900-497d-11eb-97b7-0dc4655d6e68.png
組件的使用
1. 申請mapKey
- 打開騰訊位置服務(wù)系統(tǒng),登錄/注冊賬號->右上角進入控制臺-> key管理。
- 設(shè)置合法域名或IP。這一步很重要,否則后續(xù)的逆向解析將會提示
“域名/ip未授權(quán)”?!緫?yīng)用產(chǎn)品】設(shè)置為WebServiceAPI,配置即將引入該組件的網(wǎng)站域名或ip,其他保存默認設(shè)置,單擊【保存】。
90a27910-498f-11eb-8a36-ebb87efcf8c0.png
2. 內(nèi)部實現(xiàn)邏輯
- mapKey:需換成您上一步申請到的key。
<template>
<div class="well-map">
<div class="selected-info">
<div>
<span class="label">經(jīng)度:</span>
<span>{{selfSelectedValue.location.lng}}</span>
</div>
<div>
<span class="label">緯度:</span>
<span>{{selfSelectedValue.location.lat}}</span>
</div>
<div>
<span class="label">地址:</span>
<span>{{selfSelectedValue.address}}</span>
</div>
</div>
<div class="search-row" v-clickoutside="handleBlur">
<div class="input-wrap">
<input type="text" v-model="searchKey" @input="handleSearch()" @click="handleFoucus" placeholder="請輸入要搜索的地址"/>
<button type="button" @click="handleSearch()">搜索</button>
</div>
<ul v-show="showAddressList && addressList.length">
<li v-for="(item,index) in addressList" :key="index" @click.stop="handleSelect(item)">
<span class="title">{{item.title}}</span>
<span class="other-info">{{item.address}}</span>
</li>
</ul>
</div>
<div id="well-container" class="well-map-container"></div>
</div>
</template>
<script>
/**
* 使用:<well-tencent-map :selectedValue="selectedValue" @selected-change="mapSelectedChange"></well-tencent-map>
* 參數(shù):
* 1. selectedValue:可不傳、可傳null、支持異步數(shù)據(jù)。若傳入,格式如下:
* {
* location: {
* lat: null,
* lng: null,
* },
* address: null,
* province: null,
* city: null,
* district: null
* }
* 函數(shù):
* 1. selected-change:當(dāng)選中的點發(fā)生變化時,觸發(fā)
* **/
import axios from 'axios';
export default {
props: {
selectedValue: {
type: Object,
required: false
}
},
data() {
return {
timeout: null, //搜索防抖
selfSelectedValue: {
location: {
lat: null,
lng: null,
},
address: null,
province: null,
city: null,
district: null
},
searchKey: '',//搜索Key
addressList: [],//搜索結(jié)果
mapKey: 'JDKBZ-****',//騰訊地圖mapKey,需到https://lbs.qq.com/上申請
map: null,
markerLayer: null,
showAddressList: false
};
},
mounted() {
//初始化地圖
this.initMap();
},
watch: {
selectedValue: {
handler(newValue, oldName) {
let value = newValue || {};
let _newLoca = value.location || {};
let _oldLoca = this.selfSelectedValue.location || {};
let mergeResult = {
location: {
lat: _newLoca.lat,
lng: _newLoca.lng,
},
address: value.address,
province: value.province,
city: value.city,
district: value.district,
};
//判斷經(jīng)緯度是否發(fā)生變化,如果變化,則需要重新畫點
if (_oldLoca.lat !== _newLoca.lat || _oldLoca.lng !== _newLoca.lng) {
if (this.map && this.markerLayer) {
//1. 地圖如果渲染完了,則把畫點畫上去
//2. 如果沒有渲染完,也無需擔(dān)心,在init會根據(jù)selfSelectedValue的最新值繪制
this._drawPoint(_newLoca.lat, _newLoca.lng, true);
}
}
this.selfSelectedValue = mergeResult;
},
immediate: true
}
},
directives: {
/**
* 封裝指令,監(jiān)聽點擊非目標元素之外的dom
* ***/
clickoutside: {
bind(el, binding, vnode) {
function documentHandler(e) {
console.log('documentHandler');
// 這里判斷點擊的元素是否是本身,是本身,則返回
if (el.contains(e.target)) {
console.log('el.contains(e.target)');
return false;
}
// 判斷指令中是否綁定了函數(shù)
if (binding.expression) {
// 如果綁定了函數(shù) 則調(diào)用那個函數(shù),此處binding.value就是handleClose方法
binding.value(e);
}
}
// 給當(dāng)前元素綁定個私有變量,方便在unbind中可以解除事件監(jiān)聽
el.__vueClickOutside__ = documentHandler;
document.addEventListener('click', documentHandler);
},
update() {
},
unbind(el, binding) {
// 解除事件監(jiān)聽
document.removeEventListener('click', el.__vueClickOutside__);
delete el.__vueClickOutside__;
}
}
},
methods: {
/**
* 初始化地圖
* **/
initMap() {
//初始化地圖
this.map = new TMap.Map('well-container', {
rotation: 20, //設(shè)置地圖旋轉(zhuǎn)角度
pitch: 0, //設(shè)置俯仰角度(0~45)
zoom: 16, //設(shè)置地圖縮放級別
});
//初始化marker圖層
this.markerLayer = new TMap.MultiMarker({
id: 'marker-layer',
map: this.map
});
//監(jiān)聽點擊事件添加marker
this.map.on('click', this._clickMap);
//初始化點(在_drawPoint中,做了經(jīng)緯度是否存在的判斷)
let location = this.selfSelectedValue.location || {};
this._drawPoint(location.lat, location.lng, true);
},
/**
* 搜索框聚焦
* **/
handleFoucus(e) {
console.log('handleFoucus');
this.showAddressList = true;
},
/**
* 搜索框失焦
* **/
handleBlur() {
console.log('handleBlur');
this.showAddressList = false;
},
/**
* 搜索框內(nèi)容發(fā)生變化
* timeout是為了防抖
* **/
handleSearch() {
this.showAddressList = true;
console.log('handleSearch');
clearTimeout(this.timeout);
if (!this.searchKey) {
this.addressList = [];
} else {
this.timeout = setTimeout(() => {
axios({
url: `/wellTencentMap/ws/place/v1/suggestion?keyword=${this.searchKey}&key=${this.mapKey}`,
method: 'GET'
}).then(res => {
this.addressList = (res.data && res.data.data) || [];
}).catch(err => {
console.log(err);
});
}, 300);
}
},
/**
* 選中搜索列表中的某一項,row格式如下:
* {
* "id":"1594670327289385140",
* "title":"人才好娃幼兒園",
* "address":"遼寧省鐵嶺市銀州區(qū)三眼井巷1",
* "category":"教育學(xué)校:幼兒園",
* "type":0,
* "location":{
* "lat":42.292420809,
* "lng":123.857382405
* },
* "adcode":211202,
* "province":"遼寧省",
* "city":"鐵嶺市",
* "district":"銀州區(qū)"
* }* **/
handleSelect(row) {
console.log(JSON.stringify(row));
this.searchKey = row.title;
//searchKey發(fā)生變化了,需觸發(fā)搜索
this.handleSearch();
let value = {
location: row.location,
address: row.address,
province: row.province,
city: row.city,
district: row.district
};
this.selfSelectedValue = value;
this._notifyParent(value);
//selectedValue發(fā)生變化了,需重新繪制點
this._drawPoint(row.location.lat, row.location.lng, true);
//選中之后需觸發(fā)失去焦點
this.handleBlur();
},
/**
* 根據(jù)經(jīng)緯度在地圖上標注
* @param lat 緯度
* @param lng 經(jīng)度
* @param isUpdateCenter 是否更新地圖中心點,有以下三種情況:
* 1. 外部傳入的點信息發(fā)生變化時,需更新中心點
* 2. 選中搜索列表的某一項時,需更新中心點
* 3. 點擊地圖標記點時,不需要更新中心點
* **/
_drawPoint(lat, lng, isUpdateCenter) {
//先清空點
this.markerLayer.setGeometries([]);
if (!lat || !lng) {
return;
}
//更新地圖中心位置
if (isUpdateCenter) {
this.map.setCenter(
new TMap.LatLng(lat, lng)
);
}
this.markerLayer.add({
position: new TMap.LatLng(lat, lng)
});
},
/**
* 通知父組件,標記的點發(fā)生了變化
* **/
_notifyParent(value) {
// this.$emit('update:value', selectedValue);
// this.$emit('update:selectedValue', selectedValue);
this.$emit('selected-change', value);
},
/**
* 點擊地圖
* 1. 根據(jù)經(jīng)緯度畫點
* 2, 根據(jù)經(jīng)緯度逆向查詢到地址詳細信息
* 3. 通知父組件
* **/
_clickMap(evt){
console.log('點擊地圖:', evt);
this._drawPoint(evt.latLng.lat, evt.latLng.lng, false);
let locationParam = evt.latLng.lat + ',' + evt.latLng.lng;
axios({
url: '/wellTencentMap/ws/geocoder/v1/?location=' + locationParam + '&key=' + this.mapKey + '&get_poi=0',
method: 'GET'
}).then(res => {
console.log('【' + locationParam + '】逆地址解析結(jié)果:', res);
let result = (res.data && res.data.result);
if (!result) {
// this.$message.error(res.data.message);
alert(res.data.message);
return;
}
let ad_info = result.ad_info || {};
let value = {
location: result.location,
address: result.address,
province: ad_info.province,
city: ad_info.city,
district: ad_info.district
};
this.selfSelectedValue = value;
this._notifyParent(value);
}).catch(err => {
console.log('逆地址解析失敗', err);
});
},
}
};
</script>
<style lang="less" scoped>
.well-map {
/*position: relative;*/
line-height: normal;
.search-row {
position: relative;
/*line-height: normal;*/
width: 100%;
margin: 12px 0 4px 0;
/*left: 20px;*/
/*top: 20px;*/
z-index: 99009;
.input-wrap {
height: 32px;
line-height: 32px;
display: flex;
> input {
box-sizing: border-box;
margin: 0;
position: relative;
width: 100%;
height: 100%;
padding: 4px 11px;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
background-color: #fff;
/*background-image: none;*/
border: 1px solid #d9d9d9;
border-radius: 4px 0 0 4px;
transition: all 0.3s;
border-right: none;
&:hover {
border-color: #40a9ff;
border-right-width: 1px !important;
}
&:focus {
border-color: #40a9ff;
border-right-width: 1px !important;
outline: 0;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
> button {
padding: 0 15px;
border-radius: 0 4px 4px 0;
color: #fff;
background: #1890ff;
border-color: #1890ff;
box-shadow: none;
border: none;
width: 82px;
height: 100%;
cursor: pointer;
&:hover, &:focus {
color: #fff;
background: #40a9ff;
border-color: #40a9ff;
outline: none;
}
&:active {
color: #fff;
background: #096dd9;
border-color: #096dd9;
outline: none;
}
}
}
> ul {
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: rgba(252, 250, 250, 0.918);
border: 1px solid #f1f1f1;
font-size: 13px;
color: #5a5a5a;
max-height: 280px;
overflow-y: auto;
list-style: none;
padding: 0;
margin: 0;
> li {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 100%;
border-bottom: 1px solid #f1f1f1;
padding: 10px;
margin: 0;
cursor: pointer;
&:hover {
background: #eff6fd;
}
.title {
display: block;
line-height: normal;
margin-bottom: 4px;
}
.other-info {
font-size: 12px;
color: #b9b9b9;
display: block;
line-height: normal;
}
}
}
}
.well-map-container {
width: 100%;
height: 300px;
}
.selected-info {
background: #ecf5ff;
padding: 10px 14px;
color: #565656;
font-size: 13px;
> div {
margin-bottom: 4px;
.label {
color: #757575;
}
&:last-child {
margin-bottom: 0;
}
}
}
}
</style>
3. 使用
- 引入騰訊APIjs文件。在vue項目的index.html中引入,其中key需換成第一步申請到的key。
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=JDKBZ-****"></script>
- 設(shè)置代理。在vue.config.js文件中,添加配置。
devServer: {
proxy: {
"/wellTencentMap": {
target: "https://apis.map.qq.com",
changeOrigin: true,
ws: true,
pathRewrite: {
'^/wellTencentMap': '' //重寫接口
}
},
}
}
- 替換mapKey,組件實現(xiàn)邏輯中的mapKey需換上面申請到的key。
data() {
return {
mapKey: 'JDKBZ-****',
};
},
- 使用組件。(此處我的組件名為well-tencent-map)
<well-tencent-map :selectedValue="selectedValue" @selected-change="mapSelectedChange"></well-tencent-map>
- 參數(shù):
selectedValue:可不傳、可傳null、支持異步獲取回來的數(shù)據(jù)。若傳入,格式如下:
{
location: {
lat: null,
lng: null,
},
address: null,
province: null,
city: null,
district: null
}
- 事件:
selected-change:當(dāng)標記的點發(fā)生變化時,觸發(fā)。
