view flys-artifacts/src/main/java/de/intevation/flys/artifacts/geom/Polygon2D.java @ 2089:0da8874bd378

Added initial state to map artifact to be able to advance and step back. The map artifact overrides describe() to have the complete UI information in the describe response document. flys-artifacts/trunk@3613 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Raimund Renkert <raimund.renkert@intevation.de>
date Fri, 06 Jan 2012 12:02:10 +0000
parents 6f83d9d434f2
children 5642a83420f2
line wrap: on
line source
package de.intevation.flys.geom;

import java.io.Serializable;

import java.awt.Shape;

import java.awt.geom.Path2D;
import java.awt.geom.Point2D;

import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Collections;

import de.intevation.flys.artifacts.math.Linear;

import static de.intevation.flys.geom.VectorUtils.X;
import static de.intevation.flys.geom.VectorUtils.Y;
import static de.intevation.flys.geom.VectorUtils.EPSILON;

public class Polygon2D
implements   Serializable
{
    public static final Comparator<Point2D> POINT_X_CMP =
        new Comparator<Point2D>() {
            public int compare(Point2D a, Point2D b) {
                double d = X(a) - X(b);
                if (d < 0d) return -1;
                return d > 0d ? + 1 : 0;
            }
        };

    public static final Comparator<Point2D []> FIRST_POINT_X =
        new Comparator<Point2D []>() {
            public int compare(Point2D [] a, Point2D [] b) {
                if (a.length == 0) return -1; // should not happen.
                if (b.length == 0) return +1; // should not happen.
                double d = X(a[0]) - Y(b[0]);
                if (d < 0d) return -1;
                return d > 0d ? + 1 : 0;
            }
        };

    protected List<Point2D> points;

    public Polygon2D() {
        points = new ArrayList<Point2D>();
    }

    public Polygon2D(List<Point2D> points) {
        this.points = points;
    }

    public void add(double x, double y) {
        points.add(new Point2D.Double(x, y));
    }

    protected static boolean addCheck(Point2D p, List<Point2D> points) {
        switch (points.size()) {
            case 0:
                points.add(p);
                return true;
            case 1:
                if (VectorUtils.epsilonEquals(points.get(0), p)) {
                    return false;
                }
                points.add(p);
                return true;
            default:
                int L = points.size()-1;
                Point2D last = points.get(L);
                if (VectorUtils.epsilonEquals(last, p)) {
                    return false;
                }
                Point2D before = points.get(L-1);
                if (VectorUtils.collinear(before, last, p)) {
                    points.set(L, p);
                }
                else {
                    points.add(p);
                }
                return true;
        }
    }

    public boolean addCheck(Point2D p) {
        return addCheck(p, points);
    }

    public void addReversed(List<Point2D> other) {
        for (int i = other.size()-1; i >= 0; --i) {
            addCheck(other.get(i));
        }
    }

    public double area() {
        double area = 0d;

		for (int i = 0, N = points.size(); i < N; ++i) {
			int j = (i + 1) % N;
            Point2D pi = points.get(i);
            Point2D pj = points.get(j);
			area += X(pi)*Y(pj);
			area -= X(pj)*Y(pi);
		}

        return 0.5d*area;
    }

    public Shape toShape() {
        Path2D.Double path = new Path2D.Double();

        int N = points.size();

        if (N > 0) {
            Point2D p = points.get(0);
            path.moveTo(X(p), Y(p));
            for (int i = 1; i < N; ++i) {
                p = points.get(i);
                path.lineTo(X(p), Y(p));
            }
            path.closePath();
        }

        return path;
    }

    protected static List<Point2D []> splitByNaNs(
        double [] xs,
        double [] ys
    ) {
        List<Point2D []> parts = new ArrayList<Point2D []>();

        List<Point2D> tmp = new ArrayList<Point2D>(xs.length);

        for (int i = 0; i < xs.length; ++i) {
            double x = xs[i];
            double y = ys[i];

            if (Double.isNaN(x) || Double.isNaN(y)) {
                if (!tmp.isEmpty()) {
                    Point2D [] part = new Point2D[tmp.size()];
                    parts.add(tmp.toArray(part));
                    tmp.clear();
                }
            }
            else {
                tmp.add(new Point2D.Double(x, y));
            }
        }

        if (!tmp.isEmpty()) {
            Point2D [] part = new Point2D[tmp.size()];
            parts.add(tmp.toArray(part));
        }

        return parts;
    }

    protected static boolean removeNoneIntersecting(
        List<Point2D []> As,
        List<Point2D []> Bs
    ) {
        int B = Bs.size()-1;
        OUTER: for (int i = 0; i < As.size();) {
            Point2D [] a = As.get(i);
            int lo = 0, hi = B;
            while (lo <= hi) {
                int mid = (lo + hi) >> 1;
                Point2D [] b = Bs.get(mid);
                     if (X(a[0]) > X(b[b.length-1])) lo = mid+1;
                else if (X(a[a.length-1]) < X(b[0])) hi = mid-1;
                else {
                    // found: keep
                    ++i;
                    continue OUTER;
                }
            }
            // not found: remove
            As.remove(i);
        }

        return As.isEmpty();
    }

    protected static void buildPolygons(
        Point2D []      as,
        Point2D []      bs,
        Point2D [][]    rest,
        List<Polygon2D> positives,
        List<Polygon2D> negatives
    ) {
        List<Point2D> apoints = new ArrayList<Point2D>();
        List<Point2D> bpoints = new ArrayList<Point2D>();

        double ax = X(as[0]);
        double bx = X(bs[0]);

        int ai = 1;
        int bi = 1;

        boolean intersect = false;

        if (ax == bx) {
            apoints.add(as[0]);
            bpoints.add(bs[0]);
        }
        else if (ax > bx) {
            apoints.add(as[0]);
            double bx1;
            while ((bx1 = X(bs[bi])) < ax) ++bi;
            if (bx1 == ax) {
                bpoints.add(bs[bi]);
            }
            else { // need to calculate start b point.
                intersect = true;
                double by1 = Linear.linear(
                    ax,
                    X(bs[bi-1]), bx1,
                    Y(bs[bi-1]), Y(bs[bi]));

                bpoints.add(new Point2D.Double(ax, by1));
            }
        }
        else { // bx > ax: Symmetric case
            bpoints.add(bs[0]);
            double ax1;
            while ((ax1 = X(as[ai])) < bx) ++ai;
            if (ax1 == bx) {
                apoints.add(as[ai]);
            }
            else { // need to calculate start b point.
                intersect = true;
                double ay1 = Linear.linear(
                    bx,
                    X(as[ai-1]), ax1,
                    Y(as[ai-1]), Y(as[ai]));

                apoints.add(new Point2D.Double(bx, ay1));
            }
        }

        // now we have a point in each list, decide if neg/pos.
        boolean neg = Y(bpoints.get(0)) > Y(apoints.get(0));

        // Continue with inner points

        Line line = new Line();

        while (ai < as.length && bi < bs.length) {
            double xan = X(as[ai]);
            double xbn = X(bs[bi]);
            if (xan == xbn) { 
                double yan = Y(as[ai]);
                double ybn = Y(bs[ai]);

                if (neg) {
                    if (yan > ybn) { // intersection
                        Point2D ip = VectorUtils.intersection(
                            apoints.get(apoints.size()-1), as[ai],
                            bpoints.get(bpoints.size()-1), bs[bi]);
                        addCheck(ip, apoints);
                        addCheck(ip, bpoints);
                        Polygon2D p = new Polygon2D(
                            new ArrayList<Point2D>(apoints));
                        p.addReversed(bpoints);
                        negatives.add(p);
                        apoints.clear();
                        bpoints.clear();
                        apoints.add(ip);
                        bpoints.add(ip);
                        neg = !neg;
                    }
                    else { // no intersection
                        addCheck(as[ai], apoints);
                        addCheck(bs[bi], bpoints);
                    }
                }
                else { // not neg
                    if (ybn > yan) { // intersection
                        Point2D ip = VectorUtils.intersection(
                            apoints.get(apoints.size()-1), as[ai],
                            bpoints.get(bpoints.size()-1), bs[bi]);
                        addCheck(ip, apoints);
                        addCheck(ip, bpoints);
                        Polygon2D p = new Polygon2D(
                            new ArrayList<Point2D>(apoints));
                        p.addReversed(bpoints);
                        positives.add(p);
                        apoints.clear();
                        bpoints.clear();
                        apoints.add(ip);
                        bpoints.add(ip);
                        neg = !neg;
                    }
                    else { // no intersection
                        addCheck(as[ai], apoints);
                        addCheck(bs[bi], bpoints);
                    }
                }
                ++ai;
                ++bi;
            }
            else if (xan > xbn) {
                line.set(apoints.get(apoints.size()-1), as[ai]);
                double dir = neg ? -1d: 1d; // XXX: correct sign?
                while  (bi < bs.length 
                    && X(bs[bi]) < xan
                    && line.eval(bs[bi])*dir > EPSILON) 
                    ++bi;
                if (bi == bs.length) {
                    // b run out of points
                    // calculate Y(last_a, as[ai]) for X(bs[bi-1])
                    double ay1 = Linear.linear(
                        X(bs[bi-1]),
                        X(apoints.get(apoints.size()-1)), X(as[ai]),
                        Y(apoints.get(apoints.size()-1)), Y(as[ai]));
                    addCheck(new Point2D.Double(X(bs[bi-1]), ay1), apoints);
                    addCheck(bs[bi-1], bpoints);
                    Polygon2D p = new Polygon2D(
                        new ArrayList<Point2D>(apoints));
                    p.addReversed(bpoints);
                    apoints.clear();
                    bpoints.clear();
                    (neg ? negatives : positives).add(p);
                    break;
                }
                else {
                    // TODO: intersect line and/or X(bs[bi]) >= xan?
                }
            }
            else { // xbn > xan
                line.set(bpoints.get(bpoints.size()-1), bs[bi]);
                // TODO: continue symmetric
            }
        }

        // TODO: Continue with closing segment
    }

    public static final class Line {

        private double a;
        private double b;
        private double c;

        public Line() {
        }

        public Line(Point2D p1, Point2D p2) {
            set(p1, p2);
        }

        public void set(Point2D p1, Point2D p2) {
            Point2D p3 = 
                VectorUtils.normalize(
                VectorUtils.sub(p1, p2));

            Point2D n = VectorUtils.ortho(p3);

            a = X(n);
            b = Y(n);

            // a*x + b*y + c = 0
            // c = -a*x -b*y

            c = -a*X(p1) - b*Y(p1);
        }

        public double eval(Point2D p) {
            return a*X(p) + b*Y(p) + c;
        }
    }

    public static void createPolygons(
        double [] xAs, double [] yAs,
        double [] xBs, double [] yBs,
        List<Polygon2D> positives,
        List<Polygon2D> negatives
    ) {
        if (xAs.length == 0 || xBs.length == 0) {
            return;
        }

        List<Point2D []> splAs = splitByNaNs(xAs, yAs);
        List<Point2D []> splBs = splitByNaNs(xBs, yBs);

        // They feeded us with NaNs only.
        if (splAs.isEmpty() || splBs.isEmpty()) {
            return;
        }

        // Sort each part by x to ensure that the first
        // is the smallest.
        for (Point2D [] splA: splAs) {
            Arrays.sort(splA, POINT_X_CMP);
        }

        for (Point2D [] splB: splBs) {
            Arrays.sort(splB, POINT_X_CMP);
        }

        // Now sort all parts by there first elements.
        // Should be good enough to find overlapping regions.
        Collections.sort(splAs, FIRST_POINT_X);
        Collections.sort(splBs, FIRST_POINT_X);

        // Check if the two series intersect at all.
        // If no then there will be no area between them.

        Point2D [] p1 = splAs.get(0);
        Point2D [] p2 = splBs.get(splBs.size()-1);

        // Sort out the ranges that are not intersecting
        // the ranges in the other series.
        // We are going to merge them anyway
        // so this is not strictly required. 
        // Keep it to recude cases.
        if (removeNoneIntersecting(splAs, splBs)
        ||  removeNoneIntersecting(splBs, splAs)
        ) {
            // They do not intersect at all.
            return;
        }

        // TODO: Intersect/split the two series parts.
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org