28283f5 by James Michael DuPont at 2010-03-13 1
package com.cloudmade.mappingtool.model;
2
3
	import com.cloudmade.mappingtool.business.externalization.IXMLListSerializer;
4
	import com.cloudmade.mappingtool.business.externalization.IXMLSerializer;
5
	import com.cloudmade.mappingtool.business.externalization.osm.ListExternalizer;
6
	import com.cloudmade.mappingtool.business.externalization.osm.TagExternalizer;
7
	import com.cloudmade.mappingtool.business.externalization.osm.ValidTagsSerializer;
8
	import com.cloudmade.mappingtool.events.OSMProxyCommitErrorEvent;
9
	import com.cloudmade.mappingtool.events.OSMProxyLoadErrorEvent;
10
	import com.cloudmade.mappingtool.events.OSMProxyResultEvent;
11
	import com.cloudmade.mappingtool.events.OSMProxySaveEvent;
12
	import com.cloudmade.mappingtool.map.geo.LatLong;
13
	import com.cloudmade.mappingtool.map.geo.LatLongBounds;
14
	import com.cloudmade.mappingtool.map.model.Member;
15
	import com.cloudmade.mappingtool.map.model.Node;
16
	import com.cloudmade.mappingtool.map.model.Relation;
17
	import com.cloudmade.mappingtool.map.model.Tag;
18
	import com.cloudmade.mappingtool.map.model.Way;
19
	import com.cloudmade.mappingtool.model.osm.ElementDiff;
20
	import com.cloudmade.mappingtool.model.osm.OAuthAuthorizator;
21
	import com.cloudmade.mappingtool.model.osm.Transaction;
22
	import com.cloudmade.mappingtool.model.osm.TransactionQueue;
23
	
24
	import flash.errors.IOError;
25
	import flash.events.ErrorEvent;
26
	import flash.events.Event;
27
	import flash.events.EventDispatcher;
28
	import flash.events.HTTPStatusEvent;
29
	import flash.events.IOErrorEvent;
30
	import flash.events.SecurityErrorEvent;
31
	import flash.net.URLLoader;
32
	import flash.net.URLRequest;
33
	import flash.system.Security;
34
	import flash.utils.ByteArray;
35
	
36
	import mx.managers.CursorManager;
37
	
38
	/*[Event(name="save", type="com.cloudmade.mappingtool.events.OSMProxyResultEvent")]*/
39
	/*[Event(name="result", type="com.cloudmade.mappingtool.events.OSMProxyResultEvent")]*/
40
	/*[Event(name="saveFailed", type="com.cloudmade.mappingtool.events.OSMProxySaveEvent")]*/
41
	/*[Event(name="saveComplete", type="com.cloudmade.mappingtool.events.OSMProxySaveEvent")]*/
42
	/*[Event(name="loadError", type="com.cloudmade.mappingtool.events.OSMProxyLoadErrorEvent")]*/
43
	/*[Event(name="commitError", type="com.cloudmade.mappingtool.events.OSMProxyCommitErrorEvent")]*/
44
	/*[Event(name="loadingChanged", type="flash.events.Event")]*/
45
	
46
	// TODO: Use polymorphism.
47
	
