These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare( strict_types = 1 ); |
||
4 | |||
5 | namespace DataValues\Geo\Parsers; |
||
6 | |||
7 | use DataValues\Geo\Values\LatLongValue; |
||
8 | use ValueParsers\ParseException; |
||
9 | use ValueParsers\ParserOptions; |
||
10 | |||
11 | /** |
||
12 | * Parser for geographical coordinates in Decimal Degree notation. |
||
13 | * |
||
14 | * @since 0.1 |
||
15 | * |
||
16 | * @license GPL-2.0-or-later |
||
17 | * @author Jeroen De Dauw < [email protected] > |
||
18 | * @author H. Snater < [email protected] > |
||
19 | */ |
||
20 | class DdCoordinateParser extends LatLongParserBase { |
||
21 | |||
22 | /** |
||
23 | * The symbol representing degrees. |
||
24 | * @since 0.1 |
||
25 | */ |
||
26 | public const OPT_DEGREE_SYMBOL = 'degree'; |
||
27 | |||
28 | /** |
||
29 | * @param ParserOptions|null $options |
||
30 | */ |
||
31 | 21 | public function __construct( ParserOptions $options = null ) { |
|
32 | 21 | $options = $options ?: new ParserOptions(); |
|
33 | 21 | $options->defaultOption( self::OPT_DEGREE_SYMBOL, '°' ); |
|
34 | |||
35 | 21 | parent::__construct( $options ); |
|
36 | |||
37 | 21 | $this->defaultDelimiters = [ $this->getOption( self::OPT_DEGREE_SYMBOL ) ]; |
|
0 ignored issues
–
show
|
|||
38 | 21 | } |
|
39 | |||
40 | /** |
||
41 | * @see LatLongParserBase::getParsedCoordinate |
||
42 | * |
||
43 | * @param string $coordinateSegment |
||
44 | * |
||
45 | * @return float |
||
46 | */ |
||
47 | 16 | protected function getParsedCoordinate( string $coordinateSegment ): float { |
|
48 | 16 | $coordinateSegment = $this->resolveDirection( $coordinateSegment ); |
|
49 | 16 | return $this->parseCoordinate( $coordinateSegment ); |
|
50 | } |
||
51 | |||
52 | /** |
||
53 | * @see LatLongParserBase::areValidCoordinates |
||
54 | * |
||
55 | * @param string[] $normalizedCoordinateSegments |
||
56 | * |
||
57 | * @return bool |
||
58 | */ |
||
59 | 18 | protected function areValidCoordinates( array $normalizedCoordinateSegments ): bool { |
|
60 | // TODO: Implement localized decimal separator. |
||
61 | 18 | $baseRegExp = '\d{1,3}(\.\d{1,20})?' . $this->getOption( self::OPT_DEGREE_SYMBOL ); |
|
62 | |||
63 | // Cache whether the coordinates are specified in directional format (a mixture of |
||
64 | // directional and non-directional is regarded invalid). |
||
65 | 18 | $directional = false; |
|
66 | |||
67 | 18 | $match = false; |
|
68 | |||
69 | 18 | foreach ( $normalizedCoordinateSegments as $i => $segment ) { |
|
70 | $direction = '(' |
||
71 | 18 | . $this->getOption( self::OPT_NORTH_SYMBOL ) . '|' |
|
72 | 18 | . $this->getOption( self::OPT_SOUTH_SYMBOL ) . ')'; |
|
73 | |||
74 | 18 | if ( $i === 1 ) { |
|
75 | $direction = '(' |
||
76 | 16 | . $this->getOption( self::OPT_EAST_SYMBOL ) . '|' |
|
77 | 16 | . $this->getOption( self::OPT_WEST_SYMBOL ) . ')'; |
|
78 | } |
||
79 | |||
80 | 18 | $match = preg_match( |
|
81 | 18 | '/^(' . $baseRegExp . $direction . '|' . $direction . $baseRegExp . ')$/i', |
|
82 | $segment |
||
83 | ); |
||
84 | |||
85 | 18 | if ( $directional ) { |
|
86 | // Directionality is only set after parsing latitude: When the latitude is |
||
87 | // is directional, the longitude needs to be as well. Therefore we break here since |
||
88 | // checking for directionality is the only check needed for longitude. |
||
89 | 10 | break; |
|
90 | 18 | } elseif ( $match ) { |
|
91 | // Latitude is directional, no need to check for non-directionality. |
||
92 | 10 | $directional = true; |
|
93 | 10 | continue; |
|
94 | } |
||
95 | |||
96 | 8 | $match = preg_match( '/^(-)?' . $baseRegExp . '$/i', $segment ); |
|
97 | |||
98 | 8 | if ( !$match ) { |
|
99 | // Does neither match directional nor non-directional. |
||
100 | 2 | break; |
|
101 | } |
||
102 | } |
||
103 | |||
104 | 18 | return ( 1 === $match ); |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @see ValueParser::parse |
||
109 | * |
||
110 | * @param string $value |
||
111 | * |
||
112 | * @throws ParseException |
||
113 | * @return LatLongValue |
||
114 | */ |
||
115 | 21 | public function parse( $value ): LatLongValue { |
|
116 | 21 | if ( !is_string( $value ) ) { |
|
117 | 3 | throw new ParseException( 'Not a string' ); |
|
118 | } |
||
119 | |||
120 | 18 | return parent::parse( $this->getNormalizedNotation( $value ) ); |
|
121 | } |
||
122 | |||
123 | /** |
||
124 | * Returns a normalized version of the coordinate string. |
||
125 | * |
||
126 | * @param string $coordinates |
||
127 | * |
||
128 | * @return string |
||
129 | */ |
||
130 | 18 | protected function getNormalizedNotation( string $coordinates ): string { |
|
131 | 18 | $coordinates = str_replace( |
|
132 | 18 | [ '°', '°' ], |
|
133 | 18 | $this->getOption( self::OPT_DEGREE_SYMBOL ), $coordinates |
|
134 | ); |
||
135 | |||
136 | 18 | $coordinates = $this->removeInvalidChars( $coordinates ); |
|
137 | |||
138 | 18 | return $coordinates; |
|
139 | } |
||
140 | |||
141 | /** |
||
142 | * Returns a string with whitespace, control characters and characters with ASCII values above |
||
143 | * 126 removed. |
||
144 | * |
||
145 | * @see LatLongParserBase::removeInvalidChars |
||
146 | * |
||
147 | * @param string $string |
||
148 | * |
||
149 | * @return string |
||
150 | */ |
||
151 | 18 | protected function removeInvalidChars( string $string ): string { |
|
152 | 18 | return str_replace( ' ', '', parent::removeInvalidChars( $string ) ); |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * Converts a coordinate segment to float representation. |
||
157 | * |
||
158 | * @param string $coordinateSegment |
||
159 | * |
||
160 | * @return float |
||
161 | */ |
||
162 | 16 | protected function parseCoordinate( string $coordinateSegment ): float { |
|
163 | 16 | return (float)str_replace( |
|
164 | 16 | $this->getOption( self::OPT_DEGREE_SYMBOL ), |
|
165 | 16 | '', |
|
166 | $coordinateSegment |
||
167 | ); |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * @see LatLongParserBase::splitString |
||
172 | * |
||
173 | * @param string $normalizedCoordinateString |
||
174 | * |
||
175 | * @return string[] |
||
176 | */ |
||
177 | 18 | protected function splitString( string $normalizedCoordinateString ): array { |
|
178 | 18 | $separator = $this->getOption( self::OPT_SEPARATOR_SYMBOL ); |
|
179 | |||
180 | 18 | $normalizedCoordinateSegments = explode( $separator, $normalizedCoordinateString ); |
|
181 | |||
182 | 18 | if ( count( $normalizedCoordinateSegments ) !== 2 ) { |
|
183 | // Separator not present within the string, trying to figure out the segments by |
||
184 | // splitting after the first direction character or degree symbol: |
||
185 | 12 | $delimiters = $this->defaultDelimiters; |
|
186 | |||
187 | $ns = [ |
||
188 | 12 | $this->getOption( self::OPT_NORTH_SYMBOL ), |
|
189 | 12 | $this->getOption( self::OPT_SOUTH_SYMBOL ) |
|
190 | ]; |
||
191 | |||
192 | $ew = [ |
||
193 | 12 | $this->getOption( self::OPT_EAST_SYMBOL ), |
|
194 | 12 | $this->getOption( self::OPT_WEST_SYMBOL ) |
|
195 | ]; |
||
196 | |||
197 | 12 | foreach ( $ns as $delimiter ) { |
|
198 | 12 | if ( mb_strpos( $normalizedCoordinateString, $delimiter ) === 0 ) { |
|
199 | // String starts with "north" or "west" symbol: Separation needs to be done |
||
200 | // before the "east" or "west" symbol. |
||
201 | 2 | $delimiters = array_merge( $ew, $delimiters ); |
|
202 | 2 | break; |
|
203 | } |
||
204 | } |
||
205 | |||
206 | 12 | if ( count( $delimiters ) !== count( $this->defaultDelimiters ) + 2 ) { |
|
207 | 10 | $delimiters = array_merge( $ns, $delimiters ); |
|
208 | } |
||
209 | |||
210 | 12 | foreach ( $delimiters as $delimiter ) { |
|
211 | 12 | $delimiterPos = mb_strpos( $normalizedCoordinateString, $delimiter ); |
|
212 | 12 | if ( $delimiterPos !== false ) { |
|
213 | 10 | $adjustPos = ( in_array( $delimiter, $ew ) ) ? 0 : mb_strlen( $delimiter ); |
|
214 | $normalizedCoordinateSegments = [ |
||
215 | 10 | mb_substr( $normalizedCoordinateString, 0, $delimiterPos + $adjustPos ), |
|
216 | 10 | mb_substr( $normalizedCoordinateString, $delimiterPos + $adjustPos ) |
|
217 | ]; |
||
218 | 10 | break; |
|
219 | } |
||
220 | } |
||
221 | } |
||
222 | |||
223 | 18 | return $normalizedCoordinateSegments; |
|
224 | } |
||
225 | |||
226 | } |
||
227 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..