赞
踩
哈喽,大家好呀!小韵携原创博文给大家请安啦!
前言:百度地图官方JavaScript Api会提供组件baidu-map,在开启鼠标滚轮放缩时,其中心是根据body定位的,因此可能会出现中心偏移的问题。博文提供了一个百度地图渲染和含有点击方法、查询方法的完整html,利用<iframe>标签,嵌入到页面中,就完美解决了问题。
介绍:采用<iframe>标签,将自主渲染的百度地图和封装的点击、搜索方法的html内联至指定页面位置。
目录
下图采用官方组件baidu-map,直接在页面中引入该组件。诚如上面看到的,在使用鼠标滚动控制百度地图大小时,发现中心偏移,并没有以鼠标点为中心放缩,造成了非常不理想的情况。
百度地图的放缩是以body层为参照的,如果渲染页面含有滚动条,并且不在最顶端,这个位置插入的百度地图,就会判断中心错误,并非按照鼠标的中心。而博主的页面正是在长长的需要滚动的弹出层中放置的百度地图,你说巧了嘛这不是,符合条件,问题出现。
并非只有洒家处于这样的情况,遇事不会,官方管对。查阅百度地图介绍时,发现了这种在页面中嵌入的情况(jspopularGL | 百度地图API SDK),扒扒它的源码,很清晰的发现它使用了<iframe>标签,将以body为基地的百度地图包裹了起来,妙哉!
iframe 元素会创建包含另外一个文档的内联框架(即行内框架),并且支持所有的浏览器,可以创建一个html,自主渲染出百度地图,里面设置需要的方法,再在目标文件中引入该html,同时也可以调用其中方法。说来,其实是把vue的父子组件,换成了html的iframe标签,不同之处就在于:vue组件的模式是将html文件分模块处理,无论加多少嵌套的组件,总归都是在一个大的html中,毕竟vue组件是挂载到html中元素中的。这样思路就很明确了,下一节。
-
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8">
- <title>地图单击拾取经纬度</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
- <meta http-equiv="X-UA-Compatible" content="IE=Edge">
- <style>
- body,
- html,
- #container {
- overflow: hidden;
- width: 100%;
- height: 100%;
- margin: 0;
- /* font-family: "微软雅黑"; */
- }
- .anchorBL {
- display: none;
- visibility: hidden;
- }
- .anchorTR {
- display: none;
- visibility: hidden;
- }
- .BMap_zlHolder {
- display: none;
- visibility: hidden;
- }
- </style>
- <script type="text/javascript" src="//api.map.baidu.com/api?type=webgl&v=1.0&ak=''"></script>
- </head>
- <body>
- <div id="container"></div>
- </body>
- </html>
- <script>
- var map = new BMapGL.Map('container');
- // 初始化
- map.centerAndZoom(new BMapGL.Point('',''), 15);
- map.enableScrollWheelZoom(true);
- // 地图初始化事件
- function initMap(point){
- setTimeout(() => {
- console.log(point,'iframe-initMap')
- // 这里一定要加延时器,避免了地图未完全渲染出来从而导致的中心偏移问题
- map.centerAndZoom(new BMapGL.Point(point.lng, point.lat), 15);
- // 地图标点
- map.clearOverlays();
- var marker = new BMapGL.Marker(new BMapGL.Point(point.lng, point.lat));
- marker.type = "click"
- map.addOverlay(marker);
- }, 300);
-
- }
- // 发送解析地址信息
- function iframeClickMap(addrInfo){
- console.log(addrInfo,'iframeSendMsg')
- window.parent.postMessage({
- cmd:'getMap',
- params : {
- LNG:addrInfo.longitude,
- LAT:addrInfo.latitude,
- CONNECTADDR:addrInfo.connectAddr
- }
- },'*');
- };
- // 发送搜索结果list
- function iframeSearchMap(searList){
- console.log(searList,'searList')
- window.parent.postMessage({
- cmd:'getSearch',
- params : {
- searList
- }
- },'*');
- };
- // 点击地图事件
- map.addEventListener('click', function (e) {
- // 获取到经纬度
- console.log(e,'mapHTML-Click')
- // 地图标点
- map.clearOverlays();
- var marker = new BMapGL.Marker(new BMapGL.Point(e.latlng.lng, e.latlng.lat));
- marker.type = "click"
- map.addOverlay(marker);
- // // 获取到解析位置并发送
- var latlng = {
- lng:e.latlng.lng,
- lat:e.latlng.lat
- }
- getAddressApi(latlng).then(rs=>{
- console.log(rs,'mapHTML-getLocation')
- var addrInfo = {
- connectAddr : rs.result.formatted_address_poi,
- longitude : rs.result.location.lng,
- latitude : rs.result.location.lat
- }
- iframeClickMap(addrInfo)
- })
- // BMapGL添加参数extensions_poi失效
- // var geocoder = new BMapGL.Geocoder({extensions_poi:1}); //创建地址解析器的实例
- // geocoder.getLocation(new BMapGL.Point(e.latlng.lng, e.latlng.lat), (rs) => {
- // console.log(rs,'mapHTML-getLocation')
- // var addrInfo = {
- // connectAddr : rs.address,
- // longitude : rs.point.lng,
- // latitude : rs.point.lat
- // }
- // // iframeClickMap(addrInfo)
- // });
- });
- // 关键字搜索
- window.addEventListener("message", getAddrLocation);
- function getAddrLocation(event){
- const mapInfo = event.data;
- console.log(event,'getAddrLocation')
- if(mapInfo.cmd == 'searMap'){
- const addrInfo = mapInfo.params.addrInfo || ''
- // 地址搜索不为空,则搜索选定地址
- if (addrInfo !== '') {
-
- // 方案3:采用另一个接口,只返回一个经纬度
- searchApi(addrInfo).then(rs=>{
- map.centerAndZoom(new BMapGL.Point(rs.result.location.lng, rs.result.location.lat), 15);
- // 为搜索结果标记点位 但解析器不太准确,不能搜索出小地方。
- map.clearOverlays();
- var marker = new BMapGL.Marker(new BMapGL.Point(rs.result.location.lng, rs.result.location.lat));
- marker.type = "click"
- let searGeo = new BMapGL.Geocoder(); //创建地址解析器的实例
- map.addOverlay(marker);
- console.log(rs.result.location,'rs.result.location')
- getAddressApi(rs.result.location).then(rg=>{
- console.log(rg,'getAddrLocation-getLocation')
- var addrInfo = {
- connectAddr : rg.result.formatted_address_poi,
- longitude : rg.result.location.lng,
- latitude : rg.result.location.lat
- }
- iframeClickMap(addrInfo)
- });
- })
-
- // 方案2:list供用户选择,但接口有配额限制,需要掏钱
- // searchApi(addrInfo).then(rs=>{
- // // 搜索结果是一个list,直接让用户选择吧
- // console.log(rs,'searchApi')
- // iframeSearchMap(rs.result)
- // })
-
- // 方案1:搜索不精确---原始方案,采用jsApi,但是这个问题比较多
- // searGeo.getPoint(addr, (rs) => {
- // console.log(rs,'getPoint-getPoint')
- // map.centerAndZoom(new BMapGL.Point(rs.lng, rs.lat), 15);
- // // 为搜索结果标记点位 但解析器不太准确,不能搜索出小地方。
- // map.clearOverlays();
- // var marker = new BMapGL.Marker(new BMapGL.Point(rs.lng, rs.lat));
- // marker.type = "click"
- // map.addOverlay(marker);
- // // 为搜索结果解析位置并发送
- // searGeo.getLocation(rs, (rg) => {
- // console.log(rg,'getAddrLocation-getLocation')
- // var addrInfo = {
- // connectAddr : rg.address,
- // longitude : rg.point.lng,
- // latitude : rg.point.lat
- // }
- // iframeClickMap(addrInfo)
- // });
- // });
- }
- }
- };
- // 经纬度获取为地址-api
- function getAddressApi(latlng) {
- return jsonp( `https://api.map.baidu.com/reverse_geocoding/v3/?output=json&ak=''&extensions_poi=1&location=${latlng.lat},${latlng.lng}`)
- }
- // 经纬度获取为地址-api
- function searchApi(addrInfo) {
- // 这个接口有配额限制
- // return jsonp( `https://api.map.baidu.com/place/v2/suggestion?query=${addrInfo.keyword}®ion=${addrInfo.area}&city_limit=true&output=json&ak=''`)
- // 这个接口只返回经纬度
- return jsonp( `https://api.map.baidu.com/geocoding/v3/?address=${addrInfo.addr}&output=json&ak=''`)
- }
- // 定义jsonp
- const jsonp = function (url, data) {
- return new Promise((resolve, reject) => {
- // 初始化url
- let dataString = url.indexOf('?') === -1 ? '?' : '&'
- let callbackName = `jsonpCB_${Date.now()}`
- url += `${dataString}callback=${callbackName}`
- if (data) {
- // 有请求参数,依次添加到url
- for (let k in data) {
- url += `&${k}=${data[k]}`
- }
- }
- let jsNode = document.createElement('script')
- jsNode.src = url
- // 触发callback,触发后删除js标签和绑定在window上的callback
- window[callbackName] = result => {
- delete window[callbackName]
- document.body.removeChild(jsNode)
- if (result) {
- resolve(result)
- } else {
- reject('没有返回数据')
- }
- }
- // js加载异常的情况
- jsNode.addEventListener('error', () => {
- delete window[callbackName]
- document.body.removeChild(jsNode)
- reject('JavaScript资源加载失败')
- }, false)
- // 添加js节点到document上时,开始请求
- document.body.appendChild(jsNode)
- })
-
- }
- </script>
-
-
很有必要做出如下解释:
style中的类已经取出了百度地图的版权字样等,是一个干净清爽的页面哈
其实不是很明确服务端的ak是否允许在前端直接访问,至少在本地成功了。
渲染页面的ak要使用浏览器端的ak,使用服务端ak会报“APP服务被禁用”;
访问解析接口的ak要使用服务端的ak,使用浏览器端ak会报“跨源读取阻止(CORB)功能阻止了 MIME 类型为 application/json 的跨源响应”
无非是BMap.Geocoder()的getLocation()和getPoint()两种方法,即正/逆地址解析。
但是这两个方法却有很大的毛病,getLocation()逆地址解析出的地址信息,不明确,文档上所谓的{extensions_poi:1}字段没有效果,工单反馈问题,解决方案是直接采用合适的api。
因此,使用getAddressApi和searchApi两个方法替换解析器。也因此代码中会出现不同的方案,供大家参考选择。注:采用jsonp格式请求,在此不再介绍。
在searchApi中我写了两个接口,文中采用第二个接口,需要传结构化地址信息,意思是你要传“奥体中心”,你传“奥体”就搜不出来了。第一个接口,检索功能强大,其实就是我们平常用的提示词,但确实分页返回满足检索词的所有地址,大概率在小范围内重复好多个,我认为可以递归筛选来返回更多有效的地址,一定是因为我没有资本所以才不做,这个接口额度是100次每天,单价是30元1万次,还好。
initMap:初始化地图事件,父组件使用,参数为经纬度,地图显示对应点位。
iframeClickMap:传递函数,子组件使用,参数为经纬度和地址,向父组件发送"getMap"带参指令。
map.addEventListener('click',function (e) {}):监听地图点击事件,解析点位经纬度和地址信息,通过iframeClickMap发送
window.addEventListener("message", getAddrLocation)及getAddrLocation:创建指令监听,用于获取父组件搜索事件的后续处理,其中有采用JavaScript Api官方解析器,普通api请求解析器、付费api请求解析器,根据搜索地址获取经纬度和地址并通过iframeClickMap或iframeSearchMap发送至父组件。
(Vue项目)如果我没有记错的话,文件必须放在public/static下面才能在使用时被src找到,貌似是其它位置的不会被打包,待求证哦~
<iframe src="/static/map.html" ref="mapIframe" frameborder="0" class="baidu-map"></iframe>
- mounted() {
- window.addEventListener('message',this.getIframeMessage)
- },
init():初始化地图,调用子组件initMap,参数为初始化地图中心。
getIframeMessage():获取子组件消息,获取cmd为getMap的消息的信息,即经纬度和地址信息。
searchLocation():地址检索,向子组件发送searMap的带检索关键词的消息。
- // 地图初始化
- init(e) {
- this.$refs.mapIframe.contentWindow.initMap(this.initLocation);
- },
- // getMap接收参数
- getIframeMessage(event){
- const info = event.data;
- console.log(info,'getIframeMessage')
- // 点击结果 --- 方案3
- if(info.cmd == 'getMap'){
- // console.log(info,'getIframeMessage')
- // 为表单赋值
- this.form.connectAddr = info.params.CONNECTADDR
- this.form.longitude = info.params.LNG
- this.form.latitude = info.params.LAT
- this.mapPoint.lng = info.params.LNG
- this.mapPoint.lat = info.params.LAT
- }
- // 搜索结果 --- 方案2
- if(info.cmd == 'getSearch'){
- // console.log(info,'getSearch')
- // 搜索结果
- this.searList = info.params.searList
- console.log(this.searList,'getSearch')
- }
- },
-
- // 传递搜索参数
- searchLocation() {
- const iframeWindow = this.$refs.mapIframe.contentWindow;
- iframeWindow.postMessage({
- cmd:'searMap',
- params : {
- addrInfo:{
- area:this.area,
- keyword:this.keyword,
- addr:this.area+this.keyword
- }
- }
- },'*')
- },
-
看到这里的小伙伴真的是看到了这里,感谢你们的认真阅读。
首先感谢各位伙伴网上的资料。我在优化整个代码的过程中遇到最多的问题就是“跨源读取阻止(CORB)”的问题,N次尝试后,才知道要搞两个不同的ak,但是洒家也不知道这种是否被允许,还有采用服务端ak来访问官方接口在生产环境上是否允许(但我看到之前有腾讯地图也是直接访问的官方接口,应该没问题)。
哦对了,竟然忘了展示最后结果,以下是成果,最后希望大家三键三连~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。