48
	public final class OSMProxy extends EventDispatcher {
49
		
50
		public var changesetOpened(getChangesetOpened, null) : Bool ;
51
		public var loading(getLoading, null) : Bool ;
52
		/*[Embed(source="../../../../config/map.xml", mimeType="application/octet-stream")]*/
53
		CONFIG::local
54
		static var LOCAL_MAP:Class<Dynamic>;
55
		
56
		static var instance:OSMProxy = null;
57
		
58
		public static function getInstance():OSMProxy {
59
			if (instance == null) {
60
				instance = new OSMProxy(new SingletonEnforcer());
61
			}
62
			return instance;
63
		}
64
		
65
		CONFIG::public debug
66
		var errorModeEnabled:Bool ;
67
		
68
		var nodeMap:Dynamic;
69
		var changesetId:String;
70
		var lastHttpStatus:Int;
71
		var createdWays:Array<Dynamic> ;
72
		var modifiedWays:Array<Dynamic> ;
73
		var deletedWays:Array<Dynamic> ;
74
		var createdNodes:Array<Dynamic> ;
75
		var modifiedNodes:Array<Dynamic> ;
76
		var deletedNodes:Array<Dynamic> ;
77
		var modifiedRelations:Array<Dynamic> ;
78
		var mapLoader:URLLoader ;
79
		var authorizator:OAuthAuthorizator ;
80
		var openTransaction:Transaction (authorizator, true, CONFIG::osm + "changeset/create");
81
		var closeTransaction:Transaction (authorizator, true, CONFIG::osm + "changeset/");
82
		var commitTransaction:Transaction (authorizator, false, CONFIG::osm + "changeset/");
83
		var transactionQueue:TransactionQueue ;
84
		
85
		var tagsSerializer:IXMLListSerializer; 
86
		
87
		public function new(enforcer:SingletonEnforcer) {
88
			
89
			errorModeEnabled = false;
90
			createdWays = new Array();
91
			modifiedWays = new Array();
92
			deletedWays = new Array();
93
			createdNodes = new Array();
94
			modifiedNodes = new Array();
95
			deletedNodes = new Array();
96
			modifiedRelations = new Array();
97
			mapLoader = new URLLoader();
98
			authorizator = new OAuthAuthorizator();
99
			openTransaction = new Transaction;
100
			closeTransaction = new Transaction;
101
			commitTransaction = new Transaction;
102
			transactionQueue = new TransactionQueue();
103
			super();
104
			Security.loadPolicyFile(CONFIG::crossdomain);
105
			transactionQueue.addTransaction(openTransaction);
106
			transactionQueue.addTransaction(commitTransaction);
107
			transactionQueue.addTransaction(closeTransaction);
108
			mapLoader.addEventListener(Event.COMPLETE, mapLoader_completeHandler);
109
			mapLoader.addEventListener(IOErrorEvent.IO_ERROR, mapLoader_errorHandler);
110
			mapLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, mapLoader_errorHandler);
111
			transactionQueue.addEventListener(Event.COMPLETE, transactionQueue_completeHandler);
112
			transactionQueue.addEventListener(IOErrorEvent.IO_ERROR, transactionQueue_ioErrorHandler);
113
			commitTransaction.addEventListener(IOErrorEvent.IO_ERROR, commitTransaction_ioErrorHandler);
114
			transactionQueue.addEventListener(HTTPStatusEvent.HTTP_STATUS, transactionQueue_httpStatusHandler);
115
			openTransaction.addEventListener(Event.COMPLETE, openTransaction_completeHandler, false, 1);
116
			
117
			tagsSerializer = createTagsSerializer();
118
		}
119
		
120
		var _changesetOpened:Bool;
121
		public function getChangesetOpened():Bool {
122
			return _changesetOpened;
123
		}
124
		
125
		var _loading:Bool ;
126
		
127
		/*[Bindable("loadingChanged")]*/
128
		public function getLoading():Bool {
129
			return _loading;
130
		}
131
		
132
		public function getMap(bounds:LatLongBounds):Void {
133
			CONFIG::local {
134
				var bytes:ByteArray = new LOCAL_MAP();
135
				mapLoader.data = bytes.toString();
136
				mapLoader_completeHandler(null);
137
				return;
138
			}
139
			setLoading(true);
140
			var boundsArray:Array<Dynamic> = [bounds.westLong, bounds.southLat, bounds.eastLong, bounds.northLat];
141
			var url:String = CONFIG::osm + "map?bbox=" + boundsArray.join(",");
142
			mapLoader.load(new URLRequest(url));
143
		}
144
		
145
		public function close():Void {
146
			try {
147
				mapLoader.close();
148
			} catch (error:IOError) {
149
			}
150
			setLoading(false);
151
		}
152
		
153
		public function setCredentials(username:String, password:String):Void {
154
			transactionQueue.setCredentials(username, password);
155
		}
156
		
157
		public function openChangeset(tags:Array<Dynamic>):Void {
158
			if (!_changesetOpened) {
159
				_changesetOpened = true;
160
				var xml:XML = Xml.parse("<changeset></changeset>");
161
				serializeTags(xml, tags);
162
				openTransaction.requestData = "<osm>" + xml.toXMLString() + "</osm>";
163
			}
164
		}
165
		
166
		public function createNode(node:Node):Void {
167
			createdNodes.push(node);
168
		}
169
		
170
		public function modifyNode(node:Node):Void {
171
			modifiedNodes.push(node);
172
		}
173
		
174
		public function deleteNode(node:Node):Void {
175
			deletedNodes.push(node);
176
		}
177
		
178
		public function createWay(way:Way):Void {
179
			createdWays.push(way);
180
		}
181
		
182
		public function modifyWay(way:Way):Void {
183
			modifiedWays.push(way);
184
		}
185
		
186
		public function deleteWay(way:Way):Void {
187
			deletedWays.push(way);
188
		}
189
		
190
		public function modifyRelation(relation:Relation):Void {
191
			modifiedRelations.push(relation);
192
		}
193
		
194
		public function closeChangeset():Void {
195
			if (_changesetOpened) {
196
				lastHttpStatus = 0;
197
				transactionQueue.send();
198
			}
199
		}
200
		
