001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.spherical.oned;
018
019import org.apache.commons.geometry.core.Transform;
020
021/** Implementation of the {@link Transform} interface for spherical 1D points.
022 *
023 * <p>Similar to the Euclidean 1D
024 * {@link org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D AffineTransformMatrix1D},
025 * this class performs transformations using an internal 1D affine transformation matrix. In the
026 * Euclidean case, the matrix contains a scale factor and a translation. Here, the matrix contains
027 * a scale/negation factor that takes the values -1 or +1, and a rotation value. This restriction on
028 * the allowed values in the matrix is required in order to fulfill the geometric requirements
029 * of the {@link Transform} interface. For example, if arbitrary scaling is allowed, the point {@code 0.5pi}
030 * could be scaled by 4 to {@code 2pi}, which is equivalent to {@code 0pi}. However, if the inverse scaling
031 * of {@code 1/4} is applied to {@code 0pi}, the result is {@code 0pi} and not {@code 0.5pi}. This breaks
032 * the {@link Transform} requirement that transforms be inversible.
033 * </p>
034 *
035 * <p>Instances of this class are guaranteed to be immutable.</p>
036 */
037public final class Transform1S implements Transform<Point1S> {
038    /** Static instance representing the identity transform. */
039    private static final Transform1S IDENTITY = new Transform1S(1, 0);
040
041    /** Static instance that negates azimuth values. */
042    private static final Transform1S NEGATION = new Transform1S(-1, 0);
043
044    /** Value to scale the point azimuth by. This will only be +1/-1. */
045    private final double scale;
046
047    /** Value to rotate the point azimuth by. */
048    private final double rotate;
049
050    /** Construct a new instance from its transform components.
051     * @param scale scale value for the transform; must only be +1 or -1
052     * @param rotate rotation value
053     */
054    private Transform1S(final double scale, final double rotate) {
055        this.scale = scale;
056        this.rotate = rotate;
057    }
058
059    /** Return true if the transform negates the azimuth values of transformed
060     * points, regardless of any rotation applied subsequently.
061     * @return true if the transform negates the azimuth values of transformed
062     *      points
063     * @see #preservesOrientation()
064     */
065    public boolean isNegation() {
066        return scale <= 0;
067    }
068
069    /** Get the rotation value applied by this instance, in radians.
070     * @return the rotation value applied by this instance, in radians.
071     */
072    public double getRotation() {
073        return rotate;
074    }
075
076    /** {@inheritDoc} */
077    @Override
078    public Point1S apply(final Point1S pt) {
079        final double az = pt.getAzimuth();
080        final double resultAz = (az * scale) + rotate;
081
082        return Point1S.of(resultAz);
083    }
084
085    /** {@inheritDoc} */
086    @Override
087    public boolean preservesOrientation() {
088        return !isNegation();
089    }
090
091    /** Return a new transform created by pre-multiplying this instance by a transform
092     * producing a rotation with the given angle.
093     * @param angle angle to rotate, in radians
094     * @return a new transform created by pre-multiplying this instance by a transform
095     *      producing a rotation with the given angle
096     * @see #createRotation(double)
097     */
098    public Transform1S rotate(final double angle) {
099        return premultiply(createRotation(angle));
100    }
101
102    /** Return a new transform created by pre-multiplying this instance by a transform
103     * that negates azimuth values.
104     * @return a new transform created by pre-multiplying this instance by a transform
105     *      that negates azimuth values
106     */
107    public Transform1S negate() {
108        return premultiply(createNegation());
109    }
110
111    /** Multiply the underlying matrix of this instance by that of the argument, eg,
112     * {@code other * this}. The returned transform performs the equivalent of
113     * {@code other} followed by {@code this}.
114     * @param other transform to multiply with
115     * @return a new transform computed by multiplying the matrix of this
116     *      instance by that of the argument
117     */
118    public Transform1S multiply(final Transform1S other) {
119        return multiply(this, other);
120    }
121
122    /** Multiply the underlying matrix of the argument by that of this instance, eg,
123     * {@code this * other}. The returned transform performs the equivalent of {@code this}
124     * followed by {@code other}.
125     * @param other transform to multiply with
126     * @return a new transform computed by multiplying the matrix of the
127     *      argument by that of this instance
128     */
129    public Transform1S premultiply(final Transform1S other) {
130        return multiply(other, this);
131    }
132
133    /** {@inheritDoc} */
134    @Override
135    public Transform1S inverse() {
136        final double invScale = 1.0 / scale;
137
138        final double resultRotate = -(rotate * invScale);
139
140        return new Transform1S(invScale, resultRotate);
141    }
142
143    /** {@inheritDoc} */
144    @Override
145    public int hashCode() {
146        final int prime = 31;
147        int result = 1;
148
149        result = (result * prime) + Double.hashCode(scale);
150        result = (result * prime) + Double.hashCode(rotate);
151
152        return result;
153    }
154
155    /**
156     * Return true if the given object is an instance of {@link Transform1S}
157     * and all transform element values are exactly equal.
158     * @param obj object to test for equality with the current instance
159     * @return true if all transform elements are exactly equal; otherwise false
160     */
161    @Override
162    public boolean equals(final Object obj) {
163        if (this == obj) {
164            return true;
165        }
166        if (!(obj instanceof Transform1S)) {
167            return false;
168        }
169        final Transform1S other = (Transform1S) obj;
170
171        return Double.compare(scale, other.scale) == 0 &&
172                Double.compare(rotate, other.rotate) == 0;
173    }
174
175    /** {@inheritDoc} */
176    @Override
177    public String toString() {
178        final StringBuilder sb = new StringBuilder();
179
180        sb.append(this.getClass().getSimpleName())
181            .append("[negate= ")
182            .append(isNegation())
183            .append(", rotate= ")
184            .append(getRotation())
185            .append(']');
186
187        return sb.toString();
188    }
189
190    /** Return a transform instance representing the identity transform.
191     * @return a transform instance representing the identity transform
192     */
193    public static Transform1S identity() {
194        return IDENTITY;
195    }
196
197    /** Return a transform instance that negates azimuth values.
198     * @return a transform instance that negates azimuth values.
199     */
200    public static Transform1S createNegation() {
201        return NEGATION;
202    }
203
204    /** Return a transform instance that performs a rotation with the given
205     * angle.
206     * @param angle angle of the rotation, in radians
207     * @return a transform instance that performs a rotation with the given
208     *      angle
209     */
210    public static Transform1S createRotation(final double angle) {
211        return new Transform1S(1, angle);
212    }
213
214    /** Multiply two transforms together as matrices.
215     * @param a first transform
216     * @param b second transform
217     * @return the transform computed as {@code a x b}
218     */
219    private static Transform1S multiply(final Transform1S a, final Transform1S b) {
220
221        // calculate the matrix elements
222        final double resultScale = a.scale * b.scale;
223        final double resultRotate = (a.scale * b.rotate) + a.rotate;
224
225        return new Transform1S(resultScale, resultRotate);
226    }
227}