Skip to content

Commit

Permalink
feat(*): Support KML Layer
Browse files Browse the repository at this point in the history
This introduces a new Directive called <sebm-google-map-kml-layer>

Closes #734
  • Loading branch information
sebholstein committed Dec 1, 2016
1 parent 1e20585 commit 4d2a5d5
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/core/core-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ModuleWithProviders, NgModule} from '@angular/core';

import {SebmGoogleMapKmlLayer} from './directives/google-map-kml-layer';
import {SebmGoogleMap} from './directives/google-map';
import {SebmGoogleMapCircle} from './directives/google-map-circle';
import {SebmGoogleMapInfoWindow} from './directives/google-map-info-window';
Expand All @@ -18,7 +19,7 @@ import {BROWSER_GLOBALS_PROVIDERS} from './utils/browser-globals';
export function coreDirectives() {
return [
SebmGoogleMap, SebmGoogleMapMarker, SebmGoogleMapInfoWindow, SebmGoogleMapCircle,
SebmGoogleMapPolygon, SebmGoogleMapPolyline, SebmGoogleMapPolylinePoint
SebmGoogleMapPolygon, SebmGoogleMapPolyline, SebmGoogleMapPolylinePoint, SebmGoogleMapKmlLayer
];
};

Expand Down
1 change: 1 addition & 0 deletions src/core/directives.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export {SebmGoogleMap} from './directives/google-map';
export {SebmGoogleMapCircle} from './directives/google-map-circle';
export {SebmGoogleMapInfoWindow} from './directives/google-map-info-window';
export {SebmGoogleMapKmlLayer} from './directives/google-map-kml-layer';
export {SebmGoogleMapMarker} from './directives/google-map-marker';
export {SebmGoogleMapPolygon} from './directives/google-map-polygon';
export {SebmGoogleMapPolyline} from './directives/google-map-polyline';
Expand Down
126 changes: 126 additions & 0 deletions src/core/directives/google-map-kml-layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {Directive, EventEmitter, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core';
import {Subscription} from 'rxjs/Subscription';

import {KmlMouseEvent} from './../services/google-maps-types';
import {KmlLayerManager} from './../services/managers/kml-layer-manager';

let layerId = 0;

@Directive({
selector: 'sebm-google-map-kml-layer',
inputs:
['clickable', 'preserveViewport', 'screenOverlays', 'suppressInfoWindows', 'url', 'zIndex'],
outputs: ['layerClick', 'defaultViewportChange', 'statusChange']
})
export class SebmGoogleMapKmlLayer implements OnInit, OnDestroy, OnChanges {
private _addedToManager: boolean = false;
private _id: string = (layerId++).toString();
private _subscriptions: Subscription[] = [];
private static _kmlLayerOptions: string[] =
['clickable', 'preserveViewport', 'screenOverlays', 'suppressInfoWindows', 'url', 'zIndex'];

/**
* If true, the layer receives mouse events. Default value is true.
*/
clickable: boolean = true;

/**
* By default, the input map is centered and zoomed to the bounding box of the contents of the
* layer.
* If this option is set to true, the viewport is left unchanged, unless the map's center and zoom
* were never set.
*/
preserveViewport: boolean = false;

/**
* Whether to render the screen overlays. Default true.
*/
screenOverlays: boolean = true;

/**
* Suppress the rendering of info windows when layer features are clicked.
*/
suppressInfoWindows: boolean = false;

/**
* The URL of the KML document to display.
*/
url: string = null;

/**
* The z-index of the layer.
*/
zIndex: number|null = null;

/**
* This event is fired when a feature in the layer is clicked.
*/
layerClick: EventEmitter<KmlMouseEvent> = new EventEmitter<KmlMouseEvent>();

/**
* This event is fired when the KML layers default viewport has changed.
*/
defaultViewportChange: EventEmitter<void> = new EventEmitter<void>();

/**
* This event is fired when the KML layer has finished loading.
* At this point it is safe to read the status property to determine if the layer loaded
* successfully.
*/
statusChange: EventEmitter<void> = new EventEmitter<void>();

constructor(private _manager: KmlLayerManager) {}

ngOnInit() {
if (this._addedToManager) {
return;
}
this._manager.addKmlLayer(this);
this._addedToManager = true;
this._addEventListeners();
}

ngOnChanges(changes: SimpleChanges) {
if (!this._addedToManager) {
return;
}
this._updatePolygonOptions(changes);
}

private _updatePolygonOptions(changes: SimpleChanges) {
const options = Object.keys(changes)
.filter(k => SebmGoogleMapKmlLayer._kmlLayerOptions.indexOf(k) !== -1)
.reduce((obj: any, k: string) => {
obj[k] = changes[k].currentValue;
return obj;
}, {});
if (Object.keys(options).length > 0) {
this._manager.setOptions(this, options);
}
}

private _addEventListeners() {
const listeners = [
{name: 'click', handler: (ev: KmlMouseEvent) => this.layerClick.emit(ev)},
{name: 'defaultviewport_changed', handler: () => this.defaultViewportChange.emit()},
{name: 'status_changed', handler: () => this.statusChange.emit()},
];
listeners.forEach((obj) => {
const os = this._manager.createEventObservable(obj.name, this).subscribe(obj.handler);
this._subscriptions.push(os);
});
}

/** @internal */
id(): string { return this._id; }

/** @internal */
toString(): string { return `SebmGoogleMapKmlLayer-${this._id.toString()}`; }

/** @internal */
ngOnDestroy() {
this._manager.deleteKmlLayer(this);
// unsubscribe all registered observable subscriptions
this._subscriptions.forEach(s => s.unsubscribe());
}
}
4 changes: 3 additions & 1 deletion src/core/directives/google-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {MarkerManager} from '../services/managers/marker-manager';
import {PolygonManager} from '../services/managers/polygon-manager';
import {PolylineManager} from '../services/managers/polyline-manager';

import {KmlLayerManager} from './../services/managers/kml-layer-manager';

/**
* SebMGoogleMap renders a Google Map.
* **Important note**: To be able see a map in the browser, you have to define a height for the CSS
Expand Down Expand Up @@ -40,7 +42,7 @@ import {PolylineManager} from '../services/managers/polyline-manager';
selector: 'sebm-google-map',
providers: [
GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, PolylineManager,
PolygonManager
PolygonManager, KmlLayerManager
],
inputs: [
'longitude', 'latitude', 'zoom', 'draggable: mapDraggable', 'disableDoubleClickZoom',
Expand Down
2 changes: 1 addition & 1 deletion src/core/map-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {LatLngLiteral} from './services/google-maps-types';

// exported map types
export {LatLngBounds, LatLngBoundsLiteral, LatLngLiteral, PolyMouseEvent} from './services/google-maps-types';
export {KmlMouseEvent, LatLngBounds, LatLngBoundsLiteral, LatLngLiteral, PolyMouseEvent} from './services/google-maps-types';

/**
* MouseEvent gets emitted when the user triggers mouse events on the map.
Expand Down
1 change: 1 addition & 0 deletions src/core/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {InfoWindowManager} from './services/managers/info-window-manager';
export {MarkerManager} from './services/managers/marker-manager';
export {PolygonManager} from './services/managers/polygon-manager';
export {PolylineManager} from './services/managers/polyline-manager';
export {KmlLayerManager} from './services/managers/kml-layer-manager';
export {GoogleMapsScriptProtocol, LAZY_MAPS_API_CONFIG, LazyMapsAPILoader, LazyMapsAPILoaderConfigLiteral} from './services/maps-api-loader/lazy-maps-api-loader';
export {MapsAPILoader} from './services/maps-api-loader/maps-api-loader';
export {NoOpMapsAPILoader} from './services/maps-api-loader/noop-maps-api-loader';
61 changes: 61 additions & 0 deletions src/core/services/google-maps-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,64 @@ export interface Polygon extends MVCObject {
setPaths(paths: Array<Array<LatLng|LatLngLiteral>>|Array<LatLng|LatLngLiteral>): void;
setVisible(visible: boolean): void;
}

export interface KmlLayer extends MVCObject {
getDefaultViewport(): LatLngBounds;
getMap(): GoogleMap;
getMetadata(): KmlLayerMetadata;
getStatus(): KmlLayerStatus;
getUrl(): string;
getZIndex(): number;
setMap(map: GoogleMap): void;
setOptions(options: KmlLayerOptions): void;
setUrl(url: string): void;
setZIndex(zIndex: number): void;
}

/**
* See: https://developers.google.com/maps/documentation/javascript/reference?hl=de#KmlLayerStatus
*/
export type KmlLayerStatus = 'DOCUMENT_NOT_FOUND' |
'DOCUMENT_TOO_LARGE' | 'FETCH_ERROR' | 'INVALID_DOCUMENT' | 'INVALID_REQUEST' |
'LIMITS_EXCEEDED' | 'OK' | 'TIMED_OUT' | 'UNKNOWN';

/**
* See: https://developers.google.com/maps/documentation/javascript/reference?hl=de#KmlLayerMetadata
*/
export interface KmlLayerMetadata {
author: KmlAuthor;
description: string;
hasScreenOverlays: boolean;
name: string;
snippet: string;
}

export interface KmlAuthor {
email: string;
name: string;
uri: string;
}

export interface KmlLayerOptions {
clickable?: boolean;
map?: GoogleMap;
preserveViewport?: boolean;
screenOverlays?: boolean;
suppressInfoWindows?: boolean;
url?: string;
zIndex?: number;
}

export interface KmlFeatureData {
author: KmlAuthor;
description: string;
id: string;
infoWindowHtml: string;
name: string;
snippet: string;
}

export interface KmlMouseEvent extends MouseEvent {
featureData: KmlFeatureData;
pixelOffset: Size;
}
60 changes: 60 additions & 0 deletions src/core/services/managers/kml-layer-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {Injectable, NgZone} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';

import {SebmGoogleMapKmlLayer} from './../../directives/google-map-kml-layer';
import {GoogleMapsAPIWrapper} from './../google-maps-api-wrapper';
import {KmlLayer, KmlLayerOptions} from './../google-maps-types';

declare var google: any;

/**
* Manages all KML Layers for a Google Map instance.
*/
@Injectable()
export class KmlLayerManager {
private _layers: Map<SebmGoogleMapKmlLayer, Promise<KmlLayer>> =
new Map<SebmGoogleMapKmlLayer, Promise<KmlLayer>>();

constructor(private _wrapper: GoogleMapsAPIWrapper, private _zone: NgZone) {}

/**
* Adds a new KML Layer to the map.
*/
addKmlLayer(layer: SebmGoogleMapKmlLayer) {
const newLayer = this._wrapper.getNativeMap().then(m => {
return new google.maps.KmlLayer(<KmlLayerOptions>{
clickable: layer.clickable,
map: m,
preserveViewport: layer.preserveViewport,
screenOverlays: layer.screenOverlays,
suppressInfoWindows: layer.suppressInfoWindows,
url: layer.url,
zIndex: layer.zIndex
});
});
this._layers.set(layer, newLayer);
}

setOptions(layer: SebmGoogleMapKmlLayer, options: KmlLayerOptions) {
this._layers.get(layer).then(l => l.setOptions(options));
}

deleteKmlLayer(layer: SebmGoogleMapKmlLayer) {
this._layers.get(layer).then(l => {
l.setMap(null);
this._layers.delete(layer);
});
}

/**
* Creates a Google Maps event listener for the given KmlLayer as an Observable
*/
createEventObservable<T>(eventName: string, layer: SebmGoogleMapKmlLayer): Observable<T> {
return Observable.create((observer: Observer<T>) => {
this._layers.get(layer).then((m: KmlLayer) => {
m.addListener(eventName, (e: T) => this._zone.run(() => observer.next(e)));
});
});
}
}

0 comments on commit 4d2a5d5

Please sign in to comment.