1 /* 2 * Copyright (C) 2019 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.algebra.AlgebraException; 19 import com.irurueta.algebra.Matrix; 20 import com.irurueta.algebra.SingularValueDecomposer; 21 import com.irurueta.geometry.Point; 22 import com.irurueta.navigation.LockedException; 23 import com.irurueta.navigation.NotReadyException; 24 25 /** 26 * Linearly solves the lateration problem using an homogeneous LMSE solution. 27 */ 28 public abstract class HomogeneousLinearLeastSquaresLaterationSolver<P extends Point<?>> extends LaterationSolver<P> { 29 30 /** 31 * Constructor. 32 */ 33 protected HomogeneousLinearLeastSquaresLaterationSolver() { 34 super(); 35 } 36 37 /** 38 * Constructor. 39 * 40 * @param positions known positions of static nodes. 41 * @param distances euclidean distances from static nodes to mobile node. 42 * @throws IllegalArgumentException if either positions or distances are null, 43 * don't have the same length or their length is smaller than required points. 44 */ 45 protected HomogeneousLinearLeastSquaresLaterationSolver(final P[] positions, final double[] distances) { 46 super(positions, distances); 47 } 48 49 /** 50 * Constructor. 51 * 52 * @param listener listener to be notified of events raised by this instance. 53 */ 54 protected HomogeneousLinearLeastSquaresLaterationSolver(final LaterationSolverListener<P> listener) { 55 super(listener); 56 } 57 58 /** 59 * Constructor. 60 * 61 * @param positions known positions of static nodes. 62 * @param distances euclidean distances from static nodes to mobile node. 63 * @param listener listener to be notified of events raised by this instance. 64 * @throws IllegalArgumentException if either position or distances are null, 65 * don't have the same length or their length is smaller than required points. 66 */ 67 protected HomogeneousLinearLeastSquaresLaterationSolver( 68 final P[] positions, final double[] distances, final LaterationSolverListener<P> listener) { 69 super(positions, distances, listener); 70 } 71 72 /** 73 * Solves the lateration problem. 74 * 75 * @throws LaterationException if lateration fails. 76 * @throws NotReadyException if solver is not ready. 77 * @throws LockedException if instance is busy solving the lateration problem. 78 */ 79 @Override 80 public void solve() throws LaterationException, NotReadyException, LockedException { 81 // The implementation on this method follows the algorithm bellow. 82 83 // Having 3 2D circles: 84 // c1x, c1y, r1 85 // c2x, c2y, r2 86 // c3x, c3y, r3 87 // where (c1x, c1y) are the coordinates of 1st circle center and r1 is its radius. 88 // (c2x, c2y) are the coordinates of 2nd circle center and r2 is its radius. 89 // (c3x, c3y) are the coordinates of 3rd circle center and r3 is its radius. 90 91 // The equations of the circles are as follows: 92 // (x - c1x)^2 + (y - c1y)^2 = r1^2 93 // (x - c2x)^2 + (y - c2y)^2 = r2^2 94 // (x - c3x)^2 + (y - c3y)^2 = r3^2 95 96 // x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2 = r1^2 97 // x^2 - 2*c2x*x + c2x^2 + y^2 - 2*c2y*y + c2y^2 = r2^2 98 // x^2 - 2*c3x*x + c3x^2 + y^2 - 2*c3y*y + c3y^2 = r3^2 99 100 // remove 1st equation from others (we use 1st point as reference) 101 102 // x^2 - 2*c2x*x + c2x^2 + y^2 - 2*c2y*y + c2y^2 - (x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2) = r2^2 - r1^2 103 // x^2 - 2*c3x*x + c3x^2 + y^2 - 2*c3y*y + c3y^2 - (x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2) = r3^2 - r1^2 104 105 // - 2*c2x*x + c2x^2 - 2*c2y*y + c2y^2 + 2*c1x*x - c1x^2 + 2*c1y*y - c1y^2 = r2^2 - r1^2 106 // - 2*c3x*x + c3x^2 - 2*c3y*y + c3y^2 + 2*c1x*x - c1x^2 + 2*c1y*y - c1y^2 = r3^2 - r1^2 107 108 // 2*(c1x - c2x)*x + c2x^2 + 2*(c1y - c2y)*y + c2y^2 - c1x^2 - c1y^2 = r2^2 - r1^2 109 // 2*(c1x - c3x)*x + c3x^2 + 2*(c1y - c3y)*y + c3y^2 - c1x^2 - c1y^2 = r3^2 - r1^2 110 111 // 2*(c1x - c2x)*x + 2*(c1y - c2y)*y = r2^2 - r1^2 + c1x^2 + c1y^2 - c2x^2 - c2y^2 112 // 2*(c1x - c3x)*x + 2*(c1y - c3y)*y = r3^2 - r1^2 + c1x^2 + c1y^2 - c3x^2 - c3y^2 113 114 // x and y are the inhomogeneous coordinates of the point (x,y) we want to find, we 115 // substitute such point by the corresponding homogeneous coordinates (x,y) = (x'/w', y'/w') 116 117 // Hence 118 // 2*(c1x - c2x)*x'/w' + 2*(c1y - c2y)*y'/w' = r2^2 - r1^2 + c1x^2 + c1y^2 - c2x^2 - c2y^2 119 // 2*(c1x - c3x)*x'/w' + 2*(c1y - c3y)*y'/w' = r3^2 - r1^2 + c1x^2 + c1y^2 - c3x^2 - c3y^2 120 121 // Multiplying by w' at both sides... 122 // 2*(c1x - c2x)*x' + 2*(c1y - c2y)*y' = (r2^2 - r1^2 + c1x^2 + c1y^2 - c2x^2 - c2y^2)*w' 123 // 2*(c1x - c3x)*x' + 2*(c1y - c3y)*y' = (r3^2 - r1^2 + c1x^2 + c1y^2 - c3x^2 - c3y^2)*w' 124 125 // Obtaining the following homogeneous equations 126 // 2*(c1x - c2x)*x' + 2*(c1y - c2y)*y' - (r2^2 - r1^2 + c1x^2 + c1y^2 - c2x^2 - c2y^2)*w' = 0 127 // 2*(c1x - c3x)*x' + 2*(c1y - c3y)*y' - (r3^2 - r1^2 + c1x^2 + c1y^2 - c3x^2 - c3y^2)*w' = 0 128 129 // Fixing signs... 130 // 2*(c1x - c2x)*x' + 2*(c1y - c2y)*y' + (r1^2 - r2^2 + c2x^2 + c2y^2 - c1x^2 - c1y^2)*w' = 0 131 // 2*(c1x - c3x)*x' + 2*(c1y - c3y)*y' + (r1^2 - r3^2 + c3x^2 + c3y^2 - c1x^2 - c1y^2)*w' = 0 132 133 134 // The homogeneous equations can be expressed as a linear system of homogeneous equations A*x = 0 135 // where the unknowns to be solved are (x', y', w') up to scale. 136 137 // [2*(c1x - c2x) 2*(c1y - c2y) r1^2 - r2^2 + c2x^2 + c2y^2 - c1x^2 - c1y^2][x'] = 0 138 // [2*(c1x - c3x) 2*(c1y - c3y) r1^2 - r3^2 + c3x^2 + c3y^2 - c1x^2 - c1y^2][y'] = 0 139 // [w'] 140 141 // This can be solved by using the SVD decomposition of matrix A and picking the last column of 142 // resulting V matrix. At least 2 equations are required to find a solution, since 1 additional 143 // point is used as a reference, at least 3 points are required. 144 145 // For spheres the solution is analogous 146 147 // Having 4 3D spheres: 148 // c1x, c1y, c1z, r1 149 // c2x, c2y, c2z, r2 150 // c3x, c3y, c3z, r3 151 // c4x, c4y, c4z, r4 152 // where (c1x, c1y, c1z) are the coordinates of 1st sphere center and r1 is its radius. 153 // (c2x, c2y, c2z) are the coordinates of 2nd sphere center and r2 is its radius. 154 // (c3x, c3y, c3z) are the coordinates of 3rd sphere center and r3 is its radius. 155 // (c4x, c4y, c4z) are the coordinates of 4th sphere center and r4 is its radius. 156 157 // The equations of the spheres are as follows: 158 // (x - c1x)^2 + (y - c1y)^2 + (z - c1z)^2 = r1^2 159 // (x - c2x)^2 + (y - c2y)^2 + (z - c2z)^2 = r2^2 160 // (x - c3x)^2 + (y - c3y)^2 + (z - c3z)^2 = r3^2 161 // (x - c4x)^2 + (y - c4y)^2 + (z - c4z)^2 = r4^2 162 163 // x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2 + z^2 - 2*c1z*z + c1z^2 = r1^2 164 // x^2 - 2*c2x*x + c2x^2 + y^2 - 2*c2y*y + c2y^2 + z^2 - 2*c2z*z + c2z^2 = r2^2 165 // x^2 - 2*c3x*x + c3x^2 + y^2 - 2*c3y*y + c3y^3 + z^2 - 2*c3z*z + c3z^2 = r3^2 166 // x^2 - 2*c4x*x + c4x^2 + y^2 - 2*c4y*y + c4y^2 + z^2 - 2*c4z*z + c4z^2 = r4^2 167 168 // remove 1st equation from others (we use 1st point as reference) 169 // x^2 - 2*c2x*x + c2x^2 + y^2 - 2*c2y*y + c2y^2 + z^2 - 2*c2z*z + c2z^2 - (x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2 + z^2 - 2*c1z*z + c1z^2) = r2^2 - r1^2 170 // x^2 - 2*c3x*x + c3x^2 + y^2 - 2*c3y*y + c3y^3 + z^2 - 2*c3z*z + c3z^2 - (x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2 + z^2 - 2*c1z*z + c1z^2) = r3^2 - r1^2 171 // x^2 - 2*c4x*x + c4x^2 + y^2 - 2*c4y*y + c4y^2 + z^2 - 2*c4z*z + c4z^2 - (x^2 - 2*c1x*x + c1x^2 + y^2 - 2*c1y*y + c1y^2 + z^2 - 2*c1z*z + c1z^2) = r4^2 - r1^2 172 173 // - 2*c2x*x + c2x^2 - 2*c2y*y + c2y^2 - 2*c2z*z + c2z^2 + 2*c1x*x - c1x^2 + 2*c1y*y - c1y^2 + 2*c1z*z - c1z^2 = r2^2 - r1^2 174 // - 2*c3x*x + c3x^2 - 2*c3y*y + c3y^3 - 2*c3z*z + c3z^2 + 2*c1x*x - c1x^2 + 2*c1y*y - c1y^2 + 2*c1z*z - c1z^2 = r3^2 - r1^2 175 // - 2*c4x*x + c4x^2 - 2*c4y*y + c4y^2 - 2*c4z*z + c4z^2 + 2*c1x*x - c1x^2 + 2*c1y*y - c1y^2 + 2*c1z*z - c1z^2 = r4^2 - r1^2 176 177 // 2*(c1x - c2x)*x + c2x^2 + 2*(c1y - c2y)*y + c2y^2 + 2*(c1z - c2z)*z + c2z^2 - c1x^2 - c1y^2 - c1z^2 = r2^2 - r1^2 178 // 2*(c1x - c3x)*x + c3x^2 + 2*(c1y - c3y)*y + c3y^3 + 2*(c1z - c3z)*z + c3z^2 - c1x^2 - c1y^2 - c1z^2 = r3^2 - r1^2 179 // 2*(c1x - c4x)*x + c4x^2 + 2*(c1y - c4y)*y + c4y^2 + 2*(c1z - c4z)*z + c4z^2 - c1x^2 - c1y^2 - c1z^2 = r4^2 - r1^2 180 181 // 2*(c1x - c2x)*x + 2*(c1y - c2y)*y + 2*(c1z - c2z)*z = r2^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c2x^2 - c2y^2 - c2z^2 182 // 2*(c1x - c3x)*x + 2*(c1y - c3y)*y + 2*(c1z - c3z)*z = r3^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c3x^2 - c3y^3 - c3z^2 183 // 2*(c1x - c4x)*x + 2*(c1y - c4y)*y + 2*(c1z - c4z)*z = r4^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c4x^2 - c4y^2 - c4z^2 184 185 // x, y and z the inhomogeneous coordinates of the point (x,y,z) we want to find, 186 // we substitute such point by the corresponding homogeneous coordinates 187 // (x, y, z) = (x'/w', y'/w', z'/w') 188 189 // Hence 190 // 2*(c1x - c2x)*x'/w' + 2*(c1y - c2y)*y'/w' + 2*(c1z - c2z)*z'/w' = r2^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c2x^2 - c2y^2 - c2z^2 191 // 2*(c1x - c3x)*x'/w' + 2*(c1y - c3y)*y'/w' + 2*(c1z - c3z)*z'/w' = r3^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c3x^2 - c3y^3 - c3z^2 192 // 2*(c1x - c4x)*x'/w' + 2*(c1y - c4y)*y'/w' + 2*(c1z - c4z)*z'/w' = r4^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c4x^2 - c4y^2 - c4z^2 193 194 // Multiplying by w' at both sides... 195 // 2*(c1x - c2x)*x' + 2*(c1y - c2y)*y' + 2*(c1z - c2z)*z' = (r2^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c2x^2 - c2y^2 - c2z^2)*w' 196 // 2*(c1x - c3x)*x' + 2*(c1y - c3y)*y' + 2*(c1z - c3z)*z' = (r3^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c3x^2 - c3y^3 - c3z^2)*w' 197 // 2*(c1x - c4x)*x' + 2*(c1y - c4y)*y' + 2*(c1z - c4z)*z' = (r4^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c4x^2 - c4y^2 - c4z^2)*w' 198 199 // Obtaining the following homogeneous equations 200 // 2*(c1x - c2x)*x' + 2*(c1y - c2y)*y' + 2*(c1z - c2z)*z' - (r2^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c2x^2 - c2y^2 - c2z^2)*w' = 0 201 // 2*(c1x - c3x)*x' + 2*(c1y - c3y)*y' + 2*(c1z - c3z)*z' - (r3^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c3x^2 - c3y^3 - c3z^2)*w' = 0 202 // 2*(c1x - c4x)*x' + 2*(c1y - c4y)*y' + 2*(c1z - c4z)*z' - (r4^2 - r1^2 + c1x^2 + c1y^2 + c1z^2 - c4x^2 - c4y^2 - c4z^2)*w' = 0 203 204 // Fixing signs... 205 // 2*(c1x - c2x)*x' + 2*(c1y - c2y)*y' + 2*(c1z - c2z)*z' + (r1^2 - r2^2 + c2x^2 + c2y^2 + c2z^2 - c1x^2 - c1y^2 - c1z^2)*w' = 0 206 // 2*(c1x - c3x)*x' + 2*(c1y - c3y)*y' + 2*(c1z - c3z)*z' + (r1^2 - r3^2 + c3x^2 + c3y^3 + c3z^2 - c1x^2 - c1y^2 - c1z^2)*w' = 0 207 // 2*(c1x - c4x)*x' + 2*(c1y - c4y)*y' + 2*(c1z - c4z)*z' + (r1^2 - r4^2 + c4x^2 + c4y^2 + c4z^2 - c1x^2 - c1y^2 - c1z^2)*w' = 0 208 209 // The homogeneous equations can be expressed as a linear system of homogeneous equations 210 // where the unknowns to be solved are (x', y', z', w') up to scale. 211 212 // [2*(c1x - c2x) 2*(c1y - c2y) 2*(c1z - c2z) r1^2 - r2^2 + c2x^2 + c2y^2 + c2z^2 - c1x^2 - c1y^2 - c1z^2][x'] = 0 213 // [2*(c1x - c3x) 2*(c1y - c3y) 2*(c1z - c3z) r1^2 - r3^2 + c3x^2 + c3y^3 + c3z^2 - c1x^2 - c1y^2 - c1z^2][y'] = 0 214 // [2*(c1x - c4x) 2*(c1y - c4y) 2*(c1z - c4z) r1^2 - r4^2 + c4x^2 + c4y^2 + c4z^2 - c1x^2 - c1y^2 - c1z^2][z'] = 0 215 // [w'] 216 217 // This can be solved by using the SVD decomposition of matrix A and picking the last column of 218 // resulting V matrix. At least 3 equations are required to find a solution, since 1 additional 219 // point is used as a reference, at least 4 points are required. 220 221 if (!isReady()) { 222 throw new NotReadyException(); 223 } 224 if (isLocked()) { 225 throw new LockedException(); 226 } 227 228 try { 229 locked = true; 230 231 if (listener != null) { 232 listener.onSolveStart(this); 233 } 234 235 final var numberOfPositions = positions.length; 236 final var numberOfPositionsMinus1 = numberOfPositions - 1; 237 final var dims = getNumberOfDimensions(); 238 239 final var referenceDistance = distances[0]; 240 final var sqrRefDistance = referenceDistance * referenceDistance; 241 final var refPosition = positions[0]; 242 243 var sqrRefNorm = 0.0; 244 for (var j = 0; j < dims; j++) { 245 final var coord = refPosition.getInhomogeneousCoordinate(j); 246 sqrRefNorm += coord * coord; 247 } 248 249 final var a = new Matrix(numberOfPositionsMinus1, dims + 1); 250 for (int i = 1, i2 = 0; i < numberOfPositions; i++, i2++) { 251 252 var sqrNorm = 0.0; 253 for (var j = 0; j < dims; j++) { 254 final var refCoord = refPosition.getInhomogeneousCoordinate(j); 255 final var coord = positions[i].getInhomogeneousCoordinate(j); 256 257 a.setElementAt(i2, j, 2.0 * (refCoord - coord)); 258 259 sqrNorm += coord * coord; 260 } 261 262 final var distance = distances[i]; 263 final var sqrDistance = distance * distance; 264 a.setElementAt(i2, dims, sqrRefDistance - sqrDistance + sqrNorm - sqrRefNorm); 265 } 266 267 final var decomposer = new SingularValueDecomposer(a); 268 decomposer.decompose(); 269 270 final var nullity = decomposer.getNullity(); 271 if (nullity > 1) { 272 // linear system of equations is degenerate (does not have enough rank), 273 // probably because there are dependencies or repeated data between 274 // points 275 throw new LaterationException(); 276 } 277 278 final var v = decomposer.getV(); 279 final var homogeneousEstimatedPositionCoordinates = v.getSubmatrixAsArray( 280 0, dims, dims, dims); 281 282 final var w = homogeneousEstimatedPositionCoordinates[dims]; 283 estimatedPositionCoordinates = new double[dims]; 284 for (var j = 0; j < dims; j++) { 285 estimatedPositionCoordinates[j] = homogeneousEstimatedPositionCoordinates[j] / w; 286 } 287 288 if (listener != null) { 289 listener.onSolveEnd(this); 290 } 291 } catch (final AlgebraException e) { 292 throw new LaterationException(e); 293 } finally { 294 locked = false; 295 } 296 } 297 298 /** 299 * Gets lateration solver type. 300 * 301 * @return lateration solver type. 302 */ 303 @Override 304 public LaterationSolverType getType() { 305 return LaterationSolverType.HOMOGENEOUS_LINEAR_TRILATERATION_SOLVER; 306 } 307 }