ADD ability to merge one node with another (including POI nodes)
authorSteve Bennett <stevagewp@gmail.com>
Sun, 13 Feb 2011 13:32:06 +0000 (13:32 +0000)
committerSteve Bennett <stevagewp@gmail.com>
Sun, 13 Feb 2011 13:32:06 +0000 (13:32 +0000)
ADD warning when merged tags conflict (including from way-way mergers)

net/systemeD/halcyon/NodeUI.as
net/systemeD/halcyon/connection/Entity.as
net/systemeD/halcyon/connection/Node.as
net/systemeD/halcyon/connection/Way.as
net/systemeD/halcyon/connection/actions/MergeNodesAction.as [new file with mode: 0644]
net/systemeD/halcyon/connection/actions/MergeWaysAction.as
net/systemeD/halcyon/connection/actions/ReplaceNodeAction.as
net/systemeD/potlatch2/controller/SelectedWayNode.as

index 075b329..e4e6ca3 100644 (file)
@@ -208,5 +208,10 @@ package net.systemeD.halcyon {
                                d.transform.matrix=m;
                        }
                }
+        public function hitTest(x:Number, y:Number):Node {
+            if (hitzone && hitzone.hitTestPoint(x,y,true)) { return entity as Node; }
+            return null;
+        }
+               
        }
 }
