start of turn restriction editor
authorRichard Fairhurst <richard@systemed.net>
Wed, 7 Jul 2010 13:05:19 +0000 (13:05 +0000)
committerRichard Fairhurst <richard@systemed.net>
Wed, 7 Jul 2010 13:05:19 +0000 (13:05 +0000)
14 files changed:
net/systemeD/halcyon/connection/Entity.as
net/systemeD/halcyon/connection/Relation.as
net/systemeD/potlatch2/TagViewer.mxml
net/systemeD/potlatch2/controller/SelectedWay.as
net/systemeD/potlatch2/mapfeatures/EditorFactory.as
net/systemeD/potlatch2/mapfeatures/Feature.as
net/systemeD/potlatch2/mapfeatures/MapFeatures.as
net/systemeD/potlatch2/mapfeatures/editors/RelationMemberEditor.as
net/systemeD/potlatch2/mapfeatures/editors/RelationMemberEditorFactory.as
net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml [new file with mode: 0755]
net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditorFactory.as [new file with mode: 0755]
net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml [new file with mode: 0755]
net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionRenderer.mxml [new file with mode: 0644]
resources/map_features.xml

index 3d6a7ac..8fd024c 100644 (file)
@@ -191,17 +191,25 @@ package net.systemeD.halcyon.connection {
                public function findParentRelationsOfType(type:String, role:String=""):Array {
                        var a:Array=[];
                        for (var o:Object in parents) {
-                               if (o is Relation && Relation(o).tagIs('type',type)) { 
-                                       for (var i:uint=0; i<o.length; i++) {
-                                               if (o.getMember(i).entity==this && o.getMember(i).role==role) {
-                                                       a.push(o);
-                                               }
-                                       }
+                               if (o is Relation && Relation(o).tagIs('type',type) && Relation(o).hasMemberInRole(this,role)) { 
+                                       a.push(o);
                                }
                        }
                        return a;
                }
                
+               public function countParentObjects(within:Object):uint {
+                       var count:uint=0;
+                       for (var o:Object in parents) {
+                               if (o.getType()==within.entity && o.getTag(within.k)) {
+                                       if (within.v && within.v!=o.getTag(within.k)) { break; }
+                                       if (within.role && !Relation(o).hasMemberInRole(this,within.role)) { break; }
+                                       count++;
+                               }
+                       }
+                       return count;
+               }
+               
                public function get parentObjects():Array {
                        var a:Array=[];
                        for (var o:Object in parents) { a.push(o); }
@@ -240,6 +248,14 @@ package net.systemeD.halcyon.connection {
 
                // To be overridden
 
+               public function getDescription():String {
+                       var basic:String=this.getType()+" "+_id;
+                       if (tags['ref'] && tags['name']) { return tags['ref']+' '+tags['name']+' ('+basic+')'; }
+                       if (tags['ref']) { return tags['ref']+' ('+basic+')'; }
+                       if (tags['name']) { return tags['name']+' ('+basic+')'; }
+                       return basic;
+               }
+
         public function getType():String {
             return '';
         }
index 5cab404..7b59121 100644 (file)
@@ -68,6 +68,13 @@ package net.systemeD.halcyon.connection {
                        return a;
                }
 
+               public function hasMemberInRole(entity:Entity,role:String):Boolean {
+            for (var index:uint = 0; index < members.length; index++) {
+                               if (members[index].role==role && members[index].entity == entity) { return true; }
+                       }
+                       return false;
+               }
+               
         public function insertMember(index:uint, member:RelationMember, performAction:Function):void {
             performAction(new AddMemberToRelationAction(this, index, member, members));
         }
@@ -105,7 +112,7 @@ package net.systemeD.halcyon.connection {
                        return (deleted || (members.length==0));
                }
 
-        public function getDescription():String {
+        public override function getDescription():String {
             var desc:String = "";
             var relTags:Object = getTagsHash();
             if ( relTags["type"] ) {
index 9e6eeae..e5f613b 100644 (file)
                   feature.addEventListener("imageChanged", featureImageChanged);
           }
 
-          if ( feature != null ) {
+          if ( feature != null && feature.name != null ) {
               setFeatureIcon(selectedEntity, feature);
           } else {
               blankFeatureIcon(selectedEntity);
index 9c6a777..23ba563 100644 (file)
@@ -45,14 +45,14 @@ package net.systemeD.potlatch2.controller {
                                // start new way
                                var way:Way = controller.connection.createWay({}, [entity], MainUndoStack.getGlobalStack().addAction);
                                return new DrawWay(way, true, false);
-                       } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && event.ctrlKey ) {
-                               // merge way
-                               return mergeWith(entity as Way);
                        } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && focus==selectedWay && event.shiftKey) {
                                // insert node within way (shift-click)
                 var d:DragWayNode=new DragWayNode(selectedWay, addNode(event), event, true);
                                d.forceDragStart();
                                return d;
+                       } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && event.shiftKey ) {
+                               // merge way
+                               return mergeWith(entity as Way);
                        }
                        var cs:ControllerState = sharedMouseEvents(event, entity);
                        return cs ? cs : this;
index eae6031..6e0bd18 100644 (file)
@@ -19,6 +19,7 @@ package net.systemeD.potlatch2.mapfeatures {
             case "choice": return new ChoiceEditorFactory(inputXML);
             case "speed": return new SpeedEditorFactory(inputXML);
             case "route": return new RouteEditorFactory(inputXML);
+            case "turn": return new TurnRestrictionEditorFactory(inputXML);
             
             }
             
index fac24df..69a1dfb 100644 (file)
@@ -13,23 +13,32 @@ package net.systemeD.potlatch2.mapfeatures {
         private var _xml:XML;
         private static var variablesPattern:RegExp = /[$][{]([^}]+)[}]/g;
         private var _tags:Array;
+               private var _withins:Array;
         private var _editors:Array;
 
         public function Feature(mapFeatures:MapFeatures, _xml:XML) {
             this.mapFeatures = mapFeatures;
             this._xml = _xml;
-            parseTags();
+            parseConditions();
             parseEditors();
         }
         
-        private function parseTags():void {
-            _tags = new Array();
+        private function parseConditions():void {
+            _tags = [];
+                       _withins = [];
             
+                       // parse tags
             for each(var tag:XML in definition.tag) {
-                var tagObj:Object = new Object();
-                tagObj["k"] = tag.@k;
-                tagObj["v"] = tag.@v;
-                _tags.push(tagObj);
+                _tags.push( { k:tag.@k, v:tag.@v} );
+            }
+
+                       // parse 'within'
+            for each(var within:XML in definition.within) {
+                               var obj:Object= { entity:within.@entity, k:within.@k };
+                               if (within.attribute('v'      ).length()>0) { obj['v'      ]=within.@v;       }
+                               if (within.attribute('minimum').length()>0) { obj['minimum']=within.@minimum; }
+                               if (within.attribute('role'   ).length()>0) { obj['role'   ]=within.@role;    }
+                _withins.push(obj);
             }
         }
         
@@ -78,7 +87,8 @@ package net.systemeD.potlatch2.mapfeatures {
     
         [Bindable(event="nameChanged")]
         public function get name():String {
-            return _xml.@name;
+                       if (_xml.attribute('name').length()>0) { return _xml.@name; }
+                       return null;
         }
     
         [Bindable(event="imageChanged")]
@@ -141,6 +151,10 @@ package net.systemeD.potlatch2.mapfeatures {
         public function get tags():Array {
             return _tags;
         }
+
+        public function get withins():Array {
+            return _withins;
+        }
         
         public function findFirstCategory():Category {
             for each( var cat:Category in mapFeatures.categories ) {
index 3d3d28f..13428df 100644 (file)
@@ -63,15 +63,26 @@ package net.systemeD.potlatch2.mapfeatures {
                 return null;
 
             for each(var feature:Feature in features) {
-                // check for matching tags
                 var match:Boolean = true;
+
+                // check for matching tags
                 for each(var tag:Object in feature.tags) {
                     var entityTag:String = entity.getTag(tag.k);
                     match = entityTag == tag.v || (entityTag != null && tag.v == "*");
                     if ( !match ) break;
                 }
-                if ( match )
+
+                               // check for matching withins
+                               if (match) {
+                                       for each (var within:Object in feature.withins) {
+                                               match = entity.countParentObjects(within) >= (within.minimum ? within.minimum : 1);
+                                               if (!match) { break; }
+                                       }
+                               }
+
+                if (match) {
                     return feature;
+                               }
             }
             return null;
         }
index f002cc2..92ce619 100644 (file)
@@ -34,6 +34,8 @@ package net.systemeD.potlatch2.mapfeatures.editors {
                   if ( relVal != relationTags[k] )
                       addable = false;
               }
+              if (_factory.role && !relation.hasMemberInRole(_entity,_factory.role) ) { addable=false; }
+
               if (addable) {
                   for each( var memberIndex:int in relation.findEntityMemberIndexes(_entity)) {
                       var props:Object = {};
index 86cd9aa..180810e 100644 (file)
@@ -6,6 +6,7 @@ package net.systemeD.potlatch2.mapfeatures.editors {
 
        public class RelationMemberEditorFactory extends EditorFactory {
            private var _relationTags:Object;
+               private var _role:String;
         
         public function RelationMemberEditorFactory(inputXML:XML) {
             super(inputXML);
@@ -13,27 +14,35 @@ package net.systemeD.potlatch2.mapfeatures.editors {
             for each(var match:XML in inputXML.match) {
                 _relationTags[match.@k] = match.@v;
             }
+                       for each(var role:XML in inputXML.role) {
+                               _role=role.@role;
+                       }
         }
         
         public function get relationTags():Object {
             return _relationTags;
         }
         
+        public function get role():String {
+            return _role;
+        }
+        
         override public function areTagsMatching(entity:Entity):Boolean {
             var parentRelations:Array = entity.parentRelations;
             if ( parentRelations.length == 0 )
                 return false;
-                
+
             // get relations for the entity
             for each(var relation:Relation in parentRelations) {
+                               var match:Boolean=true;
                 for ( var k:String in _relationTags ) {
                     var relVal:String = relation.getTag(k);
-                    if ( relVal != _relationTags[k] )
-                        return false;
+                    if ( relVal != _relationTags[k] ) { match=false; break; }
+                                       if ( _role && !relation.hasMemberInRole(entity,_role) ) { match=false; break; }
                 }
+                               if (match) { return true; }
             }
-            // all must match
-            return true;
+                       return false;
         }
         
         override public function createEditorInstance(entity:Entity):DisplayObject {
diff --git a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml
new file mode 100755 (executable)
index 0000000..b007f97
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<edit:RelationMemberEditor
+       xmlns:mx="http://www.adobe.com/2006/mxml" 
+       xmlns:edit="net.systemeD.potlatch2.mapfeatures.editors.*"
+       xmlns:flexlib="flexlib.controls.*"
+       verticalGap="0"
+       width="100%"
+       toolTip="{fieldDescription}"
+       initialize="addTurnRestrictions()">
+
+  <mx:Label text="{fieldName}:"/>
+  <mx:VBox verticalGap="0" width="100%" id="turnRestrictionIcons"/>
+  <mx:LinkButton label="Add new turn restriction" click="addNewTurnRestriction()" />
+
+  <mx:Script><![CDATA[
+       import net.systemeD.halcyon.connection.*;
+       import net.systemeD.potlatch2.*;
+       import mx.managers.PopUpManager;
+       import mx.core.*;
+
+       /* ** FIXME: some of this is common with RouteEditor.mxml. We can probably refactor */
+
+       public function addTurnRestrictions(event:Event=null):void {
+               turnRestrictionIcons.removeAllChildren();
+               addEventListener("relations_changed", addTurnRestrictions);
+               for each(var relation:Object in matchedRelations) {
+                       var icon:TurnRestrictionIcon = new TurnRestrictionIcon();
+                       icon.setTurnRestriction(relation);
+                       
+                       icon.addEventListener(MouseEvent.CLICK, turnRestrictionClicked);
+                       turnRestrictionIcons.addChild(icon);
+               }
+       }
+       
+       public function turnRestrictionClicked(event:Event):void {
+               var relation:Object = TurnRestrictionIcon(event.currentTarget).turnRestriction;
+               var panel:RelationEditorPanel = RelationEditorPanel(
+                       PopUpManager.createPopUp(Application(Application.application), RelationEditorPanel, true));
+               panel.setRelation(relation["relation"]);
+               PopUpManager.centerPopUp(panel);
+       }
+
+       public function addNewTurnRestriction():void {
+               var conn:Connection = Connection.getConnectionInstance();
+               var relation:Relation = conn.createRelation(
+                       { type: 'restriction' }, 
+                       [ new RelationMember(_entity, 'via') ],
+            MainUndoStack.getGlobalStack().addAction);
+               addTurnRestrictions();
+       }
+       
+  ]]></mx:Script>
+</edit:RelationMemberEditor>
+
diff --git a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditorFactory.as b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditorFactory.as
new file mode 100755 (executable)
index 0000000..d75b8dd
--- /dev/null
@@ -0,0 +1,19 @@
+package net.systemeD.potlatch2.mapfeatures.editors {
+
+       import net.systemeD.halcyon.connection.*;
+       import net.systemeD.potlatch2.mapfeatures.*;
+       import flash.display.*;
+
+       public class TurnRestrictionEditorFactory extends RelationMemberEditorFactory {
+
+               public function TurnRestrictionEditorFactory(inputXML:XML) {
+                       super(inputXML);
+               }
+               
+               override protected function createRelationMemberEditor():RelationMemberEditor {
+                       return new TurnRestrictionEditor();
+               }
+
+       }
+
+}
diff --git a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml
new file mode 100755 (executable)
index 0000000..27a2c18
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<mx:HBox
+       xmlns:mx="http://www.adobe.com/2006/mxml" 
+       xmlns:edit="net.systemeD.potlatch2.mapfeatures.editors.*"
+       xmlns:flexlib="flexlib.controls.*"
+       borderStyle="inset" verticalAlign="middle" width="100%" paddingLeft="3">
+       
+       <mx:Grid width="100%">
+               <mx:GridRow>
+                       <mx:GridItem rowSpan="2">
+                               <edit:ChoiceComboBox id="inputBox" dropdownFactory="mx.controls.TileList"
+                                       itemRenderer="net.systemeD.potlatch2.mapfeatures.editors.TurnRestrictionRenderer"
+                                               change="setRestrictionType(inputBox.selectedItem.data)" 
+                                       click="event.stopPropagation()" 
+                                               selectedItem="{findSelectedRestrictionType()}"
+                                           dropdownWidth="162" rowCount="2"
+                                           fontWeight="normal">
+                                       <mx:ArrayCollection id="restrictionTypes">
+                                               <mx:Object label="No left turn" data='no_left_turn' />
+                                               <mx:Object label="No right turn" data='no_right_turn' />
+                                               <mx:Object label="No U turns" data='no_u_turn' />
+                                               <mx:Object label="No straight on" data='no_straight_on' />
+                                               <mx:Object label="Left turn only" data='only_left_turn' />
+                                               <mx:Object label="Right turn only" data='only_right_turn' />
+                                               <mx:Object label="Straight on only" data='only_straight_on' />
+                                       </mx:ArrayCollection>
+                               </edit:ChoiceComboBox>
+                               </mx:GridItem>
+                       <mx:GridItem><mx:Text text="From" selectable="false"/></mx:GridItem>
+                       <mx:GridItem><mx:ComboBox id="from" dataProvider="{connectingWays}"
+                                                       selectedItem="{findSelected('from')}"
+                                                       change="setMember(from.selectedItem.data,'from')"
+                                                       click="event.stopPropagation()" /></mx:GridItem>
+               </mx:GridRow>
+               <mx:GridRow>
+                       <mx:GridItem><mx:Text text="To" selectable="false"/></mx:GridItem>
+                       <mx:GridItem><mx:ComboBox id="to" dataProvider="{connectingWays}"
+                                                       selectedItem="{findSelected('to')}"
+                                                       change="setMember(to.selectedItem.data,'to')"
+                                               click="event.stopPropagation()" /></mx:GridItem>
+               </mx:GridRow>
+       </mx:Grid>
+
+    <mx:Script><![CDATA[
+    import net.systemeD.halcyon.connection.*;
+    import net.systemeD.halcyon.styleparser.RuleSet;
+    import net.systemeD.potlatch2.mapfeatures.*;
+    import flash.events.*;
+    import mx.collections.ArrayCollection;
+       import mx.utils.ObjectProxy;
+
+       /* ** FIXME as of Wed:
+                 - 'from' and 'to' don't get populated with original values
+                 - scrollbar ugh
+                 - "warning: unable to bind to property 'data' on class 'Object' (class is not an IEventDispatcher)"
+                 - (plus general node redraw bug when you change something)
+       */
+
+    private var _turn:Object;
+    
+    public function setTurnRestriction(restriction:Object):void {   
+        _turn = restriction;
+    }
+    
+       private function findSelected(role:String):Object {
+               trace("calling findSelected for "+role);
+               var rel:Relation=Relation(_turn.relation);
+               for each (var item:Object in connectingWays) {
+                       if (rel.hasMemberInRole(item.data,role)) { return item; }
+               }
+               return null;
+       }
+
+       private function get connectingWays():ArrayCollection {
+               trace("calling connectingWays");
+               var ways:Array=[];
+               for each (var way:Way in Entity(_turn.entity).parentWays) {
+                       ways.push( { label:way.getDescription(), data:way } );
+               }
+               return new ArrayCollection(ways);
+       }
+    
+       private function setMember(entity:Entity,role:String):void {
+               trace("setMember "+role+" to "+entity);
+               var rel:Relation=Relation(_turn.relation);
+        var undo:CompositeUndoableAction = new CompositeUndoableAction("Delete refs");
+
+               // first, remove existing role from relation
+               var old:Array=rel.findMembersByRole(role);
+               for each (var memberEntity:Entity in old) {
+                       rel.removeMember(memberEntity, undo.push);
+               }
+               undo.doAction();
+
+               // now add new entity
+               // ** FIXME - appendMember should be undoable
+               rel.appendMember(new RelationMember(entity,role))
+       }
+       
+       private function setRestrictionType(type:String):void {
+               var rel:Relation=Relation(_turn.relation);
+               rel.setTag('restriction', type, MainUndoStack.getGlobalStack().addAction);
+               trace ("set restriction type to "+type);
+       }
+       
+       private function findSelectedRestrictionType():Object {
+               var type:String=Relation(_turn.relation).getTag('restriction');
+               for each (var item:Object in restrictionTypes) {
+                       if (item.data==type) { return item; }
+               }
+               return null;
+       }
+
+    public function get turnRestriction():Object {
+        return _turn;
+    }
+    
+    ]]></mx:Script>
+</mx:HBox>
+
diff --git a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionRenderer.mxml b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionRenderer.mxml
new file mode 100644 (file)
index 0000000..dcb07d0
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<mx:Canvas
+       xmlns:mx="http://www.adobe.com/2006/mxml" 
+       xmlns:edit="net.systemeD.potlatch2.mapfeatures.editors.*"
+       width="40" height="40" maxWidth="40" maxHeight="40" minWidth="40" minHeight="40"
+       toolTip="{data.label}"
+       mouseEnabled="false" mouseChildren="false">
+
+    <mx:Image x="0" y="0" source="{'features/restriction__'+data.data+'.png'}"  left="4" top="4" />
+  
+</mx:Canvas>
+
index 15b0e3d..62fe7f2 100644 (file)
     <inputSet ref="cycle"/>
     <inputSet ref="bus-route"/>
   </inputSet>
+  
+  <inputSet id="junctionNode">
+    <inputSet ref="turnRestrictions"/>
+  </inputSet>
 
   <inputSet id="roadRefs">
     <input type="freetext" presence="always"
         key="maxspeed"/>
   </inputSet>
   
+  <inputSet id="turnRestrictions">
+    <input type="turn" name="Turn restriction" description="Turn restriction" category="Restrictions" priority="normal" presence="onTagMatch">
+       <match k="type" v="restriction"/>
+       <role role="via"/>
+    </input>
+  </inputSet>
+
   <inputSet id="naptan">
     <input type="freetext" presence="onTagMatch" category="Naptan" description="12 character internal Naptan ID" name="Atco Code" key="naptan:AtcoCode" />
     <input type="choice" presence="onTagMatch" category="Naptan" description="The eight-point compass bearning" name="Naptan Bearing" key="naptan:Bearing" >
     <input type="freetext" presence="always" category="Naming" name="Network" key="network" description="The network of the tram service" priority="low"/>
   </feature>
   
+
+    <!-- Junction nodes (for turn restrictions) -->
+
+    <feature>
+       <point/>
+       <within entity="way" k="highway" minimum="2"/>
+       <inputSet ref="junctionNode"/>
+    </feature>
+
 </mapFeatures>