201
		function serializeNodes(xml:XML, nodes:Array<Dynamic>):Void {
202
			for (node in nodes) {
203
				var nodeXml:XML = Xml.parse("<node></node>");
204
				nodeXml.@id = node.id;
205
				nodeXml.@version = node.version;
206
				nodeXml.@lat = node.position.lat;
207
				nodeXml.@lon = node.position.long;
208
				nodeXml.@changeset = changesetId;
209
				serializeTags(nodeXml, node.tags.source);
210
				xml.appendChild(nodeXml);
211
			}
212
		}
213
		
214
		function serializeWays(xml:XML, ways:Array<Dynamic>):Void {
215
			for (way in ways) {
216
				var wayXml:XML = Xml.parse("<way></way>");
217
				wayXml.@id = way.id;
218
				wayXml.@version = way.version;
219
				wayXml.@changeset = changesetId;
220
				var nodes:Array<Dynamic> = way.nodes.source;
221
				for (node in nodes) {
222
					wayXml.appendChild(Xml.parse("<nd ref={node.id}/>"));
223
				}
224
				serializeTags(wayXml, way.tags.source);
225
				xml.appendChild(wayXml);
226
			}
227
		}
228
		
229
		function serializeRelations(xml:XML, relations:Array<Dynamic>):Void {
230
			for (relation in relations) {
231
				var relationXml:XML = Xml.parse("<relation></relation>");
232
				relationXml.@id = relation.id;
233
				relationXml.@version = relation.version;
234
				relationXml.@changeset = changesetId;
235
				serializeMembers(relationXml, relation.members.source);
236
				serializeTags(relationXml, relation.tags.source);
237
				xml.appendChild(relationXml);
238
			}
239
		}
240
		
241
		function serializeMembers(xml:XML, members:Array<Dynamic>):Void {
242
			for (member in members) {
243
				var memberXml:XML = Xml.parse("<member/>");
244
				memberXml.@type = member.type;
245
				memberXml.@ref = member.elementId;
246
				memberXml.@role = member.role;
247
				xml.appendChild(memberXml);
248
			}
249
		}
250
		
251
		function serializeTags(xml:XML, tags:Array<Dynamic>):Void {
252
			var tagsXml:XMLList = tagsSerializer.serialize(tags);
253
			for (tagXml in tagsXml) {
254
				xml.appendChild(tagXml);
255
			}
256
		}
257
		
258
		function createTagsSerializer():IXMLListSerializer {
259
			var tagSerializer:IXMLSerializer = new TagExternalizer();
260
			var tagsSerializer:IXMLListSerializer = new ListExternalizer(tagSerializer, null);
261
			return new ValidTagsSerializer(tagsSerializer);
262
		} 
263
		
264
		function deserializeNodes(xml:XMLList):Array<Dynamic> {
265
			var nodes:Array<Dynamic> = new Array();
266
			var node:Node;
267
			xml.(
268
				node = new Node(@id, deserializeTags(elements("tag")), new LatLong(@lat, @lon)),
269
				node.version = @version,
270
				nodes.push(node)
271
			);
272
			return nodes;
273
		}
274
		
275
		function deserializeWays(xml:XMLList):Array<Dynamic> {
276
			var ways:Array<Dynamic> = new Array();
277
			var nodes:Array<Dynamic>;
278
			var way:Way;
279
			xml.(
280
				nodes = new Array(),
281
				nd.(nodes.push(nodeMap[@ref])),
282
				way = new Way(@id, deserializeTags(elements("tag")), nodes),
283
				way.version = @version,
284
				ways.push(way)
285
			);
286
			return ways;
287
		}
288
		
289
		function dererializeRelations(xml:XMLList):Array<Dynamic> {
290
			var relations:Array<Dynamic> = new Array();
291
			var members:Array<Dynamic>;
292
			var relation:Relation;
293
			xml.(
294
				members = deserializeRelationMembers(elements("member")),
295
				relation = new Relation(@id, deserializeTags(elements("tag")), members),
296
				relation.version = @version,
297
				relations.push(relation)
298
			);
299
			return relations;
300
		}
301
		
302
		function deserializeTags(xml:XMLList):Array<Dynamic> {
303
			var tags:Array<Dynamic> = new Array();
304
			xml.(tags.push(new Tag(@k, @v)));
305
			return tags;
306
		}
307
		
308
		function deserializeRelationMembers(xml:XMLList):Array<Dynamic> {
309
			var members:Array<Dynamic> = new Array();
310
			xml.(members.push(new Member(@ref, @type, @role)));
311
			return members;
312
		}
313
		
314
		function deserializeDiffs(xml:XMLList):Array<Dynamic> {
315
			var diffs:Array<Dynamic> = new Array();
316
			// Parse create or modify, but not delete.
317
			xml.(hasOwnProperty("@new_version")).
318
				(diffs.push(new ElementDiff(@old_id, @new_id, @new_version)));
319
			return diffs;
320
		}
