diff --git a/data/core.yaml b/data/core.yaml index 4fe523c6f..1b868b3e8 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1243,6 +1243,11 @@ en: points-lines-areas-relations: message: "Deleting {n} features: {p} points, {l} lines, {a} areas, and {r} relations" tip: "Only redundant or nonexistent features should be deleted." + missing_role: + title: Missing Roles + message: "{member} has no role within {relation}" + multipolygon: + tip: "Multipolygon members must have an inner or outer role." missing_tag: title: Missing Tags any: @@ -1283,6 +1288,8 @@ en: annotation: Removed an old tag. remove_deprecated_tag_combo: annotation: Removed an old tag combination. + remove_from_relation: + title: Remove from relation remove_generic_name: title: Remove the name annotation: Removed a generic name. @@ -1295,6 +1302,10 @@ en: title: Reposition the features select_preset: title: Select a feature type + set_as_inner: + title: Set as inner + set_as_outer: + title: Set as outer tag_as_disconnected: title: Tag as disconnected annotation: Tagged very close features as disconnected. diff --git a/dist/locales/en.json b/dist/locales/en.json index 95390ba16..ad85bdf37 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -1523,6 +1523,13 @@ }, "tip": "Only redundant or nonexistent features should be deleted." }, + "missing_role": { + "title": "Missing Roles", + "message": "{member} has no role within {relation}", + "multipolygon": { + "tip": "Multipolygon members must have an inner or outer role." + } + }, "missing_tag": { "title": "Missing Tags", "any": { @@ -1579,6 +1586,9 @@ "remove_deprecated_tag_combo": { "annotation": "Removed an old tag combination." }, + "remove_from_relation": { + "title": "Remove from relation" + }, "remove_generic_name": { "title": "Remove the name", "annotation": "Removed a generic name." @@ -1596,6 +1606,12 @@ "select_preset": { "title": "Select a feature type" }, + "set_as_inner": { + "title": "Set as inner" + }, + "set_as_outer": { + "title": "Set as outer" + }, "tag_as_disconnected": { "title": "Tag as disconnected", "annotation": "Tagged very close features as disconnected." diff --git a/modules/ui/issues.js b/modules/ui/issues.js index 7343a35b9..283a2415f 100644 --- a/modules/ui/issues.js +++ b/modules/ui/issues.js @@ -308,6 +308,8 @@ export function uiIssues(context) { d += '.highway'; } else if (d === 'almost_junction') { d += '.highway-highway'; + } else if (d === 'missing_role') { + d += '.multipolygon'; } return t('issues.' + d + '.tip'); }) diff --git a/modules/validations/index.js b/modules/validations/index.js index 235f2e540..3a4a48e2a 100644 --- a/modules/validations/index.js +++ b/modules/validations/index.js @@ -5,6 +5,7 @@ export { validationDisconnectedWay } from './disconnected_way'; export { validationGenericName } from './generic_name'; export { validationManyDeletions } from './many_deletions'; export { validationMaprules } from './maprules'; +export { validationMissingRole } from './missing_role'; export { validationMissingTag } from './missing_tag'; export { validationOldMultipolygon } from './old_multipolygon'; export { validationTagSuggestsArea } from './tag_suggests_area'; diff --git a/modules/validations/missing_role.js b/modules/validations/missing_role.js new file mode 100644 index 000000000..58571ba06 --- /dev/null +++ b/modules/validations/missing_role.js @@ -0,0 +1,84 @@ +import { actionChangeMember, actionDeleteMember } from '../actions'; +import { t } from '../util/locale'; +import { utilDisplayLabel } from '../util'; +import { validationIssue, validationIssueFix } from '../core/validator'; + + +export function validationMissingRole() { + var type = 'missing_role'; + + var validation = function(entity, context) { + + var issues = []; + if (entity.type === 'way') { + context.graph().parentRelations(entity).forEach(function(relation) { + if (!relation.isMultipolygon()) { + return; + } + var member = relation.memberById(entity.id); + if (member && isMissingRole(member)) { + issues.push(makeIssue(entity, relation, member, context)); + } + }); + } else if (entity.type === 'relation' && entity.isMultipolygon()) { + entity.indexedMembers().forEach(function(member) { + var way = context.hasEntity(member.id); + if (way && isMissingRole(member)) { + issues.push(makeIssue(way, entity, member, context)); + } + }); + } + + return issues; + }; + + function isMissingRole(member) { + return !member.role || !member.role.trim().length; + } + + function makeIssue(way, relation, member, context) { + return new validationIssue({ + type: type, + severity: 'warning', + message: t('issues.missing_role.message', { + member: utilDisplayLabel(way, context), + relation: utilDisplayLabel(relation, context), + }), + tooltip: t('issues.missing_role.multipolygon.tip'), + entities: [relation, way], + info: {member: member}, + fixes: [ + makeAddRoleFix('inner', context), + makeAddRoleFix('outer', context), + new validationIssueFix({ + icon: 'iD-operation-delete', + title: t('issues.fix.remove_from_relation.title'), + onClick: function() { + context.perform( + actionDeleteMember(this.issue.entities[0].id, this.issue.info.member.index), + t('operations.delete_member.annotation') + ); + } + }) + ] + }); + } + + function makeAddRoleFix(role, context) { + return new validationIssueFix({ + title: t('issues.fix.set_as_' + role + '.title'), + onClick: function() { + var oldMember = this.issue.info.member; + var member = { id: this.issue.entities[1].id, type: oldMember.type, role: role }; + context.perform( + actionChangeMember(this.issue.entities[0].id, member, oldMember.index), + t('operations.change_role.annotation') + ); + } + }); + } + + validation.type = type; + + return validation; +}