infusion-code/angular-maps

View on GitHub
src/services/bing/bing-marker.service.ts

Summary

Maintainability
F
4 days
Test Coverage
import { Injectable, NgZone } from '@angular/core';
import { Observable, Observer, Subject } from 'rxjs';
import { ILatLong } from '../../interfaces/ilatlong';
import { IMarkerOptions } from '../../interfaces/imarker-options';
import { IMarkerIconInfo } from '../../interfaces/imarker-icon-info';
import { IPoint } from '../../interfaces/ipoint';
import { MapMarkerDirective } from '../../components/map-marker';
import { MarkerService } from '../../services/marker.service';
import { MapService } from '../../services/map.service';
import { LayerService } from '../../services/layer.service';
import { ClusterService } from '../../services/cluster.service';
import { Marker } from '../../models/marker';
import { BingMapService } from './bing-map.service';
import { BingConversions } from './bing-conversions';

/**
 * Concrete implementation of the MarkerService abstract class for Bing Maps V8.
 *
 * @export
 */
@Injectable()
export class BingMarkerService implements MarkerService {

    ///
    /// Field declarations
    ///
    private _markers: Map<MapMarkerDirective, Promise<Marker>> = new Map<MapMarkerDirective, Promise<Marker>>();

    ///
    /// Constructor
    ///

    /**
     * Creates an instance of BingMarkerService.
     * @param _mapService - {@link MapService} instance. The concrete {@link BingMapService} implementation is expected.
     * @param _layerService - {@link LayerService} instance.
     * The concrete {@link BingLayerService} implementation is expected.
     * @param _clusterService - {@link ClusterService} instance.
     * The concrete {@link BingClusterService} implementation is expected.
     * @param _zone - NgZone instance to support zone aware promises.
     *
     * @memberof BingMarkerService
     */
    constructor(private _mapService: MapService,
                private _layerService: LayerService,
                private _clusterService: ClusterService,
                private _zone: NgZone) {
    }

    ///
    /// Public members and MarkerService implementation
    ///

    /**
     * Adds a marker. Depending on the marker context, the marker will either by added to the map or a correcsponding layer.
     *
     * @param marker - The {@link MapMarkerDirective} to be added.
     *
     * @memberof BingMarkerService
     */
    public AddMarker(marker: MapMarkerDirective): void {
        const o: IMarkerOptions = {
            position: { latitude: marker.Latitude, longitude: marker.Longitude },
            title: marker.Title,
            label: marker.Label,
            draggable: marker.Draggable,
            icon: marker.IconUrl,
            iconInfo: marker.IconInfo,
            isFirst: marker.IsFirstInSet,
            isLast: marker.IsLastInSet
        };
        if (marker.Width) { o.width = marker.Width; }
        if (marker.Height) { o.height = marker.Height; }
        if (marker.Anchor) { o.anchor = marker.Anchor; }
        if (marker.Metadata) { o.metadata = marker.Metadata; }

        // create marker via promise.
        let markerPromise: Promise<Marker> = null;
        if (marker.InClusterLayer) {
            markerPromise = this._clusterService.CreateMarker(marker.LayerId, o);
        }
        else if (marker.InCustomLayer) {
            markerPromise = this._layerService.CreateMarker(marker.LayerId, o);
        }
        else {
            markerPromise = this._mapService.CreateMarker(o);
        }

        this._markers.set(marker, markerPromise);
        if (marker.IconInfo) {
            markerPromise.then((m: Marker) => {
                // update iconInfo to provide hook to do post icon creation activities and
                // also re-anchor the marker
                marker.DynamicMarkerCreated.emit(o.iconInfo);
                const p: IPoint = {
                    x: (o.iconInfo.size && o.iconInfo.markerOffsetRatio) ? (o.iconInfo.size.width * o.iconInfo.markerOffsetRatio.x) : 0,
                    y: (o.iconInfo.size && o.iconInfo.markerOffsetRatio) ? (o.iconInfo.size.height * o.iconInfo.markerOffsetRatio.y) : 0,
                };
                m.SetAnchor(p);
            });
        }
    }

