Passed
Push — php73 ( 4b71d8 )
by Jeroen De
05:36
created

DmsCoordinateParser::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace DataValues\Geo\Parsers;
6
7
use ValueParsers\ParseException;
8
use ValueParsers\ParserOptions;
9
10
/**
11
 * Parser for geographical coordinates in Degree Minute Second notation.
12
 *
13
 * @since 0.1
14
 *
15
 * @license GPL-2.0-or-later
16
 * @author Jeroen De Dauw < [email protected] >
17
 * @author H. Snater < [email protected] >
18
 */
19
class DmsCoordinateParser extends DmCoordinateParser {
20
21
	public const FORMAT_NAME = 'dms-coordinate';
22
23
	/**
24
	 * The symbol representing seconds.
25
	 * @since 0.1
26
	 */
27
	public const OPT_SECOND_SYMBOL = 'second';
28
29
	/**
30
	 * @param ParserOptions|null $options
31
	 */
32 25
	public function __construct( ParserOptions $options = null ) {
33 25
		$options = $options ?: new ParserOptions();
34 25
		$options->defaultOption( self::OPT_SECOND_SYMBOL, '"' );
35
36 25
		parent::__construct( $options );
37
38 25
		$this->defaultDelimiters = [ $this->getOption( self::OPT_SECOND_SYMBOL ) ];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->getOption(self::OPT_SECOND_SYMBOL)) of type array<integer,*,{"0":"*"}> is incompatible with the declared type array<integer,string> of property $defaultDelimiters.

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..

Loading history...
39 25
	}
40
41
	/**
42
	 * @see LatLongParserBase::areValidCoordinates
43
	 *
44
	 * @param string[] $normalizedCoordinateSegments
45
	 *
46
	 * @return bool
47
	 */
48 22
	protected function areValidCoordinates( array $normalizedCoordinateSegments ): bool {
49
		// At least one coordinate segment needs to have seconds specified (which additionally
50
		// requires minutes to be specified).
51
		$regExpLoose = '(\d{1,3}'
52 22
			. preg_quote( $this->getOption( self::OPT_DEGREE_SYMBOL ) )
53 22
			. ')(\d{1,2}'
54 22
			. preg_quote( $this->getOption( self::OPT_MINUTE_SYMBOL ) )
55 22
			. ')?((\d{1,2}'
56 22
			. preg_quote( $this->getOption( self::OPT_SECOND_SYMBOL ) )
57
			// TODO: Implement localized decimal separator.
58 22
			. ')?|(\d{1,2}\.\d{1,20}'
59 22
			. preg_quote( $this->getOption( self::OPT_SECOND_SYMBOL ) )
60 22
			. ')?)';
61 22
		$regExpStrict = str_replace( '?', '', $regExpLoose );
62
63
		// Cache whether seconds have been detected within the coordinate:
64 22
		$detectedSecond = false;
65
66
		// Cache whether the coordinates are specified in directional format (a mixture of
67
		// directional and non-directional is regarded invalid).
68 22
		$directional = false;
69
70 22
		foreach ( $normalizedCoordinateSegments as $i => $segment ) {
71
			$direction = '('
72 22
				. $this->getOption( self::OPT_NORTH_SYMBOL ) . '|'
73 22
				. $this->getOption( self::OPT_SOUTH_SYMBOL ) . ')';
74
75 22
			if ( $i === 1 ) {
76
				$direction = '('
77 20
					. $this->getOption( self::OPT_EAST_SYMBOL ) . '|'
78 20
					. $this->getOption( self::OPT_WEST_SYMBOL ) . ')';
79
			}
80
81 22
			$match = preg_match(
82 22
				'/^(' . $regExpStrict . $direction . '|' . $direction . $regExpStrict . ')$/i',
83
				$segment
84
			);
85
86 22
			if ( $match ) {
87 7
				$detectedSecond = true;
88
			} else {
89 16
				$match = preg_match(
90 16
					'/^(' . $regExpLoose . $direction . '|' . $direction . $regExpLoose . ')$/i',
91
					$segment
92
				);
93
			}
94
95 22
			if ( $match ) {
96 7
				$directional = true;
97 15
			} elseif ( !$directional ) {
98 15
				$match = preg_match( '/^(-)?' . $regExpStrict . '$/i', $segment );
99
100 15
				if ( $match ) {
101 13
					$detectedSecond = true;
102
				} else {
103 2
					$match = preg_match( '/^(-)?' . $regExpLoose . '$/i', $segment );
104
				}
105
			}
106
107 22
			if ( !$match ) {
108 2
				return false;
109
			}
110
		}
111
112 20
		return $detectedSecond;
113
	}
