sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1600392 - in /sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis: internal/referencing/ShapeUtilities.java referencing/operation/transform/AbstractMathTransform2D.java
Date Wed, 04 Jun 2014 17:45:46 GMT
Author: desruisseaux
Date: Wed Jun  4 17:45:46 2014
New Revision: 1600392

URL: http://svn.apache.org/r1600392
Log:
Ported the code that apply a transformation on a Java2D Shape.

Added:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java
  (with props)
Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform2D.java

Added: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java?rev=1600392&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java
(added)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java
[UTF-8] Wed Jun  4 17:45:46 2014
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing;
+
+import java.awt.Shape;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.QuadCurve2D;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.PathIterator;
+import org.apache.sis.util.Static;
+
+import static java.lang.Math.*;
+
+
+/**
+ * Static methods operating on shapes from the {@link java.awt.geom} package.
+ *
+ * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
+ * @since   0.5 (derived from geotk-1.1)
+ * @version 0.5
+ * @module
+ */
+public final class ShapeUtilities extends Static {
+    /**
+     * Threshold value for determining whether two points are the same, or whether two lines
are colinear.
+     */
+    private static final double EPS = 1E-6;
+
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private ShapeUtilities() {
+    }
+
+    /**
+     * Returns the control point of a quadratic curve passing by the 3 given points. There
is an infinity of quadratic
+     * curves passing by 3 points. We can express the curve we are looking for as a parabolic
equation of the form
+     * {@code y = ax²+bx+c}, but the <var>x</var> axis is not necessarily horizontal.
The <var>x</var> axis orientation
+     * in the above equation is determined by the {@code horizontal} parameter:
+     *
+     * <ul>
+     *   <li>A value of {@code true} means that the <var>x</var> axis must
be horizontal.
+     *       The quadratic curve will then look like an ordinary parabolic curve as we see
+     *       in mathematic school book.</li>
+     *   <li>A value of {@code false} means that the <var>x</var> axis
must be parallel to the
+     *       line segment joining the {@code P0} and {@code P2} ending points.</li>
+     * </ul>
+     *
+     * Note that if {@code P0.y == P2.y}, then both {@code horizontal} values produce the
same result.
+     *
+     * @param  x0 <var>x</var> value of the starting point.
+     * @param  y0 <var>y</var> value of the starting point.
+     * @param  x1 <var>x</var> value of a passing point.
+     * @param  y1 <var>y</var> value of a passing point.
+     * @param  x2 <var>x</var> value of the ending point.
+     * @param  y2 <var>y</var> value of the ending point.
+     * @param  horizontal If {@code true}, the <var>x</var> axis is considered
horizontal while computing the
+     *         {@code y = ax²+bx+c} equation terms. If {@code false}, it is considered parallel
to the line
+     *         joining the {@code P0} and {@code P2} points.
+     * @return The control point of a quadratic curve passing by the given points. The curve
starts at {@code (x0,y0)}
+     *         and ends at {@code (x2,y2)}. If two points are too close or if the three points
are colinear, then this
+     *         method returns {@code null}.
+     */
+    public static Point2D.Double parabolicControlPoint(final double x0, final double y0,
+            double x1, double y1, double x2, double y2, final boolean horizontal)
+    {
+        /*
+         * Apply a translation in such a way that (x0,y0) become the coordinate system origin.
+         * After this translation, we shall not use (x0,y0) until we are done.
+         */
+        x1 -= x0;
+        y1 -= y0;
+        x2 -= x0;
+        y2 -= y0;
+        if (horizontal) {
+            final double a = (y2 - y1*x2/x1) / (x2-x1); // Actually "a*x2"
+            final double check = abs(a);
+            if (!(check <= 1/EPS)) return null; // Two points have the same coordinates.
+            if (!(check >=   EPS)) return null; // The three points are co-linear.
+            final double b = y2/x2 - a;
+            x1 = (1 + b/(2*a))*x2 - y2/(2*a);
+            y1 = y0 + b*x1;
+            x1 += x0;
+        } else {
+            /*
+             * Apply a rotation in such a way that (x2,y2)
+             * lies on the x axis, i.e. y2 = 0.
+             */
+            final double rx2 = x2;
+            final double ry2 = y2;
+            x2 = hypot(x2,y2);
+            y2 = (x1*rx2 + y1*ry2) / x2; // use 'y2' as a temporary variable for 'x1'
+            y1 = (y1*rx2 - x1*ry2) / x2;
+            x1 = y2;
+            y2 = 0;
+            /*
+             * Now compute the control point coordinates in our new coordinate system axis.
+             */
+            final double x = 0.5;                       // Actually "x/x2"
+            final double y = (y1*x*x2) / (x1*(x2-x1));  // Actually "y/y2"
+            final double check = abs(y);
+            if (!(check <= 1/EPS)) return null; // Two points have the same coordinates.
+            if (!(check >=   EPS)) return null; // The three points are co-linear.
+            /*
+             * Applies the inverse rotation then a translation to bring
+             * us back to the original coordinate system.
+             */
+            x1 = (x*rx2 - y*ry2) + x0;
+            y1 = (y*rx2 + x*ry2) + y0;
+        }
+        return new Point2D.Double(x1,y1);
+    }
+
+    /**
+     * Attempts to replace an arbitrary shape by one of the standard Java2D constructs.
+     * For example if the given {@code path} is a {@link Path2D} containing only a single
+     * line or a quadratic curve, then this method replaces it by a {@link Line2D} or
+     * {@link QuadCurve2D} object respectively.
+     *
+     * @param  path The shape to replace by a simpler Java2D construct.
+     *         This is generally an instance of {@link Path2D}, but not necessarily.
+     * @return A simpler Java construct, or {@code path} if no better construct is proposed.
+     */
+    public static Shape toPrimitive(final Shape path) {
+        final double[] buffer = new double[6];
+        final PathIterator it = path.getPathIterator(null);
+        if (!it.isDone() && it.currentSegment(buffer) == PathIterator.SEG_MOVETO
&& !it.isDone()) {
+            final double x1 = buffer[0];
+            final double y1 = buffer[1];
+            final int code = it.currentSegment(buffer);
+            if (it.isDone()) {
+                switch (code) {
+                    case PathIterator.SEG_LINETO:  return new       Line2D.Double(x1,y1,
buffer[0], buffer[1]);
+                    case PathIterator.SEG_QUADTO:  return new  QuadCurve2D.Double(x1,y1,
buffer[0], buffer[1], buffer[2], buffer[3]);
+                    case PathIterator.SEG_CUBICTO: return new CubicCurve2D.Double(x1,y1,
buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
+                }
+            }
+        }
+        return path;
+    }
+}

Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ShapeUtilities.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform2D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform2D.java?rev=1600392&r1=1600391&r2=1600392&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform2D.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform2D.java
[UTF-8] Wed Jun  4 17:45:46 2014
@@ -19,11 +19,19 @@ package org.apache.sis.referencing.opera
 import java.util.List;
 import java.awt.Shape;
 import java.awt.geom.Point2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.QuadCurve2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.IllegalPathStateException;
+import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.internal.referencing.ShapeUtilities;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -102,10 +110,10 @@ public abstract class AbstractMathTransf
 
     /**
      * Transforms a geometric shape. This method always copy transformed coordinates in a
new object.
-     * The new object is often a {@link Path2D}, but may also be a {@link Line2D} or a
-     * {@link QuadCurve2D} if such simplification is possible.
+     * The new object is often a {@link Path2D}, but may also be a {@link Line2D} or a {@link
QuadCurve2D}
+     * if such simplification is possible.
      *
-     * @param  transform     The transform to use.
+     * @param  mt            The math transform to use.
      * @param  shape         The geometric shape to transform.
      * @param  preTransform  An optional affine transform to apply <em>before</em>
the
      *                       transformation using {@code this}, or {@code null} if none.
@@ -114,18 +122,155 @@ public abstract class AbstractMathTransf
      * @param  horizontal    {@code true} for forcing parabolic equation.
      *
      * @return The transformed geometric shape.
-     * @throws MismatchedDimensionException if this transform doesn't is not two-dimensional.
      * @throws TransformException If a transformation failed.
      */
-    static Shape createTransformedShape(final MathTransform2D transform,
+    static Shape createTransformedShape(final MathTransform2D mt,
                                         final Shape           shape,
                                         final AffineTransform preTransform,
                                         final AffineTransform postTransform,
                                         final boolean         horizontal)
             throws TransformException
     {
-        // TODO
-        throw new UnsupportedOperationException();
+        final PathIterator     it = shape.getPathIterator(preTransform);
+        final Path2D.Double  path = new Path2D.Double(it.getWindingRule());
+        final double[]     buffer = new double[6];
+
+        double ax=0, ay=0;  // Coordinate of the last point before transform.
+        double px=0, py=0;  // Coordinate of the last point after  transform.
+        for (; !it.isDone(); it.next()) {
+            switch (it.currentSegment(buffer)) {
+                default: {
+                    throw new IllegalPathStateException();
+                }
+                case PathIterator.SEG_CLOSE: {
+                    /*
+                     * Close the geometric shape and continues the loop. We use the 'continue'
instruction
+                     * here instead of 'break' because we do not want to execute the code
after the switch
+                     * (addition of transformed points into the path - there is no such point
in a SEG_CLOSE).
+                     */
+                    path.closePath();
+                    continue;
+                }
+                case PathIterator.SEG_MOVETO: {
+                    /*
+                     * Transform the single point and adds it to the path. We use the 'continue'
instruction
+                     * here instead of 'break' because we do not want to execute the code
after the switch
+                     * (addition of a line or a curve - there is no such curve to add here;
we are just moving
+                     * the cursor).
+                     */
+                    ax = buffer[0];
+                    ay = buffer[1];
+                    mt.transform(buffer, 0, buffer, 0, 1);
+                    px = buffer[0];
+                    py = buffer[1];
+                    path.moveTo(px, py);
+                    continue;
+                }
+                case PathIterator.SEG_LINETO: {
+                    /*
+                     * Insert a new control point at 'buffer[0,1]'. This control point will
+                     * be initialised with coordinates in the middle of the straight line:
+                     *
+                     *  x = 0.5 * (x1+x2)
+                     *  y = 0.5 * (y1+y2)
+                     *
+                     * This point will be transformed after the 'switch', which is why we
use
+                     * the 'break' statement here instead of 'continue' as in previous case.
+                     */
+                    buffer[0] = 0.5 * (ax + (ax = buffer[0]));
+                    buffer[1] = 0.5 * (ay + (ay = buffer[1]));
+                    buffer[2] = ax;
+                    buffer[3] = ay;
+                    break;
+                }
+                case PathIterator.SEG_QUADTO: {
+                    /*
+                     * Replace the control point in 'buffer[0,1]' by a new control point
lying on the quadratic curve.
+                     * Coordinates for a point in the middle of the curve can be computed
with:
+                     *
+                     *  x = 0.5 * (ctrlx + 0.5 * (x1+x2))
+                     *  y = 0.5 * (ctrly + 0.5 * (y1+y2))
+                     *
+                     * There is no need to keep the old control point because it was not
lying on the curve.
+                     */
+                    buffer[0] = 0.5 * (buffer[0] + 0.5*(ax + (ax = buffer[2])));
+                    buffer[1] = 0.5 * (buffer[1] + 0.5*(ay + (ay = buffer[3])));
+                    break;
+                }
+                case PathIterator.SEG_CUBICTO: {
+                    /*
+                     * Replace the control point in 'buffer[0,1]' by a new control point
lying on the cubic curve.
+                     * Coordinates for a point in the middle of the curve can be computed
with:
+                     *
+                     *  x = 0.25 * (1.5 * (ctrlx1 + ctrlx2) + 0.5 * (x1 + x2));
+                     *  y = 0.25 * (1.5 * (ctrly1 + ctrly2) + 0.5 * (y1 + y2));
+                     *
+                     * There is no need to keep the old control point because it was not
lying on the curve.
+                     *
+                     * NOTE: The computed point is on the curve, but may not be representative
of the shape.
+                     *       This algorithm replaces two control points by a single one,
because we did not
+                     *       venture into a more sophisticated algorithm producing a CubicCurve2D.
For now,
+                     *       we presume that the current algorithm is okay if the curve is
smooth enough.
+                     */
+                    buffer[0] = 0.25 * (1.5 * (buffer[0] + buffer[2]) + 0.5 * (ax + (ax =
buffer[4])));
+                    buffer[1] = 0.25 * (1.5 * (buffer[1] + buffer[3]) + 0.5 * (ay + (ay =
buffer[5])));
+                    buffer[2] = ax;
+                    buffer[3] = ay;
+                    break;
+                }
+            }
+            /*
+             * Apply the transform on the point in the buffer, and append the transformed
points
+             * to the general path. Try to add them as a quadratic line, or as a straight
line if
+             * the computed control point is colinear with the starting and ending points.
+             */
+            mt.transform(buffer, 0, buffer, 0, 2);
+            final Point2D ctrlPoint = ShapeUtilities.parabolicControlPoint(px, py,
+                    buffer[0], buffer[1],
+                    buffer[2], buffer[3],
+                    horizontal);
+            px = buffer[2];
+            py = buffer[3];
+            if (ctrlPoint != null) {
+                path.quadTo(ctrlPoint.getX(), ctrlPoint.getY(), px, py);
+            } else {
+                path.lineTo(px, py);
+            }
+        }
+        /*
+         * Shape transformation is done. Apply an affine transform if it was requested,
+         * then simplify the geometric object (not the coordinate values) if possible.
+         */
+        if (postTransform != null) {
+            path.transform(postTransform);
+        }
+        return ShapeUtilities.toPrimitive(path);
+    }
+
+    /**
+     * Gets the derivative of this transform at a point.
+     * The default implementation performs the following steps:
+     *
+     * <ul>
+     *   <li>Copy the coordinate in a temporary array and pass that array to the
+     *       {@link #transform(double[], int, double[], int, boolean)} method,
+     *       with the {@code derivate} boolean argument set to {@code true}.</li>
+     *   <li>If the later method returned a non-null matrix, returns that matrix.
+     *       Otherwise throws {@link TransformException}.</li>
+     * </ul>
+     *
+     * @param  point The coordinate point where to evaluate the derivative.
+     * @return The derivative at the specified point as a 2×2 matrix.
+     * @throws TransformException if the derivative can not be evaluated at the specified
point.
+     */
+    @Override
+    public Matrix derivative(final Point2D point) throws TransformException {
+        final double[] coordinate = new double[] {point.getX(), point.getY()};
+        final Matrix derivative = transform(coordinate, 0, null, 0, true);
+        if (derivative == null) {
+            throw new TransformException(Errors.format(Errors.Keys.CanNotComputeDerivative));
+        }
+        return derivative;
     }
 
     /**



Mime
View raw message