diff --git a/data/core.yaml b/data/core.yaml index a14817f71..289a5aa0e 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -407,21 +407,24 @@ en: description: vertex: single: Extract this point from its parent lines/areas. + multiple: Extract these points from their parent features. line: single: Extract a point from this line. + multiple: Extract points from these lines. area: single: Extract a point from this area. + multiple: Extract points from these areas. + feature: + multiple: Extract points from these features. annotation: single: Extracted a point. + multiple: "Extracted {n} points." too_large: - area: - single: A point can't be extracted from this area because not enough of it is currently visible. - restriction: - vertex: - single: "This point can't be extracted because it would damage a \"{relation}\" relation." + single: A point can't be extracted because not enough of this feature is visible. + multiple: Points can't be extracted because not enough of these features are visible. connected_to_hidden: - vertex: - single: This point can't be extracted because it is connected to a hidden feature. + single: This point can't be extracted because it is connected to a hidden feature. + multiple: Points can't be extracted from these features because some are connected to hidden features. restriction: controls: distance: Distance diff --git a/dist/locales/en.json b/dist/locales/en.json index 45ff60d95..795ba224a 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -531,32 +531,32 @@ "key": "E", "description": { "vertex": { - "single": "Extract this point from its parent lines/areas." + "single": "Extract this point from its parent lines/areas.", + "multiple": "Extract these points from their parent features." }, "line": { - "single": "Extract a point from this line." + "single": "Extract a point from this line.", + "multiple": "Extract points from these lines." }, "area": { - "single": "Extract a point from this area." + "single": "Extract a point from this area.", + "multiple": "Extract points from these areas." + }, + "feature": { + "multiple": "Extract points from these features." } }, "annotation": { - "single": "Extracted a point." + "single": "Extracted a point.", + "multiple": "Extracted {n} points." }, "too_large": { - "area": { - "single": "A point can't be extracted from this area because not enough of it is currently visible." - } - }, - "restriction": { - "vertex": { - "single": "This point can't be extracted because it would damage a \"{relation}\" relation." - } + "single": "A point can't be extracted because not enough of this feature is visible.", + "multiple": "Points can't be extracted because not enough of these features are visible." }, "connected_to_hidden": { - "vertex": { - "single": "This point can't be extracted because it is connected to a hidden feature." - } + "single": "This point can't be extracted because it is connected to a hidden feature.", + "multiple": "Points can't be extracted from these features because some are connected to hidden features." } } }, diff --git a/modules/actions/extract.js b/modules/actions/extract.js index 08cbee56b..4a4ebba05 100644 --- a/modules/actions/extract.js +++ b/modules/actions/extract.js @@ -99,27 +99,5 @@ export function actionExtract(entityID) { return extractedNodeID; }; - action.disabled = function(graph) { - var entity = graph.entity(entityID); - - if (entity.type === 'node') { - var parentRels = graph.parentRelations(entity); - for (var i = 0; i < parentRels.length; i++) { - var relation = parentRels[i]; - if (!relation.hasFromViaTo()) continue; - - for (var j = 0; j < relation.members.length; j++) { - var m = relation.members[j]; - if (m.id === entityID && (m.role === 'via' || m.role === 'location_hint')) { - return 'restriction'; - } - } - } - } - - return false; - }; - - return action; } diff --git a/modules/operations/extract.js b/modules/operations/extract.js index be0409d84..f7593472c 100644 --- a/modules/operations/extract.js +++ b/modules/operations/extract.js @@ -3,47 +3,65 @@ import { behaviorOperation } from '../behavior/operation'; import { modeSelect } from '../modes/select'; import { t } from '../core/localizer'; import { presetManager } from '../presets'; +import { utilArrayUniq } from '../util/array'; export function operationExtract(context, selectedIDs) { - var entityID = selectedIDs.length && selectedIDs[0]; - var action = actionExtract(entityID); - var geometry = entityID && context.graph().hasEntity(entityID) && context.graph().geometry(entityID); - var extent = (geometry === 'area' || geometry === 'line') && context.graph().entity(entityID).extent(context.graph()); + var _amount = selectedIDs.length === 1 ? 'single' : 'multiple'; + var _geometries = utilArrayUniq(selectedIDs.map(function(entityID) { + return context.graph().hasEntity(entityID) && context.graph().geometry(entityID); + }).filter(Boolean)); + var _geometryID = _geometries.length === 1 ? _geometries[0] : 'feature'; + + var _extent; + var _actions = selectedIDs.map(function(entityID) { + var graph = context.graph(); + var entity = graph.hasEntity(entityID); + if (!entity || !entity.hasInterestingTags()) return; + + if (entity.type === 'node' && graph.parentWays(entity).length === 0) return; + + var geometry = graph.geometry(entityID); + if (geometry === 'area' || geometry === 'line') { + var preset = presetManager.match(entity, graph); + // only allow extraction from ways/multipolygons if the preset supports points + if (preset.geometry.indexOf('point') === -1) return; + } + + _extent = _extent ? _extent.extend(entity.extent(graph)) : entity.extent(graph); + + return actionExtract(entityID); + }).filter(Boolean); var operation = function () { - context.perform(action, operation.annotation()); // do the extract - context.enter(modeSelect(context, [action.getExtractedNodeID()])); + var combinedAction = function(graph) { + _actions.forEach(function(action) { + graph = action(graph); + }); + return graph; + }; + context.perform(combinedAction, operation.annotation()); // do the extract + + var extractedNodeIDs = _actions.map(function(action) { + return action.getExtractedNodeID(); + }); + context.enter(modeSelect(context, extractedNodeIDs)); }; operation.available = function () { - if (selectedIDs.length !== 1) return false; - - var graph = context.graph(); - var entity = graph.hasEntity(entityID); - if (!entity) return false; - - if (!entity.hasInterestingTags()) return false; - - if (geometry === 'area' || geometry === 'line') { - var preset = presetManager.match(entity, graph); - // only allow extraction from ways/multipolygons if the preset supports points - return preset.geometry.indexOf('point') !== -1; - } - - return entity.type === 'node' && graph.parentWays(entity).length > 0; + return _actions.length && selectedIDs.length === _actions.length; }; operation.disabled = function () { - var reason = action.disabled(context.graph()); - if (reason) { - return reason; - } else if (geometry === 'vertex' && selectedIDs.some(context.hasHiddenConnections)) { + + if (selectedIDs.some(function(entityID) { + return context.graph().geometry(entityID) === 'vertex' && context.hasHiddenConnections(entityID); + })) { return 'connected_to_hidden'; - } else if (extent && extent.area() && extent.percentContainedIn(context.map().extent()) < 0.8) { + } else if (_extent && _extent.area() && _extent.percentContainedIn(context.map().extent()) < 0.8) { return 'too_large'; } @@ -54,16 +72,15 @@ export function operationExtract(context, selectedIDs) { operation.tooltip = function () { var disableReason = operation.disabled(); if (disableReason) { - return t('operations.extract.' + disableReason + '.' + geometry + '.single', - { relation: presetManager.item('type/restriction').name() }); + return t('operations.extract.' + disableReason + '.' + _amount); } else { - return t('operations.extract.description.' + geometry + '.single'); + return t('operations.extract.description.' + _geometryID + '.' + _amount); } }; operation.annotation = function () { - return t('operations.extract.annotation.single'); + return t('operations.extract.annotation.' + _amount, { n: selectedIDs.length }); }; diff --git a/modules/validations/mismatched_geometry.js b/modules/validations/mismatched_geometry.js index d8577a2df..e4e7fac02 100644 --- a/modules/validations/mismatched_geometry.js +++ b/modules/validations/mismatched_geometry.js @@ -201,8 +201,7 @@ export function validationMismatchedGeometry() { var entityId = this.entityIds[0]; var extractOnClick = null; - if (!context.hasHiddenConnections(entityId) && - !actionExtract(entityId).disabled(context.graph())) { + if (!context.hasHiddenConnections(entityId)) { extractOnClick = function(context) { var entityId = this.issue.entityIds[0]; diff --git a/test/spec/operations/extract.js b/test/spec/operations/extract.js index bc9727cdf..f95af5cf1 100644 --- a/test/spec/operations/extract.js +++ b/test/spec/operations/extract.js @@ -41,11 +41,6 @@ describe('iD.operationExtract', function () { expect(result).to.be.not.ok; }); - it('is not available for two selected ids', function () { - var result = iD.operationExtract(fakeContext, ['a', 'b']).available(); - expect(result).to.be.not.ok; - }); - it('is not available for unknown selected id', function () { var result = iD.operationExtract(fakeContext, ['z']).available(); expect(result).to.be.not.ok; @@ -85,6 +80,11 @@ describe('iD.operationExtract', function () { var result = iD.operationExtract(fakeContext, ['b']).available(); expect(result).to.be.ok; }); + + it('is available for two selected nodes with tags and parent ways', function () { + var result = iD.operationExtract(fakeContext, ['a', 'b']).available(); + expect(result).to.be.ok; + }); }); @@ -112,7 +112,7 @@ describe('iD.operationExtract', function () { expect(result).to.be.not.ok; }); - it('returns not-enabled for via node in restriction', function () { + it('returns enabled for via node in restriction', function () { // https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that // from & to roles are only appropriate for Ways graph = iD.coreGraph([ @@ -134,10 +134,10 @@ describe('iD.operationExtract', function () { }) ]); var result = iD.operationExtract(fakeContext, ['d']).disabled(); - expect(result).to.eql('restriction'); + expect(result).to.be.not.ok; }); - it('returns not-enabled for location_hint node in restriction', function () { + it('returns enabled for location_hint node in restriction', function () { // https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that // from & to roles are only appropriate for Ways graph = iD.coreGraph([ @@ -160,7 +160,7 @@ describe('iD.operationExtract', function () { }) ]); var result = iD.operationExtract(fakeContext, ['d']).disabled(); - expect(result).to.eql('restriction'); + expect(result).to.be.not.ok; }); }); });