diff --git a/data/core.yaml b/data/core.yaml index c8e85f958..5c643d630 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -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. diff --git a/dist/locales/en.json b/dist/locales/en.json index 020b50df8..7cbc5718a 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -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", diff --git a/js/id/geo/extent.js b/js/id/geo/extent.js index 04c8c7434..8548fbff4 100644 --- a/js/id/geo/extent.js +++ b/js/id/geo/extent.js @@ -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(','); } + }); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 39926aff1..a786627c9 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -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() { diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 8c6b81d3f..525ee7047 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -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() { diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index e99827806..851e7e248 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -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() { diff --git a/js/id/operations/rotate.js b/js/id/operations/rotate.js index e762a8875..3edc3e4d9 100644 --- a/js/id/operations/rotate.js +++ b/js/id/operations/rotate.js @@ -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'; diff --git a/test/spec/geo/extent.js b/test/spec/geo/extent.js index 9ca751efc..4201b609c 100644 --- a/test/spec/geo/extent.js +++ b/test/spec/geo/extent.js @@ -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); + }); + + }); + });