【在Angular项目中使用googlemaps】

1 篇文章 0 订阅
1 篇文章 0 订阅

在Angular2+项目中使用googlemaps



前期方案调研(部分截图)

在这里插入图片描述
在这里插入图片描述

国外项目需要引入地图中画线和测距离业务,对比了国外几大地图服务商,客户决定使用流行的google maps。另外googlemaps需要付费使用


一、Googlemaps是什么?

Google是 最早的地图服务提供商之一,有自己的地图检索网站和app,同时开放了Dynamic Maps JS, GeoLocation API 等商务服务。

二、使用步骤

1.开发前准备

  1. 在google官方网站上申请API Key
  2. 在管理页面 开启Maps JavaScript API. 如果需要其他服务,如测距等需要另外操作
  3. 以下是一些参考网址
    API Key:
    https://developers.google.com/maps/documentation/geocoding/get-api-key
    Billing Form:
    https://console.cloud.google.com/freetrial/signup/tos
    Quick calculator:
    https://mapsplatformtransition.withgoogle.com/calculator
    Pricing List:
    https://cloud.google.com/maps-platform/pricing/sheet/

2.引入库

package.json(示例):

"@types/googlemaps": "^3.38.1",

3.部分参考代码

import { Injectable, Inject, Optional, NgZone, OnDestroy, Component } from '@angular/core';
import { ReplaySubject } from 'rxjs';
import { MAP_CONFIG_TOKEN } from './config';
import { isMapsApiLoaded } from './util';
@Component({
  template: ''
})
export abstract class XXApiLoader implements OnDestroy {
  api$: ReplaySubject<any> = new ReplaySubject(1);

  abstract load();

  protected constructor(@Inject(Object)protected config) {
    this.config = this.config || {apiUrl: 'https://maps.google.com/maps/api/js'};
  }

  ngOnDestroy() {
    this.api$.complete();
  }
}

@Injectable()
export class XXAsyncCallbackApiLoader extends XXApiLoader {
  constructor(protected zone: NgZone, @Optional() @Inject(MAP_CONFIG_TOKEN) config, private service: XXService) {
    super(config);
  }

  load() {
    if (typeof window === 'undefined') {
      return;
    }

    if (isMapsApiLoaded()) {
      // @ts-ignore
      this.api$.next(google.maps);
    } else if (!document.querySelector('#map-api')) {
      (<any>window)['mapRef'] = (<any>window)['mapRef'] || [];
      // @ts-ignore
      (<any>window)['mapRef'].push({ zone: this.zone, componentFn: () => this.api$.next(google.maps)});
      this.addGoogleMapsApi();
    }
  }

  private addGoogleMapsApi() {
    (<any>window)['initMap'] = (<any>window)['initMap'] || function() {
      (<any>window)['mapRef'].forEach(mapRef => {
        mapRef.zone.run(function() { mapRef.componentFn(); });
      });
      (<any>window)['mapRef'].splice(0, (<any>window)['mapRef'].length);
    };

    const script = document.createElement( 'script' );
    script.id = 'map-api';

    // script.src = "https://maps.google.com/maps/api/js?callback=initMap";
    let apiUrl = this.service.getGoogleMapAPIKey() ? this.service.getGoogleMapAPIKey() : this.config.apiUrl ;
    apiUrl += apiUrl.indexOf('?') !== -1 ? '&' : '?';
    script.src = apiUrl + 'callback=initMap';
    document.querySelector('body').appendChild(script);
  }
}

@Injectable()
export class XXAsyncApiLoader extends XXApiLoader {
  constructor(@Optional() @Inject(NG_MAP_CONFIG_TOKEN) config, private iddService: FenceIDDService ) {
    super(config);
  }

