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