Package bs.bs2d.geom

Source Code of bs.bs2d.geom.AreaSet$ComponentMovingAverageFilter

package bs.bs2d.geom;

import bs.bs2d.gui.plot.ColorMap;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateFilter;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryComponentFilter;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.util.AffineTransformation;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
import com.vividsolutions.jts.operation.valid.ConnectedInteriorTester;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.Collection;
import org.jaitools.jts.LineSmoother;

/**
*
* @author Djen
*/
public class AreaSet {
   
    private static GeometryFactory GF = JTSUtils.getFactory();
   
    private final MultiPolygon shell;
    private final MultiPolygon[] polygons;
    private final Shape[] shapes;
   
    /**
     * Creates a new AreaSet from the given Shapes. The relative area threshold
     * will be used to compute the absolute threshold by multiplying it with the
     * total area of this AreaSet.
     * @param shapes the shapes
     * @param thrsh the relative area threshold in the interval [0, 1] (Default
     * is 0.04)
     * @param filterDegree the degree of the moving average filter that is
     * applied to smooth the area outlines. Set to 0 to disable filter. *Filtering is not currently supported
     */
    public AreaSet(Shape[] shapes, float thrsh, int filterDegree){
       
        // create polygons
        polygons = new MultiPolygon[shapes.length];
        for (int i = 0; i < shapes.length; i++) {
            polygons[i] = JTSUtils.createMultiPolygon(shapes[i]);
        }
       
        shell = (MultiPolygon)polygons[0].clone();
       
        // apply filter
//        Geometry preserve;
//        preserve = GF.createMultiLineString(JTSUtils.getLineStrings(shell));
//        preserve = preserve.buffer(0.0001);
//        for (int i = 0; i < polygons.length; i++) {
//            polygons[i] = applyMAFilter(polygons[i], filterDegree, preserve);
//        }
        thrsh *= shell.getArea();
        computeSubAreas(polygons, thrsh);
       
        // update the shapes for painting
        this.shapes = createShapes(polygons);
    }

    public AreaSet(MultiPolygon[] polygons) {
        this.shell = (MultiPolygon) polygons[0].clone();
        this.polygons = polygons;
        this.shapes = createShapes(polygons);
//        if(!isValid())
//            throw new RuntimeException("invalid area set!");
    }
   
    /**
     * Creates an area set with no subareas.
     * @return A new area set that equals the shell of this area set.
     */
    public AreaSet createSingularAreaSet(){
        MultiPolygon mp = (MultiPolygon) shell.clone();
        return new AreaSet(new MultiPolygon[]{mp});
    }
   
    /**
     * Applys a moving average filter to the polygon outlines. If filtering
     * results in an invalid geometry the filter degree is lowered successively.
     * If no filter could be applied or filter degree is 0 the original polygon
     * is returned.
     * @param poly the polygon to be smoothed
     * @param degree the filter degree. Set 0 to disable filter.
     * @param preserve a geometry containing all the points that are to be
     * preserved. No vertices conained in the preserve geometry will be changed
     * by the filter.
     * @return a new polygon with the filter applied or the original polygon if
     * no filter was applied
     */
    private MultiPolygon applyMAFilter(MultiPolygon poly, int degree, Geometry preserve){
        MultiPolygon filtered = null;
       
        for(int deg = degree; deg > 0; deg--){
            filtered = (MultiPolygon) poly.clone();
            filtered.apply(new ComponentMovingAverageFilter(deg, preserve));
           
            if(filtered.isValid()){
                ConnectedInteriorTester t1 = new ConnectedInteriorTester(null);
               
                System.err.println("Applied filter degree: " + deg);
                break;
            }
        }
       
        if(filtered != null && filtered.isValid()){
            return filtered;
        } else {
            if(degree > 0)
                System.err.println("No filter applied to polygon");
            return poly;
        }
    }
   