    /**
     * Registers an event delegate for a marker.
     *
     * @param eventName - The name of the event to register (e.g. 'click')
     * @param marker - The {@link MapMarker} for which to register the event.
     * @returns - Observable emiting an instance of T each time the event occurs.
     *
     * @memberof BingMarkerService
     */
    public CreateEventObservable<T>(eventName: string, marker: MapMarkerDirective): Observable<T> {
        const b: Subject<T> = new Subject<T>();
        if (eventName === 'mousemove') {
            return b.asObservable();
        }
        if (eventName === 'rightclick') {
            return b.asObservable();
        }
        ///
        /// mousemove and rightclick are not supported by bing polygons.
        ///


        return Observable.create((observer: Observer<T>) => {
            this._markers.get(marker).then((m: Marker) => {
                m.AddListener(eventName, (e: T) => this._zone.run(() =>
                    observer.next(e)));
            });
        });
    }

    /**
     * Deletes a marker.
     *
     * @param marker - {@link MapMarker} to be deleted.
     * @returns - A promise fullfilled once the marker has been deleted.
     *
     * @memberof BingMarkerService
     */
    public DeleteMarker(marker: MapMarkerDirective): Promise<void> {
        const m = this._markers.get(marker);
        let p: Promise<void> = Promise.resolve();
        if (m != null) {
            p = m.then((ma: Marker) => {
                if (marker.InClusterLayer) {
                    this._clusterService.GetNativeLayer(marker.LayerId).then(l => { l.RemoveEntity(ma); });
                }
                if (marker.InCustomLayer) {
                    this._layerService.GetNativeLayer(marker.LayerId).then(l => { l.RemoveEntity(ma); });
                }
                return this._zone.run(() => {
                    ma.DeleteMarker();
                    this._markers.delete(marker);
                });
            });
        }
        return p;
    }

    /**
     * Obtains geo coordinates for the marker on the click location
     *
     * @param e - The mouse event.
     * @returns - {@link ILatLong} containing the geo coordinates of the clicked marker.
     *
     * @memberof BingMarkerService
     */
    public GetCoordinatesFromClick(e: MouseEvent | any): ILatLong {
        if (!e) {
            return null;
        }
        if (!e.primitive) {
            return null;
        }
        if (!(e.primitive instanceof Microsoft.Maps.Pushpin)) {
            return null;
        }
        const p: Microsoft.Maps.Pushpin = e.primitive;
        const loc: Microsoft.Maps.Location = p.getLocation();
        return { latitude: loc.latitude, longitude: loc.longitude };
    }

    /**
     * Obtains the marker model for the marker allowing access to native implementation functionatiliy.
     *
     * @param marker - The {@link MapMarker} for which to obtain the marker model.
     * @returns - A promise that when fullfilled contains the {@link Marker} implementation of the underlying platform.
     *
     * @memberof BingMarkerService
     */
    public GetNativeMarker(marker: MapMarkerDirective): Promise<Marker> {
        return this._markers.get(marker);
    }

    /**
     * Obtains the marker pixel location for the marker on the click location
     *
     * @param e - The mouse event.
     * @returns - {@link ILatLong} containing the pixels of the marker on the map canvas.
     *
     * @memberof BingMarkerService
     */
    public GetPixelsFromClick(e: MouseEvent | any): IPoint {
        const loc: ILatLong = this.GetCoordinatesFromClick(e);
        if (loc == null) {
            return null;
        }
        const l: Microsoft.Maps.Location = BingConversions.TranslateLocation(loc);
        const p: Microsoft.Maps.Point = <Microsoft.Maps.Point>(<BingMapService>
            this._mapService).MapInstance.tryLocationToPixel(l, Microsoft.Maps.PixelReference.control);
        if (p == null) { return null; }
        return { x: p.x, y: p.y };
    }

