view flys-artifacts/src/main/java/de/intevation/flys/artifacts/geom/Lines.java @ 2190:2792d2617be9

Introduced an abstract MinMaxState and a concrete class IntRangeState to add min/max value pairs. flys-artifacts/trunk@3800 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Fri, 27 Jan 2012 13:23:56 +0000
parents a7def20539fb
children a6fa128e4654
line wrap: on
line source
package de.intevation.flys.geom;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

import java.awt.geom.Point2D;
import java.awt.geom.Line2D;

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

import org.apache.log4j.Logger;

import gnu.trove.TDoubleArrayList;

public class Lines
{
    private static Logger log = Logger.getLogger(Lines.class);

    public static final double EPSILON = 1e-4;

    public static enum Mode { UNDEF, WET, DRY };

    protected Lines() {
    }

    public static List<Line2D> fillWater(List<Point2D> points, double waterLevel) {

        boolean debug = log.isDebugEnabled();

        if (debug) {
            log.debug("fillWater");
            log.debug("----------------------------");
        }

        List<Line2D> result = new ArrayList();

        int N = points.size();

        if (N == 0) {
            return result;
        }

        if (N == 1) {
            Point2D p = points.get(0);
            // Only generate point if over water
            if (waterLevel > p.getY()) {
                result.add(new Line2D.Double(
                    p.getX(), waterLevel,
                    p.getX(), waterLevel));
            }
            return result;
        }

        double minX =  Double.MAX_VALUE;
        double minY =  Double.MAX_VALUE;
        double maxX = -Double.MAX_VALUE;
        double maxY = -Double.MAX_VALUE;

        // To ensure for sequences of equals x's that
        // the original index order is preserved.
        for (Point2D p: points) {
            double x = p.getX(), y = p.getY();
            if (x < minX) minX = x;
            if (x > maxX) maxX = x;
            if (y < minY) minY = y;
            if (y > maxY) maxY = y;
        }

        if (minY > waterLevel) { // profile completely over water level
            log.debug("complete over water");
            return result;
        }

        if (waterLevel > maxY) { // water floods profile
            log.debug("complete under water");
            result.add(new Line2D.Double(minX, waterLevel, maxX, waterLevel));
            return result;
        }

        Mode mode = Mode.UNDEF;

        double startX = minX;

        for (int i = 1; i < N; ++i) {
            Point2D p1 = points.get(i-1);
            Point2D p2 = points.get(i);

            if (p1.getY() < waterLevel && p2.getY() < waterLevel) {
                // completely under water
                if (debug) {
                    log.debug("under water: " + p1 + " " + p2);
                }
                if (mode != Mode.WET) {
                    startX = p1.getX();
                    mode = Mode.WET;
                }
                continue;
            }

            if (p1.getY() > waterLevel && p2.getY() > waterLevel) {
                if (debug) {
                    log.debug("over water: " + p1 + " " + p2);
                }
                // completely over water
                if (mode == Mode.WET) {
                    log.debug("over/wet");
                    result.add(new Line2D.Double(
                        startX, waterLevel,
                        p1.getX(), waterLevel));
                }
                mode = Mode.DRY;
                continue;
            }

            if (Math.abs(p1.getX() - p2.getX()) < EPSILON) {
                // vertical line
                switch (mode) {
                    case WET:
                        log.debug("vertical/wet");
                        mode = Mode.DRY;
                        result.add(new Line2D.Double(
                            startX, waterLevel,
                            p1.getX(), waterLevel));
                        break;
                    case DRY:
                        log.debug("vertical/dry");
                        mode = Mode.WET;
                        startX = p2.getX();
                        break;
                    default: // UNDEF
                        log.debug("vertical/undef");
                        if (p2.getY() < waterLevel) {
                            mode = Mode.WET;
                            startX = p2.getX();
                        }
                        else {
                            mode = Mode.DRY;
                        }
                }
                continue;
            }

            // check if waterlevel directly hits the vertices;

            boolean p1W = Math.abs(waterLevel - p1.getY()) < EPSILON;
            boolean p2W = Math.abs(waterLevel - p2.getY()) < EPSILON;

            if (p1W || p2W) {
                if (debug) {
                    log.debug("water hits vertex: " + p1 + " " + p2 + " " + mode);
                }
                if (p1W && p2W) { // parallel to water -> dry
                    log.debug("water hits both vertices");
                    if (mode == Mode.WET) {
                        result.add(new Line2D.Double(
                            startX, waterLevel,
                            p1.getX(), waterLevel));
                    }
                    mode = Mode.DRY;
                }
                else if (p1W) { // p1 == waterlevel
                    log.debug("water hits first vertex");
                    if (p2.getY() > waterLevel) { // --> dry
                        if (mode == Mode.WET) {
                            result.add(new Line2D.Double(
                                startX, waterLevel,
                                p1.getX(), waterLevel));
                        }
                        mode = Mode.DRY;
                    }
                    else { // --> wet
                        if (mode != Mode.WET) {
                            startX = p1.getX();
                            mode = Mode.WET;
                        }
                    }
                }
                else { // p2 == waterlevel
                    log.debug("water hits second vertex");
                    if (p1.getY() > waterLevel) { // --> wet
                        if (mode != Mode.WET) {
                            startX = p2.getX();
                            mode = Mode.WET;
                        }
                    }
                    else { // --> dry
                        if (mode == Mode.WET) {
                            result.add(new Line2D.Double(
                                startX, waterLevel,
                                p2.getX(), waterLevel));
                        }
                        mode = Mode.DRY;
                    }
                }
                if (debug) {
                    log.debug("mode is now: " + mode);
                }
                continue;
            }

            // intersection case
            double x = Linear.linear(
                waterLevel,
                p1.getY(), p2.getY(),
                p1.getX(), p2.getX());

            if (debug) {
                log.debug("intersection p1:" + p1);
                log.debug("intersection p2:" + p2);
                log.debug("intersection at x: " + x);
            }

            switch (mode) {
                case WET:
                    log.debug("intersect/wet");
                    mode = Mode.DRY;
                    result.add(new Line2D.Double(
                        startX, waterLevel,
                        x, waterLevel));
                    break;

                case DRY:
                    log.debug("intersect/dry");
                    mode   = Mode.WET;
                    startX = x;
                    break;

                default: // UNDEF
                    log.debug("intersect/undef");
                    if (p2.getY() > waterLevel) {
                        log.debug("intersect/undef/over");
                        mode = Mode.DRY;
                        result.add(new Line2D.Double(
                            p1.getX(), waterLevel,
                            x, waterLevel));
                    }
                    else {
                        mode = Mode.WET;
                        startX = x;
                    }
            } // switch mode
        } // for all points p[i] and p[i-1]

        if (mode == Mode.WET) {
            result.add(new Line2D.Double(
                startX, waterLevel,
                maxX, waterLevel));
        }

        return result;
    }

    public static double [][] createWaterLines(
        List<Point2D> points,
        double        waterlevel
    ) {
        List<Line2D> lines = fillWater(points, waterlevel);

        TDoubleArrayList lxs = new TDoubleArrayList();
        TDoubleArrayList lys = new TDoubleArrayList();

        for (Iterator<Line2D> iter = lines.iterator(); iter.hasNext();) {
            Line2D  l  = iter.next();
            Point2D p1 = l.getP1();
            Point2D p2 = l.getP2();
            lxs.add(p1.getX());
            lys.add(p1.getY());
            lxs.add(p2.getX());
            lys.add(p2.getY());
            if (iter.hasNext()) {
                lxs.add(Double.NaN);
                lys.add(Double.NaN);
            }
        }

        return new double [][] { lxs.toNativeArray(), lys.toNativeArray() };
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org