    private MultiPolygon applyCIFilter(MultiPolygon poly, Geometry preserve){
        GeometryFactory gf = JTSUtils.getFactory();
       
        // split lines trings along preserve geometry and seperate segments to
        // keep and to smooth
        MultiLineString lines = gf.createMultiLineString(JTSUtils.getLineStrings(poly));
        MultiLineString prsrvLines = gf.createMultiLineString(JTSUtils.getLineStrings(preserve));
       
        Geometry segments = lines.difference(prsrvLines);
        LineString[] segLines = JTSUtils.getLineStrings(segments);
       
        ArrayList<LineString> smooth = new ArrayList<>();
        ArrayList<LineString> keep = new ArrayList<>();
       
        for (LineString ls : segLines) {
            if(preserve.contains(ls))
                keep.add(ls);
            else
                smooth.add(ls);
        }
       
        // apply smoothing
        LineSmoother smoother = new LineSmoother(gf);
        Polygonizer polygonizer = new Polygonizer();
       
        for (int i = 0; i < smooth.size(); i++) {
            polygonizer.add(smoother.smooth(smooth.get(i), 0.5));
        }
        polygonizer.add(keep);
       
        Collection<Polygon> polys = (Collection<Polygon>) polygonizer.getPolygons();
        poly = gf.createMultiPolygon(polys.toArray(new Polygon[polys.size()]));
       
        if(!polygonizer.getInvalidRingLines().isEmpty())
            System.err.println("invalid ring lines found!");
        if(!polygonizer.getDangles().isEmpty())
            System.err.println("dangles found!");
        if(!polygonizer.getCutEdges().isEmpty())
            System.err.println("cut edges found!");
       
        if(!poly.isValid())
            throw new RuntimeException("invalid polygon!");
       
        return poly;
    }
   
   
    /**
     * Applys the given threshold to the set of MultiPolygons removing any
     * regions smaller than the thresholdvalue. At the same time higher order
     * areas are subtracted from lower order areas to eliminate any overlapping
     * regions.
     * As a result the given set of Multipolygons will contain no Polygons with
     * an area smaller that thrsh and any geometry in the set may touch, but
     * never intersect.
     * @param polygons the original overlapping set of geomtetries
     * @param thrsh the area threshold
     */
    private void computeSubAreas(MultiPolygon[] polygons, float thrsh){
        ArrayList<Polygon> subtract = new ArrayList<>();
       
        //start with highest order area
        for (int i = polygons.length - 1; i > 0; i--) {
            // apply threshold to polygons
            Polygon[] polys = JTSUtils.getPolygons(polygons[i]);
            for (int j = 0; j < polys.length; j++) {
                polys[j] = getThresholdedPolygon(polys[j], thrsh);
                subtract.add(polys[j]);
            }
            polygons[i] = GF.createMultiPolygon(polys);
           
            // subtract accumulated polys from next highest area
            Geometry diff = polygons[i-1];
            for (Polygon p : subtract)
                diff = diff.difference(p);
            polygons[i-1] = JTSUtils.toMultiPolygon(diff);
        }
       
        // sort out bottom area
        // can't have any holes smaller than thrsh
        ArrayList<Polygon> add = new ArrayList<>();
        ArrayList<Polygon> remain = new ArrayList<>();
       
        Polygon[] polys = JTSUtils.getPolygons(polygons[0]);
        for (Polygon p : polys) {
            if(p.getArea() < thrsh){
                add.add(p);
            } else {
                remain.add(p);
            }
        }
       
        // add small polys to higher order
        for (Polygon p : add) {
            for (int i = 1; i < polygons.length; i++) {
                MultiPolygon poly = polygons[i];
                if(!poly.disjoint(p) || i == polygons.length - 1){
                    // make sure p gets added somewhere
                    polygons[i] = JTSUtils.toMultiPolygon(polygons[i].union(p));
                    break;
                }
            }
        }
       
        // group remaining first order polygons
        polygons[0] = GF.createMultiPolygon(remain.toArray(new Polygon[0]));
    }
   
