/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * JUMP is Copyright (C) 2003 Vivid Solutions * * This program implements extensions to JUMP and is * Copyright (C) 2004 Integrated Systems Analysts, Inc. * * 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: * * Integrated Systems Analysts, Inc. * 630C Anchors St., Suite 101 * Fort Walton Beach, Florida * USA * * (850)862-7321 */ package org.openjump.core.ui.plugin.tools; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.openjump.core.geomutils.algorithm.GeometryConverter; import org.openjump.core.geomutils.algorithm.IntersectGeometries; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.index.SpatialIndex; import com.vividsolutions.jts.index.strtree.STRtree; import com.vividsolutions.jts.operation.polygonize.Polygonizer; 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.FeatureCollection; import com.vividsolutions.jump.feature.FeatureDataset; import com.vividsolutions.jump.feature.FeatureDatasetFactory; import com.vividsolutions.jump.feature.FeatureSchema; import com.vividsolutions.jump.task.TaskMonitor; import com.vividsolutions.jump.tools.AttributeMapping; import com.vividsolutions.jump.workbench.model.Layer; import com.vividsolutions.jump.workbench.model.StandardCategoryNames; 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.ThreadedBasePlugIn; import com.vividsolutions.jump.workbench.ui.GUIUtil; import com.vividsolutions.jump.workbench.ui.GenericNames; import com.vividsolutions.jump.workbench.ui.MenuNames; import com.vividsolutions.jump.workbench.ui.MultiInputDialog; /** * @author sstein * @version 4 May 2008 * * Merges/Intersects two polygon layers into one layer. It therefore calculates * all geometric intersections between the polygons. Afterwards the attributes * are transferred. The later step assumes that a new created intersection * polygon has at max only one correspondent polygon per layer. * TODO : abstract methods to be able to intersect not only two polygon layers * TODO: translate Error messages * */ public class IntersectPolygonLayersPlugIn extends ThreadedBasePlugIn { private final static String LAYER1 = GenericNames.LAYER_A; private final static String LAYER2 = GenericNames.LAYER_B; private final static String sTRANSFER = I18N .get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Transfer-attributes"); private String sDescription = "Intersects all geometries of two layers that contain both polygons. Note: The Planar Graph function provides similar functionality."; private final static String sAccurracy = "Set calculation accuray in map units"; // -- reset in execute to correct language private MultiInputDialog dialog; private Layer layer1 = null; private Layer layer2 = null; private String methodNameToRun; private boolean exceptionThrown = false; private PlugInContext context = null; private boolean transferAtt = true; private double accurracy = 0.01; public void initialize(PlugInContext context) throws Exception { context.getFeatureInstaller().addMainMenuItem( this, new String[] {MenuNames.TOOLS, MenuNames.TOOLS_ANALYSIS}, this.getName(), false, null, new MultiEnableCheck().add( new EnableCheckFactory(context.getWorkbenchContext()) .createTaskWindowMustBeActiveCheck()).add( new EnableCheckFactory(context.getWorkbenchContext()) .createAtLeastNLayersMustExistCheck(1))); this.sDescription = I18N .get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.sDescrition"); } public boolean execute(PlugInContext context) throws Exception { MultiInputDialog dialog = new MultiInputDialog(context .getWorkbenchFrame(), getName(), true); if(layer1 == null){ layer1 = context.getCandidateLayer(0); layer2 = context.getCandidateLayer(0); } setDialogValues(dialog, context); GUIUtil.centreOnWindow(dialog); dialog.setVisible(true); if (!dialog.wasOKPressed()) { return false; } getDialogValues(dialog); return true; } public String getName() { return I18N .get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Intersect-Polygon-Layers") + "..."; } public void run(TaskMonitor monitor, PlugInContext context) throws Exception { this.context = context; monitor.allowCancellationRequests(); FeatureSchema featureSchema = new FeatureSchema(); FeatureCollection resultColl = runIntersectionNew(layer1 .getFeatureCollectionWrapper(), layer2 .getFeatureCollectionWrapper(), this.transferAtt, monitor, context); if ((resultColl != null) && (resultColl.size() > 0)) { context.addLayer(StandardCategoryNames.RESULT, I18N.get("ui.plugin.analysis.GeometryFunctionPlugIn.intersection") + "-" + layer1.getName() +"-"+ layer2.getName(), resultColl); } if (exceptionThrown) context .getWorkbenchFrame() .warnUser( I18N .get("org.openjump.sigle.plugin.SpatialJoinPlugIn.Error-while-executing-spatial-function")); } /** * Merges/Intersects two polygon layers into one layer. It therefore * extracts all lines of the polygons and creates new polygons * with the Polygonizer class. Afterwards it is checked which polygons have a * correspondent in the input layers. If the polygon does not have it is removed * otherwise the attributes are transferred. The later step assumes that a new created * intersection polygon has at max only one correspondent polygon per layer. * * @param fcA * @param fcB * @param transfer Attributes should attributes be transfered? * @param monitor can be null * @param context can be null * @return a FeatureCollection that contains the result of the intersection * (i.e. the new created features) */ private FeatureCollection runIntersectionNew(FeatureCollection fcA, FeatureCollection fcB, boolean transferAttributes, TaskMonitor monitor, PlugInContext context) { FeatureCollection fd = null; // -- put all geoms in one list and calculate their intersections // i.e. iterate until pure ArrayList<Geometry> geomsToCheck = new ArrayList<Geometry>(); for (Iterator iterator = fcA.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); geomsToCheck.add(f.getGeometry()); } for (Iterator iterator = fcB.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); geomsToCheck.add(f.getGeometry()); } // -- sort out the different geometry types and receive Lines ArrayList lines = new ArrayList(); for (Iterator iterator = geomsToCheck.iterator(); iterator.hasNext();) { Geometry geom = (Geometry) iterator.next(); if ((geom instanceof Polygon) || (geom instanceof MultiPolygon)) { // everything is fine // -- get Lines lines.addAll(GeometryConverter.transformPolygonToLineStrings(geom)); } else { // -- if (context != null) { context.getWorkbenchFrame().warnUser( I18N.get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Geometry-no-Polygon-or-Multi-Polygon")); } // -- return null; } } //-- calculate the intersections and use the Polygonizer Collection nodedLines = IntersectGeometries.nodeLines((List) lines); Polygonizer polygonizer = new Polygonizer(); for (Iterator i = nodedLines.iterator(); i.hasNext(); ) { Geometry g = (Geometry) i.next(); polygonizer.add(g); } //-- get the Polygons Collection withoutIntersection = polygonizer.getPolygons(); //-- check if the polygon has a correspondent // if yes, transfer the attributes - if no: remove the polygon //-- build a tree for the existing layers first. SpatialIndex treeA = new STRtree(); for (Iterator iterator = fcA.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); treeA.insert(f.getGeometry().getEnvelopeInternal(), f); } SpatialIndex treeB = new STRtree(); for (Iterator iterator = fcB.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); treeB.insert(f.getGeometry().getEnvelopeInternal(), f); } // -- get all intersecting features (usually there should be only one // corresponding feature per layer) // to avoid problems with spatial predicates we do the query for an // internal point of the result polygons // and apply an point in polygon test AttributeMapping mapping = new AttributeMapping(fcA.getFeatureSchema(), fcB.getFeatureSchema()); // -- create the empty dataset with the final FeatureSchema fd = new FeatureDataset(mapping.createSchema("Geometry")); // -- add the features and do the attribute mapping for (Iterator iterator = withoutIntersection.iterator(); iterator .hasNext();) { boolean errorInA = false; boolean errorInB = false; Geometry geom = (Geometry) iterator.next(); Point pt = geom.getInteriorPoint(); Feature f = new BasicFeature(fd.getFeatureSchema()); Feature featureA = null; Feature featureB = null; // -- query Layer A --- List candidatesA = treeA.query(pt.getEnvelopeInternal()); int foundCountA = 0; for (Iterator iterator2 = candidatesA.iterator(); iterator2.hasNext();){ Feature ftemp = (Feature) iterator2.next(); if (ftemp.getGeometry().contains(pt)) { foundCountA++; featureA = ftemp; } } if (foundCountA > 1) { if (context != null) { errorInA = true; String errorStrg = I18N.get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Found-more-than-one-source-feature-in-Layer"); context.getWorkbenchFrame().warnUser(errorStrg+ " " + GenericNames.LAYER_A); context.getWorkbenchFrame().getOutputFrame().createNewDocument(); context.getWorkbenchFrame().getOutputFrame().addText("IntersectPolygonLayersPlugIn: " + errorStrg+ ": " + GenericNames.LAYER_A + ". Reason: The Layer contains probably objects that overlay each other. Will set polygon values of item with FID: " + f.getID() + " to NaN. Use i)" + I18N.get("org.openjump.sigle.plugin.SpatialJoinPlugIn.Transfer-Attributes") + " or ii)" + I18N.get("org.openjump.core.ui.plugin.tools.JoinAttributesSpatiallyPlugIn.Join-Attributes-Spatially") + " functions to obtain atributes from " + GenericNames.LAYER_A); } } else if (foundCountA == 0) { if (context != null) { // context.getWorkbenchFrame().warnUser("no corresponding // feature in Layer A"); } } // -- query Layer B --- List candidatesB = treeB.query(pt.getEnvelopeInternal()); int foundCountB = 0; for (Iterator iterator2 = candidatesB.iterator(); iterator2.hasNext();){ Feature ftemp = (Feature) iterator2.next(); if (ftemp.getGeometry().contains(pt)) { foundCountB++; featureB = ftemp; } } if (foundCountB > 1) { if (context != null) { errorInB = true; String errorStrg = I18N.get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Found-more-than-one-source-feature-in-Layer"); context.getWorkbenchFrame().warnUser(errorStrg+ " " + GenericNames.LAYER_B); context.getWorkbenchFrame().getOutputFrame().createNewDocument(); context.getWorkbenchFrame().getOutputFrame().addText("IntersectPolygonLayersPlugIn: " + errorStrg+ ": " + GenericNames.LAYER_B + ". Reason: The Layer contains probably objects that overlay each other. Will set polygon values of item with FID: " + f.getID() + " to NaN. Use " + I18N.get("org.openjump.sigle.plugin.SpatialJoinPlugIn.Transfer-Attributes") + " or " + I18N.get("org.openjump.core.ui.plugin.tools.JoinAttributesSpatiallyPlugIn.Join-Attributes-Spatially") + " functions to obtain atributes from " + GenericNames.LAYER_B); } } else if (foundCountB == 0) { if (context != null) { // context.getWorkbenchFrame().warnUser("no corresponding // feature in Layer B"); } } if ((foundCountA > 0) || (foundCountB > 0)){ // -- before mapping check and set for error values if (errorInA){ featureA = resetFeatureValuesToNaN(featureA); } if (errorInB){ featureB = resetFeatureValuesToNaN(featureB); } // -- do mapping mapping.transferAttributes(featureA, featureB, f); // -- set Geometry f.setGeometry((Geometry) geom.clone()); fd.add(f); } // else{ // System.out.println("polygon without correspondent"); // } } // -- return fd; } /** * All values are set to NaN. * @param f */ public static Feature resetFeatureValuesToNaN(Feature f){ //-- work only on a copy so the original feature isn't changed Feature ftemp = f.clone(true); FeatureSchema fs = ftemp.getSchema(); for (int i =0; i < fs.getAttributeCount(); i++){ AttributeType type = fs.getAttributeType(i); if (!type.equals(AttributeType.GEOMETRY)){ if(type.equals(AttributeType.DOUBLE)){ ftemp.setAttribute(i, Double.NaN); } if(type.equals(AttributeType.INTEGER)){ ftemp.setAttribute(i, null); } if(type.equals(AttributeType.STRING)){ ftemp.setAttribute(i, "NaN"); } if(type.equals(AttributeType.OBJECT)){ ftemp.setAttribute(i, null); } if(type.equals(AttributeType.DATE)){ ftemp.setAttribute(i, null); } } } return ftemp; } /** * Merges/Intersects two polygon layers into one layer. It therefore * calculates all geometric intersections between the polygons. Afterwards * the attributes are transferred. The later step assumes that a new created * intersection polygon has at max only one correspondent polygon per layer. * Note: this approach using the IntersectGeometries() class results in * unwanted spikes during the polygon intersection. The method runIntersectionNew() * tries to avoid this by not using the polygon intersection but creating polygons * from all lines derived from the polygons and removing some of those. * * @param fcA * @param fcB * @param accuraccy this parameter has currently no effect (now the default fixed precision model is used) * @param transferAttributes should attributes be transfered? * @param monitor can be null * @param context can be null * @return a FeatureCollection that contains the result of the intersection * (i.e. the new created features) */ private FeatureCollection runIntersectionOld(FeatureCollection fcA, FeatureCollection fcB, double accurracy, boolean transferAttributes, TaskMonitor monitor, PlugInContext context) { FeatureCollection fd = null; // -- put all geoms in one list and calculate their intersections // i.e. iterate until pure ArrayList<Geometry> geomsToCheck = new ArrayList<Geometry>(); for (Iterator iterator = fcA.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); geomsToCheck.add(f.getGeometry()); } for (Iterator iterator = fcB.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); geomsToCheck.add(f.getGeometry()); } // -- sort out the different geometry types for (Iterator iterator = geomsToCheck.iterator(); iterator.hasNext();) { Geometry geom = (Geometry) iterator.next(); if ((geom instanceof Polygon) || (geom instanceof MultiPolygon)) { // everything is fine } else { // -- if (context != null) { context .getWorkbenchFrame() .warnUser( I18N .get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Geometry-no-Polygon-or-Multi-Polygon")); } // -- return null; } } ArrayList<Geometry> withoutIntersection = IntersectGeometries .intersectPolygons(geomsToCheck, accurracy, monitor, context); if (transferAttributes == false) { fd = FeatureDatasetFactory.createFromGeometry(withoutIntersection); return fd; } if (monitor != null) { monitor.report(this.sTRANSFER); } // ===================== Transfer Attributes ================== // note: if we transfer attributes we need a unique set of // FeatureSchemas // Hence a feature that has a corespondant in only one layer should // still // have all attributes from both source layers. // ============================================================ // -- put all original objects in trees for faster query SpatialIndex treeA = new STRtree(); for (Iterator iterator = fcA.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); treeA.insert(f.getGeometry().getEnvelopeInternal(), f); } SpatialIndex treeB = new STRtree(); for (Iterator iterator = fcB.iterator(); iterator.hasNext();) { Feature f = (Feature) iterator.next(); treeB.insert(f.getGeometry().getEnvelopeInternal(), f); } // -- get all intersecting features (usually there should be only one // corresponding feature per layer) // to avoid problems with spatial predicates we do the query for an // internal point of the result polygons // and apply an point in polygon test AttributeMapping mapping = new AttributeMapping(fcA.getFeatureSchema(), fcB.getFeatureSchema()); // -- create the empty dataset with the final FeatureSchema fd = new FeatureDataset(mapping.createSchema("Geometry")); // -- add the features and do the attribute mapping for (Iterator iterator = withoutIntersection.iterator(); iterator .hasNext();) { Geometry geom = (Geometry) iterator.next(); Point pt = geom.getInteriorPoint(); Feature f = new BasicFeature(fd.getFeatureSchema()); Feature featureA = null; Feature featureB = null; // -- query Layer A --- List candidatesA = treeA.query(pt.getEnvelopeInternal()); int foundCountA = 0; for (Iterator iterator2 = candidatesA.iterator(); iterator2 .hasNext();) { Feature ftemp = (Feature) iterator2.next(); if (ftemp.getGeometry().contains(pt)) { foundCountA++; featureA = ftemp; } } if (foundCountA > 1) { if (context != null) { context .getWorkbenchFrame() .warnUser( I18N .get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Found-more-than-one-source-feature-in-Layer") + " " + GenericNames.LAYER_A); } } else if (foundCountA == 0) { if (context != null) { // context.getWorkbenchFrame().warnUser("no corresponding // feature in Layer A"); } } // -- query Layer B --- List candidatesB = treeB.query(pt.getEnvelopeInternal()); int foundCountB = 0; for (Iterator iterator2 = candidatesB.iterator(); iterator2 .hasNext();) { Feature ftemp = (Feature) iterator2.next(); if (ftemp.getGeometry().contains(pt)) { foundCountB++; featureB = ftemp; } } if (foundCountB > 1) { if (context != null) { context .getWorkbenchFrame() .warnUser( I18N .get("org.openjump.plugin.tools.IntersectPolygonLayersPlugIn.Found-more-than-one-source-feature-in-Layer") + " " + GenericNames.LAYER_B); } } else if (foundCountB == 0) { if (context != null) { // context.getWorkbenchFrame().warnUser("no corresponding // feature in Layer B"); } } // -- do mapping mapping.transferAttributes(featureA, featureB, f); // -- set Geometry f.setGeometry((Geometry) geom.clone()); fd.add(f); } // -- return fd; } private void setDialogValues(MultiInputDialog dialog, PlugInContext context) { dialog.setSideBarDescription(sDescription); // Set initial layer values to the first and second layers in the layer // list. // In #initialize we've already checked that the number of layers >= 2. // [Jon Aquino] dialog.addLayerComboBox(LAYER1, layer1, context.getLayerManager()); dialog.addLayerComboBox(LAYER2, layer2, context.getLayerManager()); //dialog.addDoubleField(sAccurracy, this.accurracy, 7); //dialog.addCheckBox(sTRANSFER, this.transferAtt); } private void getDialogValues(MultiInputDialog dialog) { layer1 = dialog.getLayer(LAYER1); layer2 = dialog.getLayer(LAYER2); //this.transferAtt = dialog.getBoolean(sTRANSFER); //this.accurracy = dialog.getDouble(sAccurracy); } }