nl.cloudfarming.client.geoviewer.jxmap.map.SplitPolygonDrawingContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of geoviewer-jxmap Show documentation
Show all versions of geoviewer-jxmap Show documentation
AgroSense geoviewer JXMap implementation. Contains a map/geoviewer TopComponent based on the JXMap classes from swingx.
The newest version!
/**
* Copyright (C) 2008-2012 AgroSense Foundation.
*
* AgroSense 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 3 of the License, or
* (at your option) any later version.
*
* There are special exceptions to the terms and conditions of the GPLv3 as it is applied to
* this software, see the FLOSS License Exception
* .
*
* AgroSense 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 AgroSense. If not, see .
*/
package nl.cloudfarming.client.geoviewer.jxmap.map;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import nl.cloudfarming.client.geoviewer.SingleObjectLayer;
import nl.cloudfarming.client.geoviewer.SplitGeometryHandler;
import nl.cloudfarming.client.geoviewer.render.DrawingRenderer;
import nl.cloudfarming.client.geoviewer.render.SplitPolygonDrawingRenderer;
import nl.cloudfarming.client.logging.AppLogFactory;
import nl.cloudfarming.client.logging.AppLogger;
import org.jdesktop.swingx.JXMapViewer;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
/**
* Split a polygon into two parts based on a drawn line.
*
* @author johan
*/
@NbBundle.Messages({
"# {0} - name of object being split",
"SplitPolygonDrawingContext.description=splitting geometry for {0}",
"SplitPolygonDrawingContext.error no geometry=There is no source geometry to split",
"# {0} - number of polygons in a multipolygon",
"SplitPolygonDrawingContext.error more than one polygon=Only single polygons are supported, the source multi-polygon contains {0} polygons",
"SplitPolygonDrawingContext.error not a polygon=The source geometry is not a (multi-)polygon",
"SplitPolygonDrawingContext.error empty polygon=The source polygon contains no points",
"SplitPolygonDrawingContext.warn polygon holes=Polygon holes aren't handled yet and will be lost when splitting",
})
public class SplitPolygonDrawingContext extends AbstractLineDrawingContext {
private static final AppLogger APP_LOGGER = AppLogFactory.getLogger(SplitPolygonDrawingContext.class);
private final Node node;
private final Polygon polygonToSplit;
private final List results = new ArrayList<>();
private final SplitGeometryHandler handler;
public SplitPolygonDrawingContext(Node node, JXMapViewer mapViewer) {
super(node.getLookup().lookup(SingleObjectLayer.class), mapViewer);
this.handler = node.getLookup().lookup(SplitGeometryHandler.class);
this.node = node;
this.polygonToSplit = validateSource(handler.getSource());
}
@Override
public boolean canStart() {
return polygonToSplit != null;
}
/**
* Try to extract a single polygon from the source geometry.
* TODO:
* - support multipolygons and holes.
* - see if this can be combined with the geometry validation from AGROSENSE-824.
* - create a testable validation class
*
* @param source
* @return the extracted polygon if successful, null otherwise
*/
private Polygon validateSource(Geometry source) {
Polygon polygon;
if (source == null) {
// should already have been handled by disabling the split action
APP_LOGGER.severe(Bundle.SplitPolygonDrawingContext_error_no_geometry());
return null;
}
if (source instanceof Polygon) {
polygon = (Polygon) source;
} else if (source instanceof MultiPolygon) {
MultiPolygon multiPolygon = (MultiPolygon) source;
int num = multiPolygon.getNumGeometries();
if (num != 1) {
APP_LOGGER.log(Level.SEVERE, Bundle.SplitPolygonDrawingContext_error_more_than_one_polygon(num));
return null;
}
polygon = (Polygon) multiPolygon.getGeometryN(0);
} else {
APP_LOGGER.severe(Bundle.SplitPolygonDrawingContext_error_not_a_polygon());
return null;
}
if (polygon.isEmpty()) {
APP_LOGGER.severe(Bundle.SplitPolygonDrawingContext_error_empty_polygon());
return null;
}
if (polygon.getNumInteriorRing() > 0) {
APP_LOGGER.warning(Bundle.SplitPolygonDrawingContext_warn_polygon_holes());
}
return polygon;
}
@Override
public boolean canFinish() {
// enough points in splitline?
if (coords.size() < 2) {
return false;
}
// is the source split into two parts?
return results.size() == 2;
}
private void split() {
results.clear();
// enough points in splitline?
int len = coords.size();
if (len < 2) {
return;
}
// create the splitline geometry:
CoordinateSequence coordSequence = new CoordinateArraySequence(coords.toArray(new Coordinate[len]));
LineString splitLine = new LineString(coordSequence, geometryFactory);
// since we're splitting into two parts, the intersection should be a single LineString:
Geometry intersection = splitLine.intersection(polygonToSplit);
if (intersection == null || intersection.isEmpty() || !(intersection instanceof LineString)) {
return;
}
// ensure that the line segments are properly noded and that vertices are created at line intersections:
// using strategy from http://stackoverflow.com/a/6263275
// TODO: handle polygon holes
MultiLineString mls = new MultiLineString(new LineString[] {splitLine, polygonToSplit.getExteriorRing()}, geometryFactory);
Geometry union = mls.union();
Polygonizer polygonizer = new Polygonizer();
polygonizer.add(union);
Collection polygons = polygonizer.getPolygons();
if (polygons.size() == 2) {
results.addAll(polygons);
}
}
@Override
protected void pointAdded() {
split();
}
@Override
protected void pointRemoved() {
split();
}
@Override
public boolean finish() {
if (handler.handle(results.toArray(new Geometry[2]), node)) {
cleanup();
return true;
}
return false;
}
@Override
public DrawingRenderer getRenderer() {
return new SplitPolygonDrawingRenderer(coords, polygonToSplit, results);
}
@Override
public String getDescription() {
return Bundle.SplitPolygonDrawingContext_description(node.getDisplayName());
}
/**
* Placeholder action for selecting helplines.
*
* TODO: toggle between two mutually exclusive modes: either select a line or draw one.
*/
@NbBundle.Messages({
"drawing_select_helpline_action_name=Select helpline",
"drawing_select_helpline_action_tooltip=Select helpline",
"drawing_select_helpline_action_icon=nl/cloudfarming/client/geoviewer/jxmap/icons/map16.png"})
private class SelectHelplineAction extends AbstractAction {
public SelectHelplineAction() {
putValue(SHORT_DESCRIPTION, Bundle.drawing_select_helpline_action_tooltip());
putValue(LARGE_ICON_KEY, ImageUtilities.loadImageIcon(Bundle.drawing_select_helpline_action_icon(), true));
setEnabled(false);
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
}