Experimental "create multipolygon" feature.
authorRichard Fairhurst <richard@systemeD.net>
Sat, 1 Oct 2011 10:53:35 +0000 (11:53 +0100)
committerRichard Fairhurst <richard@systemeD.net>
Sat, 1 Oct 2011 10:53:35 +0000 (11:53 +0100)
For now, you need to (multiple) select the ways and press 'K'. (The intention is to expose it via the Toolbox UI: when 2+ enclosed areas are selected, the "join" icon will change to a "multipolygon" icon.)

net/systemeD/halcyon/MapPaint.as
net/systemeD/halcyon/WayUI.as
net/systemeD/halcyon/connection/Way.as
net/systemeD/potlatch2/controller/SelectedMultiple.as

index a340f60..6dc75b4 100644 (file)
@@ -249,6 +249,18 @@ package net.systemeD.halcyon {
                                if (nodeuis[node.id]) { deleteNodeUI(node); }
                        }
                }
+               
+               /** Return WayUI properties */
+               public function wayUIProperties(way:Way):Object {
+                       if (wayuis[way.id]) {
+                               return { centroid_x: wayuis[way.id].centroid_x,
+                                            centroid_y: wayuis[way.id].centroid_y,
+                                            patharea:   wayuis[way.id].patharea,
+                                            pathlength: wayuis[way.id].pathlength };
+                       } else {
+                               return {};
+                       }
+               }
 
                /** Make a UI object representing a node. */
                public function createNodeUI(node:Node,rotation:Number=0,layer:int=NO_LAYER,stateClasses:Object=null):NodeUI {
index f278972..ed9156f 100644 (file)
@@ -218,6 +218,7 @@ package net.systemeD.halcyon {
                                centroid_x=c[0];
                                centroid_y=c[1];
                        }
+                       patharea=Math.abs(patharea);
                }
 
                // ------------------------------------------------------------------------------------------
index d5f991c..0897732 100644 (file)
@@ -157,6 +157,39 @@ package net.systemeD.halcyon.connection {
             performAction(new ReverseNodesAction(this, nodes));
         }
 
+               
+               /** Is a point within this way?
+               * From http://as3.miguelmoraleda.com/2009/10/28/point-in-polygon-with-actionscript-3punto-dentro-de-un-poligono-con-actionscript-3/
+               */
+
+               public function pointWithin(lon:Number,lat:Number):Boolean {
+                       if (!isArea()) return false;
+                       
+                       var counter:uint = 0;
+                       var p1x:Number = nodes[0].lon;
+                       var p1y:Number = nodes[0].lat;
+                       var p2x:Number, p2y:Number;
+                       for (var i:uint = 1; i <= length; i++) {
+                               p2x = nodes[i % length].lon;
+                               p2y = nodes[i % length].lat;
+                               if (lat > Math.min(p1y, p2y)) {
+                                       if (lat <= Math.max(p1y, p2y)) {
+                                               if (lon <= Math.max(p1x, p2x)) {
+                                                       if (p1y != p2y) {
+                                                               var xinters:Number = (lat - p1y) * (p2x - p1x) / (p2y - p1y) + p1x;
+                                                               if (p1x == p2x || lon <= xinters) counter++;
+                                                       }
+                                               }
+                                       }
+                               }
+                               p1x = p2x;
+                               p1y = p2y;
+                       }
+                       if (counter % 2 == 0) { return false; }
+                       else { return true; }
+               }
+
         /**
          * Finds the 1st way segment which intersects the projected
          * coordinate and adds the node to that segment. If snap is
index 1edadc9..c6ab833 100644 (file)
@@ -35,6 +35,7 @@ package net.systemeD.potlatch2.controller {
 
                override public function processKeyboardEvent(event:KeyboardEvent):ControllerState {
                        if (event.keyCode==74) return mergeWays();      // 'J'
+                       if (event.keyCode==75) return createMultipolygon();
                        var cs:ControllerState = sharedKeyboardEvents(event);
                        return cs ? cs : this;
                }
@@ -104,6 +105,72 @@ package net.systemeD.potlatch2.controller {
                        }
                        return false;
                }
+               
+               /** Create multipolygon from selection, or add to existing multipolygon. */
+               
+               public function createMultipolygon():ControllerState {
+                       var entity:Entity;
+                       var relation:Relation;
+                       var outer:Way;
+                       var inner:Way;
+                       var inners:Array=[];
+
+                       // If there's an existing outer in the selection, use that
+                       for each (entity in selection) {
+                               if (!entity is Way) continue;
+                               var r:Array=entity.findParentRelationsOfType('multipolygon','outer');
+                               if (r.length) { outer=Way(entity); relation=r[0]; }
+                       }
+
+                       // Otherwise, find the way with the biggest area
+                       var largest:Number=0;
+                       if (!outer) {
+                               for each (entity in selection) {
+                                       if (!entity is Way) continue;
+                                       if (!Way(entity).isArea()) continue;
+                                       var props:Object=layer.wayUIProperties(entity as Way);
+                                       if (props.patharea>largest) { outer=Way(entity); }
+                               }
+                       }
+                       
+                       // If we still don't have an outer, then squawk
+                       if (!outer) {
+                               controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "No areas selected"));
+                               return this;
+                       }
+                       
+                       // Identify the inners
+                       for each (entity in selection) {
+                               if (entity==outer) continue;
+                               if (!entity is Way) continue;
+                               if (!Way(entity).isArea()) continue;
+                               var node:Node=Way(entity).getFirstNode();
+                               if (outer.pointWithin(node.lon,node.lat)) inners.push(entity);
+                       }
+                       if (inners.length==0) {
+                controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't identify inner areas"));
+                               return this;
+                       }
+
+                       // If relation exists, add any inners that aren't currently present
+                       if (relation) {
+                               var action:CompositeUndoableAction = new CompositeUndoableAction("Add to multipolygon");
+                               for each (inner in inners) {
+                                       if (!relation.hasMemberInRole(inner,'inner'))
+                                               relation.appendMember(new RelationMember(inner,'inner'),action.push);
+                               }
+                               MainUndoStack.getGlobalStack().addAction(action);
+                               
+                       // Otherwise, create whole new relation
+                       } else {
+                               var memberlist:Array=[new RelationMember(outer,'outer')];
+                               for each (inner in inners) 
+                                       memberlist.push(new RelationMember(inner,'inner'));
+                               relation = entity.connection.createRelation( { type: 'multipolygon' }, memberlist, MainUndoStack.getGlobalStack().addAction);
+                       }
+
+                       return new SelectedWay(outer);
+               }
 
                override public function enterState():void {
                        selection=initSelection.concat();