validate crossing aeroways (#9315)

This commit is contained in:
Kyle Hensel
2025-02-05 14:41:56 +01:00
committed by Martin Raifer
parent a2e347ac97
commit f244e63661
5 changed files with 131 additions and 3 deletions
+1
View File
@@ -41,6 +41,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
#### :scissors: Operations
#### :camera: Street-Level
#### :white_check_mark: Validation
* Add warning if aeroways cross each other, buildings or highways ([#9315], thanks [@k-yle])
#### :bug: Bugfixes
* Prevent degenerate ways caused by deleting a corner of a triangle ([#10003], thanks [@k-yle])
#### :earth_asia: Localization
+10
View File
@@ -1778,6 +1778,16 @@ en:
title: Crossing Ways
message: "{feature} crosses {feature2}"
tip: "Find features that incorrectly cross over one another"
aeroway-aeroway:
reference: "Crossing aeroways should be connected."
aeroway-building:
reference: "Aeroways crossing buildings should use bridges or different layers."
aeroway-highway:
reference: "Aeroways crossing railways should be connected."
aeroway-railway:
reference: "Aeroways crossing highways should be connected."
aeroway-waterway:
reference: "Aeroways crossing waterways should use tunnels."
building-building:
reference: "Buildings should not intersect except on different layers."
building-highway:
+4
View File
@@ -233,6 +233,10 @@ export var osmRoutableHighwayTagValues = {
unclassified: true, road: true, service: true, track: true, living_street: true, bus_guideway: true, busway: true,
path: true, footway: true, cycleway: true, bridleway: true, pedestrian: true, corridor: true, steps: true, ladder: true
};
/** aeroway tags that are treated as routable for aircraft */
export const osmRoutableAerowayTags = {
runway: true, taxiway: true
};
// "highway" tag values that generally do not allow motor vehicles
export var osmPathHighwayTagValues = {
path: true, footway: true, cycleway: true, bridleway: true, pedestrian: true, corridor: true, steps: true, ladder: true
+28 -3
View File
@@ -8,7 +8,7 @@ import { modeSelect } from '../modes/select';
import { geoAngle, geoExtent, geoLatToMeters, geoLonToMeters, geoLineIntersection,
geoSphericalClosestNode, geoSphericalDistance, geoVecAngle, geoVecLength, geoMetersToLat, geoMetersToLon } from '../geo';
import { osmNode } from '../osm/node';
import { osmFlowingWaterwayTagValues, osmPathHighwayTagValues, osmRailwayTrackTagValues, osmRoutableHighwayTagValues } from '../osm/tags';
import { osmFlowingWaterwayTagValues, osmPathHighwayTagValues, osmRailwayTrackTagValues, osmRoutableAerowayTags, osmRoutableHighwayTagValues } from '../osm/tags';
import { t } from '../core/localizer';
import { utilDisplayLabel } from '../util/utilDisplayLabel';
import { validationIssue, validationIssueFix } from '../core/validation';
@@ -44,7 +44,7 @@ export function validationCrossingWays(context) {
}
function allowsBridge(featureType) {
return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway';
return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway' || featureType === 'aeroway';
}
function allowsTunnel(featureType) {
return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway';
@@ -63,6 +63,8 @@ export function validationCrossingWays(context) {
var tags = entity.tags;
if (tags.aeroway in osmRoutableAerowayTags) return 'aeroway';
if (hasTag(tags, 'building') && !ignoredBuildings[tags.building]) return 'building';
if (hasTag(tags, 'highway') && osmRoutableHighwayTagValues[tags.highway]) return 'highway';
@@ -126,6 +128,9 @@ export function validationCrossingWays(context) {
primary: true, primary_link: true, secondary: true, secondary_link: true
};
/**
* @returns {object | null} the tags for the connecting node, or null if the entities should not be joined
*/
function tagsForConnectionNodeIfAllowed(entity1, entity2, graph, lessLikelyTags) {
var featureType1 = getFeatureType(entity1, graph);
var featureType2 = getFeatureType(entity2, graph);
@@ -134,6 +139,27 @@ export function validationCrossingWays(context) {
var geometry2 = entity2.geometry(graph);
var bothLines = geometry1 === 'line' && geometry2 === 'line';
/**
* @typedef {NonNullable<ReturnType<getFeatureType>>} FeatureType
* @type {`${FeatureType}-${FeatureType}`}
*/
const featureTypes = [featureType1, featureType2].sort().join('-');
if (featureTypes === 'aeroway-aeroway') return {};
if (featureTypes === 'aeroway-highway') {
const isServiceRoad = entity1.tags.highway === 'service' || entity2.tags.highway === 'service';
const isPath = entity1.tags.highway in osmPathHighwayTagValues || entity2.tags.highway in osmPathHighwayTagValues;
// only significant roads get the aeroway=aircraft_crossing tag
return isServiceRoad || isPath ? {} : { aeroway: 'aircraft_crossing' };
}
if (featureTypes === 'aeroway-railway') {
return { aeroway: 'aircraft_crossing', railway: 'level_crossing' };
}
if (featureTypes === 'aeroway-waterway') return null;
if (featureType1 === featureType2) {
if (featureType1 === 'highway') {
var entity1IsPath = osmPathHighwayTagValues[entity1.tags.highway];
@@ -167,7 +193,6 @@ export function validationCrossingWays(context) {
if (featureType1 === 'railway') return {};
} else {
var featureTypes = [featureType1, featureType2];
if (featureTypes.indexOf('highway') !== -1) {
if (featureTypes.indexOf('railway') !== -1) {
if (!bothLines) return {};
+88
View File
@@ -202,6 +202,60 @@ describe('iD.validations.crossing_ways', function () {
expect(issues).to.have.lengthOf(0);
});
it('ignores a routable aeroway crossing a non-routable aeroway', function() {
createWaysWithOneCrossingPoint({ aeroway: 'taxiway' }, { aeroway: 'aerodrome' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway crossing a road tunnel', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { highway: 'trunk', tunnel: 'yes', layer: '-1' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway crossing a road bridge', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { highway: 'trunk', bridge: 'yes', layer: '1' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway crossing a rail tunnel', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { railway: 'track', tunnel: 'yes', layer: '-1' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway crossing a rail bridge', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { railway: 'track', bridge: 'yes', layer: '1' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway bridge crossing a road', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway', bridge: 'yes', layer: '2' }, { highway: 'trunk', layer: '1' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway bridge crossing a railway', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway', bridge: 'yes', layer: '1' }, { railway: 'track' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway crossing a culvert', function() {
createWaysWithOneCrossingPoint({ aeroway: 'taxiway' }, { waterway: 'ditch', tunnel: 'culvert', layer: -1 });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
it('ignores an aeroway crossing a building on a different layer', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { building: 'yes', layer: '0.5' });
const issues = validate();
expect(issues).to.have.lengthOf(0);
});
// warning crossing cases between ways
it('flags road crossing road', function() {
createWaysWithOneCrossingPoint({ highway: 'residential' }, { highway: 'residential' });
@@ -453,4 +507,38 @@ describe('iD.validations.crossing_ways', function () {
verifySingleCrossingIssue(validate(), {});
});
it('flags an aeroway crosing another aeroway', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { aeroway: 'taxiway' });
verifySingleCrossingIssue(validate(), {});
});
it('flags an aeroway crosing a major road', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { highway: 'motorway' });
verifySingleCrossingIssue(validate(), { aeroway: 'aircraft_crossing' });
});
it('flags an aeroway crosing a service road', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { highway: 'service' });
verifySingleCrossingIssue(validate(), {});
});
it('flags an aeroway crosing a path', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { highway: 'corridor' });
verifySingleCrossingIssue(validate(), {});
});
it('flags an aeroway crosing a railway', function() {
createWaysWithOneCrossingPoint({ aeroway: 'taxiway' }, { railway: 'disused' });
verifySingleCrossingIssue(validate(), { aeroway: 'aircraft_crossing', railway: 'level_crossing' });
});
it('flags an aeroway crosing a waterway', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { waterway: 'canal' });
verifySingleCrossingIssue(validate(), null);
});
it('flags an aeroway crosing a building', function() {
createWaysWithOneCrossingPoint({ aeroway: 'runway' }, { building: 'hangar' });
verifySingleCrossingIssue(validate(), null);
});
});