Completed
Push — cleanup/small-fixes ( a08300 )
by Marcus
01:18
created

src/Factory/CoordinateFactory.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
declare(strict_types=1);
3
4
namespace Location\Factory;
5
6
use Location\Coordinate;
7
use Location\Ellipsoid;
8
9
/**
10
 * Coordinate Factory
11
 *
12
 * @author Marcus Jaschen <[email protected]>
13
 */
14
class CoordinateFactory implements GeometryFactoryInterface
15
{
16
    /**
17
     * Creates a Coordinate instance from the given string.
18
     *
19
     * The string is parsed by a regular expression for a known
20
     * format of geographical coordinates.
21
     *
22
     * @param string $string formatted geographical coordinate
23
     * @param \Location\Ellipsoid $ellipsoid
24
     *
25
     * @return Coordinate
26
     * @throws \InvalidArgumentException
27
     */
28
    public static function fromString(string $string, Ellipsoid $ellipsoid = null): Coordinate
29
    {
30
        $string = self::mergeSecondsToMinutes($string);
31
32
        $result = self::parseDecimalMinutesWithoutCardinalLetters($string, $ellipsoid);
33
34
        if ($result instanceof Coordinate) {
35
            return $result;
36
        }
37
38
        $result = self::parseDecimalMinutesWithCardinalLetters($string, $ellipsoid);
39
40
        if ($result instanceof Coordinate) {
41
            return $result;
42
        }
43
44
        $result = self::parseDecimalDegreesWithoutCardinalLetters($string, $ellipsoid);
45
46
        if ($result instanceof Coordinate) {
47
            return $result;
48
        }
49
50
        $result = self::parseDecimalDegreesWithCardinalLetters($string, $ellipsoid);
51
52
        if ($result instanceof Coordinate) {
53
            return $result;
54
        }
55
56
        throw new \InvalidArgumentException('Format of coordinates was not recognized');
57
    }
58
59
    /**
60
     * @param string $string
61
     * @param Ellipsoid $ellipsoid
62
     *
63
     * @return Coordinate|null
64
     * @throws \InvalidArgumentException
65
     */
66
    private static function parseDecimalMinutesWithoutCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
67
    {
68
        // Decimal minutes without cardinal letters, e. g. "52 12.345, 13 23.456",
69
        // "52° 12.345, 13° 23.456", "52° 12.345′, 13° 23.456′", "52 12.345 N, 13 23.456 E",
70
        // "N52° 12.345′ E13° 23.456′"
71
        if (preg_match(
72
            '/(-?\d{1,2})°?\s+(\d{1,2}\.?\d*)[\'′]?[, ]\s*(-?\d{1,3})°?\s+(\d{1,2}\.?\d*)[\'′]?/ui',
73
            $string,
74
            $match
75
        )
76
        ) {
77
            $latitude  = (int)$match[1] >= 0 ? (int)$match[1] + (float)$match[2] / 60 : (int)$match[1] - (float)$match[2] / 60;
78
            $longitude = (int)$match[3] >= 0 ? (int)$match[3] + (float)$match[4] / 60 : (int)$match[3] - (float)$match[4] / 60;
79
80
            return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
81
        }
82
83
        return null;
84
    }
85
86
    /**
87
     * @param string $string
88
     * @param Ellipsoid $ellipsoid
89
     *
90
     * @return Coordinate|null
91
     * @throws \InvalidArgumentException
92
     */
93
    private static function parseDecimalMinutesWithCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
94
    {
95
        // Decimal minutes with cardinal letters, e. g. "52 12.345, 13 23.456",
96
        // "52° 12.345, 13° 23.456", "52° 12.345′, 13° 23.456′", "52 12.345 N, 13 23.456 E",
97
        // "N52° 12.345′ E13° 23.456′"
98
        if (preg_match(
99
            '/([NS]?\s*)(\d{1,2})°?\s+(\d{1,2}\.?\d*)[\'′]?(\s*[NS]?)[, ]\s*([EW]?\s*)(\d{1,3})°?\s+(\d{1,2}\.?\d*)[\'′]?(\s*[EW]?)/ui',
100
            $string,
101
            $match
102
        )
103
        ) {
104
            $latitude = (int)$match[2] + (float)$match[3] / 60;
105 View Code Duplication
            if (strtoupper(trim($match[1])) === 'S' || strtoupper(trim($match[4])) === 'S') {
0 ignored issues
show
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...
106
                $latitude = - $latitude;
107
            }
108
            $longitude = (int)$match[6] + (float)$match[7] / 60;
109 View Code Duplication
            if (strtoupper(trim($match[5])) === 'W' || strtoupper(trim($match[8])) === 'W') {
0 ignored issues
show
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...
110
                $longitude = - $longitude;
111
            }
112
113
            return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
114
        }
115
116
        return null;
117
    }
118
119
    /**
120
     * @param string $string
121
     * @param Ellipsoid $ellipsoid
122
     *
123
     * @return Coordinate|null
124
     * @throws \InvalidArgumentException
125
     */
126
    private static function parseDecimalDegreesWithoutCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
127
    {
128
        // The most simple format: decimal degrees without cardinal letters,
129
        // e. g. "52.5, 13.5" or "53.25732 14.24984"
130
        if (preg_match('/(-?\d{1,2}\.?\d*)°?[, ]\s*(-?\d{1,3}\.?\d*)°?/u', $string, $match)) {
131
            return new Coordinate((float)$match[1], (float)$match[2], $ellipsoid);
132
        }
133
134
        return null;
135
    }
136
137
    /**
138
     * @param string $string
139
     * @param Ellipsoid $ellipsoid
140
     *
141
     * @return Coordinate|null
142
     * @throws \InvalidArgumentException
143
     */
144
    private static function parseDecimalDegreesWithCardinalLetters(string $string, Ellipsoid $ellipsoid = null)
145
    {
146
        // Decimal degrees with cardinal letters, e. g. "N52.5, E13.5",
147
        // "40.2S, 135.3485W", or "56.234°N, 157.245°W"
148
        if (preg_match(
149
            '/([NS]?\s*)(\d{1,2}\.?\d*)°?(\s*[NS]?)[, ]\s*([EW]?\s*)(\d{1,3}\.?\d*)°?(\s*[EW]?)/ui',
150
            $string,
151
            $match
152
        )
153
        ) {
154
            $latitude = $match[2];
155 View Code Duplication
            if (strtoupper(trim($match[1])) === 'S' || strtoupper(trim($match[3])) === 'S') {
0 ignored issues
show
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...
156
                $latitude = - $latitude;
157
            }
158
            $longitude = $match[5];
159 View Code Duplication
            if (strtoupper(trim($match[4])) === 'W' || strtoupper(trim($match[6])) === 'W') {
0 ignored issues
show
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...
160
                $longitude = - $longitude;
161
            }
162
163
            return new Coordinate((float)$latitude, (float)$longitude, $ellipsoid);
164
        }
165
166
        return null;
167
    }
168
169
    /**
170
     * @param string $string
171
     *
172
     * @return string
173
     */
174
    private static function mergeSecondsToMinutes(string $string): string
175
    {
176
        return preg_replace_callback(
177
            '/(\d+)(°|\s)\s*(\d+)(\'|′|\s)(\s*([0-9\.]*))("|\'\'|″|′′)?/u',
178
            function (array $matches): string {
179
                return sprintf('%d %f', $matches[1], $matches[3] + $matches[6] / 60);
180
            },
181
            $string
182
        );
183
    }
184
}
185