Move bindable metadata to put it immediately adjacent to the function definition...
[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.DebugURLRequest;
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 request:DebugURLRequest = new DebugURLRequest("map_features.xml");
43             var loader:URLLoader = new URLLoader();
44             loader.addEventListener(Event.COMPLETE, onFeatureLoad);
45             loader.load(request.request);
46         }
47
48         /** The loaded source XML file itself. */
49         internal function get definition():XML {
50             return xml;
51         }
52
53         /** Load XML file, then trawl over it, setting up convenient indexes into the list of map features. */
54         private function onFeatureLoad(event:Event):void {
55                         var f:Feature;
56
57             xml = new XML(URLLoader(event.target).data);
58             _features = [];
59             _tags = { relation:{}, way:{}, node:{} };
60
61             for each(var feature:XML in xml.feature) {
62                 f=new Feature(this,feature);
63                 _features.push(f);
64                 for each (var tag:Object in f.tags) {
65                     if (f.isType('line') || f.isType('area')) { addToTagList('way',tag); }
66                     if (f.isType('relation'))                 { addToTagList('relation',tag); }
67                     if (f.isType('point'))                    { addToTagList('node',tag); }
68                 }
69             }
70
71             _categories = new Array();
72             for each(var catXML:XML in xml.category) {
73                 if ( catXML.child("category").length() == 0 )
74                   _categories.push(new Category(this, catXML.@name, catXML.@id, _categories.length));
75             }
76             dispatchEvent(new Event("featuresLoaded"));
77         }
78
79         /** Add one item to tagList index, which will end up being a list like: ["way"]["highway"]["residential"] */
80                 private function addToTagList(type:String,tag:Object):void {
81                         if (tag.v=='*') { return; }
82                         if (!_tags[type][tag.k]) { _tags[type][tag.k]=new Array(); }
83                         if (_tags[type][tag.k].indexOf(tag.v)==-1) { _tags[type][tag.k].push(tag.v); }
84                 }
85
86         /** Indicates whether the XML file has finished being loaded. */
87         public function hasLoaded():Boolean {
88             return xml != null;
89         }
90
91         /** Find the first Feature (template) that matches the given Entity (actual existing object in the map).
92          *
93          * This is done to provide appropriate editing controls that correspond to the selected Entity.
94          *
95          * @param entity The Entity to try and match against.
96          * @return The first suitable Feature, or null. */
97
98         public function findMatchingFeature(entity:Entity):Feature {
99             if ( xml == null )
100                 return null;
101
102             for each(var feature:Feature in features) {
103                 var match:Boolean = true;
104
105                 // check for matching tags
106                 for each(var tag:Object in feature.tags) {
107                     var entityTag:String = entity.getTag(tag.k);
108                     match = entityTag == tag.v || (entityTag != null && tag.v == "*");
109                     if ( !match ) break;
110                 }
111
112                 // check for matching withins
113                 if (match) {
114                     for each (var within:Object in feature.withins) {
115                         match = entity.countParentObjects(within) >= (within.minimum ? within.minimum : 1);
116                         if (!match) { break; }
117                     }
118                 }
119
120                 if (match) {
121                     return feature;
122                 }
123             }
124             return null;
125         }
126
127
128         /** Array of every Category found in the map features file. */
129         [Bindable(event="featuresLoaded")]
130         public function get categories():Array {
131             if ( xml == null )
132                 return null;
133             return _categories;
134         }
135
136         /** Categories that contain at least one Feature corresponding to a certain type, such as "area" or "point".
137         *
138         * @return Filtered Array of Category objects, possibly empty. null if XML file is not yet processed.
139         */
140         [Bindable(event="featuresLoaded")]
141         public function getCategoriesForType(type:String):Array {
142             if ( xml == null )
143                 return null;
144             if ( type == null || type == "" )
145                 return []; //_categories;
146
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);
151             }
152             return filteredCategories;
153         }
154
155         /** All features.
156         *
157         * @return null if XML file not yet processed. */
158         [Bindable(event="featuresLoaded")]
159         public function get features():Array {
160             if ( xml == null )
161                 return null;
162             return _features;
163         }
164
165         /** All Features of type "point".
166         *
167         * @return null if XML file not yet processed.
168         */
169         [Bindable(event="featuresLoaded")]
170         public function get pois():Array {
171             if (xml == null )
172                 return null;
173             var pois:Array = [];
174             var counter:int = 0;
175             for each ( var feature:Feature in _features ) {
176                   if (feature.isType("point")) {
177                   pois.push(feature);
178                   }
179             }
180             return pois;
181         }
182
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"}...]
186          */
187         [Bindable(event="featuresLoaded")]
188         public function getAutoCompleteKeys(type:String):Array {
189             var list:Array=[];
190             var a:Array=[];
191
192             for (var k:String in _tags[type]) { list.push(k); }
193                 list.sort();
194
195                 for each (k in list) { a.push( { name: k } ); }
196                     return a;
197                 }
198
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.
201         *
202         * @example <listing version="3.0">getAutoCompleteValues("way", "highway")</listing>
203         * Returns: [{name: "motorway"}, {name: "residential"}...]
204         */
205         [Bindable(event="featuresLoaded")]
206         public function getAutoCompleteValues(type:String,key:String):Array {
207             var a: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 } ); }
211             }
212             return a;
213         }
214
215     }
216
217 }
218
219