1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.geometry.spherical.oned;
18
19 import org.apache.commons.geometry.core.Transform;
20
21 /** Implementation of the {@link Transform} interface for spherical 1D points.
22 *
23 * <p>Similar to the Euclidean 1D
24 * {@link org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D AffineTransformMatrix1D},
25 * this class performs transformations using an internal 1D affine transformation matrix. In the
26 * Euclidean case, the matrix contains a scale factor and a translation. Here, the matrix contains
27 * a scale/negation factor that takes the values -1 or +1, and a rotation value. This restriction on
28 * the allowed values in the matrix is required in order to fulfill the geometric requirements
29 * of the {@link Transform} interface. For example, if arbitrary scaling is allowed, the point {@code 0.5pi}
30 * could be scaled by 4 to {@code 2pi}, which is equivalent to {@code 0pi}. However, if the inverse scaling
31 * of {@code 1/4} is applied to {@code 0pi}, the result is {@code 0pi} and not {@code 0.5pi}. This breaks
32 * the {@link Transform} requirement that transforms be inversible.
33 * </p>
34 *
35 * <p>Instances of this class are guaranteed to be immutable.</p>
36 */
37 public final class Transform1S implements Transform<Point1S> {
38 /** Static instance representing the identity transform. */
39 private static final Transform1S IDENTITY = new Transform1S(1, 0);
40
41 /** Static instance that negates azimuth values. */
42 private static final Transform1S NEGATION = new Transform1S(-1, 0);
43
44 /** Value to scale the point azimuth by. This will only be +1/-1. */
45 private final double scale;
46
47 /** Value to rotate the point azimuth by. */
48 private final double rotate;
49
50 /** Construct a new instance from its transform components.
51 * @param scale scale value for the transform; must only be +1 or -1
52 * @param rotate rotation value
53 */
54 private Transform1S(final double scale, final double rotate) {
55 this.scale = scale;
56 this.rotate = rotate;
57 }
58
59 /** Return true if the transform negates the azimuth values of transformed
60 * points, regardless of any rotation applied subsequently.
61 * @return true if the transform negates the azimuth values of transformed
62 * points
63 * @see #preservesOrientation()
64 */
65 public boolean isNegation() {
66 return scale <= 0;
67 }
68
69 /** Get the rotation value applied by this instance, in radians.
70 * @return the rotation value applied by this instance, in radians.
71 */
72 public double getRotation() {
73 return rotate;
74 }
75
76 /** {@inheritDoc} */
77 @Override
78 public Point1S apply(final Point1S pt) {
79 final double az = pt.getAzimuth();
80 final double resultAz = (az * scale) + rotate;
81
82 return Point1S.of(resultAz);
83 }
84
85 /** {@inheritDoc} */
86 @Override
87 public boolean preservesOrientation() {
88 return !isNegation();
89 }
90
91 /** Return a new transform created by pre-multiplying this instance by a transform
92 * producing a rotation with the given angle.
93 * @param angle angle to rotate, in radians
94 * @return a new transform created by pre-multiplying this instance by a transform
95 * producing a rotation with the given angle
96 * @see #createRotation(double)
97 */
98 public Transform1S rotate(final double angle) {
99 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 }