PageRenderTime 74ms CodeModel.GetById 54ms app.highlight 17ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/google/maps/extras/markertracker/MarkerTracker.as

http://gmaps-utility-library-flash.googlecode.com/
ActionScript | 368 lines | 230 code | 58 blank | 80 comment | 32 complexity | 08cdba30a214078d586ef0c025fba726 MD5 | raw file
  1/** 
  2 * MarkerTracker v1.0
  3 * Author:	Michael Menzel
  4 * Email:	mugglmenzel@gmail.com
  5 * 
  6 * Copyright 2009 Michael Menzel
  7 * 
  8 * Licensed under the Apache License, Version 2.0 (the "License"); 
  9 * you may not use this file except in compliance with the License. 
 10 * You may obtain a copy of the License at
 11 * 
 12 *     http://www.apache.org/licenses/LICENSE-2.0
 13 *
 14 * Unless required by applicable law or agreed to in writing, software 
 15 * distributed under the License is distributed on an "AS IS" BASIS, 
 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 17 * See the License for the specific language governing permissions and 
 18 * limitations under the License.
 19 *
 20 */  
 21
 22
 23package com.google.maps.extras.markertracker
 24{
 25	import com.google.maps.LatLng;
 26	import com.google.maps.LatLngBounds;
 27	import com.google.maps.Map;
 28	import com.google.maps.MapMouseEvent;
 29	import com.google.maps.MapMoveEvent;
 30	import com.google.maps.overlays.Marker;
 31	import com.google.maps.overlays.Polyline;
 32	import com.google.maps.overlays.PolylineOptions;
 33	import com.google.maps.styles.StrokeStyle;
 34	
 35	import flash.display.DisplayObject;
 36	import flash.events.Event;
 37	import flash.geom.Point;
 38	
 39	import mx.utils.ObjectUtil;
 40	
 41	
 42	public class MarkerTracker
 43	{
 44
 45		public static const DEFAULT_PADDING:Number = 25;
 46		public static const DEFAULT_ICON_SCALE:Number = 0.6;
 47		public static const DEFAULT_ARROW_COLOR:Number = 0xff0000;
 48		public static const DEFAULT_ARROW_WEIGHT:Number = 20;
 49		public static const DEFAULT_ARROW_LENGTH:Number = 20;
 50		public static const DEFAULT_ARROW_OPACITY:Number = 0.8;
 51		public static const DEFAULT_UPDATE_EVENT:String = MapMoveEvent.MOVE_STEP;
 52		public static const DEFAULT_PAN_EVENT:String = MapMouseEvent.CLICK;
 53		public static const DEFAULT_QUICK_PAN_ENABLED:Boolean = true;
 54
 55		private var padding_:Number;
 56		private var iconScale_:Number;
 57		private var color_:Number;
 58		private var weight_:Number;
 59		private var length_:Number;
 60		private var opacity_:Number;
 61		private var updateEvent_:String;
 62		private var panEvent_:String;
 63		private var quickPanEnabled_:Boolean;
 64		
 65		private var babyMarker_:Marker;
 66		
 67		private var map_:Map;
 68		private var marker_:Marker;
 69		private var enabled_:Boolean = true;
 70		private var arrowDisplayed_:Boolean = false;
 71		private var arrow_:Polyline;
 72		private var oldArrow_:Polyline;
 73		private var control_:Object;
 74
 75		/**
 76		 * Creates a MarkerTracker for the given marker and displays it on the map as needed.
 77		 *
 78		 * @constructor
 79		 * @param {Marker} The marker to be tracked.
 80		 * @param {Map} The map that will display the MarkerTracker. 
 81		 * @param {MarkerTrackerOptions} Object that contains the options for customizing the 
 82		 *                  look and behavior of arrows:
 83		 */
 84
 85		public function MarkerTracker(marker:Marker, map:Map, opts:MarkerTrackerOptions)
 86		{
 87
 88			this.map_ = map;
 89			this.marker_ = marker;
 90			this.enabled_ = true;
 91			this.arrowDisplayed_ = false;
 92			this.arrow_ = null;
 93			this.oldArrow_ = null;
 94			this.control_ = null;
 95			
 96			  
 97			opts = opts || new MarkerTrackerOptions();
 98			this.iconScale_ = MarkerTracker.DEFAULT_ICON_SCALE;
 99			if (opts.iconScale) {
100				this.iconScale_ = opts.iconScale;
101			}
102			this.padding_ = MarkerTracker.DEFAULT_PADDING;
103			if (opts.padding) {
104				this.padding_ = opts.padding;
105			}
106			this.color_ = MarkerTracker.DEFAULT_ARROW_COLOR;
107			if (opts.arrowColor) {
108				this.color_ = opts.arrowColor;
109			}
110			this.weight_ = MarkerTracker.DEFAULT_ARROW_WEIGHT;
111			if (opts.arrowWeight) {
112				this.weight_ = opts.arrowWeight;
113			}
114			this.length_ = MarkerTracker.DEFAULT_ARROW_LENGTH;
115			if (opts.arrowLength) {
116				this.length_ = opts.arrowLength;
117			}
118			this.opacity_ = MarkerTracker.DEFAULT_ARROW_OPACITY;
119			if (opts.arrowOpacity) {
120				this.opacity_ = opts.arrowOpacity;
121			}
122			this.updateEvent_ = MarkerTracker.DEFAULT_UPDATE_EVENT;
123			if (opts.updateEvent) {
124				this.updateEvent_ = opts.updateEvent;
125			}
126			this.panEvent_ = MarkerTracker.DEFAULT_PAN_EVENT;
127			if (opts.panEvent) {
128				this.panEvent_ = opts.panEvent;
129			}
130			this.quickPanEnabled_ = MarkerTracker.DEFAULT_QUICK_PAN_ENABLED;
131			if (opts.quickPanEnabled) {
132				this.quickPanEnabled_ = opts.quickPanEnabled;
133			}
134			
135			this.babyMarker_ = new Marker(new LatLng(0, 0));
136			if(marker.getOptions().icon){
137				//replicate a different sized icon 
138				var babyIcon:DisplayObject = DisplayObject(ObjectUtil.copy(marker.getOptions().icon));
139				babyIcon.width = marker.getOptions().icon.width * this.iconScale_;
140				babyIcon.height = marker.getOptions().icon.height * this.iconScale_ ;
141				this.babyMarker_.getOptions().icon = babyIcon;				
142			} else {
143				this.babyMarker_.setOptions(marker.getOptions());
144			}
145			this.babyMarker_.getOptions().hasShadow = false;
146			this.babyMarker_.getOptions().clickable = true;
147			
148			//bind the update task to the event trigger
149			this.map_.addEventListener(this.updateEvent_, this.updateArrow_);
150			//update the arrow if the marker moves
151			this.marker_.addEventListener(Event.CHANGE, this.updateArrow_);
152			if (this.quickPanEnabled_) {
153				this.babyMarker_.addEventListener(this.panEvent_, this.panToMarker_);
154			}
155			
156			//do an inital check
157			this.updateArrow_(null);
158		}
159
160		/**
161		 * Destroys the marker tracker for successful garbage collecting.
162		 */
163		 public function destroy():void 
164		 {
165		 	this.disable();
166		 	
167		 	this.marker_.removeEventListener(Event.CHANGE, this.updateArrow_);
168		 	this.map_.removeEventListener(this.updateEvent_, this.updateArrow_);
169		 	
170		 	this.babyMarker_ = null;
171		 }
172
173		/**
174		 *  Disables the marker tracker.
175		 */
176		public function disable():void 
177		{
178			this.enabled_ = false;
179			this.updateArrow_(null);
180		}
181		
182		/**
183		 *  Enables the marker tracker.
184		 */
185		public function enable():void 
186		{
187			this.enabled_ = true;
188			this.updateArrow_(null);
189		}
190		
191		/**
192		 * Reactivates the marker tracker after being destroyed.
193		 */
194		 public function reactivate():void
195		 {
196			this.enabled_ = true;
197			this.arrowDisplayed_ = false;
198			this.arrow_ = null;
199			this.oldArrow_ = null;
200			this.control_ = null;
201
202			this.babyMarker_ = new Marker(new LatLng(0, 0));
203			if(this.marker_.getOptions().icon){
204				var babyIcon:DisplayObject = DisplayObject(ObjectUtil.copy(this.marker_.getOptions().icon));
205				babyIcon.width = this.marker_.getOptions().icon.width * this.iconScale_;
206				babyIcon.height = this.marker_.getOptions().icon.height * this.iconScale_ ;
207				this.babyMarker_.getOptions().icon = babyIcon;				
208			} else {
209				this.babyMarker_.setOptions(this.marker_.getOptions());
210			}
211			this.babyMarker_.getOptions().hasShadow = false;
212			this.babyMarker_.getOptions().clickable = true;
213			
214			this.map_.addEventListener(this.updateEvent_, this.updateArrow_);
215			this.marker_.addEventListener(Event.CHANGE, this.updateArrow_);
216			if (this.quickPanEnabled_) {
217				this.babyMarker_.addEventListener(this.panEvent_, this.panToMarker_);
218			}
219
220			this.updateArrow_(null);
221		 }
222
223
224		/**
225		 *  Called on the trigger event to update the arrow. Primary function is to
226		 *  check if the parent marker is in view, if not draw the tracking arrow.
227		 */
228		
229		private function updateArrow_(event:Event):void 
230		{
231			if(!this.map_.getLatLngBounds().containsLatLng(this.marker_.getLatLng()) && this.enabled_) {
232				this.drawArrow_();
233			} else if(this.arrowDisplayed_) {
234		    	this.hideArrow_();
235		  	}
236		}
237
238
239
240		/**
241		 *  Draws or redraws the arrow as needed, called when the parent marker is
242		 *  not with in the map view.
243		 */
244		
245		private function drawArrow_():void 
246		{
247		
248			//convert to pixels
249			var bounds:LatLngBounds = this.map_.getLatLngBounds();
250			var SW:Point = this.map_.fromLatLngToPoint(bounds.getSouthWest());
251			var NE:Point = this.map_.fromLatLngToPoint(bounds.getNorthEast());
252			
253			//include the padding while deciding on the arrow location
254			var minX:Number =  SW.x + this.padding_;
255			var minY:Number =  NE.y + this.padding_;
256			var maxX:Number =  NE.x - this.padding_;
257			var maxY:Number =  SW.y - this.padding_;
258			
259			// find the geometric info for the marker realative to the center of the map
260			var centerPoint:Point = this.map_.fromLatLngToPoint(this.map_.getCenter());
261			var locPoint:Point = this.map_.fromLatLngToPoint(this.marker_.getLatLng());
262			  
263			//get the slope of the line
264			var m:Number = (centerPoint.y-locPoint.y) / (centerPoint.x-locPoint.x);
265			var b:Number = (centerPoint.y - m*centerPoint.x);
266			  
267			// end the line within the bounds
268			var x:Number = maxX;
269			if ( locPoint.x < maxX && locPoint.x > minX ) {
270				x = locPoint.x;
271			} else if (centerPoint.x > locPoint.x) {
272				x = minX; 
273			}
274			
275			//calculate y and check boundaries again  
276			var y:Number = m * x + b;
277			if( y > maxY ) {
278				y = maxY;
279				x = (y - b)/m;
280			} else if(y < minY) {
281				y = minY;
282				x = (y - b) / m;
283			}
284			  
285			// get the proper angle of the arrow
286			var ang:Number = Math.atan(-m);
287			if(x > centerPoint.x) {
288				ang = ang + Math.PI; 
289			} 
290			  
291			// define the point of the arrow
292			var arrowLoc:LatLng = this.map_.fromPointToLatLng(new Point(x, y));
293			
294			// left side of marker is at -1,1
295			var arrowLeft:LatLng = 	this.map_.fromPointToLatLng(
296										this.getRotatedPoint_(((-1) * this.length_), this.length_, ang, x, y) );
297			            
298			// right side of marker is at -1,-1
299			var arrowRight:LatLng = this.map_.fromPointToLatLng( 
300			          					this.getRotatedPoint_(((-1)*this.length_), ((-1)*this.length_), ang, x, y));
301			
302			  
303			this.oldArrow_ = this.arrow_;
304			this.arrow_ = 	new Polyline([arrowLeft, arrowLoc, arrowRight], 
305								new PolylineOptions({strokeStyle: 
306			  						new StrokeStyle({color: this.color_, thickness: this.weight_, alpha: this.opacity_})}));
307			this.map_.addOverlay(this.arrow_);
308			  
309			// move the babyMarker to -1,0
310			this.babyMarker_.setLatLng(this.map_.fromPointToLatLng(this.getRotatedPoint_(((-2)*this.length_), 0, ang, x, y)));
311			          
312			if (!this.arrowDisplayed_) {
313				this.map_.addOverlay(this.babyMarker_);
314				this.arrowDisplayed_ = true;
315			}
316			if (this.oldArrow_) {
317				this.map_.removeOverlay(this.oldArrow_);
318			}			
319		}
320		
321		
322		
323		/**
324		 *  Hides the arrows.
325		 */
326		 
327		private function hideArrow_():void 
328		{		
329			this.map_.removeOverlay(this.babyMarker_);
330			if(this.arrow_) {
331				this.map_.removeOverlay(this.arrow_);
332			}
333			if(this.oldArrow_) {
334				this.map_.removeOverlay(this.oldArrow_);
335			}
336			this.arrowDisplayed_ = false;			
337		}
338		
339		
340		/**
341		 *  Pans the map to the parent marker.
342		 */
343		
344		private function panToMarker_(event:Event):void 
345		{
346			this.map_.panTo(this.marker_.getLatLng());
347		}
348
349		/**
350		 *  This applies a counter-clockwise rotation to any point.
351		 *  
352		 * @param {Number} x The x value of the point.
353		 * @param {Number} y The y value of the point.
354		 * @param {Number} ang The counter clockwise angle of rotation.
355		 * @param {Number} xoffset Adds a position offset to the x position.
356		 * @param {Number} yoffset Adds a position offset to the y position.
357		 * @return {Point} A rotated GPoint.
358		 */
359		
360		private function getRotatedPoint_(x:Number, y:Number, ang:Number, xoffset:Number, yoffset:Number):Point 
361		{
362			var newx:Number = y * Math.sin(ang) - x * Math.cos(ang) + xoffset;
363			var newy:Number = x * Math.sin(ang) + y * Math.cos(ang) + yoffset;
364			return new Point(newx, newy);
365		}
366
367	}
368}