1 /*
2 * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.navigation.lateration;
17
18 import com.irurueta.geometry.Point;
19 import com.irurueta.navigation.LockedException;
20 import com.irurueta.navigation.NotReadyException;
21
22 /**
23 * Solves the lateration problem.
24 * This is a formulation for a nonlinear least squares optimizer.
25 * This class is base on the implementation found at:
26 * <a href="https://github.com/lemmingapex/trilateration">https://github.com/lemmingapex/trilateration</a>
27 *
28 * @param <P> a {@link Point} type.
29 */
30 @SuppressWarnings("Duplicates")
31 public abstract class LaterationSolver<P extends Point<?>> {
32
33 /**
34 * Minimum allowed distance for a given circle or sphere.
35 */
36 public static final double EPSILON = 1e-7;
37
38 /**
39 * Known positions of static nodes.
40 */
41 protected P[] positions;
42
43 /**
44 * Euclidean distances from static nodes to mobile node.
45 */
46 protected double[] distances;
47
48 /**
49 * Listener to be notified of events raised by this instance.
50 */
51 protected LaterationSolverListener<P> listener;
52
53 /**
54 * Estimated inhomogeneous position coordinates.
55 */
56 protected double[] estimatedPositionCoordinates;
57
58 /**
59 * Indicates if this instance is locked because lateration is being solved.
60 */
61 protected boolean locked;
62
63 /**
64 * Constructor.
65 */
66 protected LaterationSolver() {
67 }
68
69 /**
70 * Constructor.
71 * Sets known positions and Euclidean distances.
72 *
73 * @param positions known positions of static nodes.
74 * @param distances euclidean distances from static nodes to mobile node.
75 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their
76 * length is smaller than required (3 for 2D points or 4 for 3D points).
77 */
78 protected LaterationSolver(final P[] positions, final double[] distances) {
79 internalSetPositionsAndDistances(positions, distances);
80 }
81
82 /**
83 * Constructor.
84 *
85 * @param listener listener to be notified of events raised by this instance.
86 */
87 protected LaterationSolver(final LaterationSolverListener<P> listener) {
88 this.listener = listener;
89 }
90
91 /**
92 * Constructor.
93 * Sets known positions and Euclidean distances.
94 *
95 * @param positions known positions of static nodes.
96 * @param distances euclidean distances from static nodes to mobile node.
97 * @param listener listener to be notified of events raised by this instance.
98 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their
99 * length is smaller than required (3 for 2D points or 4 for 3D points).
100 */
101 protected LaterationSolver(
102 final P[] positions, final double[] distances, final LaterationSolverListener<P> listener) {
103 this(positions, distances);
104 this.listener = listener;
105 }
106
107 /**
108 * Gets listener to be notified of events raised by this instance.
109 *
110 * @return listener to be notified of events raised by this instance.
111 */
112 public LaterationSolverListener<P> getListener() {
113 return listener;
114 }
115
116 /**
117 * Sets listener to be notified of events raised by this instance.
118 *
119 * @param listener listener to be notified of events raised by this instance.
120 * @throws LockedException if instance is busy solving the lateration problem.
121 */
122 public void setListener(final LaterationSolverListener<P> listener) throws LockedException {
123 if (isLocked()) {
124 throw new LockedException();
125 }
126 this.listener = listener;
127 }
128
129 /**
130 * Gets known positions of static nodes.
131 *
132 * @return known positions of static nodes.
133 */
134 public P[] getPositions() {
135 return positions;
136 }
137
138 /**
139 * Gets euclidean distances from static nodes to mobile node.
140 *
141 * @return euclidean distances from static nodes to mobile node.
142 */
143 public double[] getDistances() {
144 return distances;
145 }
146
147 /**
148 * Indicates whether solver is ready to find a solution.
149 *
150 * @return true if solver is ready, false otherwise.
151 */
152 public boolean isReady() {
153 return positions != null && distances != null && positions.length >= getMinRequiredPositionsAndDistances();
154 }
155
156 /**
157 * Returns boolean indicating if solver is locked because estimation is under
158 * progress.
159 *
160 * @return true if solver is locked, false otherwise.
161 */
162 public boolean isLocked() {
163 return locked;
164 }
165
166 /**
167 * Sets known positions and Euclidean distances.
168 * If any distance value is zero or negative, it will be fixed assuming an EPSILON value.
169 *
170 * @param positions known positions of static nodes.
171 * @param distances euclidean distances from static nodes to mobile node.
172 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their
173 * length is smaller than required (2 points).
174 * @throws LockedException if instance is busy solving the lateration problem.
175 */
176 public void setPositionsAndDistances(
177 final P[] positions, final double[] distances) throws LockedException {
178 if (isLocked()) {
179 throw new LockedException();
180 }
181 internalSetPositionsAndDistances(positions, distances);
182 }
183
184 /**
185 * Gets estimated inhomogeneous position coordinates.
186 *
187 * @return estimated inhomogeneous position coordinates.
188 */
189 public double[] getEstimatedPositionCoordinates() {
190 return estimatedPositionCoordinates;
191 }
192
193 /**
194 * Gets estimated position and stores result into provided instance.
195 *
196 * @param estimatedPosition instance where estimated position will be stored.
197 */
198 public void getEstimatedPosition(final P estimatedPosition) {
199 if (estimatedPositionCoordinates != null) {
200 for (var i = 0; i < estimatedPositionCoordinates.length; i++) {
201 estimatedPosition.setInhomogeneousCoordinate(i, estimatedPositionCoordinates[i]);
202 }
203 }
204 }
205
206 /**
207 * Gets estimated position.
208 *
209 * @return estimated position.
210 */
211 public abstract P getEstimatedPosition();
212
213 /**
214 * Gets number of dimensions of provided points.
215 *
216 * @return number of dimensions of provided points.
217 */
218 public abstract int getNumberOfDimensions();
219
220 /**
221 * Solves the lateration problem.
222 *
223 * @throws LaterationException if lateration fails.
224 * @throws NotReadyException is solver is not ready.
225 * @throws LockedException if instance is busy solving the lateration problem.
226 */
227 public abstract void solve() throws LaterationException, NotReadyException, LockedException;
228
229 /**
230 * Gets lateration solver type.
231 *
232 * @return lateration solver type.
233 */
234 public abstract LaterationSolverType getType();
235
236 /**
237 * Minimum required number of positions and distances.
238 * This value will depend on actual implementation and whether we are solving a 2D or 3D problem.
239 *
240 * @return minimum required number of positions and distances.
241 */
242 public abstract int getMinRequiredPositionsAndDistances();
243
244 /**
245 * Internally sets known positions and Euclidean distances.
246 * If any distance value is zero or negative, it will be fixed assuming an EPSILON value.
247 *
248 * @param positions known positions of static nodes.
249 * @param distances euclidean distances from static nodes to mobile node.
250 * @throws IllegalArgumentException if either positions or distances are null, don't have the same length or their
251 * length is smaller than required (2 points).
252 */
253 protected void internalSetPositionsAndDistances(final P[] positions, final double[] distances) {
254 if (positions == null || distances == null) {
255 throw new IllegalArgumentException();
256 }
257
258 if (positions.length < getMinRequiredPositionsAndDistances()) {
259 throw new IllegalArgumentException();
260 }
261
262 if (positions.length != distances.length) {
263 throw new IllegalArgumentException();
264 }
265
266 this.positions = positions;
267 this.distances = distances;
268
269 // fix distances if needed
270 for (var i = 0; i < this.distances.length; i++) {
271 if (this.distances[i] < EPSILON) {
272 this.distances[i] = EPSILON;
273 }
274 }
275 }
276 }