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