    /**
     * Converts a geo location to a pixel location relative to the map canvas.
     *
     * @param target - Either a {@link MapMarker} or a {@link ILatLong} for the basis of translation.
     * @returns - A promise that when fullfilled contains a {@link IPoint}
     * with the pixel coordinates of the MapMarker or ILatLong relative to the map canvas.
     *
     * @memberof BingMarkerService
     */
    public LocationToPoint(target: MapMarkerDirective | ILatLong): Promise<IPoint> {
        if (target == null) {
            return Promise.resolve(null);
        }
        if (target instanceof MapMarkerDirective) {
            return this._markers.get(target).then((m: Marker) => {
                const l: ILatLong = m.Location;
                const p: Promise<IPoint> = this._mapService.LocationToPoint(l);
                return p;
            });
        }
        return this._mapService.LocationToPoint(target);
    }

    /**
     * Updates the anchor position for the marker.
     *
     * @param - The {@link MapMarker} object for which to upate the anchor.
     * Anchor information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the anchor position has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateAnchor(marker: MapMarkerDirective): Promise<void> {
        return this._markers.get(marker).then((m: Marker) => {
            m.SetAnchor(marker.Anchor);
        });
    }

    /**
     * Updates whether the marker is draggable.
     *
     * @param - The {@link MapMarker} object for which to upate dragability.
     * Dragability information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the marker has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateDraggable(marker: MapMarkerDirective): Promise<void> {
        return this._markers.get(marker).then((m: Marker) => m.SetDraggable(marker.Draggable));
    }

    /**
     * Updates the Icon on the marker.
     *
     * @param - The {@link MapMarker} object for which to upate the icon.
     * Icon information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the icon information has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateIcon(marker: MapMarkerDirective): Promise<void> {
        const payload = (m: Marker, icon: string, iconInfo: IMarkerIconInfo) => {
            if (icon && icon !== '') {
                m.SetIcon(icon);
                marker.DynamicMarkerCreated.emit(iconInfo);
            }
        };
        return this._markers.get(marker).then((m: Marker) => {
            if (marker.IconInfo) {
                const s = Marker.CreateMarker(marker.IconInfo);
                if (typeof(s) === 'string') { return(payload(m, s, marker.IconInfo)); }
                else {
                    return s.then(x => {
                        return(payload(m, x.icon, x.iconInfo));
                    });
                }
            }
            else {
                return(m.SetIcon(marker.IconUrl));
            }
        });
    }

    /**
     * Updates the label on the marker.
     *
     * @param - The {@link MapMarkerDirective} object for which to upate the label.
     * Label information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the label has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateLabel(marker: MapMarkerDirective): Promise<void> {
        return this._markers.get(marker).then((m: Marker) => { m.SetLabel(marker.Label); });
    }

    /**
     * Updates the geo coordinates for the marker.
     *
     * @param - The {@link MapMarkerDirective} object for which to upate the coordinates.
     * Coordinate information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the position has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateMarkerPosition(marker: MapMarkerDirective): Promise<void> {
        return this._markers.get(marker).then(
            (m: Marker) => m.SetPosition({
                latitude: marker.Latitude,
                longitude: marker.Longitude
            }));
    }

    /**
     * Updates the title on the marker.
     *
     * @param - The {@link MapMarkerDirective} object for which to upate the title.
     * Title information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the title has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateTitle(marker: MapMarkerDirective): Promise<void> {
        return this._markers.get(marker).then((m: Marker) => m.SetTitle(marker.Title));
    }

    /**
     * Updates the visibility on the marker.
     *
     * @param - The {@link MapMarkerDirective} object for which to upate the visiblity.
     * Visibility information is present in the underlying {@link Marker} model object.
     * @returns - A promise that is fullfilled when the visibility has been updated.
     *
     * @memberof BingMarkerService
     */
    public UpdateVisible(marker: MapMarkerDirective): Promise<void> {
        return this._markers.get(marker).then((m: Marker) => m.SetVisible(marker.Visible));
    }
}