Move each entity into its own file

This commit is contained in:
Richard Fairhurst
2012-07-12 13:05:48 +01:00
parent 6be53b0f5f
commit eef3f68faf
4 changed files with 323 additions and 221 deletions

View File

@@ -9,6 +9,7 @@ define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
// Entity base class
declare("iD.Entity", null, {
connection: null,
id: NaN,
loaded: false,
@@ -20,49 +21,80 @@ declare("iD.Entity", null, {
MAINKEYS: ['highway','amenity','railway','waterway'],
constructor:function() {
// summary: The base class for an entity (way, node or relation).
this.tags={};
this.parents=new Hashtable();
},
isType:function(_type) {
return this.entityType==_type;
isType:function(type) {
// summary: Is this entity of the specified type ('node','way','relation')?
return this.entityType==type; // Boolean
},
toString:function() {
return this.entityType+"."+this.id;
},
// --------------------------------
// Provoke redraw and other changes
refresh:function() { this.connection.refreshEntity(this); },
refresh:function() {
// summary: Ask the connection to provoke redraw and other changes.
this.connection.refreshEntity(this);
},
// Clean and dirty (only called from UndoableEntityAction)
// ---------------
// Clean and dirty
markClean:function() { this.modified=false; },
markDirty:function() { this.modified=true; },
isDirty:function() { return this.modified; },
_markClean:function() {
// summary: Mark entity as clean. Should only be called from UndoableEntityAction.
this.modified=false;
},
_markDirty:function() {
// summary: Mark entity as dirty. Should only be called from UndoableEntityAction.
this.modified=true;
},
isDirty:function() {
// summary: Is the entity dirty?
return this.modified; // Boolean
},
// --------
// Deletion
setDeletedState:function(isDeleted) { this.deleted=isDeleted; },
setDeletedState:function(isDeleted) {
// summary: Mark entity as deleted or not.
this.deleted=isDeleted;
},
// -------------------------------------
// Bounding box check (to be overridden)
within:function(left,right,top,bottom) { return !this.deleted; },
within:function(left,right,top,bottom) {
// summary: Is the entity within the specified bbox?
return !this.deleted; // Boolean
},
// -------------
// Tag functions
getTagsHash:function() {
return this.tags;
// summary: Tag getter.
// returns: The tags hash (reference to the actual object property, not a copy).
return this.tags; // Object
},
numTags:function() {
// summary: Count how many tags this entity has.
var c=0;
for (var i in this.tags) { c++; }
return c;
return c; // int
},
friendlyName:function() {
// summary: Rough-and-ready function to return a human-friendly name
// for the object. Really just a placeholder for something better.
// returns: A string such as 'river' or 'Fred's House'.
if (this.numTags()==0) { return ''; }
var n=[];
if (this.tags['name']) { n.push(this.tags['name']); }
@@ -72,37 +104,45 @@ declare("iD.Entity", null, {
if (this.tags[this.MAINKEYS[i]]) { n.push(this.tags[this.MAINKEYS[i]]); break; }
}
}
return n.length==0 ? 'unknown' : n.join('; ');
return n.length==0 ? 'unknown' : n.join('; '); // String
},
// ---------------
// Parent-handling
addParent:function(_entity) {
this.parents.put(_entity,true);
addParent:function(entity) {
// summary: Record a parent (a relation or way which contains this entity).
this.parents.put(entity,true);
},
removeParent:function(_entity) {
removeParent:function(entity) {
// summary: Remove a parent (e.g. when node removed from a way).
this.parents.remove(_entity);
},
hasParent:function(_entity) {
return this.parents.containsKey(_entity);
hasParent:function(entity) {
// summary: Does this entity have the specified parent (e.g. is it in a certain relation)?
return this.parents.containsKey(entity); // Boolean
},
parentObjects:function() {
return this.parents.keys();
// summary: List of all parents of this entity.
return this.parents.keys(); // Boolean
},
hasParentWays:function() {
// summary: Does this entity have any parents which are ways?
var p=this.parentObjects();
for (var i in p) {
if (p[i].entityType=='way') { return true; }
}
return false;
return false; // Boolean
},
parentWays:function() {
return this.parentObjectsOfClass('way');
// summary: Return an array of all ways that this entity is a member of.
return this._parentObjectsOfClass('way'); // Array
},
parentRelations:function() {
return this.parentObjectsOfClass('relation');
// summary: Return an array of all relations that this entity is a member of.
return this._parentObjectsOfClass('relation'); // Array
},
parentObjectsOfClass:function(_class) {
_parentObjectsOfClass:function(_class) {
var p=this.parentObjects(), c=[];
for (var i in p) {
if (p[i].entityType==_class) { c.push(p[i]); }
@@ -119,205 +159,6 @@ declare("iD.Entity", null, {
});
// ----------------------------------------------------------------------
// Node class
declare("iD.Node", [iD.Entity], {
lat:NaN,
latp:NaN,
lon:NaN,
entityType:"node",
constructor:function(_conn,_id,_lat,_lon,_tags,_loaded) {
this.connection=_conn;
this.id=Number(_id);
this.lat=Number(_lat);
this.lon=Number(_lon);
this.tags=_tags;
this.loaded=(_loaded==undefined) ? true : _loaded;
this.project();
this.modified=this.id<0;
},
project:function() { this.latp=180/Math.PI * Math.log(Math.tan(Math.PI/4+this.lat*(Math.PI/180)/2)); },
latp2lat:function(a) { return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2); },
within:function(left,right,top,bottom) { return (this.lon>=left) && (this.lon<=right) && (this.lat>=bottom) && (this.lat<=top) && !this.deleted; },
refresh:function() {
var ways=this.parentWays();
var conn=this.connection;
array.forEach(ways,function(way) { conn.refreshEntity(way); });
this.connection.refreshEntity(this);
},
doSetLonLatp:function(lon,latproj,performAction) {
performAction(new iD.actions.MoveNodeAction(this, this.latp2lat(latproj), lon, lang.hitch(this,this._setLatLonImmediate) ));
},
_setLatLonImmediate:function(lat,lon) {
this.lat = lat;
this.lon = lon;
this.project();
var ways = this.parentWays();
for (var i=0; i<ways.length; i++) { ways[i].expandBbox(this); }
},
});
// ----------------------------------------------------------------------
// Way class
declare("iD.Way", [iD.Entity], {
nodes: null,
entityType: "way",
edgel: NaN,
edger: NaN,
edget: NaN,
edgeb: NaN,
constructor:function(_conn,_id,_nodes,_tags,_loaded) {
this.connection=_conn;
this.id=Number(_id);
this.nodes=_nodes;
this.tags=_tags;
this.loaded=(_loaded==undefined) ? true : _loaded;
this.modified=this.id<0;
var w=this; array.forEach(_nodes,function(node) {
node.addParent(w);
});
this.calculateBbox();
},
length:function() {
return this.nodes.length;
},
isClosed:function() {
return this.nodes[this.nodes.length-1]==this.nodes[0];
},
isType:function(_type) {
switch (_type) {
case 'way': return true;
case 'area': return this.isClosed;
case 'line': return !(this.isClosed);
}
return false;
},
getNode:function(index) { return this.nodes[index]; },
getFirstNode:function() { return this.nodes[0]; },
getLastNode:function() { return this.nodes[this.nodes.length-1]; },
// Bounding-box handling
within:function(left,right,top,bottom) {
if (!this.edgel ||
(this.edgel<left && this.edger<left ) ||
(this.edgel>right && this.edger>right ) ||
(this.edgeb<bottom && this.edget<bottom) ||
(this.edgeb>top && this.edgeb>top ) || this.deleted) { return false; }
return true;
},
calculateBbox:function() {
this.edgel=999999; this.edger=-999999;
this.edgeb=999999; this.edget=-999999;
for (var i in this.nodes) { this.expandBbox(this.nodes[i]); }
},
expandBbox:function(node) {
this.edgel=Math.min(this.edgel,node.lon);
this.edger=Math.max(this.edger,node.lon);
this.edgeb=Math.min(this.edgeb,node.lat);
this.edget=Math.max(this.edget,node.lat);
},
// Action callers
doAppendNode:function(node, performAction) {
if (node!=this.getLastNode()) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, -1, true));
return this.nodes.length + 1;
},
doPrependNode:function(node, performAction) {
if (node!=this.getFirstNode()) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, 0, true));
return this.nodes.length + 1;
},
doInsertNode:function(index, node, performAction) {
if (index>0 && this.getNode(index-1)==node) return;
if (index<this.nodes.length-1 && this.getNode(index)==node) return;
performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, index, false));
},
doInsertNodeAtClosestPosition:function(newNode, isSnap, performAction) {
var closestProportion = 1;
var newIndex = 0;
var snapped;
for (var i=0; i<this.nodes.length-1; i++) {
var node1 = this.getNode(i);
var node2 = this.getNode(i+1);
var directDist = this.pythagoras(node1, node2);
var viaNewDist = this.pythagoras(node1, newNode) + this.pythagoras(node2, newNode);
var proportion = Math.abs(viaNewDist/directDist - 1);
if (proportion < closestProportion) {
newIndex = i+1;
closestProportion = proportion;
snapped = this.calculateSnappedPoint(node1, node2, newNode);
}
}
// splice in new node
if (isSnap) { newNode.doSetLonLatp(snapped.x, snapped.y, performAction); }
this.doInsertNode(newIndex, newNode, performAction);
return newIndex;
},
pythagoras:function(node1, node2) { return (Math.sqrt(Math.pow(node1.lon-node2.lon,2)+Math.pow(node1.latp-node2.latp,2))); },
calculateSnappedPoint:function(node1, node2, newNode) {
var w = node2.lon - node1.lon;
var h = node2.latp - node1.latp;
var u = ((newNode.lon-node1.lon) * w + (newNode.latp-node1.latp) * h) / (w*w + h*h);
return { x: node1.lon + u*w, y: node1.latp + u*h };
},
});
// ----------------------------------------------------------------------
// Relation class
declare("iD.Relation", [iD.Entity], {
members:null,
entityType:"relation",
constructor:function(_conn,_id,_members,_tags,_loaded) {
this.connection=_conn;
this.id=Number(_id);
this.members=_members;
this.tags=_tags;
this.modified=this.id<0;
this.loaded=(_loaded==undefined) ? true : _loaded;
var r=this; array.forEach(_members,function(member) {
member.entity.addParent(r);
});
},
});
// ----------------------------------------------------------------------
// RelationMember class
declare("iD.RelationMember", [], {
entity:null,
role:"",
constructor:function(_entity,_role) {
this.entity=_entity;
this.role=_role;
},
});
// ----------------------------------------------------------------------
// End of module
});

64
js/iD/Node.js Normal file
View File

@@ -0,0 +1,64 @@
// iD/Node.js
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/Entity','iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
// ----------------------------------------------------------------------
// Node class
declare("iD.Node", [iD.Entity], {
lat:NaN,
latp:NaN,
lon:NaN,
entityType:"node",
constructor:function(conn,id,lat,lon,tags,loaded) {
// summary: An OSM node.
this.connection=conn;
this.id=Number(id);
this.lat=Number(lat);
this.lon=Number(lon);
this.tags=tags;
this.loaded=(loaded==undefined) ? true : loaded;
this.project();
this.modified=this.id<0;
},
project:function() {
// summary: Update the projected latitude value (this.latp) from the latitude (this.lat).
this.latp=180/Math.PI * Math.log(Math.tan(Math.PI/4+this.lat*(Math.PI/180)/2));
},
latp2lat:function(a) {
// summary: Get a latitude from a projected latitude.
// returns: Latitude.
return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2); // Number
},
within:function(left,right,top,bottom) { return (this.lon>=left) && (this.lon<=right) && (this.lat>=bottom) && (this.lat<=top) && !this.deleted; },
refresh:function() {
var ways=this.parentWays();
var conn=this.connection;
array.forEach(ways,function(way) { conn.refreshEntity(way); });
this.connection.refreshEntity(this);
},
doSetLonLatp:function(lon,latproj,performAction) {
// summary: Change the position of a node, using an undo stack.
performAction(new iD.actions.MoveNodeAction(this, this.latp2lat(latproj), lon, lang.hitch(this,this._setLatLonImmediate) ));
},
_setLatLonImmediate:function(lat,lon) {
this.lat = lat;
this.lon = lon;
this.project();
var ways = this.parentWays();
for (var i=0; i<ways.length; i++) { ways[i].expandBbox(this); }
},
});
// ----------------------------------------------------------------------
// End of module
});

