All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.cloudfarming.client.geoviewer.jxmap.map.SplitPolygonDrawingContext Maven / Gradle / Ivy

Go to download

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) {
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy