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

eu.limetri.client.mapviewer.swing.jxmap.map.SplitPolygonDrawingContext Maven / Gradle / Ivy

/**
 *  Copyright (C) 2008-2013 LimeTri. All rights reserved.
 *
 *  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 eu.limetri.client.mapviewer.swing.jxmap.map;

import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;


import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;

import eu.limetri.client.mapviewer.api.SingleObjectLayer;
import eu.limetri.client.mapviewer.api.SplitGeometryHandler;
import eu.limetri.client.mapviewer.swing.JXMapViewer;
import eu.limetri.client.mapviewer.swing.render.DrawingRenderer;
import eu.limetri.client.mapviewer.swing.render.SplitPolygonDrawingRenderer;

/**
 * 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 Logger LOGGER = Logger.getLogger(SplitPolygonDrawingContext.class.getName());

    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
            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) {
                LOGGER.log(Level.SEVERE, Bundle.SplitPolygonDrawingContext_error_more_than_one_polygon(num));
                return null;
            }
            polygon = (Polygon) multiPolygon.getGeometryN(0);
        } else {
            LOGGER.severe(Bundle.SplitPolygonDrawingContext_error_not_a_polygon());
            return null;
        }
        
        if (polygon.isEmpty()) {
            LOGGER.severe(Bundle.SplitPolygonDrawingContext_error_empty_polygon());
            return null;
        }
        
        if (polygon.getNumInteriorRing() > 0) {
            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=eu/limetri/client/mapviewer/swing/jxmap/icons/map24.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