package substance.mapping.utils { /** * GhostTracker * * @author shaun.tinney@findsubstance.com; * @since 21.04.2009; * @desc tracks markers when they go out of the viewable map area; */ import flash.display.*; import flash.events.*; import flash.geom.*; import flash.net.*; import flash.system.*; import flash.text.*; import flash.ui.*; import flash.utils.*; import fl.motion.easing.*; import gs.TweenLite; import com.google.maps.*; import com.google.maps.overlays.*; import com.google.maps.interfaces.*; import com.google.maps.styles.StrokeStyle; import substance.mapping.MapConfig; import substance.mapping.markers.*; public class GhostTracker { //-------------------------------------- // CLASS CONSTANTS //-------------------------------------- public static const NAME : String = 'substance.mapping.utils.GhostTracker'; //-------------------------------------- // STATIC VARIABLES //-------------------------------------- //-------------------------------------- // CONSTRUCTOR //-------------------------------------- public function GhostTracker ( marker : Marker, map : IMap ) { _marker = marker; _map = map; _map.addEventListener( MapMoveEvent.MOVE_STEP, updateArrow ); _map.addEventListener( MapEvent.OVERLAY_MOVED, updateArrow ); _map.addEventListener( MapZoomEvent.ZOOM_CHANGED, updateArrow ); _map.addEventListener( Event.CHANGE, updateArrow ); _marker.addEventListener( Event.CHANGE, updateArrow ); _customMarkerIcon = _marker.getOptions().icon; _ghostMarker = new Marker( new LatLng( 0, 0 ) ); _ghostMarker.setOptions( new MarkerOptions( { icon : _customMarkerIcon.ghost, clickable : true, hasShadow : false } ) ); _ghostMarker.addEventListener( MapMouseEvent.CLICK, panToMarker ); _customMarkerIcon.ghost.addEventListener( MapMouseEvent.CLICK, panToMarker ); _map.addOverlay( _ghostMarker ); } //-------------------------------------- // INSTANCE VARIABLES //-------------------------------------- // private properties; private var _map : IMap; private var _marker : Marker; private var _customMarkerIcon : *; // settings; private var _enabled : Boolean = true; private var _arrowDisplayed : Boolean = false; private var _hideDelay : Number = 0; private var _padding : Number = MapConfig.EDGE_PADDING; // display objects; private var _arrow : Polyline; private var _ghostMarker : Marker; //-------------------------------------- // GETTER/SETTERS //-------------------------------------- //-------------------------------------- // PUBLIC METHODS //-------------------------------------- public function enable ( newMarker : Marker = null ) : void { if ( newMarker ) { _marker.removeEventListener( Event.CHANGE, updateArrow ); _marker = newMarker; _marker.addEventListener( Event.CHANGE, updateArrow ); _customMarkerIcon = _marker.getOptions().icon; _ghostMarker = new Marker( new LatLng( 0, 0 ) ); _ghostMarker.setOptions( new MarkerOptions( { icon : _customMarkerIcon.ghost, clickable : true, hasShadow : false } ) ); _ghostMarker.addEventListener( MapMouseEvent.CLICK, panToMarker ); _map.addOverlay( _ghostMarker ); } _hideDelay = 0; _enabled = true; updateArrow(); } public function disable ( delay : Number = 0 ) : void { _hideDelay = delay; _enabled = false; updateArrow(); } //-------------------------------------- // EVENT HANDLERS //-------------------------------------- //-------------------------------------- // PRIVATE & PROTECTED INSTANCE METHODS //-------------------------------------- private function updateArrow ( e : Event = null ) : void { var ll : LatLng = _marker.getLatLng(); var pt : Point = _map.fromLatLngToViewport( ll ); if ( _enabled && ( !_map.getLatLngBounds().containsLatLng( ll ) || pt.y < 30 ) && ( ll.lat() != 0 && ll.lng() != 0 ) ) { _customMarkerIcon.hide(); _customMarkerIcon.ghost.show(); drawArrow(); } else if ( _arrowDisplayed ) { if ( e != null ) _customMarkerIcon.show( 0.25 ); _customMarkerIcon.ghost.hide( _hideDelay ); hideArrow(); } } private function drawArrow () : void { if ( !_customMarkerIcon.ghost.showing ) { hideArrow(); return; } // convert _map bounds to pixels; var bounds : LatLngBounds = _map.getLatLngBounds(); var sw : Point = _map.fromLatLngToPoint( bounds.getSouthWest() ); var ne : Point = _map.fromLatLngToPoint( bounds.getNorthEast() ); // include padding while calculating arrow location; var minX : Number = sw.x + _padding; var minY : Number = ne.y + _padding; var maxX : Number = ne.x - _padding; var maxY : Number = sw.y - _padding; // find geometric info for _marker realative to center of _map; var centerPoint : Point = _map.fromLatLngToPoint( _map.getCenter() ); var locPoint : Point = _map.fromLatLngToPoint( _marker.getLatLng() ); // get slope of line; var m : Number = ( centerPoint.y - locPoint.y ) / ( centerPoint.x - locPoint.x ); var b : Number = ( centerPoint.y - m * centerPoint.x ); // end line within bounds; var x : Number = maxX; // calculate x and check boundaries; if ( locPoint.x < maxX && locPoint.x > minX ) x = locPoint.x; else if ( centerPoint.x > locPoint.x ) x = minX; // calculate y and check boundaries; var y : Number = m * x + b; var ang : Number = Math.atan( -m ); // adjust bounds; if ( y > maxY ) { y = maxY; x = ( y - b ) / m; } else if ( y < minY ) { y = minY; x = ( y - b ) / m; } // adjust angle; if ( x > centerPoint.x ) ang = ang + Math.PI; // init arrow options; var aColor : Number = 0x666666; var aLength : Number = 3; var aThickness : Number = 2; var aOpacity : Number = 0.8; var arrowLoc : LatLng = _map.fromPointToLatLng( new Point( x, y ) ); var arrowLeft : LatLng = _map.fromPointToLatLng( getPointAtRotation( ( ( -1 ) * aLength ), aLength, ang, x, y ) ); var arrowRight : LatLng = _map.fromPointToLatLng( getPointAtRotation( ( ( -1 ) * aLength ), ( ( -1 ) * aLength ), ang, x, y ) ); var aOpts : PolylineOptions = new PolylineOptions( { strokeStyle : new StrokeStyle( { color : aColor, thickness : aThickness, alpha : aOpacity } ) } ); // if values are numbers; if ( !isNaN( x ) && !isNaN( y ) ) { // clear current arrow; if ( _arrow ) _map.removeOverlay( _arrow ); // draw new arrow; _arrow = new Polyline( [ arrowLeft, arrowLoc, arrowRight ], aOpts ); // add arrow; _map.addOverlay( _arrow ); // reposition ghost marker; _ghostMarker.setLatLng( _map.fromPointToLatLng( getPointAtRotation( ( ( -2 ) * _padding ), 0, ang, x, y ) ) ); } // toggle displayed property; _arrowDisplayed = true; } private function hideArrow () : void { if ( _arrow ) _map.removeOverlay( _arrow ); _arrowDisplayed = false; } private function getPointAtRotation ( x : Number, y : Number, ang : Number, xoffset : Number, yoffset : Number ) : Point { return new Point( y * Math.sin( ang ) - x * Math.cos( ang ) + xoffset, x * Math.sin( ang ) + y * Math.cos( ang ) + yoffset ); } private function panToMarker ( e : Event ) : void { _customMarkerIcon.autoSelect(); } } }