modules/validations/short_road.js (99 lines of code) (raw):
import { Extent } from '@id-sdk/extent';
import { geoSphericalDistance } from '@id-sdk/geo';
import { modeDrawLine } from '../modes';
import { operationDelete } from '../operations/index';
import { t } from '../core/localizer';
import { utilDisplayLabel } from '../util';
import { validationIssue, validationIssueFix } from '../core/validation';
export function validationShortRoad(context) {
var type = 'short_road';
// Thresholds for number of nodes and total length for a short road. A road
// is considered as "short" only if it has less than 7 nodes and is shorter
// than 20 meters.
var SHORT_WAY_NODES_THD = 7;
var SHORT_WAY_LENGTH_THD_METERS = 20;
function wayLength(way, graph) {
var length = 0;
for (var i = 0; i < way.nodes.length - 1; i++) {
var n1 = graph.entity(way.nodes[i]),
n2 = graph.entity(way.nodes[i + 1]);
length += geoSphericalDistance(n1.loc, n2.loc);
}
return length;
}
function continueDrawing(way, vertex, context) {
// make sure the vertex is actually visible and editable
var map = context.map();
if (!map.editable() || !map.trimmedExtent().contains(new Extent(vertex.loc))) {
map.zoomToEase(vertex);
}
context.enter(
modeDrawLine(context, way.id, context.graph(), context.graph(), '', way.affix(vertex.id), true)
);
}
var validation = function(entity, graph) {
if (entity.type !== 'way' || !entity.tags.highway || entity.isClosed() || entity.nodes.length >= SHORT_WAY_NODES_THD) return [];
var firstNode = graph.entity(entity.first()),
lastNode = graph.entity(entity.last()),
pwaysStart = graph.parentWays(firstNode),
pwaysEnd = graph.parentWays(lastNode),
firstNodeOK = pwaysStart.length > 1 || firstNode.tags.noexit === 'yes',
lastNodeOK = pwaysEnd.length > 1 || lastNode.tags.noexit === 'yes';
// only do check on roads with open ends
if ((firstNodeOK && lastNodeOK) || wayLength(entity, graph) >= SHORT_WAY_LENGTH_THD_METERS) return [];
var fixes = [];
if (!firstNodeOK) {
fixes.push(new validationIssueFix({
icon: 'iD-operation-continue-left',
title: t('issues.fix.continue_from_start.title'),
entityIds: [entity.first()],
onClick: function() {
var vertex = context.entity(entity.first());
continueDrawing(entity, vertex, context);
}
}));
}
if (!lastNodeOK) {
fixes.push(new validationIssueFix({
icon: 'iD-operation-continue',
title: t('issues.fix.continue_from_end.title'),
entityIds: [entity.last()],
onClick: function() {
var vertex = context.entity(entity.last());
continueDrawing(entity, vertex, context);
}
}));
}
if (!operationDelete(context, [entity.id]).disabled()) {
fixes.push(new validationIssueFix({
icon: 'iD-operation-delete',
title: t('issues.fix.delete_feature.title'),
entityIds: [entity.id],
onClick: function() {
var id = this.issue.entityIds[0];
var operation = operationDelete(context, [id]);
if (!operation.disabled()) {
operation();
}
}
}));
}
return [new validationIssue({
type: type,
severity: 'warning',
message: function(context) {
var entity = context.hasEntity(this.entityIds[0]);
if (!entity) return '';
var entityLabel = utilDisplayLabel(entity, context.graph());
return t('issues.short_road.message', { highway: entityLabel });
},
reference: function(selection) {
selection.selectAll('.issue-reference')
.data([0])
.enter()
.append('div')
.attr('class', 'issue-reference')
.text(t('issues.short_road.reference'));
},
entityIds: [entity.id],
fixes: fixes
})];
};
validation.type = type;
return validation;
}