    private Polygon getThresholdedPolygon(Polygon poly, float thrsh){
       
        // sort out holes
        int rings = poly.getNumInteriorRing();
        ArrayList<LinearRing> holes = new ArrayList<>(rings);
        for (int j = 0; j < rings; j++) {
            Coordinate[] coords = poly.getInteriorRingN(j).getCoordinates();
            Polygon hole = GF.createPolygon(coords);
            if(hole.getArea() >= thrsh)
                holes.add(GF.createLinearRing(coords));
        }
       
        // create new polygon if holes changed
        if(rings != holes.size()){
            Coordinate[] coords = poly.getExteriorRing().getCoordinates();
            LinearRing outline = GF.createLinearRing(coords);
            LinearRing[] newHoles = holes.toArray(new LinearRing[0]);
            poly = GF.createPolygon(outline, newHoles);
        }
       
        //check polygon
        if(poly.getArea() < thrsh){
            return GF.createPolygon(new Coordinate[0]);
        }
       
        return poly;
    }
   
    private Shape[] createShapes(MultiPolygon[] polygons){
        Shape[] shps = new Shape[polygons.length];
        for (int i = 0; i < polygons.length; i++) {
            MultiPolygon poly = polygons[i];
            shps[i] = JTSUtils.toShape(poly);
        }
        return shps;
    }

    public Shape[] getShapes() {
        return shapes;
    }
   
    public MultiPolygon[] getPolygons(){
        return polygons;
    }
   
    public void paint(Graphics2D g, AffineTransform transform, ColorMap colorMap){
        for (int i = 0; i < shapes.length; i++) {
            Shape s = shapes[i];
            g.setColor(colorMap.getColor(i));
            g.fill(transform.createTransformedShape(s));
        }
    }

    /**
     * @return the shell
     */
    public MultiPolygon getShell() {
        return shell;
    }

    public Envelope getEnvelope(){
        return getShell().getEnvelopeInternal();
    }
   
//    public AreaSet getTranslated(final double dx, final double dy){
//        System.out.println("before: " + isValid());
//        System.out.println("translate - x: " + dx + " y: " + dy);
//        AffineTransformation t;
////        t = AffineTransformation.translationInstance(dx, dy);
//////        AffineTransform t;
//////        t = AffineTransform.getTranslateInstance(dx, dy);
////        AreaSet as = getTransformed(t);
//       
//        CoordinateFilter cf = new CoordinateFilter() {
//
//            @Override
//            public void filter(Coordinate coord) {
//                coord.x += dx;
//                coord.y += dy;
//            }
//        };
//       
//        AreaSet as = copy();
//        for (MultiPolygon poly : as.polygons) {
//            poly.apply(cf);
//        }
//        as = new AreaSet(as.polygons); // rebuild shell and shapes
//       
//        System.out.println("after: " + as.isValid());
//       
//        return as;
//   }
//   
//    public AreaSet getTransformed(AffineTransform t){
//        System.out.println("before t: " + isValid());
//        MultiPolygon[] tPolys = new MultiPolygon[shapes.length];
//        for (int i = 0; i < shapes.length; i++) {
//            tPolys[i] = JTSUtils.createMultiPolygon(t.createTransformedShape(shapes[i]));
//        }
//       
//        return new AreaSet(tPolys);
//    }
//   
//    public AreaSet getTransformed(AffineTransformation t){
//        MultiPolygon[] tPolys = new MultiPolygon[polygons.length];
//        for (int i = 0; i < polygons.length; i++) {
////            System.out.println("poly " + i + " valid: " + polygons[i].isValid());
//            tPolys[i] = JTSUtils.toMultiPolygon(t.transform(polygons[i]));
////            System.out.println("poly still valid: " + tPolys[i].isValid());
//        }
//       
//        return new AreaSet(tPolys);
//    }
//   
    /**
     * Creates an exact independent copy of this AreaSet.
     * @return a copy of this AreaSet
     */
    public AreaSet copy(){
        MultiPolygon[] plgns = new MultiPolygon[polygons.length];
        for (int i = 0; i < plgns.length; i++)
            plgns[i] = (MultiPolygon) polygons[i].clone();
       
        return new AreaSet(plgns);
    }
   
