Clean confusing indentation
[potlatch2.git] / net / systemeD / potlatch2 / mapfeatures / MapFeatures.as
1 package net.systemeD.potlatch2.mapfeatures {
2
3     import flash.events.EventDispatcher;
4     import flash.events.Event;
5     import flash.net.URLLoader;
6
7         import flash.system.Security;
8         import flash.net.*;
9
10         import mx.core.UIComponent;
11         import mx.controls.DataGrid;
12
13     import net.systemeD.halcyon.connection.*;
14         import net.systemeD.halcyon.NestedXMLLoader;
15
16     /** All the information about all available map features that can be selected by the user or matched against entities in the map.
17     * The list of map features is populated from an XML file the first time the MapFeatures instance is accessed.
18     *
19     * <p>There are four "types" of features: point, line, area, relation. However, the autocomplete functions refer to these as node,
20     * way (line/area) and relation.</p>
21     */
22         public class MapFeatures extends EventDispatcher {
23         private static var instance:MapFeatures;
24
25         /** Instantiates MapFeatures by loading it if required. */
26         public static function getInstance():MapFeatures {
27             if ( instance == null ) {
28                 instance = new MapFeatures();
29                 instance.loadFeatures();
30             }
31             return instance;
32         }
33
34         private var xml:XML = null;
35         private var _features:Array = null;
36         private var _categories:Array = null;
37 //              private var _keys:Array = null;
38                 private var _tags:Object = null;
39
40         /** Loads list of map features from XML file which it first retrieves. */
41         protected function loadFeatures():void {
42             var xmlLoader:NestedXMLLoader = new NestedXMLLoader();
43             xmlLoader.addEventListener(Event.COMPLETE, onFeatureLoad);
44             xmlLoader.load("map_features.xml");
45         }
46
47         /** The loaded source XML file itself. */
48         internal function get definition():XML {
49             return xml;
50         }
51
52         /** Load XML file, then trawl over it, setting up convenient indexes into the list of map features. */
53         private function onFeatureLoad(event:Event):void {
54                         var f:Feature;
55
56             xml = NestedXMLLoader(event.target).xml;
57             _features = [];
58             _tags = { relation:{}, way:{}, node:{} };
59
60             for each(var feature:XML in xml..feature) {
61                 f=new Feature(this,feature);
62                 _features.push(f);
63                 for each (var tag:Object in f.tags) {
64                     if (f.isType('line') || f.isType('area')) { addToTagList('way',tag); }
65                     if (f.isType('relation'))                 { addToTagList('relation',tag); }
66                     if (f.isType('point'))                    { addToTagList('node',tag); }
67                 }
68             }
69
70             _categories = new Array();
71             for each(var catXML:XML in xml.category) {
72                 if ( catXML.child("category").length() == 0 )
73                   _categories.push(new Category(this, catXML.@name, catXML.@id, _categories.length));
74             }
75             dispatchEvent(new Event("featuresLoaded"));
76         }
77
78         /** Add one item to tagList index, which will end up being a list like: ["way"]["highway"]["residential"] */
79                 private function addToTagList(type:String,tag:Object):void {
80                         if (tag.v=='*') { return; }
81                         if (!_tags[type][tag.k]) { _tags[type][tag.k]=new Array(); }
82                         if (_tags[type][tag.k].indexOf(tag.v)==-1) { _tags[type][tag.k].push(tag.v); }
83                 }
84
85         /** Indicates whether the XML file has finished being loaded. */
86         public function hasLoaded():Boolean {
87             return xml != null;
88         }
89
90         /** Find the first Feature (template) that matches the given Entity (actual existing object in the map).
91          *
92          * This is done to provide appropriate editing controls that correspond to the selected Entity.
93          *
94          * @param entity The Entity to try and match against.
95          * @return The first suitable Feature, or null. */
96
97         public function findMatchingFeature(entity:Entity):Feature {
98             if ( xml == null )
99                 return null;
100
101             for each(var feature:Feature in features) {
102                 var match:Boolean = true;
103
104                 // check for matching tags
105                 for each(var tag:Object in feature.tags) {
106                     var entityTag:String = entity.getTag(tag.k);
107                     match = entityTag == tag.v || (entityTag != null && tag.v == "*");
108                     if ( !match ) break;
109                 }
110
111                 // check for matching withins
112                 if (match) {
113                     for each (var within:Object in feature.withins) {
114                         match = entity.countParentObjects(within) >= (within.minimum ? within.minimum : 1);
115                         if (!match) { break; }
116                     }
117                 }
118
119                 if (match) {
120                     return feature;
121                 }
122             }
123             return null;
124         }
125
126
127         /** Array of every Category found in the map features file. */
128         [Bindable(event="featuresLoaded")]
129         public function get categories():Array {
130             if ( xml == null )
131                 return null;
132             return _categories;
133         }
134
135         /** Categories that contain at least one Feature corresponding to a certain type, such as "area" or "point".
136         *
137         * @return Filtered Array of Category objects, possibly empty. null if XML file is not yet processed.
138         */
139         [Bindable(event="featuresLoaded")]
140         public function getCategoriesForType(type:String):Array {
141             if ( xml == null )
142                 return null;
143             if ( type == null || type == "" )
144                 return []; //_categories;
145
146             var filteredCategories:Array = new Array();
147             for each( var cat:Category in _categories ) {
148                 if ( cat.getFeaturesForType(type).length > 0 )
149                     filteredCategories.push(cat);
150             }
151             return filteredCategories;
152         }
153
154         /** All features.
155         *
156         * @return null if XML file not yet processed. */
157         [Bindable(event="featuresLoaded")]
158         public function get features():Array {
159             if ( xml == null )
160                 return null;
161             return _features;
162         }
163
164         /** All Features of type "point".
165         *
166         * @return null if XML file not yet processed.
167         */
168         [Bindable(event="featuresLoaded")]
169         public function get pois():Array {
170             if (xml == null )
171                 return null;
172             var pois:Array = [];
173             var counter:int = 0;
174             for each ( var feature:Feature in _features ) {
175                   if (feature.isType("point")) {
176                   pois.push(feature);
177                   }
178             }
179             return pois;
180         }
181
182         /** A list of all Keys for all features of the given type, sorted.
183          * @example <listing version="3.0">getAutoCompleteKeys ("way")</listing>
184          * Returns: [{name: "building"}, {name: "highway"}...]
185          */
186         [Bindable(event="featuresLoaded")]
187         public function getAutoCompleteKeys(type:String):Array {
188             var list:Array=[];
189             var a:Array=[];
190
191             for (var k:String in _tags[type]) { list.push(k); }
192             list.sort();
193
194             for each (k in list) { a.push( { name: k } ); }
195             return a;
196         }
197
198         /** Get all the possible values that could go with a given key and type.
199         * TODO: Include values previously entered by the user, but not existent in XML file.
200         *
201         * @example <listing version="3.0">getAutoCompleteValues("way", "highway")</listing>
202         * Returns: [{name: "motorway"}, {name: "residential"}...]
203         */
204         [Bindable(event="featuresLoaded")]
205         public function getAutoCompleteValues(type:String,key:String):Array {
206             var a:Array=[];
207             if (_tags[type][key]) {
208                 _tags[type][key].sort();
209                 for each (var v:String in _tags[type][key]) { a.push( { name: v } ); }
210             }
211             return a;
212         }
213
214     }
215
216 }
217
218