001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032:
033: package com.vividsolutions.jump.workbench.ui.plugin;
034:
035: import java.awt.Color;
036: import java.util.ArrayList;
037: import java.util.Iterator;
038: import java.util.List;
039:
040: import com.vividsolutions.jts.geom.Geometry;
041: import com.vividsolutions.jts.geom.GeometryCollection;
042: import com.vividsolutions.jts.geom.GeometryFactory;
043: import com.vividsolutions.jts.geom.LineString;
044: import com.vividsolutions.jts.geom.MultiLineString;
045: import com.vividsolutions.jts.geom.MultiPoint;
046: import com.vividsolutions.jts.geom.MultiPolygon;
047: import com.vividsolutions.jts.geom.Point;
048: import com.vividsolutions.jts.geom.Polygon;
049: import com.vividsolutions.jump.I18N;
050: import com.vividsolutions.jump.feature.AttributeType;
051: import com.vividsolutions.jump.feature.BasicFeature;
052: import com.vividsolutions.jump.feature.Feature;
053: import com.vividsolutions.jump.feature.FeatureDataset;
054: import com.vividsolutions.jump.feature.FeatureSchema;
055: import com.vividsolutions.jump.qa.ValidationError;
056: import com.vividsolutions.jump.qa.Validator;
057: import com.vividsolutions.jump.task.TaskMonitor;
058: import com.vividsolutions.jump.util.CollectionMap;
059: import com.vividsolutions.jump.workbench.model.Layer;
060: import com.vividsolutions.jump.workbench.model.StandardCategoryNames;
061: import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
062: import com.vividsolutions.jump.workbench.plugin.PlugInContext;
063: import com.vividsolutions.jump.workbench.plugin.ThreadedPlugIn;
064: import com.vividsolutions.jump.workbench.ui.GUIUtil;
065: import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
066: import com.vividsolutions.jump.workbench.ui.images.IconLoader;
067: import com.vividsolutions.jump.workbench.ui.renderer.style.RingVertexStyle;
068: import com.vividsolutions.jump.workbench.ui.renderer.style.VertexStyle;
069:
070: public class ValidateSelectedLayersPlugIn extends AbstractPlugIn
071: implements ThreadedPlugIn {
072: private static String CHECK_BASIC_TOPOLOGY = "";
073: private final static String CHECK_POLYGON_ORIENTATION = I18N
074: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-polygon-orientation");
075: private final static String CHECK_LINESTRINGS_SIMPLE = I18N
076: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-that-linestrings-are-simple");
077: private final static String CHECK_POLYGONS_HAVE_NO_HOLES = I18N
078: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-polygons-and-multipolygons-with-holes");
079: private final static String CHECK_NO_REPEATED_CONSECUTIVE_POINTS = I18N
080: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-repeated-consective-points");
081: private final static String CHECK_MIN_SEGMENT_LENGTH = I18N
082: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-minimum-segment-length");
083: private final static String CHECK_MIN_ANGLE = I18N
084: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-minimum-angle");
085: private final static String MIN_SEGMENT_LENGTH = I18N
086: .get("ui.plugin.ValidateSelectedLayersPlugIn.minimum-segment-length");
087: private final static String MIN_ANGLE = I18N
088: .get("ui.plugin.ValidateSelectedLayersPlugIn.minimum-angle-in-degrees");
089: private final static String MIN_POLYGON_AREA = I18N
090: .get("ui.plugin.ValidateSelectedLayersPlugIn.minimum-polygon-area");
091: private final static String CHECK_MIN_POLYGON_AREA = I18N
092: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-minimum-polygon-area");
093: private final static String DISALLOW_POINTS = I18N
094: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-points");
095: private final static String DISALLOW_LINESTRINGS = I18N
096: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-linestrings");
097: private final static String DISALLOW_POLYGONS = I18N
098: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-polygons");
099: private final static String DISALLOW_MULTIPOINTS = I18N
100: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-multipoints");
101: private final static String DISALLOW_MULTILINESTRINGS = I18N
102: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-multilinestrings");
103: private final static String DISALLOW_MULTIPOLYGONS = I18N
104: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-multipolygons");
105: private final static String DISALLOW_GEOMETRYCOLLECTIONS = I18N
106: .get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-geometrycollections");
107: private static final String ERROR = "ERROR";
108: private static final String SOURCE_FID = "SOURCE_FID";
109: private static final String GEOMETRY = "GEOMETRY";
110: private MultiInputDialog dialog;
111: private FeatureSchema schema;
112: private GeometryFactory geometryFactory = new GeometryFactory();
113: private Color GOLD = new Color(255, 192, 0, 150);
114: private Validator validator;
115:
116: public ValidateSelectedLayersPlugIn() {
117: initFeatureSchema();
118: }
119:
120: public boolean execute(PlugInContext context) throws Exception {
121: validator = prompt(context);
122:
123: return validator != null;
124: }
125:
126: public void run(TaskMonitor monitor, PlugInContext context)
127: throws Exception {
128: //Call #getSelectedLayers before #clear, because #clear will surface
129: //output window. [Jon Aquino]
130: Layer[] selectedLayers = context.getSelectedLayers();
131: context.getOutputFrame().createNewDocument();
132: context
133: .getOutputFrame()
134: .addHeader(
135: 1,
136: I18N
137: .get("ui.plugin.ValidateSelectedLayersPlugIn.validation-errors"));
138:
139: for (int i = 0; (i < selectedLayers.length)
140: && !monitor.isCancelRequested(); i++) {
141: validate(selectedLayers[i], validator, context, monitor);
142: }
143: }
144:
145: private void initFeatureSchema() {
146: schema = new FeatureSchema();
147: schema.addAttribute(ERROR, AttributeType.STRING);
148: schema.addAttribute(SOURCE_FID, AttributeType.INTEGER);
149: schema.addAttribute(GEOMETRY, AttributeType.GEOMETRY);
150: }
151:
152: private Validator prompt(PlugInContext context) {
153: if (dialog == null) {
154: initDialog(context);
155: }
156:
157: dialog.setVisible(true);
158:
159: if (!dialog.wasOKPressed()) {
160: return null;
161: }
162:
163: Validator validator = new Validator();
164: validator.setCheckingBasicTopology(dialog
165: .getBoolean(CHECK_BASIC_TOPOLOGY));
166: validator.setCheckingNoRepeatedConsecutivePoints(dialog
167: .getBoolean(CHECK_NO_REPEATED_CONSECUTIVE_POINTS));
168: validator.setCheckingLineStringsSimple(dialog
169: .getBoolean(CHECK_LINESTRINGS_SIMPLE));
170: validator.setCheckingPolygonOrientation(dialog
171: .getBoolean(CHECK_POLYGON_ORIENTATION));
172: validator.setCheckingNoHoles(dialog
173: .getBoolean(CHECK_POLYGONS_HAVE_NO_HOLES));
174: validator.setCheckingMinSegmentLength(dialog
175: .getBoolean(CHECK_MIN_SEGMENT_LENGTH));
176: validator.setCheckingMinAngle(dialog
177: .getBoolean(CHECK_MIN_ANGLE));
178: validator.setCheckingMinPolygonArea(dialog
179: .getBoolean(CHECK_MIN_POLYGON_AREA));
180: validator.setMinSegmentLength(dialog
181: .getDouble(MIN_SEGMENT_LENGTH));
182: validator.setMinAngle(dialog.getDouble(MIN_ANGLE));
183: validator.setMinPolygonArea(dialog.getDouble(MIN_POLYGON_AREA));
184:
185: ArrayList disallowedGeometryClasses = new ArrayList();
186:
187: if (dialog.getBoolean(DISALLOW_POINTS)) {
188: disallowedGeometryClasses.add(Point.class);
189: }
190:
191: if (dialog.getBoolean(DISALLOW_LINESTRINGS)) {
192: disallowedGeometryClasses.add(LineString.class);
193: }
194:
195: if (dialog.getBoolean(DISALLOW_POLYGONS)) {
196: disallowedGeometryClasses.add(Polygon.class);
197: }
198:
199: if (dialog.getBoolean(DISALLOW_MULTIPOINTS)) {
200: disallowedGeometryClasses.add(MultiPoint.class);
201: }
202:
203: if (dialog.getBoolean(DISALLOW_MULTILINESTRINGS)) {
204: disallowedGeometryClasses.add(MultiLineString.class);
205: }
206:
207: if (dialog.getBoolean(DISALLOW_MULTIPOLYGONS)) {
208: disallowedGeometryClasses.add(MultiPolygon.class);
209: }
210:
211: if (dialog.getBoolean(DISALLOW_GEOMETRYCOLLECTIONS)) {
212: disallowedGeometryClasses.add(GeometryCollection.class);
213: }
214:
215: validator
216: .setDisallowedGeometryClasses(disallowedGeometryClasses);
217:
218: return validator;
219: }
220:
221: private void validate(final Layer layer, final Validator validator,
222: PlugInContext context, TaskMonitor monitor) {
223: List validationErrors = validator.validate(layer
224: .getFeatureCollectionWrapper().getFeatures(), monitor);
225:
226: if (!validationErrors.isEmpty()) {
227: addLayer(
228: toLayer(
229: I18N
230: .get("ui.plugin.ValidateSelectedLayersPlugIn.error-locations")
231: + " - " + layer.getName(),
232: toLocationFeatures(validationErrors, layer),
233: layer, true, context), context);
234: addLayer(
235: toLayer(
236: I18N
237: .get("ui.plugin.ValidateSelectedLayersPlugIn.bad-features")
238: + " - " + layer.getName(),
239: toFeatures(validationErrors, layer), layer,
240: false, context), context);
241: }
242:
243: outputSummary(context, layer, validationErrors);
244: }
245:
246: private void outputSummary(PlugInContext context, Layer layer,
247: List validationErrors) {
248: context
249: .getOutputFrame()
250: .addHeader(
251: 2,
252: I18N
253: .get("ui.plugin.ValidateSelectedLayersPlugIn.layer")
254: + " " + layer.getName());
255:
256: if (validationErrors.isEmpty()) {
257: context
258: .getOutputFrame()
259: .addText(
260: I18N
261: .get("ui.plugin.ValidateSelectedLayersPlugIn.no-validation-errors"));
262:
263: return;
264: }
265:
266: CollectionMap descriptionToErrorMap = new CollectionMap();
267:
268: for (Iterator i = validationErrors.iterator(); i.hasNext();) {
269: ValidationError error = (ValidationError) i.next();
270: descriptionToErrorMap.addItem(error.getMessage(), error);
271: }
272:
273: for (Iterator i = descriptionToErrorMap.keySet().iterator(); i
274: .hasNext();) {
275: String message = (String) i.next();
276: context.getOutputFrame()
277: .addField(
278: message + ":",
279: descriptionToErrorMap.getItems(message)
280: .size()
281: + "");
282: }
283: }
284:
285: private List toFeatures(List validationErrors, Layer sourceLayer) {
286: ArrayList features = new ArrayList();
287:
288: for (Iterator i = validationErrors.iterator(); i.hasNext();) {
289: ValidationError error = (ValidationError) i.next();
290: features.add(toFeature(error, sourceLayer, (Geometry) error
291: .getFeature().getGeometry().clone()));
292: }
293:
294: return features;
295: }
296:
297: private List toLocationFeatures(List validationErrors,
298: Layer sourceLayer) {
299: ArrayList features = new ArrayList();
300:
301: for (Iterator i = validationErrors.iterator(); i.hasNext();) {
302: ValidationError error = (ValidationError) i.next();
303: Geometry geometry = geometryFactory.createPoint(error
304: .getLocation());
305: features.add(toFeature(error, sourceLayer, geometry));
306: }
307:
308: return features;
309: }
310:
311: private Feature toFeature(ValidationError error, Layer sourceLayer,
312: Geometry geometry) {
313: Feature ringFeature = new BasicFeature(schema);
314: ringFeature.setAttribute(SOURCE_FID, new Integer(error
315: .getFeature().getID()));
316: ringFeature.setAttribute(ERROR, error.getMessage());
317: ringFeature.setGeometry(geometry);
318:
319: return ringFeature;
320: }
321:
322: private void addLayer(Layer errorLayer, PlugInContext context) {
323: context.getLayerManager().addLayer(StandardCategoryNames.QA,
324: errorLayer);
325: }
326:
327: private Layer toLayer(String name, List features,
328: Layer sourceLayer, boolean ringVertices,
329: PlugInContext context) {
330: boolean firingEvents = context.getLayerManager()
331: .isFiringEvents();
332: context.getLayerManager().setFiringEvents(false);
333:
334: try {
335: FeatureDataset errorFeatureCollection = new FeatureDataset(
336: features, schema);
337: Layer errorLayer = new Layer(name, GOLD,
338: errorFeatureCollection, context.getLayerManager());
339:
340: if (ringVertices) {
341: errorLayer.getBasicStyle().setEnabled(false);
342: changeVertexToRing(errorLayer);
343: }
344:
345: showVertices(errorLayer);
346:
347: return errorLayer;
348: } finally {
349: context.getLayerManager().setFiringEvents(firingEvents);
350: }
351: }
352:
353: private void changeVertexToRing(Layer errorLayer) {
354: boolean firingEvents = errorLayer.getLayerManager()
355: .isFiringEvents();
356: errorLayer.getLayerManager().setFiringEvents(false);
357:
358: try {
359: //Many parties assume that a layer always has a VertexStyle. Therefore,
360: //disable events while we make the switch. [Jon Aquino]
361: errorLayer.removeStyle(errorLayer
362: .getStyle(VertexStyle.class));
363: errorLayer.addStyle(new RingVertexStyle());
364: errorLayer.getBasicStyle().setLineWidth(5);
365: } finally {
366: errorLayer.getLayerManager().setFiringEvents(firingEvents);
367: }
368:
369: errorLayer.fireAppearanceChanged();
370: }
371:
372: private void showVertices(Layer errorLayer) {
373: errorLayer.getVertexStyle().setEnabled(true);
374: errorLayer.fireAppearanceChanged();
375: }
376:
377: private void initDialog(PlugInContext context) {
378:
379: CHECK_BASIC_TOPOLOGY = I18N
380: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-basic-topology");
381: dialog = new MultiInputDialog(
382: context.getWorkbenchFrame(),
383: I18N
384: .get("ui.plugin.ValidateSelectedLayersPlugIn.validate-selected-layers"),
385: true);
386: dialog.setSideBarImage(IconLoader.icon("Validate.gif"));
387: dialog
388: .setSideBarDescription(I18N
389: .get("ui.plugin.ValidateSelectedLayersPlugIn.tests-layers-against-various-criteria"));
390: dialog
391: .addLabel("<HTML><STRONG>"
392: + I18N
393: .get("ui.plugin.ValidateSelectedLayersPlugIn.geometry-metrics-validation")
394: + "</STRONG></HTML>");
395: dialog.addSeparator();
396: dialog.addCheckBox(CHECK_BASIC_TOPOLOGY, true, "Test");
397: dialog.addCheckBox(CHECK_NO_REPEATED_CONSECUTIVE_POINTS, false);
398: dialog
399: .addCheckBox(
400: CHECK_POLYGON_ORIENTATION,
401: false,
402: I18N
403: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-that-polygon-shells-are-oriented-clockwise-and-holes-counterclockwise"));
404: dialog.addCheckBox(CHECK_MIN_SEGMENT_LENGTH, false);
405: dialog.addPositiveDoubleField(MIN_SEGMENT_LENGTH, 0.001, 5);
406: dialog.addCheckBox(CHECK_MIN_ANGLE, false);
407: dialog.addPositiveDoubleField(MIN_ANGLE, 1, 5);
408: dialog.addCheckBox(CHECK_MIN_POLYGON_AREA, false);
409: dialog.addPositiveDoubleField(MIN_POLYGON_AREA, 0.001, 5);
410: dialog
411: .addCheckBox(
412: CHECK_LINESTRINGS_SIMPLE,
413: false,
414: I18N
415: .get("ui.plugin.ValidateSelectedLayersPlugIn.check-that-linestrings-are-simple"));
416: dialog.startNewColumn();
417: dialog
418: .addLabel("<HTML><STRONG>"
419: + I18N
420: .get("ui.plugin.ValidateSelectedLayersPlugIn.geometry-types-validation")
421: + "</STRONG></HTML>");
422: dialog.addSeparator();
423: dialog.addCheckBox(DISALLOW_POINTS, false);
424: dialog.addCheckBox(DISALLOW_LINESTRINGS, false);
425: dialog.addCheckBox(DISALLOW_POLYGONS, false);
426: dialog.addCheckBox(DISALLOW_MULTIPOINTS, false);
427: dialog.addCheckBox(DISALLOW_MULTILINESTRINGS, false);
428: dialog.addCheckBox(DISALLOW_MULTIPOLYGONS, false);
429: dialog.addCheckBox(CHECK_POLYGONS_HAVE_NO_HOLES, false);
430: dialog
431: .addCheckBox(
432: DISALLOW_GEOMETRYCOLLECTIONS,
433: false,
434: I18N
435: .get("ui.plugin.ValidateSelectedLayersPlugIn.geometry-collection-subtypes-are-not-disallowed"));
436: GUIUtil.centreOnWindow(dialog);
437: }
438: }
|