/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. package com.google.maps.extras.markertracker
  22. {
  23. import com.google.maps.LatLng;
  24. import com.google.maps.LatLngBounds;
  25. import com.google.maps.Map;
  26. import com.google.maps.MapMouseEvent;
  27. import com.google.maps.MapMoveEvent;
  28. import com.google.maps.overlays.Marker;
  29. import com.google.maps.overlays.Polyline;
  30. import com.google.maps.overlays.PolylineOptions;
  31. import com.google.maps.styles.StrokeStyle;
  32. import flash.display.DisplayObject;
  33. import flash.events.Event;
  34. import flash.geom.Point;
  35. import mx.utils.ObjectUtil;
  36. public class MarkerTracker
  37. {
  38. public static const DEFAULT_PADDING:Number = 25;
  39. public static const DEFAULT_ICON_SCALE:Number = 0.6;
  40. public static const DEFAULT_ARROW_COLOR:Number = 0xff0000;
  41. public static const DEFAULT_ARROW_WEIGHT:Number = 20;
  42. public static const DEFAULT_ARROW_LENGTH:Number = 20;
  43. public static const DEFAULT_ARROW_OPACITY:Number = 0.8;
  44. public static const DEFAULT_UPDATE_EVENT:String = MapMoveEvent.MOVE_STEP;
  45. public static const DEFAULT_PAN_EVENT:String = MapMouseEvent.CLICK;
  46. public static const DEFAULT_QUICK_PAN_ENABLED:Boolean = true;
  47. private var padding_:Number;
  48. private var iconScale_:Number;
  49. private var color_:Number;
  50. private var weight_:Number;
  51. private var length_:Number;
  52. private var opacity_:Number;
  53. private var updateEvent_:String;
  54. private var panEvent_:String;
  55. private var quickPanEnabled_:Boolean;
  56. private var babyMarker_:Marker;
  57. private var map_:Map;
  58. private var marker_:Marker;
  59. private var enabled_:Boolean = true;
  60. private var arrowDisplayed_:Boolean = false;
  61. private var arrow_:Polyline;
  62. private var oldArrow_:Polyline;
  63. private var control_:Object;
  64. /**
  65. * Creates a MarkerTracker for the given marker and displays it on the map as needed.
  66. *
  67. * @constructor
  68. * @param {Marker} The marker to be tracked.
  69. * @param {Map} The map that will display the MarkerTracker.
  70. * @param {MarkerTrackerOptions} Object that contains the options for customizing the
  71. * look and behavior of arrows:
  72. */
  73. public function MarkerTracker(marker:Marker, map:Map, opts:MarkerTrackerOptions)
  74. {
  75. this.map_ = map;
  76. this.marker_ = marker;
  77. this.enabled_ = true;
  78. this.arrowDisplayed_ = false;
  79. this.arrow_ = null;
  80. this.oldArrow_ = null;
  81. this.control_ = null;
  82. opts = opts || new MarkerTrackerOptions();
  83. this.iconScale_ = MarkerTracker.DEFAULT_ICON_SCALE;
  84. if (opts.iconScale) {
  85. this.iconScale_ = opts.iconScale;
  86. }
  87. this.padding_ = MarkerTracker.DEFAULT_PADDING;
  88. if (opts.padding) {
  89. this.padding_ = opts.padding;
  90. }
  91. this.color_ = MarkerTracker.DEFAULT_ARROW_COLOR;
  92. if (opts.arrowColor) {
  93. this.color_ = opts.arrowColor;
  94. }
  95. this.weight_ = MarkerTracker.DEFAULT_ARROW_WEIGHT;
  96. if (opts.arrowWeight) {
  97. this.weight_ = opts.arrowWeight;
  98. }
  99. this.length_ = MarkerTracker.DEFAULT_ARROW_LENGTH;
  100. if (opts.arrowLength) {
  101. this.length_ = opts.arrowLength;
  102. }
  103. this.opacity_ = MarkerTracker.DEFAULT_ARROW_OPACITY;
  104. if (opts.arrowOpacity) {
  105. this.opacity_ = opts.arrowOpacity;
  106. }
  107. this.updateEvent_ = MarkerTracker.DEFAULT_UPDATE_EVENT;
  108. if (opts.updateEvent) {
  109. this.updateEvent_ = opts.updateEvent;
  110. }
  111. this.panEvent_ = MarkerTracker.DEFAULT_PAN_EVENT;
  112. if (opts.panEvent) {
  113. this.panEvent_ = opts.panEvent;
  114. }
  115. this.quickPanEnabled_ = MarkerTracker.DEFAULT_QUICK_PAN_ENABLED;
  116. if (opts.quickPanEnabled) {
  117. this.quickPanEnabled_ = opts.quickPanEnabled;
  118. }
  119. this.babyMarker_ = new Marker(new LatLng(0, 0));
  120. if(marker.getOptions().icon){
  121. //replicate a different sized icon
  122. var babyIcon:DisplayObject = DisplayObject(ObjectUtil.copy(marker.getOptions().icon));
  123. babyIcon.width = marker.getOptions().icon.width * this.iconScale_;
  124. babyIcon.height = marker.getOptions().icon.height * this.iconScale_ ;
  125. this.babyMarker_.getOptions().icon = babyIcon;
  126. } else {
  127. this.babyMarker_.setOptions(marker.getOptions());
  128. }
  129. this.babyMarker_.getOptions().hasShadow = false;
  130. this.babyMarker_.getOptions().clickable = true;
  131. //bind the update task to the event trigger
  132. this.map_.addEventListener(this.updateEvent_, this.updateArrow_);
  133. //update the arrow if the marker moves
  134. this.marker_.addEventListener(Event.CHANGE, this.updateArrow_);
  135. if (this.quickPanEnabled_) {
  136. this.babyMarker_.addEventListener(this.panEvent_, this.panToMarker_);
  137. }
  138. //do an inital check
  139. this.updateArrow_(null);
  140. }
  141. /**
  142. * Destroys the marker tracker for successful garbage collecting.
  143. */
  144. public function destroy():void
  145. {
  146. this.disable();
  147. this.marker_.removeEventListener(Event.CHANGE, this.updateArrow_);
  148. this.map_.removeEventListener(this.updateEvent_, this.updateArrow_);
  149. this.babyMarker_ = null;
  150. }
  151. /**
  152. * Disables the marker tracker.
  153. */
  154. public function disable():void
  155. {
  156. this.enabled_ = false;
  157. this.updateArrow_(null);
  158. }
  159. /**
  160. * Enables the marker tracker.
  161. */
  162. public function enable():void
  163. {
  164. this.enabled_ = true;
  165. this.updateArrow_(null);
  166. }
  167. /**
  168. * Reactivates the marker tracker after being destroyed.
  169. */
  170. public function reactivate():void
  171. {
  172. this.enabled_ = true;
  173. this.arrowDisplayed_ = false;
  174. this.arrow_ = null;
  175. this.oldArrow_ = null;
  176. this.control_ = null;
  177. this.babyMarker_ = new Marker(new LatLng(0, 0));
  178. if(this.marker_.getOptions().icon){
  179. var babyIcon:DisplayObject = DisplayObject(ObjectUtil.copy(this.marker_.getOptions().icon));
  180. babyIcon.width = this.marker_.getOptions().icon.width * this.iconScale_;
  181. babyIcon.height = this.marker_.getOptions().icon.height * this.iconScale_ ;
  182. this.babyMarker_.getOptions().icon = babyIcon;
  183. } else {
  184. this.babyMarker_.setOptions(this.marker_.getOptions());
  185. }
  186. this.babyMarker_.getOptions().hasShadow = false;
  187. this.babyMarker_.getOptions().clickable = true;
  188. this.map_.addEventListener(this.updateEvent_, this.updateArrow_);
  189. this.marker_.addEventListener(Event.CHANGE, this.updateArrow_);
  190. if (this.quickPanEnabled_) {
  191. this.babyMarker_.addEventListener(this.panEvent_, this.panToMarker_);
  192. }
  193. this.updateArrow_(null);
  194. }
  195. /**
  196. * Called on the trigger event to update the arrow. Primary function is to
  197. * check if the parent marker is in view, if not draw the tracking arrow.
  198. */
  199. private function updateArrow_(event:Event):void
  200. {
  201. if(!this.map_.getLatLngBounds().containsLatLng(this.marker_.getLatLng()) && this.enabled_) {
  202. this.drawArrow_();
  203. } else if(this.arrowDisplayed_) {
  204. this.hideArrow_();
  205. }
  206. }
  207. /**
  208. * Draws or redraws the arrow as needed, called when the parent marker is
  209. * not with in the map view.
  210. */
  211. private function drawArrow_():void
  212. {
  213. //convert to pixels
  214. var bounds:LatLngBounds = this.map_.getLatLngBounds();
  215. var SW:Point = this.map_.fromLatLngToPoint(bounds.getSouthWest());
  216. var NE:Point = this.map_.fromLatLngToPoint(bounds.getNorthEast());
  217. //include the padding while deciding on the arrow location
  218. var minX:Number = SW.x + this.padding_;
  219. var minY:Number = NE.y + this.padding_;
  220. var maxX:Number = NE.x - this.padding_;
  221. var maxY:Number = SW.y - this.padding_;
  222. // find the geometric info for the marker realative to the center of the map
  223. var centerPoint:Point = this.map_.fromLatLngToPoint(this.map_.getCenter());
  224. var locPoint:Point = this.map_.fromLatLngToPoint(this.marker_.getLatLng());
  225. //get the slope of the line
  226. var m:Number = (centerPoint.y-locPoint.y) / (centerPoint.x-locPoint.x);
  227. var b:Number = (centerPoint.y - m*centerPoint.x);
  228. // end the line within the bounds
  229. var x:Number = maxX;
  230. if ( locPoint.x < maxX && locPoint.x > minX ) {
  231. x = locPoint.x;
  232. } else if (centerPoint.x > locPoint.x) {
  233. x = minX;
  234. }
  235. //calculate y and check boundaries again
  236. var y:Number = m * x + b;
  237. if( y > maxY ) {
  238. y = maxY;
  239. x = (y - b)/m;
  240. } else if(y < minY) {
  241. y = minY;
  242. x = (y - b) / m;
  243. }
  244. // get the proper angle of the arrow
  245. var ang:Number = Math.atan(-m);
  246. if(x > centerPoint.x) {
  247. ang = ang + Math.PI;
  248. }
  249. // define the point of the arrow
  250. var arrowLoc:LatLng = this.map_.fromPointToLatLng(new Point(x, y));
  251. // left side of marker is at -1,1
  252. var arrowLeft:LatLng = this.map_.fromPointToLatLng(
  253. this.getRotatedPoint_(((-1) * this.length_), this.length_, ang, x, y) );
  254. // right side of marker is at -1,-1
  255. var arrowRight:LatLng = this.map_.fromPointToLatLng(
  256. this.getRotatedPoint_(((-1)*this.length_), ((-1)*this.length_), ang, x, y));
  257. this.oldArrow_ = this.arrow_;
  258. this.arrow_ = new Polyline([arrowLeft, arrowLoc, arrowRight],
  259. new PolylineOptions({strokeStyle:
  260. new StrokeStyle({color: this.color_, thickness: this.weight_, alpha: this.opacity_})}));
  261. this.map_.addOverlay(this.arrow_);
  262. // move the babyMarker to -1,0
  263. this.babyMarker_.setLatLng(this.map_.fromPointToLatLng(this.getRotatedPoint_(((-2)*this.length_), 0, ang, x, y)));
  264. if (!this.arrowDisplayed_) {
  265. this.map_.addOverlay(this.babyMarker_);
  266. this.arrowDisplayed_ = true;
  267. }
  268. if (this.oldArrow_) {
  269. this.map_.removeOverlay(this.oldArrow_);
  270. }
  271. }
  272. /**
  273. * Hides the arrows.
  274. */
  275. private function hideArrow_():void
  276. {
  277. this.map_.removeOverlay(this.babyMarker_);
  278. if(this.arrow_) {
  279. this.map_.removeOverlay(this.arrow_);
  280. }
  281. if(this.oldArrow_) {
  282. this.map_.removeOverlay(this.oldArrow_);
  283. }
  284. this.arrowDisplayed_ = false;
  285. }
  286. /**
  287. * Pans the map to the parent marker.
  288. */
  289. private function panToMarker_(event:Event):void
  290. {
  291. this.map_.panTo(this.marker_.getLatLng());
  292. }
  293. /**
  294. * This applies a counter-clockwise rotation to any point.
  295. *
  296. * @param {Number} x The x value of the point.
  297. * @param {Number} y The y value of the point.
  298. * @param {Number} ang The counter clockwise angle of rotation.
  299. * @param {Number} xoffset Adds a position offset to the x position.
  300. * @param {Number} yoffset Adds a position offset to the y position.
  301. * @return {Point} A rotated GPoint.
  302. */
  303. private function getRotatedPoint_(x:Number, y:Number, ang:Number, xoffset:Number, yoffset:Number):Point
  304. {
  305. var newx:Number = y * Math.sin(ang) - x * Math.cos(ang) + xoffset;
  306. var newy:Number = x * Math.sin(ang) + y * Math.cos(ang) + yoffset;
  307. return new Point(newx, newy);
  308. }
  309. }
  310. }