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        // Test all way segments around this node.
050        // If there is an error in the unchanged part of the way, we do not need to warn the user about it.
051        for (Way way : n.getParentWays()) {
052                if (ignoreWay(way)) {
053                    continue;
054                }
055                // Do not simply use index of - a node may be in a way multiple times
056                for (int i = 0; i < way.getNodesCount(); i++) {
057                    if (n == way.getNode(i)) {
058                        if (i > 0) {
059                            visitWaySegment(way, i - 1);
060                        }
061                        if (i < way.getNodesCount() - 1) {
062                            visitWaySegment(way, i);
063                        }
064                    }
065                }
066            }
067    }
068
069    @Override
070    public void visit(Way w) {
071        if (ignoreWay(w)) {
072            return;
073        }
074        visitedWays.add(w);
075
076        testWay(w);
077    }
078
079    private void testWay(Way w) {
080        for (int i = 0; i < w.getNodesCount() - 1; i++) {
081            visitWaySegment(w, i);
082        }
083    }
084
085    private boolean ignoreWay(Way w) {
086        return visitedWays.contains(w) || w.hasTag("route", "ferry") || w.hasTag("bay", "fjord") || w.hasTag("natural", "strait");
087    }
088
089    private void visitWaySegment(Way w, int i) {
090        LatLon coor1 = w.getNode(i).getCoor();
091        LatLon coor2 = w.getNode(i + 1).getCoor();
092
093        if (coor1 != null && coor2 != null) {
094            Double length = coor1.greatCircleDistance(coor2);
095            if (length > maxlength) {
096                addErrorForSegment(new WaySegment(w, i), length / 1000.0);
097            }
098        }
099    }
100
101    private void addErrorForSegment(WaySegment waySegment, Double length) {
102        if (reported.add(waySegment)) {
103            errors.add(TestError.builder(this, Severity.WARNING, LONG_SEGMENT)
104                    .message(tr("Long segments"), marktr("Very long segment of {0} kilometers"), length.intValue())
105                    .primitives(waySegment.way)
106                    .highlightWaySegments(Collections.singleton(waySegment))
107                    .build());
108        }
109    }
110
111    @Override
112    public void startTest(ProgressMonitor monitor) {
113        super.startTest(monitor);
114        maxlength = Config.getPref().getInt("validator.maximum.segment.length", 15_000);
115        reported = new HashSet<>();
116        visitedWays = new HashSet<>();
117    }
118
119    @Override
120    public void endTest() {
121        super.endTest();
122        // free memory
123        visitedWays = null;
124        reported = null;
125    }
126
127    @Override
128    public boolean isPrimitiveUsable(OsmPrimitive p) {
129        return p.isUsable() && (isUsableWay(p) || isUsableNode(p));
130    }
131
132    private static boolean isUsableNode(OsmPrimitive p) {
133        // test changed nodes - ways referred by them may not be checked automatically.
134        return p instanceof Node && p.isDrawable();
135    }
136
137    private static boolean isUsableWay(OsmPrimitive p) {
138        // test only Ways with at least 2 nodes
139        return p instanceof Way && ((Way) p).getNodesCount() > 1;
140    }
141}