PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/mstandio/UniversalMap/navigation/MapViewer.as

http://universalmap.googlecode.com/
ActionScript | 560 lines | 397 code | 72 blank | 91 comment | 60 complexity | 565d435fef3ea85da1c53641c6b5da9a MD5 | raw file
  1. package plugins.mstandio.UniversalMap.navigation {
  2. /**
  3. * MapViewer provides masked map, and dragging interface if needed
  4. * it also executes updates on maps and navigation points
  5. * casted form UniversalMap
  6. * drag and scalling based on http://blog.des84.com/2009/04/16/drag-large-images-updated-now-with-zoom.des84
  7. * @author mstandio
  8. */
  9. import flash.display.Loader;
  10. import flash.display.Sprite;
  11. import flash.events.Event;
  12. import flash.display.Bitmap;
  13. import flash.events.MouseEvent;
  14. import flash.ui.Mouse;
  15. import flash.net.URLRequest;
  16. import flash.events.IOErrorEvent;
  17. import flash.system.ApplicationDomain;
  18. import flash.utils.getDefinitionByName;
  19. import flash.utils.getQualifiedClassName;
  20. import gs.TweenLite;
  21. import plugins.mstandio.UniversalMap.UniversalMap;
  22. import plugins.mstandio.UniversalMap.navigation.*;
  23. import plugins.mstandio.utils.combobox.ComboBox;
  24. public class MapViewer extends Sprite {
  25. [Embed(source="../images/interface/hand_closed.png")]
  26. private var Bitmap_handClosed:Class;
  27. [Embed(source="../images/interface/hand_opened.png")]
  28. private var Bitmap_handOpened:Class;
  29. [Embed(source = '../images/interface/map_navigation_move.png')]
  30. private var Bitmap_mapNavigationMove :Class;
  31. [Embed(source = '../images/interface/map_navigation_zoom.png')]
  32. private var Bitmap_mapNavigationZoom :Class;
  33. private var mapMask:Sprite;
  34. private var mapMaskWidth:Number;
  35. private var mapMaskHeight:Number;
  36. private var mapContainer:Sprite; // contains map image and navigation points, masked by mapMask
  37. private var map:Map; // Data structure containing url of image, name and array of naviagtion points
  38. private var mapChanged:Boolean; // if update comes from new space on same map or from new map
  39. private var mapImage:Loader; // External image
  40. private var mapWidth:Number; // Size of external image
  41. private var mapHeight:Number;
  42. private var cursor:Bitmap; // Grabbing hand cursor
  43. private var mouseOver:Boolean;
  44. private var dragActive:Boolean; // If grabbing hand is dragging map
  45. private var normalCursorForced:Boolean; // When mouse moves over NavigationPoint
  46. private var mapNavigation:Sprite; // container for mapNavigationMove and mapNavigationZoom
  47. private var mapNavigationMove:Sprite; // alternative to dragging navigation that scrolls map
  48. private var mapNavigationZoom:Sprite; // additional buttons for dragging
  49. private var mapNavigationSpeed:Number; // see constructor
  50. private var mapNavigationZoomSpeed:Number;
  51. private var mapNavigationZoomMax:Number;
  52. private var params:NavigationPointParameters; // passed down to specific NavigationPoint
  53. /**
  54. * Constructor - captain obvoius strikes back
  55. * @param map
  56. * @param mapMaskWidth
  57. * @param mapMaskHeight
  58. * @param params
  59. */
  60. public function MapViewer(map:Map, mapMaskWidth:Number, mapMaskHeight:Number, params:NavigationPointParameters) {
  61. this.map = map;
  62. this.mapMaskWidth = mapMaskWidth;
  63. this.mapMaskHeight = mapMaskHeight;
  64. this.params = params;
  65. /****hard-coded params if you wanna change them, well, good luck*****************/
  66. this.mapNavigationSpeed = 15; // Speed of scrolling with navigation arrows
  67. this.mapNavigationZoomSpeed = 0.15; // Scale change after map zoom
  68. this.mapNavigationZoomMax = 1.5; // Scale for maximum zoom
  69. /********************************************************************************/
  70. this.normalCursorForced = false;
  71. this.dragActive = false;
  72. this.mapMask = new Sprite();
  73. this.mapMask.graphics.beginFill(0x000000);
  74. this.mapMask.graphics.drawRect(0, 0, this.mapMaskWidth, this.mapMaskHeight);
  75. this.mapMask.graphics.endFill();
  76. this.addChild(mapMask);
  77. this.mapContainer = new Sprite();
  78. this.mapContainer.mask = mapMask;
  79. this.addChild(mapContainer);
  80. this.loadMap();
  81. }
  82. /**
  83. * loading map from requested url
  84. */
  85. private function loadMap():void {
  86. this.mapImage = new Loader();
  87. var request:URLRequest = new URLRequest(this.map.mapUrl);
  88. this.mapImage.load(request);
  89. this.mapImage.contentLoaderInfo.addEventListener(Event.COMPLETE, mapLoaded);
  90. this.mapImage.addEventListener(IOErrorEvent.IO_ERROR, mapNotLoaded);
  91. }
  92. /**
  93. * When Map is loaded
  94. * @param e
  95. */
  96. private function mapLoaded(e:Event):void {
  97. this.mapImage.removeEventListener(Event.COMPLETE, mapLoaded);
  98. this.mapImage.removeEventListener(IOErrorEvent.IO_ERROR, mapNotLoaded);
  99. this.mapWidth = e.target.width; // size of image
  100. this.mapHeight = e.target.height;
  101. this.mapChanged = true;
  102. this.mapContainer.addChild(this.mapImage);
  103. this.determineDrag(); // if dragging interface is needed
  104. this.updateNavigationPoints(this.params); // places navigation points on map Container according to recieved params
  105. }
  106. /**
  107. * When map is not loaded
  108. * @param e
  109. */
  110. private function mapNotLoaded(e:Event):void {
  111. this.mapImage.removeEventListener(IOErrorEvent.IO_ERROR, mapNotLoaded);
  112. this.mapImage.removeEventListener(Event.COMPLETE, mapNotLoaded);
  113. TweenLite.killDelayedCallsTo(loadMap);
  114. TweenLite.delayedCall(3, loadMap);
  115. }
  116. /**
  117. * Determines if size of map reqiures dragging interface
  118. */
  119. private function determineDrag():void {
  120. // if image size is bigger then map window
  121. if ((this.mapHeight > this.mapMaskHeight) || (this.mapWidth > this.mapMaskWidth)) {
  122. // if dragging hasn't been activated yet
  123. if(dragActive == false){
  124. this.dragActive = true;
  125. mapContainer.addEventListener(MouseEvent.ROLL_OVER, overMc);
  126. mapContainer.addEventListener(MouseEvent.ROLL_OUT, outMc);
  127. mapContainer.addEventListener(MouseEvent.MOUSE_MOVE, moveMc);
  128. mapContainer.addEventListener(MouseEvent.MOUSE_DOWN, dragMc);
  129. mapContainer.addEventListener(MouseEvent.MOUSE_UP, unDragMc);
  130. cursor = new Bitmap(new Bitmap_handOpened().bitmapData);
  131. cursor.name = "cursor";
  132. cursor.alpha = 1 / UniversalMap.mapAlpha;
  133. cursor.visible = false;
  134. parent.addChild(cursor);
  135. if (this.mapNavigation == null) {
  136. this.buildMapNavigation();
  137. }
  138. this.addChild(this.mapNavigation);
  139. this.mapNavigationZoom.visible = this.map.mapAllowZoom;
  140. }
  141. }
  142. // image is smaller than map window or size is equal
  143. else {
  144. // if it hasnt been deactivated yet
  145. if (dragActive = true) {
  146. this.dragActive = false;
  147. mapContainer.removeEventListener(MouseEvent.ROLL_OVER, overMc);
  148. mapContainer.removeEventListener(MouseEvent.ROLL_OUT, outMc);
  149. mapContainer.removeEventListener(MouseEvent.MOUSE_MOVE, moveMc);
  150. mapContainer.removeEventListener(MouseEvent.MOUSE_DOWN, dragMc);
  151. mapContainer.removeEventListener(MouseEvent.MOUSE_UP, unDragMc);
  152. if (this.getChildByName("cursor") != null) {
  153. removeChild(getChildByName("cursor"));
  154. }
  155. if (this.getChildByName("mapNavigation") != null) {
  156. removeChild(getChildByName("mapNavigation"));
  157. }
  158. }
  159. this.mapContainer.x = this.mapMaskWidth * 0.5 - this.mapContainer.width * 0.5; // center map
  160. this.mapContainer.y = this.mapMaskHeight* 0.5 - this.mapContainer.height * 0.5;
  161. }
  162. // if MapNavigation has been placed or removed then replace Combobox
  163. (UniversalMap(root)).placeComboBox();
  164. }
  165. /**
  166. * Checks state of each navigation point and updates its state with params
  167. * @param params
  168. */
  169. public function updateNavigationPoints(params:NavigationPointParameters):void {
  170. if(this.mapChanged){
  171. // removes all navigation points and map
  172. while (this.mapContainer.numChildren) {
  173. if (this.mapContainer.getChildAt(0) is this.getClass(NavigationPoint)) {
  174. NavigationPoint(this.mapContainer.getChildAt(0)).killCam();
  175. }
  176. this.mapContainer.removeChildAt(0);
  177. }
  178. this.mapContainer.addChild(this.mapImage);
  179. }
  180. this.params = params;
  181. var somePointShowing:Boolean = false; // determines if map should scroll to the center or not
  182. for each(var navigationPoint:NavigationPoint in map.navigationPoints) {
  183. if (this.mapChanged) {
  184. // adding navigation points again
  185. this.mapContainer.addChild(navigationPoint);
  186. navigationPoint.restorebeforeZoom();
  187. }
  188. if ((params.currentSpace == navigationPoint.targetSpace) || (params.currentSpace + ".preview" == navigationPoint.targetSpace) || (params.currentSpace == navigationPoint.targetSpace + ".preview")) {
  189. // point represents current space
  190. navigationPoint.showCam(params);
  191. navigationPoint.focus();
  192. somePointShowing = true;
  193. }else {
  194. // point doesnt represent current space
  195. if (this.mapChanged) {
  196. navigationPoint.killCam();
  197. }else{
  198. navigationPoint.hideCam();
  199. }
  200. }
  201. }
  202. this.mapChanged = false;
  203. // scroll to the center of the map, couse current map has no point representing current space
  204. if (!somePointShowing) {
  205. this.gotoXY(this.mapImage.width / 2, this.mapImage.height / 2, true);
  206. // set child index of showing point to highest value
  207. }else {
  208. for each(navigationPoint in map.navigationPoints) {
  209. if ((params.currentSpace == navigationPoint.targetSpace) || (params.currentSpace + ".preview" == navigationPoint.targetSpace) || (params.currentSpace == navigationPoint.targetSpace + ".preview")) {
  210. this.mapContainer.setChildIndex(navigationPoint, this.mapContainer.numChildren - 1);
  211. break;
  212. }
  213. }
  214. }
  215. UniversalMap(root).firstShowWindow();
  216. }
  217. /**
  218. * Updates MapViewer with new map and new parameters of camera for navigation point
  219. * @param map
  220. * @param params
  221. */
  222. public function changeMap(map:Map, params:NavigationPointParameters):void {
  223. // removes all navigation points and map
  224. TweenLite.killTweensOf(this.mapContainer);
  225. this.mapContainer.x = this.mapContainer.y = 0;
  226. while (this.mapContainer.numChildren){
  227. this.mapContainer.removeChildAt(0);
  228. }
  229. this.map = map;
  230. this.params = params;
  231. // begin map building
  232. this.loadMap();
  233. }
  234. /**
  235. * Returning to normal cursor for entering NavigationPoint
  236. * casted from NavigationPoint
  237. * @param e
  238. */
  239. public function forceNormalCursor(command:Boolean):void {
  240. if(this.dragActive){
  241. if (command) {
  242. this.normalCursorForced = true;
  243. }else{
  244. this.normalCursorForced = false;
  245. }
  246. }
  247. }
  248. /**
  249. * Adds mapNavigation with navigation buttons
  250. */
  251. private function buildMapNavigation():void {
  252. this.mapNavigation = new Sprite();
  253. this.mapNavigation.name = "mapNavigation";
  254. // building mapNavigationMove
  255. this.mapNavigationMove = new Sprite();
  256. var m_n:Bitmap = new Bitmap(new Bitmap_mapNavigationMove().bitmapData);
  257. mapNavigationMove.addChild(m_n);
  258. var goLeft:Sprite = new Sprite();
  259. goLeft.graphics.beginFill(0x000000,0);
  260. goLeft.graphics.drawRect(0, 0, 15, 15);
  261. goLeft.graphics.endFill();
  262. goLeft.buttonMode = true;
  263. mapNavigationMove.addChild(goLeft);
  264. var goRight:Sprite = new Sprite();
  265. goRight.graphics.beginFill(0x000000,0);
  266. goRight.graphics.drawRect(0, 0, 15, 15);
  267. goRight.graphics.endFill();
  268. goRight.buttonMode = true;
  269. mapNavigationMove.addChild(goRight);
  270. var goUp:Sprite = new Sprite();
  271. goUp.graphics.beginFill(0x000000,0);
  272. goUp.graphics.drawRect(0, 0, 15, 15);
  273. goUp.graphics.endFill();
  274. goUp.buttonMode = true;
  275. mapNavigationMove.addChild(goUp);
  276. var goDown:Sprite = new Sprite();
  277. goDown.graphics.beginFill(0x000000,0);
  278. goDown.graphics.drawRect(0, 0, 15, 15);
  279. goDown.graphics.endFill();
  280. goDown.buttonMode = true;
  281. mapNavigationMove.addChild(goDown);
  282. goLeft.y = m_n.height * 0.5 - goLeft.height * 0.5;
  283. goRight.y = m_n.height * 0.5 - goLeft.height * 0.5;
  284. goRight.x = m_n.width - goRight.width;
  285. goUp.x = m_n.width * 0.5 - goUp.width * 0.5;
  286. goDown.x = m_n.width * 0.5 - goDown.width * 0.5 ;
  287. goDown.y = m_n.height - goDown.height ;
  288. goLeft.addEventListener(MouseEvent.CLICK, mapNavLeft);
  289. goRight.addEventListener(MouseEvent.CLICK, mapNavRight);
  290. goUp.addEventListener(MouseEvent.CLICK, mapNavUp);
  291. goDown.addEventListener(MouseEvent.CLICK, mapNavDown);
  292. this.mapNavigationMove.x = this.mapNavigationMove.y = 2;
  293. this.mapNavigation.addChild(this.mapNavigationMove);
  294. // building mapNavigationZoom
  295. this.mapNavigationZoom = new Sprite();
  296. this.mapNavigationZoom.name = "mapNavigationZoom";
  297. var m_n_z:Bitmap = new Bitmap(new Bitmap_mapNavigationZoom().bitmapData);
  298. mapNavigationZoom.addChild(m_n_z);
  299. var zoomIn:Sprite = new Sprite();
  300. zoomIn.graphics.beginFill(0x000000,0);
  301. zoomIn.graphics.drawRect(0, 0, 21, 18);
  302. zoomIn.graphics.endFill();
  303. zoomIn.buttonMode = true;
  304. mapNavigationZoom.addChild(zoomIn);
  305. var zoomOut:Sprite = new Sprite();
  306. zoomOut.graphics.beginFill(0x000000,0);
  307. zoomOut.graphics.drawRect(0, 0, 21, 18);
  308. zoomOut.graphics.endFill();
  309. zoomOut.buttonMode = true;
  310. mapNavigationZoom.addChild(zoomOut);
  311. zoomIn.x = m_n_z.width * 0.5 - zoomIn.width * 0.5;
  312. zoomOut.y = m_n_z.height - zoomOut.height ;
  313. zoomOut.x = m_n_z.width * 0.5 - zoomOut.width * 0.5 ;
  314. zoomIn.addEventListener(MouseEvent.CLICK, mapZoomIn);
  315. zoomOut.addEventListener(MouseEvent.CLICK, mapZoomOut);
  316. this.mapNavigationZoom.x = (this.mapNavigationMove.width +this.mapNavigationMove.x) * 0.5 - this.mapNavigationZoom.width * 0.5;
  317. this.mapNavigationZoom.y = this.mapNavigationMove.y + this.mapNavigationMove.height + 8; // 8 is vertical distance between mapNavigation and mapNavigationZoom
  318. this.mapNavigation.alpha = 1 / UniversalMap.mapAlpha;
  319. this.mapNavigation.addChild(mapNavigationZoom);
  320. }
  321. private function mapNavLeft(e:MouseEvent):void {
  322. this.gotoXY(-mapContainer.x-mapNavigationSpeed, -mapContainer.y+(mapMaskHeight*0.5));
  323. }
  324. private function mapNavRight(e:MouseEvent):void {
  325. this.gotoXY(-mapContainer.x+mapMaskWidth+mapNavigationSpeed, -mapContainer.y+(mapMaskHeight*0.5));
  326. }
  327. private function mapNavUp(e:MouseEvent):void {
  328. this.gotoXY(-mapContainer.x+(mapMaskWidth*0.5), -mapContainer.y-mapNavigationSpeed);
  329. }
  330. private function mapNavDown(e:MouseEvent):void {
  331. this.gotoXY(-mapContainer.x+(mapMaskWidth*0.5), -mapContainer.y+mapMask.height+mapNavigationSpeed);
  332. }
  333. private function mapZoomIn(e:MouseEvent):void {
  334. var xScaleOld:Number = this.mapImage.scaleX;
  335. var yScaleOld:Number = this.mapImage.scaleY;
  336. var widthOld:Number = this.mapImage.width;
  337. if ((this.mapImage.width * (xScaleOld + this.mapNavigationZoomSpeed)) / this.mapWidth <= this.mapNavigationZoomMax) {
  338. this.mapImage.scaleX = (xScaleOld + this.mapNavigationZoomSpeed);
  339. this.mapImage.scaleY = (yScaleOld + this.mapNavigationZoomSpeed);
  340. verifyDrag(new Event(Event.ENTER_FRAME));
  341. this.rescalePoints((this.mapImage.width) / widthOld);
  342. }
  343. }
  344. private function mapZoomOut(e:MouseEvent):void {
  345. var xScaleOld:Number = this.mapImage.scaleX;
  346. var yScaleOld:Number = this.mapImage.scaleY;
  347. var widthOld:Number = this.mapImage.width;
  348. var heightOld:Number = this.mapImage.height;
  349. this.mapImage.scaleX = (xScaleOld - this.mapNavigationZoomSpeed);
  350. this.mapImage.scaleY = (yScaleOld - this.mapNavigationZoomSpeed);
  351. if (this.mapImage.width <= this.mapMaskWidth || this.mapImage.height <= this.mapMaskHeight ){
  352. if ( this.mapImage.width <= this.mapMaskWidth ){
  353. this.mapImage.width = this.mapMaskWidth;
  354. this.mapImage.height = this.mapMaskWidth / (widthOld / heightOld);
  355. }else if (this.mapImage.height <= this.mapMaskHeight) {
  356. this.mapImage.height = this.mapMaskHeight;
  357. this.mapImage.width = (widthOld / heightOld) * this.mapMaskHeight;
  358. }
  359. }
  360. verifyDrag(new Event(Event.ENTER_FRAME));
  361. this.rescalePoints((this.mapImage.width)/widthOld);
  362. }
  363. /**
  364. * Rescales all present navigation points
  365. * @param ratio
  366. */
  367. private function rescalePoints(ratio:Number):void {
  368. for (var i:Number = 0; i<this.mapContainer.numChildren ; i++ ) {
  369. this.mapContainer.getChildAt(i);
  370. if (this.mapContainer.getChildAt(i) is this.getClass(NavigationPoint)) {
  371. NavigationPoint(this.mapContainer.getChildAt(i)).rescale(ratio);
  372. }
  373. }
  374. // go to zoomed point
  375. this.gotoXY( ((this.mapMaskWidth * 0.5 - this.mapContainer.x) * ratio) , ((this.mapMaskHeight * 0.5 - this.mapContainer.y ) * ratio ), false);
  376. }
  377. /**
  378. * Moves map to specified position
  379. * @param x
  380. * @param y
  381. */
  382. public function gotoXY(x:Number, y:Number, useTween:Boolean=true):void {
  383. if (this.dragActive && x>0 && y>0 && x<= this.mapWidth && y<= this.mapHeight) {
  384. var x:Number = x;
  385. var y:Number = y;
  386. x = (x < this.mapMask.width * 0.5) ? this.mapMask.width * 0.5 :
  387. ((x > this.mapImage.width - this.mapMask.width * 0.5) ? this.mapImage.width - this.mapMask.width * 0.5 : x);
  388. y = (y < this.mapMask.height * 0.5) ? this.mapMask.height * 0.5 :
  389. ((y > this.mapImage.height - this.mapMask.height * 0.5) ? this.mapImage.height - this.mapMask.height * 0.5 : y);
  390. if (useTween) {
  391. TweenLite.to(this.mapContainer, 1, { x: - x + this.mapMaskWidth * 0.5 , y: - y + mapMaskHeight * 0.5 } )
  392. }else {
  393. this.mapContainer.x = - x + this.mapMaskWidth * 0.5;
  394. this.mapContainer.y = - y + this.mapMaskHeight * 0.5;
  395. }
  396. }
  397. }
  398. /**
  399. * mouse enters mapMask
  400. */
  401. private function overMc(m:MouseEvent):void {
  402. this.cursor.x = mouseX - this.cursor.width * 0.5;
  403. this.cursor.y = mouseY;
  404. this.mouseOver = true;
  405. Mouse.hide();
  406. this.cursor.visible = true;
  407. }
  408. /**
  409. * mouse leaves mapMask
  410. */
  411. private function outMc(m:MouseEvent):void {
  412. this.mouseOver = false;
  413. Mouse.show();
  414. cursor.visible = false;
  415. }
  416. /**
  417. * Displays dragging hand
  418. */
  419. private function moveMc(m:MouseEvent):void {
  420. this.cursor.x = mouseX - this.cursor.width * 0.5;
  421. this.cursor.y = mouseY;
  422. if (mouseOver && !this.normalCursorForced) {
  423. this.cursor.visible = true;
  424. Mouse.hide();
  425. }else{
  426. this.cursor.visible = false;
  427. Mouse.show();
  428. }
  429. }
  430. /**
  431. * Starts dragginng
  432. */
  433. private function dragMc(m:MouseEvent):void {
  434. if (!this.normalCursorForced) {
  435. TweenLite.killTweensOf(this.mapContainer);
  436. mapContainer.startDrag();
  437. mapContainer.addEventListener(Event.ENTER_FRAME, verifyDrag);
  438. cursor.bitmapData = new Bitmap_handClosed().bitmapData;
  439. for each(var navigationPoint:NavigationPoint in map.navigationPoints) {
  440. if (navigationPoint.getShowing()) {
  441. navigationPoint.setUnFocused();
  442. }
  443. }
  444. }
  445. }
  446. /**
  447. * Stops dragginng
  448. */
  449. private function unDragMc(m:MouseEvent):void {
  450. mapContainer.stopDrag();
  451. mapContainer.removeEventListener(Event.ENTER_FRAME, verifyDrag);
  452. cursor.bitmapData = new Bitmap_handOpened().bitmapData;
  453. if (mouseOver && !this.normalCursorForced) {
  454. verifyDrag(new Event(Event.ENTER_FRAME));
  455. }
  456. }
  457. /**
  458. * Corrects location of dragged Sprite if it goes out of mapMask
  459. * also cancels dragging if clicked mouse goes out of mapMask
  460. */
  461. private function verifyDrag(e:Event):void{
  462. if ( mapContainer.x > mapMask.x ){
  463. mapContainer.x = mapMask.x;
  464. }
  465. if ( mapContainer.x < (( mapMask.x + mapMask.width) - this.mapImage.width ) ){
  466. mapContainer.x = (( mapMask.x + mapMask.width) - this.mapImage.width );
  467. }
  468. if ( mapContainer.y > mapMask.y ){
  469. mapContainer.y = mapMask.y;
  470. }
  471. if ( mapContainer.y < (( mapMask.y + mapMask.height ) - this.mapImage.height ) ){
  472. mapContainer.y = (( mapMask.y + mapMask.height ) - this.mapImage.height );
  473. }
  474. if (!mouseOver){
  475. mapContainer.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_UP));
  476. }
  477. }
  478. public function getDragActive():Boolean {
  479. return this.dragActive;
  480. }
  481. public function getNavigationWidth():Number {
  482. return this.mapNavigation.width;
  483. }
  484. public function getClass(obj:Object):Class {
  485. return Class(getDefinitionByName(getQualifiedClassName(obj)));
  486. }
  487. }
  488. }