Daniel Werner has submitted this change and it was merged. Change subject: Added DmCoordinateParser ......................................................................
Added DmCoordinateParser Change-Id: Icef0d92110a62f535048e079a18920d4c1b0b554 --- M ValueParsers/ValueParsers.classes.php M ValueParsers/ValueParsers.mw.php A ValueParsers/includes/parsers/DmCoordinateParser.php A ValueParsers/tests/includes/parsers/DmCoordinateParserTest.php 4 files changed, 341 insertions(+), 0 deletions(-) Approvals: Daniel Werner: Verified; Looks good to me, approved jenkins-bot: Verified diff --git a/ValueParsers/ValueParsers.classes.php b/ValueParsers/ValueParsers.classes.php index a93708b..922b140 100644 --- a/ValueParsers/ValueParsers.classes.php +++ b/ValueParsers/ValueParsers.classes.php @@ -37,6 +37,7 @@ 'ValueParsers\ApiParseValue' => 'includes/api/ApiParseValue.php', 'ValueParsers\BoolParser' => 'includes/parsers/BoolParser.php', + 'ValueParsers\DmCoordinateParser' => 'includes/parsers/DmCoordinateParser.php', 'ValueParsers\DmsCoordinateParser' => 'includes/parsers/DmsCoordinateParser.php', 'ValueParsers\FloatCoordinateParser' => 'includes/parsers/FloatCoordinateParser.php', 'ValueParsers\GeoCoordinateParser' => 'includes/parsers/GeoCoordinateParser.php', diff --git a/ValueParsers/ValueParsers.mw.php b/ValueParsers/ValueParsers.mw.php index 3fdf2da..1efbd51 100644 --- a/ValueParsers/ValueParsers.mw.php +++ b/ValueParsers/ValueParsers.mw.php @@ -69,6 +69,7 @@ 'includes/api/ApiParseValue', 'includes/parsers/BoolParser', + 'includes/parsers/DmCoordinateParser', 'includes/parsers/DmsCoordinateParser', 'includes/parsers/FloatCoordinateParser', 'includes/parsers/GeoCoordinateParser', diff --git a/ValueParsers/includes/parsers/DmCoordinateParser.php b/ValueParsers/includes/parsers/DmCoordinateParser.php new file mode 100644 index 0000000..ac4e80e --- /dev/null +++ b/ValueParsers/includes/parsers/DmCoordinateParser.php @@ -0,0 +1,245 @@ +<?php + +namespace ValueParsers; + +use DataValues\GeoCoordinateValue; +use LogicException; + +/** + * Parser for geographical coordinates in Decimal Minute notation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @since 0.1 + * + * @file + * @ingroup ValueParsers + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroended...@gmail.com > + */ +class DmCoordinateParser extends StringValueParser { + + /** + * The symbols representing the different directions for usage in directional notation. + * @since 0.1 + */ + const OPT_NORTH_SYMBOL = 'north'; + const OPT_EAST_SYMBOL = 'east'; + const OPT_SOUTH_SYMBOL = 'south'; + const OPT_WEST_SYMBOL = 'west'; + + /** + * The symbols representing degrees, minutes and seconds. + * @since 0.1 + */ + const OPT_DEGREE_SYMBOL = 'degree'; + const OPT_MINUTE_SYMBOL = 'minute'; + + /** + * The symbol to use as separator between latitude and longitude. + * @since 0.1 + */ + const OPT_SEPARATOR_SYMBOL = 'separator'; + + /** + * @since 0.1 + * + * @param ParserOptions|null $options + */ + public function __construct( ParserOptions $options = null ) { + parent::__construct( $options ); + + $this->defaultOption( self::OPT_NORTH_SYMBOL, 'N' ); + $this->defaultOption( self::OPT_EAST_SYMBOL, 'E' ); + $this->defaultOption( self::OPT_SOUTH_SYMBOL, 'S' ); + $this->defaultOption( self::OPT_WEST_SYMBOL, 'W' ); + + $this->defaultOption( self::OPT_DEGREE_SYMBOL, '°' ); + $this->defaultOption( self::OPT_MINUTE_SYMBOL, "'" ); + + $this->defaultOption( self::OPT_SEPARATOR_SYMBOL, ',' ); + } + + /** + * @see StringValueParser::stringParse + * + * @since 0.1 + * + * @param string $value + * + * @return GeoCoordinateValue + * @throws ParseException + * @throws LogicException + */ + protected function stringParse( $value ) { + $value = $this->getNormalizedNotation( $value ); + + if ( !$this->areDMCoordinates( $value ) ) { + throw new ParseException( 'Not a geographical coordinate in DM format' ); + } + + $coordinates = explode( $this->getOption( self::OPT_SEPARATOR_SYMBOL ), $value ); + + if ( count( $coordinates ) !== 2 ) { + throw new LogicException( 'A coordinates string with an incorrect segment count has made it through validation' ); + } + + list( $latitude, $longitude ) = $coordinates; + + $latitude = $this->getParsedCoordinate( $latitude ); + $longitude = $this->getParsedCoordinate( $longitude ); + + return new GeoCoordinateValue( $latitude, $longitude ); + } + + /** + * Parsers a single coordinate (either latitude or longitude) and returns it as a float. + * + * @since 0.1 + * + * @param string $coordinate + * + * @return float + */ + protected function getParsedCoordinate( $coordinate ) { + $coordinate = $this->resolveDirection( $coordinate ); + return $this->parseDMCoordinate( $coordinate ); + } + + /** + * Turns directional notation (N/E/S/W) of a single coordinate into non-directional notation (+/-). + * This method assumes there are no preceding or tailing spaces. + * + * @since 0.1 + * + * @param string $coordinate + * + * @return string + */ + protected function resolveDirection( $coordinate ) { + // Get the last char, which could be a direction indicator + $lastChar = strtoupper( substr( $coordinate, -1 ) ); + + $n = $this->getOption( self::OPT_NORTH_SYMBOL ); + $e = $this->getOption( self::OPT_EAST_SYMBOL ); + $s = $this->getOption( self::OPT_SOUTH_SYMBOL ); + $w = $this->getOption( self::OPT_WEST_SYMBOL ); + + // If there is a direction indicator, remove it, and prepend a minus sign for south and west directions. + // If there is no direction indicator, the coordinate is already non-directional and no work is required. + if ( in_array( $lastChar, array( $n, $e, $s, $w ) ) ) { + $coordinate = substr( $coordinate, 0, -1 ); + + if ( in_array( $lastChar, array( $s, $w ) ) ) { + $coordinate = '-' . $coordinate; + } + } + + return $coordinate; + } + + /** + * Returns a normalized version of the coordinate string. + * + * @since 0.1 + * + * @param string $coordinates + * + * @return string + */ + protected function getNormalizedNotation( $coordinates ) { + $minute = $this->getOption( self::OPT_MINUTE_SYMBOL ); + + $coordinates = str_replace( array( '°', '°' ), $this->getOption( self::OPT_DEGREE_SYMBOL ), $coordinates ); + $coordinates = str_replace( array( '′', '′', '´', '′' ), $minute, $coordinates ); + + $coordinates = $this->removeInvalidChars( $coordinates ); + + return $coordinates; + } + + /** + * Returns a string with whitespace, control characters and characters with ASCII values above 126 removed. + * + * @since 0.1 + * + * @param string $string + * + * @return string + */ + protected function removeInvalidChars( $string ) { + $filtered = array(); + + foreach ( str_split( $string ) as $character ) { + $asciiValue = ord( $character ); + + if ( ( $asciiValue > 32 && $asciiValue < 127 ) || $asciiValue == 194 || $asciiValue == 176 ) { + $filtered[] = $character; + } + } + + return implode( '', $filtered ); + } + + /** + * Takes a set of coordinates in Decimal Minute representation, and returns them in float representation. + * + * @since 0.1 + * + * @param string $coordinate + * + * @return float + */ + protected function parseDMCoordinate( $coordinate ) { + $isNegative = $coordinate{0} == '-'; + + if ( $isNegative ) { + $coordinate = substr( $coordinate, 1 ); + } + + list( $degrees, $minutes ) = explode( $this->getOption( self::OPT_DEGREE_SYMBOL ), $coordinate ); + + $minutes = substr( $minutes, 0, -1 ); + + $coordinate = $degrees + $minutes / 60; + + if ( $isNegative ) { + $coordinate *= -1; + } + + return (float)$coordinate; + } + + /** + * returns whether the coordinates are in Decimal Minute representation. + * + * @since 0.1 + * + * @param string $coordinates + * + * @return boolean + */ + protected function areDMCoordinates( $coordinates ) { + $sep = $this->getOption( self::OPT_SEPARATOR_SYMBOL ); + + $match = preg_match( '/(-)?\d{1,3}°(\d{1,2}(\.\d{1,20})?\')?' . $sep . '(-)?\d{1,3}°(\d{1,2}(\.\d{1,20})?\')?$/i', $coordinates ) // Non-directional + || preg_match( '/\d{1,3}°(\d{1,2}(\.\d{1,20})?\')?(N|S)' . $sep . '\d{1,3}°(\d{1,2}(\.\d{1,20})?\')?(E|W)?$/i', $coordinates ); // Directional + + return $match; + } + +} diff --git a/ValueParsers/tests/includes/parsers/DmCoordinateParserTest.php b/ValueParsers/tests/includes/parsers/DmCoordinateParserTest.php new file mode 100644 index 0000000..2d034b9 --- /dev/null +++ b/ValueParsers/tests/includes/parsers/DmCoordinateParserTest.php @@ -0,0 +1,94 @@ +<?php + +namespace ValueParsers\Test; + +use DataValues\GeoCoordinateValue; +use ValueParsers\DmCoordinateParser; + +/** + * Unit tests for the ValueParsers\DmCoordinateValue class. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 0.1 + * + * @ingroup ValueParsersTest + * + * @group ValueParsers + * @group DataValueExtensions + * @group GeoCoordinateParserTest + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroended...@gmail.com > + */ +class DmCoordinateParserTest extends StringValueParserTest { + + /** + * @see ValueParserTestBase::validInputProvider + * + * @since 0.1 + * + * @return array + */ + public function validInputProvider() { + $argLists = array(); + + // TODO: test with different parser options + + $valid = array( + "55° 0', 37° 0'" => array( 55, 37 ), + "55° 30', 37° 30'" => array( 55.5, 37.5 ), + "0° 0', 0° 0'" => array( 0, 0 ), + "-55° 30', -37° 30'" => array( -55.5, -37.5 ), + "0° 0.3' S, 0° 0.3' W" => array( -0.005, -0.005 ), + ); + + foreach ( $valid as $value => $expected ) { + $expected = new GeoCoordinateValue( $expected[0], $expected[1] ); + $argLists[] = array( (string)$value, $expected ); + } + + return $argLists; + } + + public function invalidInputProvider() { + $argLists = parent::invalidInputProvider(); + + $invalid = array( + '~=[,,_,,]:3', + 'ohi there', + ); + + foreach ( $invalid as $value ) { + $argLists[] = array( $value ); + } + + return $argLists; + } + + /** + * @see ValueParserTestBase::getParserClass + * + * @since 0.1 + * + * @return string + */ + protected function getParserClass() { + return 'ValueParsers\DmCoordinateParser'; + } + +} -- To view, visit https://gerrit.wikimedia.org/r/58981 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Icef0d92110a62f535048e079a18920d4c1b0b554 Gerrit-PatchSet: 3 Gerrit-Project: mediawiki/extensions/DataValues Gerrit-Branch: master Gerrit-Owner: Jeroen De Dauw <jeroended...@gmail.com> Gerrit-Reviewer: Daniel Werner <daniel.wer...@wikimedia.de> Gerrit-Reviewer: John Erling Blad <jeb...@gmail.com> Gerrit-Reviewer: Tobias Gritschacher <tobias.gritschac...@wikimedia.de> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits