sascha@360: package de.intevation.gnv.math;
sascha@360: 
sascha@361: import com.vividsolutions.jts.geom.Coordinate;
sascha@360: 
sascha@779: import java.util.Iterator;
sascha@360: import java.util.List;
sascha@360: import java.util.NoSuchElementException;
sascha@360: 
sascha@360: /**
sascha@805:  * Given a list of line segments instances of this class are able
sascha@805:  * to span a metric system between a start and an end point
sascha@805:  * represented as scalar values to 2D coordinate on the
sascha@805:  * course of the segments.
sascha@805:  *
sascha@798:  * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
sascha@360:  */
sascha@360: public class LinearToMap
sascha@360: {
sascha@805:     /**
sascha@805:      * Represents a segment of the line string.
sascha@805:      */
sascha@360:     public static final class Range {
sascha@360:         private Range next;
sascha@360: 
sascha@360:         private double from;
sascha@360:         private double to;
sascha@360:         private double b;
sascha@360: 
sascha@361:         private Coordinate p1;
sascha@361:         private Coordinate p2;
sascha@360: 
sascha@360:         private Interpolator interpolator;
sascha@360: 
sascha@805:         /**
sascha@805:          * Default constructor.
sascha@805:          */
sascha@360:         public Range() {
sascha@360:         }
sascha@360: 
sascha@805:         /**
sascha@805:          * Constructor to create a segment that maps
sascha@805:          * a coordinate pair to two scalar values.
sascha@805:          * Interpolations inside this segment are done with
sascha@805:          * a given interpolator.
sascha@805:          * @param from Start point of the segment.
sascha@805:          * @param to End point of the segment.
sascha@805:          * @param interpolator The interpolator.
sascha@805:          * @param p1 The scalar value mapped to the start point.
sascha@805:          * @param p2 The scalar value mappend to the end point.
sascha@805:          */
sascha@360:         public Range(
sascha@778:             double       from,
sascha@360:             double       to,
sascha@360:             Interpolator interpolator,
sascha@361:             Coordinate   p1,
sascha@361:             Coordinate   p2
sascha@360:         ) {
sascha@360:             this.from         = from;
sascha@360:             this.to           = to;
sascha@360:             this.interpolator = interpolator;
sascha@360:             this.p1           = p1;
sascha@360:             this.p2           = p2;
sascha@360: 
sascha@360:             b = from == to
sascha@360:                 ? 0d
sascha@360:                 : 1.0d/(to - from);
sascha@360:         }
sascha@360: 
sascha@805:         /**
sascha@805:          * Interpolated a coordinate on the segment given a scalar value
sascha@805:          * between the start and end point of the range.
sascha@805:          * @param x The scalar value.
sascha@805:          * @param v The interpolated value is stored here.
sascha@805:          */
sascha@361:         public void eval(double x, Coordinate v) {
sascha@360:             interpolator.interpolate((x - from)*b, v);
sascha@360:         }
sascha@360: 
sascha@805:         /**
sascha@805:          * Checks if a given value is inside this segment.
sascha@805:          * @param x The value to test
sascha@805:          * @return true if inside, else false.
sascha@805:          */
sascha@360:         public boolean inside(double x) {
sascha@360:             return x >= from && x <= to;
sascha@360:         }
sascha@360: 
sascha@805:         /**
sascha@805:          * Returns the start point of this segment.
sascha@805:          * @return The start point.
sascha@805:          */
sascha@361:         public Coordinate startPoint() {
sascha@360:             return p1;
sascha@360:         }
sascha@360: 
sascha@805:         /**
sascha@805:          * Return the end point of this segment.
sascha@805:          * @return The end point.
sascha@805:          */
sascha@361:         public Coordinate endPoint() {
sascha@360:             return p2;
sascha@360:         }
sascha@360:     } // class Range
sascha@360: 
sascha@805:     /**
sascha@805:      * The head of the internal range list.
sascha@805:      */
sascha@360:     protected Range head;
sascha@805: 
sascha@805:     /**
sascha@805:      * The last accessed segment. Used to accelerate
sascha@805:      * the access of the right segment.
sascha@805:      */
sascha@360:     protected Range last;
sascha@360: 
sascha@805:     /**
sascha@805:      * Default constructor.
sascha@805:      */
sascha@360:     public LinearToMap() {
sascha@360:     }
sascha@360: 
sascha@805:     /**
sascha@805:      * Constructor to create a LinearToMap that maps
sascha@805:      * given scalar values to coordinates of a given
sascha@805:      * list of line segments.
sascha@805:      * @param path The list of line segments.
sascha@805:      * @param from The start value mapped to the start point
sascha@805:      * of the first line segment.
sascha@805:      * @param to The end value mapped to the end point of
sascha@805:      * the last line segment.
sascha@805:      * @param metrics The metric used to span the 2D space.
sascha@805:      */
sascha@360:     public LinearToMap(
sascha@778:         List<? extends Coordinate> path,
sascha@778:         double                     from,
sascha@361:         double                     to,
sascha@361:         Metrics                    metrics
sascha@360:     ) {
sascha@360:         double diagramLength = Math.abs(to - from);
sascha@360: 
sascha@360:         double worldLength = length(path, metrics);
sascha@360: 
sascha@360:         double rangeStart = from;
sascha@360: 
sascha@360:         Range last = null;
sascha@360: 
sascha@360:         for (int i = 1, N = path.size(); i < N; ++i) {
sascha@361:             Coordinate p1 = path.get(i-1);
sascha@361:             Coordinate p2 = path.get(i);
sascha@360:             double segmentLength = metrics.distance(p1, p2);
sascha@360: 
sascha@360:             double relativeLength = segmentLength / worldLength;
sascha@360: 
sascha@360:             double rangeLength = diagramLength * relativeLength;
sascha@360: 
sascha@360:             double rangeEnd = rangeStart + rangeLength;
sascha@360: 
sascha@360:             Range range = new Range(
sascha@360:                 rangeStart, rangeEnd,
sascha@360:                 metrics.getInterpolator(p1, p2),
sascha@360:                 p1, p2);
sascha@360: 
sascha@360:             if (last == null) {
sascha@360:                 last = head = range;
sascha@360:             }
sascha@360:             else {
sascha@360:                 last.next = range;
sascha@360:                 last = range;
sascha@360:             }
sascha@360:             rangeStart = rangeEnd;
sascha@360:         }
sascha@360:     }
sascha@360: 
sascha@805:     /**
sascha@805:      * Returns a segment on which a given value is found.
sascha@805:      * @param diagramX The value.
sascha@805:      * @return The segment or null if no matching segment was found.
sascha@805:      */
sascha@360:     protected Range locateRange(double diagramX) {
sascha@360: 
sascha@360:         if (last != null && last.inside(diagramX)) {
sascha@360:             return last;
sascha@360:         }
sascha@360: 
sascha@360:         Range current = head;
sascha@360:         while (current != null) {
sascha@360:             if (current.inside(diagramX)) {
sascha@360:                 return last = current;
sascha@360:             }
sascha@360:             current = current.next;
sascha@360:         }
sascha@360: 
sascha@360:         return null;
sascha@360:     }
sascha@360: 
sascha@805:     /**
sascha@805:      * Interpolates a coordinate at a given scalar position.
sascha@805:      * @param diagramX The scalar position.
sascha@805:      * @param v The interpolated coordinate is stored here.
sascha@805:      * @return true if the scalar position is inside the
sascha@805:      * spanned range of the line string, else false.
sascha@805:      */
sascha@361:     public boolean locate(double diagramX, Coordinate v) {
sascha@360:         Range range = locateRange(diagramX);
sascha@360:         if (range == null) {
sascha@360:             return false;
sascha@360:         }
sascha@360:         range.eval(diagramX, v);
sascha@360:         return true;
sascha@360:     }
sascha@360: 
sascha@805:     /**
sascha@805:      * Returns the length of a given line string using
sascha@805:      * a given metric.
sascha@805:      * @param path The line string.
sascha@805:      * @param metrics The used metric.
sascha@805:      * @return The length of the line string.
sascha@805:      */
sascha@361:     public static double length(
sascha@778:         List<? extends Coordinate> path,
sascha@361:         Metrics                    metrics
sascha@361:     ) {
sascha@360:         double sum = 0d;
sascha@360:         for (int i = path.size()-1; i >= 1; --i) {
sascha@361:             Coordinate p1 = path.get(i);
sascha@361:             Coordinate p2 = path.get(i-1);
sascha@360:             sum += metrics.distance(p1, p2);
sascha@360:         }
sascha@360:         return sum;
sascha@360:     }
sascha@360: 
sascha@805:     /**
sascha@805:      * Return the number of segments in this map.
sascha@805:      * @return the number of segments.
sascha@805:      */
sascha@360:     public int numRanges() {
sascha@360:         int count = 0;
sascha@360:         Range current = head;
sascha@360:         while (current != null) {
sascha@360:             ++count;
sascha@360:             current = current.next;
sascha@360:         }
sascha@360:         return count;
sascha@360:     }
sascha@360: 
sascha@805:     /**
sascha@805:      * Returns an iterator over all segments of this map.
sascha@805:      * @return The iterator.
sascha@805:      */
sascha@360:     public Iterator ranges() {
sascha@360:         return new Iterator() {
sascha@360: 
sascha@360:             Range current = head;
sascha@360: 
sascha@360:             public boolean hasNext() {
sascha@360:                 return current != null;
sascha@360:             }
sascha@360: 
sascha@360:             public Object next() {
sascha@360:                 if (!hasNext()) {
sascha@360:                     throw new NoSuchElementException();
sascha@360:                 }
sascha@360:                 Range x = current;
sascha@360:                 current = current.next;
sascha@360:                 return x;
sascha@360:             }
sascha@360: 
sascha@360:             public void remove() {
sascha@360:                 throw new UnsupportedOperationException();
sascha@360:             }
sascha@360:         };
sascha@360:     }
sascha@360: }
sascha@798: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :