1 package net.systemeD.halcyon.connection {
3 import flash.events.Event;
4 import flash.events.EventDispatcher;
5 import flash.utils.Dictionary;
7 import net.systemeD.halcyon.connection.actions.*;
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 tags:Object = {};
18 private var modified:Boolean = false;
19 private var _loaded:Boolean = true;
20 private var parents:Dictionary = new Dictionary();
21 public var status:String;
22 /** Lock against purging when off-screen */
23 public var locked:Boolean = false;
24 public var deleted:Boolean = false;
25 /** Have all its parents (ie, relations that contain this object as a member, ways that contain this node) been loaded into memory */
26 public var parentsLoaded:Boolean = true;
28 public function Entity(connection:Connection, id:Number, version:uint, tags:Object, loaded:Boolean, uid:Number, timestamp:String) {
29 this._connection = connection;
31 this._version = version;
33 this._timestamp = timestamp;
35 this._loaded = loaded;
40 public function get id():Number {
44 /** Current version number. */
45 public function get version():uint {
49 /** User ID who last edited this entity (from OSM API). */
50 public function get uid():Number {
54 /** Is entity fully loaded, or is it just a placeholder reference (as a relation member)? */
55 public function get loaded():Boolean {
59 /** List of entities. Overridden by EntityCollection. */
60 public function get entities():Array {
64 /** Most recent modification of the entity (from OSM API). */
65 public function get timestamp():String {
69 /** Connection to which this entity belongs. */
70 public function get connection():Connection {
74 /** Set a bunch of properties in one hit. Implicitly makes entity not deleted. */
75 public function updateEntityProperties(version:uint, tags:Object, loaded:Boolean, parentsLoaded:Boolean, uid:Number, timestamp:String):void {
76 _version=version; this.tags=tags; _loaded=loaded; this.parentsLoaded=parentsLoaded; _uid = uid; _timestamp = timestamp;
80 /** Assign a new ID and version. */
81 public function renumber(newID:Number, newVersion:uint):void {
83 this._version = newVersion;
86 // Tag-handling methods
88 /** Whether the entity has > 0 tags. */
89 public function hasTags():Boolean {
90 for (var key:String in tags)
95 /** Whether the entity has any tags other than meta-tags (attribution, created_by, source, tiger:...) */
96 public function hasInterestingTags():Boolean {
97 for (var key:String in tags) {
98 if (key != "attribution" && key != "created_by" && key != "source" && key.indexOf('tiger:') != 0) {
106 /** Compare tags between two entities. */
107 public function sameTags(entity:Entity):Boolean {
108 var o:Object=entity.getTagsHash();
109 for (var k:String in tags)
110 if (!o[k] || o[k]!=tags[k]) return false;
112 if (!tags[k] || tags[k]!=o[k]) return false;
116 /** Rough function to detect entities untouched since TIGER import. */
117 public function isUneditedTiger():Boolean {
118 // todo: make this match the rules from the tiger edited map
119 // http://github.com/MapQuest/TIGER-Edited-map/blob/master/inc/layer-tiger.xml.inc
120 if (this is Way && (uid == 7168 || uid == 15169 || uid == 20587)) {//todo fixme etc
126 /** Retrieve a tag by key. */
127 public function getTag(key:String):String {
131 /** @return true if there exists key=value */
132 public function tagIs(key:String,value:String):Boolean {
133 if (!tags[key]) { return false; }
134 return tags[key]==value;
137 /** Set key=value, with optional undoability.
138 * @param key Name of key to set
139 * @param value Value to set tag to
140 * @param performAction Single-argument function to pass a SetTagAction to.
141 * @example setTag("highway", "residential", MainUndoStack.getGlobalStack().addAction);
143 public function setTag(key:String, value:String, performAction:Function):void {
144 performAction(new SetTagAction(this, key, value));
147 /** Change oldKey=[value] to newKey=[value], with optional undoability.
148 * @param oldKey Name of key to rename
149 * @parame newKey New name of key
150 * @param performAction Single-argument function to pass a SetTagKeyAction to.
151 * @example renameTag("building", "amenity", MainUndoStack.getGlobalStack().addAction);
153 public function renameTag(oldKey:String, newKey:String, performAction:Function):void {
154 performAction(new SetTagKeyAction(this, oldKey, newKey));
157 public function getTagList():TagList {
158 return new TagList(tags);
161 /** Returns an object that duplicates the tags on this entity. */
162 public function getTagsCopy():Object {
163 var copy:Object = {};
164 for (var key:String in tags )
165 copy[key] = tags[key];
169 public function getTagsHash():Object {
170 // hm, not sure we should be doing this, but for read-only purposes
171 // it's faster than using getTagsCopy
175 /** Returns an array that duplicates the tags on this entity. */
176 public function getTagArray():Array {
178 for (var key:String in tags )
179 copy.push(new Tag(this, key, tags[key]));
183 /** Change entity status. */
184 public function setStatus(s:String):void {
186 if (s==status) return;
188 dispatchEvent(new EntityEvent(Connection.STATUS_CHANGED,this));
191 // Clean/dirty methods
193 /** Check if entity is modified since last markClean(). */
194 public function get isDirty():Boolean {
198 /** Reset modified flag. You should not be calling this directly, instead you should be calling markClean from your UndoableEntityAction */
199 public function markClean():void {
203 /** Set entity as modified. You should not be calling this directly, instead you should be calling markDirty from your UndoableEntityAction */
204 internal function markDirty():void {
209 /** Delete entity - must be overridden. */
210 public function remove(performAction:Function):void {
214 /** Whether entity is marked deleted. */
215 public function isDeleted():Boolean {
219 /** Mark entity as deleted. */
220 public function setDeletedState(isDeleted:Boolean):void {
223 var n:Node = Node(this);
225 connection.removeDupe(n);
227 connection.addDupe(n);
232 /** Whether entity is "empty" - to be overridden by subclass. */
233 internal function isEmpty():Boolean {
237 /** Free up memory by converting entity to a dummy entity, for entities that we no longer need
238 * but which are part of a still-in-memory relation */
239 public function nullify():void {
243 /** Implement nullifybehaviour: delete tags, etc. */
244 protected function nullifyEntity():void {
245 // this is the common nullify behaviour for all entity types (we'd call this by super() if ActionScript let us)
252 public function within(left:Number,right:Number,top:Number,bottom:Number):Boolean {
253 return true; // to be overridden
256 public function removeFromParents(performAction:Function):void {
257 for (var o:Object in parents) {
258 if (o is Relation) { Relation(o).removeMember(this, performAction); }
259 else if (o is Way) { Way(o).removeNode(Node(this), performAction); }
260 if (o.isEmpty()) { o.remove(performAction); }
266 /** Create parent link from this entity to another. */
267 public function addParent(parent:Entity):void {
268 parents[parent]=true;
270 if ( parent is Relation )
271 dispatchEvent(new RelationMemberEvent(Connection.ADDED_TO_RELATION, this, parent as Relation, -1));
274 /** Remove parent link. */
275 public function removeParent(parent:Entity):void {
276 delete parents[parent];
278 if ( parent is Relation )
279 dispatchEvent(new RelationMemberEvent(Connection.REMOVED_FROM_RELATION, this, parent as Relation, -1));
282 /** Get array of all Ways of which this object (presumably a node) is a child. */
283 public function get parentWays():Array {
285 for (var o:Object in parents) {
286 if (o is Way) { a.push(o); }
291 /** Whether this entity has any parents. */
292 public function get hasParents():Boolean {
293 for (var o:Object in parents) { return true; }
297 /** Whether this entity has any parents that are Ways. */
298 public function get hasParentWays():Boolean {
299 for (var o:Object in parents) {
300 if (o is Way) { return true; }
305 /** How many parents are Ways? */
306 public function get numParentWays():uint {
308 for (var o:Object in parents) {
309 if (o is Way) { i++; }
314 /** All parents that are Relations */
315 public function get parentRelations():Array {
317 for (var o:Object in parents) {
318 if (o is Relation) { a.push(o); }
323 /** Returns parents that are relations, and of the specified type, and of which this entity is the correct role (if provided).
325 * @example entity.findParentRelationsOfType('multipolygon','inner');
327 public function findParentRelationsOfType(type:String, role:String=null):Array {
329 for (var o:Object in parents) {
330 if (o is Relation && Relation(o).tagIs('type',type) && (role==null || Relation(o).hasMemberInRole(this,role))) {
337 public function getRelationMemberships():Array {
338 var memberships:Array = [];
339 for each( var rel:Relation in parentRelations ) {
340 for each( var memberIndex:int in rel.findEntityMemberIndexes(this)) {
345 role: rel.getMember(memberIndex).role,
346 description: rel.getDescription(),
347 id_idx: rel.id + "/"+memberIndex });
353 /** How many parents does this entity have that satisfy the "within" constraint? */
354 public function countParentObjects(within:Object):uint {
356 for (var o:Object in parents) {
357 if (o.getType()==within.entity && o.getTag(within.k)) {
358 if (within.v && within.v!=o.getTag(within.k)) { break; }
359 if (within.role && !Relation(o).hasMemberInRole(this,within.role)) { break; }
366 /** All parents of this entity. */
367 public function get parentObjects():Array {
369 for (var o:Object in parents) { a.push(o); }
373 /** Whether 'entity' is a parent of this Entity. */
374 public function hasParent(entity:Entity):Boolean {
375 return parents[entity] == true;
378 /** Returns all relations that this Entity is part of, as array of {relation, position, role}, sorted by position. */
379 public function get memberships():Array {
381 for (var o:Object in parents) {
383 for (var i:uint=0; i<o.length; i++) {
384 if (o.getMember(i).entity==this) {
385 list.push( { relation:o, position:i, role: o.getMember(i).role } );
390 // it's useful to return in a sorted order, even if the relations are interleaved
391 // e.g. [{r0 p1},{r1 p1},{r0 p4}]
392 return list.sortOn("position");
397 /** Temporarily prevent redrawing of the object. */
398 public function suspend():void {
399 dispatchEvent(new EntityEvent(Connection.SUSPEND_REDRAW, this));
401 /** Resume redrawing of the object */
402 public function resume():void {
403 dispatchEvent(new EntityEvent(Connection.RESUME_REDRAW, this));
407 /** Basic description of Entity - should be overriden by subclass. */
408 public function getDescription():String {
409 var basic:String=this.getType()+" "+_id;
410 if (tags['ref'] && tags['name']) { return tags['ref']+' '+tags['name']+' ('+basic+')'; }
411 if (tags['ref']) { return tags['ref']+' ('+basic+')'; }
412 if (tags['name']) { return tags['name']+' ('+basic+')'; }
416 /** The type of Entity (node, way etc). By default, returns ''. */
417 public function getType():String {
421 /** Compare type against supplied string */
422 public function isType(str:String):Boolean {
423 return getType()==str;
426 /** Copy tags from another entity into this one, creating "key=value1; value2" pairs if necessary.
427 * * @return Array of keys that require manual merging, in order to warn the user. */
428 public function mergeTags(source: Entity, performAction:Function):Boolean {
429 var sourcetags:Object = source.getTagsHash();
430 var conflict:Boolean = false;
431 for (var k:String in sourcetags) {
432 var v1:String = tags[k];
433 var v2:String = sourcetags[k];
434 if ( v1 && v1 != v2) {
435 setTag(k, v1+"; "+v2, performAction);
438 setTag(k, v2, performAction);