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); }
// 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 '';
}
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));
}
return (deleted || (members.length==0));
}
- public function getDescription():String {
+ public override function getDescription():String {
var desc:String = "";
var relTags:Object = getTagsHash();
if ( relTags["type"] ) {
feature.addEventListener("imageChanged", featureImageChanged);
}
- if ( feature != null ) {
+ if ( feature != null && feature.name != null ) {
setFeatureIcon(selectedEntity, feature);
} else {
blankFeatureIcon(selectedEntity);
// 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;
case "choice": return new ChoiceEditorFactory(inputXML);
case "speed": return new SpeedEditorFactory(inputXML);
case "route": return new RouteEditorFactory(inputXML);
+ case "turn": return new TurnRestrictionEditorFactory(inputXML);
}
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);
}
}
[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")]
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 ) {
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;
}
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 = {};
public class RelationMemberEditorFactory extends EditorFactory {
private var _relationTags:Object;
+ private var _role:String;
public function RelationMemberEditorFactory(inputXML:XML) {
super(inputXML);
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 {
--- /dev/null
+<?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>
+
--- /dev/null
+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();
+ }
+
+ }
+
+}
--- /dev/null
+<?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>
+
--- /dev/null
+<?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>
+
<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>