/*
* 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 org.openjump.core.ui.plugin.tools;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.operation.linemerge.LineMerger;
import com.vividsolutions.jts.operation.union.UnaryUnionOp;
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.FeatureSchema;
import com.vividsolutions.jump.task.TaskMonitor;
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.MultiInputDialog;
import com.vividsolutions.jump.workbench.ui.images.IconLoader;
/**
* UnionByAttribute plugin is used to union features having the same attribute
* value together.
* <br>
* There are three options available :
* <ul>
* <li>Features with empty values can be discarded</li>
* <li>LineStrings can be merged (union do not merge by default)</li>
* <li>Values of numeric attributes can be added up</li>
* </ul>
*/
public class UnionByAttributePlugIn extends AbstractPlugIn implements ThreadedPlugIn {
private final static String LAYER = I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.layer");
private final static String ATTRIBUTE = I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.attribute");
private final static String IGNORE_EMPTY = I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.ignore-empty");
private final static String MERGE_LINES = I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.merge-lines");
private final static String TOTAL_NUMERIC_FIELDS = I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.total-numeric-fields");
private MultiInputDialog dialog;
private GeometryFactory factory;
public UnionByAttributePlugIn() {}
@Override
public String getName() {
return I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.union-by-attribute") + "...";
}
@Override
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)));
}
@Override
public boolean execute(PlugInContext context) throws Exception {
initDialog(context);
dialog.setVisible(true);
if (!dialog.wasOKPressed()) {
return false;
}
return true;
}
private void initDialog(PlugInContext context) {
dialog = new MultiInputDialog(context.getWorkbenchFrame(),
I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.union-by-attribute"), true);
dialog.setSideBarImage(IconLoader.icon("UnionByAttribute.gif"));
dialog.setSideBarDescription(
I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.creates-a-new-layer-containing-the-unions-of-features-having-a-common-attribute-value"));
dialog.addLayerComboBox(LAYER, context.getCandidateLayer(0), context.getLayerManager());
List list = getFieldsFromLayerWithoutGeometry(context.getCandidateLayer(0));
Object val = list.size()>0?list.iterator().next():null;
final JComboBox jcb_attribute = dialog.addComboBox(ATTRIBUTE, val, list,
I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.select-attribute"));
if (list.size() == 0) jcb_attribute.setEnabled(false);
final JCheckBox jcb_ignore_empty = dialog.addCheckBox(IGNORE_EMPTY, true);
if (list.size() == 0) jcb_ignore_empty.setEnabled(false);
dialog.addSeparator();
final JCheckBox jcb_merge_lines = dialog.addCheckBox(MERGE_LINES, true);
final JCheckBox jcb_total_numeric_fields = dialog.addCheckBox(TOTAL_NUMERIC_FIELDS, true);
dialog.getComboBox(LAYER).addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
List list = getFieldsFromLayerWithoutGeometry();
if (list.size() == 0) {
jcb_attribute.setModel(new DefaultComboBoxModel(new String[0]));
jcb_attribute.setEnabled(false);
jcb_ignore_empty.setEnabled(false);
}
jcb_attribute.setModel(new DefaultComboBoxModel(list.toArray(new String[0])));
}
});
GUIUtil.centreOnWindow(dialog);
}
public void run(TaskMonitor monitor, PlugInContext context) throws Exception {
monitor.allowCancellationRequests();
// Get options from the dialog
Layer layer = dialog.getLayer(LAYER);
FeatureCollection fc = layer.getFeatureCollectionWrapper();
FeatureSchema schema = fc.getFeatureSchema();
String att = dialog.getText(ATTRIBUTE);
boolean ignore_empty = dialog.getBoolean(IGNORE_EMPTY);
boolean merge_lines = dialog.getBoolean(MERGE_LINES);
boolean total_numeric_fields = dialog.getBoolean(TOTAL_NUMERIC_FIELDS);
if (fc.getFeatures().size() > 0 &&
((Feature)fc.getFeatures().get(0)).getGeometry() != null) {
factory = ((Feature)fc.getFeatures().get(0)).getGeometry().getFactory();
context.getOutputFrame().createNewDocument();
context.getOutputFrame().append("<h1>Union by attribute</h1>");
context.getOutputFrame().addText("Processed layer : " + layer.getName());
context.getOutputFrame().addText("Aggregator attribute : " + att);
context.getOutputFrame().addText("Empty values : " + (ignore_empty?"ignored":"processed"));
context.getOutputFrame().addText("LineStrings : " + (merge_lines?"merged":"unioned but not merged"));
context.getOutputFrame().addText("Numeric fields : " + (total_numeric_fields?"added up":"discarded"));
context.getOutputFrame().append("<h3>Warnings :</h3>");
}
else {
context.getWorkbenchFrame().warnUser(I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.no-data-to-be-unioned"));
return;
}
// Create the schema for the output dataset
FeatureSchema newSchema = new FeatureSchema();
//fix bug on 2007-09-17 : must take the geometry name of the source layer
//newSchema.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
newSchema.addAttribute(schema.getAttributeName(schema.getGeometryIndex()), AttributeType.GEOMETRY);
newSchema.addAttribute(att, schema.getAttributeType(att));
// if total_numeric_fields is true, add numeric fields to the result layer
if (total_numeric_fields) {
for (int i = 0, max = schema.getAttributeCount() ; i < max ; i++) {
if (schema.getAttributeType(i) == AttributeType.INTEGER ||
schema.getAttributeType(i) == AttributeType.DOUBLE)
newSchema.addAttribute(schema.getAttributeName(i),
schema.getAttributeType(i));
}
}
// Order features by attribute value in a map
Map map = new HashMap();
monitor.report(I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.union-by-attribute"));
for (Iterator i = fc.iterator() ; i.hasNext() ; ) {
Feature f = (Feature)i.next();
Object key = f.getAttribute(att);
if (ignore_empty && (key == null || key.toString().trim().length() == 0)) {
continue;
}
else if (!map.containsKey(key)) {
FeatureCollection fd = new FeatureDataset(fc.getFeatureSchema());
fd.add(f);
map.put(key, fd);
}
else {
((FeatureCollection)map.get(key)).add(f);
}
}
// Computing the result
int count = 1;
FeatureCollection resultfc = new FeatureDataset(newSchema);
for (Iterator i = map.keySet().iterator() ; i.hasNext() ; ) {
monitor.report(I18N.get("ui.plugin.analysis.UnionByAttributePlugIn.union-by-attribute") + " (" + count++ + "/" + map.size() + ")");
Object key = i.next();
FeatureCollection fca = (FeatureCollection)map.get(key);
if (fca.size() > 0) {
Feature feature = union(context, monitor, fca, merge_lines, total_numeric_fields);
feature.setAttribute(att, key);
Feature newFeature = new BasicFeature(newSchema);
// Copy feature attributes in newFeature
for (int j = 0, max = newSchema.getAttributeCount() ; j < max ; j++) {
newFeature.setAttribute(j, feature.getAttribute(newSchema.getAttributeName(j)));
}
resultfc.add(newFeature);
}
}
context.getLayerManager().addCategory(StandardCategoryNames.RESULT);
context.addLayer(StandardCategoryNames.RESULT, layer.getName() + "-" + att + " (union)", resultfc);
context.getOutputFrame().append("<h3>End of process</h3>");
}
/**
* New method for union. Uses new UnaryUnionOp which is much more
* efficient for large datasets.
*/
private Feature union(PlugInContext context, TaskMonitor monitor,
FeatureCollection fc, boolean merge_lines, boolean total) {
Collection points = new ArrayList();
Collection lineStrings = new ArrayList();
Collection polygons = new ArrayList();
Collection geoms = new ArrayList();
for (Iterator it = fc.iterator() ; it.hasNext() ; ) {
Feature f = (Feature) it.next();
Geometry g = f.getGeometry();
if (!g.isValid()) {
context.getWorkbenchFrame().warnUser("Invalid geometries have been excluded !");
context.getOutputFrame().addText("Feature " + f.getID() + " has invalid geometry : it has been excluded from union");
continue;
}
else if (g.isEmpty()) continue;
else if (g instanceof Point) points.add(g);
else if (g instanceof LineString) lineStrings.add(g);
else if (g instanceof Polygon) polygons.add(g);
else if (g instanceof GeometryCollection) {
Geometry gc = (GeometryCollection)g;
for (int j = 0 ; j < gc.getNumGeometries() ; j++) {
Geometry gp = gc.getGeometryN(j);
if (gp instanceof Point) points.add(gp);
else if (gp instanceof LineString) lineStrings.add(gp);
else if (gp instanceof Polygon) polygons.add(gp);
else;
}
}
}
Geometry gp;
if (points.size()>0 && null != (gp = UnaryUnionOp.union(points))) {
geoms.add(gp);
}
if (merge_lines && lineStrings.size()>0) {
LineMerger merger = new LineMerger();
merger.add(lineStrings);
geoms.addAll(merger.getMergedLineStrings());
}
else if (lineStrings.size()>0) {
gp = UnaryUnionOp.union(lineStrings);
if (gp != null) geoms.add(gp);
}
if (polygons.size()>0 && null != (gp = UnaryUnionOp.union(polygons))) {
geoms.add(gp);
}
FeatureSchema schema = fc.getFeatureSchema();
Feature feature = new BasicFeature(schema);
if (geoms.size()==0) {
feature.setGeometry(factory.createGeometryCollection(new Geometry[]{}));
}
else {
feature.setGeometry(UnaryUnionOp.union(geoms));
}
if (total) {
feature = totalNumericValues(fc, feature);
}
return feature;
}
// Set the sum of fc collection numeric attribute values into feature numeric attributes.
private Feature totalNumericValues(FeatureCollection fc, Feature feature) {
FeatureSchema schema = fc.getFeatureSchema();
for (Iterator it = fc.iterator() ; it.hasNext() ; ) {
Feature f = (Feature)it.next();
for (int i = 0, max = schema.getAttributeCount() ; i < max ; i++) {
if (schema.getAttributeType(i) == AttributeType.INTEGER) {
Object val = feature.getAttribute(i);
int val1 = (val == null)? 0 : ((Integer)val).intValue();
val = f.getAttribute(i);
val1 = (val == null)? val1 : val1 + ((Integer)val).intValue();
feature.setAttribute(i, new Integer(val1));
}
else if (schema.getAttributeType(i) == AttributeType.DOUBLE) {
Object val = feature.getAttribute(i);
double val1 = (val == null)? 0 : ((Double)val).doubleValue();
val = f.getAttribute(i);
val1 = (val == null)? val1 : val1 + ((Double)val).doubleValue();
feature.setAttribute(i, new Double(val1));
}
}
}
return feature;
}
private List getFieldsFromLayerWithoutGeometry(Layer lyr) {
List fields = new ArrayList();
FeatureSchema schema = lyr.getFeatureCollectionWrapper().getFeatureSchema();
for (int i = 0 ; i < schema.getAttributeCount() ; i++) {
if (schema.getAttributeType(i) != AttributeType.GEOMETRY) {
fields.add(schema.getAttributeName(i));
}
}
return fields;
}
private List getFieldsFromLayerWithoutGeometry() {
return getFieldsFromLayerWithoutGeometry(dialog.getLayer(LAYER));
}
}