view gnv-artifacts/src/main/java/de/intevation/gnv/math/LinearToMap.java @ 1109:2b4d197a538c

Fixed the time range validation - introduced an epsilon of one second for valid time ranges (issue286). gnv-artifacts/trunk@1240 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 29 Jun 2010 09:07:26 +0000
parents bb7afd783321
children f953c9a559d8
line wrap: on
line source
package de.intevation.gnv.math;

import com.vividsolutions.jts.geom.Coordinate;

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * Given a list of line segments instances of this class are able
 * to span a metric system between a start and an end point
 * represented as scalar values to 2D coordinate on the
 * course of the segments.
 *
 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
 */
public class LinearToMap
{
    /**
     * Represents a segment of the line string.
     */
    public static final class Range {
        private Range next;

        private double from;
        private double to;
        private double b;

        private Coordinate p1;
        private Coordinate p2;

        private Interpolator interpolator;

        /**
         * Default constructor.
         */
        public Range() {
        }

        /**
         * Constructor to create a segment that maps
         * a coordinate pair to two scalar values.
         * Interpolations inside this segment are done with
         * a given interpolator.
         * @param from Start point of the segment.
         * @param to End point of the segment.
         * @param interpolator The interpolator.
         * @param p1 The scalar value mapped to the start point.
         * @param p2 The scalar value mappend to the end point.
         */
        public Range(
            double       from,
            double       to,
            Interpolator interpolator,
            Coordinate   p1,
            Coordinate   p2
        ) {
            this.from         = from;
            this.to           = to;
            this.interpolator = interpolator;
            this.p1           = p1;
            this.p2           = p2;

            b = from == to
                ? 0d
                : 1.0d/(to - from);
        }

        /**
         * Interpolated a coordinate on the segment given a scalar value
         * between the start and end point of the range.
         * @param x The scalar value.
         * @param v The interpolated value is stored here.
         */
        public void eval(double x, Coordinate v) {
            interpolator.interpolate((x - from)*b, v);
        }

        /**
         * Checks if a given value is inside this segment.
         * @param x The value to test
         * @return true if inside, else false.
         */
        public boolean inside(double x) {
            return x >= from && x <= to;
        }

        /**
         * Returns the start point of this segment.
         * @return The start point.
         */
        public Coordinate startPoint() {
            return p1;
        }

        /**
         * Return the end point of this segment.
         * @return The end point.
         */
        public Coordinate endPoint() {
            return p2;
        }
    } // class Range

    /**
     * The head of the internal range list.
     */
    protected Range head;

    /**
     * The last accessed segment. Used to accelerate
     * the access of the right segment.
     */
    protected Range last;

    /**
     * Default constructor.
     */
    public LinearToMap() {
    }

    /**
     * Constructor to create a LinearToMap that maps
     * given scalar values to coordinates of a given
     * list of line segments.
     * @param path The list of line segments.
     * @param from The start value mapped to the start point
     * of the first line segment.
     * @param to The end value mapped to the end point of
     * the last line segment.
     * @param metrics The metric used to span the 2D space.
     */
    public LinearToMap(
        List<? extends Coordinate> path,
        double                     from,
        double                     to,
        Metrics                    metrics
    ) {
        double diagramLength = Math.abs(to - from);

        double worldLength = length(path, metrics);

        double rangeStart = from;

        Range last = null;

        for (int i = 1, N = path.size(); i < N; ++i) {
            Coordinate p1 = path.get(i-1);
            Coordinate p2 = path.get(i);
            double segmentLength = metrics.distance(p1, p2);

            double relativeLength = segmentLength / worldLength;

            double rangeLength = diagramLength * relativeLength;

            double rangeEnd = rangeStart + rangeLength;

            Range range = new Range(
                rangeStart, rangeEnd,
                metrics.getInterpolator(p1, p2),
                p1, p2);

            if (last == null) {
                last = head = range;
            }
            else {
                last.next = range;
                last = range;
            }
            rangeStart = rangeEnd;
        }
    }

    /**
     * Returns a segment on which a given value is found.
     * @param diagramX The value.
     * @return The segment or null if no matching segment was found.
     */
    protected Range locateRange(double diagramX) {

        if (last != null && last.inside(diagramX)) {
            return last;
        }

        Range current = head;
        while (current != null) {
            if (current.inside(diagramX)) {
                return last = current;
            }
            current = current.next;
        }

        return null;
    }

    /**
     * Interpolates a coordinate at a given scalar position.
     * @param diagramX The scalar position.
     * @param v The interpolated coordinate is stored here.
     * @return true if the scalar position is inside the
     * spanned range of the line string, else false.
     */
    public boolean locate(double diagramX, Coordinate v) {
        Range range = locateRange(diagramX);
        if (range == null) {
            return false;
        }
        range.eval(diagramX, v);
        return true;
    }

    /**
     * Returns the length of a given line string using
     * a given metric.
     * @param path The line string.
     * @param metrics The used metric.
     * @return The length of the line string.
     */
    public static double length(
        List<? extends Coordinate> path,
        Metrics                    metrics
    ) {
        double sum = 0d;
        for (int i = path.size()-1; i >= 1; --i) {
            Coordinate p1 = path.get(i);
            Coordinate p2 = path.get(i-1);
            sum += metrics.distance(p1, p2);
        }
        return sum;
    }

    /**
     * Return the number of segments in this map.
     * @return the number of segments.
     */
    public int numRanges() {
        int count = 0;
        Range current = head;
        while (current != null) {
            ++count;
            current = current.next;
        }
        return count;
    }

    /**
     * Returns an iterator over all segments of this map.
     * @return The iterator.
     */
    public Iterator ranges() {
        return new Iterator() {

            Range current = head;

            public boolean hasNext() {
                return current != null;
            }

            public Object next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                Range x = current;
                current = current.next;
                return x;
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org