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