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