Two more files documented.
[potlatch2.git] / net / systemeD / halcyon / connection / Entity.as
1 package net.systemeD.halcyon.connection {
2
3     import flash.events.EventDispatcher;
4     import flash.utils.Dictionary;
5
6     import net.systemeD.halcyon.connection.actions.*;
7
8     /** An Entity is an object stored in the map database, and therefore uploaded and downloaded. This includes Nodes, Ways, Relations but also Changesets etc. */
9     public class Entity extends EventDispatcher {
10         private var _id:Number;
11         private var _version:uint;
12         private var _uid:Number;
13         private var _timestamp:String;
14         private var tags:Object = {};
15         private var modified:Boolean = false;
16         private var _loaded:Boolean = true;
17         private var parents:Dictionary = new Dictionary();
18         /** Lock against purging when off-screen */
19         public var locked:Boolean = false;
20         public var deleted:Boolean = false;
21         /** Have all its parents (ie, relations that contain this object as a member, ways that contain this node) been loaded into memory */
22         public var parentsLoaded:Boolean = true;
23
24         public function Entity(id:Number, version:uint, tags:Object, loaded:Boolean, uid:Number, timestamp:String) {
25             this._id = id;
26             this._version = version;
27             this._uid = uid;
28             this._timestamp = timestamp;
29             this.tags = tags;
30                         this._loaded = loaded;
31             modified = id < 0;
32         }
33
34         /** ID for the entity. */
35         public function get id():Number {
36             return _id;
37         }
38
39         /** Current version number. */
40         public function get version():uint {
41             return _version;
42         }
43
44         /** User identifier associated with the entity. (?) */
45         public function get uid():Number {
46             return _uid;
47         }
48
49         public function get loaded():Boolean {
50             return _loaded;
51         }
52
53         /** Most recent modification of the entity. */
54         public function get timestamp():String {
55             return _timestamp;
56         }
57
58         /** Set a bunch of properties in one hit. Implicitly makes entity not deleted. */
59         public function updateEntityProperties(version:uint, tags:Object, loaded:Boolean, parentsLoaded:Boolean, uid:Number, timestamp:String):void {
60             _version=version; this.tags=tags; _loaded=loaded; this.parentsLoaded=parentsLoaded; _uid = uid; _timestamp = timestamp;
61             deleted=false;
62         }
63
64         /** Assign a new ID and version. */
65         public function renumber(newID:Number, newVersion:uint):void {
66             this._id = newID;
67             this._version = newVersion;
68         }
69
70                 // Tag-handling methods
71
72         /** Whether the entity has > 0 tags. */
73         public function hasTags():Boolean {
74             for (var key:String in tags)
75                 return true;
76             return false;
77         }
78
79         /** Whether the entity has any tags other than meta-tags (attribution, created_by, source, tiger:...) */
80         public function hasInterestingTags():Boolean {
81             for (var key:String in tags) {
82               if (key != "attribution" && key != "created_by" && key != "source" && key.indexOf('tiger:') != 0) {
83                 //trace(key);
84                 return true;
85               }
86             }
87             return false;
88         }
89
90         /** Rough function to detect entities untouched since TIGER import. */
91         public function isUneditedTiger():Boolean {
92             // todo: make this match the rules from the tiger edited map
93             // http://github.com/MapQuest/TIGER-Edited-map/blob/master/inc/layer-tiger.xml.inc
94             if (this is Way && (uid == 7168 || uid == 15169 || uid == 20587)) {//todo fixme etc
95               return true;
96             }
97             return false;
98         }
99
100         /** Retrieve a tag by key. */
101         public function getTag(key:String):String {
102             return tags[key];
103         }
104
105         /** @return true iff there exists key=value */
106         public function tagIs(key:String,value:String):Boolean {
107             if (!tags[key]) { return false; }
108             return tags[key]==value;
109         }
110
111         /** Set key=value, with optional undoability.
112          * @param key Name of key to set
113          * @parame value Value to set tag to
114          * @param performAction Single-argument function to pass a SetTagAction to.
115          * @example setTag("highway", "residential", MainUndoStack.getGlobalStack().addAction);
116          */
117         public function setTag(key:String, value:String, performAction:Function):void {
118             performAction(new SetTagAction(this, key, value));
119         }
120
121         /** Change oldKey=[value] to newKey=[value], with optional undoability.
122          * @param oldKey Name of key to rename
123          * @parame newKey New name of key
124          * @param performAction Single-argument function to pass a SetTagKeyAction to.
125          * @example renameTag("building", "amenity", MainUndoStack.getGlobalStack().addAction);
126          */
127         public function renameTag(oldKey:String, newKey:String, performAction:Function):void {
128             performAction(new SetTagKeyAction(this, oldKey, newKey));
129         }
130
131         public function getTagList():TagList {
132             return new TagList(tags);
133         }
134
135         /** Returns an object that duplicates the tags on this entity. */
136         public function getTagsCopy():Object {
137             var copy:Object = {};
138             for (var key:String in tags )
139                 copy[key] = tags[key];
140             return copy;
141         }
142
143         public function getTagsHash():Object {
144             // hm, not sure we should be doing this, but for read-only purposes
145             // it's faster than using getTagsCopy
146             return tags;
147         }
148
149         /** Returns an array that duplicates the tags on this entity. */
150         public function getTagArray():Array {
151             var copy:Array = [];
152             for (var key:String in tags )
153                 copy.push(new Tag(this, key, tags[key]));
154             return copy;
155         }
156
157                 // Clean/dirty methods
158
159         /** Check if entity is modified since last markClean(). */
160         public function get isDirty():Boolean {
161             return modified;
162         }
163
164         /** Reset modified flag. */
165         public function markClean():void {
166             modified = false;
167         }
168
169         /** Set entity as modified. */
170         internal function markDirty():void {
171             modified = true;
172         }
173
174
175         /** Delete entity - must be overridden. */
176         public function remove(performAction:Function):void {
177             // to be overridden
178         }
179
180         /** Whether entity is marked deleted. */
181         public function isDeleted():Boolean {
182             return deleted;
183         }
184
185         /** Mark entity as deleted. */
186         public function setDeletedState(isDeleted:Boolean):void {
187             deleted = isDeleted;
188             if (this is Node) {
189                 var n:Node = Node(this);
190                 if (isDeleted) {
191                     Connection.getConnection().removeDupe(n);
192                 } else {
193                     Connection.getConnection().addDupe(n);
194                 }
195             }
196         }
197
198         /** Whether entity is "empty" - to be overridden by subclass. */
199         internal function isEmpty():Boolean {
200             return false;
201         }
202
203         /** Free up memory by converting entity to a dummy entity, for entities that we no longer need
204         *  but which are part of a still-in-memory relation */
205         public function nullify():void {
206             nullifyEntity();
207         }
208
209         /** Implement nullifybehaviour: delete tags, etc. */
210         protected function nullifyEntity():void {
211             // this is the common nullify behaviour for all entity types (we'd call this by super() if ActionScript let us)
212             _version=0;
213             _loaded=false;
214             tags={};
215         }
216
217
218         public function within(left:Number,right:Number,top:Number,bottom:Number):Boolean {
219             return true;        // to be overridden
220         }
221
222         public function removeFromParents(performAction:Function):void {
223             for (var o:Object in parents) {
224                 if (o is Relation) { Relation(o).removeMember(this, performAction); }
225                 else if (o is Way) { Way(o).removeNode(Node(this), performAction); }
226                 if (o.isEmpty()) { o.remove(performAction); }
227             }
228         }
229
230         // Parent handling
231
232         /** Create parent link from this entity to another. */
233         public function addParent(parent:Entity):void {
234             parents[parent]=true;
235
236             if ( parent is Relation )
237                 dispatchEvent(new RelationMemberEvent(Connection.ADDED_TO_RELATION, this, parent as Relation, -1));
238         }
239
240         /** Remove parent link. */
241         public function removeParent(parent:Entity):void {
242             delete parents[parent];
243
244             if ( parent is Relation )
245                 dispatchEvent(new RelationMemberEvent(Connection.REMOVED_FROM_RELATION, this, parent as Relation, -1));
246         }
247
248         /** Get array of all Ways of which this object (presumably a node) is a child. */
249         public function get parentWays():Array {
250             var a:Array=[];
251             for (var o:Object in parents) {
252                 if (o is Way) { a.push(o); }
253             }
254             return a;
255         }
256
257         /** Whether this entity has any parents. */
258         public function get hasParents():Boolean {
259             for (var o:Object in parents) { return true; }
260             return false;
261         }
262
263         /** Whether this entity has any parents that are Ways. */
264         public function get hasParentWays():Boolean {
265             for (var o:Object in parents) {
266                 if (o is Way) { return true; }
267             }
268             return false;
269         }
270
271         /** How many parents are Ways? */
272         public function get numParentWays():uint {
273             var i:uint=0;
274             for (var o:Object in parents) {
275                 if (o is Way) { i++; }
276             }
277             return i;
278         }
279
280         /** All parents that are Relations */
281         public function get parentRelations():Array {
282             var a:Array=[];
283             for (var o:Object in parents) {
284                 if (o is Relation) { a.push(o); }
285             }
286             return a;
287         }
288
289         /** Returns parents that are relations, and of the specified type, and of which this entity is the correct role (if provided).
290         *
291         * @example entity.findParentRelationsOfType('multipolygon','inner');
292         */
293         public function findParentRelationsOfType(type:String, role:String=null):Array {
294             var a:Array=[];
295             for (var o:Object in parents) {
296                 if (o is Relation && Relation(o).tagIs('type',type) && (role==null || Relation(o).hasMemberInRole(this,role))) {
297                     a.push(o);
298                 }
299             }
300             return a;
301         }
302
303         /** How many parents does this entity have that satisfy the "within" constraint? */
304         public function countParentObjects(within:Object):uint {
305             var count:uint=0;
306             for (var o:Object in parents) {
307                 if (o.getType()==within.entity && o.getTag(within.k)) {
308                     if (within.v && within.v!=o.getTag(within.k)) { break; }
309                     if (within.role && !Relation(o).hasMemberInRole(this,within.role)) { break; }
310                     count++;
311                 }
312             }
313             return count;
314         }
315
316         /** All parents of this entity. */
317         public function get parentObjects():Array {
318             var a:Array=[];
319             for (var o:Object in parents) { a.push(o); }
320                 return a;
321             }
322
323         /** Whether 'entity' is a parent of this Entity. */
324         public function hasParent(entity:Entity):Boolean {
325             return parents[entity] == true;
326         }
327
328             /** Returns all relations that this Entity is part of, as array of {relation, position, role}, sorted by position. */
329             public function get memberships():Array {
330                         var list:Array=[];
331                         for (var o:Object in parents) {
332                                 if (o is Relation) {
333                                         for (var i:uint=0; i<o.length; i++) {
334                                                 if (o.getMember(i).entity==this) {
335                                                         list.push( { relation:o, position:i, role: o.getMember(i).role } );
336                                                 }
337                                         }
338                                 }
339                         }
340             // it's useful to return in a sorted order, even if the relations are interleaved
341             // e.g. [{r0 p1},{r1 p1},{r0 p4}]
342                         return list.sortOn("position");
343                 }
344
345
346
347                 /** Temporarily prevent redrawing of the object. */
348                 public function suspend():void {
349                         dispatchEvent(new EntityEvent(Connection.SUSPEND_REDRAW, this));
350                 }
351                 /** Resume redrawing of the object */
352                 public function resume():void {
353                         dispatchEvent(new EntityEvent(Connection.RESUME_REDRAW, this));
354                 }
355
356
357                 /** Basic description of Entity - should be overriden by subclass. */
358                 public function getDescription():String {
359                         var basic:String=this.getType()+" "+_id;
360                         if (tags['ref'] && tags['name']) { return tags['ref']+' '+tags['name']+' ('+basic+')'; }
361                         if (tags['ref']) { return tags['ref']+' ('+basic+')'; }
362                         if (tags['name']) { return tags['name']+' ('+basic+')'; }
363                         return basic;
364                 }
365
366         /** The type of Entity (node, way etc). By default, returns ''. */
367         public function getType():String {
368             return '';
369         }
370
371     }
372
373 }
374