Passed
Push — master ( 9d3cd0...e94f7b )
by Amir
01:44 queued 11s
created

src/Parsers/DmCoordinateParser.php (1 issue)

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
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 Decimal Minute 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 DmCoordinateParser extends DdCoordinateParser {
20
21
	public const FORMAT_NAME = 'dm-coordinate';
22
23
	/**
24
	 * The symbols representing minutes.
25
	 * @since 0.1
26
	 */
27
	public const OPT_MINUTE_SYMBOL = 'minute';
28
29
	/**
30
	 * @param ParserOptions|null $options
31
	 */
32 21
	public function __construct( ParserOptions $options = null ) {
33 21
		$options = $options ?: new ParserOptions();
34 21
		$options->defaultOption( self::OPT_MINUTE_SYMBOL, "'" );
35
36 21
		parent::__construct( $options );
37
38 21
		$this->defaultDelimiters = [ $this->getOption( self::OPT_MINUTE_SYMBOL ) ];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->getOption(self::OPT_MINUTE_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 21
	}
40
41
	/**
42
	 * @see LatLongParserBase::areValidCoordinates
43
	 *
44
	 * @param string[] $normalizedCoordinateSegments
45
	 *
46
	 * @return bool
47
	 */
48 18
	protected function areValidCoordinates( array $normalizedCoordinateSegments ): bool {
49
		// At least one coordinate segment needs to have minutes specified.
50
		$regExpStrict = '\d{1,3}'
51 18
			. preg_quote( $this->getOption( self::OPT_DEGREE_SYMBOL ) )
52
			// TODO: Implement localized decimal separator.
53 18
			. '(\d{1,2}(\.\d{1,20})?'
54 18
			. preg_quote( $this->getOption( self::OPT_MINUTE_SYMBOL ) )
55 18
			. ')';
56 18
		$regExpLoose = $regExpStrict . '?';
57
58
		// Cache whether minutes have been detected within the coordinate:
59 18
		$detectedMinute = false;
60
61
		// Cache whether the coordinates are specified in directional format (a mixture of
62
		// directional and non-directional is regarded invalid).
63 18
		$directional = false;
64
65 18
		foreach ( $normalizedCoordinateSegments as $i => $segment ) {
66
			$direction = '('
67 18
				. $this->getOption( self::OPT_NORTH_SYMBOL ) . '|'
68 18
				. $this->getOption( self::OPT_SOUTH_SYMBOL ) . ')';
69
70 18
			if ( $i === 1 ) {
71
				$direction = '('
72 16
					. $this->getOption( self::OPT_EAST_SYMBOL ) . '|'
73 16
					. $this->getOption( self::OPT_WEST_SYMBOL ) . ')';
74
			}
75
76 18
			$match = preg_match(
77 18
				'/^(' . $regExpStrict . $direction . '|' . $direction . $regExpStrict . ')$/i',
78
				$segment
79
			);
80
81 18
			if ( $match ) {
82 6
				$detectedMinute = true;
83
			} else {
84 12
				$match = preg_match(
85 12
					'/^(' . $regExpLoose . $direction . '|' . $direction . $regExpLoose . ')$/i',
86
					$segment
87
				);
88
			}
89
90 18
			if ( $match ) {
91 6
				$directional = true;
92 12
			} elseif ( !$directional ) {
93 12
				$match = preg_match( '/^(-)?' . $regExpStrict . '$/i', $segment );
94
95 12
				if ( $match ) {
96 10
					$detectedMinute = true;
97
				} else {
98 2
					$match = preg_match( '/^(-)?' . $regExpLoose . '$/i', $segment );
99
				}
100
			}
101
102 18
			if ( !$match ) {
103 2
				return false;
104
			}
105
		}
106
107 16
		return $detectedMinute;
108
	}
109
110
	/**
111
	 * @see DdCoordinateParser::getNormalizedNotation
112
	 *
113
	 * @param string $coordinates
114
	 *
115
	 * @return string
116
	 */
117 18
	protected function getNormalizedNotation( string $coordinates ): string {
118 18
		$minute = $this->getOption( self::OPT_MINUTE_SYMBOL );
119
120 18
		$coordinates = str_replace( [ '&#8242;', '&prime;', '´', '′' ], $minute, $coordinates );
121
122 18
		$coordinates = parent::getNormalizedNotation( $coordinates );
123
124 18
		$coordinates = $this->removeInvalidChars( $coordinates );
125
126 18
		return $coordinates;
127
	}
128
129
	/**
130
	 * @see DdCoordinateParser::parseCoordinate
131
	 *
132
	 * @param string $coordinateSegment
133
	 *
134
	 * @return float
135
	 */
136 16
	protected function parseCoordinate( string $coordinateSegment ): float {
137 16
		$isNegative = substr( $coordinateSegment, 0, 1 ) === '-';
138
139 16
		if ( $isNegative ) {
140 5
			$coordinateSegment = substr( $coordinateSegment, 1 );
141
		}
142
143 16
		$degreeSymbol = $this->getOption( self::OPT_DEGREE_SYMBOL );
144 16
		$exploded = explode( $degreeSymbol, $coordinateSegment );
145
146 16
		if ( count( $exploded ) !== 2 ) {
147
			throw new ParseException(
148
				'Unable to explode coordinate segment by degree symbol (' . $degreeSymbol . ')',
149
				$coordinateSegment,
150
				self::FORMAT_NAME
151
			);
152
		}
153
154 16
		list( $degrees, $minutes ) = $exploded;
155
156 16
		$minutes = substr( $minutes, 0, -1 );
157
158 16
		$coordinateSegment = $degrees + $minutes / 60;
159
160 16
		if ( $isNegative ) {
161 5
			$coordinateSegment *= -1;
162
		}
163
164 16
		return (float)$coordinateSegment;
165
	}
166
167
}
168