/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui.plugin; import java.awt.Color; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.feature.AttributeType; import com.vividsolutions.jump.feature.BasicFeature; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureDataset; import com.vividsolutions.jump.feature.FeatureSchema; import com.vividsolutions.jump.qa.ValidationError; import com.vividsolutions.jump.qa.Validator; import com.vividsolutions.jump.task.TaskMonitor; import com.vividsolutions.jump.util.CollectionMap; import com.vividsolutions.jump.workbench.WorkbenchContext; import com.vividsolutions.jump.workbench.model.Layer; import com.vividsolutions.jump.workbench.model.StandardCategoryNames; import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn; import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory; import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck; import com.vividsolutions.jump.workbench.plugin.PlugInContext; import com.vividsolutions.jump.workbench.plugin.ThreadedPlugIn; import com.vividsolutions.jump.workbench.ui.GUIUtil; import com.vividsolutions.jump.workbench.ui.MenuNames; import com.vividsolutions.jump.workbench.ui.DualPaneInputDialog; import com.vividsolutions.jump.workbench.ui.images.IconLoader; import com.vividsolutions.jump.workbench.ui.renderer.style.RingVertexStyle; import com.vividsolutions.jump.workbench.ui.renderer.style.VertexStyle; public class ValidateSelectedLayersPlugIn extends AbstractPlugIn implements ThreadedPlugIn { private static String CHECK_BASIC_TOPOLOGY = ""; private final static String CHECK_POLYGON_ORIENTATION = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-polygon-orientation"); private final static String CHECK_LINESTRINGS_SIMPLE = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-that-linestrings-are-simple"); private final static String CHECK_POLYGONS_HAVE_NO_HOLES = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-polygons-and-multipolygons-with-holes"); private final static String CHECK_NO_REPEATED_CONSECUTIVE_POINTS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-repeated-consective-points"); private final static String CHECK_MIN_SEGMENT_LENGTH = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-minimum-segment-length"); private final static String CHECK_MIN_ANGLE = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-minimum-angle"); private final static String MIN_SEGMENT_LENGTH = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.minimum-segment-length"); private final static String MIN_ANGLE = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.minimum-angle-in-degrees"); private final static String MIN_POLYGON_AREA = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.minimum-polygon-area"); private final static String CHECK_MIN_POLYGON_AREA = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-minimum-polygon-area"); private final static String DISALLOW_POINTS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-points"); private final static String DISALLOW_LINESTRINGS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-linestrings"); private final static String DISALLOW_POLYGONS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-polygons"); private final static String DISALLOW_MULTIPOINTS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-multipoints"); private final static String DISALLOW_MULTILINESTRINGS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-multilinestrings"); private final static String DISALLOW_MULTIPOLYGONS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-multipolygons"); private final static String DISALLOW_GEOMETRYCOLLECTIONS = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.disallow-geometrycollections"); private static final String ERROR = "ERROR"; private static final String SOURCE_FID = "SOURCE_FID"; private static final String GEOMETRY = "GEOMETRY"; private DualPaneInputDialog dialog; private FeatureSchema schema; private GeometryFactory geometryFactory = new GeometryFactory(); private Color GOLD = new Color(255, 192, 0, 150); private Validator validator; public ValidateSelectedLayersPlugIn() { initFeatureSchema(); } public boolean execute(PlugInContext context) throws Exception { validator = prompt(context); return validator != null; } public void initialize(PlugInContext context) throws Exception { FeatureInstaller featureInstaller = new FeatureInstaller(context.getWorkbenchContext()); featureInstaller.addMainMenuItem( this, //exe new String[] {MenuNames.TOOLS, MenuNames.TOOLS_QA}, //menu path this.getName() + "...", //name methode .getName recieved by AbstractPlugIn false, //checkbox null, //icon createEnableCheck(context.getWorkbenchContext())); //enable check } public static MultiEnableCheck createEnableCheck(WorkbenchContext workbenchContext) { EnableCheckFactory checkFactory = new EnableCheckFactory(workbenchContext); return new MultiEnableCheck() .add(checkFactory.createWindowWithLayerNamePanelMustBeActiveCheck()) .add(checkFactory.createAtLeastNLayersMustBeSelectedCheck(1)); } public void run(TaskMonitor monitor, PlugInContext context) throws Exception { //Call #getSelectedLayers before #clear, because #clear will surface //output window. [Jon Aquino] Layer[] selectedLayers = context.getSelectedLayers(); context.getOutputFrame().createNewDocument(); context.getOutputFrame().addHeader(1, I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.validation-errors")); for (int i = 0; (i < selectedLayers.length) && !monitor.isCancelRequested(); i++) { validate(selectedLayers[i], validator, context, monitor); } } private void initFeatureSchema() { schema = new FeatureSchema(); schema.addAttribute(ERROR, AttributeType.STRING); schema.addAttribute(SOURCE_FID, AttributeType.INTEGER); schema.addAttribute(GEOMETRY, AttributeType.GEOMETRY); } private Validator prompt(PlugInContext context) { if (dialog == null) { initDialog(context); } GUIUtil.centreOnWindow(dialog); dialog.setVisible(true); if (!dialog.wasOKPressed()) { return null; } Validator validator = new Validator(); validator.setCheckingBasicTopology(dialog.getBoolean( CHECK_BASIC_TOPOLOGY)); validator.setCheckingNoRepeatedConsecutivePoints(dialog.getBoolean( CHECK_NO_REPEATED_CONSECUTIVE_POINTS)); validator.setCheckingLineStringsSimple(dialog.getBoolean( CHECK_LINESTRINGS_SIMPLE)); validator.setCheckingPolygonOrientation(dialog.getBoolean( CHECK_POLYGON_ORIENTATION)); validator.setCheckingNoHoles(dialog.getBoolean( CHECK_POLYGONS_HAVE_NO_HOLES)); validator.setCheckingMinSegmentLength(dialog.getBoolean( CHECK_MIN_SEGMENT_LENGTH)); validator.setCheckingMinAngle(dialog.getBoolean(CHECK_MIN_ANGLE)); validator.setCheckingMinPolygonArea(dialog.getBoolean( CHECK_MIN_POLYGON_AREA)); validator.setMinSegmentLength(dialog.getDouble(MIN_SEGMENT_LENGTH)); validator.setMinAngle(dialog.getDouble(MIN_ANGLE)); validator.setMinPolygonArea(dialog.getDouble(MIN_POLYGON_AREA)); ArrayList disallowedGeometryClasses = new ArrayList(); if (dialog.getBoolean(DISALLOW_POINTS)) { disallowedGeometryClasses.add(Point.class); } if (dialog.getBoolean(DISALLOW_LINESTRINGS)) { disallowedGeometryClasses.add(LineString.class); } if (dialog.getBoolean(DISALLOW_POLYGONS)) { disallowedGeometryClasses.add(Polygon.class); } if (dialog.getBoolean(DISALLOW_MULTIPOINTS)) { disallowedGeometryClasses.add(MultiPoint.class); } if (dialog.getBoolean(DISALLOW_MULTILINESTRINGS)) { disallowedGeometryClasses.add(MultiLineString.class); } if (dialog.getBoolean(DISALLOW_MULTIPOLYGONS)) { disallowedGeometryClasses.add(MultiPolygon.class); } if (dialog.getBoolean(DISALLOW_GEOMETRYCOLLECTIONS)) { disallowedGeometryClasses.add(GeometryCollection.class); } validator.setDisallowedGeometryClasses(disallowedGeometryClasses); return validator; } private void validate(final Layer layer, final Validator validator, PlugInContext context, TaskMonitor monitor) { List validationErrors = validator.validate(layer.getFeatureCollectionWrapper() .getFeatures(), monitor); if (!validationErrors.isEmpty()) { addLayer(toLayer(I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.error-locations")+" - " + layer.getName(), toLocationFeatures(validationErrors, layer), layer, true, context), context); addLayer(toLayer(I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.bad-features")+" - " + layer.getName(), toFeatures(validationErrors, layer), layer, false, context), context); } outputSummary(context, layer, validationErrors); } private void outputSummary(PlugInContext context, Layer layer, List validationErrors) { context.getOutputFrame().addHeader(2, I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.layer")+" " + layer.getName()); if (validationErrors.isEmpty()) { context.getOutputFrame().addText(I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.no-validation-errors")); return; } CollectionMap descriptionToErrorMap = new CollectionMap(); for (Iterator i = validationErrors.iterator(); i.hasNext();) { ValidationError error = (ValidationError) i.next(); descriptionToErrorMap.addItem(error.getMessage(), error); } for (Iterator i = descriptionToErrorMap.keySet().iterator(); i.hasNext();) { String message = (String) i.next(); context.getOutputFrame().addField(message + ":", descriptionToErrorMap.getItems(message).size() + ""); } } private List toFeatures(List validationErrors, Layer sourceLayer) { ArrayList features = new ArrayList(); for (Iterator i = validationErrors.iterator(); i.hasNext();) { ValidationError error = (ValidationError) i.next(); features.add(toFeature(error, sourceLayer, (Geometry) error.getFeature().getGeometry().clone())); } return features; } private List toLocationFeatures(List validationErrors, Layer sourceLayer) { ArrayList features = new ArrayList(); for (Iterator i = validationErrors.iterator(); i.hasNext();) { ValidationError error = (ValidationError) i.next(); Geometry geometry = geometryFactory.createPoint(error.getLocation()); features.add(toFeature(error, sourceLayer, geometry)); } return features; } private Feature toFeature(ValidationError error, Layer sourceLayer, Geometry geometry) { Feature ringFeature = new BasicFeature(schema); ringFeature.setAttribute(SOURCE_FID, new Integer(error.getFeature().getID())); ringFeature.setAttribute(ERROR, error.getMessage()); ringFeature.setGeometry(geometry); return ringFeature; } private void addLayer(Layer errorLayer, PlugInContext context) { context.getLayerManager().addLayer(StandardCategoryNames.QA, errorLayer); } private Layer toLayer(String name, List features, Layer sourceLayer, boolean ringVertices, PlugInContext context) { boolean firingEvents = context.getLayerManager().isFiringEvents(); context.getLayerManager().setFiringEvents(false); try { FeatureDataset errorFeatureCollection = new FeatureDataset(features, schema); Layer errorLayer = new Layer(name, GOLD, errorFeatureCollection, context.getLayerManager()); if (ringVertices) { errorLayer.getBasicStyle().setEnabled(false); changeVertexToRing(errorLayer); } showVertices(errorLayer); return errorLayer; } finally { context.getLayerManager().setFiringEvents(firingEvents); } } private void changeVertexToRing(Layer errorLayer) { boolean firingEvents = errorLayer.getLayerManager().isFiringEvents(); errorLayer.getLayerManager().setFiringEvents(false); try { //Many parties assume that a layer always has a VertexStyle. Therefore, //disable events while we make the switch. [Jon Aquino] errorLayer.removeStyle(errorLayer.getStyle(VertexStyle.class)); errorLayer.addStyle(new RingVertexStyle()); errorLayer.getBasicStyle().setLineWidth(5); } finally { errorLayer.getLayerManager().setFiringEvents(firingEvents); } errorLayer.fireAppearanceChanged(); } private void showVertices(Layer errorLayer) { errorLayer.getVertexStyle().setEnabled(true); errorLayer.fireAppearanceChanged(); } private void initDialog(PlugInContext context) { CHECK_BASIC_TOPOLOGY = I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-basic-topology"); dialog = new DualPaneInputDialog(context.getWorkbenchFrame(), I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.validate-selected-layers"), true); dialog.setSideBarImage(IconLoader.icon("Validate.gif")); dialog.setSideBarDescription(I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.tests-layers-against-various-criteria")); dialog.addLabel("<HTML><STRONG>"+I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.geometry-metrics-validation")+"</STRONG></HTML>"); dialog.addSeparator(); dialog.addCheckBox(CHECK_BASIC_TOPOLOGY, true, "Test"); dialog.addCheckBox(CHECK_NO_REPEATED_CONSECUTIVE_POINTS, false); dialog.addCheckBox(CHECK_POLYGON_ORIENTATION, false, I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-that-polygon-shells-are-oriented-clockwise-and-holes-counterclockwise")); dialog.addCheckBox(CHECK_MIN_SEGMENT_LENGTH, false); dialog.addPositiveDoubleField(MIN_SEGMENT_LENGTH, 0.001, 5); dialog.addCheckBox(CHECK_MIN_ANGLE, false); dialog.addPositiveDoubleField(MIN_ANGLE, 1, 5); dialog.addCheckBox(CHECK_MIN_POLYGON_AREA, false); dialog.addPositiveDoubleField(MIN_POLYGON_AREA, 0.001, 5); dialog.addCheckBox(CHECK_LINESTRINGS_SIMPLE, false, I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.check-that-linestrings-are-simple")); //dialog.startNewColumn(); dialog.setRightPane(); dialog.addLabel("<HTML><STRONG>"+I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.geometry-types-validation")+"</STRONG></HTML>"); dialog.addSeparator(); dialog.addCheckBox(DISALLOW_POINTS, false); dialog.addCheckBox(DISALLOW_LINESTRINGS, false); dialog.addCheckBox(DISALLOW_POLYGONS, false); dialog.addCheckBox(DISALLOW_MULTIPOINTS, false); dialog.addCheckBox(DISALLOW_MULTILINESTRINGS, false); dialog.addCheckBox(DISALLOW_MULTIPOLYGONS, false); dialog.addCheckBox(CHECK_POLYGONS_HAVE_NO_HOLES, false); dialog.addCheckBox(DISALLOW_GEOMETRYCOLLECTIONS, false, I18N.get("ui.plugin.ValidateSelectedLayersPlugIn.geometry-collection-subtypes-are-not-disallowed")); } }