1 package net.systemeD.halcyon.connection {
5 import flash.events.EventDispatcher;
6 import flash.events.Event;
7 import net.systemeD.halcyon.Globals;
8 import net.systemeD.halcyon.connection.actions.*;
9 import net.systemeD.halcyon.AttentionEvent;
10 import net.systemeD.halcyon.MapEvent;
12 public class Connection extends EventDispatcher {
14 private static var connectionInstance:Connection = null;
16 protected static var policyURL:String;
17 protected static var apiBaseURL:String;
18 protected static var params:Object;
20 public static function getConnection(initparams:Object=null):Connection {
21 if ( connectionInstance == null ) {
23 params = initparams == null ? new Object() : initparams;
24 policyURL = getParam("policy", "http://127.0.0.1:3000/api/crossdomain.xml");
25 apiBaseURL = getParam("api", "http://127.0.0.1:3000/api/0.6/");
26 var connectType:String = getParam("connection", "XML");
28 if ( connectType == "XML" )
29 connectionInstance = new XMLConnection();
30 else if ( connectType == "OSM" )
31 connectionInstance = new OSMConnection();
33 connectionInstance = new AMFConnection();
35 return connectionInstance;
38 public static function getParam(name:String, defaultValue:String):String {
39 return params[name] == null ? defaultValue : params[name];
42 public function get apiBase():String {
46 public static function get serverName():String {
47 return getParam("serverName", "Localhost");
50 public static function getConnectionInstance():Connection {
51 return connectionInstance;
54 public function getEnvironment(responder:Responder):void {}
57 public static var LOAD_STARTED:String = "load_started";
58 public static var LOAD_COMPLETED:String = "load_completed";
59 public static var SAVE_STARTED:String = "save_started";
60 public static var SAVE_COMPLETED:String = "save_completed";
61 public static var DATA_DIRTY:String = "data_dirty";
62 public static var DATA_CLEAN:String = "data_clean";
63 public static var NEW_CHANGESET:String = "new_changeset";
64 public static var NEW_CHANGESET_ERROR:String = "new_changeset_error";
65 public static var NEW_NODE:String = "new_node";
66 public static var NEW_WAY:String = "new_way";
67 public static var NEW_RELATION:String = "new_relation";
68 public static var NEW_POI:String = "new_poi";
69 public static var NODE_RENUMBERED:String = "node_renumbered";
70 public static var WAY_RENUMBERED:String = "way_renumbered";
71 public static var RELATION_RENUMBERED:String = "relation_renumbered";
72 public static var TAG_CHANGED:String = "tag_change";
73 public static var NODE_MOVED:String = "node_moved";
74 public static var NODE_ALTERED:String = "node_altered";
75 public static var WAY_NODE_ADDED:String = "way_node_added";
76 public static var WAY_NODE_REMOVED:String = "way_node_removed";
77 public static var WAY_REORDERED:String = "way_reordered";
78 public static var ENTITY_DRAGGED:String = "entity_dragged";
79 public static var NODE_DELETED:String = "node_deleted";
80 public static var WAY_DELETED:String = "way_deleted";
81 public static var RELATION_DELETED:String = "relation_deleted";
82 public static var RELATION_MEMBER_ADDED:String = "relation_member_added";
83 public static var RELATION_MEMBER_REMOVED:String = "relation_member_deleted";
84 public static var ADDED_TO_RELATION:String = "added_to_relation";
85 public static var REMOVED_FROM_RELATION:String = "removed_from_relation";
86 public static var SUSPEND_REDRAW:String = "suspend_redraw";
87 public static var RESUME_REDRAW:String = "resume_redraw";
88 public static var TRACES_LOADED:String = "traces_loaded";
90 // store the data we download
91 private var negativeID:Number = -1;
92 private var nodes:Object = {};
93 private var ways:Object = {};
94 private var relations:Object = {};
95 private var pois:Array = [];
96 private var changeset:Changeset = null;
97 private var changesetUpdated:Number;
98 private var modified:Boolean = false;
99 public var nodecount:int=0;
100 public var waycount:int=0;
101 public var relationcount:int=0;
102 private var traces:Array = [];
103 private var nodePositions:Object = {};
104 protected var traces_loaded:Boolean = false;
106 protected function get nextNegative():Number {
110 protected function setNode(node:Node, queue:Boolean):void {
111 if (!nodes[node.id]) { nodecount++; }
112 nodes[node.id] = node;
114 if (node.loaded) { sendEvent(new EntityEvent(NEW_NODE, node),queue); }
117 protected function setWay(way:Way, queue:Boolean):void {
118 if (!ways[way.id] && way.loaded) { waycount++; }
120 if (way.loaded) { sendEvent(new EntityEvent(NEW_WAY, way),queue); }
123 protected function setRelation(relation:Relation, queue:Boolean):void {
124 if (!relations[relation.id]) { relationcount++; }
125 relations[relation.id] = relation;
126 if (relation.loaded) { sendEvent(new EntityEvent(NEW_RELATION, relation),queue); }
129 protected function setOrUpdateNode(newNode:Node, queue:Boolean):void {
130 if (nodes[newNode.id]) {
131 var wasDeleted:Boolean=nodes[newNode.id].isDeleted();
132 nodes[newNode.id].update(newNode.version, newNode.getTagsHash(), true, newNode.parentsLoaded, newNode.lat, newNode.lon, newNode.uid, newNode.timestamp);
133 if (wasDeleted) sendEvent(new EntityEvent(NEW_NODE, nodes[newNode.id]), false);
135 setNode(newNode, queue);
139 protected function renumberNode(oldID:Number, newID:Number, version:uint):void {
140 var node:Node=nodes[oldID];
141 if (oldID!=newID) { removeDupe(node); }
142 node.renumber(newID, version);
143 if (oldID==newID) return; // if only a version change, return
146 if (node.loaded) { sendEvent(new EntityRenumberedEvent(NODE_RENUMBERED, node, oldID),false); }
150 protected function renumberWay(oldID:Number, newID:Number, version:uint):void {
151 var way:Way=ways[oldID];
152 way.renumber(newID, version);
153 if (oldID==newID) return;
155 if (way.loaded) { sendEvent(new EntityRenumberedEvent(WAY_RENUMBERED, way, oldID),false); }
159 protected function renumberRelation(oldID:Number, newID:Number, version:uint):void {
160 var relation:Relation=relations[oldID];
161 relation.renumber(newID, version);
162 if (oldID==newID) return;
163 relations[newID] = relation;
164 if (relation.loaded) { sendEvent(new EntityRenumberedEvent(RELATION_RENUMBERED, relation, oldID),false); }
165 delete relations[oldID];
169 public function sendEvent(e:*,queue:Boolean):void {
170 // queue is only used for AMFConnection
174 public function registerPOI(node:Node):void {
175 if ( pois.indexOf(node) < 0 ) {
177 sendEvent(new EntityEvent(NEW_POI, node),false);
181 public function unregisterPOI(node:Node):void {
182 var index:uint = pois.indexOf(node);
184 pois.splice(index,1);
188 public function getNode(id:Number):Node {
192 public function getWay(id:Number):Way {
196 public function getRelation(id:Number):Relation {
197 return relations[id];
200 protected function findEntity(type:String, id:*):Entity {
201 var i:Number=Number(id);
202 switch (type.toLowerCase()) {
203 case 'node': return getNode(id);
204 case 'way': return getWay(id);
205 case 'relation': return getRelation(id);
206 default: return null;
210 // Remove data from Connection
211 // These functions are used only internally to stop redundant data hanging around
212 // (either because it's been deleted on the server, or because we have panned away
213 // and need to reduce memory usage)
215 protected function killNode(id:Number):void {
216 if (!nodes[id]) return;
217 nodes[id].dispatchEvent(new EntityEvent(Connection.NODE_DELETED, nodes[id]));
218 removeDupe(nodes[id]);
219 if (nodes[id].parentRelations.length>0) {
227 protected function killWay(id:Number):void {
228 if (!ways[id]) return;
229 ways[id].dispatchEvent(new EntityEvent(Connection.WAY_DELETED, ways[id]));
230 if (ways[id].parentRelations.length>0) {
238 protected function killRelation(id:Number):void {
239 if (!relations[id]) return;
240 relations[id].dispatchEvent(new EntityEvent(Connection.RELATION_DELETED, relations[id]));
241 if (relations[id].parentRelations.length>0) {
242 relations[id].nullify();
244 delete relations[id];
249 protected function killWayWithNodes(id:Number):void {
250 var way:Way=ways[id];
252 for (var i:uint=0; i<way.length; i++) {
254 if (node.isDirty) { continue; }
255 if (node.parentWays.length>1) {
256 node.removeParent(way);
264 protected function killEntity(entity:Entity):void {
265 if (entity is Way) { killWay(entity.id); }
266 else if (entity is Node) { killNode(entity.id); }
267 else if (entity is Relation) { killRelation(entity.id); }
270 public function createNode(tags:Object, lat:Number, lon:Number, performCreate:Function):Node {
271 var node:Node = new Node(nextNegative, 0, tags, true, lat, lon);
272 performCreate(new CreateEntityAction(node, setNode));
277 public function createWay(tags:Object, nodes:Array, performCreate:Function):Way {
278 var way:Way = new Way(nextNegative, 0, tags, true, nodes.concat());
279 performCreate(new CreateEntityAction(way, setWay));
284 public function createRelation(tags:Object, members:Array, performCreate:Function):Relation {
285 var relation:Relation = new Relation(nextNegative, 0, tags, true, members.concat());
286 performCreate(new CreateEntityAction(relation, setRelation));
291 public function getAllNodeIDs():Array {
293 for each (var node:Node in nodes)
298 public function getAllWayIDs():Array {
300 for each (var way:Way in ways)
305 public function getAllRelationIDs():Array {
307 for each (var relation:Relation in relations)
308 list.push(relation.id);
312 public function getMatchingRelationIDs(match:Object):Array {
315 for each (var relation:Relation in relations) {
317 if (relation.deleted) { ok=false; }
318 for (var k:String in match) {
319 if (!relation.getTagsHash()[k] || relation.getTagsHash()[k]!=match[k]) { ok=false; }
321 if (ok) { list.push(relation.id); }
326 public function getObjectsByBbox(left:Number, right:Number, top:Number, bottom:Number):Object {
327 var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [] };
328 for each (var way:Way in ways) {
329 if (way.within(left,right,top,bottom)) { o.waysInside.push(way); }
330 else { o.waysOutside.push(way); }
332 for each (var poi:Node in pois) {
333 if (poi.within(left,right,top,bottom)) { o.poisInside.push(poi); }
334 else { o.poisOutside.push(poi); }
339 public function purgeOutside(left:Number, right:Number, top:Number, bottom:Number):void {
340 for each (var way:Way in ways) {
341 if (!way.within(left,right,top,bottom) && !way.isDirty && !way.locked && !way.hasLockedNodes()) {
342 killWayWithNodes(way.id);
345 for each (var poi:Node in pois) {
346 if (!poi.within(left,right,top,bottom) && !poi.isDirty && !poi.locked) {
350 // ** should purge relations too, if none of their members are on-screen
353 public function markDirty():void {
354 if (!modified) { dispatchEvent(new Event(DATA_DIRTY)); }
357 public function markClean():void {
358 if (modified) { dispatchEvent(new Event(DATA_CLEAN)); }
361 public function get isDirty():Boolean {
365 // Changeset tracking
367 protected function setActiveChangeset(changeset:Changeset):void {
368 this.changeset = changeset;
369 changesetUpdated = new Date().getTime();
370 sendEvent(new EntityEvent(NEW_CHANGESET, changeset),false);
373 protected function freshenActiveChangeset():void {
374 changesetUpdated = new Date().getTime();
377 protected function closeActiveChangeset():void {
381 public function getActiveChangeset():Changeset {
382 if (changeset && (new Date().getTime()) > (changesetUpdated+58*60*1000)) {
383 closeActiveChangeset();
388 protected function addTrace(t:Object):void {
392 protected function clearTraces():void {
396 public function getTraces():Array {
400 public function addDupe(node:Node):void {
401 if (getNode(node.id) != node) { return; } // make sure it's on this connection
402 var a:String = node.lat+","+node.lon;
403 if(!nodePositions[a]) {
404 nodePositions[a] = [];
406 nodePositions[a].push(node);
407 if (nodePositions[a].length > 1) { // don't redraw if it's the only node in town
408 for each (var n:Node in nodePositions[a]) {
409 n.dispatchEvent(new Event(Connection.NODE_ALTERED));
414 public function removeDupe(node:Node):void {
415 if (getNode(node.id) != node) { return; } // make sure it's on this connection
416 var a:String = node.lat+","+node.lon;
417 var redraw:Boolean=node.isDupe();
418 var dupes:Array = [];
419 for each (var dupe:Node in nodePositions[a]) {
420 if (dupe!=node) { dupes.push(dupe); }
422 nodePositions[a] = dupes;
423 for each (var n:Node in nodePositions[a]) { // redraw any nodes remaining
424 n.dispatchEvent(new Event(Connection.NODE_ALTERED));
426 if (redraw) { node.dispatchEvent(new Event(Connection.NODE_ALTERED)); } //redraw the one being moved
429 public function nodesAtPosition(lat:Number, lon:Number):uint {
430 if (nodePositions[lat+","+lon]) {
431 return nodePositions[lat+","+lon].length;
436 public function getNodesAtPosition(lat:Number, lon:Number):Array {
437 if (nodePositions[lat+","+lon]) {
438 return nodePositions[lat+","+lon];
445 protected function throwConflictError(entity:Entity,serverVersion:uint,message:String):void {
446 dispatchEvent(new MapEvent(MapEvent.ERROR, {
447 message: "An item you edited has been changed by another mapper. Download their version and try again? (The server said: "+message+")",
448 yes: function():void { revertBeforeUpload(entity) },
449 no: cancelUpload }));
450 // ** FIXME: this should also offer the choice of 'overwrite?'
452 protected function throwAlreadyDeletedError(entity:Entity,message:String):void {
453 dispatchEvent(new MapEvent(MapEvent.ERROR, {
454 message: "You tried to delete something that's already been deleted. Forget it and try again? (The server said: "+message+")",
455 yes: function():void { deleteBeforeUpload(entity) },
456 no: cancelUpload }));
458 protected function throwInUseError(entity:Entity,message:String):void {
459 dispatchEvent(new MapEvent(MapEvent.ERROR, {
460 message: "You tried to delete something that's since been used elsewhere. Restore it and try again? (The server said: "+message+")",
461 yes: function():void { revertBeforeUpload(entity) },
462 no: cancelUpload }));
464 protected function throwEntityError(entity:Entity,message:String):void {
465 dispatchEvent(new MapEvent(MapEvent.ERROR, {
466 message: "There is a problem with your changes which needs to be fixed before you can save: "+message+". Click 'OK' to see the offending item.",
467 ok: function():void { goToEntity(entity) } }));
469 protected function throwChangesetError(message:String):void {
470 dispatchEvent(new MapEvent(MapEvent.ERROR, {
471 message: "The changeset in which you're saving changes is no longer valid. Start a new one and retry? (The server said: "+message+")",
472 yes: retryUploadWithNewChangeset,
473 no: cancelUpload }));
475 protected function throwBugError(message:String):void {
476 dispatchEvent(new MapEvent(MapEvent.ERROR, {
477 message: "An unexpected error occurred, probably due to a bug in Potlatch 2. Do you want to retry? (The server said: "+message+")",
479 no: cancelUpload }));
481 protected function throwServerError(message:String):void {
482 dispatchEvent(new MapEvent(MapEvent.ERROR, {
483 message: "A server error occurred. Do you want to retry? (The server said: "+message+")",
485 no: cancelUpload }));
488 public function retryUpload(e:Event=null):void {
489 removeEventListener(LOAD_COMPLETED,retryUpload);
492 public function cancelUpload():void {
495 public function retryUploadWithNewChangeset():void {
496 // ** FIXME: we need to move the create-changeset-then-upload logic out of SaveDialog
498 public function goToEntity(entity:Entity):void {
499 dispatchEvent(new AttentionEvent(AttentionEvent.ATTENTION, entity));
501 public function revertBeforeUpload(entity:Entity):void {
502 addEventListener(LOAD_COMPLETED,retryUpload);
505 public function deleteBeforeUpload(entity:Entity):void {
506 var a:CompositeUndoableAction = new CompositeUndoableAction("Delete refs");
507 entity.remove(a.push);
513 // these are functions that the Connection implementation is expected to
514 // provide. This class has some generic helpers for the implementation.
515 public function loadBbox(left:Number, right:Number,
516 top:Number, bottom:Number):void {
518 public function loadEntity(entity:Entity):void {}
519 public function setAuthToken(id:Object):void {}
520 public function setAccessToken(key:String, secret:String):void {}
521 public function createChangeset(tags:Object):void {}
522 public function closeChangeset():void {}
523 public function uploadChanges():void {}
524 public function fetchUserTraces(refresh:Boolean=false):void {}
525 public function fetchTrace(id:Number, callback:Function):void {}
526 public function hasAccessToken():Boolean { return false; }