All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.shaded.org.locationtech.jts.shape.CubicBezierCurve Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * Copyright (c) 2021 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package com.hazelcast.shaded.org.locationtech.jts.shape;

import com.hazelcast.shaded.org.locationtech.jts.algorithm.Angle;
import com.hazelcast.shaded.org.locationtech.jts.geom.Coordinate;
import com.hazelcast.shaded.org.locationtech.jts.geom.CoordinateList;
import com.hazelcast.shaded.org.locationtech.jts.geom.Geometry;
import com.hazelcast.shaded.org.locationtech.jts.geom.GeometryFactory;
import com.hazelcast.shaded.org.locationtech.jts.geom.LineString;
import com.hazelcast.shaded.org.locationtech.jts.geom.LinearRing;
import com.hazelcast.shaded.org.locationtech.jts.geom.Polygon;
import com.hazelcast.shaded.org.locationtech.jts.geom.util.GeometryMapper;
import com.hazelcast.shaded.org.locationtech.jts.io.WKTWriter;

/**
 * Creates a curved geometry by replacing the segments
 * of the input with Cubic Bezier Curves.
 * The Bezier control points are determined from the segments of the geometry
 * and the alpha control parameter controlling curvedness, and
 * the optional skew parameter controlling the shape of the curve at vertices.
 * The Bezier Curves are created to be C2-continuous (smooth) 
 * at each input vertex.
 * 

* Alternatively, the Bezier control points can be supplied explicitly. *

* The result is not guaranteed to be valid, since large alpha values * may cause self-intersections. */ public class CubicBezierCurve { /** * Creates a geometry of linearized Cubic Bezier Curves * defined by the segments of the input and a parameter * controlling how curved the result should be. * * @param geom the geometry defining the curve * @param alpha curvedness parameter (0 is linear, 1 is round, >1 is increasingly curved) * @return the linearized curved geometry */ public static Geometry bezierCurve(Geometry geom, double alpha) { CubicBezierCurve curve = new CubicBezierCurve(geom, alpha); return curve.getResult(); } /** * Creates a geometry of linearized Cubic Bezier Curves * defined by the segments of the input and a parameter * controlling how curved the result should be, with a skew factor * affecting the curve shape at each vertex. * * @param geom the geometry defining the curve * @param alpha curvedness parameter (0 is linear, 1 is round, >1 is increasingly curved) * @param skew the skew parameter (0 is none, positive skews towards longer side, negative towards shorter * @return the linearized curved geometry */ public static Geometry bezierCurve(Geometry geom, double alpha, double skew) { CubicBezierCurve curve = new CubicBezierCurve(geom, alpha, skew); return curve.getResult(); } /** * Creates a geometry of linearized Cubic Bezier Curves * defined by the segments of the input * and a list (or lists) of control points. *

* Typically the control point geometry * is a {@link LineString} or {@link MultiLineString} * containing an element for each line or ring in the input geometry. * The list of control points for each linear element must contain two * vertices for each segment (and thus 2 * npts - 2). * * @param geom the geometry defining the curve * @param controlPoints a geometry containing the control point elements. * @return the linearized curved geometry */ public static Geometry bezierCurve(Geometry geom, Geometry controlPoints) { CubicBezierCurve curve = new CubicBezierCurve(geom, controlPoints); return curve.getResult(); } private double minSegmentLength = 0.0; private int numVerticesPerSegment = 16; private Geometry inputGeom; private double alpha =-1; private double skew = 0; private Geometry controlPoints = null; private final GeometryFactory geomFactory; private Coordinate[] bezierCurvePts; private double[][] interpolationParam; private int controlPointIndex = 0; /** * Creates a new instance producing a Bezier curve defined by a geometry * and an alpha curvedness value. * * @param geom geometry defining curve * @param alpha curvedness parameter (0 = linear, 1 = round, 2 = distorted) */ CubicBezierCurve(Geometry geom, double alpha) { this.inputGeom = geom; this.geomFactory = geom.getFactory(); if ( alpha < 0.0 ) alpha = 0; this.alpha = alpha; } /** * Creates a new instance producing a Bezier curve defined by a geometry, * an alpha curvedness value, and a skew factor. * * @param geom geometry defining curve * @param alpha curvedness parameter (0 is linear, 1 is round, >1 is increasingly curved) * @param skew the skew parameter (0 is none, positive skews towards longer side, negative towards shorter */ CubicBezierCurve(Geometry geom, double alpha, double skew) { this.inputGeom = geom; this.geomFactory = geom.getFactory(); if ( alpha < 0.0 ) alpha = 0; this.alpha = alpha; this.skew = skew; } /** * Creates a new instance producing a Bezier curve defined by a geometry, * and a list (or lists) of control points. *

* Typically the control point geometry * is a {@link LineString} or {@link MultiLineString} * containing an element for each line or ring in the input geometry. * The list of control points for each linear element must contain two * vertices for each segment (and thus 2 * npts - 2). * * @param geom geometry defining curve * @param controlPoints the geometry containing the control points */ CubicBezierCurve(Geometry geom, Geometry controlPoints) { this.inputGeom = geom; this.geomFactory = geom.getFactory(); this.controlPoints = controlPoints; } /** * Gets the computed linearized Bezier curve geometry. * * @return a linearized curved geometry */ public Geometry getResult() { bezierCurvePts = new Coordinate[numVerticesPerSegment]; interpolationParam = computeIterpolationParameters(numVerticesPerSegment); return GeometryMapper.flatMap(inputGeom, 1, new GeometryMapper.MapOp() { @Override public Geometry map(Geometry geom) { if (geom instanceof LineString) { return bezierLine((LineString) geom); } if (geom instanceof Polygon ) { return bezierPolygon((Polygon) geom); } //-- Points return geom.copy(); } }); } private LineString bezierLine(LineString ls) { Coordinate[] coords = ls.getCoordinates(); CoordinateList curvePts = bezierCurve(coords, false); curvePts.add(coords[coords.length - 1].copy(), false); return geomFactory.createLineString(curvePts.toCoordinateArray()); } private LinearRing bezierRing(LinearRing ring) { Coordinate[] coords = ring.getCoordinates(); CoordinateList curvePts = bezierCurve(coords, true); curvePts.closeRing(); return geomFactory.createLinearRing(curvePts.toCoordinateArray()); } private Polygon bezierPolygon(Polygon poly) { LinearRing shell = bezierRing(poly.getExteriorRing()); LinearRing[] holes = null; if (poly.getNumInteriorRing() > 0) { holes = new LinearRing[poly.getNumInteriorRing()]; for (int i = 0; i < poly.getNumInteriorRing(); i++) { holes[i] = bezierRing(poly.getInteriorRingN(i)); } } return geomFactory.createPolygon(shell, holes); } private CoordinateList bezierCurve(Coordinate[] coords, boolean isRing) { Coordinate[] control = controlPoints(coords, isRing); CoordinateList curvePts = new CoordinateList(); for (int i = 0; i < coords.length - 1; i++) { int ctrlIndex = 2 * i; addCurve(coords[i], coords[i + 1], control[ctrlIndex], control[ctrlIndex + 1], curvePts); } return curvePts; } private Coordinate[] controlPoints(Coordinate[] coords, boolean isRing) { if (controlPoints != null) { if (controlPointIndex >= controlPoints.getNumGeometries()) { throw new IllegalArgumentException("Too few control point elements"); } Geometry ctrlPtsGeom = controlPoints.getGeometryN(controlPointIndex++); Coordinate[] ctrlPts = ctrlPtsGeom.getCoordinates(); int expectedNum1 = 2 * coords.length - 2; int expectedNum2 = isRing ? coords.length - 1 : coords.length; if (expectedNum1 != ctrlPts.length && expectedNum2 != ctrlPts.length) { throw new IllegalArgumentException( String.format("Wrong number of control points for element %d - expected %d or %d, found %d", controlPointIndex-1, expectedNum1, expectedNum2, ctrlPts.length )); } return ctrlPts; } return controlPoints(coords, isRing, alpha, skew); } private void addCurve(Coordinate p0, Coordinate p1, Coordinate ctrl0, Coordinate crtl1, CoordinateList curvePts) { double len = p0.distance(p1); if ( len < minSegmentLength ) { // segment too short - copy input coordinate curvePts.add(new Coordinate(p0)); } else { cubicBezier(p0, p1, ctrl0, crtl1, interpolationParam, bezierCurvePts); for (int i = 0; i < bezierCurvePts.length - 1; i++) { curvePts.add(bezierCurvePts[i], false); } } } //-- chosen to make curve at right-angle corners roughly circular private static final double CIRCLE_LEN_FACTOR = 3.0 / 8.0; /** * Creates control points for each vertex of curve. * The control points are collinear with each vertex, * thus providing C1-continuity. * By default the control vectors are the same length, * which provides C2-continuity (same curvature on each * side of vertex. * The alpha parameter controls the length of the control vectors. * Alpha = 0 makes the vectors zero-length, and hence flattens the curves. * Alpha = 1 makes the curve at right angles roughly circular. * Alpha > 1 starts to distort the curve and may introduce self-intersections. *

* The control point array contains a pair of coordinates for each input segment. * * @param coords * @param isRing * @param alpha determines the curviness * @return the control point array */ private Coordinate[] controlPoints(Coordinate[] coords, boolean isRing, double alpha, double skew) { int N = coords.length; int start = 1; int end = N - 1; if (isRing) { N = coords.length - 1; start = 0; end = N; } int nControl = 2 * coords.length - 2; Coordinate[] ctrl = new Coordinate[nControl]; for (int i = start; i < end; i++) { int iprev = i == 0 ? N - 1 : i - 1; Coordinate v0 = coords[iprev]; Coordinate v1 = coords[i]; Coordinate v2 = coords[i + 1]; double interiorAng = Angle.angleBetweenOriented(v0, v1, v2); double orient = Math.signum(interiorAng); double angBisect = Angle.bisector(v0, v1, v2); double ang0 = angBisect - orient * Angle.PI_OVER_2; double ang1 = angBisect + orient * Angle.PI_OVER_2; double dist0 = v1.distance(v0); double dist1 = v1.distance(v2); double lenBase = Math.min(dist0, dist1); double intAngAbs = Math.abs(interiorAng); //-- make acute corners sharper by shortening tangent vectors double sharpnessFactor = intAngAbs >= Angle.PI_OVER_2 ? 1 : intAngAbs / Angle.PI_OVER_2; double len = alpha * CIRCLE_LEN_FACTOR * sharpnessFactor * lenBase; double stretch0 = 1; double stretch1 = 1; if (skew != 0) { double stretch = Math.abs(dist0 - dist1) / Math.max(dist0, dist1); int skewIndex = dist0 > dist1 ? 0 : 1; if (skew < 0) skewIndex = 1 - skewIndex; if (skewIndex == 0) { stretch0 += Math.abs(skew) * stretch; } else { stretch1 += Math.abs(skew) * stretch; } } Coordinate ctl0 = Angle.project(v1, ang0, stretch0 * len); Coordinate ctl1 = Angle.project(v1, ang1, stretch1 * len); int index = 2 * i - 1; // for a ring case the first control point is for last segment int i0 = index < 0 ? nControl - 1 : index; ctrl[i0] = ctl0; ctrl[index + 1] = ctl1; //System.out.println(WKTWriter.toLineString(v1, ctl0)); //System.out.println(WKTWriter.toLineString(v1, ctl1)); } if (! isRing) { setLineEndControlPoints(coords, ctrl); } return ctrl; } /** * Sets the end control points for a line. * Produce a symmetric curve for the first and last segments * by using mirrored control points for start and end vertex. * * @param coords * @param ctrl */ private void setLineEndControlPoints(Coordinate[] coords, Coordinate[] ctrl) { int N = ctrl.length; ctrl[0] = mirrorControlPoint(ctrl[1], coords[1], coords[0]); ctrl[N - 1] = mirrorControlPoint(ctrl[N - 2], coords[coords.length - 1], coords[coords.length - 2]); } /** * Creates a control point aimed at the control point at the opposite end of the segment. * * Produces overly flat results, so not used currently. * * @param c * @param p1 * @param p0 * @return */ private static Coordinate aimedControlPoint(Coordinate c, Coordinate p1, Coordinate p0) { double len = p1.distance(c); double ang = Angle.angle(p0, p1); return Angle.project(p0, ang, len); } private static Coordinate mirrorControlPoint(Coordinate c, Coordinate p0, Coordinate p1) { double vlinex = p1.x - p0.x; double vliney = p1.y - p0.y; // rotate line vector by 90 double vrotx = -vliney; double vroty = vlinex; double midx = (p0.x + p1.x) / 2; double midy = (p0.y + p1.y) / 2; return reflectPointInLine(c, new Coordinate(midx, midy), new Coordinate(midx + vrotx, midy + vroty)); } private static Coordinate reflectPointInLine(Coordinate p, Coordinate p0, Coordinate p1) { double vx = p1.x - p0.x; double vy = p1.y - p0.y; double x = p0.x - p.x; double y = p0.y - p.y; double r = 1 / (vx * vx + vy * vy); double rx = p.x + 2 * (x - x * vx * vx * r - y * vx * vy * r); double ry = p.y + 2 * (y - y * vy * vy * r - x * vx * vy * r); return new Coordinate(rx, ry); } /** * Calculates vertices along a cubic Bezier curve. * * @param p0 start point * @param p1 end point * @param ctrl1 first control point * @param ctrl2 second control point * @param param interpolation parameters * @param curve array to hold generated points */ private void cubicBezier(final Coordinate p0, final Coordinate p1, final Coordinate ctrl1, final Coordinate ctrl2, double[][] param, Coordinate[] curve) { int n = curve.length; curve[0] = new Coordinate(p0); curve[n - 1] = new Coordinate(p1); for (int i = 1; i < n - 1; i++) { Coordinate c = new Coordinate(); double sum = param[i][0] + param[i][1] +param[i][2] +param[i][3]; c.x = param[i][0] * p0.x + param[i][1] * ctrl1.x + param[i][2] * ctrl2.x + param[i][3] * p1.x; c.x /= sum; c.y = param[i][0] * p0.y + param[i][1] * ctrl1.y + param[i][2] * ctrl2.y + param[i][3] * p1.y; c.y /= sum; curve[i] = c; } } /** * Gets the interpolation parameters for a Bezier curve approximated by a * given number of vertices. * * @param n number of vertices * @return array of double[4] holding the parameter values */ private static double[][] computeIterpolationParameters(int n) { double[][] param = new double[n][4]; for (int i = 0; i < n; i++) { double t = (double) i / (n - 1); double tc = 1.0 - t; param[i][0] = tc * tc * tc; param[i][1] = 3.0 * tc * tc * t; param[i][2] = 3.0 * tc * t * t; param[i][3] = t * t * t; } return param; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy