modules/validations/unsquare_way.js (87 lines of code) (raw):
import { t } from '../util/locale';
//import { actionChangeTags } from '../actions/change_tags';
import { actionOrthogonalize } from '../actions/orthogonalize';
import { geoOrthoCanOrthogonalize } from '../geo/ortho';
import { utilDisplayLabel } from '../util';
import { validationIssue, validationIssueFix } from '../core/validation';
import { services } from '../services';
export function validationUnsquareWay(context) {
var type = 'unsquare_way';
var DEFAULT_DEG_THRESHOLD = 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 nodeThreshold = 10;
function isBuilding(entity, graph) {
if (entity.type !== 'way' || entity.geometry(graph) !== 'area') return false;
return entity.tags.building && entity.tags.building !== 'no';
}
var validation = function checkUnsquareWay(entity, graph) {
if (!isBuilding(entity, graph)) return [];
// don't flag ways marked as physically unsquare
if (entity.tags.nonsquare === 'yes') return [];
var isClosed = entity.isClosed();
if (!isClosed) return []; // this building has bigger problems
// don't flag ways with lots of nodes since they are likely detail-mapped
var nodes = graph.childNodes(entity).slice(); // shallow copy
if (nodes.length > nodeThreshold + 1) return []; // +1 because closing node appears twice
// ignore if not all nodes are fully downloaded
var osm = services.osm;
if (!osm || nodes.some(function(node) { return !osm.isDataLoaded(node.loc); })) return [];
// don't flag connected ways to avoid unresolvable unsquare loops
var hasConnectedSquarableWays = nodes.some(function(node) {
return graph.parentWays(node).some(function(way) {
if (way.id === entity.id) return false;
if (isBuilding(way, graph)) return true;
return graph.parentRelations(way).some(function(parentRelation) {
return parentRelation.isMultipolygon() &&
parentRelation.tags.building &&
parentRelation.tags.building !== 'no';
});
});
});
if (hasConnectedSquarableWays) return [];
// user-configurable square threshold
var storedDegreeThreshold = context.storage('validate-square-degrees');
var degreeThreshold = isNaN(storedDegreeThreshold) ? DEFAULT_DEG_THRESHOLD : parseFloat(storedDegreeThreshold);
var points = nodes.map(function(node) { return context.projection(node.loc); });
if (!geoOrthoCanOrthogonalize(points, isClosed, epsilon, degreeThreshold, true)) return [];
var autoArgs;
// don't allow autosquaring features linked to wikidata
if (!entity.tags.wikidata) {
// use same degree threshold as for detection
var autoAction = actionOrthogonalize(entity.id, context.projection, undefined, degreeThreshold);
autoAction.transitionable = false; // when autofixing, do it instantly
autoArgs = [autoAction, t('operations.orthogonalize.annotation.feature.single')];
}
return [new validationIssue({
type: type,
subtype: 'building',
severity: 'warning',
message: function(context) {
var entity = context.hasEntity(this.entityIds[0]);
return entity ? t('issues.unsquare_way.message', { feature: utilDisplayLabel(entity, context) }) : '';
},
reference: showReference,
entityIds: [entity.id],
hash: JSON.stringify(autoArgs !== undefined) + degreeThreshold,
dynamicFixes: function() {
return [
new validationIssueFix({
icon: 'iD-operation-orthogonalize',
title: t('issues.fix.square_feature.title'),
autoArgs: autoArgs,
onClick: function(context, completionHandler) {
var entityId = this.issue.entityIds[0];
// use same degree threshold as for detection
context.perform(
actionOrthogonalize(entityId, context.projection, undefined, degreeThreshold),
t('operations.orthogonalize.annotation.feature.single')
);
// run after the squaring transition (currently 150ms)
window.setTimeout(function() { completionHandler(); }, 175);
}
}),
/*
new validationIssueFix({
title: t('issues.fix.tag_as_unsquare.title'),
onClick: function(context) {
var entityId = this.issue.entityIds[0];
var entity = context.entity(entityId);
var tags = Object.assign({}, entity.tags); // shallow copy
tags.nonsquare = 'yes';
context.perform(
actionChangeTags(entityId, tags),
t('issues.fix.tag_as_unsquare.annotation')
);
}
})
*/
];
}
})];
function showReference(selection) {
selection.selectAll('.issue-reference')
.data([0])
.enter()
.append('div')
.attr('class', 'issue-reference')
.text(t('issues.unsquare_way.buildings.reference'));
}
};
validation.type = type;
return validation;
}