321
		
322
		function setLoading(value:Bool):Void {
323
			if (value == _loading) return;
324
			_loading = value;
325
			dispatchEvent(new Event("loadingChanged"));
326
			/*if (value) {
327
				CursorManager.setBusyCursor();
328
			} else {
329
				CursorManager.removeBusyCursor();
330
			}*/
331
		}
332
		
333
		function endChangeset():Void {
334
			createdWays = new Array();
335
			modifiedWays = new Array();
336
			deletedWays = new Array();
337
			createdNodes = new Array();
338
			modifiedNodes = new Array();
339
			deletedNodes = new Array();
340
			modifiedRelations = new Array();
341
			_changesetOpened = false;
342
		}
343
		
344
		function mapLoader_completeHandler(event:Event):Void {
345
			var xml:XML = new XML(mapLoader.data);
346
			var nodes:Array<Dynamic> = deserializeNodes(xml.node);
347
			nodeMap = new Object();
348
			for (node in nodes) {
349
				nodeMap[node.id] = node;
350
			}
351
			var ways:Array<Dynamic> = deserializeWays(xml.way);
352
			var relations:Array<Dynamic> = dererializeRelations(xml.relation);
353
			dispatchEvent(new OSMProxyResultEvent(OSMProxyResultEvent.RESULT, nodes, ways, relations));
354
			setLoading(false);
355
		}
356
		
357
		function mapLoader_errorHandler(event:ErrorEvent):Void {
358
			setLoading(false);
359
			dispatchEvent(new OSMProxyLoadErrorEvent(OSMProxyLoadErrorEvent.LOAD_ERROR));
360
		}
361
		
362
		function openTransaction_completeHandler(event:Event):Void {
363
			changesetId = String(openTransaction.data);
364
			var xml:XML = new XML("<osmChange></osmChange>");
365
			var createXml:XML = new XML("<create></create>");
366
			serializeNodes(createXml, createdNodes);
367
			serializeWays(createXml, createdWays);
368
			xml.appendChild(createXml);
369
			var modifyXml:XML = new XML("<modify></modify>");
370
			serializeNodes(modifyXml, modifiedNodes);
371
			serializeWays(modifyXml, modifiedWays);
372
			serializeRelations(modifyXml, modifiedRelations);
373
			xml.appendChild(modifyXml);
374
			var deleteXml:XML = new XML("<delete></delete>");
375
			serializeWays(deleteXml, deletedWays);
376
			serializeNodes(deleteXml, deletedNodes);
377
			xml.appendChild(deleteXml);
378
			commitTransaction.requestData = xml.toXMLString();
379
			commitTransaction.url = changesetId + "/upload";
380
			closeTransaction.url = changesetId + "/close";
381
			CONFIG::debug {
382
				if (errorModeEnabled) {
383
					commitTransaction.requestData = commitTransaction.requestData.substr(1);
384
				}
385
			}
386
		}
387
		
388
		function transactionQueue_completeHandler(event:Event):Void {
389
			endChangeset();
390
			var xml:XML = new XML(commitTransaction.data);
391
			var nodeDiffs:Array<Dynamic> = deserializeDiffs(xml.node);
392
			var wayDiffs:Array<Dynamic> = deserializeDiffs(xml.way);
393
			var relationDiffs:Array<Dynamic> = deserializeDiffs(xml.relation);
394
			dispatchEvent(new OSMProxySaveEvent(OSMProxySaveEvent.SAVE_COMPLETE, changesetId));
395
			dispatchEvent(new OSMProxyResultEvent(OSMProxyResultEvent.SAVE, nodeDiffs, wayDiffs, relationDiffs));
396
		}
397
		
398
		function transactionQueue_ioErrorHandler(event:IOErrorEvent):Void {
399
			endChangeset();
400
			//Alert.show("httpStatus: " + lastHttpStatus);
401
			dispatchEvent(new OSMProxySaveEvent(OSMProxySaveEvent.SAVE_FAILED));
402
		}
403
		
404
		function transactionQueue_httpStatusHandler(event:HTTPStatusEvent):Void {
405
			lastHttpStatus = event.status;
406
		}
407
		
11ab0d0 by James Michael DuPont at 2010-03-13 408
		function commitTransaction_ioErrorHandler(event:IOErrorEvent):Void {
2fc88c4 by James Michael DuPont at 2010-03-12 409
			dispatchEvent(new OSMProxyCommitErrorEvent(OSMProxyCommitErrorEvent.COMMIT_ERROR, commitTransaction.requestData));
28283f5 by James Michael DuPont at 2010-03-13 410
		}
411
	}