diff --git a/css/80_app.css b/css/80_app.css index 75209416c..d76c9a5e0 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -3397,6 +3397,14 @@ button.autofix.action.active { color: #05ac10; } +input.square-degrees-input { + padding: 2px; + height: unset; + text-align: center; + background: rgba(0,0,0,0); + color: currentColor; +} + /* Entity Issues List */ .entity-issues .issue-container .issue { diff --git a/modules/core/validator.js b/modules/core/validator.js index cfffcd680..376f7d5e2 100644 --- a/modules/core/validator.js +++ b/modules/core/validator.js @@ -1,6 +1,7 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { coreDifference } from './difference'; +import { geoExtent } from '../geo/extent'; import { modeSelect } from '../modes/select'; import { utilArrayGroupBy, utilRebind } from '../util'; import { t } from '../util/locale'; @@ -69,6 +70,56 @@ export function coreValidator(context) { dispatch.call('validated'); }; + + // when the user changes the squaring thereshold, rerun this on all buildings + validator.changeSquareThreshold = function() { + var checkUnsquareWay = _rules.unsquare_way; + if (typeof checkUnsquareWay !== 'function') return; + + // uncache existing + Object.values(_issuesByIssueID) + .filter(function(issue) { return issue.type === 'unsquare_way'; }) + .forEach(function(issue) { + var entityId = issue.entityIds[0]; // always 1 entity for unsquare way + if (_issuesByEntityID[entityId]) { + _issuesByEntityID[entityId].delete(issue.id); + } + delete _issuesByIssueID[issue.id]; + }); + + var buildings = context.intersects(geoExtent([-180,-90],[180, 90])) // everywhere + .filter(function(entity) { + return entity.type === 'way' && entity.tags.building && entity.tags.building !== 'no'; + }); + + // rerun for all buildings + buildings.forEach(function(entity) { + var detected = checkUnsquareWay(entity, context); + if (detected.length !== 1) return; + + var issue = detected[0]; + var ignoreFix = new validationIssueFix({ + title: t('issues.fix.ignore_issue.title'), + icon: 'iD-icon-close', + onClick: function() { + ignoreIssue(this.issue.id); + } + }); + ignoreFix.type = 'ignore'; + ignoreFix.issue = issue; + issue.fixes.push(ignoreFix); + + if (!_issuesByEntityID[entity.id]) { + _issuesByEntityID[entity.id] = new Set(); + } + _issuesByEntityID[entity.id].add(issue.id); + _issuesByIssueID[issue.id] = issue; + }); + + dispatch.call('validated'); + }; + + // options = { // what: 'all', // 'all' or 'edited' // where: 'all', // 'all' or 'visible' @@ -89,13 +140,13 @@ export function coreValidator(context) { if (!opts.includeIgnored && _ignoredIssueIDs[issue.id]) return false; // Sanity check: This issue may be for an entity that not longer exists. - // If we detect this, uncache and return false so it is not incluced.. + // If we detect this, uncache and return false so it is not included.. var entityIds = issue.entityIds || []; for (var i = 0; i < entityIds.length; i++) { var entityId = entityIds[i]; if (!context.hasEntity(entityId)) { delete _issuesByEntityID[entityId]; - delete _issuesByIssueID[entityId]; + delete _issuesByIssueID[issue.id]; return false; } } diff --git a/modules/ui/issues.js b/modules/ui/issues.js index 65efe66c0..4cccfdc54 100644 --- a/modules/ui/issues.js +++ b/modules/ui/issues.js @@ -10,11 +10,16 @@ import { geoSphericalDistance } from '../geo'; import { svgIcon } from '../svg/icon'; import { uiDisclosure } from './disclosure'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilHighlightEntities } from '../util'; +import { utilGetSetValue, utilHighlightEntities, utilNoAuto } from '../util'; export function uiIssues(context) { var key = t('issues.key'); + + var MINSQUARE = 0; + var MAXSQUARE = 20; + var DEFAULTSQUARE = 6.5; // see also unsquare_way.js + var _errorsSelection = d3_select(null); var _warningsSelection = d3_select(null); var _rulesList = d3_select(null); @@ -564,10 +569,10 @@ export function uiIssues(context) { label .append('span') - .text(function(d) { + .html(function(d) { var params = {}; if (d === 'unsquare_way') { - params.val = 6.5; + params.val = ''; } return t('issues.' + d + '.title', params); }); @@ -581,19 +586,70 @@ export function uiIssues(context) { .selectAll('input') .property('checked', active) .property('indeterminate', false); + + + // user-configurable square threshold + var degStr = context.storage('validate-square-degrees'); + if (degStr === null) { + degStr = '' + DEFAULTSQUARE; + } + + var span = items.selectAll('.square-degrees'); + var input = span.selectAll('.square-degrees-input') + .data([0]); + + // enter / update + input.enter() + .append('input') + .attr('type', 'number') + .attr('min', '' + MINSQUARE) + .attr('max', '' + MAXSQUARE) + .attr('step', '0.5') + .attr('class', 'square-degrees-input') + .call(utilNoAuto) + .on('input', function() { + this.style.width = (this.value.length + 1) + 'ch'; // resize + }) + .on('blur', changeSquare) + .merge(input) + .property('value', degStr) + .style('width', (degStr.length + 1) + 'ch'); // resize } + function changeSquare() { + var input = d3_select(this); + var degStr = utilGetSetValue(input).trim(); + var degNum = parseFloat(degStr, 10); + + if (!isFinite(degNum) || degNum > MAXSQUARE || degNum < MINSQUARE) { + degNum = DEFAULTSQUARE; + } + + degNum = Math.round(degNum * 10 ) / 10; // round to 1 decimal + degStr = '' + degNum; + + input + .property('value', degStr) + .style('width', (degStr.length + 1) + 'ch'); // resize + + context.storage('validate-square-degrees', degStr); + context.validator().changeSquareThreshold(degNum); + } + + + function hidePane() { + context.ui().togglePanes(); + } + + + var paneTooltip = tooltip() .placement((textDirection === 'rtl') ? 'right' : 'left') .html(true) .title(uiTooltipHtml(t('issues.title'), key)); - function hidePane() { - context.ui().togglePanes(); - } - uiIssues.togglePane = function() { if (d3_event) d3_event.preventDefault(); diff --git a/modules/validations/unsquare_way.js b/modules/validations/unsquare_way.js index c888df9fe..5ce332340 100644 --- a/modules/validations/unsquare_way.js +++ b/modules/validations/unsquare_way.js @@ -8,16 +8,17 @@ import { validationIssue, validationIssueFix } from '../core/validation'; export function validationUnsquareWay() { var type = 'unsquare_way'; + var DEFAULTSQUARE = 6.5; // see also issues.js // use looser epsilon for detection to reduce warnings of buildings that are essentially square already var epsilon = 0.05; - var degreeThreshold = 13; - var autofixDegreeThreshold = 6.5; var nodeThreshold = 10; + // var degreeThreshold = 13; + // var autofixDegreeThreshold = 6.5; + function isBuilding(entity, graph) { if (entity.type !== 'way' || entity.geometry(graph) !== 'area') return false; - return entity.tags.building && entity.tags.building !== 'no'; } @@ -55,6 +56,13 @@ export function validationUnsquareWay() { if (hasConnectedSquarableWays) return []; +// testing: user-configurable square threshold +var degreeThreshold = context.storage('validate-square-degrees'); +if (degreeThreshold === null) { + degreeThreshold = '' + DEFAULTSQUARE; +} +var autofixDegreeThreshold = degreeThreshold; + var points = nodes.map(function(node) { return context.projection(node.loc); }); if (!geoOrthoCanOrthogonalize(points, isClosed, epsilon, degreeThreshold, true)) return []; diff --git a/test/spec/core/validator.js b/test/spec/core/validator.js index 2e40b04d1..36cfb814a 100644 --- a/test/spec/core/validator.js +++ b/test/spec/core/validator.js @@ -1,4 +1,4 @@ -describe('iD.validations.validator', function () { +describe('iD.coreValidator', function () { var context; beforeEach(function() {