/* * 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: 05.06.2006 * last modified: * * @author sstein * * description: * created voronoi regions/thiessen polygons from a set of points. * The Delauney algorithm used for the triangulation is by L. Paul Chew and * his free demonstration java-applet.<p> * @see <a href="http://www.cs.cornell.edu/Info/People/chew/Delaunay.html">chew</a> * *****************************************************/ package org.openjump.core.ui.plugin.tools; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.swing.JCheckBox; import javax.swing.JComboBox; import org.openjump.core.graph.delauneySimplexInsert.DTriangulationForJTS; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.index.quadtree.Quadtree; import com.vividsolutions.jump.I18N; 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.tools.OverlayEngine; 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.MultiInputDialog; import com.vividsolutions.jump.workbench.ui.plugin.FeatureInstaller; /** * Creates voronoi regions from a set of points. * The Delauney algorithm used for the triangulation is by L. Paul Chew and * his free demonstration java-applet.<p> * http://www.cs.cornell.edu/Info/People/chew/Delaunay.html * * @author sstein * **/ public class CreateThiessenPolygonsPlugIn extends AbstractPlugIn implements ThreadedPlugIn{ private String sName = "Create Thiessen Polygons"; private String CLAYER = "select point layer"; private String BLAYER = "background layer to delineate the thiessen polygon size"; private String sUseBGD = "use background layer"; private String sideBarText = "Creates a Delaunay triangulation and returns the Voronoi regions."; private String msgCreateDG = "create triangulation"; private String msgCreatePolys = "create polygons from voronoi edges"; private String msgAddAttributesPolys = "add attributes from points"; private String msgMultiplePointsInPoly = "Error: found multiple points in polygon"; //-- private String msgNoPoint = "no point geometry"; private Layer itemlayer = null; private Layer bckgrdlayer = null; private PlugInContext pcontext = null; private boolean useBackground = false; //-- private MultiInputDialog dialog; private JCheckBox checkbox; private JComboBox layerComboBoxBackground; public void initialize(PlugInContext context) throws Exception { this.CLAYER = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.select-point-layer"); this.BLAYER = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.background-layer-to-estimate-the-thiessen-polygon-size"); this.sUseBGD = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.use-background-layer"); this.sName = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.Create-Thiessen-Polygons"); this.sideBarText = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.Creates-a-Delaunay-triangulation-and-returns-the-Voronoi-regions"); this.msgCreateDG = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.create-triangulation"); this.msgCreatePolys = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.create-polygons-from-voronoi-edges"); this.msgNoPoint =I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.no-point-geometry"); this.msgAddAttributesPolys = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.add-attributes-from-points"); this.msgMultiplePointsInPoly = I18N.get("org.openjump.core.ui.plugin.tools.CreateThiessenPolygonsPlugIn.Error-found-multiple-points-in-polygon"); this.pcontext = context; FeatureInstaller featureInstaller = new FeatureInstaller(context.getWorkbenchContext()); featureInstaller.addMainMenuItem( this, //exe new String[] {MenuNames.TOOLS, MenuNames.TOOLS_GENERATE}, //menu path this.sName + "..." /*+ "{pos:2}"*/, //name methode .getName received 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.createAtLeastNLayersMustExistCheck(1)); } public boolean execute(PlugInContext context) throws Exception{ this.reportNothingToUndoYet(context); dialog = new MultiInputDialog( context.getWorkbenchFrame(), this.sName, true); setDialogValues(dialog, context); GUIUtil.centreOnWindow(dialog); dialog.setVisible(true); if (! dialog.wasOKPressed()) { return false; } getDialogValues(dialog); return true; } private void setDialogValues(MultiInputDialog dialog, PlugInContext context) { dialog.setSideBarDescription(this.sideBarText); JComboBox addLayerComboBoxBuild = dialog.addLayerComboBox(this.CLAYER, context.getCandidateLayer(0), null, context.getLayerManager()); checkbox = dialog.addCheckBox(this.sUseBGD, this.useBackground); checkbox.addItemListener(new MethodItemListener()); //-- add Background-Layer DropDown .. and enable or disable dependent on checkbox values layerComboBoxBackground = dialog.addLayerComboBox(this.BLAYER, context.getCandidateLayer(0), null, context.getLayerManager()); layerComboBoxBackground.setEnabled(this.useBackground); } private void getDialogValues(MultiInputDialog dialog) { this.itemlayer = dialog.getLayer(this.CLAYER); this.bckgrdlayer = dialog.getLayer(this.BLAYER); this.useBackground = dialog.getBoolean(this.sUseBGD); } private void updateUIForMethod(){ //-- if use of background, LAYER selection needs to work boolean val=checkbox.isSelected(); layerComboBoxBackground.setEnabled(val); dialog.validate(); } public void run(TaskMonitor monitor, PlugInContext context) throws Exception{ this.createGraph(context, monitor); System.gc(); } private boolean createGraph(PlugInContext context, TaskMonitor monitor) throws Exception{ System.gc(); //flush garbage collector // -------------------------- //-- get selected items final Collection features = this.itemlayer.getFeatureCollectionWrapper().getFeatures(); //-- get objects from background layer (assuming there is only one List bkg = this.bckgrdlayer.getFeatureCollectionWrapper().getFeatures(); //-- ArrayList points = new ArrayList(); Quadtree qtree = new Quadtree(); //-- tree used later to transfer attributes for (Iterator iter = features.iterator(); iter.hasNext();) { Feature f = (Feature) iter.next(); Geometry g = f.getGeometry(); if(g instanceof Point){ if(this.useBackground){ // check if point is in one of the polygons of the background-layer boolean isInside = false; for (Iterator iterator = bkg.iterator(); iterator.hasNext();) { Feature ftemp = (Feature) iterator.next(); if (ftemp.getGeometry().covers(g)){ isInside = true; } } if(isInside){ points.add(f.getGeometry()); qtree.insert(g.getEnvelopeInternal(), f); } } else{ points.add(f.getGeometry()); qtree.insert(g.getEnvelopeInternal(), f); } } else{ context.getWorkbenchFrame().warnUser(this.msgNoPoint); } } if (points.size() > 0){ monitor.report(this.msgCreateDG); DTriangulationForJTS tri = null; if (this.useBackground == true){ FeatureCollection fc = this.bckgrdlayer.getFeatureCollectionWrapper(); Envelope env = fc.getEnvelope(); tri = new DTriangulationForJTS(points, env); } else{ tri = new DTriangulationForJTS(points); } //ArrayList nodes = tri.drawAllSites(); //FeatureCollection myCollA = FeatureDatasetFactory.createFromGeometry(nodes); //context.addLayer(StandardCategoryNames.WORKING, "sites", myCollA); //ArrayList nodes2 = tri.getInitialSimmplexAsJTSPoints(); //FeatureCollection myCollD = FeatureDatasetFactory.createFromGeometry(nodes2); //context.addLayer(StandardCategoryNames.WORKING, "cornerpoints", myCollD); //ArrayList edges = tri.drawAllVoronoi(); //FeatureCollection myCollB = FeatureDatasetFactory.createFromGeometry(edges); //context.addLayer(StandardCategoryNames.WORKING, "voronoi edges", myCollB); //ArrayList bbox = new ArrayList(); //bbox.add(tri.getThiessenBoundingBox()); //FeatureCollection myCollE = FeatureDatasetFactory.createFromGeometry(bbox); //context.addLayer(StandardCategoryNames.WORKING, "bbox", myCollE); monitor.report(this.msgCreatePolys); ArrayList polys = tri.getThiessenPolys(); //FeatureCollection myCollC = FeatureDatasetFactory.createFromGeometry(polys); monitor.report(this.msgAddAttributesPolys); //-- add attributes FeatureCollection myCollC = this.transferAttributes(this.itemlayer.getFeatureCollectionWrapper().getFeatureSchema(), qtree, polys); //-- clip to background polygon if (this.useBackground){ OverlayEngine oe = new OverlayEngine(); FeatureCollection a = myCollC; FeatureCollection b = this.bckgrdlayer.getFeatureCollectionWrapper(); AttributeMapping mapping = new AttributeMapping(a.getFeatureSchema(), new FeatureSchema()); FeatureCollection overlay = oe.overlay(a, b, mapping, monitor); myCollC = overlay; } //-- context.addLayer(StandardCategoryNames.WORKING, "Thiessen polygons", myCollC); } else{ context.getWorkbenchFrame().warnUser(this.msgNoPoint); } return true; } public FeatureDataset transferAttributes(FeatureSchema fs, Quadtree treeWithFeatures, ArrayList thiessenGeoms){ FeatureDataset fd = new FeatureDataset(fs); //-- walk through list of polygons and find points that are inside for (Iterator iterator = thiessenGeoms.iterator(); iterator.hasNext();) { Geometry poly = (Geometry) iterator.next(); Feature newFeature = new BasicFeature(fs); newFeature.setGeometry(poly); //-- find points near by Collection candidates = treeWithFeatures.query(poly.getEnvelopeInternal()); //-- find the candidate points that are inside the thiessen poly // this should only be one int pointsInside = 0; for (Iterator iterator2 = candidates.iterator(); iterator2.hasNext();) { Feature pt = (Feature) iterator2.next(); if (poly.contains(pt.getGeometry())){ //-- create a copy of feature without geometry // and add new thiessen poly geom newFeature = pt.clone(false); newFeature.setGeometry(poly); //-- do some tests pointsInside = pointsInside +1; if (pointsInside > 1){ //-- too much points => no unique identification // this actually should not happen, but one never knows this.pcontext.getWorkbenchFrame().warnUser(this.msgMultiplePointsInPoly + ": " + pointsInside); //-- reset attributes to zero (i.e. create a new feature) if (pointsInside == 2){//=2 to do this only once newFeature = new BasicFeature(fs); newFeature.setGeometry(poly); } } } } //-- add thiessen poly (with or without attributes) fd.add(newFeature); } return fd; } private class MethodItemListener implements ItemListener{ public void itemStateChanged(ItemEvent e) { updateUIForMethod(); } } }