change relation.appendMember to use the undo system, and update various places as...
[potlatch2.git] / net / systemeD / halcyon / connection / Connection.as
1 package net.systemeD.halcyon.connection {
2
3     import flash.net.*;
4
5     import flash.events.EventDispatcher;
6     import flash.events.Event;
7         import net.systemeD.halcyon.Globals;
8         import net.systemeD.halcyon.connection.actions.*;
9
10         public class Connection extends EventDispatcher {
11
12         private static var connectionInstance:Connection = null;
13
14         protected static var policyURL:String;
15         protected static var apiBaseURL:String;
16         protected static var params:Object;
17
18         public static function getConnection(initparams:Object=null):Connection {
19             if ( connectionInstance == null ) {
20             
21                 params = initparams == null ? new Object() : initparams;
22                 policyURL = getParam("policy", "http://127.0.0.1:3000/api/crossdomain.xml");
23                 apiBaseURL = getParam("api", "http://127.0.0.1:3000/api/0.6/");
24                 var connectType:String = getParam("connection", "XML");
25                 
26                 if ( connectType == "XML" )
27                     connectionInstance = new XMLConnection();
28                 else if ( connectType == "OSM" )
29                     connectionInstance = new OSMConnection();
30                 else
31                     connectionInstance = new AMFConnection();
32             }
33             return connectionInstance;
34         }
35
36         public static function getParam(name:String, defaultValue:String):String {
37             return params[name] == null ? defaultValue : params[name];
38         }
39
40         public function get apiBase():String {
41             return apiBaseURL;
42         }
43
44         public static function get serverName():String {
45             return getParam("serverName", "Localhost");
46         }
47                 
48                 public static function getConnectionInstance():Connection {
49             return connectionInstance;
50                 }
51
52                 public function getEnvironment(responder:Responder):void {}
53
54         // connection events
55         public static var LOAD_STARTED:String = "load_started";
56         public static var LOAD_COMPLETED:String = "load_completed";
57         public static var SAVE_STARTED:String = "save_started";
58         public static var SAVE_COMPLETED:String = "save_completed";
59         public static var DATA_DIRTY:String = "data_dirty";
60         public static var DATA_CLEAN:String = "data_clean";
61         public static var NEW_CHANGESET:String = "new_changeset";
62         public static var NEW_CHANGESET_ERROR:String = "new_changeset_error";
63         public static var NEW_NODE:String = "new_node";
64         public static var NEW_WAY:String = "new_way";
65         public static var NEW_RELATION:String = "new_relation";
66         public static var NEW_POI:String = "new_poi";
67         public static var NODE_RENUMBERED:String = "node_renumbered";
68         public static var WAY_RENUMBERED:String = "way_renumbered";
69         public static var RELATION_RENUMBERED:String = "relation_renumbered";
70         public static var TAG_CHANGED:String = "tag_change";
71         public static var NODE_MOVED:String = "node_moved";
72         public static var NODE_ALTERED:String = "node_altered";
73         public static var WAY_NODE_ADDED:String = "way_node_added";
74         public static var WAY_NODE_REMOVED:String = "way_node_removed";
75         public static var WAY_REORDERED:String = "way_reordered";
76         public static var WAY_DRAGGED:String = "way_dragged";
77                 public static var NODE_DELETED:String = "node_deleted";
78                 public static var WAY_DELETED:String = "way_deleted";
79                 public static var RELATION_DELETED:String = "relation_deleted";
80                 public static var RELATION_MEMBER_ADDED:String = "relation_member_added";
81                 public static var RELATION_MEMBER_REMOVED:String = "relation_member_deleted";
82                 public static var ADDED_TO_RELATION:String = "added_to_relation";
83                 public static var REMOVED_FROM_RELATION:String = "removed_from_relation";
84                 public static var SUSPEND_REDRAW:String = "suspend_redraw";
85                 public static var RESUME_REDRAW:String = "resume_redraw";
86         public static var TRACES_LOADED:String = "traces_loaded";
87
88         // store the data we download
89         private var negativeID:Number = -1;
90         private var nodes:Object = {};
91         private var ways:Object = {};
92         private var relations:Object = {};
93         private var pois:Array = [];
94         private var changeset:Changeset = null;
95                 private var changesetUpdated:Number;
96                 private var modified:Boolean = false;
97                 public var nodecount:int=0;
98                 public var waycount:int=0;
99                 public var relationcount:int=0;
100         private var traces:Array = [];
101         private var nodePositions:Object = {};
102         protected var traces_loaded:Boolean = false;
103
104         protected function get nextNegative():Number {
105             return negativeID--;
106         }
107
108         protected function setNode(node:Node, queue:Boolean):void {
109                         if (!nodes[node.id]) { nodecount++; }
110             nodes[node.id] = node;
111             addDupe(node);
112             if (node.loaded) { sendEvent(new EntityEvent(NEW_NODE, node),queue); }
113         }
114
115         protected function setWay(way:Way, queue:Boolean):void {
116                         if (!ways[way.id] && way.loaded) { waycount++; }
117             ways[way.id] = way;
118             if (way.loaded) { sendEvent(new EntityEvent(NEW_WAY, way),queue); }
119         }
120
121         protected function setRelation(relation:Relation, queue:Boolean):void {
122                         if (!relations[relation.id]) { relationcount++; }
123             relations[relation.id] = relation;
124             if (relation.loaded) { sendEvent(new EntityEvent(NEW_RELATION, relation),queue); }
125         }
126
127                 protected function setOrUpdateNode(newNode:Node, queue:Boolean):void {
128                 if (nodes[newNode.id]) {
129                                 var wasDeleted:Boolean=nodes[newNode.id].isDeleted();
130                                 nodes[newNode.id].update(newNode.version, newNode.getTagsHash(), true, newNode.lat, newNode.lon, newNode.uid, newNode.timestamp);
131                                 if (wasDeleted) sendEvent(new EntityEvent(NEW_NODE, nodes[newNode.id]), false);
132                         } else {
133                                 setNode(newNode, queue);
134                         }
135                 }
136
137                 protected function renumberNode(oldID:Number, newID:Number, version:uint):void {
138                         var node:Node=nodes[oldID];
139                         if (oldID!=newID) { removeDupe(node); }
140                         node.renumber(newID, version);
141                         if (oldID==newID) return;                                       // if only a version change, return
142                         nodes[newID]=node;
143                         addDupe(node);
144                         if (node.loaded) { sendEvent(new EntityRenumberedEvent(NODE_RENUMBERED, node, oldID),false); }
145                         delete nodes[oldID];
146                 }
147
148                 protected function renumberWay(oldID:Number, newID:Number, version:uint):void {
149                         var way:Way=ways[oldID];
150                         way.renumber(newID, version);
151                         if (oldID==newID) return;
152                         ways[newID]=way;
153                         if (way.loaded) { sendEvent(new EntityRenumberedEvent(WAY_RENUMBERED, way, oldID),false); }
154                         delete ways[oldID];
155                 }
156
157                 protected function renumberRelation(oldID:Number, newID:Number, version:uint):void {
158                         var relation:Relation=relations[oldID];
159                         relation.renumber(newID, version);
160                         if (oldID==newID) return;
161                         relations[newID] = relation;
162                         if (relation.loaded) { sendEvent(new EntityRenumberedEvent(RELATION_RENUMBERED, relation, oldID),false); }
163                         delete relations[oldID];
164                 }
165
166
167                 public function sendEvent(e:*,queue:Boolean):void {
168                         // queue is only used for AMFConnection
169                         dispatchEvent(e);
170                 }
171
172         public function registerPOI(node:Node):void {
173             if ( pois.indexOf(node) < 0 ) {
174                 pois.push(node);
175                 sendEvent(new EntityEvent(NEW_POI, node),false);
176             }
177         }
178
179         public function unregisterPOI(node:Node):void {
180             var index:uint = pois.indexOf(node);
181             if ( index >= 0 ) {
182                 pois.splice(index,1);
183             }
184         }
185
186         public function getNode(id:Number):Node {
187             return nodes[id];
188         }
189
190         public function getWay(id:Number):Way {
191             return ways[id];
192         }
193
194         public function getRelation(id:Number):Relation {
195             return relations[id];
196         }
197
198                 // Remove data from Connection
199                 // These functions are used only internally to stop redundant data hanging around
200                 // (either because it's been deleted on the server, or because we have panned away
201                 //  and need to reduce memory usage)
202
203                 protected function killNode(id:Number):void {
204                         if (!nodes[id]) return;
205             nodes[id].dispatchEvent(new EntityEvent(Connection.NODE_DELETED, nodes[id]));
206                         removeDupe(nodes[id]);
207                         if (nodes[id].parentRelations.length>0) {
208                                 nodes[id].nullify();
209                         } else {
210                                 delete nodes[id];
211                         }
212                         nodecount--;
213                 }
214
215                 protected function killWay(id:Number):void {
216                         if (!ways[id]) return;
217             ways[id].dispatchEvent(new EntityEvent(Connection.WAY_DELETED, ways[id]));
218                         if (ways[id].parentRelations.length>0) {
219                                 ways[id].nullify();
220                         } else {
221                                 delete ways[id];
222                         }
223                         waycount--;
224                 }
225
226                 protected function killRelation(id:Number):void {
227                         if (!relations[id]) return;
228             relations[id].dispatchEvent(new EntityEvent(Connection.RELATION_DELETED, relations[id]));
229                         if (relations[id].parentRelations.length>0) {
230                                 relations[id].nullify();
231                         } else {
232                                 delete relations[id];
233                         }
234                         relationcount--;
235                 }
236
237                 protected function killWayWithNodes(id:Number):void {
238                         var way:Way=ways[id];
239                         var node:Node;
240                         for (var i:uint=0; i<way.length; i++) {
241                                 node=way.getNode(i);
242                                 if (node.isDirty) { continue; }
243                                 if (node.parentWays.length>1) {
244                                         node.removeParent(way);
245                                 } else {
246                                         killNode(node.id);
247                                 }
248                         }
249                         killWay(id);
250                 }
251                 
252
253
254         public function createNode(tags:Object, lat:Number, lon:Number, performCreate:Function):Node {
255             var node:Node = new Node(nextNegative, 0, tags, true, lat, lon);
256             performCreate(new CreateEntityAction(node, setNode));
257                         //markDirty();
258             return node;
259         }
260
261         public function createWay(tags:Object, nodes:Array, performCreate:Function):Way {
262             var way:Way = new Way(nextNegative, 0, tags, true, nodes.concat());
263             performCreate(new CreateEntityAction(way, setWay));
264                         //markDirty();
265             return way;
266         }
267
268         public function createRelation(tags:Object, members:Array, performCreate:Function):Relation {
269             var relation:Relation = new Relation(nextNegative, 0, tags, true, members.concat());
270             performCreate(new CreateEntityAction(relation, setRelation));
271                         //markDirty();
272             return relation;
273         }
274
275         public function getAllNodeIDs():Array {
276             var list:Array = [];
277             for each (var node:Node in nodes)
278                 list.push(node.id);
279             return list;
280         }
281
282         public function getAllWayIDs():Array {
283             var list:Array = [];
284             for each (var way:Way in ways)
285                 list.push(way.id);
286             return list;
287         }
288
289         public function getAllRelationIDs():Array {
290             var list:Array = [];
291             for each (var relation:Relation in relations)
292                 list.push(relation.id);
293             return list;
294         }
295
296         public function getMatchingRelationIDs(match:Object):Array {
297             var list:Array = [];
298                         var ok:Boolean;
299             for each (var relation:Relation in relations) {
300                                 ok=true;
301                                 if (relation.deleted) { ok=false; }
302                                 for (var k:String in match) {
303                                         if (!relation.getTagsHash()[k] || relation.getTagsHash()[k]!=match[k]) { ok=false; }
304                                 }
305                                 if (ok) { list.push(relation.id); }
306                         }
307             return list;
308         }
309
310                 public function getObjectsByBbox(left:Number, right:Number, top:Number, bottom:Number):Object {
311                         var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [] };
312                         for each (var way:Way in ways) {
313                                 if (way.within(left,right,top,bottom)) { o.waysInside.push(way); }
314                                                                   else { o.waysOutside.push(way); }
315                         }
316                         for each (var poi:Node in pois) {
317                                 if (poi.within(left,right,top,bottom)) { o.poisInside.push(poi); }
318                                                                   else { o.poisOutside.push(poi); }
319                         }
320                         return o;
321                 }
322
323                 public function purgeOutside(left:Number, right:Number, top:Number, bottom:Number):void {
324                         for each (var way:Way in ways) {
325                                 if (!way.within(left,right,top,bottom) && !way.isDirty && !way.locked && !way.hasLockedNodes()) {
326                                         killWayWithNodes(way.id);
327                                 }
328                         }
329                         for each (var poi:Node in pois) {
330                                 if (!poi.within(left,right,top,bottom) && !poi.isDirty && !poi.locked) {
331                                         killNode(poi.id);
332                                 }
333                         }
334                         // ** should purge relations too, if none of their members are on-screen
335                 }
336
337                 public function markDirty():void {
338             if (!modified) { dispatchEvent(new Event(DATA_DIRTY)); }
339                         modified=true;
340                 }
341                 public function markClean():void {
342             if (modified) { dispatchEvent(new Event(DATA_CLEAN)); }
343                         modified=false;
344                 }
345                 public function get isDirty():Boolean {
346                         return modified;
347                 }
348
349                 // Changeset tracking
350
351         protected function setActiveChangeset(changeset:Changeset):void {
352             this.changeset = changeset;
353                         changesetUpdated = new Date().getTime();
354             sendEvent(new EntityEvent(NEW_CHANGESET, changeset),false);
355         }
356
357                 protected function freshenActiveChangeset():void {
358                         changesetUpdated = new Date().getTime();
359                 }
360                 
361                 protected function closeActiveChangeset():void {
362                         changeset = null;
363                 }
364         
365         public function getActiveChangeset():Changeset {
366                         if (changeset && (new Date().getTime()) > (changesetUpdated+58*60*1000)) {
367                                 closeActiveChangeset();
368                         }
369             return changeset;
370         }
371
372         protected function addTrace(t:Object):void {
373             traces.push(t);
374         }
375
376         protected function clearTraces():void {
377             traces = [];
378         }
379
380         public function getTraces():Array {
381             return traces;
382         }
383
384         public function addDupe(node:Node):void {
385             if (getNode(node.id) != node) { return; } // make sure it's on this connection
386             var a:String = node.lat+","+node.lon;
387             if(!nodePositions[a]) {
388               nodePositions[a] = [];
389             }
390             nodePositions[a].push(node);
391             if (nodePositions[a].length > 1) { // don't redraw if it's the only node in town
392               for each (var n:Node in nodePositions[a]) {
393                 n.dispatchEvent(new Event(Connection.NODE_ALTERED));
394               }
395             }
396         }
397
398         public function removeDupe(node:Node):void {
399             if (getNode(node.id) != node) { return; } // make sure it's on this connection
400             var a:String = node.lat+","+node.lon;
401             var redraw:Boolean=node.isDupe();
402             var dupes:Array = [];
403             for each (var dupe:Node in nodePositions[a]) {
404               if (dupe!=node) { dupes.push(dupe); }
405             }
406             nodePositions[a] = dupes;
407             for each (var n:Node in nodePositions[a]) { // redraw any nodes remaining
408               n.dispatchEvent(new Event(Connection.NODE_ALTERED));
409             }
410             if (redraw) { node.dispatchEvent(new Event(Connection.NODE_ALTERED)); } //redraw the one being moved
411         }
412
413         public function nodesAtPosition(lat:Number, lon:Number):uint {
414             if (nodePositions[lat+","+lon]) {
415               return nodePositions[lat+","+lon].length;
416             }
417             return 0;
418         }
419
420         // these are functions that the Connection implementation is expected to
421         // provide. This class has some generic helpers for the implementation.
422                 public function loadBbox(left:Number, right:Number,
423                                                                 top:Number, bottom:Number):void {
424             }
425             
426             public function setAuthToken(id:Object):void {}
427         public function setAccessToken(key:String, secret:String):void {}
428             public function createChangeset(tags:Object):void {}
429                 public function closeChangeset():void {}
430             public function uploadChanges():void {}
431         public function fetchUserTraces(refresh:Boolean=false):void {}
432         public function fetchTrace(id:Number, callback:Function):void {}
433         public function hasAccessToken():Boolean { return false; }
434     }
435
436 }
437