index 1160e49..6a24481 100644 (file)
@@ -2,10 +2,11 @@ package net.systemeD.halcyon.connection {
 
     import flash.events.EventDispatcher;
     import flash.utils.Dictionary;
-
+    
     import net.systemeD.halcyon.connection.actions.*;
 
-    /** An Entity is an object stored in the map database, and therefore uploaded and downloaded. This includes Nodes, Ways, Relations but also Changesets etc. */
+    /** An Entity is an object stored in the map database, and therefore uploaded and downloaded. This includes Nodes, Ways, 
+    *   Relations but also Changesets etc. */
     public class Entity extends EventDispatcher {
         private var _id:Number;
         private var _version:uint;
@@ -394,6 +395,33 @@ package net.systemeD.halcyon.connection {
                public function isType(str:String):Boolean {
                        return getType()==str;
                }
+               
+        /** Copy tags from another entity into this one, creating "key=value1; value2" pairs if necessary.
+        * * @return Array of keys that require manual merging, in order to warn the user. */ 
+        public function mergeTags(source: Entity, performAction:Function):Array {
+            var sourcetags:Object = source.getTagsHash();
+            var problem_keys:Array=new Array(); 
+            for (var k:String in sourcetags) {
+                var v1:String = tags[k];
+                var v2:String = sourcetags[k];
+                if ( v1 && v1 != v2) {
+                    // This can create broken tags (does anything support "highway=residential; tertiary"?). 
+                    // Probably better to do something like:
+                    // highway=residential
+                    // highway:tomerge=tertiary
+                    
+                    setTag(k, v1+"; "+v2, performAction);
+                    problem_keys.push(k);
+                } else {
+                    setTag(k, v2, performAction);
+                }
+            }
+            if (problem_keys.length > 0)
+                return problem_keys;
+            else 
+                return null;
+        }
+
 
     }
 
index 7261b35..e185d3f 100644 (file)
@@ -134,6 +134,12 @@ package net.systemeD.halcyon.connection {
                public override function getType():String {
                        return 'node';
                }
+               
+        /** Merge another node into this one, removing the other one. */
+        public function mergeWith(node:Node, performAction:Function):void {
+            performAction(new MergeNodesAction(this, node));
+        }
+               
     }
 
 }
index f0b7ce9..92d2655 100644 (file)
@@ -136,6 +136,7 @@ package net.systemeD.halcyon.connection {
             markDirty();
         }
 
+               /** Merges another way into this one, removing the other one. */
                public function mergeWith(way:Way,topos:int,frompos:int, performAction:Function):void {
                        performAction(new MergeWaysAction(this, way, topos, frompos));
                }
diff --git a/net/systemeD/halcyon/connection/actions/MergeNodesAction.as b/net/systemeD/halcyon/connection/actions/MergeNodesAction.as
new file mode 100644 (file)
index 0000000..60b7d4c
--- /dev/null
@@ -0,0 +1,53 @@
+package net.systemeD.halcyon.connection.actions\r
+{\r
+       import net.systemeD.halcyon.connection.*;\r
+\r
+\r
+       public class MergeNodesAction extends CompositeUndoableAction {\r
+        // Node2's tags are merged into node1, then node2 is deleted.       \r
+        private var node1:Node;\r
+        private var node2:Node;\r
+        static public var lastProblemTags:Array;\r
+    \r
+        public function MergeNodesAction(destnode:Node, sourcenode:Node) {\r
+            super("Merge nodes "+destnode.id+" "+sourcenode.id);\r
+            this.node1 = destnode;\r
+            this.node2 = sourcenode;\r
+            lastProblemTags=null;\r
+        }\r
+        \r
+        public override function doAction():uint {\r
+\r
+            super.clearActions();\r
+            node1.suspend();\r
+\r
+//            mergeRelations(); TODO\r
+            lastProblemTags= node1.mergeTags(node2,push); // TODO use to warn user\r
+            node2.replaceWith(node1, push);\r
+            node2.remove(push);\r
+\r
+            super.doAction();\r
+            node1.resume();\r
+            \r
+            return SUCCESS;\r
+        }\r
+\r
+        public override function undoAction():uint {\r
+            node1.suspend();\r
+            super.undoAction();\r
+            node1.resume();\r
+            \r
+            return SUCCESS;\r
+        }\r
+        \r
+        public function mergeRelations():void {\r
+            for each (var r:Relation in node2.parentRelations) {\r
+                // ** needs to copy roles as well\r
+                if (r.findEntityMemberIndex(node1)==-1) {\r
+                    r.appendMember(new RelationMember(node1, ''), push);\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+}
\ No newline at end of file
index 104362f..33d5c80 100644 (file)
@@ -7,6 +7,7 @@ package net.systemeD.halcyon.connection.actions {
         private var way2:Way;
         private var toPos:uint;
         private var fromPos:uint;
+        static public var lastProblemTags:Array;
     
         public function MergeWaysAction(way1:Way, way2:Way, toPos:uint, fromPos:uint) {
             super("Merge ways "+way1.id+" "+way2.id);
@@ -14,6 +15,7 @@ package net.systemeD.halcyon.connection.actions {
             this.way2 = way2;
             this.toPos = toPos;
             this.fromPos = fromPos;
+            lastProblemTags=null;
         }
         
         public override function doAction():uint {
@@ -24,7 +26,7 @@ package net.systemeD.halcyon.connection.actions {
             way1.suspend();
 
             mergeRelations();
-               mergeTags();
+            lastProblemTags = way1.mergeTags(way2,push);
                mergeNodes();
                        way2.remove(push);
 
@@ -51,20 +53,6 @@ package net.systemeD.halcyon.connection.actions {
                        }
         }
         
-        public function mergeTags():void {
-               var way1Tags:Object = way1.getTagsHash();
-                       var way2Tags:Object = way2.getTagsHash();
-                       for (var k:String in way2Tags) {
-                           var v1:String = way1Tags[k];
-                           var v2:String = way2Tags[k];
-                               if ( v1 && v1 != v2) {
-                                       way1.setTag(k, v1+"; "+v2, push);
-                                       // ** send a warning about tags not matching
-                               } else {
-                                       way1.setTag(k, v2, push);
-                               }
-                       }
-        }
         
         public function mergeNodes():void {
             var i:int;
index ea1df2f..39c22e0 100644 (file)
@@ -2,6 +2,7 @@ package net.systemeD.halcyon.connection.actions {
 
     import net.systemeD.halcyon.connection.*;
 
+    /** Action that substitutes one node instead of another, in all the ways and relations that that node is part of. */
     public class ReplaceNodeAction extends CompositeUndoableAction {
 
         private var node:Node;
index 4dca787..9d47c5a 100644 (file)
@@ -3,6 +3,7 @@ package net.systemeD.potlatch2.controller {
        import flash.geom.Point;
        import flash.ui.Keyboard;
        
+       import net.systemeD.halcyon.AttentionEvent;
        import net.systemeD.halcyon.Globals;
        import net.systemeD.halcyon.WayUI;
        import net.systemeD.halcyon.connection.*;
@@ -85,6 +86,11 @@ package net.systemeD.potlatch2.controller {
                        return parentWay;
                }
 
+        public function get selectedNode():Node {
+            return parentWay.getNode(selectedIndex);
+        }
+
+
                private function cycleWays():ControllerState {
                        var wayList:Array=firstSelected.parentWays;
                        if (wayList.length==1) { return this; }
@@ -165,19 +171,58 @@ package net.systemeD.potlatch2.controller {
             return this;
         }
 
+        /** Attempt to either merge the currently selected node with another very nearby node, or failing that,
+        *   attach it mid-way along a very nearby way. */
         public function join():ControllerState {
-            // detect the ways that overlap this node
             var p:Point = new Point(controller.map.lon2coord(Node(firstSelected).lon),
                                              controller.map.latp2coord(Node(firstSelected).latp));
             var q:Point = map.localToGlobal(p);
+
+            // First, look for POI nodes in 20x20 pixel box around the current node
+            var hitnodes:Array = map.connection.getObjectsByBbox(
+                map.coord2lon(p.x-10),
+                map.coord2lon(p.x+10),
+                map.coord2lat(p.y-10),
+                map.coord2lat(p.y+10)).poisInside;
+            
+            for each (var n: Node in hitnodes) {
+                if (!n.hasParent(selectedWay)) { 
+                   return doMergeNodes(n);
+                }
+            }
+            
             var ways:Array=[]; var w:Way;
             for each (var wayui:WayUI in controller.map.paint.wayuis) {
                 w=wayui.hitTest(q.x, q.y);
-                if (w && w!=selectedWay) { ways.push(w); }
+                if (w && w!=selectedWay) { 
+                // hit a way, now let's see if we hit a specific node
+                    for (var i:uint = 0; i < w.length; i++) {
+                       n = w.getNode(i);
+                       var x:Number = map.lon2coord(n.lon);
+                       var y:Number = map.latp2coord(n.latp);
+                       if (n != selectedNode && Math.abs(x-p.x) + Math.abs(y-p.y) < 10) {
+                            return doMergeNodes(n);
+                        }    
+                    }
+                    ways.push(w); 
+                }
             }
 
+            // No nodes hit, so join our node onto any overlapping ways.
             Node(firstSelected).join(ways,MainUndoStack.getGlobalStack().addAction);
             return this;
         }
+        
+        private function doMergeNodes(n:Node): ControllerState {
+               n.mergeWith(Node(firstSelected), MainUndoStack.getGlobalStack().addAction);
+            // only merge one node at a time - too confusing otherwise?
+            var msg:String = "Nodes merged."
+            if (MergeNodesAction.lastProblemTags) {
+                msg += " *Warning* The following tags conflicted and need attention: " + MergeNodesAction.lastProblemTags;
+            }
+            map.connection.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg));
+            return new SelectedWayNode(n.parentWays[0], Way(n.parentWays[0]).indexOfNode(n));
+        }
     }
 }
+