Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(*): Support KML Layer #748

Merged
merged 1 commit into from
Dec 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)));
});
});
}
}