Passed
Push — master ( 954049...2e99dd )
by Leszek
07:28
created

DmsCoordinateParser::getNormalizedNotation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

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