mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-25 23:13:42 +00:00
206 lines
7.6 KiB
JavaScript
206 lines
7.6 KiB
JavaScript
import { t } from '../util/locale';
|
|
import { modeDrawLine } from '../modes/draw_line';
|
|
import { operationDelete } from '../operations/delete';
|
|
import { utilDisplayLabel } from '../util';
|
|
import { osmRoutableHighwayTagValues } from '../osm/tags';
|
|
import { validationIssue, validationIssueFix } from '../core/validation';
|
|
import { services } from '../services';
|
|
|
|
export function validationDisconnectedWay() {
|
|
var type = 'disconnected_way';
|
|
|
|
function isTaggedAsHighway(entity) {
|
|
return osmRoutableHighwayTagValues[entity.tags.highway];
|
|
}
|
|
|
|
var validation = function checkDisconnectedWay(entity, graph) {
|
|
|
|
var routingIslandWays = routingIslandForEntity(entity);
|
|
if (!routingIslandWays) return [];
|
|
|
|
var fixes = [];
|
|
|
|
var isSingle = routingIslandWays.size === 1;
|
|
|
|
if (isSingle) {
|
|
|
|
if (entity.type === 'way' && !entity.isClosed()) {
|
|
var firstID = entity.first();
|
|
var lastID = entity.last();
|
|
|
|
var first = graph.entity(firstID);
|
|
if (first.tags.noexit !== 'yes') {
|
|
fixes.push(new validationIssueFix({
|
|
icon: 'iD-operation-continue-left',
|
|
title: t('issues.fix.continue_from_start.title'),
|
|
entityIds: [firstID],
|
|
onClick: function(context) {
|
|
var wayId = this.issue.entityIds[0];
|
|
var way = context.entity(wayId);
|
|
var vertexId = this.entityIds[0];
|
|
var vertex = context.entity(vertexId);
|
|
continueDrawing(way, vertex, context);
|
|
}
|
|
}));
|
|
}
|
|
var last = graph.entity(lastID);
|
|
if (last.tags.noexit !== 'yes') {
|
|
fixes.push(new validationIssueFix({
|
|
icon: 'iD-operation-continue',
|
|
title: t('issues.fix.continue_from_end.title'),
|
|
entityIds: [lastID],
|
|
onClick: function(context) {
|
|
var wayId = this.issue.entityIds[0];
|
|
var way = context.entity(wayId);
|
|
var vertexId = this.entityIds[0];
|
|
var vertex = context.entity(vertexId);
|
|
continueDrawing(way, vertex, context);
|
|
}
|
|
}));
|
|
}
|
|
|
|
} else {
|
|
fixes.push(new validationIssueFix({
|
|
title: t('issues.fix.connect_feature.title')
|
|
}));
|
|
}
|
|
|
|
fixes.push(new validationIssueFix({
|
|
icon: 'iD-operation-delete',
|
|
title: t('issues.fix.delete_feature.title'),
|
|
entityIds: [entity.id],
|
|
onClick: function(context) {
|
|
var id = this.issue.entityIds[0];
|
|
var operation = operationDelete([id], context);
|
|
if (!operation.disabled()) {
|
|
operation();
|
|
}
|
|
}
|
|
}));
|
|
} else {
|
|
fixes.push(new validationIssueFix({
|
|
title: t('issues.fix.connect_features.title')
|
|
}));
|
|
}
|
|
|
|
return [new validationIssue({
|
|
type: type,
|
|
severity: 'warning',
|
|
message: function(context) {
|
|
if (this.entityIds.length === 1) {
|
|
var entity = context.hasEntity(this.entityIds[0]);
|
|
return entity ? t('issues.disconnected_way.highway.message', { highway: utilDisplayLabel(entity, context) }) : '';
|
|
}
|
|
return t('issues.disconnected_way.routable.message.multiple', { count: this.entityIds.length.toString() });
|
|
},
|
|
reference: showReference,
|
|
entityIds: Array.from(routingIslandWays).map(function(way) { return way.id; }),
|
|
fixes: fixes
|
|
})];
|
|
|
|
|
|
function showReference(selection) {
|
|
selection.selectAll('.issue-reference')
|
|
.data([0])
|
|
.enter()
|
|
.append('div')
|
|
.attr('class', 'issue-reference')
|
|
.text(t('issues.disconnected_way.routable.reference'));
|
|
}
|
|
|
|
function routingIslandForEntity(entity) {
|
|
|
|
if (entity.type !== 'way') return null;
|
|
|
|
if (!isRoutableWay(entity, true)) return null;
|
|
|
|
var waysToCheck = [entity];
|
|
var routingIsland = new Set([entity]);
|
|
|
|
while (waysToCheck.length) {
|
|
var wayToCheck = waysToCheck.pop();
|
|
var childNodes = graph.childNodes(wayToCheck);
|
|
for (var i in childNodes) {
|
|
var vertex = childNodes[i];
|
|
var result = isConnectedVertex(vertex, routingIsland);
|
|
if (result === true) {
|
|
return null;
|
|
} else if (result === false) {
|
|
continue;
|
|
}
|
|
result.forEach(function(connectedWay) {
|
|
if (!routingIsland.has(connectedWay)) {
|
|
routingIsland.add(connectedWay);
|
|
waysToCheck.push(connectedWay);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return routingIsland;
|
|
}
|
|
|
|
function isConnectedVertex(vertex, routingIslandWays) {
|
|
// assume ways overlapping unloaded tiles are connected to the wider road network - #5938
|
|
var osm = services.osm;
|
|
if (osm && !osm.isDataLoaded(vertex.loc)) return true;
|
|
|
|
// entrances are considered connected
|
|
if (vertex.tags.entrance &&
|
|
vertex.tags.entrance !== 'no') return true;
|
|
if (vertex.tags.amenity === 'parking_entrance') return true;
|
|
|
|
var parentsWays = graph.parentWays(vertex);
|
|
|
|
// standalone vertex
|
|
if (parentsWays.length === 1) return false;
|
|
|
|
var connectedWays = new Set();
|
|
|
|
for (var i in parentsWays) {
|
|
var parentWay = parentsWays[i];
|
|
|
|
// ignore any way we've already accounted for
|
|
if (routingIslandWays.has(parentWay)) continue;
|
|
|
|
if (isRoutableWay(parentWay, false)) connectedWays.add(parentWay);
|
|
}
|
|
|
|
if (connectedWays.size) return connectedWays;
|
|
|
|
return false;
|
|
}
|
|
|
|
function isRoutableWay(way, ignoreInnerWays) {
|
|
if (isTaggedAsHighway(way) || way.tags.route === 'ferry') return true;
|
|
|
|
return graph.parentRelations(way).some(function(parentRelation) {
|
|
if (parentRelation.tags.type === 'route' &&
|
|
parentRelation.tags.route === 'ferry') return true;
|
|
|
|
if (parentRelation.isMultipolygon() &&
|
|
isTaggedAsHighway(parentRelation) &&
|
|
(!ignoreInnerWays || parentRelation.memberById(way.id).role !== 'inner')) return true;
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
function continueDrawing(way, vertex, context) {
|
|
// make sure the vertex is actually visible and editable
|
|
var map = context.map();
|
|
if (!map.editable() || !map.trimmedExtent().contains(vertex.loc)) {
|
|
map.zoomToEase(vertex);
|
|
}
|
|
|
|
context.enter(
|
|
modeDrawLine(context, way.id, context.graph(), context.graph(), 'line', way.affix(vertex.id), true)
|
|
);
|
|
}
|
|
|
|
|
|
validation.type = type;
|
|
|
|
return validation;
|
|
}
|