PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/net/systemeD/halcyon/Map.as

https://github.com/netconstructor/Potlatch2
ActionScript | 578 lines | 383 code | 98 blank | 97 comment | 70 complexity | 45c7e343732c41f6bb0bb9321353a1bc MD5 | raw file
  1. package net.systemeD.halcyon {
  2. import flash.display.Loader;
  3. import flash.display.Sprite;
  4. import flash.events.*;
  5. import flash.external.ExternalInterface;
  6. import flash.geom.Rectangle;
  7. import flash.net.*;
  8. import flash.text.Font;
  9. import flash.text.TextField;
  10. import flash.ui.Keyboard;
  11. import net.systemeD.halcyon.connection.*;
  12. import net.systemeD.halcyon.styleparser.*;
  13. // for experimental export function:
  14. // import flash.net.FileReference;
  15. // import com.adobe.images.JPGEncoder;
  16. /** The representation of part of the map on the screen, including information about coordinates, background imagery, paint properties etc. */
  17. public class Map extends Sprite {
  18. /** master map scale - how many Flash pixels in 1 degree longitude (for Landsat, 5120) */
  19. public const MASTERSCALE:Number=5825.4222222222;
  20. /** don't zoom out past this */
  21. public const MINSCALE:uint=13;
  22. /** don't zoom in past this */
  23. public const MAXSCALE:uint=23;
  24. /** sprite for ways and (POI/tagged) nodes in core layer */
  25. public var paint:MapPaint;
  26. /** sprite for vector background layers */
  27. public var vectorbg:Sprite;
  28. /** map scale */
  29. public var scale:uint=14;
  30. /** current scaling factor for lon/latp */
  31. public var scalefactor:Number=MASTERSCALE;
  32. public var edge_l:Number; // current bounding box
  33. public var edge_r:Number; // |
  34. public var edge_t:Number; // |
  35. public var edge_b:Number; // |
  36. public var centre_lat:Number; // centre lat/lon
  37. public var centre_lon:Number; // |
  38. /** urllon-xradius/masterscale; */
  39. public var baselon:Number;
  40. /** lat2lat2p(urllat)+yradius/masterscale; */
  41. public var basey:Number;
  42. /** width (Flash pixels) */
  43. public var mapwidth:uint;
  44. /** height (Flash pixels) */
  45. public var mapheight:uint;
  46. /** Is the map being panned */
  47. public var dragstate:uint=NOT_DRAGGING; // dragging map (panning)
  48. /** Can the map be panned */
  49. private var _draggable:Boolean=true; // |
  50. private var lastxmouse:Number; // |
  51. private var lastymouse:Number; // |
  52. private var downX:Number; // |
  53. private var downY:Number; // |
  54. private var downTime:Number; // |
  55. public const NOT_DRAGGING:uint=0; // |
  56. public const NOT_MOVED:uint=1; // |
  57. public const DRAGGING:uint=2; // |
  58. /** How far the map can be dragged without actually triggering a pan. */
  59. public const TOLERANCE:uint=7; // |
  60. /** object containing HTML page parameters */
  61. public var initparams:Object;
  62. /** reference to backdrop sprite */
  63. public var backdrop:Object;
  64. /** background tile object */
  65. public var tileset:TileSet;
  66. /** background tile URL, name and scheme */
  67. private var tileparams:Object={ url:'' };
  68. /** internal style URL */
  69. private var styleurl:String='';
  70. /** show all objects, even if unstyled? */
  71. public var showall:Boolean=true;
  72. /** server connection */
  73. public var connection:Connection;
  74. /** VectorLayer objects */
  75. public var vectorlayers:Object={};
  76. // ------------------------------------------------------------------------------------------
  77. /** Map constructor function */
  78. public function Map(initparams:Object) {
  79. this.initparams=initparams;
  80. connection = Connection.getConnection(initparams);
  81. connection.addEventListener(Connection.NEW_WAY, newWayCreated);
  82. connection.addEventListener(Connection.NEW_POI, newPOICreated);
  83. connection.addEventListener(Connection.WAY_RENUMBERED, wayRenumbered);
  84. connection.addEventListener(Connection.NODE_RENUMBERED, nodeRenumbered);
  85. gotEnvironment(null);
  86. addEventListener(Event.ENTER_FRAME, everyFrame);
  87. scrollRect=new Rectangle(0,0,800,600);
  88. }
  89. public function gotEnvironment(r:Object):void {
  90. var loader:Loader = new Loader();
  91. loader.contentLoaderInfo.addEventListener(Event.COMPLETE, gotFont);
  92. loader.load(new URLRequest("FontLibrary.swf"));
  93. }
  94. public function gotFont(r:Event):void {
  95. var FontLibrary:Class = r.target.applicationDomain.getDefinition("FontLibrary") as Class;
  96. Font.registerFont(FontLibrary.DejaVu);
  97. if (initparams['lat'] != null) {
  98. // parameters sent from HTML
  99. init(initparams['lat'],
  100. initparams['lon'],
  101. initparams['zoom']);
  102. } else {
  103. // somewhere innocuous
  104. init(53.09465,-2.56495,17);
  105. }
  106. }
  107. // ------------------------------------------------------------------------------------------
  108. /** Initialise map at a given lat/lon */
  109. public function init(startlat:Number, startlon:Number, startscale:uint=0):void {
  110. while (numChildren) { removeChildAt(0); }
  111. tileset=new TileSet(this); // 0 - 900913 background
  112. if (initparams['tileblocks']) { // | option to block dodgy tile sources
  113. tileset.blocks=initparams['tileblocks'];// |
  114. } // |
  115. addChild(tileset); // |
  116. tileset.init(tileparams, false,
  117. initparams['background_dim'] ==null ? true : initparams['background_dim'],
  118. initparams['background_sharpen']==null ? false : initparams['background_sharpen']);
  119. vectorbg = new Sprite(); // 1 - vector background layers
  120. addChild(vectorbg); // |
  121. paint = new MapPaint(this,-5,5); // 2 - core paint object
  122. addChild(paint); // |
  123. paint.isBackground=false; // |
  124. if (styleurl) { // if we've only just set up paint, then setStyle won't have created the RuleSet
  125. paint.ruleset=new RuleSet(MINSCALE,MAXSCALE,redraw,redrawPOIs);
  126. paint.ruleset.loadFromCSS(styleurl);
  127. }
  128. if (startscale>0) {
  129. scale=startscale;
  130. this.dispatchEvent(new MapEvent(MapEvent.SCALE, {scale:scale}));
  131. }
  132. scalefactor=MASTERSCALE/Math.pow(2,13-scale);
  133. baselon =startlon -(mapwidth /2)/scalefactor;
  134. basey =lat2latp(startlat)+(mapheight/2)/scalefactor;
  135. updateCoords(0,0);
  136. this.dispatchEvent(new Event(MapEvent.INITIALISED));
  137. download();
  138. if (ExternalInterface.available) {
  139. ExternalInterface.addCallback("setPosition", function (lat:Number,lon:Number,zoom:uint):void {
  140. updateCoordsFromLatLon(lat, lon);
  141. changeScale(zoom);
  142. });
  143. }
  144. }
  145. // ------------------------------------------------------------------------------------------
  146. /** Recalculate co-ordinates from new Flash origin */
  147. public function updateCoords(tx:Number,ty:Number):void {
  148. setScrollRectXY(tx,ty);
  149. edge_t=coord2lat(-ty );
  150. edge_b=coord2lat(-ty+mapheight);
  151. edge_l=coord2lon(-tx );
  152. edge_r=coord2lon(-tx+mapwidth );
  153. setCentre();
  154. tileset.update();
  155. }
  156. /** Move the map to centre on a given latitude/longitude. */
  157. public function updateCoordsFromLatLon(lat:Number,lon:Number):void {
  158. var cy:Number=-(lat2coord(lat)-mapheight/2);
  159. var cx:Number=-(lon2coord(lon)-mapwidth/2);
  160. updateCoords(cx,cy);
  161. }
  162. private function setScrollRectXY(tx:Number,ty:Number):void {
  163. var w:Number=scrollRect.width;
  164. var h:Number=scrollRect.height;
  165. scrollRect=new Rectangle(-tx,-ty,w,h);
  166. }
  167. private function setScrollRectSize(width:Number,height:Number):void {
  168. var sx:Number=scrollRect.x ? scrollRect.x : 0;
  169. var sy:Number=scrollRect.y ? scrollRect.y : 0;
  170. scrollRect=new Rectangle(sx,sy,width,height);
  171. }
  172. private function getX():Number { return -scrollRect.x; }
  173. private function getY():Number { return -scrollRect.y; }
  174. private function setCentre():void {
  175. centre_lat=coord2lat(-getY()+mapheight/2);
  176. centre_lon=coord2lon(-getX()+mapwidth/2);
  177. this.dispatchEvent(new MapEvent(MapEvent.MOVE, {lat:centre_lat, lon:centre_lon, scale:scale, minlon:edge_l, maxlon:edge_r, minlat:edge_b, maxlat:edge_t}));
  178. }
  179. /** Sets the offset between the background imagery and the map. */
  180. public function nudgeBackground(x:Number,y:Number):void {
  181. this.dispatchEvent(new MapEvent(MapEvent.NUDGE_BACKGROUND, { x: x, y: y }));
  182. }
  183. private function moveMap(dx:Number,dy:Number):void {
  184. updateCoords(getX()+dx,getY()+dy);
  185. updateEntityUIs(false, false);
  186. download();
  187. }
  188. /** Recentre map at given lat/lon, updating the UI and downloading entities. */
  189. public function moveMapFromLatLon(lat:Number,lon:Number):void {
  190. updateCoordsFromLatLon(lat,lon);
  191. updateEntityUIs(false,false);
  192. download();
  193. }
  194. /** Recentre map at given lat/lon, if that point is currently outside the visible area. */
  195. public function scrollIfNeeded(lat:Number,lon:Number): void{
  196. if (lat> edge_t || lat < edge_b || lon < edge_l || lon > edge_r) {
  197. moveMapFromLatLon(lat, lon);
  198. }
  199. }
  200. // Co-ordinate conversion functions
  201. public function latp2coord(a:Number):Number { return -(a-basey)*scalefactor; }
  202. public function coord2latp(a:Number):Number { return a/-scalefactor+basey; }
  203. public function lon2coord(a:Number):Number { return (a-baselon)*scalefactor; }
  204. public function coord2lon(a:Number):Number { return a/scalefactor+baselon; }
  205. public function latp2lat(a:Number):Number { return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2); }
  206. public function lat2latp(a:Number):Number { return 180/Math.PI * Math.log(Math.tan(Math.PI/4+a*(Math.PI/180)/2)); }
  207. public function lat2coord(a:Number):Number { return -(lat2latp(a)-basey)*scalefactor; }
  208. public function coord2lat(a:Number):Number { return latp2lat(a/-scalefactor+basey); }
  209. // ------------------------------------------------------------------------------------------
  210. /** Resize map size based on current stage and height */
  211. public function updateSize(w:uint, h:uint):void {
  212. mapwidth = w; centre_lon=coord2lon(-getX()+w/2);
  213. mapheight= h; centre_lat=coord2lat(-getY()+h/2);
  214. setScrollRectSize(w,h);
  215. this.dispatchEvent(new MapEvent(MapEvent.RESIZE, {width:w, height:h}));
  216. if ( backdrop != null ) {
  217. backdrop.width=mapwidth;
  218. backdrop.height=mapheight;
  219. }
  220. if ( mask != null ) {
  221. mask.width=mapwidth;
  222. mask.height=mapheight;
  223. }
  224. }
  225. /** Download map data. Data is downloaded for the connection and the vector layers, where supported.
  226. * The bounding box for the download is taken from the current map edges.
  227. */
  228. public function download():void {
  229. this.dispatchEvent(new MapEvent(MapEvent.DOWNLOAD, {minlon:edge_l, maxlon:edge_r, maxlat:edge_t, minlat:edge_b} ));
  230. connection.loadBbox(edge_l,edge_r,edge_t,edge_b);
  231. // Do the same for vector layers
  232. for each (var layer:VectorLayer in vectorlayers) {
  233. layer.loadBbox(edge_l,edge_r,edge_t,edge_b);
  234. }
  235. }
  236. private function newWayCreated(event:EntityEvent):void {
  237. var way:Way = event.entity as Way;
  238. if (!way.loaded || !way.within(edge_l,edge_r,edge_t,edge_b)) { return; }
  239. paint.createWayUI(way);
  240. }
  241. private function newPOICreated(event:EntityEvent):void {
  242. var node:Node = event.entity as Node;
  243. if (!node.within(edge_l,edge_r,edge_t,edge_b)) { return; }
  244. paint.createNodeUI(node);
  245. }
  246. private function wayRenumbered(event:EntityRenumberedEvent):void {
  247. var way:Way = event.entity as Way;
  248. paint.renumberWayUI(way,event.oldID);
  249. }
  250. private function nodeRenumbered(event:EntityRenumberedEvent):void {
  251. var node:Node = event.entity as Node;
  252. paint.renumberNodeUI(node,event.oldID);
  253. }
  254. /** Visually mark an entity as highlighted. */
  255. public function setHighlight(entity:Entity, settings:Object):void {
  256. if ( entity is Way && paint.wayuis[entity.id] ) { paint.wayuis[entity.id].setHighlight(settings); }
  257. else if ( entity is Node && paint.nodeuis[entity.id]) { paint.nodeuis[entity.id].setHighlight(settings); }
  258. }
  259. public function setHighlightOnNodes(way:Way, settings:Object):void {
  260. if (paint.wayuis[way.id]) paint.wayuis[way.id].setHighlightOnNodes(settings);
  261. }
  262. public function protectWay(way:Way):void {
  263. if (paint.wayuis[way.id]) paint.wayuis[way.id].protectSprites();
  264. }
  265. public function unprotectWay(way:Way):void {
  266. if (paint.wayuis[way.id]) paint.wayuis[way.id].unprotectSprites();
  267. }
  268. public function limitWayDrawing(way:Way,except:Number=NaN,only:Number=NaN):void {
  269. if (!paint.wayuis[way.id]) return;
  270. paint.wayuis[way.id].drawExcept=except;
  271. paint.wayuis[way.id].drawOnly =only;
  272. paint.wayuis[way.id].redraw();
  273. }
  274. /** Protect Entities and EntityUIs against purging. This prevents the currently selected items
  275. from being purged even though they're off-screen. */
  276. public function setPurgable(entities:Array, purgable:Boolean):void {
  277. for each (var entity:Entity in entities) {
  278. entity.locked=!purgable;
  279. if ( entity is Way ) {
  280. var way:Way=entity as Way;
  281. if (paint.wayuis[way.id]) { paint.wayuis[way.id].purgable=purgable; }
  282. for (var i:uint=0; i<way.length; i++) {
  283. var node:Node=way.getNode(i)
  284. node.locked=!purgable;
  285. if (paint.nodeuis[node.id]) { paint.nodeuis[node.id].purgable=purgable; }
  286. }
  287. } else if ( entity is Node && paint.nodeuis[entity.id]) {
  288. paint.nodeuis[entity.id].purgable=purgable;
  289. }
  290. }
  291. }
  292. // Handle mouse events on ways/nodes
  293. private var mapController:MapController = null;
  294. /** Assign map controller. */
  295. public function setController(controller:MapController):void {
  296. this.mapController = controller;
  297. }
  298. public function entityMouseEvent(event:MouseEvent, entity:Entity):void {
  299. if ( mapController != null )
  300. mapController.entityMouseEvent(event, entity);
  301. }
  302. // ------------------------------------------------------------------------------------------
  303. // Add vector layer
  304. public function addVectorLayer(layer:VectorLayer):void {
  305. vectorlayers[layer.name]=layer;
  306. vectorbg.addChild(layer.paint);
  307. }
  308. public function removeVectorLayer(layer:VectorLayer):void {
  309. if (!layer) return;
  310. layer.blank();
  311. vectorbg.removeChild(layer.paint);
  312. delete vectorlayers[layer.name];
  313. }
  314. public function findVectorLayer(name:String):VectorLayer {
  315. for each (var layer:VectorLayer in vectorlayers) {
  316. if (layer.name==name) { return layer; }
  317. }
  318. return null;
  319. }
  320. // ------------------------------------------------------------------------------------------
  321. // Redraw all items, zoom in and out
  322. public function updateEntityUIs(redraw:Boolean,remove:Boolean):void {
  323. paint.updateEntityUIs(connection.getObjectsByBbox(edge_l, edge_r, edge_t, edge_b), redraw, remove);
  324. for each (var v:VectorLayer in vectorlayers) {
  325. v.paint.updateEntityUIs(v.getObjectsByBbox(edge_l, edge_r, edge_t, edge_b), redraw, remove);
  326. }
  327. }
  328. /** Redraw everything, including in every vector layer. */
  329. public function redraw():void {
  330. paint.redraw();
  331. for each (var v:VectorLayer in vectorlayers) { v.paint.redraw(); }
  332. }
  333. /** Redraw POI's, including in every vector layer. */
  334. public function redrawPOIs():void {
  335. paint.redrawPOIs();
  336. for each (var v:VectorLayer in vectorlayers) { v.paint.redrawPOIs(); }
  337. }
  338. /** Increase scale. */
  339. public function zoomIn():void {
  340. if (scale==MAXSCALE) { return; }
  341. changeScale(scale+1);
  342. }
  343. /** Decrease scale. */
  344. public function zoomOut():void {
  345. if (scale==MINSCALE) { return; }
  346. changeScale(scale-1);
  347. }
  348. private function changeScale(newscale:uint):void {
  349. scale=newscale;
  350. this.dispatchEvent(new MapEvent(MapEvent.SCALE, {scale:scale}));
  351. scalefactor=MASTERSCALE/Math.pow(2,13-scale);
  352. updateCoordsFromLatLon((edge_t+edge_b)/2,(edge_l+edge_r)/2); // recentre
  353. tileset.changeScale(scale);
  354. updateEntityUIs(true,true);
  355. download();
  356. }
  357. /** Switch to new MapCSS. */
  358. public function setStyle(url:String):void {
  359. styleurl=url;
  360. if (paint) {
  361. paint.ruleset=new RuleSet(MINSCALE,MAXSCALE,redraw,redrawPOIs);
  362. paint.ruleset.loadFromCSS(url);
  363. }
  364. }
  365. /** Select a new background imagery. */
  366. public function setBackground(bg:Object):void {
  367. tileparams=bg;
  368. if (tileset) { tileset.init(bg, bg.url!=''); }
  369. }
  370. /** Set background dimming on/off. */
  371. public function setDimming(dim:Boolean):void {
  372. if (tileset) { tileset.setDimming(dim); }
  373. }
  374. /** Return background dimming. */
  375. public function getDimming():Boolean {
  376. if (tileset) { return tileset.getDimming(); }
  377. return true;
  378. }
  379. /** Set background sharpening on/off. */
  380. public function setSharpen(sharpen:Boolean):void {
  381. if (tileset) { tileset.setSharpen(sharpen); }
  382. }
  383. /** Return background sharpening. */
  384. public function getSharpen():Boolean {
  385. if (tileset) { return tileset.getSharpen(); }
  386. return false;
  387. }
  388. // ------------------------------------------------------------------------------------------
  389. // Export (experimental)
  390. // ** just a bit of fun for now!
  391. // really needs to take a bbox, and make sure that the image is correctly cropped/resized
  392. // to that area (will probably require creating a new DisplayObject with a different origin
  393. // and mask)
  394. /*
  395. public function export():void {
  396. trace("size is "+this.width+","+this.height);
  397. var jpgSource:BitmapData = new BitmapData(800,800); // (this.width, this.height);
  398. jpgSource.draw(this);
  399. var jpgEncoder:JPGEncoder = new JPGEncoder(85);
  400. var jpgStream:ByteArray = jpgEncoder.encode(jpgSource);
  401. var fileRef:FileReference = new FileReference();
  402. // fileRef.save(jpgStream,'map.jpeg');
  403. }
  404. */
  405. // ==========================================================================================
  406. // Events
  407. // ------------------------------------------------------------------------------------------
  408. // Mouse events
  409. /** Should map be allowed to pan? */
  410. public function set draggable(draggable:Boolean):void {
  411. _draggable=draggable;
  412. dragstate=NOT_DRAGGING;
  413. }
  414. /** Prepare for being dragged by recording start time and location of mouse. */
  415. public function mouseDownHandler(event:MouseEvent):void {
  416. if (!_draggable) { return; }
  417. dragstate=NOT_MOVED;
  418. lastxmouse=stage.mouseX; downX=stage.mouseX;
  419. lastymouse=stage.mouseY; downY=stage.mouseY;
  420. downTime=new Date().getTime();
  421. }
  422. /** Respond to mouse up by possibly moving map. */
  423. public function mouseUpHandler(event:MouseEvent=null):void {
  424. if (dragstate==DRAGGING) { moveMap(x,y); }
  425. dragstate=NOT_DRAGGING;
  426. }
  427. /** Respond to mouse movement, dragging the map if tolerance threshold met. */
  428. public function mouseMoveHandler(event:MouseEvent):void {
  429. if (!_draggable) { return; }
  430. if (dragstate==NOT_DRAGGING) {
  431. this.dispatchEvent(new MapEvent(MapEvent.MOUSE_MOVE, { x: coord2lon(mouseX), y: coord2lat(mouseY) }));
  432. return;
  433. }
  434. if (dragstate==NOT_MOVED) {
  435. if (new Date().getTime()-downTime<300) {
  436. if (Math.abs(downX-stage.mouseX)<=TOLERANCE && Math.abs(downY-stage.mouseY)<=TOLERANCE ) return;
  437. } else {
  438. if (Math.abs(downX-stage.mouseX)<=TOLERANCE/2 && Math.abs(downY-stage.mouseY)<=TOLERANCE/2) return;
  439. }
  440. dragstate=DRAGGING;
  441. }
  442. setScrollRectXY(getX()+stage.mouseX-lastxmouse,getY()+stage.mouseY-lastymouse);
  443. lastxmouse=stage.mouseX; lastymouse=stage.mouseY;
  444. setCentre();
  445. }
  446. // ------------------------------------------------------------------------------------------
  447. // Do every frame
  448. private function everyFrame(event:Event):void {
  449. if (tileset) { tileset.serviceQueue(); }
  450. }
  451. // ------------------------------------------------------------------------------------------
  452. // Miscellaneous events
  453. /** Respond to cursor movements and zoom in/out.*/
  454. public function keyUpHandler(event:KeyboardEvent):void {
  455. if (event.target is TextField) return; // not meant for us
  456. switch (event.keyCode) {
  457. case Keyboard.PAGE_UP: zoomIn(); break; // Page Up - zoom in
  458. case Keyboard.PAGE_DOWN: zoomOut(); break; // Page Down - zoom out
  459. case Keyboard.LEFT: moveMap(mapwidth/2,0); break; // left cursor
  460. case Keyboard.UP: moveMap(0,mapheight/2); break; // up cursor
  461. case Keyboard.RIGHT: moveMap(-mapwidth/2,0); break; // right cursor
  462. case Keyboard.DOWN: moveMap(0,-mapheight/2); break; // down cursor
  463. }
  464. }
  465. // ------------------------------------------------------------------------------------------
  466. // Debugging
  467. public function clearDebug():void {
  468. if (!Globals.vars.hasOwnProperty('debug')) return;
  469. Globals.vars.debug.text='';
  470. }
  471. public function addDebug(text:String):void {
  472. trace(text);
  473. if (!Globals.vars.hasOwnProperty('debug')) return;
  474. if (!Globals.vars.debug.visible) return;
  475. Globals.vars.debug.appendText(text+"\n");
  476. Globals.vars.debug.scrollV=Globals.vars.debug.maxScrollV;
  477. }
  478. }
  479. }