  load() {
    if (typeof window === 'undefined') {
      return;
    }

    if (isMapsApiLoaded()) {
      // @ts-ignore
      this.api$.next(google.maps);
    } else if (!document.querySelector('#ngui-map-api')) {
      const script = document.createElement('script');
      script.id = 'ngui-map-api';

      script.async = true;
      // @ts-ignore
      script.onload = () => this.api$.next(google.maps);
      script.src = this.service.getGoogleMapAPIKey() ? this.service.getGoogleMapAPIKey() : this.config.apiUrl;
      document.querySelector('body').appendChild(script);
    }
  }
}
import {Injectable, SimpleChanges, NgZone, OnDestroy, AfterViewInit} from '@angular/core';
import { OptionBuilder } from './option-builder';
import { GeoCoder } from './geo-coder';

/**
 * collection of map instance-related properties and methods
 */
@Injectable()
export class XXMap implements OnDestroy, AfterViewInit {

  constructor(
    private geoCoder: GeoCoder,
    private optionBuilder: OptionBuilder,
    private zone: NgZone,
    private logger: LoggerService
  ) {}

  setObjectEvents(definedEvents: string[], thisObj: any, prefix: string) {
    definedEvents.forEach(definedEvent => {
      const eventName = this.getEventName(definedEvent),
        zone = this.zone;
      zone.runOutsideAngular(() => {
        thisObj[prefix].addListener(eventName, function(event) {
          const param: any = event ? event : {};
          param.target = this;
          zone.run(() => thisObj[definedEvent].emit(param));
        });
      });
    });
  }

  clearObjectEvents(definedEvents: string[], thisObj: any, prefix: string) {
    definedEvents.forEach(definedEvent => {
      const eventName = this.getEventName(definedEvent);
      this.zone.runOutsideAngular(() => {
        if (thisObj[prefix]) {
          // @ts-ignore
          google.maps.event.clearListeners(thisObj[prefix], eventName);
        }
      });
    });

    if (thisObj[prefix]) {
      if (thisObj[prefix].setMap) {
        thisObj[prefix].setMap(null);
      }

      delete thisObj[prefix].nguiMapComponent;
      delete thisObj[prefix];
    }

  }

  updateGoogleObject = (object: any, changes: SimpleChanges)  => {
    let val: any, currentValue: any, setMethodName: string;
    if (object) {
      for (const key in changes) {
        setMethodName = `set${key.replace(/^[a-z]/, x => x.toUpperCase()) }`;
        currentValue = changes[key].currentValue;
        if (['position', 'center'].indexOf(key) !== -1 && typeof currentValue === 'string') {
          // To preserve setMethod name in Observable callback, wrap it as a function, then execute
          // tslint:disable-next-line: no-shadowed-variable
          ((setMethodName) => {
            this.geoCoder.geocode({address: currentValue}).subscribe(results => {
              if (typeof object[setMethodName] === 'function') {
                object[setMethodName](results[0].geometry.location);
              } else {
                this.logger.error(
                  'Not all options are dynamically updatable according to Googles Maps API V3 documentation.\n' +
                  'Please check Google Maps API documentation, and use "setOptions" instead.'
                );
              }
            });
          })(setMethodName);
        } else {
          val =  this.optionBuilder.googlize(currentValue);
          if (typeof object[setMethodName] === 'function') {
            object[setMethodName](val);
          } else {
            this.logger.warn(
              'Not all options are dynamically updatable according to Googles Maps API V3 documentation.\n' +
              'Please check Google Maps API documentation, and use "setOptions" instead.'
            );
          }
        }
      }
    }
  }

  private getEventName(definedEvent) {
    return definedEvent
      .replace(/([A-Z])/g, ($1) => `_${$1.toLowerCase()}`) // positionChanged -> position_changed
      .replace(/^map_/, '');                               // map_click -> click  to avoid DOM conflicts
  }

  ngAfterViewInit(): void {
    this.logger.onInit(XXMap);
  }

  ngOnDestroy(): void {
    this.logger.onDestroy(XXMap);

  }
}

import {
  Component,
  ElementRef,
  ViewEncapsulation,
  EventEmitter,
  SimpleChanges,
  Output,
  NgZone,
  AfterViewInit, AfterViewChecked, OnChanges, OnDestroy
} from '@angular/core';

