001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.Arrays; 007import java.util.Set; 008import java.util.stream.Collectors; 009 010import org.openstreetmap.josm.data.osm.OsmPrimitive; 011import org.openstreetmap.josm.data.osm.Way; 012import org.openstreetmap.josm.data.validation.Severity; 013import org.openstreetmap.josm.data.validation.Test; 014import org.openstreetmap.josm.data.validation.TestError; 015import org.openstreetmap.josm.tools.Logging; 016import org.openstreetmap.josm.tools.Utils; 017 018/** 019 * Test that validates {@code lane:} tags. 020 * @since 6592 021 */ 022public class Lanes extends Test.TagTest { 023 024 private static final String[] BLACKLIST = { 025 "source:lanes", 026 "note:lanes", 027 "proposed:lanes", 028 "source:proposed:lanes", 029 "piste:lanes", 030 }; 031 032 /** 033 * Constructs a new {@code Lanes} test. 034 */ 035 public Lanes() { 036 super(tr("Lane tags"), tr("Test that validates ''lane:'' tags.")); 037 } 038 039 static int getLanesCount(String value) { 040 return value.isEmpty() ? 0 : value.replaceAll("[^|]", "").length() + 1; 041 } 042 043 protected void checkNumberOfLanesByKey(final OsmPrimitive p, String lanesKey, String message) { 044 final Set<Integer> lanesCount = 045 p.keys() 046 .filter(x -> x.endsWith(":" + lanesKey)) 047 .filter(x -> !Arrays.asList(BLACKLIST).contains(x)) 048 .map(key -> getLanesCount(p.get(key))) 049 .collect(Collectors.toSet()); 050 051 if (lanesCount.size() > 1) { 052 // if not all numbers are the same 053 errors.add(TestError.builder(this, Severity.WARNING, 3100) 054 .message(message) 055 .primitives(p) 056 .build()); 057 } else if (lanesCount.size() == 1 && p.hasKey(lanesKey)) { 058 // ensure that lanes <= *:lanes 059 try { 060 if (Integer.parseInt(p.get(lanesKey)) > lanesCount.iterator().next()) { 061 errors.add(TestError.builder(this, Severity.WARNING, 3100) 062 .message(tr("Number of {0} greater than {1}", lanesKey, "*:" + lanesKey)) 063 .primitives(p) 064 .build()); 065 } 066 } catch (NumberFormatException ignore) { 067 Logging.debug(ignore.getMessage()); 068 } 069 } 070 } 071 072 protected void checkNumberOfLanes(final OsmPrimitive p) { 073 final String lanes = p.get("lanes"); 074 if (lanes == null) return; 075 final String forward = Utils.firstNonNull(p.get("lanes:forward"), "0"); 076 final String backward = Utils.firstNonNull(p.get("lanes:backward"), "0"); 077 try { 078 if (Integer.parseInt(lanes) < Integer.parseInt(forward) + Integer.parseInt(backward)) { 079 errors.add(TestError.builder(this, Severity.WARNING, 3101) 080 .message(tr("Number of {0} greater than {1}", tr("{0}+{1}", "lanes:forward", "lanes:backward"), "lanes")) 081 .primitives(p) 082 .build()); 083 } 084 } catch (NumberFormatException ignore) { 085 Logging.debug(ignore.getMessage()); 086 } 087 } 088 089 @Override 090 public void check(OsmPrimitive p) { 091 checkNumberOfLanesByKey(p, "lanes", tr("Number of lane dependent values inconsistent")); 092 checkNumberOfLanesByKey(p, "lanes:forward", tr("Number of lane dependent values inconsistent in forward direction")); 093 checkNumberOfLanesByKey(p, "lanes:backward", tr("Number of lane dependent values inconsistent in backward direction")); 094 checkNumberOfLanes(p); 095 } 096 097 @Override 098 public boolean isPrimitiveUsable(OsmPrimitive p) { 099 return p.isTagged() && p instanceof Way && p.hasTag("highway") && super.isPrimitiveUsable(p); 100 } 101}