angular调用百度地图api踩坑记录

苗大 · 2021-8-16 · 次阅读


前言:

跳过踩坑碎碎念,直接看使用方法

根据百度地图api示例,调用地图需要先引入js:

<script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=您的密钥"></script>

之后才能进行各种api调用,但angular直接调用互联网的js,需要利用angular-cli.json的script属性或者直接在index.html中引入js,这种全局调用的方式并不是笔者想要的。

于是就去搜索了各种angular异步调用js方法,在StackOverFlow一篇问答:angular如何动态加载外部js 里有介绍说用system.js或者scriptjs工具来加载,笔者尝试了scriptjs加载的方法,发现运行时提示警告:

depends on 'scriptjs'. CommonJS or AMD dependencies can  cause optimization bailouts.

StackOverFlow上类似问题的解释是:When you use a dependency that is packaged with CommonJS, it can result in larger slower applications
看来这种方式也不是很好,会导致打包后的程序更慢更大,于是笔者就想到直接找别人写好的轮子岂不是更快,最后在GitHub上找到angular-baidu-maps,看到下面调用代码大喜:

import { AbmModule } from 'angular-baidu-maps';

@NgModule({
  imports: [
    BrowserModule,
    AbmModule.forRoot({
      apiKey: '' // app key为必选项
    })
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

看起来很好用,这就开整!

安装&升级Angular12踩坑

根据上面GitHub的README.md可以很快安装成功,但当笔者调用时候却报了提示angular版本不兼容,查了下发现,插件是angular12版本,而项目用的却是angular11,咨询了项目负责人后获得了升级许可,升级完终于可以调用成功了,但却发现之前PrimeNG的css样式都失效了,去官网看了下才发现原来,PrimeNG 12之后css改为primeflex3.0,于是又是一番折腾,终于把样式改正常了,虽然运行终端log里一直有大量的下面这种警告:

assets/css/style-blessed.scss - Warning: Css Minimizer Plugin:  > assets/css/style-blessed.scss:2700:1: warning: Expected identifier but 
found "."
    2700 │   .social-icon {
         ╵   ^


查了下应该是css压缩转换相关的问题,但是试了各种方法依然没能解决,虽然不影响运行和打包,但还是挺烦人的,如果有大佬有好的解决办法,希望可以留言告诉笔者,万分感谢!

正式使用

需求:

  1. 添加大量自定义标签;
  2. 点击自定义标签显示信息窗;
  3. 获取用户定位并显示;

添加大量自定义标签

需要使用到BMapLib.MarkerClusterer类,这个类是另外加载的,所以我们要在app.module.ts引用时在AbmModule的libraries里添加上:

//baidu map
AbmModule.forRoot({
  apiKey: '你的apiKey',
  libraries: [
    '//api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay_min.js',
    '//api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer_min.js',
  ],
}),

接着在调用api的组件里添加定义:

declare const BMapLib: any;

之后如果你之前已经写好标签数组(markers)就可以像这样直接调用了:

new BMapLib.MarkerClusterer(map, {markers: markers})

我们这里先从创建自定义标签开始:

// 创建一个包含经纬度的点
const point = new BMap.Point(lng, lat);
// 创建一个自定义图标,包含图片路径和一个包含宽高的Size对象
const myIcon = new BMap.Icon(
  '/img/map/marker_icon.png',
  new BMap.Size(iconWidth, iconHeight)
);
// 创建标注
const marker = new BMap.Marker(point, { icon: myIcon });
// 设置标注标题
marker.setTitle(name);

于是一个自定义标注完成了,之后只要将标注用MarkerClusterer聚合:

// 写在循环内,将所有标注放到一个数组里
markers.push(marker)
// 标注导入聚合
const markerClusterer = new BMapLib.MarkerClusterer(map, {markers: markers})
// 聚合点样式(和标注类似,多了一个聚合后文字的颜色)
const polymerizationStyles = [
  {
    url: '/img/map/cluster_icon.png',
    size: new BMap.Size(clusterIconWidth, clusterIconHeight),
    textColor: #FFF,
  },
];
// 设置超过几个点开始聚合
markerClusterer.setMinClusterSize(2);
// 调用样式
markerClusterer.setStyles(polymerizationStyles);

到这里,信息窗就完成了,下面开始获取用户定位

点击自定义标签显示信息窗

信息框添加本身比较简单,可以用js原始的添加删除事件方法,我这里为了统一管理事件用了 rxjs

// 引入rxjs相关函数
import { Subscription, fromEvent, merge } from 'rxjs';
// 定义一个订阅器
subscription: Subscription;

下面代码是写在创建marker的循环里的:

// 信息框内容
const info_html = `<div><span>我是信息窗信息</span></div>`;
// 点击标注显示信息窗()
const markerEvent = fromEvent(marker, 'click').pipe(
  tap((e) => marker.openInfoWindow(new BMap.InfoWindow(info_html)))
);
// 事件和marker一样用一个数组聚合
events.push(markerEvent);

之后在循环外面将事件聚合和发布:

// merge聚合全部点击事件, 再通一发布
this.subscription = merge(...events).subscribe();

这样我们就能在ngOnDestroy()里轻松销毁所有事件:

ngOnDestroy(): void {
  // 销毁事件
  this.subscription.unsubscribe();
}

到这里,大量自定义标签的添加就完成了,下面开始添加信息窗

获取用户定位并显示

获取用户定位我们需要用到BMap的Geolocation方法:

// 定位所需的成功状态
declare const BMAP_STATUS_SUCCESS: any;

// 获取当前位置经纬度
const geolocation = new BMap.Geolocation();
// 为了在下面匿名函数内调用到组件对象
const _this = this;
geolocation.getCurrentPosition(function (r) {
  if (this.getStatus() == BMAP_STATUS_SUCCESS) {
    console.log('您的位置:' + r.point.lng + ',' + r.point.lat);
    //自定义定位标识(和marker一样)
    const myCurIcon = new BMap.Icon(
      '/img/map/cluster_icon.png',
      new BMap.Size(curIconWidth, curIconHeight)
    );
    // 创建当前位置标注
    const curMarker = new BMap.Marker(r.point, { icon: myCurIcon });
    curMarker.setTitle(curIconTitle);
    //将当前位置标注添加到地图
    map.addOverlay(curMarker);
    // 这里的curLat和curLng是需要输出到父组件的
    // @Output() curLat = new EventEmitter<string>();
    _this.curLat.emit(r.point.lat);
    _this.curLng.emit(r.point.lng);
  } else {
    console.log('failed' + this.getStatus());
    alert('获取当前位置失败,请开启定位权限!');
  }
});

这样就能拿到用户的定位并显示了,父组件拿到定位后也可以进行调用百度计算路径的网址:

/**
 * 参数说明:
 * mode:出行方式,这里用的是开车 - driving
 * region:地域,这里的是西安(似乎随便填一个就行
 * origin:起始地经纬度,这里传入了子组件传来的经纬度
 * destination:终点经纬度
 */
<a
  [href]="'http://api.map.baidu.com/direction?mode=driving&output=html&region=%E8%A5%BF%E5%AE%89%E2%80%8B&origin='
   + curLat + ',' + curLng 
   + '&destination=' 
   + lat + ',' + lng"
  target="_blank"
>

2021/8/27 更新 获取当前位置经纬度方法2

由于需求增加,需要在加载百度页面的其他页面也要获取到当前位置经纬度,于是搜寻了下js原生的获取方法:

ngOnInit() {
if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition((p) => {
        const { latitude, longitude } = p.coords;
        console.log('latitude, longitude: ' , latitude, longitude);
      });
    }
}

这个方法简单,而且可以直接在初始化时候调用,不用等待百度地图相关api的启用。

参考资料&鸣谢:


我不是天生的王者,但我骨子里流动着不让我低头的血液!