001 // License: GPL. See LICENSE file for details.
002 package org.openstreetmap.josm.data.validation;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.io.BufferedReader;
007 import java.io.File;
008 import java.io.FileNotFoundException;
009 import java.io.FileReader;
010 import java.io.FileWriter;
011 import java.io.IOException;
012 import java.io.PrintWriter;
013 import java.util.ArrayList;
014 import java.util.Arrays;
015 import java.util.Collection;
016 import java.util.HashMap;
017 import java.util.Map;
018 import java.util.TreeSet;
019 import java.util.regex.Matcher;
020 import java.util.regex.Pattern;
021
022 import javax.swing.JOptionPane;
023
024 import org.openstreetmap.josm.Main;
025 import org.openstreetmap.josm.actions.ValidateAction;
026 import org.openstreetmap.josm.data.validation.tests.BuildingInBuilding;
027 import org.openstreetmap.josm.data.validation.tests.Coastlines;
028 import org.openstreetmap.josm.data.validation.tests.CrossingWays;
029 import org.openstreetmap.josm.data.validation.tests.DeprecatedTags;
030 import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
031 import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
032 import org.openstreetmap.josm.data.validation.tests.DuplicateWay;
033 import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
034 import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
035 import org.openstreetmap.josm.data.validation.tests.NameMismatch;
036 import org.openstreetmap.josm.data.validation.tests.NodesDuplicatingWayTags;
037 import org.openstreetmap.josm.data.validation.tests.NodesWithSameName;
038 import org.openstreetmap.josm.data.validation.tests.OverlappingAreas;
039 import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
040 import org.openstreetmap.josm.data.validation.tests.PowerLines;
041 import org.openstreetmap.josm.data.validation.tests.RelationChecker;
042 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
043 import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
044 import org.openstreetmap.josm.data.validation.tests.TagChecker;
045 import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest;
046 import org.openstreetmap.josm.data.validation.tests.UnclosedWays;
047 import org.openstreetmap.josm.data.validation.tests.UnconnectedWays;
048 import org.openstreetmap.josm.data.validation.tests.UntaggedNode;
049 import org.openstreetmap.josm.data.validation.tests.UntaggedWay;
050 import org.openstreetmap.josm.data.validation.tests.WayConnectedToArea;
051 import org.openstreetmap.josm.data.validation.tests.WronglyOrderedWays;
052 import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
053 import org.openstreetmap.josm.gui.layer.Layer;
054 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
055 import org.openstreetmap.josm.gui.layer.ValidatorLayer;
056 import org.openstreetmap.josm.gui.preferences.ValidatorPreference;
057 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
058
059 /**
060 *
061 * A OSM data validator
062 *
063 * @author Francisco R. Santos <frsantos@gmail.com>
064 */
065 public class OsmValidator implements LayerChangeListener {
066
067 public static ValidatorLayer errorLayer = null;
068
069 /** The validate action */
070 public ValidateAction validateAction = new ValidateAction();
071
072 /** Grid detail, multiplier of east,north values for valuable cell sizing */
073 public static double griddetail;
074
075 public static final Collection<String> ignoredErrors = new TreeSet<String>();
076
077 /**
078 * All available tests
079 * TODO: is there any way to find out automatically all available tests?
080 */
081 @SuppressWarnings("unchecked")
082 public static Class<Test>[] allAvailableTests = new Class[] {
083 DuplicateNode.class, // ID 1 .. 99
084 OverlappingWays.class, // ID 101 .. 199
085 UntaggedNode.class, // ID 201 .. 299
086 UntaggedWay.class, // ID 301 .. 399
087 SelfIntersectingWay.class, // ID 401 .. 499
088 DuplicatedWayNodes.class, // ID 501 .. 599
089 CrossingWays.class, // ID 601 .. 699
090 SimilarNamedWays.class, // ID 701 .. 799
091 NodesWithSameName.class, // ID 801 .. 899
092 Coastlines.class, // ID 901 .. 999
093 WronglyOrderedWays.class, // ID 1001 .. 1099
094 UnclosedWays.class, // ID 1101 .. 1199
095 TagChecker.class, // ID 1201 .. 1299
096 UnconnectedWays.class, // ID 1301 .. 1399
097 DuplicateWay.class, // ID 1401 .. 1499
098 NameMismatch.class, // ID 1501 .. 1599
099 MultipolygonTest.class, // ID 1601 .. 1699
100 RelationChecker.class, // ID 1701 .. 1799
101 TurnrestrictionTest.class, // ID 1801 .. 1899
102 DuplicateRelation.class, // ID 1901 .. 1999
103 BuildingInBuilding.class, // ID 2001 .. 2099
104 DeprecatedTags.class, // ID 2101 .. 2199
105 OverlappingAreas.class, // ID 2201 .. 2299
106 WayConnectedToArea.class, // ID 2301 .. 2399
107 NodesDuplicatingWayTags.class, // ID 2401 .. 2499
108 PowerLines.class, // ID 2501 .. 2599
109 };
110
111 public OsmValidator() {
112 checkValidatorDir();
113 initializeGridDetail();
114 initializeTests(getTests());
115 loadIgnoredErrors(); //FIXME: load only when needed
116 }
117
118 /**
119 * Returns the plugin's directory of the plugin
120 *
121 * @return The directory of the plugin
122 */
123 public static String getValidatorDir()
124 {
125 return Main.pref.getPreferencesDir() + "validator/";
126 }
127
128 /**
129 * Check if plugin directory exists (store ignored errors file)
130 */
131 private void checkValidatorDir() {
132 try {
133 File pathDir = new File(getValidatorDir());
134 if (!pathDir.exists()) {
135 pathDir.mkdirs();
136 }
137 } catch (Exception e){
138 e.printStackTrace();
139 }
140 }
141
142 private void loadIgnoredErrors() {
143 ignoredErrors.clear();
144 if (Main.pref.getBoolean(ValidatorPreference.PREF_USE_IGNORE, true)) {
145 try {
146 final BufferedReader in = new BufferedReader(new FileReader(getValidatorDir() + "ignorederrors"));
147 for (String line = in.readLine(); line != null; line = in.readLine()) {
148 ignoredErrors.add(line);
149 }
150 } catch (final FileNotFoundException e) {
151 // Ignore
152 } catch (final IOException e) {
153 e.printStackTrace();
154 }
155 }
156 }
157
158 public static void addIgnoredError(String s) {
159 ignoredErrors.add(s);
160 }
161
162 public static boolean hasIgnoredError(String s) {
163 return ignoredErrors.contains(s);
164 }
165
166 public static void saveIgnoredErrors() {
167 try {
168 final PrintWriter out = new PrintWriter(new FileWriter(getValidatorDir() + "ignorederrors"), false);
169 for (String e : ignoredErrors) {
170 out.println(e);
171 }
172 out.close();
173 } catch (final IOException e) {
174 e.printStackTrace();
175 }
176 }
177
178 public static void initializeErrorLayer() {
179 if (!Main.pref.getBoolean(ValidatorPreference.PREF_LAYER, true))
180 return;
181 if (errorLayer == null) {
182 errorLayer = new ValidatorLayer();
183 Main.main.addLayer(errorLayer);
184 }
185 }
186
187 /** Gets a map from simple names to all tests. */
188 public static Map<String, Test> getAllTestsMap() {
189 Map<String, Test> tests = new HashMap<String, Test>();
190 for (Class<Test> testClass : getAllAvailableTests()) {
191 try {
192 Test test = testClass.newInstance();
193 tests.put(testClass.getSimpleName(), test);
194 } catch (Exception e) {
195 e.printStackTrace();
196 continue;
197 }
198 }
199 applyPrefs(tests, false);
200 applyPrefs(tests, true);
201 return tests;
202 }
203
204 private static void applyPrefs(Map<String, Test> tests, boolean beforeUpload) {
205 Pattern regexp = Pattern.compile("(\\w+)=(true|false),?");
206 Matcher m = regexp.matcher(Main.pref.get(beforeUpload ? ValidatorPreference.PREF_TESTS_BEFORE_UPLOAD
207 : ValidatorPreference.PREF_TESTS));
208 int pos = 0;
209 while (m.find(pos)) {
210 String testName = m.group(1);
211 Test test = tests.get(testName);
212 if (test != null) {
213 boolean enabled = Boolean.valueOf(m.group(2));
214 if (beforeUpload) {
215 test.testBeforeUpload = enabled;
216 } else {
217 test.enabled = enabled;
218 }
219 }
220 pos = m.end();
221 }
222 }
223
224 public static Collection<Test> getTests() {
225 return getAllTestsMap().values();
226 }
227
228 public static Collection<Test> getEnabledTests(boolean beforeUpload) {
229 Collection<Test> enabledTests = getTests();
230 for (Test t : new ArrayList<Test>(enabledTests)) {
231 if (beforeUpload ? t.testBeforeUpload : t.enabled) {
232 continue;
233 }
234 enabledTests.remove(t);
235 }
236 return enabledTests;
237 }
238
239 /**
240 * Gets the list of all available test classes
241 *
242 * @return An array of the test classes
243 */
244 public static Class<Test>[] getAllAvailableTests() {
245 return allAvailableTests;
246 }
247
248 /**
249 * Initialize grid details based on current projection system. Values based on
250 * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&error
251 * until most bugs were discovered while keeping the processing time reasonable)
252 */
253 public void initializeGridDetail() {
254 String code = Main.getProjection().toCode();
255 if (Arrays.asList(ProjectionPreference.wgs84.allCodes()).contains(code)) {
256 OsmValidator.griddetail = 10000;
257 } else if (Arrays.asList(ProjectionPreference.mercator.allCodes()).contains(code)) {
258 OsmValidator.griddetail = 0.01;
259 } else if (Arrays.asList(ProjectionPreference.lambert.allCodes()).contains(code)) {
260 OsmValidator.griddetail = 0.1;
261 } else {
262 OsmValidator.griddetail = 1.0;
263 }
264 }
265
266 /**
267 * Initializes all tests
268 * @param allTests The tests to initialize
269 */
270 public static void initializeTests(Collection<Test> allTests) {
271 for (Test test : allTests) {
272 try {
273 if (test.enabled) {
274 test.initialize();
275 }
276 } catch (Exception e) {
277 e.printStackTrace();
278 JOptionPane.showMessageDialog(Main.parent,
279 tr("Error initializing test {0}:\n {1}", test.getClass()
280 .getSimpleName(), e),
281 tr("Error"),
282 JOptionPane.ERROR_MESSAGE);
283 }
284 }
285 }
286
287 /* -------------------------------------------------------------------------- */
288 /* interface LayerChangeListener */
289 /* -------------------------------------------------------------------------- */
290 @Override
291 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
292 }
293
294 @Override
295 public void layerAdded(Layer newLayer) {
296 }
297
298 @Override
299 public void layerRemoved(Layer oldLayer) {
300 if (oldLayer == errorLayer) {
301 errorLayer = null;
302 return;
303 }
304 if (Main.map.mapView.getLayersOfType(OsmDataLayer.class).isEmpty()) {
305 if (errorLayer != null) {
306 Main.map.mapView.removeLayer(errorLayer);
307 }
308 }
309 }
310 }