fix way splitting and node/UI renumbering
[potlatch2.git] / net / systemeD / halcyon / connection / XMLConnection.as
1 package net.systemeD.halcyon.connection {
2
3     import flash.events.*;
4
5         import flash.system.Security;
6         import flash.net.*;
7     import org.iotashan.oauth.*;
8
9
10         public class XMLConnection extends Connection {
11
12         //public var readConnection:NetConnection;
13
14                 public function XMLConnection() {
15
16                         if (Connection.policyURL!='')
17                 Security.loadPolicyFile(Connection.policyURL);
18             var oauthPolicy:String = Connection.getParam("oauth_policy", "");
19             if ( oauthPolicy != "" ) {
20                 trace(oauthPolicy);
21                 Security.loadPolicyFile(oauthPolicy);
22             }
23                 }
24                 
25                 override public function loadBbox(left:Number,right:Number,
26                                                                 top:Number,bottom:Number):void {
27             var mapVars:URLVariables = new URLVariables();
28             mapVars.bbox= left+","+bottom+","+right+","+top;
29
30             var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
31             mapRequest.data = mapVars;
32
33             var mapLoader:URLLoader = new URLLoader();
34             mapLoader.addEventListener(Event.COMPLETE, loadedMap);
35             mapLoader.addEventListener(IOErrorEvent.IO_ERROR, errorOnMapLoad);
36             mapLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, mapLoadStatus);
37             mapLoader.load(mapRequest);
38             dispatchEvent(new Event(LOAD_STARTED));
39                 }
40
41         private function parseTags(tagElements:XMLList):Object {
42             var tags:Object = {};
43             for each (var tagEl:XML in tagElements)
44                 tags[tagEl.@k] = tagEl.@v;
45             return tags;
46         }
47
48         private function errorOnMapLoad(event:Event):void {
49             trace("error loading map");
50         }
51         private function mapLoadStatus(event:HTTPStatusEvent):void {
52             trace("loading map status = "+event.status);
53         }
54         
55         private function loadedMap(event:Event):void {
56             dispatchEvent(new Event(LOAD_COMPLETED));
57
58             var map:XML = new XML(URLLoader(event.target).data);
59             var id:Number;
60             var version:uint;
61             var tags:Object;
62
63             for each(var nodeData:XML in map.node) {
64                 id = Number(nodeData.@id);
65                 version = uint(nodeData.@version);
66
67                 var node:Node = getNode(id);
68                 if ( node == null || !node.loaded ) {
69                     var lat:Number = Number(nodeData.@lat);
70                     var lon:Number = Number(nodeData.@lon);
71                     tags = parseTags(nodeData.tag);
72                     if ( node == null )
73                         setNode(new Node(id, version, tags, true, lat, lon),false);
74                     else {
75                         node.update(version, tags, true, lat, lon);
76                         sendEvent(new EntityEvent(NEW_NODE, node), false);
77                     }
78                 }
79             }
80
81             for each(var data:XML in map.way) {
82                 id = Number(data.@id);
83                 version = uint(data.@version);
84
85                 var way:Way = getWay(id);
86                 if ( way == null || !way.loaded ) {
87                     var nodes:Array = [];
88                     for each(var nd:XML in data.nd)
89                         nodes.push(getNode(Number(nd.@ref)));
90                     tags = parseTags(data.tag);
91                     if ( way == null )
92                         setWay(new Way(id, version, tags, true, nodes),false);
93                     else {
94                         way.update(version, tags, true, nodes);
95                         sendEvent(new EntityEvent(NEW_WAY, way), false);
96                     }
97                 }
98             }
99             
100             for each(var relData:XML in map.relation) {
101                 id = Number(relData.@id);
102                 version = uint(relData.@version);
103                 
104                 var rel:Relation = getRelation(id);
105                 if ( rel == null || !rel.loaded ) {
106                     tags = parseTags(relData.tag);
107                     var members:Array = [];
108                     for each(var memberXML:XML in relData.member) {
109                         var type:String = memberXML.@type.toLowerCase();
110                         var role:String = memberXML.@role;
111                         var memberID:Number = Number(memberXML.@ref);
112                         var member:Entity = null;
113                         if ( type == "node" ) {
114                             member = getNode(memberID);
115                             if ( member == null ) {
116                                 member = new Node(memberID,0,{},false,0,0);
117                                 setNode(Node(member),true);
118                             }
119                         } else if ( type == "way" ) {
120                             member = getWay(memberID);
121                             if (member == null) {
122                                 member = new Way(memberID,0,{},false,[]);
123                                 setWay(Way(member),true);
124                             }
125                         } else if ( type == "relation" ) {
126                             member = getRelation(memberID);
127                             if (member == null) {
128                                 member = new Relation(memberID,0,{},false,[]);
129                                 setRelation(Relation(member),true);
130                             }
131                         }
132                         
133                         if ( member != null )
134                             members.push(new RelationMember(member, role));
135                     }
136                     
137                     if ( rel == null )
138                         setRelation(new Relation(id, version, tags, true, members), false);
139                     else {
140                         rel.update(version,tags,true,members);
141                         sendEvent(new EntityEvent(NEW_RELATION, rel), false);
142                     }
143                 }
144             }
145             
146             registerPOINodes();
147         }
148         
149         protected function registerPOINodes():void {
150             for each (var nodeID:Number in getAllNodeIDs()) {
151                 var node:Node = getNode(nodeID);
152                 if (!node.hasParentWays)
153                     registerPOI(node);
154             }
155         }
156
157         protected var appID:OAuthConsumer;
158         protected var authToken:OAuthToken;
159         
160             override public function setAppID(id:Object):void {
161                 appID = OAuthConsumer(id);
162             }
163             
164             override public function setAuthToken(id:Object):void {
165                 authToken = OAuthToken(id);
166             }
167
168         private var httpStatus:int = 0;
169         
170         private function recordStatus(event:HTTPStatusEvent):void {
171             httpStatus = event.status;
172         }
173         
174         private var lastUploadedChangesetTags:Object;
175         
176         override public function createChangeset(tags:Object):void {
177             lastUploadedChangesetTags = tags;
178             
179                 var changesetXML:XML = <osm version="0.6"><changeset /></osm>;
180                 var changeset:XML = <changeset />;
181                 for (var tagKey:Object in tags) {
182               var tagXML:XML = <tag/>;
183               tagXML.@k = tagKey;
184               tagXML.@v = tags[tagKey];
185               changesetXML.changeset.appendChild(tagXML);
186             }        
187
188             // make an OAuth query
189             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
190             var url:String = Connection.apiBaseURL+"changeset/create";
191             //var params:Object = { _method: "PUT" };
192             var oauthRequest:OAuthRequest = new OAuthRequest("PUT", url, null, appID, authToken);
193             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
194
195             // build the actual request
196             var urlReq:URLRequest = new URLRequest(String(urlStr));
197             urlReq.method = "POST";
198             urlReq.data = changesetXML.toXMLString();
199             urlReq.contentType = "application/xml";
200             urlReq.requestHeaders = new Array(new URLRequestHeader("X_HTTP_METHOD_OVERRIDE", "PUT"));
201             var loader:URLLoader = new URLLoader();
202             loader.addEventListener(Event.COMPLETE, changesetCreateComplete);
203             loader.addEventListener(IOErrorEvent.IO_ERROR, changesetCreateError);
204             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
205                 loader.load(urlReq);
206             }
207
208         private function changesetCreateComplete(event:Event):void {
209             // response should be a Number changeset id
210             var id:Number = Number(URLLoader(event.target).data);
211             
212             // which means we now have a new changeset!
213             setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
214         }
215
216         private function changesetCreateError(event:IOErrorEvent):void {
217             dispatchEvent(new Event(NEW_CHANGESET_ERROR));
218         }
219         
220         override public function uploadChanges():void {
221             var changeset:Changeset = getActiveChangeset();
222             var upload:XML = <osmChange version="0.6"/>
223             upload.appendChild(addCreated(changeset, getAllNodeIDs, getNode, serialiseNode));
224             upload.appendChild(addCreated(changeset, getAllWayIDs, getWay, serialiseWay));
225             upload.appendChild(addCreated(changeset, getAllRelationIDs, getRelation, serialiseRelation));
226             upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
227             upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
228             upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
229
230             // *** TODO *** deleting items
231             
232             // now actually upload them
233             // make an OAuth query
234             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
235             var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
236             var oauthRequest:OAuthRequest = new OAuthRequest("POST", url, null, appID, authToken);
237             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
238
239             // build the actual request
240             var urlReq:URLRequest = new URLRequest(String(urlStr));
241             urlReq.method = "POST";
242             urlReq.data = upload.toXMLString();
243             urlReq.contentType = "text/xml";
244             var loader:URLLoader = new URLLoader();
245             loader.addEventListener(Event.COMPLETE, diffUploadComplete);
246             loader.addEventListener(IOErrorEvent.IO_ERROR, diffUploadError);
247             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
248                 loader.load(urlReq);
249                 
250                 dispatchEvent(new Event(SAVE_STARTED));
251         }
252
253         private function diffUploadComplete(event:Event):void {
254             // response should be XML describing the progress
255             var results:XML = new XML((URLLoader(event.target).data));
256             
257             for each( var update:XML in results.child("*") ) {
258                 var oldID:Number = Number(update.@old_id);
259                 var newID:Number = Number(update.@new_id);
260                 var version:uint = uint(update.@new_version);
261                 var type:String = update.name();
262                 
263                 var entity:Entity;
264                 if ( type == "node" ) entity = getNode(oldID);
265                 else if ( type == "way" ) entity = getWay(oldID);
266                 else if ( type == "relation" ) entity = getRelation(oldID);
267                 entity.markClean(newID, version);
268                 
269                 if ( oldID != newID ) {
270                     if ( type == "node" ) renumberNode(oldID, entity as Node, false);
271                     else if ( type == "way" ) renumberWay(oldID, entity as Way, false);
272                     else if ( type == "relation" ) renumberRelation(oldID, entity as Relation, false);
273                 }
274                 // *** TODO *** handle deleting
275             }
276
277                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
278         }
279
280         private function diffUploadError(event:IOErrorEvent):void {
281             trace("error "+URLLoader(event.target).data + " "+httpStatus+ " " + event.text);
282
283                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, false));
284         }
285
286         private function addCreated(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
287             var create:XML = <create version="0.6"/>
288             for each( var id:Number in getIDs() ) {
289                 if ( id >= 0 )
290                     continue;
291                     
292                 var entity:Object = get(id);
293                 var xml:XML = serialise(entity);
294                 xml.@changeset = changeset.id;
295                 create.appendChild(xml);
296             }
297             return create.hasComplexContent() ? create : <!-- blank create section -->;
298         }
299
300         private function addModified(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
301             var modify:XML = <modify version="0.6"/>
302             for each( var id:Number in getIDs() ) {
303                 var entity:Entity = get(id);
304                 // creates are already included
305                 if ( id < 0 || !entity.isDirty )
306                     continue;
307                     
308                 var xml:XML = serialise(entity);
309                 xml.@changeset = changeset.id;
310                 modify.appendChild(xml);
311             }
312             return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
313         }
314
315         private function serialiseNode(node:Node):XML {
316             var xml:XML = <node/>
317             serialiseEntity(node, xml);
318             xml.@lat = node.lat;
319             xml.@lon = node.lon;
320             return xml;
321         }
322
323         private function serialiseWay(way:Way):XML {
324             var xml:XML = <way/>
325             serialiseEntity(way, xml);
326             for ( var i:uint = 0; i < way.length; i++ ) {
327                 var nd:XML = <nd/>
328                 nd.@ref = way.getNode(i).id;
329                 xml.appendChild(nd);
330             }
331             return xml;
332         }
333
334         private function serialiseRelation(relation:Relation):XML {
335             var xml:XML = <relation/>
336             serialiseEntity(relation, xml);
337             for ( var i:uint = 0; i < relation.length; i++ ) {
338                 var relMember:RelationMember = relation.getMember(i);
339                 var member:XML = <member/>
340                 member.@ref = relMember.entity.id;
341                 member.@type = relMember.entity.getType();
342                 member.@role = relMember.role;
343                 xml.appendChild(member);
344             }
345             return xml;
346         }
347         
348         private function serialiseEntity(entity:Entity, xml:XML):void {
349             xml.@id = entity.id;
350             xml.@version = entity.version;
351             for each( var tag:Tag in entity.getTagArray() ) {
352               var tagXML:XML = <tag/>
353               tagXML.@k = tag.key;
354               tagXML.@v = tag.value;
355               xml.appendChild(tagXML);
356             }
357         }
358         }
359 }