ingo@1115: /* ingo@1115: * Copyright (c) 2010 by Intevation GmbH ingo@1115: * ingo@1115: * This program is free software under the LGPL (>=v2.1) ingo@1115: * Read the file LGPL.txt coming with the software for details ingo@1115: * or visit http://www.gnu.org/licenses/ if it does not exist. ingo@1115: */ ingo@1115: 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 Sascha L. Teichmann 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 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 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 :