/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.geometry.iso.coordinate; import org.geotools.geometry.iso.util.DoubleOperation; import org.geotools.geometry.iso.util.algorithmND.AlgoPointND; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.coordinate.LineSegment; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * * Thus its interpolation attribute shall be "linear". The default * GenericCurve::parameterization = c(s) is: (L : Distance) = endParam – * startParam c(s) = * ControlPoint[1]+((s-startParam)/L)*(ControlPoint[2]-ControlPoint[1]) Any * other point in the controlPoint array must fall on this line. The control * points of a LineSegment shall all lie on the straight line between its start * point and end point. Between these two points, other positions may be * interpolated linearly. NOTE The linear interpolation, given using a * constructive parameter t, 0 ? t ? 1.0, where c(o) = c.startPoint() and * c(1)=c.endPoint(), is: c(t) = c(0)(1-t) + c(1)t * * @author Jackson Roehrig & Sanjay Jena * * * * * @source $URL$ */ public class LineSegmentImpl extends LineStringImpl implements LineSegment { /** * The constructor of a LineSegment takes two positions and creates the * appropriate line segment joining them. Constructors are class scoped. * * LineSegment::LineSegment(point[2] : Position) : LineSegment * * @param pointArray * @param startPar * */ public LineSegmentImpl(PointArrayImpl pointArray, double startPar) { super(pointArray, startPar); if (pointArray.length() != 2) { throw new IllegalArgumentException( "A line segement must have exact two positions"); //$NON-NLS-1$ } } /** * * A LineSegment consists of two distinct DirectPositions (the startPoint * and endPoint) joined by a straight line. Thus its interpolation attribute * is "linear". The default GenericCurve::parameterization = c(s) is: * * (L : Distance) = endParam - startParam * * c(s) = ControlPoint[1]+((s-startParam)L)(ControlPoint[2]-ControlPoint[1]) * * Any other point in the controlPoint array must fall on this line. The * control points of a LineSegment lie all on the straight line between its * start point and end point. Between these two points, other positions may * be interpolated linearly. * * NOTE The linear interpolation, given using a constructive parameter t, 0<=t<=1.0, * where c(o) = c.startPoint() and c(1)=c. endPoint(), is: c(t) = c(0)(1-t) + * c(1)t * * The constructor of a LineSegment takes two positions and creates the * appropriate line segment joining them. Constructors are class scoped. * LineSegment::LineSegment(point[2] : Position) : LineSegment * * @param other */ public LineSegmentImpl(LineSegmentImpl other) { super(other); } /** * This functionality taken from CoordinateFactory where * I am not quite clear as to the purpose (Jody). * * @param crs * @param to * @param from * @param startParam */ public LineSegmentImpl( CoordinateReferenceSystem crs, double[] from, double[] to, double startParam ) { this( new PointArrayImpl( new DirectPositionImpl( crs, from ), new DirectPositionImpl( crs, to )), startParam ); } public LineSegmentImpl(DirectPosition one, DirectPosition two, double startParam) { this( new PointArrayImpl( one, two), startParam ); } /* * (non-Javadoc) * * @see org.geotools.geometry.featgeom.coordinate.CurveSegmentImpl#isSimple() * @version Implementation OK */ public boolean isSimple() { // a lineSegment is always simple since it is a straight line between two points return true; } /* * (non-Javadoc) * * @see org.opengis.geometry.coordinate.GenericCurve#forParam(double) * @version Shall this method override the LineString method? correctness * not verified. */ public DirectPosition forParamInSegment(double dist) { // Test ok - n dimensional valid // If the distance parameter is not within the parameter interval, return NULL if (dist < this.startParam || dist > this.endParam) return null; double par = DoubleOperation.div(DoubleOperation.subtract(dist, this.startParam), this.length()); return LineSegmentImpl.linearInterpolate(this.getStartPoint(), this.getEndPoint(), par); } /** * Executes a linear Interpolation between two positions and returns the * DirectPosition at the distance 'par' on this straight line * * This method is NON-ROBUST, e.g. it might suffer by errors caused by the Floating-point arithmetic * * @param p0 - * start position of linear interpolation * @param p1 - * end position of linear interpolation * @param par - * distance on the straight line, for which to search * DirectPosition (from p0 to p1) * @return Position on the straight line at parameter 'par' */ public static DirectPositionImpl linearInterpolate(DirectPosition p0, DirectPosition p1, double par) { // Test ok // 0.0 <= factor <= 1.0 // par = 0 => result = dp0 // par = 1 => result = dp1 double[] coord = AlgoPointND.evaluate(p0.getCoordinate(), p1.getCoordinate(), par); return new DirectPositionImpl( p0.getCoordinateReferenceSystem(), coord ); } /** * The operation "tangent" shall return the tangent vector along this * GenericCurve at the passed parameter value. This vector approximates the * derivative of the parameterization of the curve. The tangent shall be a * unit vector (have length 1.0), which is consistent with the * parameterization by arc length. * * This function will build the tangent vector at the position defined by * the distance. The vector will have his origin at the specified position, * and his vectorPosition in direction of the curve If the position, which * is defined by the distance, lays on a control point, this control point * will be the originPosition of the Vector * * @param distance * a <code>double</code> value * @return a <code>Vector</code> value */ public double[] getTangentInSegment(double distance) { // Works for n dimensional coordinates // Calculate the normative vector from origin double[] startpoint = this.getStartPoint().getCoordinate(); double[] endpoint = this.getEndPoint().getCoordinate(); double[] newEndPoint = AlgoPointND.subtract(startpoint, endpoint); newEndPoint = AlgoPointND.normalize(newEndPoint); // TODO this part depends from forParam() - implement this method! // Add the coordinate at distance to the normative vector double[] posAtDistance = this.forParamInSegment(distance).getCoordinate(); newEndPoint = AlgoPointND.add(newEndPoint, posAtDistance); return newEndPoint; } // public double[] getTangent(double distance) { // Vector2D vec = null; // /* If distance-param lays on endPoint.. */ // if (this.endParam == distance) { // /* Create vector between startPoint and endpoint of segment */ // vec = new Vector2D(this.startPoint(), this.getEndPoint()); // /* .. double the length of the vector.. */ // vec = vec.scale(2.0); // /* .. and create a new vector which has the endPoint of the segment as // his startPosition, // * and the endPoint of the double-scaled vecor as his endPosition */ // vec = new Vector2D(this.getEndPoint(), vec.getVectorPos()); // /* If distance-param lays between controlPoints (or at startPoint of // LineString) */ // } else { // /* Create vector between distance-Position and endpoint of segment */ // vec = new Vector2D(this.param(distance), this.getEndPoint()); // } // // /* Return normalized vector */ // vec = vec.getNormal(); // return vec; // } // /** // * @param ls // * @return boolean // */ // public boolean isParallel(LineSegmentImpl ls){ // return // LineSegmentImpl.isParallel((DirectPositionImpl)this.getStartPoint(), // (DirectPositionImpl)this.getEndPoint(), // (DirectPositionImpl)ls.getStartPoint(), // (DirectPositionImpl)ls.getEndPoint()); // } // /** // * @param p0 // * @param p1 // * @param q0 // * @param q1 // * @return boolean // */ // public static boolean isParallel(DirectPositionImpl p0, // DirectPositionImpl p1, DirectPositionImpl q0, DirectPositionImpl q1) { // return // Math.abs(((Double)DirectPositionImpl.cross(p1.subtract(p0),q1.subtract(q0)))) // <= DirectPositionImpl.EPSILON; // } // // This method returns: // // 1) four parameters if the segments are overlaped, the first two // parameters // // corresponding to the intersection points on this segment and the last // // two on the other segment. If both first parameters are Double.NaN, // // this segment contains the other segment. If both last parameters are // // Double.NaN, this segment is within the other one. // // 2) two parameters if the interior or the boundary of this segment // intersects // // the interior or the boundary of the other segment. If the boundaries // // intersect, the parameters are rounded to exactly startParam or // endParam // public static double[] constrIntersection(DirectPositionImpl p0, // DirectPositionImpl p1, DirectPositionImpl q0, DirectPositionImpl q1){ // // the range of the returned values is from 0.0 to 1.0 // // Overlaping: // // (p0,p1) *------------->* *------------->* *------->* *------------->* // *------------->* // // (q0,q1) *------------->* *------>* *------------->* *------------->* // *------------->* // // result: (0.0,1.0,0.0,1.0) (0.2,0.8,NaN,NaN) (NaN,NaN,0.2,0.8) // (0.2,NaN,NaN,0.8) (NaN,0,8,0.2,NaN) // // // // (p0,p1) *----->* *------->* *----->* *------->* // // (q0,q1) *------>* *----->* *------>* *----->* // // result: (1.0,NaN,NaN,0.0) (NaN,0.0,1.0,NaN) (1.0,NaN,NaN,0.0) // (NaN,0.0,1.0,NaN) // // Seg 0: \ / // // \/ result: (0.5,0.5) // // /\ // // Seg1 / \ // // // // |p1 // // | q0_________q1 result: (0.5,NaN) // // | // // |p0 // // // // q0 _________q1 // // |p1 // // | result: (NaN,0.5) // // | // // |p0 // // // double eps = DirectPositionImpl.EPSILON; // int i; // if ( LineSegmentImpl.isParallel(p0, p1, q0, q1) ) {//the segments are // parallel // double[] result = new double[4]; // result[0] = Double.NaN; // result[1] = Double.NaN; // result[2] = Double.NaN; // result[3] = Double.NaN; // // return nothing if they are not on the same line or outside the line // result[0] = LineSegmentImpl.constrParamForPoint(p0, p1, q0); // result[1] = LineSegmentImpl.constrParamForPoint(p0, p1, q1); // result[2] = LineSegmentImpl.constrParamForPoint(q0, q1, p0); // result[3] = LineSegmentImpl.constrParamForPoint(q0, q1, p1); // for ( i = 0 ; i <= 3; ++i) { // if ( result[i] < 0.0 || result[i] > 1.0 ) result[i] = Double.NaN; // } // return result; // } else { // //Dim result[1] As double // //result = intersectionConstrParam(p0, p1, q0, q1) // // return nothing if the the four points are on the same line (det <= // eps) // double[] result = new double[2]; // result[0] = Double.NaN; // result[1] = Double.NaN; // DirectPositionImpl ep = p1.subtract(p0); // DirectPositionImpl eq = q1.subtract(q0); // DirectPositionImpl pq = q0.subtract(p0); // double det = (Double)DirectPositionImpl.cross(ep,eq); // not 0, because // LineSegment.isParallel() = false // double rp = (Double)DirectPositionImpl.cross(pq,eq) / det; // double rq = (Double)DirectPositionImpl.cross(pq,ep) / det; // if ( Math.abs(rp) < eps ) rp = 0.0; // if ( Math.abs(rp - 1.0) < eps ) rp = 1.0; // if ( Math.abs(rq) < eps ) rq = 0.0; // if ( Math.abs(rq - 1.0) < eps ) rq = 1.0; // if ( rp < 0.0 || rp > 1.0 ) rp = Double.NaN; // if ( rq < 0.0 || rq > 1.0 ) rq = Double.NaN; // result[0] = rp; // result[1] = rq; // return result; // } // } // public static double constrParamForPoint(DirectPositionImpl p0 , // DirectPositionImpl p1 , DirectPositionImpl dp) { // // return the construction parametric coordinate (0.0 <= result <= 1.0) // of // // dp on the line (p0,p1) // // if p0.equals(p1) then: if dp.equals(p0) return 0.0, else Double.NaN // // if dp is close to p0 then result = 0.0 // // if dp is close to p1 then result = 1.0 // // if the line (p0,p1) does not contain dp then return Double.NaN // double eps = DirectPositionImpl.EPSILON; // double result = Double.NaN; // // return nothing if dp is not on the same line as (p0,p1): colinear // if (LineSegmentImpl.isParallel(p0, p1, p0, dp)) return Double.NaN; // if (p0.equals(p1)) { // if (p0.equals(dp)) // return 0.0; // else // return Double.NaN; // } // // choose the most apropriated axis // double coord[]; // coord = p0.getCoordinate(); // double p0x = coord[0]; // double p0y = coord[1]; // coord = p1.getCoordinate(); // double p1x = coord[0]; // double p1y = coord[1]; // coord = dp.getCoordinate(); // double dpx = coord[0]; // double dpy = coord[1]; // if (Math.abs(p0x - p1x) > Math.abs(p0y - p1y)) // result = (dpx - p0x) / (p1x - p0x); // else // result = (dpy - p0y) / (p1y - p0y); // if (Math.abs(result) <= eps) result = 0.0; // if (Math.abs(1.0 - result) <= eps) result = 1.0; // if (result < 0.0 || result > 1.0) return Double.NaN; // return result; // } // public int intersectionToRight(DirectPositionImpl dp) { // // returns 1 if a horizontal line including dp intersects this // // LineSegment // double x = dp.getX(); // double y = dp.getY(); // DirectPositionImpl dp0 = this.getStartPoint(); // DirectPositionImpl dp1 = this.getEndPoint(); // // double x0 = Math.min(dp0.getX(), dp1.getX()); // double x1 = Math.max(dp0.getX(), dp1.getX()); // double y0 = Math.min(dp0.getY(), dp1.getY()); // double y1 = Math.max(dp0.getY(), dp1.getY()); // // if (y0 == y1) // return 0; // if ((y < y0) || (y >= y1)) // return 0; // if ((x0 == x1) && (x < x0)) // return 0; // if (x0 != x1) { // double xa = x1; // double ya = y1; // double xb = x0; // double yb = y0; // if (((x1 > x0) && (y1 > y0)) || ((x1 < x0) && (y1 > y0))) { // xa = x0; // ya = y0; // xb = x1; // yb = y1; // } // if (((y - ya) * (xb - xa)) > ((x - xa) * (yb - ya))) // return 0; // } // return 1; // } // /** // * Checks weather the LineSegment intersects with another LineSegment // * // * @param other // * LineSegment // * @return TRUE, if both the LineSegments have points in common // (Intersection); // * FALSE, if they are dijoint // */ // public boolean intersects(LineSegmentImpl seg) { // return this.intersects(seg.getStartPoint(), seg.getEndPoint()); // } // // public boolean intersects(DirectPositionImpl q0, DirectPositionImpl q1) { // // double[] intsc = LineSegment.constrIntersection( // // (DirectPosition) this.getStartPoint(), // // (DirectPosition) this.getEndPoint(), // // (DirectPosition) q0, (DirectPosition) q1); // // if (intsc.length == 2) { // // return !Double.isNaN(intsc[0]) && !Double.isNaN(intsc[1]); // // } else if (intsc.length == 4) { // // return !Double.isNaN(intsc[0]) || !Double.isNaN(intsc[1]) // // || !Double.isNaN(intsc[2]) || !Double.isNaN(intsc[3]); // // } else { // // return !Double.isNaN(intsc[0]) && !Double.isNaN(intsc[1]); // // } // /* Create Line2D.Double objects from LineSegments */ // Line2D l1 = new Line2D.Double(this.getStartPoint().getX(), // this.getStartPoint().getY(), this.getEndPoint().getX(), // this.getEndPoint().getY()); // Line2D l2 = new Line2D.Double(q0.getX(), q0.getY(), q1.getX(), // q1.getY()); // return l1.intersectsLine(l2); // } // // public boolean rightSide(DirectPositionImpl dp){ // double eps; // DirectPositionImpl p0 = // (DirectPositionImpl)dp.subtract(this.getStartPoint()); // DirectPositionImpl p1 = // (DirectPositionImpl)this.getEndPoint().subtract(this.getStartPoint()); // return ((Double)DirectPositionImpl.cross(p0,p1)) > // DirectPositionImpl.EPSILON; // } // // public double[] getCoordinate() { // double[] c0 = this.getStartPoint().getCoordinate(); // double[] c1 = this.getEndPoint().getCoordinate(); // return new double[] {c0[0],c0[1],c1[0],c1[1]}; // } }