/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2014, Geomatys * * 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.geotoolkit.geometry.jts; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import org.apache.sis.util.ArgumentChecks; import static java.lang.StrictMath.*; import org.apache.sis.util.NullArgumentException; /** * Contain method to translate any {@code LineString}. * * @author Remi Marechal (Geomatys). */ public class LineStringTranslator { /** * Accepted tolerance. * @see #intersectionRightLine(com.vividsolutions.jts.geom.Coordinate, com.vividsolutions.jts.geom.Coordinate, com.vividsolutions.jts.geom.Coordinate, com.vividsolutions.jts.geom.Coordinate) */ private static final double TOLERANCE = 1E-9; /** * {@link IllegalArgumentException} use to stipulate that impossible to translate current {@link LineString}. * @see #intersectionRightLine(com.vividsolutions.jts.geom.Coordinate, com.vividsolutions.jts.geom.Coordinate, com.vividsolutions.jts.geom.Coordinate, com.vividsolutions.jts.geom.Coordinate) */ private static final IllegalArgumentException NO_INTERSECTION_EXCEPTION = new IllegalArgumentException("no intersection between lines."); public static MultiLineString translateLineString(final MultiLineString line, final double offset) { ArgumentChecks.ensureNonNull("MultiLineString", line); if (offset == 0) return line; final GeometryFactory geomFact = line.getFactory(); final LineString[] ls = new LineString[line.getNumGeometries()]; for(int i=0;i<ls.length;i++){ ls[i] = translateLineString((LineString)line.getGeometryN(i), offset); } final MultiLineString mls = geomFact.createMultiLineString(ls); mls.setSRID(line.getSRID()); mls.setUserData(line.getUserData()); return mls; } /** * <p>Translate {@code LineString} in function of offset value.<br/> * If offset is positive, translation is on the right of the {@link LineString} * and else on the left relative to the sens of the {@link LineString}.<br/> * Moreover the distance between the two generates {@link LineString}s is equal * to {@link StrictMath#abs(double) } of the offset.<br/> * In other word is the standard normal length of each {@link LineString} segments.</p> * * <p>For example : (generate {@link LineString} own letter with ' character).</p> * <p> * With a positive offset : </p><br/> * <pre> * B___________ C * | _______ \ * | |B' C'\ \ * | | \ \ * | | D' D * A A' * </pre> * <br/><p> * And with a negative offset : </p><br/> * <pre> * B'________________C' * | B___________ C \ * | | \ \ * | | \ \ * | | \ \ * | | D D' * A' A * </pre> * * @param line {@link LineString} which will be translate. * @param offset define length and sens of translation (+ for right and - for left). * @return the translated {@link LineString}. * @throws NullArgumentException if line is {@code null}. */ public static LineString translateLineString(final LineString line, final double offset) { ArgumentChecks.ensureNonNull("linestring", line); if (offset == 0) return line; final GeometryFactory geomFact = line.getFactory(); final Coordinate[] coords = line.getCoordinates(); final int coordsLength = coords.length; final Coordinate[] dstCoords = new Coordinate[coordsLength]; Coordinate dstPtn0 = new Coordinate(); Coordinate dstPtn1 = new Coordinate(); translateVector(coords[0], coords[1], dstPtn0, dstPtn1, offset); dstCoords[0] = dstPtn0; if (coordsLength == 2) { dstCoords[1] = dstPtn1; return geomFact.createLineString(dstCoords); } final int segmentNumber = coordsLength - 1; for (int c = 1; c < segmentNumber; c++) { Coordinate dstc0 = new Coordinate(); Coordinate dstc1 = new Coordinate(); translateVector(coords[c], coords[c+1], dstc0, dstc1, offset); //-- intersection between current translated segment and precedently generated segment. final Coordinate i = intersectionRightLine(dstPtn0, dstPtn1, dstc0, dstc1); //-- store generated intersection point dstCoords[c] = i; dstPtn0 = dstc0; dstPtn1 = dstc1; if (c == segmentNumber - 1) { if (coords[0].equals2D(coords[coordsLength - 1])) { //-- in case of closed linestring final Coordinate iend = intersectionRightLine(dstPtn0, dstPtn1, dstCoords[0], dstCoords[1]); dstCoords[0] = iend; dstCoords[++c] = new Coordinate(iend); } else { dstCoords[++c] = dstc1; } } } final LineString ls = geomFact.createLineString(dstCoords); ls.setSRID(line.getSRID()); ls.setUserData(line.getUserData()); return ls; } /** * Translate a single segment from {@link LineString}. * * @param srcPt0 begin point of the segment which will be translate. * @param srcPt1 ending point of the segment which will be translated. * @param destPt0 the destination begin point of the translated segment. * @param destPt1 the destination ending point of the translated segment. * @param offset define length and sens of translation (+ for right and - for left). */ private static void translateVector(final Coordinate srcPt0, final Coordinate srcPt1, final Coordinate destPt0, final Coordinate destPt1, final double offset) { final double vx = srcPt1.x - srcPt0.x; final double vy = srcPt1.y - srcPt0.y; //-- orthogonal vector coordinates. double ox = vy; double oy = - vx; final double normO = hypot(ox, oy); ox /= normO; oy /= normO; ox *= offset; oy *= offset; destPt0.x = srcPt0.x + ox; destPt0.y = srcPt0.y + oy; destPt1.x = srcPt1.x + ox; destPt1.y = srcPt1.y + oy; } /** * Found intersection point between two lines (AB) (CD). * * @param A begin point of the vector AB. * @param B ending point of the vector AB. * @param C begin point of the vector CD. * @param D ending point of the vector CD. * @return intersection point of the two right line define by the two vector AB AC. * @throws IllegalArgumentException if the two right line don't intersect. */ private static Coordinate intersectionRightLine(final Coordinate A, final Coordinate B, final Coordinate C, final Coordinate D) { /* * That u director vector of (AB) line. */ final double ux = B.x - A.x; final double uy = B.y - A.y; /* * That v director vector of (CD) line. */ final double vx = D.x - C.x; final double vy = D.y - C.y; //-- particularity cases. /* * pB = pC */ if (abs(B.x - C.x) < TOLERANCE && abs(B.y - C.y) < TOLERANCE) return new Coordinate(B.x, B.y); /* * pA = pD * if lineString sens is respected, normaly should never append. */ if (abs(A.x - D.x) < TOLERANCE && abs(A.y - D.y) < TOLERANCE) return new Coordinate(A.x, A.y); /* * pA = pB * Should never append. */ if (abs(A.x - B.x) < TOLERANCE && abs(A.y - B.y) < TOLERANCE) { /* * CA vector. */ final double wx = A.x - C.x; final double wy = A.y - C.y; /* * Compute scalar product of vector CA and CD. * If equal zero means colinear vector and AB € CD. */ if (abs(wx * vy - wy * vx) < TOLERANCE) return new Coordinate(A.x, A.y); throw NO_INTERSECTION_EXCEPTION; } /* * pC = pD * Should never append. */ if (abs(C.x - D.x) < TOLERANCE && abs(C.y - D.y) < TOLERANCE) { /* * AC vector. */ final double wx = C.x - A.x; final double wy = C.y - A.y; /* * Compute scalar product of vector AC and AB. * If equal zero means colinear vector and CD € AB. */ if (abs(ux * wy - uy * wx) < TOLERANCE) return new Coordinate(C.x, C.y); throw NO_INTERSECTION_EXCEPTION; } if (abs(vy) < TOLERANCE && abs(ux) < TOLERANCE) { /* * B * | * C __|___ D * | * A */ return new Coordinate(A.x, C.y); } if (abs(vx) < TOLERANCE && abs(uy) < TOLERANCE) { /* * D * | * A __|___ B * | * C */ return new Coordinate(C.x, A.y); } if (abs(ux) < TOLERANCE && abs(vx) < TOLERANCE) { /* * B * | D * A | * C */ if (abs(A.x - C.x) > TOLERANCE) throw NO_INTERSECTION_EXCEPTION; return new Coordinate(A.x, getIntersectionOnOneDimension(A.y, B.y, C.y, D.y)); } if (abs(uy) < TOLERANCE && abs(vy) < TOLERANCE) { /* * A_____B * C_____D */ if (abs(A.y - C.y) > TOLERANCE) throw NO_INTERSECTION_EXCEPTION; return new Coordinate(getIntersectionOnOneDimension(A.x, B.x, C.x, D.x), A.y); } if (abs(ux) <= TOLERANCE) { /* * B D * | / * |/ * /| * / A * C */ final double yv = (A.x - C.x) * (vy / vx) + C.y; return new Coordinate(A.x, yv); } if (abs(uy) <= TOLERANCE) { /* * D * / * A__/____B * / * C */ final double xv = (A.y - C.y) * (vx / vy) + C.x; return new Coordinate(xv, A.y); } if (abs(vx) <= TOLERANCE) { /* * D B * | / * |/ * /| * / C * A */ final double yu = (C.x - A.x) * (uy / ux) + A.y; return new Coordinate(C.x, yu); } if (abs(vy) <= TOLERANCE) { /* * B * / * C__/____D * / * A */ final double xu = (C.y - A.y) * (ux / uy) + A.x; return new Coordinate(xu, C.y); } final double vx_vy = vx / vy; /* * (AB) Cartesian equation. * {X(t) = t * ux + A.x * {Y(t) = t * uy + A.y */ final double t = ((A.y - C.y) * vx_vy + C.x - A.x) / (ux - uy * vx_vy); final double x = t * ux + A.x; final double y = t * uy + A.y; /* * That x tq : y = ax + b cartesian line expression. */ final double y2 = (x - C.x) / vx_vy + C.y; // y2 = (x - pC.x) * (vy / vx) + pC.y assert abs(y - y2) <= TOLERANCE : "Error computing y from lines should be equal : y(AB) = "+y+" y(CD) = "+y2; return new Coordinate(x, y); } /** * Returns intersection point between 2 spans. * * @param a begin coordinate of the ab span. * @param b ending coordinate of the ab span. * @param c begin coordinate of the cd span. * @param d ending coordinate of the cd span. * @return intersection point between 2 spans. * @throws IllegalArgumentException if no intersection. */ private static double getIntersectionOnOneDimension(final double a, final double b, final double c, final double d) { /* * Define intersection on 1 axis if exist */ final double iminx = max(a, c); final double imaxx = min(b, d); if (imaxx + TOLERANCE < iminx - TOLERANCE) { /* * A_____B C____D */ throw NO_INTERSECTION_EXCEPTION; } return (iminx + imaxx) / 2.0; } }