| Total Complexity | 63 |
| Total Lines | 301 |
| Duplicated Lines | 0 % |
| Changes | 8 | ||
| Bugs | 0 | Features | 0 |
Complex classes like AutoConversion often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AutoConversion, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 38 | trait AutoConversion |
||
| 39 | { |
||
| 40 | private int $maxChainDepth = 4; // if traits could have constants... |
||
| 41 | |||
| 42 | private static array $directTransformationPathCache = []; |
||
| 43 | |||
| 44 | private static array $indirectTransformationPathCache = []; |
||
| 45 | |||
| 46 | private static array $transformationsByCRS = []; |
||
| 47 | |||
| 48 | private static array $transformationsByCRSPair = []; |
||
| 49 | |||
| 50 | public function convert(CoordinateReferenceSystem $to, bool $ignoreBoundaryRestrictions = false): Point |
||
| 51 | { |
||
| 52 | if ($this->getCRS() == $to) { |
||
| 53 | return $this; |
||
| 54 | } |
||
| 55 | |||
| 56 | if (strpos($this->getCRS()->getSRID(), CoordinateReferenceSystem::CRS_SRID_PREFIX_EPSG) !== 0 || strpos($to->getSRID(), CoordinateReferenceSystem::CRS_SRID_PREFIX_EPSG) !== 0) { |
||
| 57 | throw new UnknownConversionException('Automatic conversions are only supported for EPSG CRSs'); |
||
| 58 | } |
||
| 59 | |||
| 60 | $point = $this; |
||
| 61 | $path = $this->findOperationPath($this->getCRS(), $to, $ignoreBoundaryRestrictions); |
||
| 62 | |||
| 63 | foreach ($path as $step) { |
||
| 64 | $target = CoordinateReferenceSystem::fromSRID($step['in_reverse'] ? $step['source_crs'] : $step['target_crs']); |
||
| 65 | $point = $point->performOperation($step['operation'], $target, $step['in_reverse']); |
||
| 66 | } |
||
| 67 | |||
| 68 | return $point; |
||
| 69 | } |
||
| 70 | |||
| 71 | protected function findOperationPath(CoordinateReferenceSystem $source, CoordinateReferenceSystem $target, bool $ignoreBoundaryRestrictions): array |
||
| 72 | { |
||
| 73 | self::buildSupportedTransformationsByCRS(); |
||
| 74 | self::buildSupportedTransformationsByCRSPair(); |
||
| 75 | $boundaryCheckPoint = $ignoreBoundaryRestrictions ? null : $this->getPointForBoundaryCheck(); |
||
| 76 | |||
| 77 | // Try simple direct match before doing anything more complex! |
||
| 78 | $candidatePaths = $this->buildDirectTransformationPathsToCRS($source, $target); |
||
| 79 | |||
| 80 | usort($candidatePaths, static function (array $a, array $b) { |
||
| 81 | return count($a['path']) <=> count($b['path']) ?: $a['accuracy'] <=> $b['accuracy']; |
||
| 82 | }); |
||
| 83 | |||
| 84 | foreach ($candidatePaths as $candidatePath) { |
||
| 85 | if ($this->validatePath($candidatePath['path'], $boundaryCheckPoint)) { |
||
| 86 | return $candidatePath['path']; |
||
| 87 | } |
||
| 88 | } |
||
| 89 | |||
| 90 | // Otherwise, recursively calculate permutations of intermediate CRSs |
||
| 91 | $candidatePaths = $this->buildIndirectTransformationPathsToCRS($source, $target); |
||
| 92 | |||
| 93 | usort($candidatePaths, static function (array $a, array $b) { |
||
| 94 | return count($a['path']) <=> count($b['path']) ?: $a['accuracy'] <=> $b['accuracy']; |
||
| 95 | }); |
||
| 96 | |||
| 97 | foreach ($candidatePaths as $candidatePath) { |
||
| 98 | if ($this->validatePath($candidatePath['path'], $boundaryCheckPoint)) { |
||
| 99 | return $candidatePath['path']; |
||
| 100 | } |
||
| 101 | } |
||
| 102 | |||
| 103 | throw new UnknownConversionException('Unable to perform conversion, please file a bug if you think this is incorrect'); |
||
| 104 | } |
||
| 105 | |||
| 106 | protected function validatePath(array $candidatePath, ?GeographicValue $boundaryCheckPoint): bool |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | * Build the set of possible direct paths that lead from the current CRS to the target CRS. |
||
| 162 | */ |
||
| 163 | protected function buildDirectTransformationPathsToCRS(CoordinateReferenceSystem $source, CoordinateReferenceSystem $target): array |
||
| 164 | { |
||
| 165 | $cacheKey = $source->getSRID() . '|' . $target->getSRID(); |
||
| 166 | if (!isset(self::$directTransformationPathCache[$cacheKey])) { |
||
| 167 | $simplePaths = [[$source->getSRID(), $target->getSRID()]]; |
||
| 168 | |||
| 169 | // Expand each CRS->CRS permutation with the various ways of achieving that (can be lots :/) |
||
| 170 | $fullPaths = $this->expandSimplePaths($simplePaths, $source->getSRID(), $target->getSRID()); |
||
| 171 | |||
| 172 | $paths = []; |
||
| 173 | foreach ($fullPaths as $fullPath) { |
||
| 174 | $paths[] = ['path' => $fullPath, 'accuracy' => array_sum(array_column($fullPath, 'accuracy'))]; |
||
| 175 | } |
||
| 176 | |||
| 177 | self::$directTransformationPathCache[$cacheKey] = $paths; |
||
| 178 | } |
||
| 179 | |||
| 180 | return self::$directTransformationPathCache[$cacheKey]; |
||
| 181 | } |
||
| 182 | |||
| 183 | /** |
||
| 184 | * Build the set of possible indirect paths that lead from the current CRS to the target CRS. |
||
| 185 | */ |
||
| 186 | protected function buildIndirectTransformationPathsToCRS(CoordinateReferenceSystem $source, CoordinateReferenceSystem $target): array |
||
| 187 | { |
||
| 188 | $cacheKey = $source->getSRID() . '|' . $target->getSRID(); |
||
| 189 | if (!isset(self::$indirectTransformationPathCache[$cacheKey])) { |
||
| 190 | $simplePaths = []; |
||
| 191 | $visited = []; |
||
| 192 | foreach (CoordinateReferenceSystem::getSupportedSRIDs() as $crs => $name) { |
||
| 193 | $visited[$crs] = false; |
||
| 194 | } |
||
| 195 | $currentPath = []; |
||
| 196 | $this->DFS($source->getSRID(), $target->getSRID(), $visited, $currentPath, $simplePaths); |
||
| 197 | |||
| 198 | // Then expand each CRS->CRS permutation with the various ways of achieving that (can be lots :/) |
||
| 199 | $fullPaths = $this->expandSimplePaths($simplePaths, $source->getSRID(), $target->getSRID()); |
||
| 200 | |||
| 201 | $paths = []; |
||
| 202 | foreach ($fullPaths as $fullPath) { |
||
| 203 | $paths[] = ['path' => $fullPath, 'accuracy' => array_sum(array_column($fullPath, 'accuracy'))]; |
||
| 204 | } |
||
| 205 | |||
| 206 | self::$indirectTransformationPathCache[$cacheKey] = $paths; |
||
| 207 | } |
||
| 208 | |||
| 209 | return self::$indirectTransformationPathCache[$cacheKey]; |
||
| 210 | } |
||
| 211 | |||
| 212 | protected function DFS(string $u, string $v, array &$visited, array &$currentPath, array &$simplePaths): void |
||
| 213 | { |
||
| 214 | $currentPath[] = $u; |
||
| 215 | if ($u === $v) { |
||
| 216 | $simplePaths[] = $currentPath; |
||
| 217 | } else { |
||
| 218 | $visited[$u] = true; |
||
| 219 | if (count($currentPath) <= $this->maxChainDepth) { |
||
| 220 | foreach (static::$transformationsByCRS[$u] as $nextU) { |
||
| 221 | if (!$visited[$nextU]) { |
||
| 222 | $this->DFS($nextU, $v, $visited, $currentPath, $simplePaths); |
||
| 223 | } |
||
| 224 | } |
||
| 225 | } |
||
| 226 | } |
||
| 227 | |||
| 228 | array_pop($currentPath); |
||
| 229 | $visited[$u] = false; |
||
| 230 | } |
||
| 231 | |||
| 232 | protected function expandSimplePaths(array $simplePaths, string $fromSRID, string $toSRID): array |
||
| 233 | { |
||
| 234 | $fullPaths = []; |
||
| 235 | foreach ($simplePaths as $simplePath) { |
||
| 236 | $transformationsToMakePath = [[]]; |
||
| 237 | $from = array_shift($simplePath); |
||
| 238 | assert($from === $fromSRID); |
||
| 239 | do { |
||
| 240 | $to = array_shift($simplePath); |
||
| 241 | $wipTransformationsInPath = []; |
||
| 242 | foreach (static::$transformationsByCRSPair[$from . '|' . $to] ?? [] as $transformation) { |
||
| 243 | foreach ($transformationsToMakePath as $transformationToMakePath) { |
||
| 244 | $wipTransformationsInPath[] = array_merge($transformationToMakePath, [$transformation]); |
||
| 245 | } |
||
| 246 | } |
||
| 247 | |||
| 248 | $transformationsToMakePath = $wipTransformationsInPath; |
||
| 249 | $from = $to; |
||
| 250 | } while (count($simplePath) > 0); |
||
| 251 | assert($to === $toSRID); |
||
| 252 | |||
| 253 | foreach ($transformationsToMakePath as $transformationToMakePath) { |
||
| 254 | $fullPaths[] = $transformationToMakePath; |
||
| 255 | } |
||
| 256 | } |
||
| 257 | |||
| 258 | return $fullPaths; |
||
| 259 | } |
||
| 260 | |||
| 261 | /** |
||
| 262 | * Boundary polygons are defined as WGS84, so theoretically all that needs to happen is |
||
| 263 | * to conversion to WGS84 by calling ->convert(). However, that leads quickly to either circularity |
||
| 264 | * when a conversion is possible, or an exception because not every CRS has a WGS84 transformation |
||
| 265 | * available to it even when chaining. |
||
| 266 | */ |
||
| 267 | protected function getPointForBoundaryCheck(): ?GeographicValue |
||
| 300 | } |
||
| 301 | } |
||
| 302 | } |
||
| 303 | |||
| 304 | protected static function buildSupportedTransformationsByCRS(): void |
||
| 305 | { |
||
| 306 | if (!static::$transformationsByCRS) { |
||
| 307 | foreach (CRSTransformations::getSupportedTransformations() as $transformation) { |
||
| 308 | if (!isset(static::$transformationsByCRS[$transformation['source_crs']][$transformation['target_crs']])) { |
||
| 309 | static::$transformationsByCRS[$transformation['source_crs']][$transformation['target_crs']] = $transformation['target_crs']; |
||
| 310 | } |
||
| 311 | if ($transformation['reversible'] && !isset(static::$transformationsByCRS[$transformation['target_crs']][$transformation['source_crs']])) { |
||
| 312 | static::$transformationsByCRS[$transformation['target_crs']][$transformation['source_crs']] = $transformation['source_crs']; |
||
| 313 | } |
||
| 314 | } |
||
| 315 | } |
||
| 316 | } |
||
| 317 | |||
| 318 | protected static function buildSupportedTransformationsByCRSPair(): void |
||
| 329 | } |
||
| 330 | } |
||
| 331 | } |
||
| 332 | } |
||
| 333 | |||
| 334 | abstract public function getCRS(): CoordinateReferenceSystem; |
||
| 335 | |||
| 336 | abstract public function getCoordinateEpoch(): ?DateTimeImmutable; |
||
| 337 | |||
| 338 | abstract protected function performOperation(string $srid, CoordinateReferenceSystem $to, bool $inReverse): Point; |
||
| 339 | } |
||
| 340 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths