disable Move and Rotate operations if area < 80% contained in the viewport

see #542.
Also included:
1. DRY up code for "% contained in" extent testing.
2. If action.disabled() returns a better reason, show that instead of the too_large one.
This commit is contained in:
Bryan Housel
2014-07-11 17:11:50 -04:00
parent 098a1ac5a7
commit 874a3e2ad6
8 changed files with 83 additions and 38 deletions

View File

@@ -120,6 +120,7 @@ en:
area: Moved an area.
multiple: Moved multiple objects.
incomplete_relation: This feature can't be moved because it hasn't been fully downloaded.
too_large: This can't be moved because not enough of it is currently visible.
rotate:
title: Rotate
description: Rotate this object around its center point.
@@ -127,6 +128,7 @@ en:
annotation:
line: Rotated a line.
area: Rotated an area.
too_large: This can't be rotated because not enough of it is currently visible.
reverse:
title: Reverse
description: Make this line go in the opposite direction.

View File

@@ -150,7 +150,8 @@
"area": "Moved an area.",
"multiple": "Moved multiple objects."
},
"incomplete_relation": "This feature can't be moved because it hasn't been fully downloaded."
"incomplete_relation": "This feature can't be moved because it hasn't been fully downloaded.",
"too_large": "This can't be moved because not enough of it is currently visible."
},
"rotate": {
"title": "Rotate",
@@ -159,7 +160,8 @@
"annotation": {
"line": "Rotated a line.",
"area": "Rotated an area."
}
},
"too_large": "This can't be rotated because not enough of it is currently visible."
},
"reverse": {
"title": "Reverse",

View File

@@ -57,6 +57,18 @@ _.extend(iD.geo.Extent.prototype, {
Math.min(obj[1][1], this[1][1])]);
},
percentContainedIn: function(obj) {
if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj);
var a1 = this.intersection(obj).area(),
a2 = this.area();
if (a1 === Infinity || a2 === Infinity || a1 === 0 || a2 === 0) {
return 0;
} else {
return a1 / a2;
}
},
padByMeters: function(meters) {
var dLat = iD.geo.metersToLat(meters),
dLon = iD.geo.metersToLon(meters, this.center()[1]);
@@ -68,4 +80,5 @@ _.extend(iD.geo.Extent.prototype, {
toParam: function() {
return [this[0][0], this[0][1], this[1][0], this[1][1]].join(',');
}
});

View File

@@ -1,5 +1,7 @@
iD.operations.Circularize = function(selectedIDs, context) {
var entityId = selectedIDs[0],
entity = context.entity(entityId),
extent = entity.extent(context.graph()),
geometry = context.geometry(entityId),
action = iD.actions.Circularize(entityId, context.projection);
@@ -9,24 +11,17 @@ iD.operations.Circularize = function(selectedIDs, context) {
};
operation.available = function() {
var entity = context.entity(entityId);
return selectedIDs.length === 1 &&
entity.type === 'way' &&
_.uniq(entity.nodes).length > 1;
};
operation.disabled = function() {
var way = context.entity(entityId),
wayExtent = way.extent(context.graph()),
mapExtent = context.extent(),
intersection = mapExtent.intersection(wayExtent),
pctVisible = intersection.area() / wayExtent.area();
if (pctVisible < 0.8) {
return 'too_large';
} else {
return action.disabled(context.graph());
var reason;
if (extent.percentContainedIn(context.extent()) < 0.8) {
reason = 'too_large';
}
return action.disabled(context.graph()) || reason;
};
operation.tooltip = function() {

View File

@@ -1,4 +1,8 @@
iD.operations.Move = function(selectedIDs, context) {
var extent = selectedIDs.reduce(function(extent, id) {
return extent.extend(context.entity(id).extent(context.graph()));
}, iD.geo.Extent());
var operation = function() {
context.enter(iD.modes.Move(context, selectedIDs));
};
@@ -9,8 +13,11 @@ iD.operations.Move = function(selectedIDs, context) {
};
operation.disabled = function() {
return iD.actions.Move(selectedIDs)
.disabled(context.graph());
var reason;
if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) {
reason = 'too_large';
}
return iD.actions.Move(selectedIDs).disabled(context.graph()) || reason;
};
operation.tooltip = function() {

View File

@@ -1,15 +1,16 @@
iD.operations.Orthogonalize = function(selectedIDs, context) {
var entityId = selectedIDs[0],
entity = context.entity(entityId),
extent = entity.extent(context.graph()),
geometry = context.geometry(entityId),
action = iD.actions.Orthogonalize(entityId, context.projection);
function operation() {
var operation = function() {
var annotation = t('operations.orthogonalize.annotation.' + geometry);
context.perform(action, annotation);
}
};
operation.available = function() {
var entity = context.entity(entityId);
return selectedIDs.length === 1 &&
entity.type === 'way' &&
entity.isClosed() &&
@@ -17,17 +18,11 @@ iD.operations.Orthogonalize = function(selectedIDs, context) {
};
operation.disabled = function() {
var way = context.entity(entityId),
wayExtent = way.extent(context.graph()),
mapExtent = context.extent(),
intersection = mapExtent.intersection(wayExtent),
pctVisible = intersection.area() / wayExtent.area();
if (pctVisible < 0.8) {
return 'too_large';
} else {
return action.disabled(context.graph());
var reason;
if (extent.percentContainedIn(context.extent()) < 0.8) {
reason = 'too_large';
}
return action.disabled(context.graph()) || reason;
};
operation.tooltip = function() {

View File

@@ -1,31 +1,37 @@
iD.operations.Rotate = function(selectedIDs, context) {
var entityId = selectedIDs[0];
var entityId = selectedIDs[0],
entity = context.entity(entityId),
extent = entity.extent(context.graph()),
geometry = context.geometry(entityId);
var operation = function() {
context.enter(iD.modes.RotateWay(context, entityId));
};
operation.available = function() {
var graph = context.graph(),
entity = graph.entity(entityId);
if (selectedIDs.length !== 1 ||
entity.type !== 'way')
if (selectedIDs.length !== 1 || entity.type !== 'way')
return false;
if (context.geometry(entityId) === 'area')
if (geometry === 'area')
return true;
if (entity.isClosed() &&
graph.parentRelations(entity).some(function(r) { return r.isMultipolygon(); }))
context.graph().parentRelations(entity).some(function(r) { return r.isMultipolygon(); }))
return true;
return false;
};
operation.disabled = function() {
return false;
if (extent.percentContainedIn(context.extent()) < 0.8) {
return 'too_large';
} else {
return false;
}
};
operation.tooltip = function() {
return t('operations.rotate.description');
var disable = operation.disabled();
return disable ?
t('operations.rotate.' + disable) :
t('operations.rotate.description');
};
operation.id = 'rotate';

View File

@@ -158,4 +158,29 @@ describe("iD.geo.Extent", function () {
expect(b.intersection(a)).to.eql(iD.geo.Extent([1, 1], [2, 2]));
});
});
describe("#percentContainedIn", function () {
it("returns a 0 if self does not intersect other", function () {
var a = iD.geo.Extent([0, 0], [1, 1]),
b = iD.geo.Extent([0, 3], [4, 1]);
expect(a.percentContainedIn(b)).to.eql(0);
expect(b.percentContainedIn(a)).to.eql(0);
});
it("returns the percent contained of self with other (1)", function () {
var a = iD.geo.Extent([0, 0], [2, 1]),
b = iD.geo.Extent([1, 0], [3, 1]);
expect(a.percentContainedIn(b)).to.eql(0.5);
expect(b.percentContainedIn(a)).to.eql(0.5);
});
it("returns the percent contained of self with other (2)", function () {
var a = iD.geo.Extent([0, 0], [4, 1]),
b = iD.geo.Extent([3, 0], [4, 2]);
expect(a.percentContainedIn(b)).to.eql(0.25);
expect(b.percentContainedIn(a)).to.eql(0.5);
});
});
});