return members.length;
}
+ public function findEntityMemberIndex(entity:Entity):int {
+ for (var index:uint = 0; index < members.length; index++) {
+ var member:RelationMember = members[index];
+ if ( member.entity == entity )
+ return index;
+ }
+ return -1;
+ }
+
public function getMember(index:uint):RelationMember {
return members[index];
}
public override function getType():String {
return 'relation';
}
+
+ public override function toString():String {
+ return "Relation("+id+"@"+version+"): "+members.length+" members "+getTagList();
+ }
+
}
}
var mapLoader:URLLoader = new URLLoader();
mapLoader.addEventListener(Event.COMPLETE, loadedMap);
+ mapLoader.addEventListener(IOErrorEvent.IO_ERROR, errorOnMapLoad);
+ mapLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, mapLoadStatus);
mapLoader.load(mapRequest);
dispatchEvent(new Event(LOAD_STARTED));
}
return tags;
}
+ private function errorOnMapLoad(event:Event):void {
+ trace("error loading map");
+ }
+ private function mapLoadStatus(event:HTTPStatusEvent):void {
+ trace("loading map status = "+event.status);
+ }
+
private function loadedMap(event:Event):void {
dispatchEvent(new Event(LOAD_COMPLETED));
version = uint(nodeData.@version);
var node:Node = getNode(id);
- if ( node == null ) {
+ if ( node == null || !node.loaded ) {
var lat:Number = Number(nodeData.@lat);
var lon:Number = Number(nodeData.@lon);
tags = parseTags(nodeData.tag);
- setNode(new Node(id, version, tags, true, lat, lon),false);
+ if ( node == null )
+ setNode(new Node(id, version, tags, true, lat, lon),false);
+ else
+ node.update(version, tags, true, lat, lon);
}
}
version = uint(data.@version);
var way:Way = getWay(id);
- if ( way == null ) {
+ if ( way == null || !way.loaded ) {
var nodes:Array = [];
for each(var nd:XML in data.nd)
nodes.push(getNode(Number(nd.@ref)));
tags = parseTags(data.tag);
- setWay(new Way(id, version, tags,true, nodes),false);
+ if ( way == null )
+ setWay(new Way(id, version, tags, true, nodes),false);
+ else
+ way.update(version, tags, true, nodes);
+ }
+ }
+
+ for each(var relData:XML in map.relation) {
+ id = Number(relData.@id);
+ version = uint(relData.@version);
+
+ var rel:Relation = getRelation(id);
+ if ( rel == null || !rel.loaded ) {
+ tags = parseTags(relData.tag);
+ var members:Array = [];
+ for each(var memberXML:XML in relData.member) {
+ var type:String = memberXML.@type.toLowerCase();
+ var role:String = memberXML.@role;
+ var memberID:Number = Number(memberXML.@ref);
+ var member:Entity = null;
+ if ( type == "node" ) {
+ member = getNode(memberID);
+ if ( member == null ) {
+ member = new Node(memberID,0,{},false,0,0);
+ setNode(Node(member),true);
+ }
+ } else if ( type == "way" ) {
+ member = getWay(memberID);
+ if (member == null) {
+ member = new Way(memberID,0,{},false,[]);
+ setWay(Way(member),true);
+ }
+ } else if ( type == "relation" ) {
+ member = getRelation(memberID);
+ if (member == null) {
+ member = new Relation(memberID,0,{},false,[]);
+ setRelation(Relation(member),true);
+ }
+ }
+
+ if ( member != null )
+ members.push(new RelationMember(member, role));
+ }
+
+ if ( rel == null )
+ setRelation(new Relation(id, version, tags, true, members), false);
+ else
+ rel.update(version,tags,true,members);
}
}
// Parse properties
// ** also do units, e.g. px/pt
if (a.match(COLOR)) {
- if (CSSCOLORS[t[a].toLowerCase()]) { t[a]=CSSCOLORS[t[a].toLowerCase()]; }
- else if ((o=HEX.exec(t[a]))) { t[a]=Number("0x"+o[1]); }
+ t[a] = parseCSSColor(t[a]);
}
// Set in styles
return null;
}
+ public static function parseCSSColor(colorStr:String):uint {
+ colorStr = colorStr.toLowerCase();
+ if (CSSCOLORS[colorStr])
+ return CSSCOLORS[colorStr];
+ else {
+ var match:Object = HEX.exec(colorStr);
+ if ( match )
+ return Number("0x"+match[1]);
+ }
+ return 0;
+ }
}
}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<mx:TitleWindow
+ xmlns:mx="http://www.adobe.com/2006/mxml"
+ xmlns:potlatch2="net.systemeD.potlatch2.*"
+ title="Edit Relation" width="350" height="400"
+ showCloseButton="true" close="PopUpManager.removePopUp(this);">
+
+ <potlatch2:TagViewer width="100%" height="100%" id="tagViewer"
+ creationComplete="checkRelation()"/>
+
+ <mx:Script><![CDATA[
+ import net.systemeD.halcyon.*;
+ import net.systemeD.halcyon.connection.*;
+ import net.systemeD.potlatch2.*;
+ import mx.managers.PopUpManager;
+
+ private var _relation:Relation;
+
+ public function setRelation(relation:Relation):void {
+ _relation = relation;
+// if ( tagViewer != null )
+// tagViewer.setEntity(_relation);
+ }
+
+ public function checkRelation():void {
+ if ( _relation != null )
+ tagViewer.setEntity(_relation);
+ }
+ ]]></mx:Script>
+</mx:TitleWindow>
+
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:flexlib="flexlib.containers.*"
backgroundColor="white"
- creationComplete="loadFeatures()">
+ initialize="loadFeatures()">
<mx:ViewStack id="stack" width="100%" height="100%">
<mx:VBox width="100%" height="100%" label="Simple">
<mx:htmlText><![CDATA[<i>No Selection</i>]]></mx:htmlText>
</mx:Label>
- <mx:DataGrid editable="true" width="100%" height="100%" id="advancedTagGrid">
+ <mx:DataGrid editable="true" width="100%" height="75%" id="advancedTagGrid">
<mx:columns>
<mx:DataGridColumn editable="true" dataField="key" headerText="Key"/>
<mx:DataGridColumn editable="true" dataField="value" headerText="Value"/>
<mx:LinkButton label="Delete" click="removeTag()"/>
<mx:LinkButton label="Add" click="addNewTag()"/>
</mx:HBox>
+
+ <mx:DataGrid editable="true" width="100%" height="25%" id="relationsGrid"
+ doubleClickEnabled="true"
+ itemDoubleClick="editRelation(ListEvent(event).rowIndex)">
+ <mx:columns>
+ <mx:DataGridColumn editable="false" dataField="description" headerText="Relation"/>
+ <mx:DataGridColumn editable="false" dataField="id" headerText="ID"/>
+ <mx:DataGridColumn editable="true" dataField="role" headerText="Role"/>
+ </mx:columns>
+ </mx:DataGrid>
+
+ <mx:HBox horizontalAlign="right" width="100%">
+ <mx:LinkButton label="Remove from" click="removeFromRelation()"/>
+ <mx:LinkButton label="Add to" click="addToRelation()"/>
+ </mx:HBox>
+
</mx:VBox>
</mx:ViewStack>
private var mapFeatures:MapFeatures;
private var selectedEntity:Entity;
- private var collection:ArrayCollection;
+ private var tagDataProvider:ArrayCollection;
private var tw:CategorySelector = null;
private var feature:Feature = null;
}
private function setupAdvanced(entity:Entity):void {
- if ( collection == null ) {
- collection = new ArrayCollection();
- advancedTagGrid.dataProvider = collection;
+ if ( tagDataProvider == null ) {
+ tagDataProvider = new ArrayCollection();
+ advancedTagGrid.dataProvider = tagDataProvider;
}
- collection.removeAll();
+ tagDataProvider.removeAll();
if ( entity == null ) {
advancedID.htmlText = "";
var tags:Array = entity.getTagArray();
tags.sortOn("key");
for each(var tag:Tag in tags)
- collection.addItem(tag);
+ tagDataProvider.addItem(tag);
+ }
+
+ if ( entity == null ) {
+ relationsGrid.dataProvider = null;
+ } else {
+ var relations:Array = [];
+ for each( var rel:Relation in entity.parentRelations ) {
+ var props:Object = {};
+ props["relation"] = rel;
+ props["id"] = rel.id;
+ var memberIndex:uint = rel.findEntityMemberIndex(entity);
+ props["role"] = rel.getMember(memberIndex).role;
+
+ var desc:String = "";
+ var relTags:Object = rel.getTagsHash();
+ if ( relTags["type"] ) {
+ desc = relTags["type"];
+ if ( relTags[desc] )
+ desc += " " + relTags[desc];
+ }
+ if ( relTags["ref"] )
+ desc += " " + relTags["ref"];
+ if ( relTags["name"] )
+ desc += " " + relTags["name"];
+ props["description"] = desc;
+
+ relations.push(props);
+ }
+ relationsGrid.dataProvider = relations;
}
}
+ private function editRelation(index:uint):void {
+ trace("edit relation "+index+" "+selectedEntity.parentRelations[index]);
+ var panel:RelationEditorPanel = RelationEditorPanel(
+ PopUpManager.createPopUp(Application(Application.application), RelationEditorPanel, true));
+ panel.setRelation(selectedEntity.parentRelations[index]);
+ PopUpManager.centerPopUp(panel);
+ }
+
private function tagChanged(event:TagEvent):void {
refreshFeatureIcon();
- if ( collection != null ) {
+ if ( tagDataProvider != null ) {
// check to see if the key is already in our list
var exists:Boolean = false;
var tag:Tag = null;
var i:uint;
- for ( i = 0; i < collection.length && !exists; i++ ) {
- tag = Tag(collection.getItemAt(i));
+ for ( i = 0; i < tagDataProvider.length && !exists; i++ ) {
+ tag = Tag(tagDataProvider.getItemAt(i));
exists = tag.key == event.key;
}
if ( !exists ) {
tag = new Tag(selectedEntity, event.key, event.newValue);
- collection.addItem(tag);
- collection.refresh();
+ tagDataProvider.addItem(tag);
+ tagDataProvider.refresh();
} else {
if ( event.newValue == null ) {
- collection.removeItemAt(i-1);
- collection.refresh();
+ tagDataProvider.removeItemAt(i-1);
+ tagDataProvider.refresh();
} else {
- collection.itemUpdated(tag, "value");
+ tagDataProvider.itemUpdated(tag, "value");
}
}
}
public function addNewTag():void {
var newKey:String = "(new tag)";
var newTag:Tag = new Tag(selectedEntity, newKey, "(new value)");
- collection.addItem(newTag);
- advancedTagGrid.editedItemPosition = {rowIndex: collection.getItemIndex(newTag), columnIndex: 0};
+ tagDataProvider.addItem(newTag);
+ advancedTagGrid.editedItemPosition = {rowIndex: tagDataProvider.getItemIndex(newTag), columnIndex: 0};
}
public function removeTag():void {
selectedEntity.setTag(k, null);
}
+ public function addToRelation():void {
+ }
+
+ public function removeFromRelation():void {
+ }
+
public function initFeatureBox():void {
tw = new CategorySelector();
tw.addEventListener("selectedType", changeFeatureType);
case "freetext": return new FreeTextEditorFactory(inputXML);
case "choice": return new ChoiceEditorFactory(inputXML);
case "speed": return new SpeedEditorFactory(inputXML);
+ case "route": return new RouteEditorFactory(inputXML);
}
_editors.push(editor);
}
}
+
public function get editors():Array {
return _editors;
}
public function htmlDetails(entity:Entity):String {
var icon:XMLList = _xml.icon;
+ return makeHTMLIcon(icon, entity);
+ }
+
+ public static function makeHTMLIcon(icon:XMLList, entity:Entity):String {
if ( icon == null )
return "";
-
+
var txt:String = icon.children().toXMLString();
var replaceTag:Function = function():String {
var value:String = entity.getTag(arguments[1]);
--- /dev/null
+package net.systemeD.potlatch2.mapfeatures.editors {
+
+ import net.systemeD.halcyon.connection.*;
+ import net.systemeD.potlatch2.mapfeatures.*;
+ import mx.containers.VBox;
+ import flash.events.*;
+
+ public class RelationMemberEditor extends VBox {
+
+ protected var _factory:RelationMemberEditorFactory;
+ protected var _entity:Entity;
+
+ [Bindable(event="factory_set")]
+ public function get fieldName():String {
+ return _factory == null ? "" : _factory.name;
+ }
+
+ [Bindable(event="factory_set")]
+ public function get fieldDescription():String {
+ return _factory == null ? "" : _factory.description;
+ }
+
+ [Bindable(event="relations_changed")]
+ public function get matchedRelations():Array {
+ if (_entity == null)
+ return [];
+
+ var relationTags:Object = _factory.relationTags;
+ var matched:Array = [];
+ for each(var relation:Relation in _entity.parentRelations) {
+ var addable:Boolean = true;
+ for ( var k:String in relationTags ) {
+ var relVal:String = relation.getTag(k);
+ if ( relVal != relationTags[k] )
+ addable = false;
+ }
+ if (addable)
+ matched.push(relation);
+ }
+ return matched;
+ }
+
+ public function addMember(relation:Relation, role:String):void {
+ if (_entity != null && !_entity.hasParent(relation))
+ relation.appendMember(new RelationMember(_entity, role));
+ }
+
+ public function set factory(factory:RelationMemberEditorFactory):void {
+ _factory = factory;
+ dispatchEvent(new Event("factory_set"));
+ }
+
+ public function set entity(entity:Entity):void {
+ _entity = entity;
+
+ // TODO: we need to listen for add/removal and relation tag changes
+ dispatchEvent(new Event("relations_changed"));
+ }
+
+ private function relationsChanged(event:TagEvent):void {
+ dispatchEvent(new Event("relations_changed"));
+ }
+
+ }
+
+}
+
+
--- /dev/null
+package net.systemeD.potlatch2.mapfeatures.editors {
+
+ import net.systemeD.halcyon.connection.*;
+ import net.systemeD.potlatch2.mapfeatures.*;
+ import flash.display.*;
+
+ public class RelationMemberEditorFactory extends EditorFactory {
+ private var _relationTags:Object;
+
+ public function RelationMemberEditorFactory(inputXML:XML) {
+ super(inputXML);
+ _relationTags = {};
+ for each(var match:XML in inputXML.match) {
+ _relationTags[match.@k] = match.@v;
+ }
+ }
+
+ public function get relationTags():Object {
+ return _relationTags;
+ }
+
+ 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) {
+ for ( var k:String in _relationTags ) {
+ var relVal:String = relation.getTag(k);
+ if ( relVal != _relationTags[k] )
+ return false;
+ }
+ }
+ // all must match
+ return true;
+ }
+
+ override public function createEditorInstance(entity:Entity):DisplayObject {
+ var editor:RelationMemberEditor = createRelationMemberEditor();
+ editor.factory = this;
+ editor.entity = entity;
+ return editor;
+ }
+
+ protected function createRelationMemberEditor():RelationMemberEditor {
+ return null;
+ }
+ }
+
+}
+
+
--- /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="addRoutes()">
+
+ <mx:Label text="{fieldName}:"/>
+ <mx:VBox verticalGap="0" width="100%" id="routeIcons"/>
+ <mx:LinkButton label="Add to route"/>
+
+ <mx:Script><![CDATA[
+ import net.systemeD.halcyon.connection.*;
+ import net.systemeD.potlatch2.*;
+ import mx.managers.PopUpManager;
+ import mx.core.*;
+
+ public function addRoutes():void {
+ for each(var relation:Relation in matchedRelations) {
+ var icon:RouteIcon = new RouteIcon();
+ icon.setRoute(relation, RouteEditorFactory(_factory).icon);
+
+ icon.addEventListener(MouseEvent.CLICK, function(event:Event):void {
+ trace(relation+" is clicked");
+ var panel:RelationEditorPanel = RelationEditorPanel(
+ PopUpManager.createPopUp(Application(Application.application), RelationEditorPanel, true));
+ panel.setRelation(relation);
+ PopUpManager.centerPopUp(panel);
+ });
+ routeIcons.addChild(icon);
+ }
+ }
+
+ ]]></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 RouteEditorFactory extends RelationMemberEditorFactory {
+ private var _icon:XMLList;
+
+ public function RouteEditorFactory(inputXML:XML) {
+ super(inputXML);
+ _icon = inputXML.icon;
+ }
+
+ override protected function createRelationMemberEditor():RelationMemberEditor {
+ return new RouteEditor();
+ }
+
+ public function get icon():XMLList {
+ return _icon;
+ }
+ }
+
+}
+
+
--- /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"
+ backgroundColor="{bg}" color="{fg}" backgroundAlpha="1.0">
+
+ <mx:Image source="{iconImage}"/>
+ <mx:Text condenseWhite="true" width="100%" htmlText="{iconHTML}" selectable="false"/>
+
+ <mx:Script><![CDATA[
+ import net.systemeD.halcyon.connection.*;
+ import net.systemeD.halcyon.styleparser.MapCSS;
+ import net.systemeD.potlatch2.mapfeatures.*;
+ import flash.events.*;
+
+ private var _iconImage:String = null;
+ private var _iconHTML:String = "";
+ private var _bg:String = "white";
+ private var _fg:String = "black";
+
+ public function setRoute(relation:Relation, icon:XMLList):void {
+ _iconHTML = Feature.makeHTMLIcon(icon, relation);
+
+ if ( icon.length() > 0 ) {
+ if (icon[0].hasOwnProperty("@image"))
+ _iconImage = icon[0].@image;
+ if (icon[0].hasOwnProperty("@foreground"))
+ _fg = icon[0].@foreground;
+ if (icon[0].hasOwnProperty("@background"))
+ _bg = icon[0].@background;
+ }
+
+ dispatchEvent(new Event("route_changed"));
+ }
+
+ [Bindable(event="route_changed")]
+ private function get iconImage():String {
+ return _iconImage;
+ }
+
+ [Bindable(event="route_changed")]
+ private function get iconHTML():String {
+ return _iconHTML;
+ }
+
+ [Bindable(event="route_changed")]
+ private function get fg():uint {
+ return MapCSS.parseCSSColor(_fg);
+ }
+
+ [Bindable(event="route_changed")]
+ private function get bg():uint {
+ return MapCSS.parseCSSColor(_bg);
+ }
+ ]]></mx:Script>
+</mx:HBox>
+
<inputSet ref="roadNames"/>
<inputSet ref="roadRestrictions"/>
<inputSet ref="roadPhysical"/>
+ <inputSet ref="cycle"/>
</inputSet>
<inputSet id="roadNames">
<inputSet ref="naptan" />
</inputSet>
+ <inputSet id="cycle">
+ <input type="route" name="National Cycle Route" description="National cycle route" category="Cycle" priority="normal">
+ <match k="type" v="route"/>
+ <match k="network" v="ncn"/>
+ <icon image="features/cycle__ncn.png" background="red" foreground="white">
+ <font size="14pt"><b>${ref}</b></font><br />
+ <font size="12pt">${name}</font>
+ </icon>
+ </input>
+ <input type="route" name="Regional Cycle Route" description="Regional cycle route" category="Cycle" priority="low">
+ <match k="type" v="route"/>
+ <match k="network" v="rcn"/>
+ <icon image="features/cycle__rcn.png" background="cyan" foreground="white">
+ <font size="14pt"><b>${ref}</b></font><br />
+ <font size="12pt">${name}</font>
+ </icon>
+ </input>
+ <input type="route" name="Local Cycle Route" description="Local cycle route" category="Cycle" priority="lowest">
+ <match k="type" v="route"/>
+ <match k="network" v="lcn"/>
+ <icon image="features/cycle__lcn.png" background="blue" foreground="white">
+ <font size="14pt"><b>${ref}</b></font><br />
+ <font size="12pt">${name}</font>
+ </icon>
+ </input>
+ </inputSet>
+
+ <inputSet id="cycle-route">
+ <inputSet ref="roadNames"/>
+ <inputSet ref="roadRefs"/>
+ </inputSet>
+
+
<feature name="Motorway">
<category>roads</category>
<icon image="features/highway__motorway.png">
<icon image="features/highway__residential.png">
<font size="14pt"><b>${name}</b></font><br/>
<font size="8pt">${postal_code}</font><br/>
- <font size="8pt"><i>A residential road is one surrounded by houses, and that isn't a higher classification.</i></font>
</icon>
<line/>
<tag k="highway" v="bus_stop"/>
<inputSet ref="buses" />
</feature>
+
+ <!-- cycle stuff -->
+ <feature name="National Cycle Network">
+ <category>paths</category>
+ <icon image="features/cycle__ncn.png" background="red" foreground="white">
+ <font size="14pt"><b>${ref}</b></font><br />
+ <font size="12pt">${name}</font>
+ </icon>
+
+ <relation/>
+ <tag k="type" v="route"/>
+ <tag k="network" v="ncn"/>
+
+ <inputSet ref="cycle-route"/>
+ </feature>
+
+ <feature name="Regional Cycle Network">
+ <category>paths</category>
+ <icon image="features/cycle__rcn.png" background="cyan" foreground="white">
+ <font size="14pt"><b>${ref}</b></font><br />
+ <font size="12pt">${name}</font>
+ </icon>
+
+ <relation/>
+ <tag k="type" v="route"/>
+ <tag k="network" v="rcn"/>
+
+ <inputSet ref="cycle-route"/>
+ </feature>
+
+ <feature name="Local Cycle Network">
+ <category>paths</category>
+ <icon image="features/cycle__lcn.png" background="blue" foreground="white">
+ <font size="14pt"><b>${ref}</b></font><br />
+ <font size="12pt">${name}</font>
+ </icon>
+
+ <relation/>
+ <tag k="type" v="route"/>
+ <tag k="network" v="lcn"/>
+
+ <inputSet ref="cycle-route"/>
+ </feature>
+
</mapFeatures>