1 package net.systemeD.potlatch2.mapfeatures {
3 import flash.events.Event;
4 import flash.events.EventDispatcher;
6 import flash.utils.ByteArray;
8 import mx.core.BitmapAsset;
9 import mx.graphics.codec.PNGEncoder;
11 import net.systemeD.halcyon.connection.Entity;
12 import net.systemeD.potlatch2.utils.CachedDataLoader;
14 /** A "map feature" is sort of a template for a map entity. It consists of a few crucial key/value pairs that define the feature, so that
15 * entities can be recognised. It also contains optional keys, with associated editing controls, that are defined as being appropriate
17 public class Feature extends EventDispatcher {
18 private var mapFeatures:MapFeatures;
20 private static var variablesPattern:RegExp = /[$][{]([^}]+)[}]/g;
21 private var _tags:Array;
22 private var _withins:Array;
23 private var _editors:Array;
25 [Embed(source="../../../../embedded/missing_icon.png")]
27 public var missingIconCls:Class;
30 /** Create this Feature from an XML subtree. */
31 public function Feature(mapFeatures:MapFeatures, _xml:XML) {
32 this.mapFeatures = mapFeatures;
38 private function parseConditions():void {
43 for each(var tag:XML in definition.tag) {
44 _tags.push( { k:String(tag.@k), v:String(tag.@v), vmatch:String(tag.@vmatch)} );
48 for each(var within:XML in definition.within) {
49 var obj:Object= { entity:within.@entity, k:within.@k };
50 if (within.attribute('v' ).length()>0) { obj['v' ]=within.@v; }
51 if (within.attribute('minimum').length()>0) { obj['minimum']=within.@minimum; }
52 if (within.attribute('role' ).length()>0) { obj['role' ]=within.@role; }
57 private function parseEditors():void {
58 _editors = new Array();
60 addEditors(definition);
62 _editors.sortOn(["sortOrder", "name"], [Array.DESCENDING | Array.NUMERIC, Array.CASEINSENSITIVE]);
65 private function addEditors(xml:XML):void {
68 for each(var inputSetRef:XML in xml.inputSet) {
69 var setName:String = String(inputSetRef.@ref);
70 for each (inputXML in mapFeatures.definition.inputSet.(@id==setName)) {
75 for each(inputXML in xml.input) {
80 private function addEditor(inputXML:XML):void {
81 var inputType:String = inputXML.@type;
82 var presenceStr:String = inputXML.@presence;
83 var sortOrderStr:String = inputXML.@priority;
84 // _tags.push( { k:String(inputXML.@key) } ); /* add the key to tags so that e.g. addr:housenumber shows up on autocomplete */
85 var editor:EditorFactory = EditorFactory.createFactory(inputType, inputXML);
86 if ( editor != null ) {
87 editor.presence = Presence.getPresence(presenceStr);
88 editor.sortOrder = EditorFactory.getPriority(sortOrderStr);
89 _editors.push(editor);
93 /** List of editing controls associated with this feature. */
94 public function get editors():Array {
98 /** The XML subtree that this feature was loaded from. */
99 public function get definition():XML {
103 [Bindable(event="nameChanged")]
104 /** The human-readable name of the feature (@name), or null if none. */
105 public function get name():String {
106 if (_xml.attribute('name').length()>0) { return _xml.@name; }
110 [Bindable(event="imageChanged")]
111 /** An icon for the feature (from icons[0]/@image). If none is defined, return default "missing icon". */
112 public function get image():ByteArray {
113 var icon:XMLList = _xml.icon;
114 var imageURL:String = null;
118 if ( icon.length() > 0 && icon[0].hasOwnProperty("@image") )
119 imageURL = icon[0].@image;
121 if ( imageURL != null ) {
122 img = CachedDataLoader.loadData(imageURL, imageLoaded);
127 var bitmap:BitmapAsset = new missingIconCls() as BitmapAsset;
128 return new PNGEncoder().encode(bitmap.bitmapData);
131 /** Can this feature be drag-and-dropped from the side panel? By default, any "point" feature can,
132 * unless it has <point draganddrop="no"/>
134 public function canDND():Boolean {
135 var point:XMLList = _xml.elements("point");
136 return point.length() > 0 && !(XML(point[0]).attribute("draganddrop")[0] == "no");
139 private function imageLoaded(url:String, data:ByteArray):void {
140 dispatchEvent(new Event("imageChanged"));
143 public function htmlDetails(entity:Entity):String {
144 var icon:XMLList = _xml.icon;
145 return makeHTMLIcon(icon, entity);
148 /** Convert the contents of the "icon" tag as an HTML string, with variable substitution. */
149 public static function makeHTMLIcon(icon:XMLList, entity:Entity):String {
153 var txt:String = icon.children().toXMLString();
154 var replaceTag:Function = function():String {
155 var value:String = entity.getTag(arguments[1]);
156 return value == null ? "" : htmlEscape(value);
158 txt = txt.replace(variablesPattern, replaceTag);
162 /** Basic HTML escaping. */
163 public static function htmlEscape(str:String):String {
164 var newStr:String = str.replace(/&/g, "&");
165 newStr = newStr.replace(/</g, "<");
166 newStr = newStr.replace(/>/g, ">");
167 newStr = newStr.replace(/"/g, """); // "
168 newStr = newStr.replace(/'/g, "'"); // '
172 /** Whether this feature belongs to the given category or not, as defined by its definition in the XML file. */
173 public function isInCategory(category:String):Boolean {
174 var cats:XMLList = _xml.category;
175 if ( cats.length() == 0 )
178 for each( var cat:XML in cats )
179 if ( cat.text()[0] == category )
185 /** List of {k, v} pairs that define the feature. */
186 public function get tags():Array {
190 /** List of "withins" which further restrict the applicability of the feature. Each within is a {entity, k, ?v, ?minimum, ?role} object. */
191 public function get withins():Array {
195 /** The first category that the feature belongs to, as defined by the order of the map features XML file. */
196 public function findFirstCategory():Category {
197 for each( var cat:Category in mapFeatures.categories ) {
198 if ( isInCategory(cat.id) )
204 /** Whether the feature is of the given type (point, line/area, relation). */
205 public function isType(type:String):Boolean {
207 return (_xml.elements(type).length() > 0) || (_xml.elements('line').length() > 0);
209 return _xml.elements(type).length() > 0;
213 /** Whether there is a help string defined or one can be derived from tags. */
214 public function hasHelpURL():Boolean {
215 return _xml.help.length() > 0 || _tags.length > 0;
218 /** The defined help string, if any. If none, generate one from tags on the feature, pointing to the OSM wiki. */
219 public function get helpURL():String {
220 if (_xml.help.length() > 0)
222 else if (_tags.length > 0) {
223 if (_tags[0].v == "*")
224 return "http://www.openstreetmap.org/wiki/Key:" + _tags[0].k;
226 return "http://www.openstreetmap.org/wiki/Tag:" + _tags[0].k + "=" + _tags[0].v;