1 package net.systemeD.potlatch2.mapfeatures {
3 import flash.events.Event;
4 import flash.events.EventDispatcher;
7 import net.systemeD.halcyon.NestedXMLLoader;
8 import net.systemeD.halcyon.connection.*;
10 /** All the information about all available map features that can be selected by the user or matched against entities in the map.
11 * The list of map features is populated from an XML file the first time the MapFeatures instance is accessed.
13 * <p>There are four "types" of features: point, line, area, relation. However, the autocomplete functions refer to these as node,
14 * way (line/area) and relation.</p>
16 public class MapFeatures extends EventDispatcher {
17 private static var instance:MapFeatures;
19 /** Instantiates MapFeatures by loading it if required. */
20 public static function getInstance():MapFeatures {
21 if ( instance == null ) {
22 instance = new MapFeatures();
23 instance.loadFeatures();
28 private var xml:XML = null;
29 private var _features:Array = null;
30 private var _categories:Array = null;
31 // private var _keys:Array = null;
32 private var _tags:Object = null;
34 /** Loads list of map features from XML file which it first retrieves. */
35 protected function loadFeatures():void {
36 var xmlLoader:NestedXMLLoader = new NestedXMLLoader();
37 xmlLoader.addEventListener(Event.COMPLETE, onFeatureLoad);
38 xmlLoader.load("map_features.xml");
41 /** The loaded source XML file itself. */
42 internal function get definition():XML {
46 /** Load XML file, then trawl over it, setting up convenient indexes into the list of map features. */
47 private function onFeatureLoad(event:Event):void {
50 xml = NestedXMLLoader(event.target).xml;
52 _tags = { relation:{}, way:{}, node:{} };
54 for each(var feature:XML in xml..feature) {
55 f=new Feature(this,feature);
57 for each (var tag:Object in f.tags) {
58 if (f.isType('line') || f.isType('area')) { addToTagList('way',tag); }
59 if (f.isType('relation')) { addToTagList('relation',tag); }
60 if (f.isType('point')) { addToTagList('node',tag); }
64 _categories = new Array();
65 for each(var catXML:XML in xml.category) {
66 if ( catXML.child("category").length() == 0 )
67 _categories.push(new Category(this, catXML.@name, catXML.@id, _categories.length));
69 dispatchEvent(new Event("featuresLoaded"));
72 /** Add one item to tagList index, which will end up being a list like: ["way"]["highway"]["residential"] */
73 private function addToTagList(type:String,tag:Object):void {
74 if (tag.v=='*') { return; }
75 if (!_tags[type][tag.k]) { _tags[type][tag.k]=new Array(); }
76 if (_tags[type][tag.k].indexOf(tag.v)==-1) { _tags[type][tag.k].push(tag.v); }
79 /** Indicates whether the XML file has finished being loaded. */
80 public function hasLoaded():Boolean {
84 /** Find the first Feature (template) that matches the given Entity (actual existing object in the map).
86 * This is done to provide appropriate editing controls that correspond to the selected Entity.
88 * @param entity The Entity to try and match against.
89 * @return The first suitable Feature, or null. */
91 public function findMatchingFeature(entity:Entity):Feature {
95 for each(var feature:Feature in features) {
96 var match:Boolean = true;
98 // check for matching tags
99 // the "match" attribute lets you specify other values that will match this feature
100 // but won't affect the default value assigned. format is "*" or a regex.
101 for each(var tag:Object in feature.tags) {
102 var entityTag:String = entity.getTag(tag.k);
103 if (entityTag == null) { match = false; break; }
108 || tag.vmatch != "" && entityTag.match(new RegExp("^" + tag.vmatch + "$"));
112 // check for matching withins
114 for each (var within:Object in feature.withins) {
115 match = entity.countParentObjects(within) >= (within.minimum ? within.minimum : 1);
116 if (!match) { break; }
128 /** Array of every Category found in the map features file. */
129 [Bindable(event="featuresLoaded")]
130 public function get categories():Array {
136 /** Categories that contain at least one Feature corresponding to a certain type, such as "area" or "point".
138 * @return Filtered Array of Category objects, possibly empty. null if XML file is not yet processed.
140 [Bindable(event="featuresLoaded")]
141 public function getCategoriesForType(type:String):Array {
144 if ( type == null || type == "" )
145 return []; //_categories;
147 var filteredCategories:Array = new Array();
148 for each( var cat:Category in _categories ) {
149 if ( cat.getFeaturesForType(type).length > 0 )
150 filteredCategories.push(cat);
152 return filteredCategories;
157 * @return null if XML file not yet processed. */
158 [Bindable(event="featuresLoaded")]
159 public function get features():Array {
165 /** All Features of type "point".
167 * @return null if XML file not yet processed.
169 [Bindable(event="featuresLoaded")]
170 public function get pois():Array {
175 for each ( var feature:Feature in _features ) {
176 if (feature.isType("point")) {
183 /** A list of all Keys for all features of the given type, sorted.
184 * @example <listing version="3.0">getAutoCompleteKeys ("way")</listing>
185 * Returns: [{name: "building"}, {name: "highway"}...]
187 [Bindable(event="featuresLoaded")]
188 public function getAutoCompleteKeys(type:String):Array {
192 for (var k:String in _tags[type]) { list.push(k); }
195 for each (k in list) { a.push( { name: k } ); }
199 /** Get all the possible values that could go with a given key and type.
200 * TODO: Include values previously entered by the user, but not existent in XML file.
202 * @example <listing version="3.0">getAutoCompleteValues("way", "highway")</listing>
203 * Returns: [{name: "motorway"}, {name: "residential"}...]
205 [Bindable(event="featuresLoaded")]
206 public function getAutoCompleteValues(type:String,key:String):Array {
208 if (_tags[type][key]) {
209 _tags[type][key].sort();
210 for each (var v:String in _tags[type][key]) { a.push( { name: v } ); }