1 package net.systemeD.halcyon.connection {
3 import flash.events.EventDispatcher;
4 import flash.utils.Dictionary;
6 import flash.net.URLLoader;
8 import net.systemeD.halcyon.connection.*;
9 import net.systemeD.halcyon.Map;
10 import net.systemeD.halcyon.Globals;
11 import net.systemeD.halcyon.MapPaint;
14 * Implements trace objects loaded from the OSM API.
15 * See also potlatch2's utils GpxImporter.as and Importer.as classes, which can handle
16 * loading GPX files (and other formats) from arbitrary urls.
18 public class Trace extends EventDispatcher {
19 private var _id:Number; // id of the trace, as reported by the server
20 private var _description:String; // description, as reported by the server
21 private var tags:Array = []; // N.B. trace tags are NOT k/v pairs
22 private var _isLoaded:Boolean; // Flag for when the data has been downloaded and parsed
23 private var _filename:String; // The original name of the file, as reported by the server
24 private var _traceData:String; // the trace data, saved as a string
26 private var _layer:MapPaint;
27 private var masterConnection:Connection; // The authenticated connection
28 private var _connection:Connection; // The one we store our fake nodes/ways in.
29 private var simplify:Boolean = false;
31 private static const STYLESHEET:String="stylesheets/gpx.css";
33 /** Create a new trace.
34 * @param masterConnection The authenticated connection to communicate with the server
36 public function Trace(masterConnection:Connection, id:int=0) {
37 this.masterConnection = masterConnection;
38 map = Globals.vars.root; // REFACTOR this prevents traces being added to arbitrary maps
42 /** Create a new trace, from the XML description given by the user/traces call.
43 * This only creates the object itself, the actual trace contents (trkseg etc) are
44 * lazily downloaded later. */
45 public function fromXML(xml:XML):Trace {
46 _id = Number(xml.@id);
47 _filename = xml.@name;
48 _description = xml.description;
50 for each(var tag:XML in xml.tag) {
51 tags.push(String(tag));
56 public function get id():Number {
60 public function get description():String {
64 public function get filename():String {
68 public function get tagsText():String {
69 return tags.join(", ");
72 private function fetchFromServer():void {
73 // todo - needs proper error handling
74 masterConnection.fetchTrace(id, saveTraceData);
75 dispatchEvent(new Event("loading_data"));
78 private function saveTraceData(event:Event):void {
79 _traceData = String(URLLoader(event.target).data);
80 dispatchEvent(new Event("loaded_data"));
83 private function get connection():Connection {
85 // create a new connection so that the ids don't impact the main layer.
86 _connection = new Connection(filename, null, null, null);
91 private function get layer():MapPaint {
93 // create a new layer for every trace, so they can be turned on/off individually
94 _layer = map.addLayer(connection, STYLESHEET);
99 public function addToMap():void {
100 // this allows adding and removing traces from the map, without re-downloading
101 // the data from the server repeatedly.
103 addEventListener("loaded_data", processEvent);
111 public function removeFromMap():void {
115 private function processEvent(e:Event):void {
116 removeEventListener("loaded_data", processEvent);
121 private function process():void {
122 var file:XML = new XML(_traceData);
123 var action:CompositeUndoableAction = new CompositeUndoableAction("add trace objects");
124 for each (var ns:Namespace in file.namespaceDeclarations()) {
125 if (ns.uri.match(/^http:\/\/www\.topografix\.com\/GPX\/1\/[01]$/)) {
126 default xml namespace = ns;
130 Trace.parseTrkSegs(file,connection,action,false);
132 for each (var wpt:XML in file.wpt) {
133 var tags:Object = {};
134 for each (var tag:XML in wpt.children()) {
135 tags[tag.name().localName]=tag.toString().substr(0,255);
137 var node:Node = connection.createNode(tags, wpt.@lat, wpt.@lon, action.push);
138 connection.registerPOI(node);
141 action.doAction(); /* just do it, don't add to undo stack */
142 default xml namespace = new Namespace("");
143 layer.updateEntityUIs(true, false);
146 /* Draw ways from <trkseg>s, with elementary filter to remove points within 3 metres of each other.
147 Optionally split way if more than 50m from previous point.
148 FIXME: do auto-joining of dupes as per Importer. */
150 public static function parseTrkSegs(file:XML, connection:Connection, action:CompositeUndoableAction, smartSplitting:Boolean=false):void {
151 for each (var ns:Namespace in file.namespaceDeclarations()) {
152 if (ns.uri.match(/^http:\/\/www\.topografix\.com\/GPX\/1\/[01]$/)) { default xml namespace = ns; }
154 for each (var trkseg:XML in file..trkseg) {
155 var pts:Array = []; // XML can fuck off
156 for each (var trkpt:XML in trkseg.children()) { pts.push([trkpt.@lat, trkpt.@lon]); }
157 if (trkseg.trkpt[0].time.length()>0) {
158 Trace.createFromOrdered(pts,connection,action,smartSplitting);
160 Trace.createFromUnordered(pts,connection,action);
164 public static function createFromOrdered(pts:Array,connection:Connection,action:CompositeUndoableAction,smartSplitting:Boolean):void {
165 var nodestring:Array = [];
166 var lat:Number = NaN, lastlat:Number = NaN;
167 var lon:Number = NaN, lastlon:Number = NaN;
169 for each (var pt:Array in pts) {
172 if (isNaN(lastlat)) { lastlat = lat; lastlon = lon; }
173 dist=Trace.greatCircle(lat, lon, lastlat, lastlon);
175 if ((dist>50 && smartSplitting) || nodestring.length>500) {
176 if (dist<=50 || !smartSplitting) nodestring.push(connection.createNode({}, lat, lon, action.push));
177 if (nodestring.length>1) connection.createWay({}, nodestring, action.push);
180 nodestring.push(connection.createNode({}, lat, lon, action.push));
181 lastlat=lat; lastlon=lon;
184 if (nodestring.length > 1) { connection.createWay({}, nodestring, action.push); }
186 public static function createFromUnordered(pts:Array,connection:Connection,action:CompositeUndoableAction):void {
187 var active:int = pts.length;
188 var lat:Number = NaN;
189 var lon:Number = NaN;
190 var nodestring:Array = [];
192 var created:Boolean = false;
193 for (var i:int=0; i<pts.length; i++) {
194 if (pts[i]==null) { continue; }
198 var bestDist:Number = 20;
199 var bestIndex:int = -1;
200 for (var j:int=i+1; j<=Math.min(i+5,pts.length-1); j++) {
201 if (pts[j]==null) { continue; }
202 var dist:Number = Trace.greatCircle(lat,lon,pts[j][0],pts[j][1]);
203 if (dist<bestDist) { bestDist=dist; bestIndex=j; }
206 if (nodestring.length > 1) { created=true; connection.createWay({}, nodestring, action.push); }
210 if (i==bestIndex) return;
211 nodestring.push(connection.createNode({}, lat, lon, action.push));
217 if (nodestring.length > 1) {
218 created=true; connection.createWay({}, nodestring, action.push);
221 if (!created || active==0) { return; }
225 public static function greatCircle(lat1:Number,lon1:Number,lat2:Number,lon2:Number):Number {
226 var dlat:Number=(lat2-lat1)*Math.PI/180;
227 var dlon:Number=(lon2-lon1)*Math.PI/180;
228 var a:Number=Math.pow(Math.sin(dlat / 2),2) +
229 Math.cos(lat1*Math.PI/180) *
230 Math.cos(lat2*Math.PI/180) *
231 Math.pow(Math.sin(dlon / 2),2);
232 a=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
233 return a*3958.75*1609;