source: josm/trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java @ 13670

Last change on this file since 13670 was 13670, checked in by Don-vip, 10 months ago

fix #16189 - Add "almost square check" for buildings (patch by marxin, modified)

  • Property svn:eol-style set to native
File size: 16.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GraphicsEnvironment;
7import java.io.File;
8import java.io.FileNotFoundException;
9import java.io.IOException;
10import java.io.PrintWriter;
11import java.nio.charset.StandardCharsets;
12import java.nio.file.Files;
13import java.nio.file.Path;
14import java.nio.file.Paths;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.EnumMap;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Map;
23import java.util.SortedMap;
24import java.util.TreeMap;
25import java.util.TreeSet;
26import java.util.function.Predicate;
27import java.util.stream.Collectors;
28
29import javax.swing.JOptionPane;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
33import org.openstreetmap.josm.data.validation.tests.Addresses;
34import org.openstreetmap.josm.data.validation.tests.ApiCapabilitiesTest;
35import org.openstreetmap.josm.data.validation.tests.BarriersEntrances;
36import org.openstreetmap.josm.data.validation.tests.Coastlines;
37import org.openstreetmap.josm.data.validation.tests.ConditionalKeys;
38import org.openstreetmap.josm.data.validation.tests.CrossingWays;
39import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
40import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
41import org.openstreetmap.josm.data.validation.tests.DuplicateWay;
42import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
43import org.openstreetmap.josm.data.validation.tests.Highways;
44import org.openstreetmap.josm.data.validation.tests.InternetTags;
45import org.openstreetmap.josm.data.validation.tests.Lanes;
46import org.openstreetmap.josm.data.validation.tests.LongSegment;
47import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
48import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
49import org.openstreetmap.josm.data.validation.tests.NameMismatch;
50import org.openstreetmap.josm.data.validation.tests.OpeningHourTest;
51import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
52import org.openstreetmap.josm.data.validation.tests.PowerLines;
53import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
54import org.openstreetmap.josm.data.validation.tests.RelationChecker;
55import org.openstreetmap.josm.data.validation.tests.RightAngleBuildingTest;
56import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
57import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
58import org.openstreetmap.josm.data.validation.tests.TagChecker;
59import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest;
60import org.openstreetmap.josm.data.validation.tests.UnclosedWays;
61import org.openstreetmap.josm.data.validation.tests.UnconnectedWays;
62import org.openstreetmap.josm.data.validation.tests.UntaggedNode;
63import org.openstreetmap.josm.data.validation.tests.UntaggedWay;
64import org.openstreetmap.josm.data.validation.tests.WayConnectedToArea;
65import org.openstreetmap.josm.data.validation.tests.WronglyOrderedWays;
66import org.openstreetmap.josm.gui.MainApplication;
67import org.openstreetmap.josm.gui.layer.ValidatorLayer;
68import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
69import org.openstreetmap.josm.gui.util.GuiHelper;
70import org.openstreetmap.josm.spi.preferences.Config;
71import org.openstreetmap.josm.tools.AlphanumComparator;
72import org.openstreetmap.josm.tools.Logging;
73import org.openstreetmap.josm.tools.Utils;
74
75/**
76 * A OSM data validator.
77 *
78 * @author Francisco R. Santos <frsantos@gmail.com>
79 */
80public final class OsmValidator {
81
82    private OsmValidator() {
83        // Hide default constructor for utilities classes
84    }
85
86    private static volatile ValidatorLayer errorLayer;
87
88    /** Grid detail, multiplier of east,north values for valuable cell sizing */
89    private static double griddetail;
90
91    private static final Collection<String> ignoredErrors = new TreeSet<>();
92
93    /**
94     * All registered tests
95     */
96    private static final Collection<Class<? extends Test>> allTests = new ArrayList<>();
97    private static final Map<String, Test> allTestsMap = new HashMap<>();
98
99    /**
100     * All available tests in core
101     */
102    @SuppressWarnings("unchecked")
103    private static final Class<Test>[] CORE_TEST_CLASSES = new Class[] {
104        /* FIXME - unique error numbers for tests aren't properly unique - ignoring will not work as expected */
105        DuplicateNode.class, // ID    1 ..   99
106        OverlappingWays.class, // ID  101 ..  199
107        UntaggedNode.class, // ID  201 ..  299
108        UntaggedWay.class, // ID  301 ..  399
109        SelfIntersectingWay.class, // ID  401 ..  499
110        DuplicatedWayNodes.class, // ID  501 ..  599
111        CrossingWays.Ways.class, // ID  601 ..  699
112        CrossingWays.Boundaries.class, // ID  601 ..  699
113        CrossingWays.Barrier.class, // ID  601 ..  699
114        CrossingWays.SelfCrossing.class, // ID  601 ..  699
115        SimilarNamedWays.class, // ID  701 ..  799
116        Coastlines.class, // ID  901 ..  999
117        WronglyOrderedWays.class, // ID 1001 .. 1099
118        UnclosedWays.class, // ID 1101 .. 1199
119        TagChecker.class, // ID 1201 .. 1299
120        UnconnectedWays.UnconnectedHighways.class, // ID 1301 .. 1399
121        UnconnectedWays.UnconnectedRailways.class, // ID 1301 .. 1399
122        UnconnectedWays.UnconnectedWaterways.class, // ID 1301 .. 1399
123        UnconnectedWays.UnconnectedNaturalOrLanduse.class, // ID 1301 .. 1399
124        UnconnectedWays.UnconnectedPower.class, // ID 1301 .. 1399
125        DuplicateWay.class, // ID 1401 .. 1499
126        NameMismatch.class, // ID  1501 ..  1599
127        MultipolygonTest.class, // ID  1601 ..  1699
128        RelationChecker.class, // ID  1701 ..  1799
129        TurnrestrictionTest.class, // ID  1801 ..  1899
130        DuplicateRelation.class, // ID 1901 .. 1999
131        WayConnectedToArea.class, // ID 2301 .. 2399
132        PowerLines.class, // ID 2501 .. 2599
133        Addresses.class, // ID 2601 .. 2699
134        Highways.class, // ID 2701 .. 2799
135        BarriersEntrances.class, // ID 2801 .. 2899
136        OpeningHourTest.class, // 2901 .. 2999
137        MapCSSTagChecker.class, // 3000 .. 3099
138        Lanes.class, // 3100 .. 3199
139        ConditionalKeys.class, // 3200 .. 3299
140        InternetTags.class, // 3300 .. 3399
141        ApiCapabilitiesTest.class, // 3400 .. 3499
142        LongSegment.class, // 3500 .. 3599
143        PublicTransportRouteTest.class, // 3600 .. 3699
144        RightAngleBuildingTest.class, // 3700 .. 3799
145    };
146
147    /**
148     * Adds a test to the list of available tests
149     * @param testClass The test class
150     */
151    public static void addTest(Class<? extends Test> testClass) {
152        allTests.add(testClass);
153        try {
154            allTestsMap.put(testClass.getName(), testClass.getConstructor().newInstance());
155        } catch (ReflectiveOperationException e) {
156            Logging.error(e);
157        }
158    }
159
160    static {
161        for (Class<? extends Test> testClass : CORE_TEST_CLASSES) {
162            addTest(testClass);
163        }
164    }
165
166    /**
167     * Initializes {@code OsmValidator}.
168     */
169    public static void initialize() {
170        checkValidatorDir();
171        initializeGridDetail();
172        loadIgnoredErrors(); //FIXME: load only when needed
173    }
174
175    /**
176     * Returns the validator directory.
177     *
178     * @return The validator directory
179     */
180    public static String getValidatorDir() {
181        File dir = new File(Config.getDirs().getUserDataDirectory(true), "validator");
182        try {
183            return dir.getAbsolutePath();
184        } catch (SecurityException e) {
185            Logging.log(Logging.LEVEL_ERROR, null, e);
186            return dir.getPath();
187        }
188    }
189
190    /**
191     * Check if validator directory exists (store ignored errors file)
192     */
193    private static void checkValidatorDir() {
194        File pathDir = new File(getValidatorDir());
195        try {
196            if (!pathDir.exists()) {
197                Utils.mkDirs(pathDir);
198            }
199        } catch (SecurityException e) {
200            Logging.log(Logging.LEVEL_ERROR, "Unable to check validator directory", e);
201        }
202    }
203
204    private static void loadIgnoredErrors() {
205        ignoredErrors.clear();
206        if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) {
207            Path path = Paths.get(getValidatorDir()).resolve("ignorederrors");
208            try {
209                if (path.toFile().exists()) {
210                    try {
211                        ignoredErrors.addAll(Files.readAllLines(path, StandardCharsets.UTF_8));
212                    } catch (FileNotFoundException e) {
213                        Logging.debug(Logging.getErrorMessage(e));
214                    } catch (IOException e) {
215                        Logging.error(e);
216                    }
217                }
218            } catch (SecurityException e) {
219                Logging.log(Logging.LEVEL_ERROR, "Unable to load ignored errors", e);
220            }
221        }
222    }
223
224    /**
225     * Adds an ignored error
226     * @param s The ignore group / sub group name
227     * @see TestError#getIgnoreGroup()
228     * @see TestError#getIgnoreSubGroup()
229     */
230    public static void addIgnoredError(String s) {
231        ignoredErrors.add(s);
232    }
233
234    /**
235     * Check if a error should be ignored
236     * @param s The ignore group / sub group name
237     * @return <code>true</code> to ignore that error
238     */
239    public static boolean hasIgnoredError(String s) {
240        return ignoredErrors.contains(s);
241    }
242
243    /**
244     * Saves the names of the ignored errors to a file
245     */
246    public static void saveIgnoredErrors() {
247        try (PrintWriter out = new PrintWriter(new File(getValidatorDir(), "ignorederrors"), StandardCharsets.UTF_8.name())) {
248            for (String e : ignoredErrors) {
249                out.println(e);
250            }
251        } catch (IOException e) {
252            Logging.error(e);
253        }
254    }
255
256    /**
257     * Initializes error layer.
258     */
259    public static synchronized void initializeErrorLayer() {
260        if (!ValidatorPrefHelper.PREF_LAYER.get())
261            return;
262        if (errorLayer == null) {
263            errorLayer = new ValidatorLayer();
264            MainApplication.getLayerManager().addLayer(errorLayer);
265        }
266    }
267
268    /**
269     * Resets error layer.
270     * @since 11852
271     */
272    public static synchronized void resetErrorLayer() {
273        errorLayer = null;
274    }
275
276    /**
277     * Gets a map from simple names to all tests.
278     * @return A map of all tests, indexed and sorted by the name of their Java class
279     */
280    public static SortedMap<String, Test> getAllTestsMap() {
281        applyPrefs(allTestsMap, false);
282        applyPrefs(allTestsMap, true);
283        return new TreeMap<>(allTestsMap);
284    }
285
286    /**
287     * Returns the instance of the given test class.
288     * @param <T> testClass type
289     * @param testClass The class of test to retrieve
290     * @return the instance of the given test class, if any, or {@code null}
291     * @since 6670
292     */
293    @SuppressWarnings("unchecked")
294    public static <T extends Test> T getTest(Class<T> testClass) {
295        if (testClass == null) {
296            return null;
297        }
298        return (T) allTestsMap.get(testClass.getName());
299    }
300
301    private static void applyPrefs(Map<String, Test> tests, boolean beforeUpload) {
302        for (String testName : Config.getPref().getList(beforeUpload
303        ? ValidatorPrefHelper.PREF_SKIP_TESTS_BEFORE_UPLOAD : ValidatorPrefHelper.PREF_SKIP_TESTS)) {
304            Test test = tests.get(testName);
305            if (test != null) {
306                if (beforeUpload) {
307                    test.testBeforeUpload = false;
308                } else {
309                    test.enabled = false;
310                }
311            }
312        }
313    }
314
315    /**
316     * Gets all tests that are possible
317     * @return The tests
318     */
319    public static Collection<Test> getTests() {
320        return getAllTestsMap().values();
321    }
322
323    /**
324     * Gets all tests that are run
325     * @param beforeUpload To get the ones that are run before upload
326     * @return The tests
327     */
328    public static Collection<Test> getEnabledTests(boolean beforeUpload) {
329        Collection<Test> enabledTests = getTests();
330        for (Test t : new ArrayList<>(enabledTests)) {
331            if (beforeUpload ? t.testBeforeUpload : t.enabled) {
332                continue;
333            }
334            enabledTests.remove(t);
335        }
336        return enabledTests;
337    }
338
339    /**
340     * Gets the list of all available test classes
341     *
342     * @return A collection of the test classes
343     */
344    public static Collection<Class<? extends Test>> getAllAvailableTestClasses() {
345        return Collections.unmodifiableCollection(allTests);
346    }
347
348    /**
349     * Initialize grid details based on current projection system. Values based on
350     * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&amp;error
351     * until most bugs were discovered while keeping the processing time reasonable)
352     */
353    public static void initializeGridDetail() {
354        String code = Main.getProjection().toCode();
355        if (Arrays.asList(ProjectionPreference.wgs84.allCodes()).contains(code)) {
356            OsmValidator.griddetail = 10_000;
357        } else if (Arrays.asList(ProjectionPreference.mercator.allCodes()).contains(code)) {
358            OsmValidator.griddetail = 0.01;
359        } else if (Arrays.asList(ProjectionPreference.lambert.allCodes()).contains(code)) {
360            OsmValidator.griddetail = 0.1;
361        } else {
362            OsmValidator.griddetail = 1.0;
363        }
364    }
365
366    /**
367     * Returns grid detail, multiplier of east,north values for valuable cell sizing
368     * @return grid detail
369     * @since 11852
370     */
371    public static double getGridDetail() {
372        return griddetail;
373    }
374
375    private static boolean testsInitialized;
376
377    /**
378     * Initializes all tests if this operations hasn't been performed already.
379     */
380    public static synchronized void initializeTests() {
381        if (!testsInitialized) {
382            Logging.debug("Initializing validator tests");
383            final long startTime = System.currentTimeMillis();
384            initializeTests(getTests());
385            testsInitialized = true;
386            if (Logging.isDebugEnabled()) {
387                final long elapsedTime = System.currentTimeMillis() - startTime;
388                Logging.debug("Initializing validator tests completed in {0}", Utils.getDurationString(elapsedTime));
389            }
390        }
391    }
392
393    /**
394     * Initializes all tests
395     * @param allTests The tests to initialize
396     */
397    public static void initializeTests(Collection<? extends Test> allTests) {
398        for (Test test : allTests) {
399            try {
400                if (test.enabled) {
401                    test.initialize();
402                }
403            } catch (Exception e) { // NOPMD
404                String message = tr("Error initializing test {0}:\n {1}", test.getClass().getSimpleName(), e);
405                Logging.error(message);
406                if (!GraphicsEnvironment.isHeadless()) {
407                    GuiHelper.runInEDT(() -> {
408                        JOptionPane.showMessageDialog(Main.parent, message, tr("Error"), JOptionPane.ERROR_MESSAGE);
409                    });
410                }
411            }
412        }
413    }
414
415    /**
416     * Groups the given collection of errors by severity, then message, then description.
417     * @param errors list of errors to group
418     * @param filterToUse optional filter
419     * @return collection of errors grouped by severity, then message, then description
420     * @since 12667
421     */
422    public static Map<Severity, Map<String, Map<String, List<TestError>>>> getErrorsBySeverityMessageDescription(
423            Collection<TestError> errors, Predicate<? super TestError> filterToUse) {
424        return errors.stream().filter(filterToUse).collect(
425                Collectors.groupingBy(TestError::getSeverity, () -> new EnumMap<>(Severity.class),
426                        Collectors.groupingBy(TestError::getMessage, () -> new TreeMap<>(AlphanumComparator.getInstance()),
427                                Collectors.groupingBy(e -> e.getDescription() == null ? "" : e.getDescription(),
428                                        () -> new TreeMap<>(AlphanumComparator.getInstance()),
429                                        Collectors.toList()
430                                ))));
431    }
432}
Note: See TracBrowser for help on using the repository browser.