import * as _ from "lodash" ; import * as $ from "jquery" ; import { UserSettingsModel } from "@elevate/shared/models" ; import { AppResourcesModel } from "../models/app-resources.model" ; import { AbstractModifier } from "./abstract.modifier" ; import * as Cookies from "js-cookie" ; import MapOptions = google . maps . MapOptions ; export class GoogleMapsModifier extends AbstractModifier { private static ENABLED = false ; protected activityId : number ; protected appResources : AppResourcesModel ; protected userSettings : UserSettingsModel ; protected pathArray : number [ ] [ ] ; protected map : google . maps . Map ; constructor ( activityId , appResources , userSettings ) { super ( ) ; this . activityId = activityId ; this . appResources = appResources ; this . userSettings = userSettings ; } public modify ( ) { if ( this . isAnalysisSection ( ) ) { console . log ( "[GoogleMapsModifier] Skipping Analysis Section" ) ; return ; } if ( ! GoogleMapsModifier . ENABLED ) { this . placeNoGoogleMapsAvailableInfo ( ) ; return ; } this . getGoogleMapsApi ( ) ; this . googleMapsApiLoaded ( this . activityId ) ; } private placeNoGoogleMapsAvailableInfo ( ) { if ( ! $ ( "#map-canvas" ) || $ ( "#map-canvas" ) . is ( ":hidden" ) || $ ( "#showInGoogleMap" ) . length ) { return ; } const dismissKey = "elevate_dismiss_no_gmaps_anymore" ; const dismissKeyLink = dismissKey + "_link" ; if ( Cookies . get ( dismissKey ) ) { return ; } const info = "Google Maps feature provided by 'Elevate' extension is no longer available. This service is now too expensive to be displayed at the moment." ; const dismiss = "dismiss this message" ; $ ( "#map-canvas" ) . before ( "<div id='" + dismissKey + "' style='text-align: center;padding: 5px;'>" + info + " <a id='" + dismissKeyLink + "'>[" + dismiss + "]</a></div>" ) . each ( ( ) => { $ ( "#" + dismissKeyLink ) . click ( ( ) => { const date = new Date ( ) ; date . setFullYear ( date . getFullYear ( ) + 1 ) ; Cookies . set ( dismissKey , "true" , { expires : date } ) ; $ ( "#" + dismissKey ) . remove ( ) ; } ) ; } ) ; } protected googleMapsApiLoaded ( activityId ) { this . placeGoogleMapsButtons ( activityId ) ; $ ( "[data-menu=\"overview\"]" ) . click ( ( ) => { setTimeout ( ( ) => { this . placeGoogleMapsButtons ( activityId ) ; } ) ; } ) ; $ ( "[data-menu=\"segments\"]" ) . click ( ( ) => { setTimeout ( ( ) => { this . placeGoogleMapsButtons ( activityId ) ; } ) ; } ) ; return true ; } protected showWaitLoadingMessage ( ) { $ . fancybox ( "<div style=\"text-align: center; padding-top: 15px;\"><img src=\"" + this . appResources . loadingIcon + "\"/></div>" , { autoScale : true , closeBtn : false , } ) ; } protected placeGoogleMapsButtons ( activityId ) { this . placeMainGoogleMapButton ( activityId ) ; this . placeSegmentAreaGoogleMapButton ( activityId ) ; } protected placeMainGoogleMapButton ( activityId ) { if ( ! $ ( "#map-canvas" ) || $ ( "#map-canvas" ) . is ( ":hidden" ) || $ ( "#showInGoogleMap" ) . length ) { return ; } $ ( "#map-canvas" ) . before ( "<a class=\"button btn-block btn-primary\" id=\"showInGoogleMap\">View in Google Maps</a>" ) . each ( ( ) => { $ ( "#showInGoogleMap" ) . on ( "click" , ( ) => { this . showWaitLoadingMessage ( ) ; this . fetchPathFromStream ( activityId , ( pathArray ) => { this . pathArray = pathArray ; const effortId = this . getEffortId ( ) ; if ( effortId ) { this . fetchSegmentInfoAndDisplayWithGoogleMap ( this . pathArray , effortId ) ; } else { this . displayGoogleMapWithPath ( this . pathArray ) ; } } ) ; } ) ; } ) ; } protected placeSegmentAreaGoogleMapButton ( activityId ) { if ( ! Strava . Labs ) { return ; } const view = Strava . Labs . Activities . SegmentLeaderboardView ; if ( ! view ) { return ; } const functionRender = view . prototype . render ; const that = this ; view . prototype . render = function ( ) { const r = functionRender . apply ( this , Array . prototype . slice . call ( arguments ) ) ; if ( $ ( "#showSegInGoogleMap" ) . length ) { return ; } let anchor ; if ( $ ( ".effort-map" ) ) { anchor = $ ( ".effort-map" ) ; } else if ( $ ( "#map-canvas" ) ) { anchor = $ ( "#map-canvas" ) ; } else { anchor = null ; } if ( ! anchor ) { console . error ( "No anchor found to attach segment google map button" ) ; } anchor . before ( "<a class=\"button btn-block btn-primary\" id=\"showSegInGoogleMap\">View in Google Maps</a>" ) . each ( ( ) => { $ ( "#showSegInGoogleMap" ) . on ( "click" , ( ) => { that . showWaitLoadingMessage ( ) ; that . fetchPathFromStream ( activityId , ( pathArray ) => { that . pathArray = pathArray ; const effortId = that . getEffortId ( ) ; if ( effortId ) { that . fetchSegmentInfoAndDisplayWithGoogleMap ( that . pathArray , effortId ) ; } else { console . error ( "Cannot display map: effortId not given" ) ; } } ) ; } ) ; } ) ; return r ; } ; } public getEffortId ( ) { return parseInt ( window . location . pathname . split ( "/" ) [ 4 ] || window . location . hash . replace ( "#" , "" ) ) || null ; } public isAnalysisSection ( ) { return ! _ . isEmpty ( window . location . pathname . match ( "analysis" ) ) ; } protected fetchPathFromStream ( activityId , callback ) { const streamPathUrl = "/activities/" + activityId + "/streams?stream_types[]=latlng" ; $ . ajax ( { url : streamPathUrl , dataType : "json" , } ) . done ( ( jsonResponse ) => { callback ( jsonResponse . latlng ) ; } ) ; } protected fetchSegmentInfoFromEffortId ( effortId , callback ) { let segmentInfoResponse ; $ . ajax ( { url : "/segment_efforts/" + effortId , type : "GET" , beforeSend : ( xhr ) => { xhr . setRequestHeader ( "X-Requested-With" , "XMLHttpRequest" ) ; } , dataType : "json" , success : ( xhrResponseText ) => { segmentInfoResponse = xhrResponseText ; } , error : ( err ) => { console . error ( err ) ; } , } ) . then ( ( ) => { if ( ! segmentInfoResponse . start_index && segmentInfoResponse . end_index ) { console . error ( "No start_index end_index found for" ) ; } callback ( segmentInfoResponse ) ; } ) ; } protected fetchSegmentInfoAndDisplayWithGoogleMap ( pathArray , effortId ) { this . fetchSegmentInfoFromEffortId ( effortId , ( segmentInfoResponse ) => { this . displayGoogleMapWithPath ( pathArray , [ segmentInfoResponse . start_index , segmentInfoResponse . end_index ] ) ; } ) ; } protected displayGoogleMapWithPath ( mainPathArray , highlightFromTo ? ) { const mapSize = [ window . innerWidth * 0.950 , window . innerHeight * 0.875 , ] ; const html = "<div style=\"padding-bottom:10px; text-align:center;\"><div style=\"height:" + mapSize [ 1 ] + "px;width:" + mapSize [ 0 ] + "px;\" id=\"gmaps_canvas\"></div><a target=\"_blank\" href=\"" + this . appResources . settingsLink + "#/globalSettings?searchText=Google%20Maps\">Go to extension settings if you want to set specific layer OR disable google maps buttons</a></div>" ; $ . fancybox ( html , { autoScale : true , transitionIn : "fade" , transitionOut : "fade" , } ) ; if ( ! $ ( "#gmaps_canvas" ) . length ) { $ ( "#map-canvas" ) . before ( html ) . each ( ( ) => { this . applyToMap ( mainPathArray , highlightFromTo ) ; } ) ; } else { this . applyToMap ( mainPathArray , highlightFromTo ) ; } } protected applyToMap ( mainPathArray , highlightFromTo ) { let layerType ; switch ( this . userSettings . reviveGoogleMapsLayerType . toUpperCase ( ) ) { case "HYBRID" : layerType = google . maps . MapTypeId . HYBRID ; break ; case "ROADMAP" : layerType = google . maps . MapTypeId . ROADMAP ; break ; case "SATELLITE" : layerType = google . maps . MapTypeId . SATELLITE ; break ; case "TERRAIN" : layerType = google . maps . MapTypeId . TERRAIN ; break ; default : layerType = google . maps . MapTypeId . TERRAIN ; break ; } this . map = new google . maps . Map ( document . getElementById ( "gmaps_canvas" ) , < MapOptions > { mapTypeId : layerType , overviewMapControl : true , } ) ; const points = [ ] ; let bounds = new google . maps . LatLngBounds ( ) ; _ . forEach ( mainPathArray , ( position ) => { const point = new google . maps . LatLng ( position [ 0 ] , position [ 1 ] ) ; points . push ( point ) ; bounds . extend ( point ) ; } ) ; const mainPathPoly = new google . maps . Polyline ( { path : points , strokeColor : "#FF0000" , strokeOpacity : .7 , strokeWeight : 4 , } ) ; mainPathPoly . setMap ( this . map ) ; this . map . fitBounds ( bounds ) ; if ( highlightFromTo ) { const secondPathPoly = new google . maps . Polyline ( { path : points . slice ( highlightFromTo [ 0 ] , highlightFromTo [ 1 ] ) , strokeColor : "#105cb6" , strokeOpacity : 1 , strokeWeight : 4 , } ) ; bounds = new google . maps . LatLngBounds ( ) ; _ . forEach ( mainPathArray . slice ( highlightFromTo [ 0 ] , highlightFromTo [ 1 ] ) , ( position ) => { const p = new google . maps . LatLng ( position [ 0 ] , position [ 1 ] ) ; bounds . extend ( p ) ; } ) ; this . map . fitBounds ( bounds ) ; secondPathPoly . setMap ( this . map ) ; } } protected getGoogleMapsApi ( ) { const script_tag = document . createElement ( "script" ) ; script_tag . setAttribute ( "type" , "text/javascript" ) ; script_tag . setAttribute ( "src" , "https://maps.google.com/maps/api/js?sensor=false" ) ; ( document . getElementsByTagName ( "head" ) [ 0 ] || document . documentElement ) . appendChild ( script_tag ) ; } }