001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.util.Collections;
008import java.util.HashSet;
009import java.util.Set;
010
011import org.openstreetmap.josm.data.coor.LatLon;
012import org.openstreetmap.josm.data.osm.Node;
013import org.openstreetmap.josm.data.osm.OsmPrimitive;
014import org.openstreetmap.josm.data.osm.Way;
015import org.openstreetmap.josm.data.osm.WaySegment;
016import org.openstreetmap.josm.data.validation.Severity;
017import org.openstreetmap.josm.data.validation.Test;
018import org.openstreetmap.josm.data.validation.TestError;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020import org.openstreetmap.josm.spi.preferences.Config;
021
022/**
023 * Checks for very long segments.
024 *
025 * @since 8320
026 */
027public class LongSegment extends Test {
028
029    /** Long segment error */
030    protected static final int LONG_SEGMENT = 3501;
031    /** Maximum segment length for this test */
032    protected int maxlength;
033    /** set of visited ways. Tracking this increases performance when checking single nodes. */
034    private Set<Way> visitedWays;
035
036    /** set of way segments that have been reported */
037    protected Set<WaySegment> reported;
038
039    /**
040     * Constructor
041     */
042    public LongSegment() {
043        super(tr("Long segments"),
044              tr("This tests for long way segments, which are usually errors."));
045    }
046
047    @Override
048    public void visit(Node n) {
049        if (!partialSelection)
050            return;
051
052        // Test all way segments around this node.
053        // If there is an error in the unchanged part of the way, we do not need to warn the user about it.
054        for (Way way : n.getParentWays()) {
055                if (ignoreWay(way)) {
056                    continue;
057                }
058                // Do not simply use index of - a node may be in a way multiple times
059                for (int i = 0; i < way.getNodesCount(); i++) {
060                    if (n == way.getNode(i)) {
061                        if (i > 0) {
062                            visitWaySegment(way, i - 1);
063                        }
064                        if (i < way.getNodesCount() - 1) {
065                            visitWaySegment(way, i);
066                        }
067                    }
068                }
069            }
070    }
071
072    @Override
073    public void visit(Way w) {
074        if (ignoreWay(w)) {
075            return;
076        }
077        visitedWays.add(w);
078
079        testWay(w);
080    }
081
082    private void testWay(Way w) {
083        for (int i = 0; i < w.getNodesCount() - 1; i++) {
084            visitWaySegment(w, i);
085        }
086    }
087
088    private boolean ignoreWay(Way w) {
089        return w.hasKey("boundary") || w.hasTag("route", "ferry") || w.hasTag("bay", "fjord")
090                || w.hasTag("natural", "strait") || visitedWays.contains(w);
091    }
092
093    private void visitWaySegment(Way w, int i) {
094        LatLon coor1 = w.getNode(i).getCoor();
095        LatLon coor2 = w.getNode(i + 1).getCoor();
096
097        if (coor1 != null && coor2 != null) {
098            Double length = coor1.greatCircleDistance(coor2);
099            if (length > maxlength) {
100                addErrorForSegment(new WaySegment(w, i), length / 1000.0);
101            }
102        }
103    }
104
105    private void addErrorForSegment(WaySegment waySegment, Double length) {
106        if (reported.add(waySegment)) {
107            errors.add(TestError.builder(this, Severity.WARNING, LONG_SEGMENT)
108                    .message(tr("Long segments"), marktr("Very long segment of {0} kilometers"), length.intValue())
109                    .primitives(waySegment.getWay())
110                    .highlightWaySegments(Collections.singleton(waySegment))
111                    .build());
112        }
113    }
114
115    @Override
116    public void startTest(ProgressMonitor monitor) {
117        super.startTest(monitor);
118        maxlength = Config.getPref().getInt("validator.maximum.segment.length", 15_000);
119        reported = new HashSet<>();
120        visitedWays = new HashSet<>();
121    }
122
123    @Override
124    public void endTest() {
125        super.endTest();
126        // free memory
127        visitedWays = null;
128        reported = null;
129    }
130
131    @Override
132    public boolean isPrimitiveUsable(OsmPrimitive p) {
133        return p.isUsable() && (isUsableWay(p) || isUsableNode(p));
134    }
135
136    private static boolean isUsableNode(OsmPrimitive p) {
137        // test changed nodes - ways referred by them may not be checked automatically.
138        return p instanceof Node && ((Node) p).isLatLonKnown();
139    }
140
141    private static boolean isUsableWay(OsmPrimitive p) {
142        // test only Ways with at least 2 nodes
143        return p instanceof Way && ((Way) p).getNodesCount() > 1;
144    }
145}