Merge branch 'master' into dynamic-layers

Conflicts:
	js/id/renderer/background_source.js
	js/id/ui/layerswitcher.js
This commit is contained in:
Tom MacWright
2013-02-06 17:39:55 -05:00
42 changed files with 724 additions and 398 deletions
-1
View File
@@ -90,7 +90,6 @@
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
<script src='js/id/behavior/drag.js'></script>
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
+33 -59
View File
@@ -181,95 +181,72 @@ path.shadow.selected {
}
path.area.stroke,
path.multipolygon {
path.line.member-type-multipolygon.stroke {
stroke-width:2;
stroke:#fff;
}
path.area.fill,
path.multipolygon {
fill:#fff;
fill-opacity:0.3;
}
path.multipolygon {
fill-rule: evenodd;
}
path.area.fill.member-type-multipolygon {
fill: none;
}
path.area.stroke.selected {
path.area.stroke.selected,
path.line.member-type-multipolygon.stroke.selected {
stroke-width:4 !important;
}
path.area.stroke.tag-natural,
path.multipolygon.tag-natural {
path.area.stroke {
stroke:#fff;
}
path.area.fill {
fill:#fff;
fill-opacity:0.3;
fill-rule: evenodd;
}
path.stroke.tag-natural {
stroke: #b6e199;
stroke-width:1;
}
path.area.fill.tag-natural,
path.multipolygon.tag-natural {
path.fill.tag-natural {
fill: #b6e199;
}
path.area.stroke.tag-natural-water,
path.multipolygon.tag-natural-water {
path.stroke.tag-natural-water {
stroke: #77d3de;
}
path.area.fill.tag-natural-water,
path.multipolygon.tag-natural-water {
path.fill.tag-natural-water {
fill: #77d3de;
}
path.area.stroke.tag-building,
path.multipolygon.tag-building {
path.stroke.tag-building {
stroke: #e06e5f;
stroke-width: 1;
}
path.area.fill.tag-building,
path.multipolygon.tag-building {
path.fill.tag-building {
fill: #e06e5f;
}
path.area.stroke.tag-landuse,
path.area.stroke.tag-natural-wood,
path.area.stroke.tag-natural-tree,
path.area.stroke.tag-natural-grassland,
path.area.stroke.tag-leisure-park,
path.multipolygon.tag-landuse,
path.multipolygon.tag-natural-wood,
path.multipolygon.tag-natural-tree,
path.multipolygon.tag-natural-grassland,
path.multipolygon.tag-leisure-park {
path.stroke.tag-landuse,
path.stroke.tag-natural-wood,
path.stroke.tag-natural-tree,
path.stroke.tag-natural-grassland,
path.stroke.tag-leisure-park {
stroke: #8cd05f;
stroke-width: 1;
}
path.area.fill.tag-landuse,
path.area.fill.tag-natural-wood,
path.area.fill.tag-natural-tree,
path.area.fill.tag-natural-grassland,
path.area.fill.tag-leisure-park,
path.multipolygon.tag-landuse,
path.multipolygon.tag-natural-wood,
path.multipolygon.tag-natural-tree,
path.multipolygon.tag-natural-grassland,
path.multipolygon.tag-leisure-park {
path.fill.tag-landuse,
path.fill.tag-natural-wood,
path.fill.tag-natural-tree,
path.fill.tag-natural-grassland,
path.fill.tag-leisure-park {
fill: #8cd05f;
fill-opacity: 0.2;
}
path.area.stroke.tag-amenity-parking,
path.multipolygon.tag-amenity-parking {
path.stroke.tag-amenity-parking {
stroke: #aaa;
stroke-width: 1;
}
path.area.fill.tag-amenity-parking,
path.multipolygon.tag-amenity-parking {
path.fill.tag-amenity-parking {
fill: #aaa;
}
path.multipolygon.tag-boundary {
path.fill.tag-boundary {
fill: none;
}
@@ -526,7 +503,7 @@ path.casing.tag-railway-subway {
/* waterways */
path.area.fill.tag-waterway {
path.fill.tag-waterway {
fill: #77d3de;
}
@@ -686,9 +663,7 @@ text.point {
}
.mode-select .area,
.mode-browse .area,
.mode-select .multipolygon,
.mode-browse .multipolygon {
.mode-browse .area {
cursor: url(../img/cursor-select-area.png), pointer;
}
@@ -701,7 +676,6 @@ text.point {
.vertex:active,
.line:active,
.area:active,
.multipolygon:active,
.midpoint:active,
.mode-select .selected {
cursor: url(../img/cursor-select-acting.png), pointer;
+11 -2
View File
@@ -49,7 +49,6 @@
<script src="js/id/svg/lines.js"></script>
<script src="js/id/svg/member_classes.js"></script>
<script src="js/id/svg/midpoints.js"></script>
<script src="js/id/svg/multipolygons.js"></script>
<script src="js/id/svg/points.js"></script>
<script src="js/id/svg/surface.js"></script>
<script src="js/id/svg/tag_classes.js"></script>
@@ -73,6 +72,7 @@
<script src='js/id/ui/flash.js'></script>
<script src='js/id/ui/save.js'></script>
<script src='js/id/ui/splash.js'></script>
<script src='js/id/ui/restore.js'></script>
<script src='js/id/ui/tag_reference.js'></script>
<script src='js/id/ui/key_reference.js'></script>
@@ -99,7 +99,6 @@
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
<script src='js/id/behavior/drag.js'></script>
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
@@ -156,6 +155,8 @@
.call(id.ui())
});
</script>
<!-- google analytics -->
<script type='text/javascript'>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-38039653-2']);
@@ -166,5 +167,13 @@ _gaq.push(['_trackPageview']);
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<!-- crazyegg -->
<script type="text/javascript">
setTimeout(function(){var a=document.createElement("script");
var b=document.getElementsByTagName("script")[0];
a.src=document.location.protocol+"//dnn506yrbagrg.cloudfront.net/pages/scripts/0013/6714.js?"+Math.floor(new Date().getTime()/3600000);
a.async=true;a.type="text/javascript";b.parentNode.insertBefore(a,b)}, 1);
</script>
</body>
</html>
+9 -9
View File
@@ -14,7 +14,7 @@
* Delegation is supported via the `delegate` function.
*/
iD.behavior.drag = function () {
iD.behavior.drag = function() {
function d3_eventCancel() {
d3.event.stopPropagation();
d3.event.preventDefault();
@@ -50,21 +50,21 @@ iD.behavior.drag = function () {
moved = 0;
var w = d3.select(window)
.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove)
.on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true);
.on(touchId !== null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove)
.on(touchId !== null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true);
if (origin) {
offset = origin.apply(target, arguments);
offset = [ offset[0] - origin_[0], offset[1] - origin_[1] ];
offset = [offset[0] - origin_[0], offset[1] - origin_[1]];
} else {
offset = [ 0, 0 ];
offset = [0, 0];
}
if (touchId == null) d3_eventCancel();
if (touchId === null) d3_eventCancel();
function point() {
var p = target.parentNode;
return touchId != null ? d3.touches(p).filter(function (p) {
return touchId !== null ? d3.touches(p).filter(function(p) {
return p.identifier === touchId;
})[0] : d3.mouse(p);
}
@@ -103,8 +103,8 @@ iD.behavior.drag = function () {
if (d3.event.target === eventTarget) w.on("click.drag", click, true);
}
w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null)
.on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null);
w.on(touchId !== null ? "touchmove.drag-" + touchId : "mousemove.drag", null)
.on(touchId !== null ? "touchend.drag-" + touchId : "mouseup.drag", null);
}
function click() {
-29
View File
@@ -1,29 +0,0 @@
iD.behavior.DragMidpoint = function(context) {
var behavior = iD.behavior.drag()
.delegate(".midpoint")
.origin(function(d) {
return context.projection(d.loc);
})
.on('start', function(d) {
var node = iD.Node();
context.perform(iD.actions.AddMidpoint(d, node));
var vertex = context.surface().selectAll('.vertex')
.filter(function(data) { return data.id === node.id; });
behavior.target(vertex.node(), vertex.datum());
})
.on('move', function(d) {
d3.event.sourceEvent.stopPropagation();
context.replace(
iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point)));
})
.on('end', function() {
context.replace(
iD.actions.Noop(),
t('operations.add.annotation.vertex'));
});
return behavior;
};
+29 -7
View File
@@ -1,5 +1,6 @@
iD.behavior.DragNode = function(context) {
var nudgeInterval;
var nudgeInterval,
wasMidpoint;
function edge(point, size) {
var pad = [30, 100, 30, 100];
@@ -35,6 +36,23 @@ iD.behavior.DragNode = function(context) {
}
function start(entity) {
wasMidpoint = entity.type === 'midpoint';
if (wasMidpoint) {
var midpoint = entity;
entity = iD.Node();
context.perform(iD.actions.AddMidpoint(midpoint, entity));
var vertex = context.surface()
.selectAll('.vertex')
.filter(function(d) { return d.id === entity.id; });
behavior.target(vertex.node(), entity);
} else {
context.perform(
iD.actions.Noop());
}
var activeIDs = _.pluck(context.graph().parentWays(entity), 'id');
activeIDs.push(entity.id);
@@ -43,9 +61,6 @@ iD.behavior.DragNode = function(context) {
.selectAll('.node, .way')
.filter(function (d) { return activeIDs.indexOf(d.id) >= 0; })
.classed('active', true);
context.perform(
iD.actions.Noop());
}
function datum() {
@@ -66,7 +81,7 @@ iD.behavior.DragNode = function(context) {
var loc = context.map().mouseCoordinates();
var d = datum();
if (d.type === 'node') {
if (d.type === 'node' && d.id !== entity.id) {
loc = d.loc;
} else if (d.type === 'way') {
loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc;
@@ -96,6 +111,11 @@ iD.behavior.DragNode = function(context) {
iD.actions.Connect([entity.id, d.id]),
connectAnnotation(d));
} else if (wasMidpoint) {
context.replace(
iD.actions.Noop(),
t('operations.add.annotation.vertex'));
} else {
context.replace(
iD.actions.Noop(),
@@ -103,10 +123,12 @@ iD.behavior.DragNode = function(context) {
}
}
return iD.behavior.drag()
.delegate("g.node")
var behavior = iD.behavior.drag()
.delegate("g.node, g.midpoint")
.origin(origin)
.on('start', start)
.on('move', move)
.on('end', end);
return behavior;
};
+31 -17
View File
@@ -1,23 +1,39 @@
iD.behavior.Draw = function(context) {
var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish'),
var event = d3.dispatch('move', 'click', 'clickWay',
'clickNode', 'undo', 'cancel', 'finish'),
keybinding = d3.keybinding('draw'),
hover = iD.behavior.Hover();
hover = iD.behavior.Hover(),
closeTolerance = 4,
tolerance = 12;
function datum() {
if (d3.event.altKey) {
return {};
} else {
return d3.event.target.__data__ || {};
}
if (d3.event.altKey) return {};
else return d3.event.target.__data__ || {};
}
function mousedown() {
var selection = d3.select(this);
selection.on('mousemove.draw', null);
d3.select(window)
.on('mouseup.draw', function() {
selection.on('mousemove.draw', mousemove);
function point() {
var p = target.node().parentNode;
return touchId !== null ? d3.touches(p).filter(function(p) {
return p.identifier === touchId;
})[0] : d3.mouse(p);
}
var target = d3.select(this),
touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null,
time = +new Date(),
pos = point();
target.on('mousemove.draw', null);
d3.select(window).on('click.draw', function() {
target.on('mousemove.draw', mousemove);
if (iD.geo.dist(pos, point()) < closeTolerance ||
(iD.geo.dist(pos, point()) < tolerance &&
(+new Date() - time) < 500)) {
click();
}
});
}
@@ -77,8 +93,7 @@ iD.behavior.Draw = function(context) {
selection
.on('mousedown.draw', mousedown)
.on('mousemove.draw', mousemove)
.on('click.draw', click);
.on('mousemove.draw', mousemove);
d3.select(document)
.call(keybinding)
@@ -93,10 +108,9 @@ iD.behavior.Draw = function(context) {
selection
.on('mousedown.draw', null)
.on('mousemove.draw', null)
.on('click.draw', null);
.on('mousemove.draw', null);
d3.select(window).on('mouseup.draw', null);
d3.select(window).on('click.draw', null);
d3.select(document)
.call(keybinding.off)
+7 -1
View File
@@ -28,7 +28,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
function move(datum) {
var loc = context.map().mouseCoordinates();
if (datum.type === 'node') {
if (datum.id === end.id || datum.id === segment.id) {
context.surface().selectAll('.way, .node')
.filter(function (d) {
return d.id === end.id || d.id === segment.id;
})
.classed('active', true);
} else if (datum.type === 'node') {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
+1 -1
View File
@@ -29,7 +29,7 @@ iD.behavior.Hash = function(context) {
var move = _.throttle(function() {
var s1 = formatter(context.map());
if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map!
}, 100);
}, 500);
function hashchange() {
if (location.hash === s0) return; // ignore spurious hashchange events
+57 -14
View File
@@ -1,23 +1,66 @@
iD.behavior.Select = function(context) {
function click() {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
if (d3.event.shiftKey) {
context.enter(iD.modes.Select(context, context.selection().concat([datum.id])));
} else {
context.enter(iD.modes.Select(context, [datum.id]));
}
} else if (!d3.event.shiftKey) {
context.enter(iD.modes.Browse(context));
}
}
var behavior = function(selection) {
selection.on('click.select', click);
var timeout = null,
// the position of the first mousedown
pos = null;
function click(event) {
d3.event = event;
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
if (d3.event.shiftKey) {
context.enter(iD.modes.Select(context, context.selection().concat([datum.id])));
} else {
context.enter(iD.modes.Select(context, [datum.id]));
}
} else if (!d3.event.shiftKey) {
context.enter(iD.modes.Browse(context));
}
}
function mousedown() {
pos = d3.mouse(context.surface().node());
selection
.on('mousemove.select', mousemove)
.on('touchmove.select', mousemove);
// we've seen a mousedown within 400ms of this one, so ignore
// both because they will be a double click
if (timeout !== null) {
window.clearTimeout(timeout);
selection.on('mousemove.select', null);
timeout = null;
} else {
// queue the click handler to fire in 400ms if no other clicks
// are detected
timeout = window.setTimeout((function(event) {
return function() {
click(event);
timeout = null;
selection.on('mousemove.select', null);
};
// save the event for the click handler
})(d3.event), 400);
}
}
// allow mousemoves to cancel the click
function mousemove() {
if (iD.geo.dist(d3.mouse(context.surface().node()), pos) > 4) {
window.clearTimeout(timeout);
timeout = null;
}
}
selection
.on('mousedown.select', mousedown)
.on('touchstart.select', mousedown);
};
behavior.off = function(selection) {
selection.on('click.select', null);
selection.on('mousedown.select', null);
};
return behavior;
+44 -1
View File
@@ -227,10 +227,53 @@ iD.Graph.prototype = {
var items = [];
for (var i in this.entities) {
var entity = this.entities[i];
if (entity && entity.intersects(extent, this)) {
if (entity && this.hasAllChildren(entity) && entity.intersects(extent, this)) {
items.push(entity);
}
}
return items;
},
hasAllChildren: function(entity) {
// we're only checking changed entities, since we assume fetched data
// must have all children present
if (this.entities.hasOwnProperty(entity.id)) {
if (entity.type === 'way') {
for (i = 0; i < entity.nodes.length; i++) {
if (!this.entities[entity.nodes[i]]) return false;
}
} else if (entity.type === 'relation') {
for (i = 0; i < entity.members.length; i++) {
if (!this.entities[entity.members[i].id]) return false;
}
}
}
return true;
},
// Obliterates any existing entities
load: function(entities) {
var base = this.base(),
i, entity, prefix;
this.entities = Object.create(base.entities);
for (i in entities) {
entity = entities[i];
prefix = i[0];
if (prefix == 'n') {
this.entities[i] = new iD.Node(entity);
} else if (prefix == 'w') {
this.entities[i] = new iD.Way(entity);
} else if (prefix == 'r') {
this.entities[i] = new iD.Relation(entity);
}
this._updateCalculated(base.entities[i], this.entities[i]);
}
return this;
}
};
+65 -2
View File
@@ -1,7 +1,8 @@
iD.History = function() {
iD.History = function(context) {
var stack, index,
imagery_used = 'Bing',
dispatch = d3.dispatch('change', 'undone', 'redone');
dispatch = d3.dispatch('change', 'undone', 'redone'),
lock = false;
function perform(actions) {
actions = Array.prototype.slice.call(actions);
@@ -26,6 +27,10 @@ iD.History = function() {
return difference;
}
function getKey(n) {
return 'iD_' + window.location.origin + '_' + n;
}
var history = {
graph: function() {
return stack[index].graph;
@@ -149,7 +154,65 @@ iD.History = function() {
stack = [{graph: iD.Graph()}];
index = 0;
dispatch.change();
},
save: function() {
if (!lock) return;
context.storage(getKey('lock'), null);
if (!stack.length) {
context.storage(getKey('history'), null);
context.storage(getKey('nextIDs'), null);
context.storage(getKey('index'), null);
return;
}
var json = JSON.stringify(stack.map(function(i) {
return _.extend(i, {
graph: i.graph.entities
});
}));
context.storage(getKey('history'), json);
context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next));
context.storage(getKey('index'), index);
},
lock: function() {
if (context.storage(getKey('lock'))) return false;
context.storage(getKey('lock'), true);
lock = true;
return lock;
},
restorableChanges: function() {
if (!this.lock()) return false;
return !!context.storage(getKey('history'));
},
load: function() {
if (!lock) return;
var json = context.storage(getKey('history')),
nextIDs = context.storage(getKey('nextIDs')),
index_ = context.storage(getKey('index'));
if (!json) return;
if (nextIDs) iD.Entity.id.next = JSON.parse(nextIDs);
if (index_ !== null) index = parseInt(index_, 10);
context.storage(getKey('history', null));
context.storage(getKey('nextIDs', null));
context.storage(getKey('index', null));
stack = JSON.parse(json).map(function(d, i) {
d.graph = iD.Graph(stack[0].graph).load(d.graph);
return d;
});
dispatch.change();
}
};
history.reset();
+29 -6
View File
@@ -26,7 +26,7 @@ _.extend(iD.Relation.prototype, {
},
geometry: function() {
return 'relation';
return this.isMultipolygon() ? 'area' : 'relation';
},
// Return the first member with the given role. A copy of the member object
@@ -83,6 +83,31 @@ _.extend(iD.Relation.prototype, {
return r;
},
asGeoJSON: function(resolver) {
if (this.isMultipolygon()) {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'MultiPolygon',
coordinates: this.multipolygon(resolver)
}
};
} else {
return {
type: 'FeatureCollection',
properties: this.tags,
features: this.members.map(function(member) {
return _.extend({role: member.role}, resolver.entity(member.id).asGeoJSON(resolver));
})
};
}
},
isMultipolygon: function() {
return this.tags.type === 'multipolygon';
},
isRestriction: function() {
return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
},
@@ -145,22 +170,20 @@ _.extend(iD.Relation.prototype, {
}
}
return joined;
return joined.map(function (nodes) { return _.pluck(nodes, 'loc'); });
}
function findOuter(inner) {
var o, outer;
inner = _.pluck(inner, 'loc');
for (o = 0; o < outers.length; o++) {
outer = _.pluck(outers[o], 'loc');
outer = outers[o];
if (iD.geo.polygonContainsPolygon(outer, inner))
return o;
}
for (o = 0; o < outers.length; o++) {
outer = _.pluck(outers[o], 'loc');
outer = outers[o];
if (iD.geo.polygonIntersectsPolygon(outer, inner))
return o;
}
+20 -8
View File
@@ -49,6 +49,7 @@ _.extend(iD.Way.prototype, {
isArea: function() {
return this.tags.area === 'yes' ||
(this.isClosed() &&
!_.isEmpty(this.tags) &&
this.tags.area !== 'no' &&
!this.tags.highway &&
!this.tags.barrier);
@@ -103,13 +104,24 @@ _.extend(iD.Way.prototype, {
},
asGeoJSON: function(resolver) {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'LineString',
coordinates: _.pluck(resolver.childNodes(this), 'loc')
}
};
if (this.isArea()) {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'Polygon',
coordinates: [_.pluck(resolver.childNodes(this), 'loc')]
}
};
} else {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'LineString',
coordinates: _.pluck(resolver.childNodes(this), 'loc')
}
};
}
}
});
+9 -7
View File
@@ -1,18 +1,20 @@
window.iD = function () {
var context = {},
history = iD.History(),
storage = localStorage || {},
storage = localStorage || {};
context.storage = function(k, v) {
if (arguments.length === 1) return storage[k];
else if (v === null) delete storage[k];
else storage[k] = v;
};
var history = iD.History(context),
dispatch = d3.dispatch('enter', 'exit'),
mode,
container,
ui = iD.ui(context),
map = iD.Map(context);
context.storage = function(k, v) {
if (arguments.length === 1) return storage[k];
else storage[k] = v;
};
// the connection requires .storage() to be available on calling.
var connection = iD.Connection(context);
+1 -2
View File
@@ -10,8 +10,7 @@ iD.modes.Browse = function(context) {
var behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.DragNode(context),
iD.behavior.DragMidpoint(context)];
iD.behavior.DragNode(context)];
mode.enter = function() {
behaviors.forEach(function(behavior) {
+35 -7
View File
@@ -6,7 +6,8 @@ iD.modes.MoveWay = function(context, wayId) {
var keybinding = d3.keybinding('move-way');
mode.enter = function() {
var origin = point(),
var origin = context.map().mouseCoordinates(),
nudgeInterval,
annotation = t('operations.move.annotation.' + context.geometry(wayId));
// If intiated via keyboard
@@ -16,17 +17,44 @@ iD.modes.MoveWay = function(context, wayId) {
iD.actions.Noop(),
annotation);
function edge(point, size) {
var pad = [30, 100, 30, 100];
if (point[0] > size[0] - pad[0]) return [-10, 0];
else if (point[0] < pad[2]) return [10, 0];
else if (point[1] > size[1] - pad[1]) return [0, -10];
else if (point[1] < pad[3]) return [0, 10];
return null;
}
function startNudge(nudge) {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = window.setInterval(function() {
context.map().pan(nudge).redraw();
}, 50);
}
function stopNudge() {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = null;
}
function point() {
return d3.mouse(context.surface().node());
return d3.mouse(context.map().surface.node());
}
function move() {
var p = point(),
delta = origin ?
[p[0] - origin[0], p[1] - origin[1]] :
[0, 0];
var p = point();
origin = p;
var delta = origin ?
[p[0] - context.projection(origin)[0],
p[1] - context.projection(origin)[1]] :
[0, 0];
var nudge = edge(p, context.map().size());
if (nudge) startNudge(nudge);
else stopNudge();
origin = context.map().mouseCoordinates();
context.replace(
iD.actions.MoveWay(wayId, delta, context.projection),
+34 -6
View File
@@ -9,8 +9,7 @@ iD.modes.Select = function(context, selection, initial) {
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.DragNode(context),
iD.behavior.DragMidpoint(context)],
iD.behavior.DragNode(context)],
radialMenu;
function changeTags(d, tags) {
@@ -113,9 +112,29 @@ iD.modes.Select = function(context, selection, initial) {
d3.mouse(context.surface().node()), context),
node = iD.Node({ loc: choice.loc });
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddVertex(datum.id, node.id, choice.index),
var prev = datum.nodes[choice.index - 1],
next = datum.nodes[choice.index],
prevParents = context.graph().parentWays({ id: prev }),
ways = [];
for (var i = 0; i < prevParents.length; i++) {
var p = prevParents[i];
for (var k = 0; k < p.nodes.length; k++) {
if (p.nodes[k] === prev) {
if (p.nodes[k-1] === next) {
ways.push({ id: p.id, index: k});
break;
} else if (p.nodes[k+1] === next) {
ways.push({ id: p.id, index: k+1});
break;
}
}
}
}
context.perform(iD.actions.AddEntity(node),
iD.actions.AddMidpoint({ ways: ways, loc: node.loc }, node),
t('operations.add.annotation.vertex'));
d3.event.preventDefault();
@@ -123,13 +142,22 @@ iD.modes.Select = function(context, selection, initial) {
}
}
function selected(entity) {
if (!entity) return false;
if (selection.indexOf(entity.id) >= 0) return true;
return d3.select(this).classed('stroke') &&
_.any(context.graph().parentRelations(entity), function(parent) {
return selection.indexOf(parent.id) >= 0;
});
}
d3.select(document)
.call(keybinding);
context.surface()
.on('dblclick.select', dblclick)
.selectAll("*")
.filter(function(d) { return d && selection.indexOf(d.id) >= 0; })
.filter(selected)
.classed('selected', true);
radialMenu = iD.ui.RadialMenu(operations);
-2
View File
@@ -130,8 +130,6 @@ iD.Background = function() {
.on('load', load);
image.style(transformProp, imageTransform);
if (Object.keys(cache).length > 100) cache = {};
}
background.offset = function(_) {
-2
View File
@@ -19,7 +19,6 @@ iD.Map = function(context) {
vertices = iD.svg.Vertices(roundedProjection),
lines = iD.svg.Lines(roundedProjection),
areas = iD.svg.Areas(roundedProjection),
multipolygons = iD.svg.Multipolygons(roundedProjection),
midpoints = iD.svg.Midpoints(roundedProjection),
labels = iD.svg.Labels(roundedProjection),
tail = d3.tail(),
@@ -87,7 +86,6 @@ iD.Map = function(context) {
.call(vertices, graph, all, filter)
.call(lines, graph, all, filter)
.call(areas, graph, all, filter)
.call(multipolygons, graph, all, filter)
.call(midpoints, graph, all, filter)
.call(labels, graph, all, filter, dimensions, !difference);
}
+12
View File
@@ -27,5 +27,17 @@ iD.svg = {
return projection(n.loc);
}).join('L'));
};
},
MultipolygonMemberTags: function (graph) {
return function (entity) {
var tags = entity.tags;
graph.parentRelations(entity).forEach(function (relation) {
if (relation.isMultipolygon()) {
tags = _.extend({}, relation.tags, tags);
}
});
return tags;
}
}
};
+20 -14
View File
@@ -1,38 +1,39 @@
iD.svg.Areas = function(projection) {
return function drawAreas(surface, graph, entities, filter) {
var areas = [];
var path = d3.geo.path().projection(projection),
areas = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (entity.geometry(graph) === 'area') {
var points = graph.childNodes(entity).map(function(n) {
return projection(n.loc);
});
areas.push({
entity: entity,
area: entity.isDegenerate() ? 0 : Math.abs(d3.geom.polygon(points).area())
area: Math.abs(path.area(entity.asGeoJSON(graph)))
});
}
}
areas.sort(function(a, b) { return b.area - a.area; });
var lineString = iD.svg.LineString(projection, graph);
function drawPaths(group, areas, filter, klass) {
var tagClasses = iD.svg.TagClasses();
if (klass === 'stroke') {
tagClasses.tags(iD.svg.MultipolygonMemberTags(graph));
}
function drawPaths(group, areas, filter, classes) {
var paths = group.selectAll('path.area')
.filter(filter)
.data(areas, iD.Entity.key);
paths.enter()
.append('path')
.attr('class', classes);
.attr('class', function (d) { return d.type + ' area ' + klass; });
paths
.order()
.attr('d', lineString)
.call(iD.svg.TagClasses())
.attr('d', function (entity) { return path(entity.asGeoJSON(graph)); })
.call(tagClasses)
.call(iD.svg.MemberClasses(graph));
paths.exit()
@@ -43,9 +44,14 @@ iD.svg.Areas = function(projection) {
areas = _.pluck(areas, 'entity');
var strokes = areas.filter(function (area) {
return area.type === 'way';
});
var fill = surface.select('.layer-fill'),
stroke = surface.select('.layer-stroke'),
fills = drawPaths(fill, areas, filter, 'way area fill'),
strokes = drawPaths(stroke, areas, filter, 'way area stroke');
stroke = surface.select('.layer-stroke');
drawPaths(fill, areas, filter, 'fill');
drawPaths(stroke, strokes, filter, 'stroke');
};
};
+2 -3
View File
@@ -335,9 +335,8 @@ iD.svg.Labels = function(projection) {
}
function getAreaLabel(entity, width, height) {
var nodes = _.pluck(graph.childNodes(entity), 'loc')
.map(iD.svg.RoundProjection(projection)),
centroid = d3.geom.polygon(nodes).centroid(),
var path = d3.geo.path().projection(projection),
centroid = path.centroid(entity.asGeoJSON(graph)),
extent = entity.extent(graph),
entitywidth = projection(extent[1])[0] - projection(extent[0])[0];
+19 -10
View File
@@ -34,19 +34,25 @@ iD.svg.Lines = function(projection) {
}
return function drawLines(surface, graph, entities, filter) {
function drawPaths(group, lines, filter, classes, lineString) {
function drawPaths(group, lines, filter, klass, lineString) {
var tagClasses = iD.svg.TagClasses();
if (klass === 'stroke') {
tagClasses.tags(iD.svg.MultipolygonMemberTags(graph));
}
var paths = group.selectAll('path.line')
.filter(filter)
.data(lines, iD.Entity.key);
paths.enter()
.append('path')
.attr('class', classes);
.attr('class', 'way line ' + klass);
paths
.order()
.attr('d', lineString)
.call(iD.svg.TagClasses())
.call(tagClasses)
.call(iD.svg.MemberClasses(graph));
paths.exit()
@@ -56,13 +62,16 @@ iD.svg.Lines = function(projection) {
}
if (!alength) {
var arrow = surface.append('text').text(arrowtext);
var container = surface.append('g')
.attr('class', 'oneway'),
arrow = container.append('text')
.attr('class', 'textpath')
.text(arrowtext);
alength = arrow.node().getComputedTextLength();
arrow.remove();
container.remove();
}
var lines = [],
lineStrings = {};
var lines = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
@@ -80,9 +89,9 @@ iD.svg.Lines = function(projection) {
stroke = surface.select('.layer-stroke'),
defs = surface.select('defs'),
text = surface.select('.layer-text'),
shadows = drawPaths(shadow, lines, filter, 'way line shadow', lineString),
casings = drawPaths(casing, lines, filter, 'way line casing', lineString),
strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString);
shadows = drawPaths(shadow, lines, filter, 'shadow', lineString),
casings = drawPaths(casing, lines, filter, 'casing', lineString),
strokes = drawPaths(stroke, lines, filter, 'stroke', lineString);
// Determine the lengths of oneway paths
var lengths = {},
-55
View File
@@ -1,55 +0,0 @@
iD.svg.Multipolygons = function(projection) {
return function(surface, graph, entities, filter) {
var multipolygons = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (entity.geometry(graph) === 'relation' && entity.tags.type === 'multipolygon') {
multipolygons.push(entity);
}
}
var lineStrings = {};
function lineString(entity) {
if (lineStrings[entity.id] !== undefined) {
return lineStrings[entity.id];
}
var multipolygon = entity.multipolygon(graph);
if (entity.members.length === 0 || !multipolygon) {
return (lineStrings[entity.id] = null);
}
multipolygon = _.flatten(multipolygon, true);
return (lineStrings[entity.id] =
multipolygon.map(function (ring) {
return 'M' + ring.map(function (node) { return projection(node.loc); }).join('L');
}).join(""));
}
function drawPaths(group, multipolygons, filter, classes) {
var paths = group.selectAll('path.multipolygon')
.filter(filter)
.data(multipolygons, iD.Entity.key);
paths.enter()
.append('path')
.attr('class', classes);
paths
.order()
.attr('d', lineString)
.call(iD.svg.TagClasses())
.call(iD.svg.MemberClasses(graph));
paths.exit()
.remove();
return paths;
}
var fill = surface.select('.layer-fill'),
paths = drawPaths(fill, multipolygons, filter, 'relation multipolygon');
};
};
+1 -1
View File
@@ -79,7 +79,7 @@ iD.svg.Points.imageIndex = [
},
{
tags: { man_made: 'lighthouse' },
icon: 'lighthouselevel_crossing'
icon: 'lighthouse'
},
{
tags: { natural: 'peak' },
+15 -7
View File
@@ -3,10 +3,11 @@ iD.svg.TagClasses = function() {
'highway', 'railway', 'waterway', 'power', 'motorway', 'amenity',
'natural', 'landuse', 'building', 'oneway', 'bridge', 'boundary',
'leisure', 'construction'
]), tagClassRe = /^tag-/;
]), tagClassRe = /^tag-/,
tags = function(entity) { return entity.tags; };
return function tagClassesSelection(selection) {
selection.each(function tagClassesEach(d, i) {
var tagClasses = function(selection) {
selection.each(function tagClassesEach(entity) {
var classes, value = this.className;
if (value.baseVal !== undefined) value = value.baseVal;
@@ -15,11 +16,10 @@ iD.svg.TagClasses = function() {
return name.length && !tagClassRe.test(name);
}).join(' ');
var tags = d.tags;
for (var k in tags) {
var t = tags(entity);
for (var k in t) {
if (!keys[k]) continue;
classes += ' tag-' + k + ' ' +
'tag-' + k + '-' + tags[k];
classes += ' tag-' + k + ' ' + 'tag-' + k + '-' + t[k];
}
classes = classes.trim();
@@ -29,4 +29,12 @@ iD.svg.TagClasses = function() {
}
});
};
tagClasses.tags = function(_) {
if (!arguments.length) return tags;
tags = _;
return tagClasses;
};
return tagClasses;
};
+7 -1
View File
@@ -195,6 +195,7 @@ iD.ui = function(context) {
history.on('change.editor', function() {
window.onbeforeunload = history.hasChanges() ? function() {
history.save();
return 'You have unsaved changes.';
} : null;
@@ -253,8 +254,13 @@ iD.ui = function(context) {
context.enter(iD.modes.Browse(context));
if (!context.storage('sawSplash')) {
iD.ui.splash();
iD.ui.splash(context.container());
context.storage('sawSplash', true);
}
if (history.restorableChanges()) {
iD.ui.restore(context.container(), history);
}
};
};
+7 -1
View File
@@ -108,7 +108,13 @@ iD.ui.layerswitcher = function(context) {
d = configured;
}
context.background().source(d);
context.history().imagery_used(d.data.sourcetag || d.data.name);
if (d.data.name === 'Custom (customized)') {
context.history()
.imagery_used('Custom (' + d.data.template + ')');
} else {
context.history()
.imagery_used(d.data.sourcetag || d.data.name);
}
context.redraw();
selectLayer(d);
}
+34
View File
@@ -0,0 +1,34 @@
iD.ui.restore = function(selection, history) {
var modal = iD.ui.modal(selection);
modal.select('.modal')
.attr('class', 'modal-splash modal');
var introModal = modal.select('.content')
.append('div')
.attr('class', 'modal-section fillL')
.text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?');
buttons = introModal
.append('div')
.attr('class', 'buttons cf')
.append('div')
.attr('class', 'button-wrap joined col4');
buttons.append('button')
.attr('class', 'save action button col6')
.text('Restore')
.on('click', function() {
history.load();
modal.remove();
});
buttons.append('button')
.attr('class', 'cancel button col6')
.text('Reset')
.on('click', function() {
modal.remove();
});
return modal;
};
+2 -2
View File
@@ -1,5 +1,5 @@
iD.ui.splash = function() {
var modal = iD.ui.modal();
iD.ui.splash = function(selection) {
var modal = iD.ui.modal(selection);
modal.select('.modal')
.attr('class', 'modal-splash modal');
+1 -4
View File
@@ -52,7 +52,6 @@
<script src="../js/id/svg/lines.js"></script>
<script src="../js/id/svg/member_classes.js"></script>
<script src="../js/id/svg/midpoints.js"></script>
<script src="../js/id/svg/multipolygons.js"></script>
<script src="../js/id/svg/points.js"></script>
<script src="../js/id/svg/surface.js"></script>
<script src="../js/id/svg/tag_classes.js"></script>
@@ -96,7 +95,6 @@
<script src='../js/id/behavior.js'></script>
<script src='../js/id/behavior/add_way.js'></script>
<script src='../js/id/behavior/drag.js'></script>
<script src='../js/id/behavior/drag_midpoint.js'></script>
<script src='../js/id/behavior/drag_node.js'></script>
<script src='../js/id/behavior/draw.js'></script>
<script src='../js/id/behavior/draw_way.js'></script>
@@ -142,7 +140,7 @@
iD.debug = true;
mocha.setup({
ui: 'bdd',
globals: ['__onresize.tail-size']
globals: ['__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onclick.draw']
});
var expect = chai.expect;
</script>
@@ -187,7 +185,6 @@
<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>
<script src="spec/svg/tag_classes.js"></script>
-1
View File
@@ -66,7 +66,6 @@
<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>
<script src="spec/svg/tag_classes.js"></script>
-1
View File
@@ -21,7 +21,6 @@
<script src="../js/id/svg/lines.js"></script>
<script src="../js/id/svg/member_classes.js"></script>
<script src="../js/id/svg/midpoints.js"></script>
<script src="../js/id/svg/multipolygons.js"></script>
<script src="../js/id/svg/points.js"></script>
<script src="../js/id/svg/surface.js"></script>
<script src="../js/id/svg/tag_classes.js"></script>
+18 -8
View File
@@ -29,21 +29,31 @@ describe("iD.behavior.Select", function() {
container.remove();
});
specify("click on entity selects the entity", function() {
happen.click(context.surface().select('.' + a.id).node());
expect(context.selection()).to.eql([a.id]);
specify("click on entity selects the entity", function(done) {
happen.mousedown(context.surface().select('.' + a.id).node());
window.setTimeout(function() {
expect(context.selection()).to.eql([a.id]);
done();
}, 600);
});
specify("click on empty space clears the selection", function() {
specify("click on empty space clears the selection", function(done) {
context.enter(iD.modes.Select(context, [a.id]));
happen.click(context.surface().node());
expect(context.selection()).to.eql([]);
happen.mousedown(context.surface().node());
window.setTimeout(function() {
expect(context.selection()).to.eql([]);
done();
}, 600);
});
specify("shift-click on entity adds the entity to the selection", function() {
specify("shift-click on entity adds the entity to the selection", function(done) {
context.enter(iD.modes.Select(context, [a.id]));
happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true});
expect(context.selection()).to.eql([a.id, b.id]);
happen.mousedown(context.surface().select('.' + b.id).node(), {shiftKey: true});
window.setTimeout(function() {
expect(context.selection()).to.eql([a.id, b.id]);
done();
}, 600);
});
specify("shift-click on empty space leaves the selection unchanged", function() {
+81 -53
View File
@@ -146,107 +146,135 @@ describe('iD.Relation', function () {
});
});
describe("#asGeoJSON", function (){
it('converts a multipolygon to a GeoJSON MultiPolygon feature', function() {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]),
json = r.asGeoJSON(g);
expect(json.type).to.equal('Feature');
expect(json.properties).to.eql({type: 'multipolygon'});
expect(json.geometry.type).to.equal('MultiPolygon');
expect(json.geometry.coordinates).to.eql([[[[1, 1], [2, 2], [3, 3], [1, 1]]]]);
});
it('converts a relation to a GeoJSON FeatureCollection', function() {
var a = iD.Node({loc: [1, 1]}),
r = iD.Relation({tags: {type: 'type'}, members: [{id: a.id, role: 'role'}]}),
g = iD.Graph([a, r]),
json = r.asGeoJSON(g);
expect(json.type).to.equal('FeatureCollection');
expect(json.properties).to.eql({type: 'type'});
expect(json.features).to.eql([_.extend({role: 'role'}, a.asGeoJSON(g))]);
});
});
describe("#multipolygon", function () {
specify("single polygon consisting of a single way", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, a]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify("single polygon consisting of multiple ways", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
d = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [c.id, d.id, a.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, d, a]]]); // TODO: not the only valid ordering
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc, a.loc]]]); // TODO: not the only valid ordering
});
specify("single polygon consisting of multiple ways, one needing reversal", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
d = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [a.id, d.id, c.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, d, a]]]); // TODO: not the only valid ordering
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc, a.loc]]]); // TODO: not the only valid ordering
});
specify("multiple polygons consisting of single ways", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
d = iD.Node(),
e = iD.Node(),
f = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
e = iD.Node({loc: [5, 5]}),
f = iD.Node({loc: [6, 6]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
w2 = iD.Way({nodes: [d.id, e.id, f.id, d.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, a]], [[d, e, f, d]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]], [[d.loc, e.loc, f.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of a single way", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id]}),
r = iD.Relation({members: [{id: w.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
d = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [c.id, d.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways, alternate order", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
d = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [c.id, d.id]}),
w2 = iD.Way({nodes: [a.id, b.id, c.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways, one needing reversal", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
d = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
d = iD.Node({loc: [4, 4]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way({nodes: [d.id, c.id]}),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("invalid geometry: unclosed ring consisting of multiple ways, one needing reversal, alternate order", function () {
@@ -259,7 +287,7 @@ describe('iD.Relation', function () {
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, d, w1, w2, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, d]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, d.loc]]]);
});
specify("single polygon with single single-way inner", function () {
@@ -274,7 +302,7 @@ describe('iD.Relation', function () {
r = iD.Relation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, d, e, f, outer, inner, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, a], [d, e, f, d]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]]]);
});
specify("single polygon with single multi-way inner", function () {
@@ -293,7 +321,7 @@ describe('iD.Relation', function () {
{id: inner1.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, outer, inner1, inner2, r]);
expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]]]);
expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]]]);
});
specify("single polygon with multiple single-way inners", function () {
@@ -315,7 +343,7 @@ describe('iD.Relation', function () {
{id: inner1.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer, inner1, inner2, r]);
expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d], [g, h, i, g]]]);
expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc], [g.loc, h.loc, i.loc, g.loc]]]);
});
specify("multiple polygons with single single-way inner", function () {
@@ -337,30 +365,30 @@ describe('iD.Relation', function () {
{id: inner.id, role: 'inner', type: 'way'}]}),
graph = iD.Graph([a, b, c, d, e, f, g, h, i, outer1, outer2, inner, r]);
expect(r.multipolygon(graph)).to.eql([[[a, b, c, a], [d, e, f, d]], [[g, h, i, g]]]);
expect(r.multipolygon(graph)).to.eql([[[a.loc, b.loc, c.loc, a.loc], [d.loc, e.loc, f.loc, d.loc]], [[g.loc, h.loc, i.loc, g.loc]]]);
});
specify("invalid geometry: unmatched inner", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id, role: 'inner', type: 'way'}]}),
g = iD.Graph([a, b, c, w, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c, a]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc, a.loc]]]);
});
specify("incomplete relation", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w1 = iD.Way({nodes: [a.id, b.id, c.id]}),
w2 = iD.Way(),
r = iD.Relation({members: [{id: w2.id, type: 'way'}, {id: w1.id, type: 'way'}]}),
g = iD.Graph([a, b, c, w1, r]);
expect(r.multipolygon(g)).to.eql([[[a, b, c]]]);
expect(r.multipolygon(g)).to.eql([[[a.loc, b.loc, c.loc]]]);
});
});
});
+21 -3
View File
@@ -95,8 +95,12 @@ describe('iD.Way', function() {
expect(iD.Way({tags: { area: 'yes' }}).isArea()).to.equal(true);
});
it('returns true if the way is closed and has no tags', function() {
expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(true);
it('returns false if the way is closed and has no tags', function() {
expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(false);
});
it('returns true if the way is closed and has tags', function() {
expect(iD.Way({nodes: ['n1', 'n1'], tags: {a: 'b'}}).isArea()).to.equal(true);
});
it('returns false if the way is closed and has tag area=no', function() {
@@ -207,7 +211,7 @@ describe('iD.Way', function() {
});
describe("#asGeoJSON", function () {
it("converts to a GeoJSON LineString features", function () {
it("converts a line to a GeoJSON LineString features", function () {
var a = iD.Node({loc: [1, 2]}),
b = iD.Node({loc: [3, 4]}),
w = iD.Way({tags: {highway: 'residential'}, nodes: [a.id, b.id]}),
@@ -219,5 +223,19 @@ describe('iD.Way', function() {
expect(json.geometry.type).to.equal('LineString');
expect(json.geometry.coordinates).to.eql([[1, 2], [3, 4]]);
});
it("converts an area to a GeoJSON Polygon features", function () {
var a = iD.Node({loc: [1, 2]}),
b = iD.Node({loc: [3, 4]}),
c = iD.Node({loc: [5, 6]}),
w = iD.Way({tags: {area: 'yes'}, nodes: [a.id, b.id, c.id, a.id]}),
graph = iD.Graph([a, b, c, w]),
json = w.asGeoJSON(graph);
expect(json.type).to.equal('Feature');
expect(json.properties).to.eql({area: 'yes'});
expect(json.geometry.type).to.equal('Polygon');
expect(json.geometry.coordinates).to.eql([[[1, 2], [3, 4], [5, 6], [1, 2]]]);
});
});
});
+10 -8
View File
@@ -1,7 +1,7 @@
describe("iD.modes.AddPoint", function () {
describe("iD.modes.AddPoint", function() {
var context;
beforeEach(function () {
beforeEach(function() {
var container = d3.select(document.createElement('div'));
context = iD()
@@ -15,20 +15,22 @@ describe("iD.modes.AddPoint", function () {
});
describe("clicking the map", function () {
it("adds a node", function () {
happen.click(context.surface().node(), {});
it("adds a node", function() {
happen.mousedown(context.surface().node(), {});
happen.click(window, {});
expect(context.changes().created).to.have.length(1);
});
it("selects the node", function () {
happen.click(context.surface().node(), {});
it("selects the node", function() {
happen.mousedown(context.surface().node(), {});
happen.click(window, {});
expect(context.mode().id).to.equal('select');
expect(context.mode().selection()).to.eql([context.changes().created[0].id]);
});
});
describe("pressing ⎋", function () {
it("exits to browse mode", function () {
describe("pressing ⎋", function() {
it("exits to browse mode", function() {
happen.keydown(document, {keyCode: 27});
expect(context.mode().id).to.equal('browse');
});
+42
View File
@@ -69,4 +69,46 @@ describe("iD.svg.Areas", function () {
expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park');
expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes');
});
it("renders fills for multipolygon areas", function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
graph = iD.Graph([a, b, c, w, r]),
areas = [w, r];
surface.call(iD.svg.Areas(projection), graph, areas, filter);
expect(surface.select('.fill')).to.be.classed('relation');
});
it("renders no strokes for multipolygon areas", function () {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}),
graph = iD.Graph([a, b, c, w, r]),
areas = [w, r];
surface.call(iD.svg.Areas(projection), graph, areas, filter);
expect(surface.selectAll('.stroke')[0].length).to.equal(0);
});
it("adds stroke classes for the tags of the parent relation of multipolygon members", function() {
var a = iD.Node({loc: [1, 1]}),
b = iD.Node({loc: [2, 2]}),
c = iD.Node({loc: [3, 3]}),
w = iD.Way({tags: {area: 'yes'}, nodes: [a.id, b.id, c.id, a.id]}),
r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon', natural: 'wood'}}),
graph = iD.Graph([a, b, c, w, r]);
surface.call(iD.svg.Areas(projection), graph, [w], filter);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
expect(surface.select('.fill')).not.to.be.classed('tag-natural-wood');
});
});
+10
View File
@@ -39,6 +39,16 @@ describe("iD.svg.Lines", function () {
expect(surface.select('.line')).to.be.classed('member-type-route');
});
it("adds stroke classes for the tags of the parent relation of multipolygon members", function() {
var line = iD.Way(),
relation = iD.Relation({members: [{id: line.id}], tags: {type: 'multipolygon', natural: 'wood'}}),
graph = iD.Graph([line, relation]);
surface.call(iD.svg.Lines(projection), graph, [line], filter);
expect(surface.select('.stroke')).to.be.classed('tag-natural-wood');
});
it("preserves non-line paths", function () {
var line = iD.Way(),
graph = iD.Graph([line]);
-43
View File
@@ -1,43 +0,0 @@
describe("iD.svg.Multipolygons", 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("adds relation and multipolygon classes", function () {
var relation = iD.Relation({tags: {type: 'multipolygon'}}),
graph = iD.Graph([relation]);
surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter);
expect(surface.select('path')).to.be.classed('relation');
expect(surface.select('path')).to.be.classed('multipolygon');
});
it("adds tag classes", function () {
var relation = iD.Relation({tags: {type: 'multipolygon', boundary: "administrative"}}),
graph = iD.Graph([relation]);
surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter);
expect(surface.select('.relation')).to.be.classed('tag-boundary');
expect(surface.select('.relation')).to.be.classed('tag-boundary-administrative');
});
it("preserves non-multipolygon paths", function () {
var relation = iD.Relation({tags: {type: 'multipolygon'}}),
graph = iD.Graph([relation]);
surface.select('.layer-fill')
.append('path')
.attr('class', 'other');
surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter);
expect(surface.selectAll('.other')[0].length).to.equal(1);
});
});
+7
View File
@@ -19,6 +19,13 @@ describe("iD.svg.TagClasses", function () {
expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary');
});
it('adds tags based on the result of the `tags` accessor', function() {
selection
.datum(iD.Entity())
.call(iD.svg.TagClasses().tags(d3.functor({highway: 'primary'})));
expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary');
});
it('removes classes for tags that are no longer present', function() {
selection
.attr('class', 'tag-highway tag-highway-primary')