Completed
Push — master ( 82e43e...e1ae07 )
by Jeroen De
02:37
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
namespace DataValues\Geo\Parsers;
4
5
use ValueParsers\ParseException;
6
use ValueParsers\ParserOptions;
7
8
/**
9
 * Parser for geographical coordinates in Degree Minute Second notation.
10
 *
11
 * @since 0.1
12
 *
13
 * @license GPL-2.0-or-later
14
 * @author Jeroen De Dauw < [email protected] >
15
 * @author H. Snater < [email protected] >
16
 */
17
class DmsCoordinateParser extends DmCoordinateParser {
18
19
	public const FORMAT_NAME = 'dms-coordinate';
20
21
	/**
22
	 * The symbol representing seconds.
23
	 * @since 0.1
24
	 */
25
	public const OPT_SECOND_SYMBOL = 'second';
26
27
	/**
28
	 * @param ParserOptions|null $options
29
	 */
30 25
	public function __construct( ParserOptions $options = null ) {
31 25
		$options = $options ?: new ParserOptions();
32 25
		$options->defaultOption( self::OPT_SECOND_SYMBOL, '"' );
33
34 25
		parent::__construct( $options );
35
36 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...
37 25
	}
38
39
	/**
40
	 * @see LatLongParserBase::areValidCoordinates
41
	 *
42
	 * @param string[] $normalizedCoordinateSegments
43
	 *
44
	 * @return bool
45
	 */
46 22
	protected function areValidCoordinates( array $normalizedCoordinateSegments ): bool {
47
		// At least one coordinate segment needs to have seconds specified (which additionally
48
		// requires minutes to be specified).
49
		$regExpLoose = '(\d{1,3}'
50 22
			. preg_quote( $this->getOption( self::OPT_DEGREE_SYMBOL ) )
51 22
			. ')(\d{1,2}'
52 22
			. preg_quote( $this->getOption( self::OPT_MINUTE_SYMBOL ) )
53 22
			. ')?((\d{1,2}'
54 22
			. preg_quote( $this->getOption( self::OPT_SECOND_SYMBOL ) )
55
			// TODO: Implement localized decimal separator.
56 22
			. ')?|(\d{1,2}\.\d{1,20}'
57 22
			. preg_quote( $this->getOption( self::OPT_SECOND_SYMBOL ) )
58 22
			. ')?)';
59 22
		$regExpStrict = str_replace( '?', '', $regExpLoose );
60
61
		// Cache whether seconds have been detected within the coordinate:
62 22
		$detectedSecond = false;
63
64
		// Cache whether the coordinates are specified in directional format (a mixture of
65
		// directional and non-directional is regarded invalid).
66 22
		$directional = false;
67
68 22
		foreach ( $normalizedCoordinateSegments as $i => $segment ) {
69
			$direction = '('
70 22
				. $this->getOption( self::OPT_NORTH_SYMBOL ) . '|'
71 22
				. $this->getOption( self::OPT_SOUTH_SYMBOL ) . ')';
72
73 22
			if ( $i === 1 ) {
74
				$direction = '('
75 20
					. $this->getOption( self::OPT_EAST_SYMBOL ) . '|'
76 20
					. $this->getOption( self::OPT_WEST_SYMBOL ) . ')';
77
			}
78
79 22
			$match = preg_match(
80 22
				'/^(' . $regExpStrict . $direction . '|' . $direction . $regExpStrict . ')$/i',
81 22
				$segment
82
			);
83
84 22
			if ( $match ) {
85 7
				$detectedSecond = true;
86
			} else {
87 16
				$match = preg_match(
88 16
					'/^(' . $regExpLoose . $direction . '|' . $direction . $regExpLoose . ')$/i',
89 16
					$segment
90
				);
91
			}
92
93 22
			if ( $match ) {
94 7
				$directional = true;
95 15
			} elseif ( !$directional ) {
96 15
				$match = preg_match( '/^(-)?' . $regExpStrict . '$/i', $segment );
97
98 15
				if ( $match ) {
99 13
					$detectedSecond = true;
100
				} else {
101 2
					$match = preg_match( '/^(-)?' . $regExpLoose . '$/i', $segment );
102
				}
103
			}
104
105 22
			if ( !$match ) {
106 22
				return false;
107
			}
108
		}
109
110 20
		return $detectedSecond;
111
	}
112
113
	/**
114
	 * @see DdCoordinateParser::getNormalizedNotation
115
	 *
116
	 * @param string $coordinates
117
	 *
118
	 * @return string
119
	 */
120 22
	protected function getNormalizedNotation( string $coordinates ): string {
121 22
		$second = $this->getOption( self::OPT_SECOND_SYMBOL );
122 22
		$minute = $this->getOption( self::OPT_MINUTE_SYMBOL );
123
124 22
		$coordinates = str_replace(
125 22
			[ '&#8243;', '&Prime;', $minute . $minute, '´´', '′′', '″' ],
126 22
			$second,
127 22
			$coordinates
128
		);
129 22
		$coordinates = str_replace( [ '&acute;', '&#180;' ], $second, $coordinates );
130
131 22
		$coordinates = parent::getNormalizedNotation( $coordinates );
132
133 22
		$coordinates = $this->removeInvalidChars( $coordinates );
134
135 22
		return $coordinates;
136
	}
137
138
	/**
139
	 * @see DdCoordinateParser::parseCoordinate
140
	 *
141
	 * @param string $coordinateSegment
142
	 *
143
	 * @return float
144
	 */
145 20
	protected function parseCoordinate( string $coordinateSegment ): float {
146 20
		$isNegative = mb_substr( $coordinateSegment, 0, 1 ) === '-';
147
148 20
		if ( $isNegative ) {
149 7
			$coordinateSegment = mb_substr( $coordinateSegment, 1 );
150
		}
151
152 20
		$degreeSymbol = $this->getOption( self::OPT_DEGREE_SYMBOL );
153 20
		$degreePosition = mb_strpos( $coordinateSegment, $degreeSymbol );
154
155 20
		if ( $degreePosition === false ) {
156
			throw new ParseException(
157
				'Did not find degree symbol (' . $degreeSymbol . ')',
158
				$coordinateSegment,
159
				self::FORMAT_NAME
160
			);
161
		}
162
163 20
		$degrees = (float)mb_substr( $coordinateSegment, 0, $degreePosition );
164
165 20
		$minutePosition = mb_strpos( $coordinateSegment, $this->getOption( self::OPT_MINUTE_SYMBOL ) );
166
167 20
		if ( $minutePosition === false ) {
168 1
			$minutes = 0;
169
		} else {
170 20
			$degSignLength = mb_strlen( $this->getOption( self::OPT_DEGREE_SYMBOL ) );
171 20
			$minuteLength = $minutePosition - $degreePosition - $degSignLength;
172 20
			$minutes = (float)mb_substr( $coordinateSegment, $degreePosition + $degSignLength, $minuteLength );
173
		}
174
175 20
		$secondPosition = mb_strpos( $coordinateSegment, $this->getOption( self::OPT_SECOND_SYMBOL ) );
176
177 20
		if ( $secondPosition === false ) {
178
			$seconds = 0;
179
		} else {
180 20
			$seconds = (float)mb_substr(
181 20
				$coordinateSegment,
182 20
				( $minutePosition === false ? $degreePosition : $minutePosition ) + 1,
183 20
				-1
184
			);
185
		}
186
187 20
		$coordinateSegment = $degrees + ( $minutes + $seconds / 60 ) / 60;
188
189 20
		if ( $isNegative ) {
190 7
			$coordinateSegment *= -1;
191
		}
192
193 20
		return (float)$coordinateSegment;
194
	}
195
196
}
197