import { OptionBuilder } from '../services/option-builder';
import { NavigatorGeolocation } from '../services/navigator-geolocation';
import { GeoCoder } from '../services/geo-coder';
import { XXMap } from '../services/xx-map';
import { XXMapApiLoader } from '../services/api-loader';
import { InfoWindow } from './info-window';
import { Subject } from 'rxjs';
import { debounceTime, tap, first } from 'rxjs/operators';
import { toCamelCase } from '../services/util';

const INPUTS = [
  'backgroundColor', 'center', 'disableDefaultUI', 'disableDoubleClickZoom', 'draggable', 'draggableCursor',
  'draggingCursor', 'heading', 'keyboardShortcuts', 'mapMaker', 'mapTypeControl', 'mapTypeId', 'maxZoom', 'minZoom',
  'noClear', 'overviewMapControl', 'panControl', 'panControlOptions', 'rotateControl', 'scaleControl', 'scrollwheel',
  'streetView', 'styles', 'tilt', 'zoom', 'streetViewControl', 'zoomControl', 'zoomControlOptions', 'mapTypeControlOptions',
  'overviewMapControlOptions', 'rotateControlOptions', 'scaleControlOptions', 'streetViewControlOptions', 'fullscreenControl', 'fullscreenControlOptions',
  'options',
  // ngui-map-specific inputs
  'geoFallbackCenter'
];

const OUTPUTS = [
  'bounds_changed', 'center_changed', 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'heading_changed', 'idle',
  'maptypeid_changed', 'mousemove', 'mouseout', 'mouseover', 'projection_changed', 'resize', 'rightclick',
  'tilesloaded', 'tile_changed', 'zoom_changed',
  // to avoid DOM event conflicts
  'mapClick', 'mapMouseover', 'mapMouseout', 'mapMousemove', 'mapDrag', 'mapDragend', 'mapDragstart'
];

