Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validate crossing aeroways #9315

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions data/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,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:
Expand Down
4 changes: 4 additions & 0 deletions modules/osm/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ export var osmRoutableHighwayTagValues = {
unclassified: true, road: true, service: true, track: true, living_street: true, bus_guideway: true,
path: true, footway: true, cycleway: true, bridleway: true, pedestrian: true, corridor: true, steps: 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
Expand Down
31 changes: 28 additions & 3 deletions modules/validations/crossing_ways.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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';
import { validationIssue, validationIssueFix } from '../core/validation';
Expand Down Expand Up @@ -42,7 +42,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';
Expand All @@ -61,6 +61,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';

Expand Down Expand Up @@ -125,6 +127,9 @@ export function validationCrossingWays(context) {
};
var nonCrossingHighways = { track: true };

/**
* @returns {object | null} the tags for the connecting node, or null if the entities should not be joined
*/
function tagsForConnectionNodeIfAllowed(entity1, entity2, graph) {
var featureType1 = getFeatureType(entity1, graph);
var featureType2 = getFeatureType(entity2, graph);
Expand All @@ -133,6 +138,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];
Expand All @@ -159,7 +185,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 {};
Expand Down
88 changes: 88 additions & 0 deletions test/spec/validations/crossing_ways.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,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' });
Expand Down Expand Up @@ -432,4 +486,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);
});
});