CoordinateFactory::fromString()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 9.1288
c 0
b 0
f 0
cc 5
nc 5
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Location\Factory;
6
7
use InvalidArgumentException;
8
use Location\Coordinate;
9
use Location\Ellipsoid;
10
11
/**
12
 * Coordinate Factory
13
 *
14
 * @author Marcus Jaschen <[email protected]>
15
 */
16
class CoordinateFactory implements GeometryFactoryInterface
17
{
18
    /**
19
     * Creates a Coordinate instance from the given string.
20
     *
21
     * The string is parsed by a regular expression for a known
22
     * format of geographical coordinates.
23
     *
24
     * @param string $string formatted geographical coordinate
25
     * @param Ellipsoid $ellipsoid
26
     *
27
     * @return Coordinate
28
     * @throws InvalidArgumentException
29
     */
30
    public static function fromString(string $string, Ellipsoid $ellipsoid = null): Coordinate
31
    {
32
        $string = self::mergeSecondsToMinutes($string);
33
34
        $result = self::parseDecimalMinutesWithoutCardinalLetters($string, $ellipsoid);
35
36
        if ($result instanceof Coordinate) {
37
            return $result;
38
        }
39
40
        $result = self::parseDecimalMinutesWithCardinalLetters($string, $ellipsoid);
41
42
        if ($result instanceof Coordinate) {
43
            return $result;
44
        }
45
46
        $result = self::parseDecimalDegreesWithoutCardinalLetters($string, $ellipsoid);
47
48
        if ($result instanceof Coordinate) {
49
            return $result;
50
        }
51
52
        $result = self::parseDecimalDegreesWithCardinalLetters($string, $ellipsoid);
53
54
        if ($result instanceof Coordinate) {
55
            return $result;
56
        }
57
58
        throw new InvalidArgumentException('Format of coordinates was not recognized');
59
    }
60
61
    /**
62
     * @param string $string
63
     * @param Ellipsoid $ellipsoid
64
     *
65
     * @return Coordinate|null
66
     * @throws InvalidArgumentException
67
     */
68
    private static function parseDecimalMinutesWithoutCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
69
    {
70
        // Decimal minutes without cardinal letters, e. g. "52 12.345, 13 23.456",
71
        // "52° 12.345, 13° 23.456", "52° 12.345′, 13° 23.456′", "52 12.345 N, 13 23.456 E",
72
        // "N52° 12.345′ E13° 23.456′"
73
        $regexp = '/(-?\d{1,2})°?\s+(\d{1,2}\.?\d*)[\'′]?[, ]\s*(-?\d{1,3})°?\s+(\d{1,2}\.?\d*)[\'′]?/u';
74
75
        if (preg_match($regexp, $string, $match) === 1) {
76
            $latitude  = (int)$match[1] >= 0
77
                ? (int)$match[1] + (float)$match[2] / 60
78
                : (int)$match[1] - (float)$match[2] / 60;
79
            $longitude = (int)$match[3] >= 0
80
                ? (int)$match[3] + (float)$match[4] / 60
81
                : (int)$match[3] - (float)$match[4] / 60;
82
83
            return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
84
        }
85
86
        return null;
87
    }
88
89
    /**
90
     * @param string $string
91
     * @param Ellipsoid $ellipsoid
92
     *
93
     * @return Coordinate|null
94
     * @throws InvalidArgumentException
95
     */
96
    private static function parseDecimalMinutesWithCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
97
    {
98
        // Decimal minutes with cardinal letters, e. g. "52 12.345, 13 23.456",
99
        // "52° 12.345, 13° 23.456", "52° 12.345′, 13° 23.456′", "52 12.345 N, 13 23.456 E",
100
        // "N52° 12.345′ E13° 23.456′"
101
        $regexp = '/([NS]?\s*)(\d{1,2})°?\s+(\d{1,2}\.?\d*)[\'′]?(\s*[NS]?)';
102
        $regexp .= '[, ]\s*([EW]?\s*)(\d{1,3})°?\s+(\d{1,2}\.?\d*)[\'′]?(\s*[EW]?)/ui';
103
104
        if (preg_match($regexp, $string, $match) === 1) {
105
            $latitude = (int)$match[2] + (float)$match[3] / 60;
106 View Code Duplication
            if (strtoupper(trim($match[1])) === 'S' || strtoupper(trim($match[4])) === 'S') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
                $latitude = - $latitude;
108
            }
109
            $longitude = (int)$match[6] + (float)$match[7] / 60;
110 View Code Duplication
            if (strtoupper(trim($match[5])) === 'W' || strtoupper(trim($match[8])) === 'W') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
                $longitude = - $longitude;
112
            }
113
114
            return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
115
        }
116
117
        return null;
118
    }
119
120
    /**
121
     * @param string $string
122
     * @param Ellipsoid $ellipsoid
123
     *
124
     * @return Coordinate|null
125
     * @throws InvalidArgumentException
126
     */
127
    private static function parseDecimalDegreesWithoutCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
128
    {
129
        // The most simple format: decimal degrees without cardinal letters,
130
        // e. g. "52.5, 13.5" or "53.25732 14.24984"
131
        if (preg_match('/(-?\d{1,2}\.?\d*)°?[, ]\s*(-?\d{1,3}\.?\d*)°?/u', $string, $match) === 1) {
132
            return new Coordinate((float)$match[1], (float)$match[2], $ellipsoid);
133
        }
134
135
        return null;
136
    }
137
138
    /**
139
     * @param string $string
140
     * @param Ellipsoid $ellipsoid
141
     *
142
     * @return Coordinate|null
143
     * @throws InvalidArgumentException
144
     */
145
    private static function parseDecimalDegreesWithCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
146
    {
147
        // Decimal degrees with cardinal letters, e. g. "N52.5, E13.5",
148
        // "40.2S, 135.3485W", or "56.234°N, 157.245°W"
149
        $regexp = '/([NS]?\s*)(\d{1,2}\.?\d*)°?(\s*[NS]?)[, ]\s*([EW]?\s*)(\d{1,3}\.?\d*)°?(\s*[EW]?)/ui';
150
151
        if (preg_match($regexp, $string, $match) === 1) {
152
            $latitude = $match[2];
153 View Code Duplication
            if (strtoupper(trim($match[1])) === 'S' || strtoupper(trim($match[3])) === 'S') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
                $latitude = - $latitude;
155
            }
156
            $longitude = $match[5];
157 View Code Duplication
            if (strtoupper(trim($match[4])) === 'W' || strtoupper(trim($match[6])) === 'W') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158
                $longitude = - $longitude;
159
            }
160
161
            return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
162
        }
163
164
        return null;
165
    }
166
167
    /**
168
     * @param string $string
169
     *
170
     * @return string
171
     */
172
    private static function mergeSecondsToMinutes(string $string): string
173
    {
174
        return preg_replace_callback(
175
            '/(\d+)(°|\s)\s*(\d+)(\'|′|\s)(\s*([0-9\.]*))("|\'\'|″|′′)?/u',
176
            static function (array $matches): string {
177
                return sprintf('%d %f', $matches[1], $matches[3] + (float)$matches[6] / 60);
178
            },
179
            $string
180
        );
181
    }
182
}
183