    public boolean isValid(){
        if(!shell.isValid())
            return false;
       
        for (MultiPolygon poly : polygons) {
            LineString[] l = JTSUtils.getLineStrings(poly);
            for (LineString ls : l) {
                if(!ls.isValid())
                    return false;
            }
           
        }
       
        return true;
    }
   
    // the number of subareas in this area set
    public int getResolution(){
        return polygons.length;
    }
   
   
    /**
     * First order moving average filter for closed loops.
     */
    private class ComponentMovingAverageFilter implements GeometryComponentFilter {

        Geometry preserve;
        int order;
       
        /**
         * Creates a new moving average filter with the given order.
         * Any points that intersect the preserve shape are left unchanged.
         * @param preserve the preserve shape
         */
        public ComponentMovingAverageFilter(int order, Geometry preserve) {
            this.preserve = preserve;
            this.order = order;
        }

        @Override
        public void filter(Geometry geom) {
            geom.apply(new CoordinateMovingAverageFilter(preserve, geom, order));
        }

    }
   
    private class CoordinateMovingAverageFilter implements CoordinateSequenceFilter{
       
        Geometry preserve;
        Coordinate[] cCoords;
        final int order;
        boolean done = false;
        boolean closed;
        int window;

        public CoordinateMovingAverageFilter(Geometry preserve, Geometry geom, int order) {
            cCoords = ((Geometry)geom.clone()).getCoordinates();
            if(cCoords.length > 0)
                closed = cCoords[0].equals(cCoords[cCoords.length - 1]);
            else
                closed = false;
           
            this.preserve = preserve;
            this.order = order;
        }
       
        @Override
        public void filter(CoordinateSequence seq, int i) {
            done = i == seq.size() - 1;
           
            if(done){
                if(closed){
                    seq.setOrdinate(i, 0, seq.getX(0));
                    seq.setOrdinate(i, 1, seq.getY(0));
                }
                return;
            }
           
            Point p = GF.createPoint(seq.getCoordinate(i));
            if(preserve.contains(p))
                return;
           
            window = order;
            window = Math.min(window, seq.size() / 2 - 1); // restric for small loops
            if(closed){
                window = Math.min(window, i);
                window = Math.min(window, seq.size() - i - 2);
            }
           
            double xMean = cCoords[i].x;
            double yMean = cCoords[i].y;
            int index;
            for (int j = 1; j <= window; j++) {
                index = i - j;
                if(index < 0)
                    index += seq.size() - 1;
               
                Coordinate cDown = cCoords[index];
                xMean += cDown.x;
                yMean += cDown.y;
               
                index = i + j;
                if(index >= seq.size())
                    index -= seq.size() - 1;
               
                Coordinate cUp = cCoords[index];
                xMean += cUp.x;
                yMean += cUp.y;
               
                if(preserve.contains(GF.createPoint(cDown)) ||
                   preserve.contains(GF.createPoint(cUp))) {
                    window = j;
                    break;
                }
            }
           
            int div = 2 * window + 1;
            xMean /= div;
            yMean /= div;
           
            seq.setOrdinate(i, 0, xMean);
            seq.setOrdinate(i, 1, yMean);
        }

        @Override
        public boolean isDone() {
            return done;
        }

        @Override
        public boolean isGeometryChanged() {
            return true;
        }
       
    }
   
}
TOP

Related Classes of bs.bs2d.geom.AreaSet$ComponentMovingAverageFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.