114
115
	/**
116
	 * @see DdCoordinateParser::getNormalizedNotation
117
	 *
118
	 * @param string $coordinates
119
	 *
120
	 * @return string
121
	 */
122 22
	protected function getNormalizedNotation( string $coordinates ): string {
123 22
		$second = $this->getOption( self::OPT_SECOND_SYMBOL );
124 22
		$minute = $this->getOption( self::OPT_MINUTE_SYMBOL );
125
126 22
		$coordinates = str_replace(
127 22
			[ '&#8243;', '&Prime;', $minute . $minute, '´´', '′′', '″' ],
128
			$second,
129
			$coordinates
130
		);
131 22
		$coordinates = str_replace( [ '&acute;', '&#180;' ], $second, $coordinates );
132
133 22
		$coordinates = parent::getNormalizedNotation( $coordinates );
134
135 22
		$coordinates = $this->removeInvalidChars( $coordinates );
136
137 22
		return $coordinates;
138
	}
139
140
	/**
141
	 * @see DdCoordinateParser::parseCoordinate
142
	 *
143
	 * @param string $coordinateSegment
144
	 *
145
	 * @return float
146
	 */
147 20
	protected function parseCoordinate( string $coordinateSegment ): float {
148 20
		$isNegative = mb_substr( $coordinateSegment, 0, 1 ) === '-';
149
150 20
		if ( $isNegative ) {
151 7
			$coordinateSegment = mb_substr( $coordinateSegment, 1 );
152
		}
153
154 20
		$degreeSymbol = $this->getOption( self::OPT_DEGREE_SYMBOL );
155 20
		$degreePosition = mb_strpos( $coordinateSegment, $degreeSymbol );
156
157 20
		if ( $degreePosition === false ) {
158
			throw new ParseException(
159
				'Did not find degree symbol (' . $degreeSymbol . ')',
160
				$coordinateSegment,
161
				self::FORMAT_NAME
162
			);
163
		}
164
165 20
		$degrees = (float)mb_substr( $coordinateSegment, 0, $degreePosition );
166
167 20
		$minutePosition = mb_strpos( $coordinateSegment, $this->getOption( self::OPT_MINUTE_SYMBOL ) );
168
169 20
		if ( $minutePosition === false ) {
170 1
			$minutes = 0;
171
		} else {
172 20
			$degSignLength = mb_strlen( $this->getOption( self::OPT_DEGREE_SYMBOL ) );
173 20
			$minuteLength = $minutePosition - $degreePosition - $degSignLength;
174 20
			$minutes = (float)mb_substr( $coordinateSegment, $degreePosition + $degSignLength, $minuteLength );
175
		}
176
177 20
		$secondPosition = mb_strpos( $coordinateSegment, $this->getOption( self::OPT_SECOND_SYMBOL ) );
178
179 20
		if ( $secondPosition === false ) {
180
			$seconds = 0;
181
		} else {
182 20
			$seconds = (float)mb_substr(
183 20
				$coordinateSegment,
184 20
				( $minutePosition === false ? $degreePosition : $minutePosition ) + 1,
185 20
				-1
186
			);
187
		}
188
189 20
		$coordinateSegment = $degrees + ( $minutes + $seconds / 60 ) / 60;
190
191 20
		if ( $isNegative ) {
192 7
			$coordinateSegment *= -1;
193
		}
194
195 20
		return (float)$coordinateSegment;
196
	}
197
198
}
199