author Christopher Cook
*  copyright import { Utils } from '../../../core/Utils' ; import { MappingAPI , IMapProvider , MapOptions , IMapManager } from '../interfaces/mapping' ; import { Events } from '../../../services/Events' ; import { Observable } from 'rxjs' ; import { Dictionary } from 'typescript-collections' ; import { GeoPosition , GeoLatLng , GeoBounds } from './../../../model/GeoPosition' ; import { Logging , LogLevel } from './../../Logging' ; import { environment } from '../../../../environments/environment' ; import { PlaceSearchResult } from '../../../model/AppModels' ; declare var mapkit ; module export class MapKitMapProvider implements IMapProvider { mapAPIType : MappingAPI ; mapReady : boolean ; providerError : string ; mapCanvasID : string ; private map : any ; private markerList : Dictionary < number , any > ; private polylinePath : any ; private mapkitUtils : MapKitUtils ; constructor constructor ( private events , private logging ) { this . events = events ; this . mapAPIType = MappingAPI . MAPKIT_JS ; this . mapReady = false ; this . markerList = new Dictionary < number , any > ( ) ; this . mapkitUtils = new MapKitUtils ( ) ; } initAPI ( ) { if ( mapkit ) { mapkit . init ( { authorizationCallback : function ( done ) { done ( environment . mapKitToken ) ; } } ) ; } } param mapcanvasID param mapConfig initMap ( mapCanvasID , mapConfig , parentMapManager ) { this . mapCanvasID = mapCanvasID ; let apiLoaded = true ; if ( typeof mapkit === 'undefined' ) { apiLoaded = false ; } else { apiLoaded = true ; } if ( apiLoaded ) { this . initAPI ( ) ; if ( this . map == null ) { let mapCanvas = document . getElementById ( mapCanvasID ) ; this . map = new mapkit . Map ( mapCanvasID ) ; this . map . annotationForCluster = function ( clusterAnnotation ) { if ( clusterAnnotation . clusteringIdentifier === "poi" ) { clusterAnnotation . title = "Charging Location" ; clusterAnnotation . subtitle = clusterAnnotation . memberAnnotations . reduce ( ( total , clusterAnnotation ) => total + clusterAnnotation . population , 0 ) ; } } ; this . mapReady = true ; mapkit . addEventListener ( "configuration-change" , ( event ) => { this . events . publish ( 'ocm:mapping:ready' ) ; } ) ; this . map . addEventListener ( "scroll-end" , ( event ) => { this . events . publish ( 'ocm:mapping:dragend' ) ; } ) ; this . map . addEventListener ( "zoom-end" , ( event ) => { this . events . publish ( 'ocm:mapping:zoom' ) ; } ) ; } } else { this . logging . log ( "Call to initMap before API is ready:" + MappingAPI [ this . mapAPIType ] , LogLevel . ERROR ) ; this . mapReady = false ; return false ; } } clearMarkers ( ) { if ( this . markerList != null ) { this . markerList . forEach ( ( key , marker ) => { try { this . map . removeAnnotation ( marker ) ; } catch { } } ) ; } this . markerList = new Dictionary < number , any > ( ) ; } param poiList param parentContext showPOIListOnMap ( poiList , parentContext ) { let clearMarkersOnRefresh = false ; let map = this . map ; let bounds = { } ; let markersAdded = 0 ; let mapProviderContext = this ; let isFirstResultSet = this . markerList . size ( ) == 0 ; if ( clearMarkersOnRefresh ) { this . clearMarkers ( ) ; } if ( poiList != null ) { let poiCount = poiList . length ; let newMarkers = [ ] ; for ( let i = 0 ; i < poiList . length ; i ++ ) { if ( poiList [ i ] . AddressInfo != null ) { if ( poiList [ i ] . AddressInfo . Latitude != null && poiList [ i ] . AddressInfo . Longitude != null ) { let poi = poiList [ i ] ; let addMarker = true ; if ( ! clearMarkersOnRefresh && this . markerList != null ) { if ( this . markerList . containsKey ( poi . ID ) ) { addMarker = false ; } } if ( addMarker ) { let iconURL = null ; let animation = null ; let shadow = null ; let markerImg = null ; iconURL = Utils . getIconForPOI ( poi ) ; let markerTooltip = "OCM-" + poi . ID + ": " + poi . AddressInfo . Title + ":" ; if ( poi . UsageType != null ) { markerTooltip += " " + poi . UsageType . Title ; } if ( poi . StatusType != null ) { markerTooltip += " " + poi . StatusType . Title ; } const coordinates = new mapkit . Coordinate ( poi . AddressInfo . Latitude , poi . AddressInfo . Longitude ) ; let marker = new mapkit . ImageAnnotation ( coordinates , { title : markerTooltip , size : { width : 34 , height : 50 } , url : { 1 : iconURL } , clusteringIdentifier : "poi" } ) ; marker . poi = poi ; marker . addEventListener ( 'select' , ( el ) => { const clickedPOI = el . target . poi ; this . events . publish ( 'ocm:poi:selected' , { poi : clickedPOI , poiId : clickedPOI . ID } ) ; } ) ; this . markerList . setValue ( poi . ID , marker ) ; newMarkers . push ( marker ) ; markersAdded ++ ; } } } } if ( newMarkers . length > 0 ) { this . map . addAnnotations ( newMarkers ) ; if ( isFirstResultSet ) { this . map . showItems ( newMarkers , { animate : true , padding : new mapkit . Padding ( 60 , 25 , 60 , 25 ) } ) ; } } this . logging . log ( markersAdded + " new map markers added out of a total " + this . markerList . size ( ) ) ; } } refreshMapLayout ( ) { if ( this . map != null ) { setTimeout ( ( ) => { this . logging . log ( "MapKit: refreshMapLayout" , LogLevel . VERBOSE ) ; } , 200 ) ; } } setMapCenter ( pos , zoomLevel ? ) { if ( this . map ) { this . map . setCenterAnimated ( new mapkit . Coordinate ( pos . coords . latitude , pos . coords . longitude ) , false ) ; if ( zoomLevel ) { this . setMapZoom ( zoomLevel ) ; } } } getMapCenter ( ) { let obs = new Observable < GeoPosition > ( observer => { if ( this . map != null ) { let pos = this . map . center ; if ( pos != null ) { observer . next ( new GeoPosition ( pos . latitude , pos . longitude ) ) ; observer . complete ( ) ; } } } ) ; return obs ; } setMapZoom ( zoom ) { const zoomLevel = Math . min ( zoom , 28 ) ; const delta = this . mapkitUtils . deltaFromZoomLevel ( this . map , this . map . center , Math . round ( zoomLevel ) ) ; const span = new mapkit . CoordinateSpan ( delta . latitudeDelta * 111 , delta . longitudeDelta * 111 ) ; const region = new mapkit . CoordinateRegion ( this . map . center , span ) ; this . map . setRegionAnimated ( region ) ; } getMapZoom ( ) { let obs = new Observable < number > ( observer => { this . logging . log ( "MapKit: getMapZoom not supported" , LogLevel . VERBOSE ) ; let zoom = this . map . _impl . zoomLevel ; observer . next ( zoom ) ; observer . complete ( ) ; } ) ; return obs ; } setMapType ( mapType ) { this . logging . log ( "MapKit: skipped setting Map Type :" + mapType ) ; } getMapBounds ( ) { let obs = new Observable < Array < GeoLatLng > > ( observer => { let bounds = new Array < GeoLatLng > ( ) ; let mapBounds = this . map . region . toBoundingRegion ( ) ; bounds . push ( new GeoLatLng ( mapBounds . southLatitude , mapBounds . westLongitude ) ) ; bounds . push ( new GeoLatLng ( mapBounds . northLatitude , mapBounds . eastLongitude ) ) ; observer . next ( bounds ) ; observer . complete ( ) ; } ) ; return obs ; } moveToMapBounds ( bounds ) { this . logging . log ( "MapKit: map bounds not implemented" ) ; } renderMap ( poiList , mapHeight , parentContext ) { document . getElementById ( this . mapCanvasID ) . style . height = mapHeight + "px" ; if ( this . mapReady ) { this . showPOIListOnMap ( poiList , parentContext ) ; } return true ; } renderPolyline ( polyline ) { this . clearPolyline ( ) ; } clearPolyline ( ) { if ( this . polylinePath != null ) { this . polylinePath . setMap ( null ) ; } } focusMap ( ) { } unfocusMap ( ) { } async placeSearch ( keyword ) { let searchService = new mapkit . Search ( { getsUserLocation : true } ) ; return new Promise < Array < PlaceSearchResult > > ( async ( resolve , reject ) => { let placeList = [ ] ; searchService . search ( keyword , async ( error , data ) => { if ( error ) { reject ( error ) ; } placeList = [ ] ; data . places . map ( ( place ) => { let placeResult = new PlaceSearchResult ( ) ; placeResult . Title = place . name ; placeResult . Address = place . formattedAddress ; placeResult . Type = "place" ; placeResult . Location = new GeoLatLng ( place . coordinate . latitude , place . coordinate . longitude ) ; placeList . push ( placeResult ) ; } ) ; resolve ( placeList ) ; } ) ; } ) ; } addPOILayer ( data ) { this . logging . log ( "Add POI Layer not implemented in this provider." ) ; } } class MapKitUtils { mercatorRadius = 85445659.44705395 ; mercatorOffset = 268435456 ; longitudeToPixelSpaceX ( longitude ) { return Math . round ( this . mercatorOffset + this . mercatorRadius * longitude * Math . PI / 180.0 ) ; } latitudeToPixelSpaceY ( latitude ) { return Math . round ( this . mercatorOffset - this . mercatorRadius * Math . log ( ( 1 + Math . sin ( latitude * Math . PI / 180.0 ) ) / ( 1 - Math . sin ( latitude * Math . PI / 180.0 ) ) ) / 2.0 ) ; } pixelSpaceXToLongitude ( pixelX ) { return ( ( Math . round ( pixelX ) - this . mercatorOffset ) / this . mercatorRadius ) * 180.0 / Math . PI ; } pixelSpaceYToLatitude ( pixelY ) { return ( Math . PI / 2.0 - 2.0 * Math . atan ( Math . exp ( ( Math . round ( pixelY ) - this . mercatorOffset ) / this . mercatorRadius ) ) ) * 180.0 / Math . PI ; } param { object } map param { object } centerCoordinates param { number } zoomLevel deltaFromZoomLevel ( map , centerCoordinates , zoomLevel ) { let centerPixelX = this . longitudeToPixelSpaceX ( centerCoordinates . longitude ) ; let centerPixelY = this . latitudeToPixelSpaceY ( centerCoordinates . latitude ) ; let zoomExponent = 20 - zoomLevel ; let zoomScale = Math . pow ( 2 , zoomExponent ) ; let mapSizeInPixels = map . visibleMapRect . size ; let scaledMapWidth = mapSizeInPixels . width * zoomScale ; let scaledMapHeight = mapSizeInPixels . height * zoomScale ; let topLeftPixelX = centerPixelX - ( scaledMapWidth / 2 ) ; let topLeftPixelY = centerPixelY - ( scaledMapHeight / 2 ) ; let minLng = this . pixelSpaceXToLongitude ( topLeftPixelX ) ; let maxLng = this . pixelSpaceXToLongitude ( topLeftPixelX + scaledMapWidth ) ; let longitudeDelta = maxLng - minLng ; let minLat = this . pixelSpaceYToLatitude ( topLeftPixelY ) ; let maxLat = this . pixelSpaceYToLatitude ( topLeftPixelY + scaledMapHeight ) ; let latitudeDelta = - 1 * ( maxLat - minLat ) ; return { latitudeDelta , longitudeDelta } ; } }