44
js/iD/Relation.js Normal file
View File

@@ -0,0 +1,44 @@
// iD/Relation.js
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/Entity','iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
// ----------------------------------------------------------------------
// Relation class
declare("iD.Relation", [iD.Entity], {
members:null,
entityType:"relation",
constructor:function(conn,id,members,tags,loaded) {
// summary: An OSM relation.
this.connection=conn;
this.id=Number(id);
this.members=members;
this.tags=tags;
this.modified=this.id<0;
this.loaded=(loaded==undefined) ? true : loaded;
var r=this; array.forEach(members,function(member) {
member.entity.addParent(r);
});
},
});
// ----------------------------------------------------------------------
// RelationMember class
declare("iD.RelationMember", [], {
entity:null,
role:"",
constructor:function(entity,role) {
// summary: An object containing both the entity that is in the relation, and its role.
this.entity=entity;
this.role=role;
},
});
// ----------------------------------------------------------------------
// End of module
});

153
js/iD/Way.js Normal file
View File

@@ -0,0 +1,153 @@
// iD/Way.js
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/Entity','iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
// ----------------------------------------------------------------------
// Way class
declare("iD.Way", [iD.Entity], {
nodes: null,
entityType: "way",
edgel: NaN,
edger: NaN,
edget: NaN,
edgeb: NaN,
constructor:function(conn,id,nodes,tags,loaded) {
// summary: An OSM way.
this.connection=conn;
this.id=Number(id);
this.nodes=nodes;
this.tags=tags;
this.loaded=(loaded==undefined) ? true : loaded;
this.modified=this.id<0;
var w=this; array.forEach(nodes,function(node) {
node.addParent(w);
});
this._calculateBbox();
},
length:function() {
// summary: Return the number of nodes in the way.
return this.nodes.length; // int
},
isClosed:function() {
// summary: Is this a closed way (first and last nodes the same)?
return this.nodes[this.nodes.length-1]==this.nodes[0]; // Boolean
},
isType:function(_type) {
// summary: Is this a 'way' (always true), an 'area' (closed) or a 'line' (unclosed)?
switch (_type) {
case 'way': return true;
case 'area': return this.isClosed;
case 'line': return !(this.isClosed);
}
return false; // Boolean
},
getNode:function(index) {
// summary: Return the node at the given position.
return this.nodes[index]; // iD.Node
},
getFirstNode:function() {
// summary: Return the first node in the way.
return this.nodes[0]; // iD.Node
},
getLastNode:function() {
// summary: Return the last node in the way.
return this.nodes[this.nodes.length-1]; // iD.Node
},
// ---------------------
// Bounding-box handling
within:function(left,right,top,bottom) {
if (!this.edgel ||
(this.edgel<left && this.edger<left ) ||
(this.edgel>right && this.edger>right ) ||
(this.edgeb<bottom && this.edget<bottom) ||
(this.edgeb>top && this.edgeb>top ) || this.deleted) { return false; }
return true;
},
_calculateBbox:function() {
this.edgel=999999; this.edger=-999999;
this.edgeb=999999; this.edget=-999999;
for (var i in this.nodes) { this.expandBbox(this.nodes[i]); }
},
expandBbox:function(node) {
// summary: Enlarge the way's bounding box to make sure it includes the co-ordinates of a supplied node.
this.edgel=Math.min(this.edgel,node.lon);
this.edger=Math.max(this.edger,node.lon);
this.edgeb=Math.min(this.edgeb,node.lat);
this.edget=Math.max(this.edget,node.lat);
},
// --------------
// Action callers
doAppendNode:function(node, performAction) {
// summary: Add a node to the end of the way, using an undo stack.
// returns: New length of the way.
if (node!=this.getLastNode()) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, -1, true));
return this.nodes.length + 1; // int
},
doPrependNode:function(node, performAction) {
// summary: Add a node to the start of the way, using an undo stack.
// returns: New length of the way.
if (node!=this.getFirstNode()) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, 0, true));
return this.nodes.length + 1; // int
},
doInsertNode:function(index, node, performAction) {
// summary: Add a node at a given index within the way, using an undo stack.
if (index>0 && this.getNode(index-1)==node) return;
if (index<this.nodes.length-1 && this.getNode(index)==node) return;
performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, index, false));
},
doInsertNodeAtClosestPosition:function(newNode, isSnap, performAction) {
// summary: Add a node into whichever segment of the way is nearest, using an undo stack.
// isSnap: Boolean Should the node position be snapped to be exactly on the segment?
// returns: The index at which the node was inserted.
var closestProportion = 1;
var newIndex = 0;
var snapped;
for (var i=0; i<this.nodes.length-1; i++) {
var node1 = this.getNode(i);
var node2 = this.getNode(i+1);
var directDist = this._pythagoras(node1, node2);
var viaNewDist = this._pythagoras(node1, newNode) + this._pythagoras(node2, newNode);
var proportion = Math.abs(viaNewDist/directDist - 1);
if (proportion < closestProportion) {
newIndex = i+1;
closestProportion = proportion;
snapped = this._calculateSnappedPoint(node1, node2, newNode);
}
}
// splice in new node
if (isSnap) { newNode.doSetLonLatp(snapped.x, snapped.y, performAction); }
this.doInsertNode(newIndex, newNode, performAction);
return newIndex; // int
},
_pythagoras:function(node1, node2) { return (Math.sqrt(Math.pow(node1.lon-node2.lon,2)+Math.pow(node1.latp-node2.latp,2))); },
_calculateSnappedPoint:function(node1, node2, newNode) {
var w = node2.lon - node1.lon;
var h = node2.latp - node1.latp;
var u = ((newNode.lon-node1.lon) * w + (newNode.latp-node1.latp) * h) / (w*w + h*h);
return { x: node1.lon + u*w, y: node1.latp + u*h };
},
});
// ----------------------------------------------------------------------
// End of module
});