/*
* 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) Stefan Steiniger.
*
* 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:
* Stefan Steiniger
* perriger@gmx.de
*/
/*****************************************************
* created: 06.June.2010
* last modified:
*
* description:
* Merges selected polygons with neighboring polygons, either with the one that is
* largest of all neighbors, or the one wiht which it has the longest common boundary.
* Note, the function may return multi-polygons if the polygons to merge have only
* one point in common.
*****************************************************/
package org.openjump.core.ui.plugin.tools.analysis.onelayer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.swing.JRadioButton;
import org.openjump.core.apitools.FeatureCollectionTools;
import org.openjump.core.apitools.objecttyperoles.PirolFeatureCollection;
import org.openjump.core.graph.polygongraph.PolygonGraph;
import org.openjump.core.graph.polygongraph.PolygonGraphEdge;
import org.openjump.core.graph.polygongraph.PolygonGraphNode;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.index.quadtree.Quadtree;
import com.vividsolutions.jts.operation.union.UnaryUnionOp;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.feature.AttributeType;
import com.vividsolutions.jump.feature.Feature;
import com.vividsolutions.jump.feature.FeatureCollection;
import com.vividsolutions.jump.feature.FeatureDataset;
import com.vividsolutions.jump.feature.FeatureSchema;
import com.vividsolutions.jump.task.TaskMonitor;
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.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.MenuNames;
import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
import com.vividsolutions.jump.workbench.ui.plugin.FeatureInstaller;
/**
* @author sstein
*
**/
public class MergeSelectedPolygonsWithNeighbourPlugIn extends ThreadedBasePlugIn{
private String sMergeTwoPolys = "Merge Selected Polygons with Neighbours";
private String sFeaturesFromDifferentLayer = "Error: Features from different layers!";
private String sSidebar = "Merges selected polygons with neighboring polygons, either with the one that is largest of " +
"all neighbors, or the one with which it has " +
"the longest common boundary. Note, the function may return multi-polygons if " +
"the polygons to merge have only one point in common.";
boolean useArea = true;
boolean useBorder = false;
String sUseArea = "merge with neighbor that has the largest area";
String sUseBoder = "merge with neighbor with the longest common edge";
String sChoseMergeMethod = "Please chose the merge method:";
String sMerged ="merged";
String sSearchingForMergeCandidates = "Searching for merge candidates...";
String sMergingPolygons = "Merging polygons...";
final static String sMERGEMETHOD = "MERGE METHOD";
private MultiInputDialog dialog;
private JRadioButton buttonSelectMergeTypeUseArea = null;
private JRadioButton buttonSelectMergeTypeUseBorder = null;
public void initialize(PlugInContext context) throws Exception {
sMergeTwoPolys = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.Merge-Selected-Polygons-with-Neighbours");
sFeaturesFromDifferentLayer = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.features-from-different-layers");
sSidebar = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.description");
sUseArea = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.merge-with-neighbor-that-has-the-largest-area");
sUseBoder = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.merge-with-neighbor-with-the-longest-common-edge");
sChoseMergeMethod = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.Please-chose-the-merge-method");
sMerged = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.merged");
sSearchingForMergeCandidates = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.Searching-for-merge-candidates");
sMergingPolygons = I18N.get("org.openjump.core.ui.plugin.tools.MergeSelectedPolygonsWithNeighbourPlugIn.Merging-polygons");
FeatureInstaller featureInstaller = new FeatureInstaller(context.getWorkbenchContext());
featureInstaller.addMainMenuItem(
this, //exe
new String[] {MenuNames.TOOLS, MenuNames.TOOLS_EDIT_GEOMETRY}, //menu path
this.getName() + "...", //name methode .getName recieved by AbstractPlugIn
false, //checkbox
null, //icon
createEnableCheck(context.getWorkbenchContext())); //enable check
}
public String getName() {
return sMergeTwoPolys;
}
public static MultiEnableCheck createEnableCheck(WorkbenchContext workbenchContext) {
EnableCheckFactory checkFactory = new EnableCheckFactory(workbenchContext);
return new MultiEnableCheck()
.add(checkFactory.createWindowWithLayerNamePanelMustBeActiveCheck())
.add(checkFactory.createAtLeastNItemsMustBeSelectedCheck(1));
}
public boolean execute(PlugInContext context) throws Exception{
initDialog(context);
dialog.setVisible(true);
if (!dialog.wasOKPressed()) {
return false;
}
else{
this.getDialogValues(dialog);
}
return true;
}
private void initDialog(PlugInContext context) {
dialog = new MultiInputDialog(context.getWorkbenchFrame(), this.getName(), true);
dialog.setSideBarDescription(sSidebar);
final String METHODGROUP = sMERGEMETHOD;
dialog.addLabel(sChoseMergeMethod);
buttonSelectMergeTypeUseArea = dialog.addRadioButton(sUseArea, METHODGROUP, this.useArea, sUseArea);
buttonSelectMergeTypeUseBorder = dialog.addRadioButton(sUseBoder, METHODGROUP, this.useBorder, sUseBoder);
GUIUtil.centreOnWindow(dialog);
}
private void getDialogValues(MultiInputDialog dialog) {
this.useArea = dialog.getBoolean(this.sUseArea);
this.useBorder = dialog.getBoolean(this.sUseBoder);
}
public void run(TaskMonitor monitor, PlugInContext context) throws Exception{
monitor.allowCancellationRequests();
// get the selected features
Collection<Feature> features = context.getWorkbenchContext().getLayerViewPanel().getSelectionManager().getFeaturesWithSelectedItems();
// get the layers
Collection<Layer> layers = context.getWorkbenchContext().getLayerViewPanel().getSelectionManager().getLayersWithSelectedItems();
if(layers.size() > 1){
context.getWorkbenchFrame().warnUser(sFeaturesFromDifferentLayer);
return;
}
else{
// parse the features and mark them as selected in the geometry
for (Iterator iterator = features.iterator(); iterator.hasNext();) {
Feature ftemp = (Feature) iterator.next();
ftemp.getGeometry().setUserData(new Boolean(true));
}
// now modify the layer and add two attributes: one for the feature to be merged
// and one for the feature to be merging candidate
Iterator iter = layers.iterator();
Layer firstLayer = (Layer)iter.next();
//make a copy of the FC first
FeatureCollection input = FeatureCollectionTools.cloneFeatureCollection(firstLayer.getFeatureCollectionWrapper());
FeatureSchema originalFeatureSchema = input.getFeatureSchema();
PirolFeatureCollection fcN = FeatureCollectionTools.addAttributeToFeatureCollection(input, "mergeid", AttributeType.INTEGER, new Integer(0));
fcN = FeatureCollectionTools.addAttributeToFeatureCollection(fcN, "selected", AttributeType.INTEGER, new Integer(0));
fcN = FeatureCollectionTools.addAttributeToFeatureCollection(fcN, "toMergeWithFID", AttributeType.INTEGER, new Integer(0));
FeatureDataset fcAssigned = new FeatureDataset(fcN.getFeatureSchema());
// put all features in a tree for faster search and set the mergeID attributes, as well as mark the which should be merged
int i = 0; int fcount = 88888888;
Quadtree qTree = new Quadtree();
ArrayList<Feature> selectedF = new ArrayList<Feature>();
for (Iterator iterator = fcN.iterator(); iterator.hasNext();) {
Feature f = (Feature) iterator.next();
fcount++;
f.setAttribute("mergeid", new Integer(fcount));
Object userdata = f.getGeometry().getUserData();
if (userdata != null){
i++;
Boolean isSelected = (Boolean)userdata;
if(isSelected){
//-- set the attribute
f.setAttribute("selected", new Integer(i));
}
selectedF.add(f);
}
// I also add the features that are selected
// because some selected features may be the only ones touching
qTree.insert(f.getGeometry().getEnvelopeInternal(), f);
}
//-- reset the userdata entry otherwise it will be stored for eternity
for (Iterator iterator = features.iterator(); iterator.hasNext();) {
Feature ftemp = (Feature) iterator.next();
ftemp.getGeometry().setUserData(null);
}
// find the polygons to merge with
int sizeS = selectedF.size(); int counterS = 0;
for (Iterator iterator = selectedF.iterator(); iterator.hasNext();) {
monitor.report(counterS, sizeS, sSearchingForMergeCandidates);
if(monitor.isCancelRequested()){
return;
}
Feature ftemp = (Feature) iterator.next();
Geometry gtemp = ftemp.getGeometry();
Collection candidates = qTree.query(gtemp.getEnvelopeInternal());
if(this.useArea){
/*********************************************************
* we are interested in the polygon with the biggest area
* hence a normal intersection test should be ok
********************************************************/
double area = -1.0;
Feature f2merge = null;
for (Iterator iterator2 = candidates.iterator(); iterator2.hasNext();) {
Feature ft = (Feature) iterator2.next();
// Fix bug 3060942 don't try to union f with itself !
if (ft.getID() == ftemp.getID()) continue;
if(gtemp.intersects(ft.getGeometry())){
double tarea = ft.getGeometry().getArea();
if(tarea > area){
f2merge = ft;
area = tarea;
}
}
}
//-- now set the with which Feature it should be merged, but check first if
// it has already a merge candidate
int val = (Integer)f2merge.getAttribute("toMergeWithFID");
if(val != 0){//it has already a merge candidate, so set value to be negative
int selectedVal = (Integer)ftemp.getAttribute("selected");
if(selectedVal >0){
//if it is one of the selected ones
//then we leave the original mergeid value in the field
}
else{
//otherwise we set
f2merge.setAttribute("toMergeWithFID", new Integer(-1));
}
}
else{//set the polygon to merge with
f2merge.setAttribute("toMergeWithFID", (Integer)ftemp.getAttribute("selected"));
}
ftemp.setAttribute("toMergeWithFID", (Integer)f2merge.getAttribute("mergeid"));
}//end (useArea)
else{
/*******************************************************************
* we are interested in the polygon with the longest common boundary
* hence we use the polygon graph
******************************************************************/
PolygonGraph pg = new PolygonGraph(candidates, null);
PolygonGraphEdge longestEdge = null; double maxlength = -1;
for (Iterator iterator2 = pg.nodes.iterator(); iterator2.hasNext();) {
PolygonGraphNode node = (PolygonGraphNode) iterator2.next();
//-- get our previously selected polygon
if(node.realWorldObject.getID() == ftemp.getID()){
//-- get the the poly with the longest common edge
for (Iterator iterator3 = node.edges.iterator(); iterator3.hasNext();) {
PolygonGraphEdge tedge = (PolygonGraphEdge) iterator3.next();
//we may have several lines for the case that another polygon is in between...
ArrayList<Geometry> lines = tedge.getBoundaries();
double length = 0;
for (Iterator iterator4 = lines.iterator(); iterator4.hasNext();) {
Geometry line = (Geometry) iterator4.next();
length = length + line.getLength();
}
if(length > maxlength){
maxlength = length;
longestEdge = tedge;
}
}
}//end if selected polygon
}
// get the feature with the longest shared edge
Feature fToMerge = null;
if(longestEdge.node1.realWorldObject.getID() == ftemp.getID()){
fToMerge = longestEdge.node2.realWorldObject;
}
else{
fToMerge = longestEdge.node1.realWorldObject;
}
//-- now set with which Feature it should be merged, but check first if
// it has already a merge candidate
int val = (Integer)fToMerge.getAttribute("toMergeWithFID");
if(val != 0){//it has already a merge candidate, so set value to be negative
int selectedVal = (Integer)ftemp.getAttribute("selected");
if(selectedVal >0){
//if it is one of the selected ones
//then we leave the original mergeid value in the field
}
else{
//otherwise we set
fToMerge.setAttribute("toMergeWithFID", new Integer(-1));
}
}
else{//set the polygon to merge with
fToMerge.setAttribute("toMergeWithFID", (Integer)ftemp.getAttribute("selected"));
}
ftemp.setAttribute("toMergeWithFID", (Integer)fToMerge.getAttribute("mergeid"));
}
counterS++;
}// end loop over all selected features to find the ones to merge
//-- so, we figured who has to be merged with whom - but we also should be able
// to identify those ones polygons that are to be merged with a polygon that
// needs to be merged too - here some output first
List allfeat = qTree.queryAll();
fcAssigned.addAll(allfeat);
//-- un-comment line below for debugging
//context.addLayer(StandardCategoryNames.RESULT, firstLayer.getName() + "_featuresWithAssigments", fcAssigned);
//-- sorting things out, and do a copy so we do not mess up things
FeatureDataset resultFList = new FeatureDataset(fcAssigned.getFeatureSchema());
FeatureDataset selectedFList = new FeatureDataset(fcAssigned.getFeatureSchema());
FeatureDataset mergeFList = new FeatureDataset(fcAssigned.getFeatureSchema());
for (Iterator iterator = allfeat.iterator(); iterator.hasNext();) {
Feature fti = (Feature) iterator.next();
Feature ft = fti.clone(true);
int selectedVal = (Integer)ft.getAttribute("selected");
int toMergeWithVal = (Integer)ft.getAttribute("toMergeWithFID");
if((selectedVal == 0) && (toMergeWithVal == 0)){
resultFList.add(ft);
}
else if(selectedVal > 0){
selectedFList.add(ft);
//add them also to the merge list (since the selected features may be a merge target too)
mergeFList.add(ft);
}
else{
mergeFList.add(ft);
}
}
//-- do the merge
int size = selectedFList.size(); int counter = 0;
for (Iterator iterator = selectedFList.iterator(); iterator.hasNext();) {
monitor.report(counter, size, sMergingPolygons);
if(monitor.isCancelRequested()){
return;
}
Feature ftemp = (Feature) iterator.next();
int toMergeWithVal = (Integer)ftemp.getAttribute("toMergeWithFID");
Feature mpoly = getPolyToMerge(toMergeWithVal, mergeFList);
if (mpoly != null){
//Do union with targetPoly and
Collection<Geometry> polygons = new ArrayList<Geometry>();
polygons.add(mpoly.getGeometry());
polygons.add(ftemp.getGeometry());
Geometry gp = UnaryUnionOp.union(polygons);
mpoly.setGeometry(gp);
// remove the selected poly from the mergeList
// there should be no need to remove and add the new
// poly, since we worked with references only
mergeFList = removeFromList(mergeFList, ftemp);
}
else{
//just return this without any merge
resultFList.add(ftemp);
mergeFList = removeFromList(mergeFList, ftemp);
}
counter++;
}
//add the remaining merged polygons
resultFList.addAll(mergeFList.getFeatures());
{// remove the additional attributes
FeatureDataset removedAttrFc = new FeatureDataset(originalFeatureSchema);
for (Iterator iterator = resultFList.iterator(); iterator.hasNext();) {
Feature ftemp = (Feature) iterator.next();
Feature fnew = FeatureCollectionTools.copyFeatureAndSetFeatureSchema(ftemp, originalFeatureSchema);
removedAttrFc.add(fnew);
}
resultFList = removedAttrFc;
}
context.addLayer(StandardCategoryNames.RESULT, firstLayer.getName() + "_" + sMerged, resultFList);
}//end else - layer size
//context.getWorkbenchContext().getLayerViewPanel().getSelectionManager().clear();
}
private Feature getPolyToMerge(int featureID, FeatureCollection mergeFList) {
Feature foundFeature = null;
for (Iterator iterator = mergeFList.iterator(); iterator.hasNext();) {
Feature ftemp = (Feature) iterator.next();
int val = (Integer)ftemp.getAttribute("mergeid");
if(val == featureID){
int selectedVal = (Integer)ftemp.getAttribute("selected");
if(selectedVal == 0){
foundFeature = ftemp;
}
else{// selecteVaL != null, so this poly will be merged as well
// search for the feature this one should be merged with
int toMergeWithVal = (Integer)ftemp.getAttribute("toMergeWithFID");
foundFeature = getPolyToMerge(toMergeWithVal, mergeFList);
}
}
}
return foundFeature;
}
private FeatureDataset removeFromList(FeatureDataset mergeFList, Feature fToDelete) {
FeatureDataset fdnew = new FeatureDataset(mergeFList.getFeatureSchema());
int valToDelete = (Integer)fToDelete.getAttribute("mergeid");
for (Iterator iterator = mergeFList.iterator(); iterator.hasNext();) {
Feature ftemp = (Feature) iterator.next();
int valCur = (Integer)ftemp.getAttribute("mergeid");
if(valToDelete == valCur){
// don't do anything
}
else{
fdnew.add(ftemp);
}
}
return fdnew;
}
}