package com.cloudmade.mappingtool.model; import com.cloudmade.mappingtool.business.externalization.IXMLListSerializer; import com.cloudmade.mappingtool.business.externalization.IXMLSerializer; import com.cloudmade.mappingtool.business.externalization.osm.ListExternalizer; import com.cloudmade.mappingtool.business.externalization.osm.TagExternalizer; import com.cloudmade.mappingtool.business.externalization.osm.ValidTagsSerializer; import com.cloudmade.mappingtool.events.OSMProxyCommitErrorEvent; import com.cloudmade.mappingtool.events.OSMProxyLoadErrorEvent; import com.cloudmade.mappingtool.events.OSMProxyResultEvent; import com.cloudmade.mappingtool.events.OSMProxySaveEvent; import com.cloudmade.mappingtool.map.geo.LatLong; import com.cloudmade.mappingtool.map.geo.LatLongBounds; import com.cloudmade.mappingtool.map.model.Member; import com.cloudmade.mappingtool.map.model.Node; import com.cloudmade.mappingtool.map.model.Relation; import com.cloudmade.mappingtool.map.model.Tag; import com.cloudmade.mappingtool.map.model.Way; import com.cloudmade.mappingtool.model.osm.ElementDiff; import com.cloudmade.mappingtool.model.osm.OAuthAuthorizator; import com.cloudmade.mappingtool.model.osm.Transaction; import com.cloudmade.mappingtool.model.osm.TransactionQueue; import flash.errors.IOError; import flash.events.ErrorEvent; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.net.URLLoader; import flash.net.URLRequest; import flash.system.Security; import flash.utils.ByteArray; import mx.managers.CursorManager; /*[Event(name="save", type="com.cloudmade.mappingtool.events.OSMProxyResultEvent")]*/ /*[Event(name="result", type="com.cloudmade.mappingtool.events.OSMProxyResultEvent")]*/ /*[Event(name="saveFailed", type="com.cloudmade.mappingtool.events.OSMProxySaveEvent")]*/ /*[Event(name="saveComplete", type="com.cloudmade.mappingtool.events.OSMProxySaveEvent")]*/ /*[Event(name="loadError", type="com.cloudmade.mappingtool.events.OSMProxyLoadErrorEvent")]*/ /*[Event(name="commitError", type="com.cloudmade.mappingtool.events.OSMProxyCommitErrorEvent")]*/ /*[Event(name="loadingChanged", type="flash.events.Event")]*/ // TODO: Use polymorphism. public final class OSMProxy extends EventDispatcher { public var changesetOpened(getChangesetOpened, null) : Bool ; public var loading(getLoading, null) : Bool ; /*[Embed(source="../../../../config/map.xml", mimeType="application/octet-stream")]*/ CONFIG::local static var LOCAL_MAP:Class; static var instance:OSMProxy = null; public static function getInstance():OSMProxy { if (instance == null) { instance = new OSMProxy(new SingletonEnforcer()); } return instance; } CONFIG::public debug var errorModeEnabled:Bool ; var nodeMap:Dynamic; var changesetId:String; var lastHttpStatus:Int; var createdWays:Array ; var modifiedWays:Array ; var deletedWays:Array ; var createdNodes:Array ; var modifiedNodes:Array ; var deletedNodes:Array ; var modifiedRelations:Array ; var mapLoader:URLLoader ; var authorizator:OAuthAuthorizator ; var openTransaction:Transaction (authorizator, true, CONFIG::osm + "changeset/create"); var closeTransaction:Transaction (authorizator, true, CONFIG::osm + "changeset/"); var commitTransaction:Transaction (authorizator, false, CONFIG::osm + "changeset/"); var transactionQueue:TransactionQueue ; var tagsSerializer:IXMLListSerializer; public function new(enforcer:SingletonEnforcer) { errorModeEnabled = false; createdWays = new Array(); modifiedWays = new Array(); deletedWays = new Array(); createdNodes = new Array(); modifiedNodes = new Array(); deletedNodes = new Array(); modifiedRelations = new Array(); mapLoader = new URLLoader(); authorizator = new OAuthAuthorizator(); openTransaction = new Transaction; closeTransaction = new Transaction; commitTransaction = new Transaction; transactionQueue = new TransactionQueue(); super(); Security.loadPolicyFile(CONFIG::crossdomain); transactionQueue.addTransaction(openTransaction); transactionQueue.addTransaction(commitTransaction); transactionQueue.addTransaction(closeTransaction); mapLoader.addEventListener(Event.COMPLETE, mapLoader_completeHandler); mapLoader.addEventListener(IOErrorEvent.IO_ERROR, mapLoader_errorHandler); mapLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, mapLoader_errorHandler); transactionQueue.addEventListener(Event.COMPLETE, transactionQueue_completeHandler); transactionQueue.addEventListener(IOErrorEvent.IO_ERROR, transactionQueue_ioErrorHandler); commitTransaction.addEventListener(IOErrorEvent.IO_ERROR, commitTransaction_ioErrorHandler); transactionQueue.addEventListener(HTTPStatusEvent.HTTP_STATUS, transactionQueue_httpStatusHandler); openTransaction.addEventListener(Event.COMPLETE, openTransaction_completeHandler, false, 1); tagsSerializer = createTagsSerializer(); } var _changesetOpened:Bool; public function getChangesetOpened():Bool { return _changesetOpened; } var _loading:Bool ; /*[Bindable("loadingChanged")]*/ public function getLoading():Bool { return _loading; } public function getMap(bounds:LatLongBounds):Void { CONFIG::local { var bytes:ByteArray = new LOCAL_MAP(); mapLoader.data = bytes.toString(); mapLoader_completeHandler(null); return; } setLoading(true); var boundsArray:Array = [bounds.westLong, bounds.southLat, bounds.eastLong, bounds.northLat]; var url:String = CONFIG::osm + "map?bbox=" + boundsArray.join(","); mapLoader.load(new URLRequest(url)); } public function close():Void { try { mapLoader.close(); } catch (error:IOError) { } setLoading(false); } public function setCredentials(username:String, password:String):Void { transactionQueue.setCredentials(username, password); } public function openChangeset(tags:Array):Void { if (!_changesetOpened) { _changesetOpened = true; var xml:XML = Xml.parse(""); serializeTags(xml, tags); openTransaction.requestData = "" + xml.toXMLString() + ""; } } public function createNode(node:Node):Void { createdNodes.push(node); } public function modifyNode(node:Node):Void { modifiedNodes.push(node); } public function deleteNode(node:Node):Void { deletedNodes.push(node); } public function createWay(way:Way):Void { createdWays.push(way); } public function modifyWay(way:Way):Void { modifiedWays.push(way); } public function deleteWay(way:Way):Void { deletedWays.push(way); } public function modifyRelation(relation:Relation):Void { modifiedRelations.push(relation); } public function closeChangeset():Void { if (_changesetOpened) { lastHttpStatus = 0; transactionQueue.send(); } } function serializeNodes(xml:XML, nodes:Array):Void { for (node in nodes) { var nodeXml:XML = Xml.parse(""); nodeXml.@id = node.id; nodeXml.@version = node.version; nodeXml.@lat = node.position.lat; nodeXml.@lon = node.position.long; nodeXml.@changeset = changesetId; serializeTags(nodeXml, node.tags.source); xml.appendChild(nodeXml); } } function serializeWays(xml:XML, ways:Array):Void { for (way in ways) { var wayXml:XML = Xml.parse(""); wayXml.@id = way.id; wayXml.@version = way.version; wayXml.@changeset = changesetId; var nodes:Array = way.nodes.source; for (node in nodes) { wayXml.appendChild(Xml.parse("")); } serializeTags(wayXml, way.tags.source); xml.appendChild(wayXml); } } function serializeRelations(xml:XML, relations:Array):Void { for (relation in relations) { var relationXml:XML = Xml.parse(""); relationXml.@id = relation.id; relationXml.@version = relation.version; relationXml.@changeset = changesetId; serializeMembers(relationXml, relation.members.source); serializeTags(relationXml, relation.tags.source); xml.appendChild(relationXml); } } function serializeMembers(xml:XML, members:Array):Void { for (member in members) { var memberXml:XML = Xml.parse(""); memberXml.@type = member.type; memberXml.@ref = member.elementId; memberXml.@role = member.role; xml.appendChild(memberXml); } } function serializeTags(xml:XML, tags:Array):Void { var tagsXml:XMLList = tagsSerializer.serialize(tags); for (tagXml in tagsXml) { xml.appendChild(tagXml); } } function createTagsSerializer():IXMLListSerializer { var tagSerializer:IXMLSerializer = new TagExternalizer(); var tagsSerializer:IXMLListSerializer = new ListExternalizer(tagSerializer, null); return new ValidTagsSerializer(tagsSerializer); } function deserializeNodes(xml:XMLList):Array { var nodes:Array = new Array(); var node:Node; xml.( node = new Node(@id, deserializeTags(elements("tag")), new LatLong(@lat, @lon)), node.version = @version, nodes.push(node) ); return nodes; } function deserializeWays(xml:XMLList):Array { var ways:Array = new Array(); var nodes:Array; var way:Way; xml.( nodes = new Array(), nd.(nodes.push(nodeMap[@ref])), way = new Way(@id, deserializeTags(elements("tag")), nodes), way.version = @version, ways.push(way) ); return ways; } function dererializeRelations(xml:XMLList):Array { var relations:Array = new Array(); var members:Array; var relation:Relation; xml.( members = deserializeRelationMembers(elements("member")), relation = new Relation(@id, deserializeTags(elements("tag")), members), relation.version = @version, relations.push(relation) ); return relations; } function deserializeTags(xml:XMLList):Array { var tags:Array = new Array(); xml.(tags.push(new Tag(@k, @v))); return tags; } function deserializeRelationMembers(xml:XMLList):Array { var members:Array = new Array(); xml.(members.push(new Member(@ref, @type, @role))); return members; } function deserializeDiffs(xml:XMLList):Array { var diffs:Array = new Array(); // Parse create or modify, but not delete. xml.(hasOwnProperty("@new_version")). (diffs.push(new ElementDiff(@old_id, @new_id, @new_version))); return diffs; } function setLoading(value:Bool):Void { if (value == _loading) return; _loading = value; dispatchEvent(new Event("loadingChanged")); /*if (value) { CursorManager.setBusyCursor(); } else { CursorManager.removeBusyCursor(); }*/ } function endChangeset():Void { createdWays = new Array(); modifiedWays = new Array(); deletedWays = new Array(); createdNodes = new Array(); modifiedNodes = new Array(); deletedNodes = new Array(); modifiedRelations = new Array(); _changesetOpened = false; } function mapLoader_completeHandler(event:Event):Void { var xml:XML = new XML(mapLoader.data); var nodes:Array = deserializeNodes(xml.node); nodeMap = new Object(); for (node in nodes) { nodeMap[node.id] = node; } var ways:Array = deserializeWays(xml.way); var relations:Array = dererializeRelations(xml.relation); dispatchEvent(new OSMProxyResultEvent(OSMProxyResultEvent.RESULT, nodes, ways, relations)); setLoading(false); } function mapLoader_errorHandler(event:ErrorEvent):Void { setLoading(false); dispatchEvent(new OSMProxyLoadErrorEvent(OSMProxyLoadErrorEvent.LOAD_ERROR)); } function openTransaction_completeHandler(event:Event):Void { changesetId = String(openTransaction.data); var xml:XML = new XML(""); var createXml:XML = new XML(""); serializeNodes(createXml, createdNodes); serializeWays(createXml, createdWays); xml.appendChild(createXml); var modifyXml:XML = new XML(""); serializeNodes(modifyXml, modifiedNodes); serializeWays(modifyXml, modifiedWays); serializeRelations(modifyXml, modifiedRelations); xml.appendChild(modifyXml); var deleteXml:XML = new XML(""); serializeWays(deleteXml, deletedWays); serializeNodes(deleteXml, deletedNodes); xml.appendChild(deleteXml); commitTransaction.requestData = xml.toXMLString(); commitTransaction.url = changesetId + "/upload"; closeTransaction.url = changesetId + "/close"; CONFIG::debug { if (errorModeEnabled) { commitTransaction.requestData = commitTransaction.requestData.substr(1); } } } function transactionQueue_completeHandler(event:Event):Void { endChangeset(); var xml:XML = new XML(commitTransaction.data); var nodeDiffs:Array = deserializeDiffs(xml.node); var wayDiffs:Array = deserializeDiffs(xml.way); var relationDiffs:Array = deserializeDiffs(xml.relation); dispatchEvent(new OSMProxySaveEvent(OSMProxySaveEvent.SAVE_COMPLETE, changesetId)); dispatchEvent(new OSMProxyResultEvent(OSMProxyResultEvent.SAVE, nodeDiffs, wayDiffs, relationDiffs)); } function transactionQueue_ioErrorHandler(event:IOErrorEvent):Void { endChangeset(); //Alert.show("httpStatus: " + lastHttpStatus); dispatchEvent(new OSMProxySaveEvent(OSMProxySaveEvent.SAVE_FAILED)); } function transactionQueue_httpStatusHandler(event:HTTPStatusEvent):Void { lastHttpStatus = event.status; } function commitTransaction_ioErrorHandler(event:IOErrorEvent):Void { dispatchEvent(new OSMProxyCommitErrorEvent(OSMProxyCommitErrorEvent.COMMIT_ERROR, commitTransaction.requestData)); } }