Merge pull request #8 from tmcw/pure-models

Pure models
This commit is contained in:
Richard Fairhurst
2012-10-17 09:04:08 -07:00
39 changed files with 4083 additions and 778 deletions
+30
View File
@@ -1,3 +1,33 @@
/* Additional CSS rules will go here */
.currentMode { font-weight: bold; }
#zoombuttons {
position:absolute;
right:20px;
top:20px;
}
#zoombuttons button {
width:30px;
height:30px;
text-align:center;
margin:0;
background:#fff;
color:#555;
font:bold 20px/20px 'Helvetica';
border:1px solid #888;
}
#zoombuttons button:active {
background:#eee;
}
#zoombuttons #zoomIn {
border-radius: 4px 0 0 4px;
}
#zoombuttons #zoomOut {
border-left:0;
border-radius: 0 4px 4px 0;
}
+30 -25
View File
@@ -8,7 +8,6 @@
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojox/layout/resources/FloatingPane.css">
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojox/layout/resources/ResizeHandle.css">
<link rel="stylesheet" href="css/app.css">
<script src="js/lib/jshashtable.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojo/dojo.js" data-dojo-config="async: true, parseOnLoad: true, baseUrl: 'js/iD/'"></script>
<style type="text/css">
:focus { outline-color: transparent; outline-style: none; }
@@ -19,23 +18,31 @@
</head>
<body class="claro">
<div id="appLayout" class="demoLayout">
<script>
<script type="text/javascript" src="js/lib/underscore-min.js"></script>
<script type="text/javascript" src="js/lib/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="js/iD/Util.js"></script>
<script type="text/javascript" src="js/iD/Node.js"></script>
<script type="text/javascript" src="js/iD/Relation.js"></script>
<script type="text/javascript" src="js/iD/Entity.js"></script>
<script type="text/javascript" src="js/iD/Way.js"></script>
<script type="text/javascript" src="js/iD/Connection.js"></script>
<script>
require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom",
"dijit/form/Button","dijit/form/ToggleButton",
"dojox/layout/FloatingPane",
"iD/actions/UndoStack","iD/actions/CreatePOIAction",
"iD/Connection",
"iD/Controller",
"iD/controller/edit/NoSelection","iD/controller/shape/NoSelection",
"iD/actions/CreateEntityAction",
"iD/controller/edit/NoSelection",
"iD/controller/shape/NoSelection",
"iD/renderer/Map","iD/styleparser/RuleSet",
"iD/ui/DragAndDrop","iD/ui/StepPane",
"dojo/domReady!"], function(lang,domGeom,domClass,on,dom){
"dojo/domReady!"], function(lang,domGeom,domClass,on,dom) {
var ruleset=new iD.styleparser.RuleSet();
var conn=new iD.Connection("http://www.overpass-api.de/api/xapi?");
// Load styles
ruleset.registerCallback(styleLoaded);
ruleset.loadFromCSS("potlatch.css",styleLoaded);
@@ -56,7 +63,7 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
// Initialise controller
var controller=new iD.Controller(map);
map.setController(controller);
// Initialise event listeners
on(window, "enterState", enterStateListener);
on(window, "exitState", exitStateListener);
@@ -77,10 +84,10 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
// Load data
map.download();
}
// ----------------------------------------------------
// State event listeners
function enterStateListener(event) {
domClass.add(event.state[0]+"Button","currentMode");
};
@@ -88,7 +95,7 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
function exitStateListener(event) {
domClass.remove(event.state[0]+"Button","currentMode");
};
// ----------------------------------------------------
// Mode button handlers
@@ -98,7 +105,7 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
enterEditMode=function() {
};
finishClicked=function() {
controller.stepper.hide();
};
@@ -106,12 +113,17 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
cancelClicked=function() {
controller.stepper.hide();
};
// ----------------------------------------------------
// Map control handlers
zoomInClicked =function() { map.zoomIn(); };
zoomOutClicked=function() { map.zoomOut(); };
$('#zoomIn').click(function() {
map.zoomIn();
});
$('#zoomOut').click(function() {
map.zoomOut();
});
});
</script>
@@ -139,14 +151,7 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
</div>
<div id="zoombuttons">
<button style="position: absolute; left: 10px; top: 40px;"
id="zoomIn" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:zoomInClicked">
+
</button>
<button style="position: absolute; left: 10px; top: 70px;"
id="zoomOut" data-dojo-type="dijit.form.Button" data-dojo-props="onClick:zoomOutClicked">
&ndash;
</button>
<button id="zoomIn">+</button><button id="zoomOut">&ndash;</button>
</div>
<!-- Map div -->
@@ -158,7 +163,7 @@ require(["dojo/_base/lang","dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/
<!-- Floating help window -->
<div id="helpPane" data-dojo-type="dojox.layout.FloatingPane"
<div id="helpPane" data-dojo-type="dojox.layout.FloatingPane"
data-dojo-props="resizable:true, closable:false, dockable:false, title: 'Step by step'"
style="position:absolute; top:45px; left:50px; width:200px; height:200px; visibility: hidden;" >
<ol id="helpSteps">
+118 -162
View File
@@ -1,61 +1,36 @@
// iD/Connection.js
define(["dojo/_base/xhr","dojo/_base/lang","dojox/xml/DomParser","dojo/_base/array",'dojo/_base/declare',
"iD/Entity","iD/Node","iD/Way","iD/Relation","iD/actions/CreateEntityAction"],
function(xhr,lang,DomParser,array,declare,Entity){
// define(["dojo/_base/xhr","dojo/_base/lang","dojox/xml/DomParser","dojo/_base/array",'dojo/_base/declare',
// "iD/Entity","iD/Node","iD/Way","iD/Relation","iD/actions/CreateEntityAction"],
// function(xhr,lang,DomParser,array,declare,Entity){
// ----------------------------------------------------------------------
// Connection base class
declare("iD.Connection", null, {
nodes: {}, // hash of node objects
ways: {}, // hash of way objects
relations: {}, // hash of relation objects
pois: null, // list of nodes which are POIs
maps: [], // list of Map objects listening to this
callback: null, // callback once .osm is parsed
modified: false, // data has been changed
nextNode: -1, // next negative ids
nextWay: -1, // |
nextRelation: -1, // |
apiBaseURL: '', // root API address
constructor:function(apiURL) {
// summary: The data store, including methods to fetch data from (and, eventually, save data to)
// an OSM API server.
this.nodes={};
this.ways={};
this.relations={};
this.pois=new Hashtable();
this.maps=[];
this.modified=false;
this.apiBaseURL=apiURL;
},
if (typeof iD === 'undefined') iD = {};
iD.Connection = function(apiURL) {
// summary: The data store, including methods to fetch data from (and, eventually, save data to)
// an OSM API server.
this.nextNode = -1; // next negative ids
this.nextWay = -1; // |
this.nextRelation = -1; // |
this.nodes={};
this.ways={};
this.relations= {};
this.pois = {};
this.maps=[];
this.modified=false;
this.apiBaseURL=apiURL;
this.callback = null;
};
iD.Connection.prototype = {
_assign:function(obj) {
// summary: Save an entity to the data store.
switch (obj.entityType) {
case "node": this.nodes[obj.id]=obj; break;
case "way": this.ways[obj.id]=obj; break;
case "relation": this.relations[obj.id]=obj; break;
case "node": this.nodes[obj.id]=obj; break;
case "way": this.ways[obj.id]=obj; break;
case "relation": this.relations[obj.id]=obj; break;
}
},
getNode:function(id) {
// summary: Return a node by id.
return this.nodes[id]; // iD.Node
},
getWay:function(id) {
// summary: Return a way by id.
return this.ways[id]; // iD.Way
},
getRelation:function(id) {
// summary: Return a relation by id.
return this.relations[id]; // iD.Relation
},
_getOrCreate:function(id,type) {
// summary: Return an entity if it exists: if not, create an empty one with the given id, and return that.
@@ -75,52 +50,45 @@ declare("iD.Connection", null, {
doCreateNode:function(tags, lat, lon, perform) {
// summary: Create a new node and save it in the data store, using an undo stack.
var node = new iD.Node(this, this.nextNode--, lat, lon, tags, true);
perform(new iD.actions.CreateEntityAction(node, lang.hitch(this,this._assign) ));
perform(new iD.actions.CreateEntityAction(node, _.bind(this._assign, this) ));
return node; // iD.Node
},
doCreateWay:function(tags, nodes, perform) {
// summary: Create a new way and save it in the data store, using an undo stack.
var way = new iD.Way(this, this.nextWay--, nodes.concat(), tags, true);
perform(new iD.actions.CreateEntityAction(way, lang.hitch(this,this._assign) ));
perform(new iD.actions.CreateEntityAction(way, _.bind(this._assign, this) ));
return way;
},
doCreateRelation:function(tags, members, perform) {
// summary: Create a new relation and save it in the data store, using an undo stack.
var relation = new iD.Relation(this, this.nextRelation--, members.concat(), tags, true);
perform(new iD.actions.CreateEntityAction(relation, lang.hitch(this,this._assign) ));
perform(new iD.actions.CreateEntityAction(relation, _.bind(this._assign, this) ));
return relation;
},
markClean:function() {
// summary: Mark the connection as clean (i.e. there's no new data to be saved).
this.modified=false;
},
markDirty:function() {
// summary: Mark the connection as dirty (i.e. there's data to be saved).
this.modified=true;
},
isDirty:function() {
// summary: Is the connection dirty?
return this.modified;
},
getObjectsByBbox:function(left,right,top,bottom) {
// summary: Find all drawable entities that are within a given bounding box.
// returns: Object An object with four properties: .poisInside, .poisOutside, .waysInside, .waysOutside.
// Each one is an array of entities.
var o={ poisInside: [], poisOutside: [],
waysInside: [], waysOutside: [] };
for (var id in this.ways) {
var way=this.ways[id];
if (way.within(left,right,top,bottom)) { o.waysInside.push(way); }
else { o.waysOutside.push(way); }
}
this.pois.each(function(node,v) {
if (node.within(left,right,top,bottom)) { o.poisInside.push(node); }
else { o.poisOutside.push(node); }
});
return o;
},
getObjectsByBbox:function(left,right,top,bottom) {
// summary: Find all drawable entities that are within a given bounding box.
// returns: Object An object with four properties: .poisInside, .poisOutside, .waysInside, .waysOutside.
// Each one is an array of entities.
var o = {
poisInside: [],
poisOutside: [],
waysInside: [],
waysOutside: []
};
for (var id in this.ways) {
var way = this.ways[id];
if (way.within(left,right,top,bottom)) { o.waysInside.push(way); }
else { o.waysOutside.push(way); }
}
_.each(this.pois, function(node) {
if (node.within(left,right,top,bottom)) { o.poisInside.push(node); }
else { o.poisOutside.push(node); }
});
return o;
},
// ---------------
// Redraw handling
@@ -132,94 +100,95 @@ declare("iD.Connection", null, {
refreshMaps:function() {
// summary: Redraw all the Map objects that take data from this Connection.
array.forEach(this.maps, function(map) {
_.each(this.maps, function(map) {
map.updateUIs(false,true);
});
},
refreshEntity:function(_entity) {
// summary: Redraw a particular entity on all the Map objects that take data from this Connection.
array.forEach(this.maps, function(map) {
_.each(this.maps, function(map) {
map.refreshUI(_entity);
});
},
// ------------
// POI handling
updatePOIs:function(nodelist) {
// summary: Update the list of POIs (nodes not in ways) from a supplied array of nodes.
for (var i in nodelist) {
if (nodelist[i].hasParentWays()) {
this.pois.remove(nodelist[i]);
if (nodelist[i].entity.hasParentWays()) {
delete this.pois[nodelist[i]._id];
} else {
this.pois.put(nodelist[i],true);
this.pois[nodelist[i]._id] = nodelist[i];
}
}
},
getPOIs:function() {
// summary: Return a list of all the POIs in this Connection.
return this.pois.keys(); // Array
return _.values(this.pois);
},
registerPOI:function(node) {
// summary: Register a node as a POI (not in a way).
this.pois.put(node,true);
this.pois[node._id] = node;
},
unregisterPOI:function(node) {
// summary: Mark a node as no longer being a POI (it's now in a way).
this.pois.remove(node);
delete this.pois[node._id];
},
// ----------
// OSM parser
loadFromAPI:function(left,right,top,bottom) {
// summary: Request data within the bbox from an external OSM server. Currently hardcoded
// to use Overpass API (which has the relevant CORS headers).
var url="http://www.overpass-api.de/api/xapi?map?bbox="+left+","+bottom+","+right+","+top;
xhr.get({ url: url,
headers: { "X-Requested-With": null },
load: lang.hitch(this, "_processOSM") });
},
loadFromAPI:function(left,right,top,bottom) {
// summary: Request data within the bbox from an external OSM server. Currently hardcoded
// to use Overpass API (which has the relevant CORS headers).
this.loadFromURL("http://www.overpass-api.de/api/xapi?map?bbox=" + [left,bottom,right,top]);
},
loadFromURL:function(url) {
// summary: Load all data from a given URL.
xhr.get({ url: url, load: lang.hitch(this, "_processOSM") });
$.ajax({
url: url,
context: this,
headers: { "X-Requested-With": null },
success: this._processOSM
});
},
_processOSM:function(result) {
var jsdom = DomParser.parse(result).childNodes[1];
_processOSM:function(dom) {
// var jsdom = $.parseXML(result).childNodes[1];
var nodelist = [];
for (var i in jsdom.childNodes) {
var obj=jsdom.childNodes[i];
for (var i in dom.childNodes[0].childNodes) {
var obj=dom.childNodes[0].childNodes[i];
switch(obj.nodeName) {
case "node":
var node = new iD.Node(this,
getAttribute(obj,'id'),
getAttribute(obj,'lat'),
getAttribute(obj,'lon'),
getTags(obj));
this._assign(node);
case "node":
var node = new iD.Node(this,
+getAttribute(obj,'id'),
+getAttribute(obj,'lat'),
+getAttribute(obj,'lon'),
getTags(obj));
this._assign(node);
nodelist.push(node);
break;
case "way":
var way = new iD.Way(this,
getAttribute(obj,'id'),
getNodes(obj,this),
getTags(obj));
var way = new iD.Way(this,
getAttribute(obj,'id'),
getNodes(obj,this),
getTags(obj));
this._assign(way);
break;
case "relation":
var relation = new iD.Relation(this,
getAttribute(obj,'id'),
getMembers(obj,this),
getTags(obj));
var relation = new iD.Relation(this,
getAttribute(obj,'id'),
getMembers(obj,this),
getTags(obj));
this._assign(relation);
break;
}
@@ -229,56 +198,43 @@ declare("iD.Connection", null, {
if (this.callback) { this.callback(); }
// Private functions to parse DOM created from XML file
function filterNodeName(n) {
return function(item) {
return item.nodeName === n;
};
}
function getAttribute(obj,name) {
var result=array.filter(obj.attributes,function(item) {
return item.nodeName==name;
});
return result[0].nodeValue;
return _.find(obj.attributes, filterNodeName(name)).nodeValue;
}
function getTags(obj) {
var tags={};
array.forEach(obj.childNodes,function(item) {
if (item.nodeName=='tag') {
tags[getAttribute(item,'k')]=getAttribute(item,'v');
}
});
return tags;
return _(obj.childNodes).chain()
.filter(filterNodeName('tag'))
.map(function(item) {
return [getAttribute(item,'k'), getAttribute(item,'v')];
}).object().value();
}
function getNodes(obj,conn) {
var nodes=[];
array.forEach(obj.childNodes,function(item) {
if (item.nodeName=='nd') {
var id=getAttribute(item,'ref');
nodes.push(conn.getNode(id));
}
});
return nodes;
return _(obj.childNodes).chain()
.filter(filterNodeName('nd'))
.map(function(item) {
return conn.nodes[getAttribute(item,'ref')];
}).value();
}
function getMembers(obj,conn) {
var members=[];
array.forEach(obj.childNodes,function(item) {
if (item.nodeName=='member') {
var id =getAttribute(item,'ref');
var type=getAttribute(item,'type');
var role=getAttribute(item,'role');
return _(obj.childNodes).chain()
.filter(filterNodeName('member'))
.map(function(item) {
var id = getAttribute(item,'ref'),
type = getAttribute(item,'type'),
role = getAttribute(item,'role');
var obj=conn._getOrCreate(id,type);
members.push(new iD.RelationMember(obj,role));
}
});
return members;
var obj = conn._getOrCreate(id,type);
return new iD.RelationMember(obj,role);
}).value();
}
}
});
// ----------------------------------------------------------------------
// End of module
});
};
+3 -5
View File
@@ -1,5 +1,3 @@
// iD/Controller.js
define(['dojo/_base/declare','dojo/on','iD/actions/UndoStack'], function(declare,on){
// ----------------------------------------------------------------------
@@ -10,7 +8,7 @@ declare("iD.Controller", null, {
map: null, // current Map
stepper: null, // current StepPane
undoStack: null, // main undoStack
constructor:function(_map) {
// summary: The Controller marshalls ControllerStates and passes events to them.
this.map=_map;
@@ -21,7 +19,7 @@ declare("iD.Controller", null, {
// summary: Set reference for the singleton-like class for the step-by-step instruction panel.
this.stepper=_stepper;
},
setState:function(newState) {
// summary: Enter a new ControllerState, firing exitState on the old one, and enterState on the new one.
if (newState==this.state) { return; }
@@ -34,7 +32,7 @@ declare("iD.Controller", null, {
newState.enterState();
on.emit(window, "enterState", { bubbles: true, cancelable: true, state: this.state.stateNameAsArray() });
},
entityMouseEvent:function(event,entityUI) {
// summary: Pass a MouseEvent on an EntityUI (e.g. clicking a way) to the current ControllerState.
if (!this.state) { return; }
+36 -120
View File
@@ -1,164 +1,80 @@
// iD/Entity.js
// Entity classes for iD
if (typeof iD === 'undefined') iD = {};
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
iD.Entity = function() {
this.parents = {};
// The ID locally
this._id = iD.Util.id();
this.connection = null;
// The ID in OSM terms
this.id = NaN;
this.loaded = false;
this.entityType = '';
this.modified = false;
this.deleted = false;
};
// ----------------------------------------------------------------------
// Entity base class
declare("iD.Entity", null, {
connection: null,
id: NaN,
loaded: false,
tags: null,
entityType: '',
parents: null,
modified: false,
deleted: false,
MAINKEYS: ['highway','amenity','railway','waterway'],
constructor:function() {
// summary: The base class for an entity (way, node or relation).
this.tags={};
this.parents=new Hashtable();
},
iD.Entity.prototype = {
isType:function(type) {
// summary: Is this entity of the specified type ('node','way','relation')?
return this.entityType==type; // Boolean
return this.entityType === type;
},
toString:function() {
return this.entityType+"."+this.id;
return this.entityType + " . " + this.id;
},
// --------------------------------
// Provoke redraw and other changes
refresh:function() {
// summary: Ask the connection to provoke redraw and other changes.
this.connection.refreshEntity(this);
},
// ---------------
// Clean and dirty
_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) {
// summary: Mark entity as deleted or not.
this.deleted=isDeleted;
},
// -------------------------------------
// Bounding box check (to be overridden)
within:function(left,right,top,bottom) {
within:function(left, right, top, bottom) {
// summary: Is the entity within the specified bbox?
return !this.deleted; // Boolean
},
// -------------
// Tag functions
getTagsHash:function() {
// 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; // 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); }
if (this.tags.ref) { n.push(this.tags.ref); }
if (n.length===0) {
for (var i=0; i<this.MAINKEYS.length; i++) {
if (this.tags[this.MAINKEYS[i]]) { n.push(this.tags[this.MAINKEYS[i]]); break; }
}
}
return n.length===0 ? 'unknown' : n.join('; '); // String
},
// ---------------
// Parent-handling
addParent:function(entity) {
addParent: function(entity) {
// summary: Record a parent (a relation or way which contains this entity).
this.parents.put(entity,true);
this.parents[entity._id] = entity;
},
removeParent:function(entity) {
removeParent: function(entity) {
// summary: Remove a parent (e.g. when node removed from a way).
this.parents.remove(_entity);
delete this.parents[entity._id];
},
hasParent:function(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
return !!this.parents[entity._id];
},
parentObjects:function() {
parentObjects: function() {
// summary: List of all parents of this entity.
return this.parents.keys(); // Boolean
return _.values(this.parents);
},
hasParentWays:function() {
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; // Boolean
return !!_.find(this.parentObjects(), function(p) {
return p.entityType === 'way';
});
},
parentWays:function() {
parentWays: function() {
// summary: Return an array of all ways that this entity is a member of.
return this._parentObjectsOfClass('way'); // Array
},
parentRelations:function() {
parentRelations: function() {
// summary: Return an array of all relations that this entity is a member of.
return this._parentObjectsOfClass('relation'); // Array
},
_parentObjectsOfClass:function(_class) {
var p=this.parentObjects(), c=[];
for (var i in p) {
if (p[i].entityType==_class) { c.push(p[i]); }
}
return c;
_parentObjectsOfClass: function(_class) {
return _.filter(this.parentObjects(), function(p) {
return p.entityType === _class;
});
}
// Halcyon also implements:
// removeFromParents()
// hasParents()
// findParentRelationsOfType(type,role)
// getRelationMemberships()
// countParentObjects(within)
// memberships()
});
// ----------------------------------------------------------------------
// End of module
});
};
+41 -45
View File
@@ -1,64 +1,60 @@
// iD/Node.js
if (typeof iD === 'undefined') iD = {};
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/Entity','iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
iD.Node = function(conn, id, lat, lon, tags, loaded) {
// summary: An OSM node.
this.entityType = 'node';
this.connection = conn;
this.id = id;
this._id = iD.Util.id();
this.entity = new iD.Entity();
this.lat = lat;
this.lon = lon;
this.tags = tags;
this.loaded = (loaded === undefined) ? true : loaded;
this.project();
this.modified = this.id < 0;
};
// ----------------------------------------------------------------------
// 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() {
iD.Node.prototype = {
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));
this.latp = 180/Math.PI *
Math.log(Math.tan(Math.PI/4+this.lat*(Math.PI/180)/2));
},
latp2lat:function(a) {
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
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; },
within: function(left, right, top, bottom) {
return (this.lon >= left) &&
(this.lon <= right) &&
(this.lat >= bottom) &&
(this.lat <= top);
},
refresh:function() {
var ways=this.parentWays();
var conn=this.connection;
array.forEach(ways,function(way) { conn.refreshEntity(way); });
refresh: function() {
var ways = this.parentWays();
_.each(ways, _.bind(function(way) { this.connection.refreshEntity(way); }, this));
this.connection.refreshEntity(this);
},
doSetLonLatp:function(lon,latproj,performAction) {
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) ));
performAction(new iD.actions.MoveNodeAction(this,
this.latp2lat(latproj),
lon,
lang.hitch(this,this._setLatLonImmediate)));
},
_setLatLonImmediate:function(lat,lon) {
_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
});
_.each(ways, _.bind(function(way) {
way.expandBbox(this);
}, this));
}
};
+18 -41
View File
@@ -1,43 +1,20 @@
// iD/Relation.js
if (typeof iD === 'undefined') iD = {};
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/Entity','iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
iD.Relation = function(conn, id, members, tags, loaded) {
this.entityType = 'relation';
this.connection = conn;
this.id = id;
this._id = iD.Util.id();
this.members = members;
this.tags = tags;
this.modified = this.id < 0;
this.loaded = (loaded === undefined) ? true : loaded;
_.each(members, _.bind(function(member) {
member.entity.entity.addParent(this);
}, this));
};
// ----------------------------------------------------------------------
// 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
});
iD.RelationMember = function(entity, role) {
this.entity = entity;
this.role = role;
};
+28
View File
@@ -0,0 +1,28 @@
if (typeof iD === 'undefined') iD = {};
iD.Util = {};
iD.Util._id = 0;
iD.Util.id = function() {
return iD.Util._id++;
};
iD.Util.friendlyName = function(entity) {
// 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 (_.isEmpty(entity.tags)) { return ''; }
var mainkeys = ['highway','amenity','railway','waterway'];
var n = _.compact([entity.tags.name, entity.tags.ref]);
if (!n.length) {
var k = _.find(mainkeys, function(m) {
return !!entity.tags[m];
});
if (k) n.push(entity.tags[k]);
}
return n.length === 0 ? 'unknown' : n.join('; ');
};
+113 -138
View File
@@ -1,153 +1,128 @@
// iD/Way.js
if (typeof iD === 'undefined') iD = {};
define(['dojo/_base/declare','dojo/_base/array','dojo/_base/lang',
'iD/Entity','iD/actions/AddNodeToWayAction','iD/actions/MoveNodeAction'
], function(declare,array,lang){
iD.Way = function(conn, id, nodes, tags, loaded) {
// summary: An OSM way.
this.connection = conn;
this.entityType = 'way';
this.id = id;
this._id = iD.Util.id();
this.nodes = nodes || [];
this.deleted = false;
this.entity = new iD.Entity();
this.tags = tags || {};
this.loaded = (loaded === undefined) ? true : loaded;
this.modified = this.id < 0;
_.each(nodes, _.bind(function(node) {
node.entity.addParent(this);
}, this));
this._calculateBbox();
};
// ----------------------------------------------------------------------
// Way class
iD.Way.prototype = {
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
},
declare("iD.Way", [iD.Entity], {
nodes: null,
entityType: "way",
edgel: NaN,
edger: NaN,
edget: NaN,
edgeb: NaN,
isType:function(type) {
// summary: Is this a 'way' (always true), an 'area' (closed) or a 'line' (unclosed)?
if (type === 'way') return true;
if (type === 'area') return this.isClosed();
if (type === 'line') return !(this.isClosed());
return false; // Boolean
},
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
},
// ---------------------
// Bounding-box handling
within:function(left,right,top,bottom) {
// TODO invert and just return
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 )) {
return false;
} else {
return true;
}
},
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
},
_calculateBbox:function() {
this.edgel = 999999; this.edger = -999999;
this.edgeb = 999999; this.edget = -999999;
_.each(this.nodes, _.bind(function(n) { this.expandBbox(n); }, this));
},
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
},
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);
},
// ---------------------
// Bounding-box handling
// --------------
// Action callers
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;
},
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
},
_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);
},
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.nodes[0]) performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, 0, true));
return this.nodes.length + 1; // int
},
// --------------
// Action callers
doInsertNode:function(index, node, performAction) {
// summary: Add a node at a given index within the way, using an undo stack.
if (index > 0 && this.nodes[index - 1]==node) return;
if (index < this.nodes.length - 1 && this.nodes[index]==node) return;
performAction(new iD.actions.AddNodeToWayAction(this, node, this.nodes, index, false));
},
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
},
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,
newIndex = 0,
snapped;
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
},
for (var i = 0; i < this.nodes.length - 1; i++) {
var node1 = this.nodes[i];
var node2 = this.nodes[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);
}
}
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;
// splice in new node
if (isSnap) { newNode.doSetLonLatp(snapped.x, snapped.y, performAction); }
this.doInsertNode(newIndex, newNode, performAction);
return newIndex; // int
},
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);
}
}
_pythagoras:function(node1, node2) {
return (Math.sqrt(Math.pow(node1.lon-node2.lon,2) +
Math.pow(node1.latp-node2.latp,2)));
},
// 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
});
_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 };
}
};
+4 -4
View File
@@ -66,20 +66,20 @@ declare("iD.actions.UndoableEntityAction", [iD.actions.UndoableAction], {
// summary: Mark a change to the entity ('dirtying' it).
if (!this.initialised) this.init();
if (!this.wasDirty) this.entity._markDirty();
if (!this.connectionWasDirty) this.entity.connection.markDirty();
if (!this.connectionWasDirty) this.entity.connection.modified = true;
},
markClean:function() {
// summary: If the entity was clean before, revert the dirty flag to that state.
if (!this.initialised) this.init();
if (!this.wasDirty) this.entity._markClean();
if (!this.connectionWasDirty) this.entity.connection.markClean();
if (!this.connectionWasDirty) this.entity.connection.modified = false;
},
init:function() {
// summary: Record whether or not the entity and connection were clean before this action started
this.wasDirty = this.entity.isDirty();
this.connectionWasDirty = this.entity.connection.isDirty();
this.wasDirty = this.entity.modified;
this.connectionWasDirty = this.entity.connection.modified;
this.initialised = true;
},
+1 -1
View File
@@ -19,7 +19,7 @@ declare("iD.controller.edit.EditBaseState", [iD.controller.ControllerState], {
// x: Number Screen co-ordinate.
// y: Number Screen co-ordinate.
// entity: iD.Entity The entity to be edited.
var h=entity.friendlyName();
var h = iD.Util.friendlyName(entity);
if (h !== '') h = h + "<br/>";
this.editortooltip = new dijit.TooltipDialog({
content: h+"<button data-dojo-type='dijit.form.Button' type='submit'>Edit tags</button> " +
+6 -6
View File
@@ -15,28 +15,28 @@ declare("iD.controller.edit.NoSelection", [iD.controller.edit.EditBaseState], {
constructor:function() {
// summary: In 'Edit object' mode but nothing selected.
},
enterState:function() {
this.controller.stepper.hide();
},
processMouseEvent:function(event,entityUI) {
this.inherited(arguments);
if (!entityUI) { return this; }
var entity=entityUI.entity;
var entity = entityUI.entity;
if (event.type=='click') {
this.inherited(arguments);
switch (entity.entityType) {
case 'node':
var ways=entity.parentWays();
if (ways.length==0) { return new iD.controller.edit.SelectedPOINode(entity); }
if (!ways.length) { return new iD.controller.edit.SelectedPOINode(entity); }
else { return new iD.controller.edit.SelectedWayNode(entity,ways[0]); }
break;
case 'way':
return new iD.controller.edit.SelectedWay(entityUI.entity, event);
}
}
return this;
}
});
// ----------------------------------------------------------------------
+12 -12
View File
@@ -4,7 +4,7 @@
Add road or shape -> DrawWay
The user is drawing a way.
Goes to:
-> click empty area: adds point, continues
-> click way: adds junction, continues
@@ -26,8 +26,8 @@ declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
wayUI: null,
editEnd: false,
constructor:function(_way) {
this.way=_way;
constructor: function(way) {
this.way = way;
},
enterState:function() {
this.wayUI=this.controller.map.getUI(this.way);
@@ -42,14 +42,14 @@ declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
this.wayUI.resetStateClass('shownodes');
this.wayUI.redraw();
},
processMouseEvent:function(event,entityUI) {
var entity=entityUI ? entityUI.entity : null;
var entityType=entity ? entity.entityType : null;
var map=this.controller.map;
var ways;
if (event.type=='mouseover' && entityType=='way' && entityUI!=this.wayUI) {
if (event.type=='mouseover' && entityType=='way' && entityUI!=this.wayUI) {
// Mouse over way, show hover highlight
entityUI.setStateClass('shownodeshover');
entityUI.redraw();
@@ -57,7 +57,7 @@ declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
this.updateElastic(event);
return this;
} else if (event.type=='mouseout' && entityType=='way' && entityUI!=this.wayUI) {
} else if (event.type=='mouseout' && entityType=='way' && entityUI!=this.wayUI) {
// Mouse left way, remove hover highlight
// Find what object we're moving into
var into=shape.byId((event.hasOwnProperty('toElement') ? event.toElement : event.relatedTarget).__gfxObject__);
@@ -117,7 +117,7 @@ declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
var action=this.undoAdder(); action(undo);
return this;
}
} else if (event.type=='click') {
// Click on empty space, add new node to way
var undo=new iD.actions.CompositeUndoableAction();
@@ -137,15 +137,15 @@ declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
map.mouseX(event), map.mouseY(event)
);
},
getDrawingNode:function() {
return (this.editEnd ? this.way.nodes[this.way.length()-1] : this.way.nodes[0]);
},
getStartNode:function() {
return (this.editEnd ? this.way.nodes[0] : this.way.nodes[this.way.length()-1]);
},
appendNode:function(node, performAction) {
if (this.editEnd) { this.way.doAppendNode(node, performAction); }
else { this.way.doPrependNode(node, performAction); }
@@ -154,13 +154,13 @@ declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
appendNewNode:function(event, undo) {
var map=this.controller.map;
var node=this.getConnection().doCreateNode(
{},
{},
map.coord2lat(map.mouseY(event)),
map.coord2lon(map.mouseX(event)), lang.hitch(undo,undo.push) );
this.appendNode(node, lang.hitch(undo,undo.push));
return node;
}
});
// ----------------------------------------------------------------------
+2 -3
View File
@@ -4,7 +4,7 @@
Add road or shape -> NoSelection
The user has clicked 'Add road or shape', but hasn't yet started drawing.
Goes to:
-> click empty area: goes to shape/DrawWay
-> click way: goes to shape/SelectedWay
@@ -37,7 +37,7 @@ declare("iD.controller.shape.NoSelection", [iD.controller.ControllerState], {
tag: "Set the type of the road or shape"
},['begin','draw','tag']).highlight('begin');
},
processMouseEvent:function(event,entityUI) {
var entity=entityUI ? entityUI.entity : null;
var entityType=entity ? entity.entityType : null;
@@ -70,7 +70,6 @@ declare("iD.controller.shape.NoSelection", [iD.controller.ControllerState], {
}
return this;
}
});
// ----------------------------------------------------------------------
+5 -5
View File
@@ -50,13 +50,13 @@ declare("iD.renderer.EntityUI", null, {
},
refreshStyleList:function(tags) {
// summary: Calculate the list of styles that apply to this UI at this zoom level.
if (!this.styleList || !this.styleList.isValidAt(this.map.scale)) {
if (!this.styleList || !this.styleList.isValidAt(this.map.scale)) {
this.styleList=this.map.ruleset.getStyles(this.entity,tags,this.map.scale);
}
this.layer=this.styleList.layerOverride();
if (isNaN(this.layer)) {
this.layer=0;
if (tags['layer']) { this.layer=Number(tags['layer']); }
if (tags.layer) { this.layer = +tags.layer; }
}
// Iterate through each subpart, drawing any styles on that layer
@@ -78,7 +78,7 @@ declare("iD.renderer.EntityUI", null, {
// --------------------
// State class handling
setStateClasses:function(stateClasses) {
// summary: Set all state classes at once, and prompt a redraw if they're different to previously,
if (stateClasses && this.stateClasses.join(',')!=stateClasses.join(',')) {
@@ -87,7 +87,7 @@ declare("iD.renderer.EntityUI", null, {
}
return this;
},
setStateClass:function(sc) {
// summary: Set a single state class, and prompt a redraw if it wasn't set previously.
if (this.stateClasses.indexOf(sc)==-1) {
@@ -122,7 +122,7 @@ declare("iD.renderer.EntityUI", null, {
// summary: Receive a mouse event (e.g. clicking on the UI), and forward it to the Controller.
this.map.controller.entityMouseEvent(event, event.gfxTarget.source);
event.stopPropagation();
},
}
});
// ----------------------------------------------------------------------
+88 -77
View File
@@ -53,7 +53,7 @@ declare("iD.renderer.Map", null, {
edgeb: NaN, // |
mapheight: NaN, // size of map object in pixels
mapwidth: NaN, // |
layers: null, // array-like object of Groups, one for each OSM layer
minlayer: -5, // minimum OSM layer supported
maxlayer: 5, // maximum OSM layer supported
@@ -61,7 +61,7 @@ declare("iD.renderer.Map", null, {
elastic: null, // Group for drawing elastic band
ruleset: null, // map style
constructor:function(obj) {
// summary: The main map display, containing the individual sprites (UIs) for each entity.
// obj: Object An object containing .lat, .lon, .scale, .div (the name of the <div> to be used),
@@ -75,7 +75,12 @@ declare("iD.renderer.Map", null, {
this.wayuis={},
this.div=document.getElementById(obj.div);
this.surface=Gfx.createSurface(obj.div, this.mapwidth, this.mapheight);
this.backdrop=this.surface.createRect( { x:0, y:0, width: this.mapwidth, height: this.mapheight }).setFill(new dojo.Color([255,255,245,1]));
this.backdrop=this.surface.createRect({
x: 0,
y: 0,
width: this.mapwidth,
height: this.mapheight
}).setFill(new dojo.Color([255,255,245,1]));
this.tilegroup=this.surface.createGroup();
this.container=this.surface.createGroup();
this.conn=obj.connection;
@@ -111,7 +116,7 @@ declare("iD.renderer.Map", null, {
this.surface.connect("onmousedown", lang.hitch(this,"_mouseEvent"));
this.surface.connect("onmouseup", lang.hitch(this,"_mouseEvent"));
},
setController:function(controller) {
// summary: Set the controller that will handle events on the map (e.g. mouse clicks).
this.controller=controller;
@@ -145,9 +150,9 @@ declare("iD.renderer.Map", null, {
sublayer:function(layer,groupType,sublayer) {
// summary: Find the gfx.Group for a given OSM layer and rendering sublayer, creating it
// if necessary. Note that sublayers are only implemented for stroke and fill.
// if necessary. Note that sublayers are only implemented for stroke and fill.
// groupType: String 'casing','text','hit','stroke', or 'fill'
var collection=this.layers[layer][groupType];
var collection=this.layers[layer][groupType], sub;
switch (groupType) {
case 'casing':
case 'text':
@@ -157,7 +162,7 @@ declare("iD.renderer.Map", null, {
// Find correct sublayer, inserting if necessary
var insertAt=collection.children.length;
for (var i=0; i<collection.children.length; i++) {
var sub=collection.children[i];
sub=collection.children[i];
if (sub.sublayer==sublayer) { return sub; }
else if (sub.sublayer>sublayer) {
sub=collection.createGroup();
@@ -168,50 +173,56 @@ declare("iD.renderer.Map", null, {
}
sub=collection.createGroup().moveToFront();
sub.sublayer=sublayer;
return sub; // dojox.gfx.Group
return sub; // dojox.gfx.Group
},
createUI:function(entity,stateClasses) {
// summary: Create a UI (sprite) for an entity, assigning any specified state classes
// (temporary attributes such as ':hover' or ':selected')
var id=entity.id;
switch (entity.entityType) {
case 'node':
if (!this.nodeuis[id]) { this.nodeuis[id]=new iD.renderer.NodeUI(entity,this,stateClasses); }
else { this.nodeuis[id].setStateClasses(stateClasses).redraw(); }
return this.nodeuis[id]; // iD.renderer.EntityUI
case 'way':
if (!this.wayuis[id]) { this.wayuis[id]=new iD.renderer.WayUI(entity,this,stateClasses); }
else { this.wayuis[id].setStateClasses(stateClasses).redraw(); }
return this.wayuis[id]; // iD.renderer.EntityUI
}
var id = entity.id;
if (entity.entityType === 'node') {
if (!this.nodeuis[id]) {
this.nodeuis[id] = new iD.renderer.NodeUI(entity,this,stateClasses);
} else {
this.nodeuis[id].setStateClasses(stateClasses).redraw();
}
return this.nodeuis[id]; // iD.renderer.EntityUI
} else if (entity.entityType === 'way') {
if (!this.wayuis[id]) {
this.wayuis[id] = new iD.renderer.WayUI(entity,this,stateClasses);
} else {
this.wayuis[id].setStateClasses(stateClasses).redraw();
}
return this.wayuis[id]; // iD.renderer.EntityUI
}
},
getUI:function(entity) {
// summary: Return the UI for an entity, if it exists.
switch (entity.entityType) {
case 'node': return this.nodeuis[entity.id]; // iD.renderer.EntityUI
case 'way': return this.wayuis[entity.id]; // iD.renderer.EntityUI
// summary: Return the UI for an entity, if it exists.
if (entity.entityType === 'node') {
return this.nodeuis[entity.id]; // iD.renderer.EntityUI
} else if (entity.entityType === 'way') {
return this.wayuis[entity.id]; // iD.renderer.EntityUI
}
return null;
},
refreshUI:function(entity) {
// summary: Redraw the UI for an entity.
switch (entity.entityType) {
case 'node': if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].redraw(); } break;
case 'way': if (this.wayuis[entity.id] ) { this.wayuis[entity.id].redraw(); } break;
case 'node': if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].redraw(); } break;
case 'way': if (this.wayuis[entity.id] ) { this.wayuis[entity.id].redraw(); } break;
}
},
deleteUI:function(entity) {
// summary: Delete the UI for an entity.
switch (entity.entityType) {
case 'node': if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].removeSprites(); delete this.nodeuis[entity.id]; } break;
case 'way': if (this.wayuis[entity.id] ) { this.wayuis[entity.id].removeSprites(); delete this.wayuis[entity.id]; } break;
case 'node':if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].removeSprites(); delete this.nodeuis[entity.id]; } break;
case 'way': if (this.wayuis[entity.id] ) { this.wayuis[entity.id].removeSprites(); delete this.wayuis[entity.id]; } break;
}
},
download:function() {
// summary: Ask the connection to download data for the current viewport.
this.conn.loadFromAPI(this.edgel, this.edger, this.edget, this.edgeb);
@@ -226,11 +237,12 @@ declare("iD.renderer.Map", null, {
var way, poi;
var o = this.conn.getObjectsByBbox(this.edgel,this.edger,this.edget,this.edgeb);
array.forEach(o.waysInside, function(way) {
if (!way.loaded) return;
if (!m.wayuis[way.id]) { m.createUI(way); }
else if (redraw) { m.wayuis[way.id].recalculate(); m.wayuis[way.id].redraw(); }
});
_(o.waysInside).chain()
.filter(function(w) { return w.loaded; })
.each(function(way) {
if (!m.wayuis[way.id]) { m.createUI(way); }
else if (redraw) { m.wayuis[way.id].recalculate(); m.wayuis[way.id].redraw(); }
});
if (remove) {
array.forEach(o.waysOutside, function(way) {
@@ -257,12 +269,12 @@ declare("iD.renderer.Map", null, {
// -------------
// Zoom handling
zoomIn:function() {
// summary: Zoom in by one level (unless maximum reached).
if (this.scale!=this.MAXSCALE) { this.changeScale(this.scale+1); }
},
zoomOut:function() {
// summary: Zoom out by one level (unless minimum reached).
if (this.scale!=this.MINSCALE) { this.changeScale(this.scale-1); }
@@ -277,7 +289,7 @@ declare("iD.renderer.Map", null, {
this.updateCoordsFromLatLon(this.centrelat,this.centrelon); // recentre
this.updateUIs(true,true);
},
_setScaleFactor:function() {
// summary: Calculate the scaling factor for this zoom level.
this.scalefactor=this.MASTERSCALE/Math.pow(2,13-this.scale);
@@ -285,12 +297,12 @@ declare("iD.renderer.Map", null, {
// ----------------------
// Elastic band redrawing
clearElastic:function() {
// summary: Remove the elastic band used to draw new ways.
this.elastic.clear();
},
drawElastic:function(x1,y1,x2,y2) {
// summary: Draw the elastic band (for new ways) between two points.
this.elastic.clear();
@@ -305,7 +317,7 @@ declare("iD.renderer.Map", null, {
// -------------
// Tile handling
// ** FIXME: see docs
loadTiles:function() {
// summary: Load all tiles for the current viewport. This is a bare-bones function
// at present: it needs configurable URLs (not just Bing), attribution/logo
@@ -320,49 +332,49 @@ declare("iD.renderer.Map", null, {
}
}
},
_fetchTile:function(z,x,y) {
// summary: Load a tile image at the given tile co-ordinates.
var t=this.tilegroup.createImage({
x: this.lon2coord(this.tile2lon(x)),
y: this.lat2coord(this.tile2lat(y)),
x: Math.floor(this.lon2coord(this.tile2lon(x))),
y: Math.floor(this.lat2coord(this.tile2lat(y))),
width: 256, height: 256,
src: this._tileURL(z,x,y)
});
this._assignTile(z,x,y,t);
},
_getTile:function(z,x,y) {
// summary: See if this tile is already loaded.
if (this.tiles[z]==undefined) { return undefined; }
if (this.tiles[z][x]==undefined) { return undefined; }
return this.tiles[z][x][y];
var k = z + ',' + x + ',' + y;
return this.tiles[k];
},
_assignTile:function(z,x,y,t) {
// summary: Store a reference to the tile so we know it's loaded.
if (this.tiles[z]==undefined) { this.tiles[z]=[]; }
if (this.tiles[z][x]==undefined) { this.tiles[z][x]=[]; }
this.tiles[z][x][y]=t;
var k = z + ',' + x + ',' + y;
if (!this.tiles[k]) {
this.tiles[z + ',' + x + ',' + y] = t;
}
},
_tileURL:function(z,x,y) {
// summary: Calculate the URL for a tile at the given co-ordinates.
var u='';
for (var zoom=z; zoom>0; zoom--) {
var byte=0;
var mask=1<<(zoom-1);
if ((x & mask)!=0) byte++;
if ((y & mask)!=0) byte+=2;
if ((x & mask) !== 0) byte++;
if ((y & mask) !== 0) byte += 2;
u=u+byte.toString();
}
return this.tilebaseURL.replace('$z',z).replace('$x',x).replace('$y',y).replace('$quadkey',u);
},
_blankTiles:function() {
// summary: Unload all tiles and remove from the display.
this.tilegroup.clear();
this.tiles=[];
this.tiles = {};
},
// -------------------------------------------
@@ -396,12 +408,12 @@ declare("iD.renderer.Map", null, {
processMove:function(e) {
// summary: Drag the map to a new origin.
// e: MouseEvent The mouse-move event that triggered it.
var x=e.clientX;
var y=e.clientY;
var x = e.clientX;
var y = e.clientY;
if (this.dragging) {
if (this.dragx) {
this.containerx+=(x-this.dragx);
this.containery+=(y-this.dragy);
this.containerx += (x - this.dragx);
this.containery += (y - this.dragy);
this.updateOrigin();
this.dragged=true;
}
@@ -411,31 +423,31 @@ declare("iD.renderer.Map", null, {
this.controller.entityMouseEvent(e,null);
}
},
updateOrigin:function() {
// summary: Tell Dojo to update the viewport origin.
this.container.setTransform([Matrix.translate(this.containerx,this.containery)]);
this.tilegroup.setTransform([Matrix.translate(this.containerx,this.containery)]);
},
_mouseEvent:function(e) {
// summary: Catch mouse events on the surface but not the tiles - in other words,
// on drawn items that don't have their own hitzones, like the fill of a shape.
if (e.type=='mousedown') { this.startDrag(e); }
// ** FIXME: we may want to reinstate this at some point...
// this.controller.entityMouseEvent(e,null);
},
// summary: Catch mouse events on the surface but not the tiles - in other words,
// on drawn items that don't have their own hitzones, like the fill of a shape.
if (e.type=='mousedown') { this.startDrag(e); }
// ** FIXME: we may want to reinstate this at some point...
// this.controller.entityMouseEvent(e,null);
},
updateCoordsFromViewportPosition:function(e) {
// summary: Update centre and bbox from the current viewport origin.
this._updateCoords(this.containerx,this.containery);
},
updateCoordsFromLatLon:function(lat,lon) {
// summary: Update centre and bbox to a specified lat/lon.
this._updateCoords(-(this.lon2coord(lon)-this.mapwidth/2),
-(this.lat2coord(lat)-this.mapheight/2));
},
// summary: Update centre and bbox to a specified lat/lon.
this._updateCoords(-(this.lon2coord(lon)-this.mapwidth/2),
-(this.lat2coord(lat)-this.mapheight/2));
},
_updateCoords:function(x,y) {
// summary: Set centre and bbox.
@@ -481,8 +493,7 @@ declare("iD.renderer.Map", null, {
// Turn event co-ordinates into map co-ordinates
mouseX:function(e) { return e.clientX - domGeom.getMarginBox(this.div).l - this.containerx; },
mouseY:function(e) { return e.clientY - domGeom.getMarginBox(this.div).t - this.containery; },
mouseY:function(e) { return e.clientY - domGeom.getMarginBox(this.div).t - this.containery; }
});
// ----------------------------------------------------------------------
+12 -14
View File
@@ -1,7 +1,7 @@
// iD/renderer/NodeUI.js
// NodeUI classes for iD
define(['dojo/_base/declare','dojo/_base/lang','dojo/_base/array','dojox/gfx/_base','iD/renderer/EntityUI'],
define(['dojo/_base/declare','dojo/_base/lang','dojo/_base/array','dojox/gfx/_base','iD/renderer/EntityUI'],
function(declare,lang,array,g){
// ----------------------------------------------------------------------
@@ -14,25 +14,25 @@ declare("iD.renderer.NodeUI", [iD.renderer.EntityUI], {
},
getEnhancedTags:function() {
var tags=this.inherited(arguments);
if (!this.entity.hasParentWays()) { tags[':poi']='yes'; }
if (!this.entity.entity.hasParentWays()) { tags[':poi']='yes'; }
// add junction and dupe
return tags;
},
redraw:function() {
// summary: Draw the object (mostly icons) and add hitzone sprites.
var node=this.entity;
var node = this.entity;
this.removeSprites();
// Tags, position and styleList
var x=this.map.lon2coord(this.entity.lon);
var y=this.map.latp2coord(this.entity.latp);
var tags=this.getEnhancedTags();
var x = Math.floor(this.map.lon2coord(this.entity.lon));
var y = Math.floor(this.map.latp2coord(this.entity.latp));
var tags = this.getEnhancedTags();
this.refreshStyleList(tags);
// Iterate through each subpart, drawing any styles on that layer
var drawn=false;
var s,p,t,w,h;
for (i=0; i<this.styleList.subparts.length; i++) {
for (i = 0; i < this.styleList.subparts.length; i++) {
var subpart=this.styleList.subparts[i];
p = this.styleList.pointStyles[subpart];
if (!p || !p.drawn()) { continue; }
@@ -44,9 +44,9 @@ declare("iD.renderer.NodeUI", [iD.renderer.EntityUI], {
// Draw icon
var shape;
switch (p.icon_image) {
case 'square': shape=this.targetGroup('stroke',p.sublayer).createRect({ x:x-w/2, y:y-h/2, width:w, height:h }); break;
case 'circle': shape=this.targetGroup('stroke',p.sublayer).createCircle({ cx:x, cy:y, r:w }); break;
default: shape=this.targetGroup('stroke',p.sublayer).createImage({ width:w, height:h, x: x-w/2, y: y-h/2, src:p.icon_image }); break;
case 'square':shape=this.targetGroup('stroke',p.sublayer).createRect({ x:x-w/2, y:y-h/2, width:w, height:h }); break;
case 'circle':shape=this.targetGroup('stroke',p.sublayer).createCircle({ cx:x, cy:y, r:w }); break;
default:shape=this.targetGroup('stroke',p.sublayer).createImage({ width:w, height:h, x: x-w/2, y: y-h/2, src:p.icon_image }); break;
}
switch (p.icon_image) {
case 'square':
@@ -55,7 +55,6 @@ declare("iD.renderer.NodeUI", [iD.renderer.EntityUI], {
this.recordSprite(shape);
// Add text label
// Add hit-zone
var hit;
switch (p.icon_image) {
@@ -64,15 +63,14 @@ declare("iD.renderer.NodeUI", [iD.renderer.EntityUI], {
}
hit.setFill([0,1,0,0]).setStroke( { width:2, color:[0,0,0,0] } );
this.recordSprite(hit);
hit.source=this;
hit.source= this;
hit.connect("onclick" , lang.hitch(this,this.entityMouseEvent));
hit.connect("onmousedown" , lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseup" , lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseenter", lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseleave", lang.hitch(this,this.entityMouseEvent));
}
},
}
});
+22 -18
View File
@@ -28,11 +28,12 @@ declare("iD.renderer.WayUI", [iD.renderer.EntityUI], {
},
redraw:function() {
// summary: Draw the object and add hitzone sprites.
var way=this.entity;
var maxwidth=4;
var i;
var way = this.entity,
maxwidth=4,
i;
this.removeSprites();
if (way.length()==0) { return; }
if (!way.nodes.length) { return; }
// Create tags and calculate styleList
var tags=this.getEnhancedTags();
@@ -40,14 +41,17 @@ declare("iD.renderer.WayUI", [iD.renderer.EntityUI], {
// List of co-ordinates
var coords=[];
for (i=0; i<way.nodes.length; i++) {
var node=way.nodes[i];
coords.push( { x: this.map.lon2coord(node.lon), y: this.map.latp2coord(node.latp) } );
for (i = 0; i < way.nodes.length; i++) {
var node = way.nodes[i];
coords.push({
x: this.map.lon2coord(node.lon),
y: this.map.latp2coord(node.latp)
});
}
// Iterate through each subpart, drawing any styles on that layer
var drawn=false;
for (i=0; i<this.styleList.subparts.length; i++) {
var drawn = false;
for (i = 0; i < this.styleList.subparts.length; i++) {
var subpart=this.styleList.subparts[i];
if (this.styleList.shapeStyles[subpart]) {
var s=this.styleList.shapeStyles[subpart];
@@ -66,13 +70,13 @@ declare("iD.renderer.WayUI", [iD.renderer.EntityUI], {
}
// Casing
if (s.casing_width) {
if (s.casing_width) {
this.recordSprite(this.targetGroup('casing').createPolyline(coords).setStroke(s.casingStyler()));
maxwidth=Math.max(maxwidth,s.width+s.casing_width*2);
drawn=true;
}
}
// Text label on path
if (this.styleList.textStyles[subpart]) {
var t=this.styleList.textStyles[subpart];
@@ -96,26 +100,26 @@ declare("iD.renderer.WayUI", [iD.renderer.EntityUI], {
if (drawn) {
var hit=this.recordSprite(this.targetGroup('hit').createPolyline(coords).setStroke( { width:maxwidth+8, color: [0,0,0,0] } ));
hit.source=this;
hit.connect("onclick" , lang.hitch(this,this.entityMouseEvent));
hit.connect("onmousedown" , lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseup" , lang.hitch(this,this.entityMouseEvent));
hit.connect("onclick", lang.hitch(this,this.entityMouseEvent));
hit.connect("onmousedown", lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseup", lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseenter", lang.hitch(this,this.entityMouseEvent));
hit.connect("onmouseleave", lang.hitch(this,this.entityMouseEvent));
}
// Draw nodes
for (i=0; i<way.length(); i++) {
for (i=0; i<way.nodes.length; i++) {
var node=way.nodes[i];
var sc=[];
if (tags[':shownodes']) { sc.push('selectedway'); }
if (tags[':shownodeshover']) { sc.push('hoverway'); }
if (node.parentWays().length>1) { sc.push('junction'); }
if (node.entity.parentWays().length>1) { sc.push('junction'); }
this.map.createUI(node,sc);
}
},
entityMouseEvent:function(event) {
this.inherited(arguments);
},
}
});
// ----------------------------------------------------------------------
+5 -3
View File
@@ -29,13 +29,15 @@ declare("iD.styleparser.Rule", null, {
test:function(entity,tags,zoom) {
// summary: Evaluate the Rule on the given entity, tags and zoom level.
// returns: true if the Rule passes, false if the conditions aren't fulfilled.
if (this.subject!='' && !entity.isType(this.subject)) { return false; }
if ((this.subject !== '') && (entity.entityType !== this.subject)) {
return false;
}
if (zoom<this.minZoom || zoom>this.maxZoom) { return false; }
var v=true; var i=0; var isAnd=this.isAnd;
array.forEach(this.conditions, function(condition) {
var r=condition.test(tags);
if (i==0) { v=r; }
if (i === 0) { v=r; }
else if (isAnd) { v=v && r; }
else { v = v || r; }
i++;
+9 -9
View File
@@ -62,20 +62,20 @@ declare("iD.styleparser.RuleChain", null, {
test:function(pos, entity, tags, zoom) {
// summary: Test a rule chain by running all the tests in reverse order.
if (this.rules.length==0) { return false; }
if (this.rules.length === 0) { return false; }
if (pos==-1) { pos=this.rules.length-1; }
var r=this.rules[pos];
var r = this.rules[pos];
if (!r.test(entity, tags, zoom)) { return false; }
if (pos==0) { return true; }
var o=entity.parentObjects();
for (var i=0; i<o.length; i++) {
if (pos === 0) { return true; }
var o = entity.entity.parentObjects();
for (var i = 0; i < o.length; i++) {
var p=o[i];
if (this.test(pos-1, p, p.getTagsHash(), zoom)) { return true; }
if (this.test(pos-1, p, p.tags, zoom)) { return true; }
}
return false;
},
}
});
+46 -46
View File
@@ -16,12 +16,12 @@ declare("iD.styleparser.RuleSet", null, {
// summary: An entire stylesheet in parsed form.
this.choosers=[];
},
registerCallback:function(callback) {
// summary: Set a callback function to be called when the CSS is loaded and parsed.
this.callback=callback;
},
getStyles:function(entity, tags, zoom) {
// summary: Find the styles for a given entity.
var sl=new iD.styleparser.StyleList();
@@ -35,13 +35,13 @@ declare("iD.styleparser.RuleSet", null, {
// summary: Load a MapCSS file from a URL, then throw it at the parser when it's loaded.
xhr.get({ url: url, load: lang.hitch(this, "parseCSS") });
},
parseCSS:function(css) {
// summary: Parse a CSS document into a set of StyleChoosers.
var previous=0; // what was the previous CSS word?
var sc=new iD.styleparser.StyleChooser(); // currently being assembled
this.choosers=[];
css=css.replace(/[\r\n]/g,""); // strip linebreaks because JavaScript doesn't have the /s modifier
css = css.replace(/[\r\n]/g,""); // strip linebreaks because JavaScript doesn't have the /s modifier
var o={};
while (css.length>0) {
@@ -139,12 +139,12 @@ declare("iD.styleparser.RuleSet", null, {
var o={};
var k, v;
// Create styles
var ss=new iD.styleparser.ShapeStyle() ;
var ps=new iD.styleparser.PointStyle() ;
var ts=new iD.styleparser.TextStyle() ;
var hs=new iD.styleparser.ShieldStyle();
var xs=new iD.styleparser.InstructionStyle();
// Create styles
var ss = new iD.styleparser.ShapeStyle();
var ps = new iD.styleparser.PointStyle();
var ts = new iD.styleparser.TextStyle();
var hs = new iD.styleparser.ShieldStyle();
var xs = new iD.styleparser.InstructionStyle();
var r=s.split(';');
var isEval={};
@@ -163,39 +163,39 @@ declare("iD.styleparser.RuleSet", null, {
if (t['z_index']) { sub=Number(t['z_index']); delete t['z_index']; }
ss.sublayer=ps.sublayer=ts.sublayer=hs.sublayer=sub;
xs.sublayer=10;
// Find "interactive" property - it's true unless explicitly set false
var inter=true;
if (t['interactive']) { inter=t['interactive'].match(this.FALSE) ? false : true; delete t['interactive']; }
ss.interactive=ps.interactive=ts.interactive=hs.interactive=xs.interactive=inter;
// Munge special values
// (we should stop doing this and do it in the style instead)
if (t['font_weight'] ) { t['font_bold' ] = t['font_weight' ].match(this.BOLD ) ? true : false; delete t['font_weight']; }
if (t['font_style'] ) { t['font_italic'] = t['font_style' ].match(this.ITALIC) ? true : false; delete t['font_style']; }
if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(this.UNDERLINE) ? true : false; delete t['text_decoration']; }
if (t['text_position'] ) { t['text_center'] = t['text_position' ].match(this.CENTER) ? true : false; delete t['text_position']; }
if (t['text_transform']) {
if (t['text_transform'].match(this.CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; }
delete t['text_transform'];
}
// Munge special values
// (we should stop doing this and do it in the style instead)
if (t['font_weight'] ) { t['font_bold' ] = t['font_weight' ].match(this.BOLD ) ? true : false; delete t['font_weight']; }
if (t['font_style'] ) { t['font_italic'] = t['font_style' ].match(this.ITALIC) ? true : false; delete t['font_style']; }
if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(this.UNDERLINE) ? true : false; delete t['text_decoration']; }
if (t['text_position'] ) { t['text_center'] = t['text_position' ].match(this.CENTER) ? true : false; delete t['text_position']; }
if (t['text_transform']) {
if (t['text_transform'].match(this.CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; }
delete t['text_transform'];
}
// Assign each property to the appropriate style
for (a in t) {
// Parse properties
// ** also do units, e.g. px/pt/m
if (a.match(this.COLOR)) { v = this.parseCSSColor(t[a]); }
else { v = t[a]; }
// Set in styles
if (ss.has(a)) { ss.setPropertyFromString(a,v,isEval[a]); }
else if (ps.has(a)) { ps.setPropertyFromString(a,v,isEval[a]); }
else if (ts.has(a)) { ts.setPropertyFromString(a,v,isEval[a]); }
else if (hs.has(a)) { hs.setPropertyFromString(a,v,isEval[a]); }
else { console.log(a+" not found"); }
}
// Assign each property to the appropriate style
for (a in t) {
// Parse properties
// ** also do units, e.g. px/pt/m
if (a.match(this.COLOR)) { v = this.parseCSSColor(t[a]); }
else { v = t[a]; }
// Add each style to list
// Set in styles
if (ss.has(a)) { ss.setPropertyFromString(a,v,isEval[a]); }
else if (ps.has(a)) { ps.setPropertyFromString(a,v,isEval[a]); }
else if (ts.has(a)) { ts.setPropertyFromString(a,v,isEval[a]); }
else if (hs.has(a)) { hs.setPropertyFromString(a,v,isEval[a]); }
else { console.log(a+" not found"); }
}
// Add each style to list
if (ss.edited) { styles.push(ss); }
if (ps.edited) { styles.push(ps); }
if (ts.edited) { styles.push(ts); }
@@ -203,7 +203,7 @@ declare("iD.styleparser.RuleSet", null, {
if (xs.edited) { styles.push(xs); }
return styles;
},
parseZoom:function(s) {
var o={};
if ((o=this.ZOOM_MINMAX.exec(s))) { return [o[1],o[2]]; }
@@ -250,7 +250,7 @@ declare("iD.styleparser.RuleSet", null, {
}
return 0;
},
// Regular expression tests and other constants
WHITESPACE :/^\s+/,
@@ -287,16 +287,16 @@ declare("iD.styleparser.RuleSet", null, {
SET_TAG_EVAL :/^\s*set\s+(\S+)\s*=\s*eval\s*\(\s*'(.+?)'\s*\)\s*$/i,
SET_TAG :/^\s*set\s+(\S+)\s*=\s*(.+?)\s*$/i,
SET_TAG_TRUE :/^\s*set\s+(\S+)\s*$/i,
EXIT :/^\s*exit\s*$/i,
EXIT :/^\s*exit\s*$/i,
oZOOM: 2,
oGROUP: 3,
oCONDITION: 4,
oOBJECT: 5,
oDECLARATION: 6,
oSUBPART: 7,
oZOOM: 2,
oGROUP: 3,
oCONDITION: 4,
oOBJECT: 5,
oDECLARATION: 6,
oSUBPART: 7,
DASH: /\-/g,
DASH: /\-/g,
COLOR: /color$/,
BOLD: /^bold$/i,
ITALIC: /^italic|oblique$/i,
+2 -2
View File
@@ -211,13 +211,13 @@ declare("iD.styleparser.TextStyle", [iD.styleparser.Style], {
return {
decoration: this.font_underline ? 'underline' : 'none',
align: 'middle',
text: _text,
text: _text
};
},
fillStyler:function() {
// not implemented yet
return this.dojoColor(0,1);
},
}
// getTextFormat, getHaloFilter, writeNameLabel
});
+1 -1
View File
@@ -20,7 +20,7 @@ declare("iD.styleparser.StyleChooser", null, {
this.ruleChains=[new iD.styleparser.RuleChain()];
this.styles=[];
},
currentChain:function() {
return this.ruleChains[this.ruleChains.length-1];
},
+14 -16
View File
@@ -8,32 +8,31 @@ define(['dojo/_base/declare'], function(declare){
declare("iD.styleparser.StyleList", null, {
shapeStyles:{},
textStyles:{},
pointStyles:{},
shieldStyles:{},
maxwidth:0,
subparts:[], // List of subparts used in this StyleList
validAt:-1, // Zoom level this is valid at (or -1 at all levels - saves recomputing)
shapeStyles: {},
textStyles: {},
pointStyles: {},
shieldStyles: {},
maxwidth: 0,
subparts: [], // List of subparts used in this StyleList
validAt: -1, // Zoom level this is valid at (or -1 at all levels - saves recomputing)
constructor:function() {
// summary: A StyleList object is the full list of all styles applied to
// a drawn entity (i.e. node/way). Each array element applies to that
// sublayer (z-index). If there is no element, nothing is drawn on that sublayer.
// StyleLists are created by StyleChooser.getStyles.
this.shapeStyles={};
this.textStyles={};
this.pointStyles={};
this.shieldStyles={};
this.subparts=[];
},
hasStyles:function() {
// summary: Does this StyleList contain any styles?
return ( this.hasShapeStyles() || this.hasTextStyles() || this.hasPointStyles() || this.hasShieldStyles() );
},
hasFills:function() {
// summary: Does this StyleList contain any styles with a fill?
for (var s in this.shapeStyles) {
@@ -71,11 +70,10 @@ declare("iD.styleparser.StyleList", null, {
return str;
},
hasShapeStyles:function() { for (var a in shapeStyles ) { return true; }; return false; },
hasTextStyles:function() { for (var a in textStyles ) { return true; }; return false; },
hasPointStyles:function() { for (var a in pointStyles ) { return true; }; return false; },
hasShieldStyles:function() { for (var a in shieldStyles) { return true; }; return false; },
hasShapeStyles:function() { for (var a in shapeStyles ) { return true; } return false; },
hasTextStyles:function() { for (var a in textStyles ) { return true; } return false; },
hasPointStyles:function() { for (var a in pointStyles ) { return true; } return false; },
hasShieldStyles:function() { for (var a in shieldStyles) { return true; } return false; }
});
// ----------------------------------------------------------------------
+2
View File
File diff suppressed because one or more lines are too long
-16
View File
@@ -1,16 +0,0 @@
/**
* Copyright 2010 Tim Down.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v<r;++v){u[t+v]=s[v]}}};function a(t){var r;if(typeof t=="string"){return t}else{if(typeof t.hashCode==p){r=t.hashCode();return(typeof r=="string")?r:a(r)}else{if(typeof t.toString==p){return t.toString()}else{try{return String(t)}catch(s){return Object.prototype.toString.call(t)}}}}}function g(r,s){return r.equals(s)}function e(r,s){return(typeof s.equals==p)?s.equals(r):(r===s)}function c(r){return function(s){if(s===null){throw new Error("null is not a valid "+r)}else{if(typeof s=="undefined"){throw new Error(r+" must not be undefined")}}}}var q=c("key"),l=c("value");function d(u,s,t,r){this[0]=u;this.entries=[];this.addEntry(s,t);if(r!==null){this.getEqualityFunction=function(){return r}}}var h=0,j=1,f=2;function o(r){return function(t){var s=this.entries.length,v,u=this.getEqualityFunction(t);while(s--){v=this.entries[s];if(u(t,v[0])){switch(r){case h:return true;case j:return v;case f:return[s,v[1]]}}}return false}}function k(r){return function(u){var v=u.length;for(var t=0,s=this.entries.length;t<s;++t){u[v+t]=this.entries[t][r]}}}d.prototype={getEqualityFunction:function(r){return(typeof r.equals==p)?g:e},getEntryForKey:o(j),getEntryAndIndexForKey:o(f),removeEntryForKey:function(s){var r=this.getEntryAndIndexForKey(s);if(r){n(this.entries,r[0]);return r[1]}return null},addEntry:function(r,s){this.entries[this.entries.length]=[r,s]},keys:k(0),values:k(1),getEntries:function(s){var u=s.length;for(var t=0,r=this.entries.length;t<r;++t){s[u+t]=this.entries[t].slice(0)}},containsKey:o(h),containsValue:function(s){var r=this.entries.length;while(r--){if(s===this.entries[r][1]){return true}}return false}};function m(s,t){var r=s.length,u;while(r--){u=s[r];if(t===u[0]){return r}}return null}function i(r,s){var t=r[s];return(t&&(t instanceof d))?t:null}function b(t,r){var w=this;var v=[];var u={};var x=(typeof t==p)?t:a;var s=(typeof r==p)?r:null;this.put=function(B,C){q(B);l(C);var D=x(B),E,A,z=null;E=i(u,D);if(E){A=E.getEntryForKey(B);if(A){z=A[1];A[1]=C}else{E.addEntry(B,C)}}else{E=new d(D,B,C,s);v[v.length]=E;u[D]=E}return z};this.get=function(A){q(A);var B=x(A);var C=i(u,B);if(C){var z=C.getEntryForKey(A);if(z){return z[1]}}return null};this.containsKey=function(A){q(A);var z=x(A);var B=i(u,z);return B?B.containsKey(A):false};this.containsValue=function(A){l(A);var z=v.length;while(z--){if(v[z].containsValue(A)){return true}}return false};this.clear=function(){v.length=0;u={}};this.isEmpty=function(){return !v.length};var y=function(z){return function(){var A=[],B=v.length;while(B--){v[B][z](A)}return A}};this.keys=y("keys");this.values=y("values");this.entries=y("getEntries");this.remove=function(B){q(B);var C=x(B),z,A=null;var D=i(u,C);if(D){A=D.removeEntryForKey(B);if(A!==null){if(!D.entries.length){z=m(v,C);n(v,z);delete u[C]}}}return A};this.size=function(){var A=0,z=v.length;while(z--){A+=v[z].entries.length}return A};this.each=function(C){var z=w.entries(),A=z.length,B;while(A--){B=z[A];C(B[0],B[1])}};this.putAll=function(H,C){var B=H.entries();var E,F,D,z,A=B.length;var G=(typeof C==p);while(A--){E=B[A];F=E[0];D=E[1];if(G&&(z=w.get(F))){D=C(F,z,D)}w.put(F,D)}};this.clone=function(){var z=new b(t,r);z.putAll(w);return z}}return b})();
+5
View File
File diff suppressed because one or more lines are too long
+17 -9
View File
@@ -6,7 +6,6 @@
Based heavily on:
MapCSS demonstration stylesheet
Richard Fairhurst, October 2009
*/
/* A set of fairly standard rules.
@@ -20,13 +19,13 @@ way[highway=primary],way[highway=primary_link],
way[highway=secondary],way[highway=secondary_link],
way[highway=tertiary],way[highway=tertiary_link],
way[highway=unclassified],
way[highway=residential] { text: name; text-color: black; font-size: 7; text-position: line;}
way[highway=residential] { text: name; text-color: black; font-size: 6; text-position: line;}
way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #809BC0; width: 7; casing-color: black; casing-width: 1; }
way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #7FC97F; width: 7; casing-color: black; casing-width: 1; }
way[highway=primary],way[highway=primary_link] { z-index: 8; color: #E46D71; width: 7; casing-color: black; casing-width: 1; }
way[highway=primary],way[highway=primary_link] { z-index: 8; color: #E46D71; width: 9; casing-color: black; casing-width: 1; }
way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #FDBF6F; width: 7; casing-width: 1; }
way[highway=tertiary],way[highway=unclassified] { z-index: 6; color: #FEFECB; width: 5; casing-width: 1; }
way[highway=residential] { z-index: 5; color: #E8E8E8; width: 5; casing-color: gray; casing-width: 1; }
way[highway=residential] { z-index: 5; color: #E8E8E8; width: 6; casing-color: gray; casing-width: 1; }
way[highway=service] { color: white; width: 3; casing-width: 1; }
/* Pedestrian precincts need to be treated carefully. Only closed-loops with an explicit
@@ -40,13 +39,22 @@ way[highway=bridleway] { z-index:9; color: #996644; width: 2; dashes: 4, 2, 2, 2
way[highway=track] { color: #996644; width: 2; dashes: 4, 2; }
way[highway=path] { color: lightgreen; width: 2; dashes: 2, 2; }
way[waterway=river], way[waterway=canal] { color: blue; width: 2; text:name; text-color:blue; font-size:9; text-position: offset; text-offset: 7;}
way[waterway=river], way[waterway=canal] {
casing-width: 1;
casing-color: #6eafd4;
color: #10539a;
width: 3;
text:name;
text-color:blue;
font-size:7;
text-position: offset;
text-offset: -5;
}
way[barrier] {color: #000000; width: 1}
/* Fills can be solid colour or bitmap images */
way[natural] :area { color: #ADD6A5; width: 1; fill-color: #ADD6A5; fill-opacity: 0.2; }
way[landuse] :area { color: #444444; width: 2; fill-color: #444444; fill-opacity: 0.3; }
way[amenity],way[shop] :area { color: #ADCEB5; width: 1; fill-color: #ADCEB5; fill-opacity: 0.2; }
@@ -96,7 +104,7 @@ node[amenity=school] { icon-image: icons/school.png; icon-width: 16; }
node[amenity=taxi] { icon-image: icons/taxi.png; icon-width: 16; }
node[amenity=telephone] { icon-image: icons/telephone.png; icon-width: 16; }
way node[barrier=gate], way node[highway=gate] { icon-image: icons/gate.png; icon-width: 7; }
/* We can stack styles at different z-index (depth) */
way[railway=rail]
@@ -110,7 +118,7 @@ way[railway=subway]
way[bridge=yes]
{ z-index: 4; color: white; width: eval('_width+3'); }
{ z-index: 3; color: black; width: eval('_width+6'); }
/* Tunnel */
way[tunnel=yes]
{ z-index: 4; color: white; width: eval('_width+2'); }
@@ -138,7 +146,7 @@ node !:drawn :poi { z-index: 2; icon-image: circle; icon-width: 4; color: green;
node :hoverway { z-index: 9; icon-image: square; icon-width: 7; color: blue; layer: 5; }
node::highlight :selected { z-index: 1; icon-image: square; icon-width: eval('_width+10'); color: yellow; }
/* Descendant selectors provide an easy way to style relations: this example means "any way
which is part of a relation whose type=route". */
+63
View File
@@ -0,0 +1,63 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.2.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.2.0/jasmine.css">
<script type="text/javascript" src="lib/jasmine-1.2.0/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.2.0/jasmine-html.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="../js/lib/underscore-min.js"></script>
<script type="text/javascript" src="../js/lib/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="../js/iD/Util.js"></script>
<script type="text/javascript" src="../js/iD/Node.js"></script>
<script type="text/javascript" src="../js/iD/Relation.js"></script>
<script type="text/javascript" src="../js/iD/Entity.js"></script>
<script type="text/javascript" src="../js/iD/Way.js"></script>
<script type="text/javascript" src="../js/iD/Connection.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/Util.js"></script>
<script type="text/javascript" src="spec/Node.js"></script>
<script type="text/javascript" src="spec/Entity.js"></script>
<script type="text/javascript" src="spec/Relation.js"></script>
<script type="text/javascript" src="spec/Way.js"></script>
<script type="text/javascript" src="spec/Connection.js"></script>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2008-2011 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+616
View File
@@ -0,0 +1,616 @@
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = doc.location.search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'}),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};
+81
View File
@@ -0,0 +1,81 @@
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
describe('Connection', function() {
var c;
beforeEach(function() {
c = new iD.Connection();
});
it('is instantiated', function() {
expect(c).toBeTruthy();
});
});
+11
View File
@@ -0,0 +1,11 @@
describe('Entity', function() {
var entity;
beforeEach(function() {
entity = new iD.Entity();
});
it('has no entity type', function() {
expect(entity.entityType).toEqual('');
});
});
+31
View File
@@ -0,0 +1,31 @@
describe('Node', function() {
var node;
beforeEach(function() {
node = new iD.Node(null, 10, 38, -77);
});
it('is a node entity', function() {
expect(node.entityType).toEqual('node');
});
it('should be initialized with a proper ID, lat, and lon', function() {
expect(node.id).toEqual(10);
expect(node.lat).toEqual(38);
expect(node.lon).toEqual(-77);
});
it('reprojects a latp parameter', function() {
expect(node.latp).toBeCloseTo(41.1376);
node.project();
expect(node.latp).toBeCloseTo(41.1376);
});
it('knows if it is within a bounding box', function() {
expect(node.within(-90, 90, 90, -90)).toBeTruthy();
});
it('knows if it is without a bounding box', function() {
expect(node.within(-90, -85, 90, -90)).toBeFalsy();
});
});
+11
View File
@@ -0,0 +1,11 @@
describe('Relation Member', function() {
var rm;
beforeEach(function() {
rm = new iD.RelationMember();
});
it('is instantiated', function() {
expect(rm).toBeTruthy();
});
});
+13
View File
@@ -0,0 +1,13 @@
describe('Util', function() {
var util;
it('gives unique ids', function() {
var a = iD.Util.id(),
b = iD.Util.id(),
c = iD.Util.id(),
d = iD.Util.id();
expect(a === b).toEqual(false);
expect(b === c).toEqual(false);
expect(c === d).toEqual(false);
});
});
+27
View File
@@ -0,0 +1,27 @@
describe('Way', function() {
var way;
beforeEach(function() {
way = new iD.Way();
});
it('is a way', function() {
expect(way.entityType).toEqual('way');
});
it('has zero nodes by default', function() {
expect(way.nodes.length).toEqual(0);
});
it('is closed by default', function() {
expect(way.isClosed()).toEqual(true);
});
it('is a way when it has no nodes', function() {
expect(way.isType('way')).toEqual(true);
});
it('is also an area when it has no nodes', function() {
expect(way.isType('area')).toEqual(true);
});
});