Merge branch 'master' into presets

Conflicts:
	js/id/util.js
This commit is contained in:
Tom MacWright
2013-01-31 11:35:29 -05:00
47 changed files with 3061 additions and 625 deletions
+151 -112
View File
@@ -246,7 +246,6 @@ form.hide {
button {
line-height:20px;
position: relative;
border:0;
color:#222;
background: white;
@@ -346,16 +345,16 @@ button.save .count {
button.save.has-count .count {
display: block;
position: absolute;
left: 115%;
top: 0;
bottom: 0;
background: rgba(255,255,255,.5);
background: rgba(255, 255, 255, .5);
color: #333;
padding: 10px;
height: 30px;
line-height: 12px;
border-radius: 4px;
margin: auto;
margin-left: 8.3333%;
}
button.save.has-count .count::before {
@@ -929,50 +928,6 @@ div.typeahead a:first-child {
left:0px; right:0px; top:0px; bottom:0px;
}
.commit-modal .user-info {
display: inline-block;
}
.commit-modal .commit-info {
margin-top: 10px;
}
.commit-modal .user-info img {
float: left;
}
.commit-modal h3 small.count {
margin-right: 10px;
text-align: center;
float: left;
height: 12px;
min-width: 12px;
font-size:12px;
line-height: 12px;
border-radius:24px;
padding:5px;
background:#7092ff;
color:#fff;
}
.commit-modal .changeset-list {
overflow: auto;
border:1px solid #ccc;
background:#fff;
max-height: 160px;
}
.commit-modal .warning-section .changeset-list {
margin-right: 20px;
overflow-x: visible;
}
.commit-section.modal-section {
padding-bottom: 0;
}
.commit-section.modal-section:last-child { padding-bottom: 20px;}
.modal-section {
padding: 20px;
}
@@ -1007,6 +962,56 @@ div.typeahead a:first-child {
display:none;
}
.loading-modal {
text-align: center;
}
/* Commit Modal
------------------------------------------------------- */
.commit-modal .user-info {
display: inline-block;
}
.commit-modal .commit-info {
margin-top: 10px;
}
.commit-modal .user-info img {
float: left;
}
.commit-modal h3 small.count {
margin-right: 10px;
text-align: center;
float: left;
height: 12px;
min-width: 12px;
font-size:12px;
line-height: 12px;
border-radius:24px;
padding:5px;
background:#7092ff;
color:#fff;
}
.commit-modal .changeset-list {
overflow: auto;
border:1px solid #ccc;
background:#fff;
max-height: 160px;
}
.commit-modal .warning-section .changeset-list button {
float: right;
}
.commit-section.modal-section {
padding-bottom: 0;
}
.commit-section.modal-section:last-child { padding-bottom: 20px;}
.changeset-list li {
border-top:1px solid #ccc;
padding:5px 10px;
@@ -1029,10 +1034,6 @@ div.typeahead a:first-child {
font:normal 12px/20px 'Helvetica Neue', Arial, sans-serif;
}
.loading-modal {
text-align: center;
}
/* Success
------------------------------------------------------- */
a.success-action {
@@ -1051,7 +1052,8 @@ a.success-action {
text-align:center;
}
.notice .notice-inner {
.notice .zoom-to {
width:100%;
height: 40px;
border-radius: 5px;
line-height: 40px;
@@ -1060,22 +1062,27 @@ a.success-action {
opacity: 0.9;
}
.notice .notice-inner .zoom-to {
width:40px;
height:40px;
.notice .zoom-to:hover {
background: #bde5aa;
}
.notice .zoom-to .icon {
margin-top:10px;
margin-right:10px;
}
.icon.zoom-in-invert {
background-position: -240px -40px;
}
/* Tooltips
------------------------------------------------------- */
.tooltip {
white-space: normal;
width: 200px;
position: absolute;
left: 0; right: 0; margin: auto;
z-index: -1000;
height: 0;
padding: 5px;
opacity: 0;
display: block;
}
@@ -1087,98 +1094,130 @@ a.success-action {
}
.tooltip.top {
margin-top: -5px;
margin-top: -10px;
text-align: center;
}
.tooltip.right {
margin-left: 5px;
margin-left: 10px;
text-align: left;
}
.tooltip.bottom {
margin-top: 5px;
margin-top: 10px;
text-align: center;
}
.tooltip.left {
margin-left: -5px;
margin-left: -10px;
text-align: right;
}
.tooltip-inner {
text-align: left;
width: 200px;
font-size: 11px;
font-weight: bold;
line-height: 20px;
padding: 5px 10px;
color: #333;
background-color: white;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
color: #333;
display: inline-block;
padding: 5px 10px;
font-size: 11px;
font-weight: bold;
line-height: 20px;
background-color: white;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.tooltip.top .tooltip-arrow {
bottom: 0;
left: 50%;
margin-left: -5px;
border-top-color: white;
border-width: 5px 5px 0;
bottom: -5px;
left: 50%;
margin-left: -5px;
border-top-color: white;
border-width: 5px 5px 0;
}
.tooltip.right .tooltip-arrow {
top: 50%;
left: 0;
margin-top: -5px;
border-right-color: white;
border-width: 5px 5px 5px 0;
top: 50%;
left: -5px;
margin-top: -5px;
border-right-color: white;
border-width: 5px 5px 5px 0;
}
.tooltip.left .tooltip-arrow {
top: 50%;
right: 0;
margin-top: -5px;
border-left-color: white;
border-width: 5px 0 5px 5px;
top: 50%;
right: 5px;
margin-top: -5px;
border-left-color: white;
border-width: 5px 0 5px 5px;
}
.tooltip.bottom .tooltip-arrow {
top: 0;
left: 50%;
margin-left: -5px;
border-bottom-color: white;
border-width: 0 5px 5px;
top: -5px;
left: 50%;
margin-left: -5px;
border-bottom-color: white;
border-width: 0 5px 5px;
}
.Browse .tooltip {
left: -20px !important; }
.Browse .tooltip .tooltip-arrow {
left: 30px;
left: 60px;
}
.tooltip .keyhint-wrap {
padding: 5px 0 5px 0;
}
.tooltip .keyhint {
float: right;
background: #eee;
display: block;
color: #222;
font-size: 10px;
padding: 0 4px;
background:#aaa;
color:#fff;
padding: 0px 7px;
text-transform: uppercase;
font-weight: bold;
display: inline-block;
border-radius: 2px;
margin-left: 4px;
border: 1px solid #CCC;
position: relative;
z-index: 1;
text-align: left;
clear: both;
}
.tooltip .keyhint .keyhint-label{
display: inline-block;
}
.tooltip .keyhint::after {
content: "";
position: absolute;
border-radius: 2px;
height: 10px;
width: 100%;
z-index: 0;
bottom: -4px;
left: -1px;
border: 1px solid #CCC;
border-top: 0;
}
.tail {
pointer-events:none;
position: absolute;
background: rgba(255, 255, 255, 0.7);
max-width: 250px;
margin-top: -15px;
padding: 5px;
-webkit-border-radius: 4px;
pointer-events:none;
position: absolute;
background: rgba(255, 255, 255, 0.7);
max-width: 250px;
margin-top: -15px;
padding: 5px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
+3 -3
View File
@@ -39,8 +39,8 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="230.7911"
inkscape:cy="190.13176"
inkscape:cx="332.2911"
inkscape:cy="175.13176"
inkscape:document-units="px"
inkscape:current-layer="layer12"
showgrid="false"
@@ -733,7 +733,7 @@
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<path
style="opacity:0.5;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.79999375;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.79999375000000006;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="m 416.5,3.9999947 c -2.48528,0 -4.5,2.0147207 -4.5,4.5 0,0.7234907 0.19662,1.3943635 0.50001,2.0000053 L 409,13.999995 l 0,1.999999 2.00001,0 3.49999,-3.499997 c 0.60565,0.303377 1.27651,0.499995 2,0.499995 2.48528,0 4.5,-2.014712 4.5,-4.4999973 0,-2.4852793 -2.01472,-4.5 -4.5,-4.5 z m 0,1.9999993 c 1.38071,0 2.5,1.1192914 2.5,2.5000007 0,1.3807157 -1.11929,2.5000003 -2.5,2.5000003 -1.38071,0 -2.5,-1.1192846 -2.5,-2.5000003 0,-1.3807093 1.11929,-2.5000007 2.5,-2.5000007 z"
id="path47528"
inkscape:connector-curvature="0" />

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

+1
View File
@@ -75,6 +75,7 @@
<script src='js/id/ui/preset.js'></script>
<script src='js/id/actions.js'></script>
<script src="js/id/actions/add_midpoint.js"></script>
<script src='js/id/actions/add_node.js'></script>
<script src='js/id/actions/add_way.js'></script>
<script src='js/id/actions/add_way_node.js'></script>
+11
View File
@@ -0,0 +1,11 @@
iD.actions.AddMidpoint = function(midpoint, node) {
return function(graph) {
graph = graph.replace(node.move(midpoint.loc));
midpoint.ways.forEach(function(way) {
graph = graph.replace(graph.entity(way.id).addNode(node.id, way.index));
});
return graph;
};
};
+9 -22
View File
@@ -1,25 +1,17 @@
iD.behavior.AddWay = function(mode) {
var map = mode.map,
history = mode.history,
controller = mode.controller,
event = d3.dispatch('startFromNode', 'startFromWay', 'start'),
draw;
function add(datum) {
if (datum.type === 'node') {
event.startFromNode(datum);
} else if (datum.type === 'way') {
var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map);
event.startFromWay(datum, choice.loc, choice.index);
} else if (datum.midpoint) {
var way = history.graph().entity(datum.way);
event.startFromWay(way, datum.loc, datum.index);
} else {
event.start(map.mouseCoordinates());
}
}
event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'),
draw = iD.behavior.Draw(map);
var addWay = function(surface) {
draw.on('click', event.start)
.on('clickWay', event.startFromWay)
.on('clickNode', event.startFromNode)
.on('clickMidpoint', event.startFromMidpoint)
.on('cancel', addWay.cancel)
.on('finish', addWay.cancel);
map.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
@@ -43,10 +35,5 @@ iD.behavior.AddWay = function(mode) {
controller.exit();
};
draw = iD.behavior.Draw()
.on('add', add)
.on('cancel', addWay.cancel)
.on('finish', addWay.cancel);
return d3.rebind(addWay, event, 'on');
};
+12 -19
View File
@@ -1,29 +1,21 @@
iD.behavior.DragMidpoint = function(mode) {
var history = mode.history,
projection = mode.map.projection,
behavior = iD.behavior.drag()
projection = mode.map.projection;
var behavior = iD.behavior.drag()
.delegate(".midpoint")
.origin(function(d) {
return projection(d.loc);
})
.on('start', function(d) {
var w, nds;
d.node = iD.Node({loc: d.loc});
var args = [iD.actions.AddNode(d.node)];
for (var i = 0; i < d.ways.length; i++) {
w = d.ways[i], nds = w.nodes;
for (var j = 0; j < nds.length; j++) {
if ((nds[j] === d.nodes[0] && nds[j + 1] === d.nodes[1]) ||
(nds[j] === d.nodes[1] && nds[j + 1] === d.nodes[0])) {
args.push(iD.actions.AddWayNode(w.id, d.node.id, j + 1));
}
}
}
history.perform.apply(history, args);
var node = d3.selectAll('.node.vertex')
.filter(function(data) { return data.id === d.node.id; });
behavior.target(node.node(), node.datum());
var node = iD.Node();
history.perform(iD.actions.AddMidpoint(d, node));
var vertex = d3.selectAll('.vertex')
.filter(function(data) { return data.id === node.id; });
behavior.target(vertex.node(), vertex.datum());
})
.on('move', function(d) {
d3.event.sourceEvent.stopPropagation();
@@ -33,7 +25,8 @@ iD.behavior.DragMidpoint = function(mode) {
.on('end', function() {
history.replace(
iD.actions.Noop(),
'added a node to a way');
'Added a node to a way.');
});
return behavior;
};
+16 -3
View File
@@ -1,5 +1,5 @@
iD.behavior.Draw = function () {
var event = d3.dispatch('move', 'add', 'undo', 'cancel', 'finish'),
iD.behavior.Draw = function(map) {
var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'),
keybinding = d3.keybinding('draw'),
down, surface, hover;
@@ -26,7 +26,20 @@ iD.behavior.Draw = function () {
}
function click() {
event.add(datum());
var d = datum();
if (d.type === 'way') {
var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map);
event.clickWay(d, choice.loc, choice.index);
} else if (d.type === 'node') {
event.clickNode(d);
} else if (d.type === 'midpoint') {
event.clickMidpoint(d);
} else {
event.click(map.mouseCoordinates());
}
}
function keydown() {
+44 -41
View File
@@ -1,11 +1,11 @@
iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var map = mode.map,
history = mode.history,
controller = mode.controller,
event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'),
way = mode.history.graph().entity(wayId),
finished = false,
draw;
annotation = 'Added to a way.',
draw = iD.behavior.Draw(map);
var node = iD.Node({loc: map.mouseCoordinates()}),
nodeId = node.id;
@@ -17,7 +17,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
function move(datum) {
var loc = map.mouseCoordinates();
if (datum.type === 'node' || datum.midpoint) {
if (datum.type === 'node' || datum.type === 'midpoint') {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc;
@@ -26,29 +26,20 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
history.replace(iD.actions.MoveNode(nodeId, loc));
}
function add(datum) {
if (datum.id === headId) {
event.addHead(datum);
} else if (datum.id === tailId) {
event.addTail(datum);
} else if (datum.type === 'node' && datum.id !== nodeId) {
event.addNode(datum);
} else if (datum.type === 'way') {
var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map);
event.addWay(datum, choice.loc, choice.index);
} else if (datum.midpoint) {
var way = history.graph().entity(datum.way);
event.addWay(way, datum.loc, datum.index);
} else {
event.add(map.mouseCoordinates());
}
}
function undone() {
controller.enter(iD.modes.Browse());
}
var drawWay = function(surface) {
draw.on('move', move)
.on('click', drawWay.add)
.on('clickWay', drawWay.addWay)
.on('clickNode', drawWay.addNode)
.on('clickMidpoint', drawWay.addMidpoint)
.on('undo', history.undo)
.on('cancel', drawWay.cancel)
.on('finish', drawWay.finish);
map.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
@@ -80,6 +71,12 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
history.on('undone.draw', null);
};
drawWay.annotation = function(_) {
if (!arguments.length) return annotation;
annotation = _;
return drawWay;
};
function ReplaceTemporaryNode(newNode) {
return function(graph) {
return graph
@@ -88,10 +85,13 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
}
}
// Connect the way to an existing node and continue drawing.
drawWay.addNode = function(node, annotation) {
history.perform(
ReplaceTemporaryNode(node),
// Accept the current position of the temporary node and continue drawing.
drawWay.add = function(loc) {
var newNode = iD.Node({loc: loc});
history.replace(
iD.actions.AddNode(newNode),
ReplaceTemporaryNode(newNode),
annotation);
finished = true;
@@ -99,7 +99,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
};
// Connect the way to an existing way.
drawWay.addWay = function(way, loc, wayIndex, annotation) {
drawWay.addWay = function(way, loc, wayIndex) {
var newNode = iD.Node({loc: loc});
history.perform(
@@ -112,13 +112,23 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
controller.enter(mode);
};
// Accept the current position of the temporary node and continue drawing.
drawWay.add = function(loc, annotation) {
var newNode = iD.Node({loc: loc});
// Connect the way to an existing node and continue drawing.
drawWay.addNode = function(node) {
history.perform(
ReplaceTemporaryNode(node),
annotation);
history.replace(
iD.actions.AddNode(newNode),
ReplaceTemporaryNode(newNode),
finished = true;
controller.enter(mode);
};
// Add a midpoint, connect the way to it, and continue drawing.
drawWay.addMidpoint = function(midpoint) {
var node = iD.Node();
history.perform(
iD.actions.AddMidpoint(midpoint, node),
ReplaceTemporaryNode(node),
annotation);
finished = true;
@@ -143,18 +153,11 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) {
drawWay.cancel = function() {
history.perform(
d3.functor(baseGraph),
'cancelled drawing');
'Cancelled drawing.');
finished = true;
controller.enter(iD.modes.Browse());
};
draw = iD.behavior.Draw()
.on('move', move)
.on('add', add)
.on('undo', history.undo)
.on('cancel', drawWay.cancel)
.on('finish', drawWay.finish);
return d3.rebind(drawWay, event, 'on');
};
+1 -1
View File
@@ -125,7 +125,7 @@ iD.Connection = function() {
}
}
return iD.Graph(entities);
return entities;
}
function authenticated() {
+156 -69
View File
@@ -1,19 +1,30 @@
iD.Graph = function(entities, mutable) {
if (!(this instanceof iD.Graph)) return new iD.Graph(entities, mutable);
iD.Graph = function(other, mutable) {
if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable);
if (other instanceof iD.Graph) {
var base = other.base();
this.entities = _.assign(Object.create(base.entities), other.entities);
this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays);
this._parentRels = _.assign(Object.create(base.parentRels), other._parentRels);
this.inherited = true;
if (_.isArray(entities)) {
this.entities = {};
for (var i = 0; i < entities.length; i++) {
this.entities[entities[i].id] = entities[i];
}
} else {
this.entities = entities || {};
if (_.isArray(other)) {
var entities = {};
for (var i = 0; i < other.length; i++) {
entities[other[i].id] = other[i];
}
other = entities;
}
this.entities = Object.create({});
this._parentWays = Object.create({});
this._parentRels = Object.create({});
this.rebase(other || {});
}
this.transients = {};
this._parentWays = {};
this._parentRels = {};
this._childNodes = {};
this.getEntity = _.bind(this.entity, this);
if (!mutable) {
this.freeze();
@@ -38,51 +49,21 @@ iD.Graph.prototype = {
},
parentWays: function(entity) {
var ent, id, parents;
if (!this._parentWays.calculated) {
for (var i in this.entities) {
ent = this.entities[i];
if (ent && ent.type === 'way') {
for (var j = 0; j < ent.nodes.length; j++) {
id = ent.nodes[j];
parents = this._parentWays[id] = this._parentWays[id] || [];
if (parents.indexOf(ent) < 0) {
parents.push(ent);
}
}
}
}
this._parentWays.calculated = true;
}
return this._parentWays[entity.id] || [];
return _.map(this._parentWays[entity.id], this.getEntity);
},
isPoi: function(entity) {
return this.parentWays(entity).length === 0;
var parentWays = this._parentWays[entity.id];
return !parentWays || parentWays.length === 0;
},
isShared: function(entity) {
var parentWays = this._parentWays[entity.id];
return parentWays && parentWays.length > 1;
},
parentRelations: function(entity) {
var ent, id, parents;
if (!this._parentRels.calculated) {
for (var i in this.entities) {
ent = this.entities[i];
if (ent && ent.type === 'relation') {
for (var j = 0; j < ent.members.length; j++) {
id = ent.members[j].id;
parents = this._parentRels[id] = this._parentRels[id] || [];
if (parents.indexOf(ent) < 0) {
parents.push(ent);
}
}
}
}
this._parentRels.calculated = true;
}
return this._parentRels[entity.id] || [];
return _.map(this._parentRels[entity.id], this.getEntity);
},
childNodes: function(entity) {
@@ -97,30 +78,132 @@ iD.Graph.prototype = {
return (this._childNodes[entity.id] = nodes);
},
merge: function(graph) {
return this.update(function () {
_.defaults(this.entities, graph.entities);
});
base: function() {
return {
'entities': iD.util.getPrototypeOf(this.entities),
'parentWays': iD.util.getPrototypeOf(this._parentWays),
'parentRels': iD.util.getPrototypeOf(this._parentRels)
};
},
// Unlike other graph methods, rebase mutates in place. This is because it
// is used only during the history operation that merges newly downloaded
// data into each state. To external consumers, it should appear as if the
// graph always contained the newly downloaded data.
rebase: function(entities) {
var base = this.base(),
i, k, child, id, keys;
// Merging of data only needed if graph is the base graph
if (!this.inherited) {
for (i in entities) {
if (!base.entities[i]) {
base.entities[i] = entities[i];
this._updateCalculated(undefined, entities[i],
base.parentWays, base.parentRels);
}
}
}
keys = Object.keys(this._parentWays);
for (i = 0; i < keys.length; i++) {
child = keys[i];
if (base.parentWays[child]) {
for (k = 0; k < base.parentWays[child].length; k++) {
id = base.parentWays[child][k];
if (this.entity(id) && !_.contains(this._parentWays[child], id)) {
this._parentWays[child].push(id);
}
}
}
}
keys = Object.keys(this._parentRels);
for (i = 0; i < keys.length; i++) {
child = keys[i];
if (base.parentRels[child]) {
for (k = 0; k < base.parentRels[child].length; k++) {
id = base.parentRels[child][k];
if (this.entity(id) && !_.contains(this._parentRels[child], id)) {
this._parentRels[child].push(id);
}
}
}
}
},
// Updates calculated properties (parentWays, parentRels) for the specified change
_updateCalculated: function(oldentity, entity, parentWays, parentRels) {
parentWays = parentWays || this._parentWays;
parentRels = parentRels || this._parentRels;
var type = entity && entity.type || oldentity && oldentity.type,
removed, added, ways, rels, i;
if (type === 'way') {
// Update parentWays
if (oldentity && entity) {
removed = _.difference(oldentity.nodes, entity.nodes);
added = _.difference(entity.nodes, oldentity.nodes);
} else if (oldentity) {
removed = oldentity.nodes;
added = [];
} else if (entity) {
removed = [];
added = entity.nodes;
}
for (i = 0; i < removed.length; i++) {
parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id);
}
for (i = 0; i < added.length; i++) {
ways = _.without(parentWays[added[i]], entity.id);
ways.push(entity.id);
parentWays[added[i]] = ways;
}
} else if (type === 'node') {
} else if (type === 'relation') {
// Update parentRels
if (oldentity && entity) {
removed = _.difference(oldentity.members, entity.members);
added = _.difference(entity.members, oldentity);
} else if (oldentity) {
removed = oldentity.members;
added = [];
} else if (entity) {
removed = [];
added = entity.members;
}
for (i = 0; i < removed.length; i++) {
parentRels[removed[i].id] = _.without(parentRels[removed[i].id], oldentity.id);
}
for (i = 0; i < added.length; i++) {
rels = _.without(parentRels[added[i].id], entity.id);
rels.push(entity.id);
parentRels[added[i].id] = rels;
}
}
},
replace: function(entity) {
return this.update(function () {
this._updateCalculated(this.entities[entity.id], entity);
this.entities[entity.id] = entity;
});
},
remove: function(entity) {
return this.update(function () {
if (entity.created()) {
delete this.entities[entity.id];
} else {
this.entities[entity.id] = undefined;
}
this._updateCalculated(entity, undefined);
this.entities[entity.id] = undefined;
});
},
update: function() {
var graph = this.frozen ? iD.Graph(_.clone(this.entities), true) : this;
var graph = this.frozen ? iD.Graph(this, true) : this;
for (var i = 0; i < arguments.length; i++) {
arguments[i].call(graph, graph);
@@ -133,7 +216,6 @@ iD.Graph.prototype = {
this.frozen = true;
if (iD.debug) {
Object.freeze(this);
Object.freeze(this.entities);
}
@@ -153,9 +235,12 @@ iD.Graph.prototype = {
},
difference: function (graph) {
var result = [], entity, oldentity, id;
var result = [],
keys = Object.keys(this.entities),
entity, oldentity, id, i;
for (id in this.entities) {
for (i = 0; i < keys.length; i++) {
id = keys[i];
entity = this.entities[id];
oldentity = graph.entities[id];
if (entity !== oldentity) {
@@ -177,7 +262,9 @@ iD.Graph.prototype = {
}
}
for (id in graph.entities) {
keys = Object.keys(graph.entities);
for (i = 0; i < keys.length; i++) {
id = keys[i];
entity = graph.entities[id];
if (entity && !this.entities.hasOwnProperty(id)) {
result.push(id);
@@ -189,25 +276,25 @@ iD.Graph.prototype = {
},
modified: function() {
var result = [];
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && entity.modified()) result.push(id);
if (entity && base[id]) result.push(id);
});
return result;
},
created: function() {
var result = [];
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && entity.created()) result.push(id);
if (entity && !base[id]) result.push(id);
});
return result;
},
deleted: function() {
var result = [];
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (!entity) result.push(id);
if (!entity && base[id]) result.push(id);
});
return result;
}
+2 -2
View File
@@ -29,9 +29,9 @@ iD.History = function() {
return stack[index].graph;
},
merge: function (graph) {
merge: function (entities) {
for (var i = 0; i < stack.length; i++) {
stack[i].graph = stack[i].graph.merge(graph);
stack[i].graph.rebase(entities);
}
},
+4 -4
View File
@@ -22,7 +22,7 @@ window.iD = function(container) {
}
function hintprefix(x, y) {
return '<span class="keyhint">' + x + '</span> ' + y;
return '<span>' + y + '</span>' + '<div class="keyhint-wrap"><span class="keyhint"> ' + x + '</span></div>';
}
var m = container.append('div')
@@ -44,10 +44,10 @@ window.iD = function(container) {
.enter().append('button')
.attr('tabindex', -1)
.attr('class', function (mode) { return mode.title + ' add-button col3'; })
.call(bootstrap.tooltip().placement('bottom').html(true))
.attr('data-original-title', function (mode) {
return hintprefix(mode.key, mode.description);
})
.call(bootstrap.tooltip().placement('bottom').html(true))
.on('click.editor', function (mode) { controller.enter(mode); });
function disableTooHigh() {
@@ -207,12 +207,12 @@ window.iD = function(container) {
limiter.select('#undo')
.property('disabled', !undo)
.attr('data-original-title', hintprefix('⌘Z', undo))
.attr('data-original-title', hintprefix('⌘ + Z', undo))
.call(refreshTooltip);
limiter.select('#redo')
.property('disabled', !redo)
.attr('data-original-title', hintprefix('⌘Z', redo))
.attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo))
.call(refreshTooltip);
});
+21 -6
View File
@@ -15,11 +15,13 @@ iD.modes.AddArea = function() {
history = mode.history,
controller = mode.controller;
function startFromNode(node) {
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(way.id, node.id));
@@ -42,13 +44,25 @@ iD.modes.AddArea = function() {
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function start(loc) {
function startFromNode(node) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromMidpoint(midpoint) {
var graph = history.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(way.id, node.id));
@@ -57,9 +71,10 @@ iD.modes.AddArea = function() {
}
behavior = iD.behavior.AddWay(mode)
.on('startFromNode', startFromNode)
.on('start', start)
.on('startFromWay', startFromWay)
.on('start', start);
.on('startFromNode', startFromNode)
.on('startFromMidpoint', startFromMidpoint);
mode.map.surface.call(behavior);
mode.map.tail('Click on the map to start drawing an area, like a park, lake, or building.', true);
+33 -19
View File
@@ -15,6 +15,33 @@ iD.modes.AddLine = function() {
history = mode.history,
controller = mode.controller;
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function startFromWay(other, loc, index) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(other.id, node.id, index));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function startFromNode(node) {
var graph = history.graph(),
parent = graph.parentWays(node)[0],
@@ -37,27 +64,13 @@ iD.modes.AddLine = function() {
}
}
function startFromWay(other, loc, index) {
function startFromMidpoint(midpoint) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(other.id, node.id, index));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddNode(node),
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, node.id));
@@ -65,9 +78,10 @@ iD.modes.AddLine = function() {
}
behavior = iD.behavior.AddWay(mode)
.on('startFromNode', startFromNode)
.on('start', start)
.on('startFromWay', startFromWay)
.on('start', start);
.on('startFromNode', startFromNode)
.on('startFromMidpoint', startFromMidpoint);
mode.map.surface.call(behavior);
mode.map.tail('Click on the map to start drawing an road, path, or route.', true);
+16 -5
View File
@@ -16,22 +16,33 @@ iD.modes.AddPoint = function() {
map.tail('Click on the map to add a point.', true);
function add() {
var node = iD.Node({loc: map.mouseCoordinates()});
function add(loc) {
var node = iD.Node({loc: loc});
history.perform(
iD.actions.AddNode(node),
'added a point');
'Added a point.');
controller.enter(iD.modes.Select(node, true));
}
function addWay(way, loc, index) {
add(loc);
}
function addNode(node) {
add(node.loc);
}
function cancel() {
controller.exit();
}
behavior = iD.behavior.Draw()
.on('add', add)
behavior = iD.behavior.Draw(map)
.on('click', add)
.on('clickWay', addWay)
.on('clickNode', addNode)
.on('clickMidpoint', addNode)
.on('cancel', cancel)
.on('finish', cancel)
(surface);
+1 -1
View File
@@ -3,7 +3,7 @@ iD.modes.Browse = function() {
button: 'browse',
id: 'browse',
title: 'Browse',
description: 'Pan and zoom the map',
description: 'Pan and zoom the map.',
key: 'b'
};
+11 -23
View File
@@ -8,33 +8,21 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
mode.enter = function() {
var way = mode.history.graph().entity(wayId),
index = -1,
headId = way.nodes[way.nodes.length - 2],
tailId = way.first(),
annotation = way.isDegenerate() ? 'started an area' : 'continued an area';
tailId = way.first();
function addHeadTail() {
behavior.finish();
}
behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph)
.annotation(way.isDegenerate() ? 'started an area' : 'continued an area');
function addNode(node) {
behavior.addNode(node, annotation);
}
var addNode = behavior.addNode;
function addWay(way, loc, index) {
behavior.addWay(way, loc, index, annotation);
}
function add(loc) {
behavior.add(loc, annotation);
}
behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode, baseGraph)
.on('addHead', addHeadTail)
.on('addTail', addHeadTail)
.on('addNode', addNode)
.on('addWay', addWay)
.on('add', add);
behavior.addNode = function(node) {
if (node.id === headId || node.id === tailId) {
behavior.finish();
} else {
addNode(node);
}
};
mode.map.surface.call(behavior);
mode.map.tail('Click to add points to your area. Click the first point to finish the area.', true);
+10 -31
View File
@@ -9,41 +9,20 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
mode.enter = function() {
var way = mode.history.graph().entity(wayId),
index = (direction === 'forward') ? undefined : 0,
headId = (direction === 'forward') ? way.last() : way.first(),
tailId = (direction === 'forward') ? way.first() : way.last(),
annotation = way.isDegenerate() ? 'started a line' : 'continued a line';
headId = (direction === 'forward') ? way.last() : way.first();
function addHead() {
behavior.finish();
}
behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph)
.annotation(way.isDegenerate() ? 'Started a line.' : 'Continued a line.');
function addTail(node) {
// connect the way in a loop
if (way.nodes.length > 2) {
behavior.addNode(node, annotation);
var addNode = behavior.addNode;
behavior.addNode = function(node) {
if (node.id === headId) {
behavior.finish();
} else {
behavior.cancel();
addNode(node);
}
}
function addNode(node) {
behavior.addNode(node, annotation);
}
function addWay(way, loc, index) {
behavior.addWay(way, loc, index, annotation);
}
function add(loc) {
behavior.add(loc, annotation);
}
behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode, baseGraph)
.on('addHead', addHead)
.on('addTail', addTail)
.on('addNode', addNode)
.on('addWay', addWay)
.on('add', add);
};
mode.map.surface.call(behavior);
mode.map.tail('Click to add more points to the line. ' +
+2 -2
View File
@@ -18,7 +18,7 @@ iD.modes.MoveWay = function(wayId) {
history.perform(
iD.actions.Noop(),
'moved a way');
'Moved a way.');
function move() {
var p = d3.mouse(selection.node()),
@@ -29,7 +29,7 @@ iD.modes.MoveWay = function(wayId) {
history.replace(
iD.actions.MoveWay(wayId, delta, projection),
'moved a way');
'Moved a way.');
}
function finish() {
+2 -2
View File
@@ -14,7 +14,7 @@ iD.modes.Select = function(entity, initial) {
if (!_.isEqual(entity.tags, tags)) {
mode.history.perform(
iD.actions.ChangeEntityTags(d.id, tags),
'changed tags');
'Changed tags.');
}
}
@@ -116,7 +116,7 @@ iD.modes.Select = function(entity, initial) {
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWayNode(datum.id, node.id, choice.index),
'added a point to a road');
'Added a point to a road.');
d3.event.preventDefault();
d3.event.stopPropagation();
+2 -2
View File
@@ -10,12 +10,12 @@ iD.operations.Circularize = function(entityId, mode) {
if (geometry === 'line') {
history.perform(
action,
'made a line circular');
'Made a line circular.');
} else if (geometry === 'area') {
history.perform(
action,
'made an area circular');
'Made an area circular.');
}
};
+5 -5
View File
@@ -9,22 +9,22 @@ iD.operations.Delete = function(entityId, mode) {
if (geometry === 'vertex') {
history.perform(
iD.actions.DeleteNode(entityId),
'deleted a vertex');
'Deleted a vertex.');
} else if (geometry === 'point') {
history.perform(
iD.actions.DeleteNode(entityId),
'deleted a point');
'Deleted a point.');
} else if (geometry === 'line') {
history.perform(
iD.actions.DeleteWay(entityId),
'deleted a line');
'Deleted a line.');
} else if (geometry === 'area') {
history.perform(
iD.actions.DeleteWay(entityId),
'deleted an area');
'Deleted an area.');
}
};
@@ -41,7 +41,7 @@ iD.operations.Delete = function(entityId, mode) {
operation.id = "delete";
operation.key = "⌫";
operation.title = "Delete";
operation.description = "Remove this from the map";
operation.description = "Remove this from the map.";
return operation;
};
+2 -2
View File
@@ -4,7 +4,7 @@ iD.operations.Reverse = function(entityId, mode) {
var operation = function() {
history.perform(
iD.actions.ReverseWay(entityId),
'reversed a line');
'Reversed a line.');
};
operation.available = function() {
@@ -20,7 +20,7 @@ iD.operations.Reverse = function(entityId, mode) {
operation.id = "reverse";
operation.key = "V";
operation.title = "Reverse";
operation.description = "Make this way go in the opposite direction";
operation.description = "Make this way go in the opposite direction.";
return operation;
};
+2 -2
View File
@@ -3,7 +3,7 @@ iD.operations.Split = function(entityId, mode) {
action = iD.actions.SplitWay(entityId);
var operation = function() {
history.perform(action, 'split a way');
history.perform(action, 'Split a way.');
};
operation.available = function() {
@@ -20,7 +20,7 @@ iD.operations.Split = function(entityId, mode) {
operation.id = "split";
operation.key = "X";
operation.title = "Split";
operation.description = "Split this into two ways at this point";
operation.description = "Split this into two ways at this point.";
return operation;
};
+2 -2
View File
@@ -3,7 +3,7 @@ iD.operations.Unjoin = function(entityId, mode) {
action = iD.actions.UnjoinNode(entityId);
var operation = function() {
history.perform(action, 'unjoined lines');
history.perform(action, 'Unjoined lines.');
};
operation.available = function() {
@@ -20,7 +20,7 @@ iD.operations.Unjoin = function(entityId, mode) {
operation.id = "unjoin";
operation.key = "⇧-J";
operation.title = "Unjoin";
operation.description = "Disconnect these ways from each other";
operation.description = "Disconnect these ways from each other.";
return operation;
};
+70 -66
View File
@@ -1,4 +1,10 @@
iD.Background = function() {
var deviceRatio = (window.devicePixelRatio &&
window.devicePixelRatio === 2) ? 0.5 : 1;
// tileSize = (deviceRatio === 0.5) ? [128,128] : [256,256];
tileSize = [256, 256];
var tile = d3.geo.tile(),
projection,
cache = {},
@@ -13,7 +19,12 @@ iD.Background = function() {
'-o-transform-origin:0 0;' +
'-webkit-user-select: none;' +
'-webkit-user-drag: none;' +
'-moz-user-drag: none;';
'-moz-user-drag: none;' +
'opacity:0;';
function tileSizeAtZoom(d, z) {
return Math.ceil(tileSize[0] * Math.pow(2, z - d[2])) / tileSize[0];
}
function atZoom(t, distance) {
var power = Math.pow(2, distance);
@@ -21,108 +32,101 @@ iD.Background = function() {
Math.floor(t[0] * power),
Math.floor(t[1] * power),
t[2] + distance];
az.push(source(az));
return az;
}
function upZoom(t, distance) {
var az = atZoom(t, distance), tiles = [];
for (var x = 0; x < 2; x++) {
for (var y = 0; y < 2; y++) {
var up = [az[0] + x, az[1] + y, az[2]];
up.push(source(up));
tiles.push(up);
}
}
return tiles;
}
function tileSize(d, z) {
return Math.ceil(256 * Math.pow(2, z - d[2])) / 256;
}
function lookUp(d) {
for (var up = -1; up > -d[2]; up--) {
if (cache[atZoom(d, up)] !== false) return atZoom(d, up);
}
}
function uniqueBy(a, n) {
var o = [], seen = {};
for (var i = 0; i < a.length; i++) {
if (seen[a[i][n]] === undefined) {
o.push(a[i]);
seen[a[i][n]] = true;
}
}
return o;
}
function addSource(d) {
d.push(source(d));
return d;
}
// derive the tiles onscreen, remove those offscreen and position tiles
// correctly for the currentstate of `projection`
function background() {
var tiles = tile
var sel = this,
tiles = tile
.scale(projection.scale())
.scaleExtent(source.scaleExtent || [1, 17])
.translate(projection.translate())(),
requests = [],
scaleExtent = tile.scaleExtent(),
z = Math.max(Math.log(projection.scale()) / Math.log(2) - 8, 0),
rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))),
ts = 256 * Math.pow(2, z - rz),
rz = Math.max(scaleExtent[0],
Math.min(scaleExtent[1], Math.floor(z))),
ts = tileSize[0] * Math.pow(2, z - rz),
tile_origin = [
projection.scale() / 2 - projection.translate()[0],
projection.scale() / 2 - projection.translate()[1]],
ups = {};
projection.scale() / 2 - projection.translate()[1]];
tiles.forEach(function(d) {
if (cache[d] === true) {
d.push(source(d));
} else if (cache[d] === false &&
cache[atZoom(d, -1)] !== false &&
!ups[atZoom(d, -1)]) {
ups[atZoom(d, -1)] = true;
tiles.push(atZoom(d, -1));
} else if (cache[d] === undefined &&
lookUp(d)) {
var upTile = lookUp(d);
if (!ups[upTile]) {
ups[upTile] = true;
tiles.push(upTile);
}
} else if (cache[d] === undefined ||
cache[d] === false) {
upZoom(d, 1).forEach(function(u) {
if (cache[u] && !ups[u]) {
ups[u] = true;
tiles.push(u);
}
});
addSource(d);
requests.push(d);
if (!cache[d[3]] && lookUp(d)) {
requests.push(addSource(lookUp(d)));
}
});
var image = this
.selectAll('img')
.data(tiles, function(d) { return d; });
requests = uniqueBy(requests, 3);
function load(d) {
cache[d.slice(0, 3)] = true;
d3.select(this).on('load', null);
cache[d[3]] = true;
d3.select(this)
.on('load', null)
.transition()
.style('opacity', 1);
background.apply(sel);
}
function error(d) {
cache[d.slice(0, 3)] = false;
cache[d[3]] = false;
d3.select(this).on('load', null);
d3.select(this).remove();
background.apply(sel);
}
function imageTransform(d) {
var _ts = tileSize[0] * Math.pow(2, z - d[2]);
var scale = tileSizeAtZoom(d, z);
return 'translate(' +
(Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' +
(Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px)' +
'scale(' + scale + ',' + scale + ')';
}
var image = this
.selectAll('img')
.data(requests, function(d) { return d[3]; });
image.exit()
.style(transformProp, imageTransform)
.transition()
.style('opacity', 0)
.remove();
image.enter().append('img')
.attr('style', imgstyle)
.attr('src', function(d) { return d[3]; })
.on('error', error)
.on('load', load);
image.exit().remove();
image.style(transformProp, function(d) {
var _ts = 256 * Math.pow(2, z - d[2]);
var scale = tileSize(d, z);
return 'translate(' +
(Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' +
(Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px) scale(' + scale + ',' + scale + ')';
});
image.style(transformProp, imageTransform);
if (Object.keys(cache).length > 100) cache = {};
}
+6 -5
View File
@@ -64,7 +64,7 @@ iD.Map = function() {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (only[parent.id] === undefined) {
only[parent.id] = graph.entity(parent.id);
only[parent.id] = parent;
addParents(graph.parentRelations(parent));
}
}
@@ -92,7 +92,7 @@ iD.Map = function() {
all = _.compact(_.values(only));
filter = function(d) {
if (d.midpoint) {
if (d.type === 'midpoint') {
for (var i = 0; i < d.ways.length; i++) {
if (d.ways[i].id in only) return true;
}
@@ -112,7 +112,7 @@ iD.Map = function() {
.call(areas, graph, all, filter)
.call(multipolygons, graph, all, filter)
.call(midpoints, graph, all, filter)
.call(labels, graph, all, filter, dimensions);
.call(labels, graph, all, filter, dimensions, !difference);
}
dispatch.drawn(map);
}
@@ -123,7 +123,7 @@ iD.Map = function() {
function connectionLoad(err, result) {
history.merge(result);
redraw(Object.keys(result.entities));
redraw(Object.keys(result));
}
function zoomPan() {
@@ -165,7 +165,8 @@ iD.Map = function() {
}
function resetTransform() {
if (!surface.style(transformProp)) return false;
var prop = surface.style(transformProp);
if (!prop || prop === 'none') return false;
surface.style(transformProp, '');
tilegroup.style(transformProp, '');
return true;
+24 -9
View File
@@ -156,7 +156,7 @@ iD.svg.Labels = function(projection) {
for (var i = 0; i < nodes.length - 1; i++) {
var current = segmentLength(i);
var portion;
if (!start && sofar + current > from) {
if (!start && sofar + current >= from) {
portion = (from - sofar) / current;
start = [
nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
@@ -164,7 +164,7 @@ iD.svg.Labels = function(projection) {
];
i0 = i + 1;
}
if (!end && sofar + current > to) {
if (!end && sofar + current >= to) {
portion = (to - sofar) / current;
end = [
nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
@@ -183,14 +183,26 @@ iD.svg.Labels = function(projection) {
}
return function drawLabels(surface, graph, entities, filter, dimensions) {
var rtree = new RTree(),
rectangles = {};
return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) {
var rtree = new RTree();
var hidePoints = !d3.select('.node.point').node();
var labelable = [], i, k, entity;
for (i = 0; i < label_stack.length; i++) labelable.push([]);
if (fullRedraw) {
rtree = new RTree();
rectangles = {};
} else {
for (i = 0; i < entities.length; i++) {
rtree.remove(rectangles[entities[i].id], entities[i].id);
}
}
// Split entities into groups specified by label_stack
for (i = 0; i < entities.length; i++) {
entity = entities[i];
@@ -252,7 +264,7 @@ iD.svg.Labels = function(projection) {
textAnchor: offset[2]
};
var rect = new RTree.Rectangle(p.x - m, p.y - m, width + 2*m, height + 2*m);
if (tryInsert(rect)) return p;
if (tryInsert(rect, entity.id)) return p;
}
@@ -275,7 +287,7 @@ iD.svg.Labels = function(projection) {
Math.abs(sub[0][1] - sub[sub.length - 1][1]) + 30
);
if (rev) sub = sub.reverse();
if (tryInsert(rect)) return {
if (tryInsert(rect, entity.id)) return {
'font-size': height + 2,
lineString: lineString(sub),
startOffset: offset + '%'
@@ -298,16 +310,19 @@ iD.svg.Labels = function(projection) {
height: height
};
var rect = new RTree.Rectangle(p.x - width/2, p.y, width, height);
if (tryInsert(rect)) return p;
if (tryInsert(rect, entity.id)) return p;
}
function tryInsert(rect) {
function tryInsert(rect, id) {
// Check that label is visible
if (rect.x1 < 0 || rect.y1 < 0 || rect.x2 > dimensions[0] ||
rect.y2 > dimensions[1]) return false;
var v = rtree.search(rect, true).length === 0;
if (v) rtree.insert(rect);
if (v) {
rtree.insert(rect, id);
rectangles[id] = rect;
}
return v;
}
+7 -11
View File
@@ -15,19 +15,15 @@ iD.svg.Midpoints = function(projection) {
b = nodes[j + 1],
id = [a.id, b.id].sort().join('-');
if (!midpoints[id] &&
iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) {
var midpoint_loc = iD.geo.interp(a.loc, b.loc, 0.5),
parents = _.intersection(graph.parentWays(a),
graph.parentWays(b));
if (midpoints[id]) {
midpoints[id].ways.push({id: entity.id, index: j + 1});
} else if (iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) {
midpoints[id] = {
loc: midpoint_loc,
ways: parents,
nodes: [a.id, b.id],
type: 'midpoint',
id: id,
midpoint: true
loc: iD.geo.interp(a.loc, b.loc, 0.5),
ways: [{id: entity.id, index: j + 1}]
};
}
}
@@ -35,7 +31,7 @@ iD.svg.Midpoints = function(projection) {
var groups = surface.select('.layer-hit').selectAll('g.midpoint')
.filter(filter)
.data(_.values(midpoints), function (d) { return [d.parents, d.id].join(","); });
.data(_.values(midpoints), function (d) { return d.id; });
var group = groups.enter()
.insert('g', ':first-child')
+1 -1
View File
@@ -36,7 +36,7 @@ iD.svg.Vertices = function(projection) {
groups.attr('transform', iD.svg.PointTransform(projection))
.call(iD.svg.TagClasses())
.call(iD.svg.MemberClasses(graph))
.classed('shared', function(entity) { return graph.parentWays(entity).length > 1; });
.classed('shared', function(entity) { return graph.isShared(entity); });
// Selecting the following implicitly
// sets the data (vertix entity) on the elements
+5 -1
View File
@@ -16,7 +16,7 @@ iD.ui.contributors = function(map) {
var l = selection
.select('.contributor-list')
.selectAll('a.user-link')
.data(subset);
.data(subset, function(d) { return d; });
l.enter().append('a')
@@ -43,6 +43,10 @@ iD.ui.contributors = function(map) {
ext[1][0], ext[1][1]];
})
.text(' and ' + (u.length - limit) + ' others');
} else {
selection
.select('.contributor-count')
.html('');
}
if (!u.length) {
+9 -12
View File
@@ -4,20 +4,17 @@ iD.ui.notice = function(selection) {
notice = {};
var div = selection.append('div')
.attr('class', 'notice')
.append('div')
.attr('class', 'notice-inner');
.attr('class', 'notice');
div.append('button')
.attr('class', 'zoom-to')
.on('click', function() {
event.zoom();
})
.append('span')
.attr('class', 'icon invert zoom-in');
var button = div.append('button')
.attr('class', 'zoom-to notice')
.on('click', event.zoom);
div.append('span')
.attr('class', 'notice-text')
button.append('span')
.attr('class', 'icon zoom-in-invert');
button.append('span')
.attr('class', 'label')
.text(t('zoom_in_edit'));
notice.message = function(_) {
+2
View File
@@ -94,3 +94,5 @@ iD.util.editDistance = function(a, b) {
}
return matrix[b.length][a.length];
};
iD.util.getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
+47
View File
@@ -0,0 +1,47 @@
from xml.dom.minidom import parse
import json
dom1 = parse('potlatch.xml')
inputSets = dom1.getElementsByTagName('inputSet')
jsonOutput = []
for inputSet in inputSets:
setId = inputSet.getAttribute('id')
inputs = inputSet.getElementsByTagName('input')
for i in inputs:
jsonInput = {}
inputType = i.getAttribute('type')
if inputType == 'choice':
choices = i.getElementsByTagName('choice')
jsonInput['type'] = 'choice'
jsonInput['description'] = i.getAttribute('description')
jsonInput['name'] = i.getAttribute('name')
jsonInput['key'] = i.getAttribute('key')
jsonInput['choices'] = []
for c in choices:
jsonInput['choices'].append({
"value": c.getAttribute('value'),
"text": c.getAttribute('text')
})
elif inputType == 'freetext':
jsonInput['type'] = 'freetext'
jsonInput['description'] = i.getAttribute('description')
jsonInput['name'] = i.getAttribute('name')
jsonInput['key'] = i.getAttribute('key')
elif inputType == 'checkbox':
jsonInput['type'] = 'checkbox'
jsonInput['description'] = i.getAttribute('description')
jsonInput['name'] = i.getAttribute('name')
jsonInput['key'] = i.getAttribute('key')
elif inputType == 'number':
jsonInput['type'] = 'number'
jsonInput['description'] = i.getAttribute('description')
jsonInput['name'] = i.getAttribute('name')
jsonInput['minimum'] = i.getAttribute('minimum')
jsonInput['maximum'] = i.getAttribute('maximum')
jsonInput['key'] = i.getAttribute('key')
jsonOutput.append(jsonInput)
json.dump(jsonOutput, open('presets_potlatch.json', 'w'), indent=4)
+693
View File
@@ -0,0 +1,693 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
See http://wiki.openstreetmap.org/wiki/Potlatch_2/Developer_Documentation/Map_Features for documentation.
Files can be included like this: <include file="map_features/roads.xml">
-->
<mapFeatures>
<!-- Categories -->
<category name="Roads" id="roads"/>
<category name="Paths" id="paths"/>
<category name="Shopping" id="shopping"/>
<category name="Food and Drink" id="foodanddrink"/>
<category name="Amenity" id="amenity"/>
<category name="Tourism" id="tourism"/>
<category name="Accommodation" id="accommodation"/>
<category name="Transport" id="transport"/>
<category name="Water" id="water"/>
<category name="Natural" id="natural"/>
<category name="Barrier" id="barrier"/>
<category name="Power" id="power"/>
<category name="Admin" id="admin"/>
<category name="Buildings" id="buildings"/>
<category name="Landuse" id="landuse"/>
<category name="Agriculture" id="agriculture"/>
<category name="Advanced" id="advanced"/>
<category name="Places" id="places"/>
<category name="Sport and Leisure" id="sport"/>
<category name="Man-made" id="manmade"/>
<!-- Common input sets -->
<inputSet id="simpleName">
<input type="freetext" presence="always" name="Name" key="name" description="The name" priority="highest"/>
</inputSet>
<inputSet id="nameAndRef">
<inputSet ref="simpleName" />
<input type="freetext" presence="always" name="Reference number" key="ref" description="A reference number or code used to identify this thing." />
</inputSet>
<inputSet id="simpleBrand">
<input type="freetext" category="Naming" presence="always" name="Brand" key="brand" description="Brand, i.e. Acme"/>
</inputSet>
<inputSet id="simpleOperator">
<input type="freetext" category="Naming" presence="always" name="Operator" key="operator" description="Operator, i.e. Acme Springfield Ltd"/>
</inputSet>
<inputSet id="source">
<input type="freetext" category="Details" presence="onTagMatch" name="Source" key="source" description="The primary source of information for this object (GPS, survey, Bing, ...)" priority="lowest"/>
</inputSet>
<inputSet id="designation">
<input type="freetext" category="Details" presence="onTagMatch" description="The official designation or classification (if any). Only use this if the organisation that runs it has its own classification system." name="Official classification" key="designation" priority="lowest" />
</inputSet>
<inputSet id="common">
<inputSet ref="source"/>
<inputSet ref="designation"/>
</inputSet>
<inputSet id="names">
<input type="freetext" presence="always"
name="Name" category="Details" priority="highest"
key="name" description="The most common name"/>
<input type="freetext" presence="onTagMatch"
name="International Name" category="Details" subcategory="Additional names"
key="int_name" description="The internationally recognised name"/>
<input type="freetext" presence="onTagMatch"
name="Historical Name" category="Details" subcategory="Additional names" priority="low"
key="old_name" description="The historic or previous name"/>
<input type="freetext" presence="onTagMatch"
name="Alternative Name" category="Details" subcategory="Additional names" priority="low"
key="alt_name" description="An alternative, currently used, name"/>
</inputSet>
<inputSet id="wifi">
<input type="choice" presence="onTagMatch" category="Details" name="Wifi" key="wifi">
<choice value="free" text="Free"/>
<choice value="yes" text="Yes"/>
<choice value="no" text="No"/>
</input>
</inputSet>
<inputSet id="buildingAddress">
<input type="freetext" presence="onTagMatch" category="Address" description="The number of the house, e.g. 156 or 10-12" name="House Number" key="addr:housenumber"/>
<input type="freetext" presence="onTagMatch" category="Address" description="The name of the house, e.g. Riverbank Cottage" name="Building Name" key="addr:housename"/>
<input type="freetext" presence="onTagMatch" category="Address" description="The Street Name (optional)" name="Street Name" key="addr:street"/>
<input type="freetext" presence="onTagMatch" category="Address" description="The postcode" name="Postcode" key="addr:postcode"/>
</inputSet>
<inputSet id="web">
<input type="freetext" presence="onTagMatch" category="Address" description="The URL of the website" name="Website" key="website"/>
</inputSet>
<inputSet id="cuisine">
<input type="choice" presence="always" name="Cuisine" category="Details" description="The type of food that they serve" key="cuisine">
<!-- The 30 most popular values according to taginfo 23/12/2010 -->
<choice value="burger" text="Burger"/>
<choice value="chicken" text="Chicken"/>
<choice value="chinese" text="Chinese"/>
<choice value="coffee_shop" text="Coffee Shop"/>
<choice value="greek" text="Greek"/>
<choice value="pizza" text="Pizza"/>
<choice value="sandwich" text="Sandwich"/>
<choice value="seafood" text="Sea Food"/>
<choice value="regional" text="Regional"/>
<choice value="italian" text="Italian"/>
<choice value="german" text="German"/>
<choice value="kebab" text="Kebab/souvlaki/gyro"/>
<choice value="indian" text="Indian"/>
<choice value="asian" text="Asian"/>
<choice value="mexican" text="Mexican"/>
<choice value="thai" text="Thai"/>
<choice value="japanese" text="Japanese"/>
<choice value="ice_cream" text="Ice-cream"/>
<choice value="fish_and_chips" text="Fish &amp; Chips"/>
<choice value="turkish" text="Turkish"/>
<choice value="french" text="French"/>
<choice value="sushi" text="Sushi"/>
<choice value="american" text="American"/>
<choice value="steak_house" text="Steak House"/>
<choice value="international" text="International"/>
<choice value="spanish" text="Spanish"/>
<choice value="vietnamese" text="Vietnamese"/>
<choice value="fish" text="Fish"/>
<choice value="bavarian" text="Bavarian"/>
<choice value="vegetarian" text="Vegetarian"/>
<help>http://wiki.openstreetmap.org/wiki/Key:cuisine</help>
</input>
</inputSet>
<!-- Roads -->
<inputSet id="majorRoad">
<inputSet ref="names"/>
<inputSet ref="roadRefs"/>
<inputSet ref="roadRestrictions"/>
<inputSet ref="roadPhysical"/>
<inputSet ref="cycle"/>
<inputSet ref="bicycle-lane"/>
<inputSet ref="bus-route"/>
<inputSet ref="tram-route"/>
<inputSet ref="pedestrians"/>
<inputSet ref="roadLanes"/>
<inputSet ref="roadRoundabout"/>
<inputSet ref="permissions"/>
</inputSet>
<inputSet id="minorRoad">
<inputSet ref="names"/>
<inputSet ref="roadRestrictions"/>
<inputSet ref="roadPhysical"/>
<inputSet ref="cycle"/>
<inputSet ref="bicycle-lane"/>
<inputSet ref="bus-route"/>
<inputSet ref="tram-route"/>
<inputSet ref="pedestrians"/>
<inputSet ref="roadLanes"/>
<inputSet ref="roadRoundabout"/>
<inputSet ref="permissions"/>
</inputSet>
<inputSet id="path">
<inputSet ref="simpleName"/>
<inputSet ref="roadPhysical"/>
<inputSet ref="cycle"/>
<inputSet ref="pedestrians"/>
<inputSet ref="permissions"/>
</inputSet>
<inputSet id="junctionNode">
<inputSet ref="turnRestrictions"/>
</inputSet>
<inputSet id="roadRefs">
<input type="freetext" presence="always"
name="Reference" category="Details" priority="high"
key="ref" description="The official reference number"/>
<input type="freetext" presence="onTagMatch"
name="International Reference" category="Details" subcategory="Additional names"
key="int_ref" description="The official international reference number"/>
<input type="freetext" presence="onTagMatch"
name="Old Reference" category="Details" subcategory="Additional names" priority="low"
key="old_ref" description="The historic or previous reference number"/>
</inputSet>
<inputSet id="roadPhysical">
<input type="freetext" presence="onTagMatch"
name="Width" category="Details" subcategory="Physical"
key="width" description="Width of the road" layout="horizontal"/>
<input type="choice" presence="onTagMatch"
name="Surface" category="Details" description="Type of road surface"
key="surface" layout="horizontal">
<choice value="unpaved" text="Unpaved" description="Road surface is unsealed"/>
<choice value="paved" text="Paved" description="Road surface is sealed"/>
<choice value="asphalt" text="Asphalt"/>
<choice value="concrete" text="Concrete"/>
<choice value="paving_stones" text="Paving stones"/>
<choice value="cobblestone" text="Cobblestone"/>
<choice value="sand" text="Sand"/>
<choice value="gravel" text="Gravel"/>
<choice value="dirt" text="Dirt"/>
<choice value="grass" text="Grass"/>
</input>
<inputSet ref="bridge"/>
<inputSet ref="tunnel"/>
<inputSet ref="embankment-cutting"/>
<!-- not sure which category best suits put area=yes -->
<input type="checkbox" presence="onTagMatch" category="Details" subcategory="Physical" key="area" name="Open area" description="The way is a large open space, like at a dock, where vehicles can move anywhere within the space, rather than just along the edge." />
</inputSet>
<inputSet id="roadLanes">
<input presence="onTagMatch" type="number" name="Lanes" category="Details" description="Total number of lanes, counting both directions"
key="lanes" minimum="1" maximum="10" layout="horizontal"/>
</inputSet>
<inputSet id="bridge">
<input type="choice" presence="onTagMatch"
name="Bridge" category="Details" description="Road goes over a bridge"
key="bridge" layout="horizontal">
<choice value="yes" text="Generic Bridge" description="Generic bridge -- type unknown"/>
<choice value="viaduct" text="Viaduct" description="Viaduct"/>
<choice value="suspension" text="Suspension bridge"/>
</input>
<input type="slider" presence="onTagMatch"
name="Layer" category="Details" description="Relative vertical positions (-5 lowest, +5 highest)"
key="layer" minimum="-5" maximum="5" default="0" snapInterval="1" labels="Lowest,Ground,Highest"
defaultName="Ground"/>
</inputSet>
<inputSet id="tunnel">
<!-- Not ideal, used for non-roads too. -->
<input type="choice" presence="onTagMatch"
name="Tunnel" category="Details" subcategory="Physical" description="Road goes into a tunnel"
key="tunnel" layout="horizontal">
<choice value="yes" text="Tunnel" description="Generic tunnel"/>
</input>
</inputSet>
<inputSet id="embankment-cutting">
<input type="choice"
name="Embankment" category="Details" subcategory="Physical" description="Road supported on a raised bed of earth and rock."
key="embankment" layout="horizontal">
<choice value="yes" text="Embankment"/>
</input>
<input type="choice"
name="Cutting" category="Details" subcategory="Physical" description="Road carved out of hill on one or both sides."
key="cutting" layout="horizontal">
<choice value="yes" text="Cutting"/>
</input>
</inputSet>
<inputSet id="rail-electrification">
<input type="choice" name="Electrified" category="Details" subcategory="Electrification" description="Is the track electrified (whether by 3rd rail, overhead wires, etc)?"
key="electrified">
<choice value="yes" text="Yes"/>
<choice value="contact_line" text="Overhead line"/>
<choice value="rail" text="Third rail"/>
<choice value="no" text="No"/>
</input>
<input type="choice" name="Voltage" category="Details" subcategory="Electrification" description="Nominal voltage of electric wires"
key="voltage" presence="withCategory">
<choice value="600" text="600V"/>
<choice value="750" text="750V"/>
<choice value="1500" text="1500V"/>
<choice value="3000" text="3000V"/>
<choice value="12000" text="12kV"/>
<choice value="15000" text="15kV"/>
<choice value="25000" text="25kV"/>
</input>
<input type="choice" name="Frequency" category="Details" subcategory="Electrification" description="Frequency in Hertz of alternating current power supply"
key="frequency" presence="withCategory">
<choice value="0" text="DC"/>
<choice value="16.67" text="16.67 Hz"/>
<choice value="16.7" text="16.7 Hz"/>
<choice value="25" text="25 Hz"/>
<choice value="50" text="50 Hz"/>
<choice value="60" text="60 Hz"/>
</input>
</inputSet>
<inputSet id="fee">
<input type="freetext" presence="onTagMatch" category="Restrictions" description="The charge/cost of using this amenity" name="Fee" key="fee"/>
</inputSet>
<inputSet id="roadRestrictions">
<input type="choice" presence="always"
name="Oneway" category="Restrictions" description="Oneway roads"
key="oneway">
<choice value="yes" match="yes|true|1" text="One way"
description="Road can only be travelled in direction of way" icon="features/oneway__yes.png"/>
<choice value="no" match="no|false|0" text="Two way"
description="Road can be travelled in both directions" icon="features/oneway__no.png"/>
<choice value="-1" match="-1|reverse" text="One way reverse"
description="Road can be travelled in opposite direction to way" icon="features/oneway__-1.png"/>
</input>
<input type="speed" presence="onTagMatch"
name="Speed Limit" category="Restrictions" description="Maximum permitted speed on this road"
key="maxspeed"/>
</inputSet>
<inputSet id="roadRoundabout">
<!-- review the choice of category -->
<input type="choice" presence="onTagMatch" name="Roundabout" category="Restrictions" description="Whether this road is a roundabout. Make the way face the direction appropriate for the country."
key="junction">
<choice value="roundabout" text="Yes"/>
</input>
</inputSet>
<inputSet id="turnRestrictions">
<input type="turn" name="Turn restriction" description="Turn restriction" category="Restrictions" priority="normal" presence="onTagMatch">
<match k="type" v="restriction"/>
<role role="via"/>
</input>
</inputSet>
<inputSet id="trafficSignals">
<!-- can't add as standalone feature due to current limitations, would conflict with junction node feature -->
<input type="choice" key="highway" name="Traffic signals" description="Intersection controlled by traffic lights" category="Restrictions" presence="always">
<choice value="traffic_signals" text="Yes"/>
</input>
</inputSet>
<inputSet id="pedestrians">
<input type="choice" name="Pedestrians permitted" description="Can pedestrians use this road, including footpaths if any?" category="Walk" key="foot">
<choice value="yes" text="Allowed"/>
<choice value="no" text="Prohibited"/>
<choice value="designated" text="Designated"/>
</input>
<input type="choice" presence="onTagMatch"
name="Sidewalks" category="Walk" description="Whether there is a sidewalk at the side of the street"
key="sidewalk" layout="horizontal">
<choice value="both" text="Both" description="There is a sidewalk on both sides of the road." match="yes"/>
<choice value="left" text="Left" description="The sidewalk is on the left of the direction of the road."/>
<choice value="right" text="Right" description="The sidewalk is on the right of the direction of the road."/>
<choice value="separate" text="Separate" description="The sidewalk has been mapped as a separate line parallel."/>
<choice value="none" text="None" description="There is no sidewalk on this section of road." match="no"/>
</input>
<input type="route" name="National Walking Route" description="National walking route" category="Walk" priority="low">
<match k="type" v="route"/>
<match k="route" v="hiking|foot"/>
<match k="network" v="nwn"/>
<icon image="features/route__nwn.png" background="red" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
<input type="route" name="Regional Walking Route" description="Regional walking route" category="Walk" priority="low">
<match k="type" v="route"/>
<match k="route" v="hiking|foot"/>
<match k="network" v="rwn"/>
<icon image="features/route__rwn.png" background="cyan" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
<input type="route" name="Local Walking Route" description="Local walking route" category="Walk" priority="lowest">
<match k="type" v="route"/>
<match k="route" v="hiking|foot"/>
<match k="network" v="lwn"/>
<icon image="features/route__lwn.png" background="blue" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
</inputSet>
<inputSet id="naptan">
<input type="freetext" presence="onTagMatch" category="Transport" description="12 character internal Naptan ID" name="Atco Code" key="naptan:AtcoCode"/>
<input type="choice" presence="onTagMatch" category="Transport" description="The eight-point compass bearning" name="Naptan Bearing" key="naptan:Bearing" >
<choice value="N" match="N" text="N" description=""/>
<choice value="NE" match="NE" text="NE" description=""/>
<choice value="E" match="E" text="E" description=""/>
<choice value="SE" match="SE" text="SE" description=""/>
<choice value="S" match="S" text="S" description=""/>
<choice value="SW" match="SW" text="SW" description=""/>
<choice value="W" match="W" text="W" description=""/>
<choice value="NW" match="NW" text="NW" description=""/>
</input>
<input type="freetext" presence="onTagMatch" category="Transport" description="The naptan common name" name="Naptan Common Name (read-only)" key="naptan:CommonName"/>
<input type="freetext" presence="onTagMatch" category="Transport" description="" name="Naptan Indicator (read-only)" key="naptan:Indicator"/>
<input type="freetext" presence="onTagMatch" category="Transport" description="" name="Naptan Street (read-only)" key="naptan:Street"/>
<input type="freetext" presence="onTagMatch" category="Transport" description="Delete this when the details have been verified on-the-ground" name="Naptan Verified?" key="naptan:verified"/>
</inputSet>
<inputSet id="buses">
<input type="freetext" presence="always" category="Transport" name="Stop Name" key="name" description="The name of the bus stop"/>
<input type="freetext" presence="always" category="Transport" name="Local Ref" key="local_ref" description="The local reference of the stop, usually one or two letters above the main flag, used at bus interchanges, e.g. L, BX"/>
<inputSet ref="naptan"/>
</inputSet>
<inputSet id="bus-route">
<input type="route" name="Bus Route" description="Bus route" category="Transport" priority="low" presence="onTagMatch">
<match k="type" v="route"/>
<match k="route" v="bus"/>
<icon image="features/route__bus.png">
<font size="12pt">${operator} <b>${ref}</b></font>
</icon>
</input>
</inputSet>
<inputSet id="tram-route">
<input type="route" name="Tram Route" description="Tram route" category="Transport" priority="low" presence="onTagMatch">
<match k="type" v="route"/>
<match k="route" v="tram"/>
<icon image="features/transport__tram.png">
<font size="12pt">${operator} <b>${ref}</b></font>
</icon>
</input>
</inputSet>
<inputSet id="train-route">
<input type="route" name="Train Route" description="Train route" category="Transport" priority="low" presence="onTagMatch">
<match k="type" v="route"/>
<match k="route" v="train"/>
<icon image="features/transport__railway.png">
<font size="12pt">${name|operator} <b>(${ref})</b></font>
</icon>
</input>
</inputSet>
<inputSet id="cycle">
<inputSet ref="bicycle-permission"/>
<input type="route" name="National Cycle Routes" description="A signposted route in a National Cycle Network, or nearest equivalent." category="Cycle" priority="low">
<match k="type" v="route"/>
<match k="route" v="bicycle"/>
<match k="network" v="ncn"/>
<icon image="features/route__ncn.png" background="#ff6f7a" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
<input type="route" name="Regional Cycle Routes" description="A signposted route in a Regional Cycle Network, or nearest equivalent." category="Cycle" priority="low">
<match k="type" v="route"/>
<match k="route" v="bicycle"/>
<match k="network" v="rcn"/>
<icon image="features/route__rcn.png" background="#6ff7ff" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
<input type="route" name="Local Cycle Routes" description="A signposted route in a Local Cycle Network, or nearest equivalent." category="Cycle" priority="lowest">
<match k="type" v="route"/>
<match k="route" v="bicycle"/>
<match k="network" v="lcn"/>
<icon image="features/route__lcn.png" background="#7d6fff" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
<input type="route" name="Mountain bike route" description="A signposted route for mountain biking." category="Cycle" priority="lowest">
<match k="type" v="route"/>
<match k="route" v="mtb"/>
<icon image="features/route__mtb.png" background="#9f4404" foreground="white">
<font size="14pt"><b>${ref}</b></font><br/>
<font size="12pt">${name}</font>
</icon>
</input>
</inputSet>
<inputSet id="bicycle-permission">
<input type="choice"
name="Bicycles permitted" category="Cycle" description="Are bicyles allowed to use this road (regardless of physical suitability)?"
key="bicycle">
<choice value="yes" text="Allowed"/>
<choice value="no" text="Prohibited" desciption="Cycling is not allowed. Pushing your bicycle is not allowed either."/>
<choice value="private" text="Private"/>
<choice value="dismount" text="Cyclists dismount" description="Signage states that cyclists should dismount and push their bike for the selected path or road."/>
<choice value="designated" text="Designated"/>
<help>http://wiki.openstreetmap.org/wiki/Key:access</help>
</input>
</inputSet>
<inputSet id="bicycle-lane">
<input type="choice"
name="Bike lanes" category="Cycle" description="Road has bike lanes within the road surface"
key="cycleway" layout="horizontal">
<choice value="no" text="No bike lanes"/>
<choice value="lane" text="On-road bike lane" description="Separated by painted line on the road from cars"/>
<choice value="track" text="Parallel track" description="Separated by kerb or parked cars"/>
<choice value="opposite_lane" text="Contraflow lane" description="Separated by painted line, and allowing bicycles in both directions in an otherwise one-way street."/>
<choice value="opposite_track" text="Contraflow track" description="Separated by kerb or parked cars, and allowing bicycles in both directions in an otherwise one-way street."/>
<choice value="opposite" text="Contraflow unmarked" description="The route may be cycled in the direction opposite of other traffic, but does not have a dedicated lane."/>
<help>http://wiki.openstreetmap.org/wiki/Key:cycleway</help>
</input>
</inputSet>
<inputSet id="route">
<inputSet ref="names"/>
<inputSet ref="roadRefs"/>
</inputSet>
<inputSet id="waterways">
<inputSet ref="simpleName"/>
<input type="freetext" presence="always" name="Width" category="Details" key="width" description="The width in metres"/>
<inputSet ref="boatPermissions"/>
</inputSet>
<!-- Would be good to have a dedicated 'access' type of input -->
<!-- all the description fields are identical at the moment for ease of maintenance, should be tailored in future. -->
<inputSet id="boatPermissions">
<input type="choice" name="Boat permission" category="Restrictions" key="boat" description="Are boats allowed to use this waterway?">
<choice value="yes" text="Allowed" description="General right of way."/>
<choice value="no" text="Prohibited" description="No access to the public."/>
<choice value="permissive" text="Permissive" description="Access permitted through private land."/>
<choice value="private" text="Private" description="No access to the public, except individual exceptions."/>
<choice value="designated" text="Designated" description="Permitted, according to signs or specific local laws."/>
</input>
</inputSet>
<inputSet id="permissions">
<input type="choice" name="General access" category="Restrictions" key="access" description="Is there a general right of access, regardless of mode of transport?">
<choice value="yes" text="Allowed" description="General right of way."/>
<choice value="no" text="Prohibited" description="No access to the public."/>
<choice value="permissive" text="Permissive" description="Access permitted through private land."/>
<choice value="private" text="Private" description="No access to the public, except individual exceptions."/>
<choice value="designated" text="Designated" description="Permitted, according to signs or specific local laws."/>
</input>
<input type="choice" name="Motor vehicles" category="Restrictions" key="motor_vehicle" description="Are cars and other private vehicles allowed?">
<choice value="yes" text="Allowed" description="General right of way."/>
<choice value="no" text="Prohibited" description="No access to the public."/>
<choice value="permissive" text="Permissive" description="Access permitted through private land."/>
<choice value="private" text="Private" description="No access to the public, except individual exceptions."/>
<choice value="designated" text="Designated" description="Permitted, according to signs or specific local laws."/>
</input>
<inputSet ref="hores-permission" />
</inputSet>
<inputSet id="horse-permission">
<input type="choice" name="Horses" category="Restrictions" key="horse" description="Are horses allowed?">
<choice value="yes" text="Allowed" description="General right of way."/>
<choice value="no" text="Prohibited" description="No access to the public."/>
<choice value="permissive" text="Permissive" description="Access permitted through private land."/>
<choice value="private" text="Private" description="No access to the public, except individual exceptions."/>
<choice value="designated" text="Designated" description="Permitted, according to signs or specific local laws."/>
</input>
</inputSet>
<inputSet id="places">
<input type="choice" name="Type of Place" presence="always" key="place">
<choice value="locality" text="Locality"/>
<choice value="hamlet" text="Hamlet"/>
<choice value="village" text="Village"/>
<choice value="suburb" text="Suburb"/>
<choice value="town" text="Town"/>
<choice value="city" text="City"/>
<choice value="county" text="County"/>
<choice value="region" text="Region"/>
<choice value="state" text="State"/>
<choice value="country" text="Country"/>
<choice value="continent" text="Continent"/>
<choice value="island" text="Island"/>
<choice value="islet" text="Islet"/>
</input>
</inputSet>
<inputSet id="isBuilding">
<input type="choice" presence="withCategory" category="Details" name="Building type, if it is one" key="building">
<choice value="yes" text="Generic building"/>
<choice value="residential" text="Generic residential"/>
<choice value="apartments" text="Big apartments house"/>
<choice value="terrace" text="Terraced house"/>
<choice value="house" text="Family house"/>
<choice value="hut" text="Small hut"/>
<choice value="garage" text="A garage"/>
<choice value="garages" text="Block of garages"/>
<choice value="office" text="Office building"/>
<choice value="public" text="Public building"/>
<choice value="industrial" text="Generic industrial"/>
<choice value="manufacture" text="Manufacture"/>
<choice value="warehouse" text="Warehouse"/>
<choice value="hangar" text="Hangar"/>
<choice value="storage_tank" text="Fluids storage tank"/>
<choice value="retail" text="Retail"/>
<choice value="supermarket" text="Supermarket"/>
<choice value="train_station" text="Train station"/>
<choice value="church" text="Church"/>
<choice value="school" text="School"/>
<choice value="bunker" text="Military bunker"/>
<choice value="collapsed" text="Collapsed building"/>
<choice value="roof" text="Just a roof"/>
<help>http://wiki.openstreetmap.org/wiki/Key:building</help>
</input>
</inputSet>
<inputSet id="powerCables">
<input type="choice" name="Cables" key="cables" presence="always" category="Details">
<choice value="2" text="2"/>
<choice value="3" text="3"/>
<choice value="4" text="4"/>
<choice value="6" text="6"/>
<choice value="8" text="8"/>
<choice value="10" text="10"/>
<choice value="12" text="12"/>
</input>
<input type="choice" name="Voltage" key="voltage" presence="always" category="Details">
<!-- choices based on http://osmdoc.com/en/tag/voltage/#values-->
<choice value="400" text="400 V"/>
<choice value="600" text="600 V"/>
<choice value="750" text="750 V"/>
<choice value="1500" text="1500 V"/>
<choice value="3000" text="3000 V"/>
<choice value="15000" text="15 kV"/>
<choice value="20000" text="20 kV"/>
<choice value="35000" text="35 kV"/>
<choice value="110000" text="110 kV"/>
<choice value="132000" text="132 kV"/>
<choice value="138000" text="138 kV"/>
<choice value="220000" text="220 kV"/>
<choice value="380000" text="380 kV"/>
<choice value="500000" text="500 kV"/>
</input>
</inputSet>
<inputSet id="pitchSport">
<input name="Sport" presence="always" category="Details" key="sport" type="choice" description="The sport that is predominantly played here.">
<choice value="9pin" text="9 pin bowling"/>
<choice value="10pin" text="10 pin bowling"/>
<choice value="american_football" text="American football"/>
<choice value="archery" text="Archery"/>
<choice value="athletics" text="Athletics"/>
<choice value="australian_football" text="Australian Rules Football"/>
<choice value="baseball" text="Baseball"/>
<choice value="basketball" text="Basketball"/>
<choice value="beachvolleyball" text="Beach volleyball"/>
<choice value="boules" text="Boules/petanque/bocci"/>
<choice value="bowls" text="Lawn bowls"/>
<choice value="canadian_football" text="Canadian football"/>
<choice value="chess" text="Chess"/>
<choice value="cricket" text="Cricket"/>
<choice value="cricket_nets" text="Cricket nets"/>
<choice value="croquet" text="Croquet"/>
<choice value="equestrian" text="Equestrian"/>
<choice value="gaelic_football" text="Gaelic football"/>
<choice value="gymnastics" text="Gymnastics"/>
<choice value="team_handball" text="(Team) handball"/>
<choice value="hockey" text="(Field) hockey"/>
<choice value="korfball" text="Korball"/>
<choice value="pelota" text="Pelota"/>
<choice value="rugby_league" text="Rugby league"/>
<choice value="rugby_union" text="Rugby union"/>
<choice value="shooting" text="Shooting"/>
<choice value="skating" text="Ice skating"/>
<choice value="skateboard" text="Skateboarding"/>
<choice value="soccer" text="Soccer/football"/>
<choice value="swimming" text="Swimming"/>
<choice value="table_tennis" text="Table tennis"/>
<choice value="tennis" text="Tennis"/>
<choice value="volleyball" text="Volleyball"/>
</input>
</inputSet>
<inputSet id="wheelchair-basic">
<input type="choice" name="Wheelchair" key="wheelchair" presence="onTagMatch" category="Details">
<choice value="yes" text="Yes: ramps/elevators/etc" />
<choice value="no" text="No: inaccessible to wheelchairs" />
<choice value="limited" text="Limited accessibility" />
</input>
</inputSet>
<!-- Features -->
<include file="map_features/roads.xml" /> <!-- includes traffic calming, roundabouts, race track -->
<include file="map_features/paths.xml" /> <!-- includes steps, highway=pedestrian, bike/hike routes -->
<include file="map_features/water.xml" /> <!-- includes coastline -->
<include file="map_features/transport.xml" /> <!-- includes rail, trams, bus, airports, car rental, bike parking... -->
<include file="map_features/power.xml" /> <!-- includes power lines, pylons, stations -->
<include file="map_features/places.xml" /> <!-- includes boundaries (inc. NP) -->
<include file="map_features/tourism.xml" /> <!-- includes accommodation -->
<include file="map_features/barriers.xml" />
<include file="map_features/shopping.xml" /> <!-- no clear distinction between this, amenities and buildings -->
<include file="map_features/amenities.xml" /> <!-- includes cafe, bar... -->
<include file="map_features/landuse.xml" /> <!-- includes outdoor leisure/sporting stuff, agriculture, natural. -->
<include file="map_features/man_made.xml" />
<include file="map_features/buildings.xml" /> <!-- a bit of a dumping ground, includes sports centre, shopping centre, hospital... -->
<!-- Relations -->
<feature name="Multipolygon">
<category>advanced</category>
<relation/>
<tag k="type" v="multipolygon"/>
<inputSet ref="common"/>
</feature>
</mapFeatures>
File diff suppressed because it is too large Load Diff
+4
View File
@@ -68,6 +68,7 @@
<script src='../js/id/ui/confirm.js'></script>
<script src='../js/id/actions.js'></script>
<script src="../js/id/actions/add_midpoint.js"></script>
<script src='../js/id/actions/add_node.js'></script>
<script src='../js/id/actions/add_way.js'></script>
<script src='../js/id/actions/add_way_node.js'></script>
@@ -137,6 +138,7 @@
<!-- include spec files here... -->
<script src="spec/lib/d3.keybinding.js"></script>
<script src="spec/actions/add_midpoint.js"></script>
<script src="spec/actions/add_node.js"></script>
<script src="spec/actions/add_way.js"></script>
<script src="spec/actions/change_entity_tags.js"></script>
@@ -170,6 +172,7 @@
<script src="spec/svg/areas.js"></script>
<script src="spec/svg/lines.js"></script>
<script src="spec/svg/member_classes.js"></script>
<script src="spec/svg/midpoints.js"></script>
<script src="spec/svg/multipolygons.js"></script>
<script src="spec/svg/points.js"></script>
<script src="spec/svg/vertices.js"></script>
@@ -182,6 +185,7 @@
<script src="spec/ui/confirm.js"></script>
<script src="spec/connection.js"></script>
<script src="spec/geo.js"></script>
<script src="spec/oauth.js"></script>
<script src="spec/taginfo.js"></script>
<script src="spec/util.js"></script>
+3
View File
@@ -32,6 +32,7 @@
<!-- include spec files here... -->
<script src="spec/lib/d3.keybinding.js"></script>
<script src="spec/actions/add_midpoint.js"></script>
<script src="spec/actions/add_node.js"></script>
<script src="spec/actions/add_way.js"></script>
<script src="spec/actions/change_entity_tags.js"></script>
@@ -65,6 +66,7 @@
<script src="spec/svg/areas.js"></script>
<script src="spec/svg/lines.js"></script>
<script src="spec/svg/member_classes.js"></script>
<script src="spec/svg/midpoints.js"></script>
<script src="spec/svg/multipolygons.js"></script>
<script src="spec/svg/points.js"></script>
<script src="spec/svg/vertices.js"></script>
@@ -77,6 +79,7 @@
<script src="spec/ui/confirm.js"></script>
<script src="spec/connection.js"></script>
<script src="spec/geo.js"></script>
<script src="spec/oauth.js"></script>
<script src="spec/taginfo.js"></script>
<script src="spec/util.js"></script>
+22
View File
@@ -0,0 +1,22 @@
describe("iD.actions.AddMidpoint", function () {
it("adds the node at the midpoint location", function () {
var node = iD.Node(),
midpoint = {loc: [1, 2], ways: []},
graph = iD.actions.AddMidpoint(midpoint, node)(iD.Graph());
expect(graph.entity(node.id).loc).to.eql([1, 2]);
});
it("adds the node to all ways at the respective indexes", function () {
var node = iD.Node(),
a = iD.Node(),
b = iD.Node(),
w1 = iD.Way(),
w2 = iD.Way({nodes: [a.id, b.id]}),
midpoint = {loc: [1, 2], ways: [{id: w1.id, index: 0}, {id: w2.id, index: 1}]},
graph = iD.actions.AddMidpoint(midpoint, node)(iD.Graph([a, b, w1, w2]));
expect(graph.entity(w1.id).nodes).to.eql([node.id]);
expect(graph.entity(w2.id).nodes).to.eql([a.id, node.id, b.id]);
});
});
+2 -3
View File
@@ -31,8 +31,7 @@ describe("iD.actions.DeleteWay", function () {
expect(graph.entity(node.id)).not.to.be.undefined;
});
// See #508
xit("deletes multiple member nodes", function () {
it("deletes multiple member nodes", function () {
var a = iD.Node(),
b = iD.Node(),
way = iD.Way({nodes: [a.id, b.id]}),
@@ -42,7 +41,7 @@ describe("iD.actions.DeleteWay", function () {
expect(graph.entity(b.id)).to.be.undefined;
});
xit("deletes a circular way's start/end node", function () {
it("deletes a circular way's start/end node", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
+4 -4
View File
@@ -26,24 +26,24 @@ describe('iD.Connection', function () {
c.loadFromURL('data/node.xml', done);
});
it('returns a graph', function (done) {
it('returns an object', function (done) {
c.loadFromURL('data/node.xml', function (err, graph) {
expect(err).to.not.be.ok;
expect(graph).to.be.instanceOf(iD.Graph);
expect(typeof graph).to.eql('object');
done();
});
});
it('parses a node', function (done) {
c.loadFromURL('data/node.xml', function (err, graph) {
expect(graph.entity('n356552551')).to.be.instanceOf(iD.Entity);
expect(graph.n356552551).to.be.instanceOf(iD.Entity);
done();
});
});
it('parses a way', function (done) {
c.loadFromURL('data/way.xml', function (err, graph) {
expect(graph.entity('w19698713')).to.be.instanceOf(iD.Entity);
expect(graph.w19698713).to.be.instanceOf(iD.Entity);
done();
});
});
+96
View File
@@ -0,0 +1,96 @@
describe('iD.geo', function() {
describe('.roundCoords', function() {
expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]);
expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]);
expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]);
});
describe('.interp', function() {
it('interpolates halfway', function() {
var a = [0, 0],
b = [10, 10];
expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]);
});
it('interpolates to one side', function() {
var a = [0, 0],
b = [10, 10];
expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]);
});
});
describe('.dist', function() {
it('distance between two same points is zero', function() {
var a = [0, 0],
b = [0, 0];
expect(iD.geo.dist(a, b)).to.eql(0);
});
it('a straight 10 unit line is 10', function() {
var a = [0, 0],
b = [10, 0];
expect(iD.geo.dist(a, b)).to.eql(10);
});
it('a pythagorean triangle is right', function() {
var a = [0, 0],
b = [4, 3];
expect(iD.geo.dist(a, b)).to.eql(5);
});
});
describe('.pointInPolygon', function() {
it('says a point in a polygon is on a polygon', function() {
var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
var point = [0.5, 0.5];
expect(iD.geo.pointInPolygon(point, poly)).to.be.true;
});
it('says a point outside of a polygon is outside', function() {
var poly = [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
[0, 0]];
var point = [0.5, 1.5];
expect(iD.geo.pointInPolygon(point, poly)).to.be.false;
});
});
describe('.polygonContainsPolygon', function() {
it('says a polygon in a polygon is in', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true;
});
it('says a polygon outside of a polygon is out', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false;
});
});
describe('.polygonIntersectsPolygon', function() {
it('says a polygon in a polygon intersects it', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
});
it('says a polygon that partially intersects does', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
});
it('says totally disjoint polygons do not intersect', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false;
});
});
describe('.pathLength', function() {
it('calculates a simple path length', function() {
var path = [[0, 0], [0, 1], [3, 5]];
expect(iD.geo.pathLength(path)).to.eql(6);
});
});
});
+221 -25
View File
@@ -1,25 +1,164 @@
describe('iD.Graph', function() {
it("can be constructed with an entities Object", function () {
var entity = iD.Entity(),
graph = iD.Graph({'n-1': entity});
expect(graph.entity('n-1')).to.equal(entity);
});
it("can be constructed with an entities Array", function () {
var entity = iD.Entity(),
graph = iD.Graph([entity]);
expect(graph.entity(entity.id)).to.equal(entity);
});
if (iD.debug) {
it("is frozen", function () {
expect(Object.isFrozen(iD.Graph())).to.be.true;
describe("constructor", function () {
it("accepts an entities Object", function () {
var entity = iD.Entity(),
graph = iD.Graph({'n-1': entity});
expect(graph.entity('n-1')).to.equal(entity);
});
it("freezes entities", function () {
expect(Object.isFrozen(iD.Graph().entities)).to.be.true;
it("accepts an entities Array", function () {
var entity = iD.Entity(),
graph = iD.Graph([entity]);
expect(graph.entity(entity.id)).to.equal(entity);
});
}
it("accepts a Graph", function () {
var entity = iD.Entity(),
graph = iD.Graph(iD.Graph([entity]));
expect(graph.entity(entity.id)).to.equal(entity);
});
it("copies other's entities", function () {
var entity = iD.Entity(),
base = iD.Graph([entity]),
graph = iD.Graph(base);
expect(graph.entities).not.to.equal(base.entities);
});
it("rebases on other's base", function () {
var base = iD.Graph(),
graph = iD.Graph(base);
expect(graph.base().entities).to.equal(base.base().entities);
});
it("freezes by default", function () {
expect(iD.Graph().frozen).to.be.true;
});
it("remains mutable if passed true as second argument", function () {
expect(iD.Graph([], true).frozen).not.to.be.true;
});
});
describe("#freeze", function () {
it("sets the frozen flag", function () {
expect(iD.Graph([], true).freeze().frozen).to.be.true;
});
if (iD.debug) {
it("freezes entities", function () {
expect(Object.isFrozen(iD.Graph().entities)).to.be.true;
});
}
});
describe("#rebase", function () {
it("preserves existing entities", function () {
var node = iD.Node({id: 'n'}),
graph = iD.Graph([node]);
graph.rebase({});
expect(graph.entity('n')).to.equal(node);
});
it("includes new entities", function () {
var node = iD.Node({id: 'n'}),
graph = iD.Graph();
graph.rebase({'n': node});
expect(graph.entity('n')).to.equal(node);
});
it("gives precedence to existing entities", function () {
var a = iD.Node({id: 'n'}),
b = iD.Node({id: 'n'}),
graph = iD.Graph([a]);
graph.rebase({'n': b});
expect(graph.entity('n')).to.equal(a);
});
it("inherits entities from base prototypally", function () {
var graph = iD.Graph();
graph.rebase({'n': iD.Node()});
expect(graph.entities).not.to.have.ownProperty('n');
});
it("updates parentWays", function () {
var n = iD.Node({id: 'n'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
w2 = iD.Way({id: 'w2', nodes: ['n']}),
graph = iD.Graph([n, w1]);
graph.rebase({ 'w2': w2 });
expect(graph.parentWays(n)).to.eql([w1, w2]);
expect(graph._parentWays.hasOwnProperty('n')).to.be.false;
});
it("avoids adding duplicate parentWays", function () {
var n = iD.Node({id: 'n'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
graph = iD.Graph([n, w1]);
graph.rebase({ 'w1': w1 });
expect(graph.parentWays(n)).to.eql([w1]);
});
it("updates parentWays for nodes with modified parentWays", function () {
var n = iD.Node({id: 'n'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
w2 = iD.Way({id: 'w2', nodes: ['n']}),
w3 = iD.Way({id: 'w3', nodes: ['n']}),
graph = iD.Graph([n, w1]),
graph2 = graph.replace(w2);
graph.rebase({ 'w3': w3 });
graph2.rebase({ 'w3': w3 });
expect(graph2.parentWays(n)).to.eql([w1, w2, w3]);
});
it("avoids re-adding removed parentWays", function() {
var n = iD.Node({id: 'n'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
graph = iD.Graph([n, w1]),
graph2 = graph.remove(w1);
graph.rebase({ 'w1': w1 });
graph2.rebase({ 'w1': w1 });
expect(graph2.parentWays(n)).to.eql([]);
});
it("updates parentRelations", function () {
var n = iD.Node({id: 'n'}),
r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}),
r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}),
graph = iD.Graph([n, r1]);
graph.rebase({'r2': r2});
expect(graph.parentRelations(n)).to.eql([r1, r2]);
expect(graph._parentRels.hasOwnProperty('n')).to.be.false;
});
it("avoids re-adding removed parentRels", function() {
var n = iD.Node({id: 'n'}),
r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}),
graph = iD.Graph([n, r1]),
graph2 = graph.remove(r1);
graph.rebase({ 'w1': r1 });
graph2.rebase({ 'w1': r1 });
expect(graph2.parentWays(n)).to.eql([]);
});
it("updates parentRels for nodes with modified parentWays", function () {
var n = iD.Node({id: 'n'}),
r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}),
r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}),
r3 = iD.Relation({id: 'r3', members: [{id: 'n'}]}),
graph = iD.Graph([n, r1]),
graph2 = graph.replace(r2);
graph.rebase({'r3': r3});
graph2.rebase({'r3': r3});
expect(graph2.parentRelations(n)).to.eql([r1, r2, r3]);
});
});
describe("#remove", function () {
it("returns a new graph", function () {
@@ -40,6 +179,20 @@ describe('iD.Graph', function() {
graph = iD.Graph([node]);
expect(graph.remove(node).entity(node.id)).to.be.undefined;
});
it("removes the entity as a parentWay", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node, w1]);
expect(graph.remove(w1).parentWays(node)).to.eql([]);
});
it("removes the entity as a parentRelation", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n' }]}),
graph = iD.Graph([node, r1]);
expect(graph.remove(r1).parentRelations(node)).to.eql([]);
});
});
describe("#replace", function () {
@@ -62,6 +215,49 @@ describe('iD.Graph', function() {
graph = iD.Graph([node1]);
expect(graph.replace(node2).entity(node2.id)).to.equal(node2);
});
it("adds parentWays", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node]);
expect(graph.replace(w1).parentWays(node)).to.eql([w1]);
});
it("removes parentWays", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node, w1]);
expect(graph.remove(w1).parentWays(node)).to.eql([]);
});
it("doesn't add duplicate parentWays", function () {
var node = iD.Node({id: 'n' }),
w1 = iD.Way({id: 'w', nodes: ['n']}),
graph = iD.Graph([node, w1]);
expect(graph.replace(w1).parentWays(node)).to.eql([w1]);
});
it("adds parentRels", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}),
graph = iD.Graph([node]);
expect(graph.replace(r1).parentRelations(node)).to.eql([r1]);
});
it("removes parentRelations", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}),
graph = iD.Graph([node, r1]);
expect(graph.remove(r1).parentRelations(node)).to.eql([]);
});
it("doesn't add duplicate parentRelations", function () {
var node = iD.Node({id: 'n' }),
r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}),
graph = iD.Graph([node, r1]);
expect(graph.replace(r1).parentRelations(node)).to.eql([r1]);
});
});
describe("#update", function () {
@@ -159,18 +355,18 @@ describe('iD.Graph', function() {
describe("#modified", function () {
it("returns an Array of ids of modified entities", function () {
var node1 = iD.Node({id: 'n1', _updated: true}),
node2 = iD.Node({id: 'n2'}),
graph = iD.Graph([node1, node2]);
expect(graph.modified()).to.eql([node1.id]);
var node = iD.Node({id: 'n1'}),
node_ = iD.Node({id: 'n1'}),
graph = iD.Graph([node]).replace(node_);
expect(graph.modified()).to.eql([node.id]);
});
});
describe("#created", function () {
it("returns an Array of ids of created entities", function () {
var node1 = iD.Node({id: 'n-1', _updated: true}),
var node1 = iD.Node({id: 'n-1'}),
node2 = iD.Node({id: 'n2'}),
graph = iD.Graph([node1, node2]);
graph = iD.Graph([node2]).replace(node1);
expect(graph.created()).to.eql([node1.id]);
});
});
@@ -185,7 +381,7 @@ describe('iD.Graph', function() {
it("doesn't include created entities that were subsequently deleted", function () {
var node = iD.Node(),
graph = iD.Graph([node]).remove(node);
graph = iD.Graph().replace(node).remove(node);
expect(graph.deleted()).to.eql([]);
});
});
+5 -7
View File
@@ -153,17 +153,15 @@ describe("iD.History", function () {
it("includes modified entities", function () {
var node1 = iD.Node({id: "n1"}),
node2 = node1.update({}),
graph = iD.Graph([node1]);
history.merge(graph);
node2 = node1.update({});
history.merge({ n1: node1});
history.perform(function (graph) { return graph.replace(node2); });
expect(history.changes().modified).to.eql([node2]);
});
it("includes deleted entities", function () {
var node = iD.Node({id: "n1"}),
graph = iD.Graph([node]);
history.merge(graph);
var node = iD.Node({id: "n1"});
history.merge({ n1: node });
history.perform(function (graph) { return graph.remove(node); });
expect(history.changes().deleted).to.eql([node]);
});
@@ -189,7 +187,7 @@ describe("iD.History", function () {
it("is the sum of all types of changes", function() {
var node1 = iD.Node({id: "n1"}),
node2 = iD.Node();
history.merge(iD.Graph([node1]));
history.merge({ n1: node1 });
history.perform(function (graph) { return graph.remove(node1); });
expect(history.numChanges()).to.eql(1);
history.perform(function (graph) { return graph.replace(node2); });
+52
View File
@@ -0,0 +1,52 @@
describe("iD.svg.Midpoints", function () {
var surface,
projection = Object,
filter = d3.functor(true);
beforeEach(function () {
surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))
.call(iD.svg.Surface());
});
it("finds the location of the midpoints", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [50, 0]}),
line = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, line]);
surface.call(iD.svg.Midpoints(projection), graph, [line], filter);
expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]);
});
it("doesn't create midpoints on segments with pixel length less than 40", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [39, 0]}),
line = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, line]);
surface.call(iD.svg.Midpoints(projection), graph, [line], filter);
expect(surface.selectAll('.midpoint')[0]).to.have.length(0);
});
it("binds a datum whose 'ways' property lists ways which include the segement", function () {
var a = iD.Node({loc: [0, 0]}),
b = iD.Node({loc: [50, 0]}),
c = iD.Node({loc: [1, 1]}),
d = iD.Node({loc: [2, 2]}),
l1 = iD.Way({nodes: [a.id, b.id]}),
l2 = iD.Way({nodes: [b.id, a.id]}),
l3 = iD.Way({nodes: [c.id, a.id, b.id, d.id]}),
l4 = iD.Way({nodes: [a.id, d.id, b.id]}),
graph = iD.Graph([a, b, c, d, l1, l2, l3, l4]),
ab = function (d) { return d.id === [a.id, b.id].sort().join("-"); };
surface.call(iD.svg.Midpoints(projection), graph, [l1, l2, l3, l4], filter);
expect(surface.selectAll('.midpoint').filter(ab).datum().ways).to.eql([
{id: l1.id, index: 1},
{id: l2.id, index: 1},
{id: l3.id, index: 2}]);
});
});
+4 -103
View File
@@ -1,123 +1,24 @@
describe('iD.Util', function() {
var util;
it('#trueObj', function() {
it('.trueObj', function() {
expect(iD.util.trueObj(['a', 'b', 'c'])).to.eql({ a: true, b: true, c: true });
expect(iD.util.trueObj([])).to.eql({});
});
it('#tagText', function() {
it('.tagText', function() {
expect(iD.util.tagText({})).to.eql('');
expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo: bar');
expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo: bar\ntwo: three');
});
it('#stringQs', function() {
it('.stringQs', function() {
expect(iD.util.stringQs('foo=bar')).to.eql({foo: 'bar'});
expect(iD.util.stringQs('foo=bar&one=2')).to.eql({foo: 'bar', one: '2' });
expect(iD.util.stringQs('')).to.eql({});
});
it('#qsString', function() {
it('.qsString', function() {
expect(iD.util.qsString({ foo: 'bar' })).to.eql('foo=bar');
expect(iD.util.qsString({ foo: 'bar', one: 2 })).to.eql('foo=bar&one=2');
expect(iD.util.qsString({})).to.eql('');
});
describe('geo', function() {
describe('#roundCoords', function() {
expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]);
expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]);
expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]);
});
describe('#interp', function() {
it('interpolates halfway', function() {
var a = [0, 0],
b = [10, 10];
expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]);
});
it('interpolates to one side', function() {
var a = [0, 0],
b = [10, 10];
expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]);
});
});
describe('#dist', function() {
it('distance between two same points is zero', function() {
var a = [0, 0],
b = [0, 0];
expect(iD.geo.dist(a, b)).to.eql(0);
});
it('a straight 10 unit line is 10', function() {
var a = [0, 0],
b = [10, 0];
expect(iD.geo.dist(a, b)).to.eql(10);
});
it('a pythagorean triangle is right', function() {
var a = [0, 0],
b = [4, 3];
expect(iD.geo.dist(a, b)).to.eql(5);
});
});
describe('#pointInPolygon', function() {
it('says a point in a polygon is on a polygon', function() {
var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
var point = [0.5, 0.5];
expect(iD.geo.pointInPolygon(point, poly)).to.be.true;
});
it('says a point outside of a polygon is outside', function() {
var poly = [
[0, 0],
[0, 1],
[1, 1],
[1, 0],
[0, 0]];
var point = [0.5, 1.5];
expect(iD.geo.pointInPolygon(point, poly)).to.be.false;
});
});
describe('#polygonContainsPolygon', function() {
it('says a polygon in a polygon is in', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true;
});
it('says a polygon outside of a polygon is out', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false;
});
});
describe('#polygonIntersectsPolygon', function() {
it('says a polygon in a polygon intersects it', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
});
it('says a polygon that partially intersects does', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
});
it('says totally disjoint polygons do not intersect', function() {
var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false;
});
});
describe('#pathLength', function() {
it('calculates a simple path length', function() {
var path = [[0, 0], [0, 1], [3, 5]];
expect(iD.geo.pathLength(path)).to.eql(6);
});
});
});
});