@Component({
  selector: 'ngui-map',
  providers: [XXMap, OptionBuilder, GeoCoder, NavigatorGeolocation],
  styles: [`
    xx-map {display: block; height: 400px;}
    .google-map {width: 100%; height: 100%}
  `],
  inputs: INPUTS,
  outputs: OUTPUTS,
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="google-map"></div>
    <ng-content></ng-content>
  `,
})
export class XXMapComponent implements OnChanges, OnDestroy, AfterViewInit, AfterViewChecked {
  @Output() public mapReady$: EventEmitter<any> = new EventEmitter();

  public el: HTMLElement;
  public map: google.maps.Map;
  public mapOptions: google.maps.MapOptions = {};

  public inputChanges$ = new Subject();

  // map objects by group
  public infoWindows: { [id: string]: InfoWindow } = { };

  // map has been fully initialized
  public mapIdledOnce: boolean = false;

  private initializeMapAfterDisplayed = false;
  private apiLoaderSub;

  constructor(
    public optionBuilder: OptionBuilder,
    public elementRef: ElementRef,
    public geolocation: NavigatorGeolocation,
    public geoCoder: GeoCoder,
    public xxMap: XXMap,
    public apiLoader: XXMapApiLoader,
    public zone: NgZone,
  ) {
    apiLoader.load();

    // all outputs needs to be initialized,
    // http://stackoverflow.com/questions/37765519/angular2-directive-cannot-read-property-subscribe-of-undefined-with-outputs
    OUTPUTS.forEach(output => this[output] = new EventEmitter());
  }

  ngAfterViewInit() {
    this.apiLoaderSub = this.apiLoader.api$
      .pipe(first())
      .subscribe(() => this.initializeMap());
  }

  ngAfterViewChecked() {
      if (this.initializeMapAfterDisplayed && this.el && this.el.offsetWidth > 0) {
        this.initializeMap();
      }
  }

  ngOnChanges(changes: SimpleChanges) {
    this.inputChanges$.next(changes);
  }

  initializeMap(): void {
    this.el = this.elementRef.nativeElement.querySelector('.google-map');
    if (this.el && this.el.offsetWidth === 0) {
        this.initializeMapAfterDisplayed = true;
        return;
    }

    this.initializeMapAfterDisplayed = false;
    this.mapOptions = this.optionBuilder.googlizeAllInputs(INPUTS, this);
    console.log('map mapOptions', this.mapOptions);

    this.mapOptions.zoom = this.mapOptions.zoom || 15;
    typeof this.mapOptions.center === 'string' && (delete this.mapOptions.center);

    this.zone.runOutsideAngular(() => {
      this.map = new google.maps.Map(this.el, this.mapOptions);
      this.map['mapObjectName'] = 'XXMapComponent';

      if (!this.mapOptions.center) { // if center is not given as lat/lng
        this.setCenter();
      }

      // set google events listeners and emits to this outputs listeners
      this.XXMap.setObjectEvents(OUTPUTS, this, 'map');

      this.map.addListener('idle', () => {
        if (!this.mapIdledOnce) {
          this.mapIdledOnce = true;
          setTimeout(() => {
            this.mapReady$.emit(this.map);
          });
        }
      });

      // update map when input changes
      this.inputChanges$.pipe(
        debounceTime(1000),
        tap((changes: SimpleChanges) => this.XXMap.updateGoogleObject(this.map, changes)),
      ).subscribe();

      if (typeof window !== 'undefined' && (<any>window)['mapRef']) {
        // expose map object for test and debugging on (<any>window)
        (<any>window)['mapRef'].map = this.map;
      }
    });
  }

  setCenter(): void {
    if (!this['center']) { // center is not from user. Thus, we set the current location
      this.geolocation.getCurrentPosition().subscribe(
        position => {
          console.log('setting map center from current location');
          let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
          this.map.setCenter(latLng);
        },
        error => {
          console.error('map: Error finding the current position');
          this.map.setCenter(this.mapOptions['geoFallbackCenter'] || new google.maps.LatLng(0, 0));
        }
      );
    }
    else if (typeof this['center'] === 'string') {
      this.geoCoder.geocode({address: this['center']}).subscribe(
        results => {
          console.log('setting map center from address', this['center']);
          this.map.setCenter(results[0].geometry.location);
        },
        error => {
          this.map.setCenter(this.mapOptions['geoFallbackCenter'] || new google.maps.LatLng(0, 0));
        });
    }
  }

  openInfoWindow(id: string, anchor: google.maps.MVCObject) {
    this.infoWindows[id].open(anchor);
  }

  closeInfoWindow(id: string) {
    // if infoWindow for id exists, close the infoWindow
    if (this.infoWindows[id])
      this.infoWindows[id].close();
  }

  ngOnDestroy() {
    this.inputChanges$.complete();
    if (this.el && !this.initializeMapAfterDisplayed) {
      this.nguiMap.clearObjectEvents(OUTPUTS, this, 'map');
    }
    if (this.apiLoaderSub) {
      this.apiLoaderSub.unsubscribe();
    }
  }

  // map.markers, map.circles, map.heatmapLayers.. etc
  addToMapObjectGroup(mapObjectName: string, mapObject: any) {
    let groupName = toCamelCase(mapObjectName.toLowerCase()) + 's'; // e.g. markers
    this.map[groupName] = this.map[groupName] || [];
    this.map[groupName].push(mapObject);
  }

  removeFromMapObjectGroup(mapObjectName: string, mapObject: any) {
    let groupName = toCamelCase(mapObjectName.toLowerCase()) + 's'; // e.g. markers
    if (this.map && this.map[groupName]) {
      let index = this.map[groupName].indexOf(mapObject);
      console.log('index', mapObject, index);
      (index > -1) && this.map[groupName].splice(index, 1);
    }
  }
}


总结

例如:以上就是今天要讲的内容,本文仅仅简单介绍了Angular2+ 上加入Googlemaps相关的业务功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值