Jeroen De Dauw has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/58925


Change subject: Added DmsCoordinateParser
......................................................................

Added DmsCoordinateParser

Change-Id: If5cf6004f67dad1424e82a9f0164005af3dc45cc
---
M ValueParsers/ValueParsers.classes.php
M ValueParsers/ValueParsers.mw.php
A ValueParsers/includes/parsers/DmsCoordinateParser.php
A ValueParsers/tests/includes/parsers/DmsCoordinateParserTest.php
4 files changed, 375 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DataValues 
refs/changes/25/58925/1

diff --git a/ValueParsers/ValueParsers.classes.php 
b/ValueParsers/ValueParsers.classes.php
index 7094f54..088459b 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\DmsCoordinateParser' => 
'includes/parsers/DmsCoordinateParser.php',
        'ValueParsers\GeoCoordinateParser' => 
'includes/parsers/GeoCoordinateParser.php',
        'ValueParsers\FloatParser' => 'includes/parsers/FloatParser.php',
        'ValueParsers\IntParser' => 'includes/parsers/IntParser.php',
diff --git a/ValueParsers/ValueParsers.mw.php b/ValueParsers/ValueParsers.mw.php
index 9d91a75..5f3538e 100644
--- a/ValueParsers/ValueParsers.mw.php
+++ b/ValueParsers/ValueParsers.mw.php
@@ -69,6 +69,7 @@
                'includes/api/ApiParseValue',
 
                'includes/parsers/BoolParser',
+               'includes/parsers/DmsCoordinateParser',
                'includes/parsers/GeoCoordinateParser',
                'includes/parsers/FloatParser',
                'includes/parsers/IntParser',
diff --git a/ValueParsers/includes/parsers/DmsCoordinateParser.php 
b/ValueParsers/includes/parsers/DmsCoordinateParser.php
new file mode 100644
index 0000000..ba9f0ef
--- /dev/null
+++ b/ValueParsers/includes/parsers/DmsCoordinateParser.php
@@ -0,0 +1,273 @@
+<?php
+
+namespace ValueParsers;
+
+use DataValues\GeoCoordinateValue;
+use LogicException;
+
+/**
+ * Parser for geographical coordinates in Degree Minute Second 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 < [email protected] >
+ */
+class DmsCoordinateParser 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';
+       const OPT_SECOND_SYMBOL = 'second';
+
+       /**
+        * 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_SECOND_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->areDMSCoordinates( $value ) ) {
+                       throw new ParseException( 'Not a geographical 
coordinate in DMS 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->parseDMSCoordinate( $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 ) {
+               $second = $this->getOption( self::OPT_SECOND_SYMBOL );
+               $minute = $this->getOption( self::OPT_MINUTE_SYMBOL );
+
+               $coordinates = str_replace( array( '&#176;', '&deg;' ), 
$this->getOption( self::OPT_DEGREE_SYMBOL ), $coordinates );
+               $coordinates = str_replace( array( '&acute;', '&#180;' ), 
$second, $coordinates );
+               $coordinates = str_replace( array( '&#8242;', '&prime;', '´', 
'′' ), $minute, $coordinates );
+               $coordinates = str_replace( array( '&#8243;', '&Prime;', 
$minute . $minute, '´´', '′′', '″' ), $second, $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 DMS representation, and returns them 
in float representation.
+        *
+        * @since 0.1
+        *
+        * @param string $coordinate
+        *
+        * @return float
+        */
+       protected function parseDMSCoordinate( $coordinate ) {
+               $isNegative = $coordinate{0} == '-';
+
+               if ( $isNegative ) {
+                       $coordinate = substr( $coordinate, 1 );
+               }
+
+               $degreePosition = strpos( $coordinate, $this->getOption( 
self::OPT_DEGREE_SYMBOL ) );
+               $degrees = substr( $coordinate, 0, $degreePosition );
+
+               $minutePosition = strpos( $coordinate, $this->getOption( 
self::OPT_MINUTE_SYMBOL ) );
+
+               if ( $minutePosition === false ) {
+                       $minutes = 0;
+               }
+               else {
+                       $degSignLength = strlen( $this->getOption( 
self::OPT_DEGREE_SYMBOL ) );
+                       $minuteLength = $minutePosition - $degreePosition - 
$degSignLength;
+                       $minutes = substr( $coordinate, $degreePosition + 
$degSignLength, $minuteLength );
+               }
+
+               $secondPosition = strpos( $coordinate, $this->getOption( 
self::OPT_SECOND_SYMBOL ) );
+
+               if ( $minutePosition === false ) {
+                       $seconds = 0;
+               }
+               else {
+                       $secondLength = $secondPosition - $minutePosition - 1;
+                       $seconds = substr( $coordinate, $minutePosition + 1, 
$secondLength );
+               }
+
+               $coordinate = $degrees + ( $minutes + $seconds / 60 ) / 60;
+
+               if ( $isNegative ) {
+                       $coordinate *= -1;
+               }
+
+               return (float)$coordinate;
+       }
+
+       /**
+        * returns whether the coordinates are in DMS representation.
+        * TODO: nicify
+        *
+        * @since 0.1
+        *
+        * @param string $coordinates
+        *
+        * @return boolean
+        */
+       protected function areDMSCoordinates( $coordinates ) {
+               $sep = $this->getOption( self::OPT_SEPARATOR_SYMBOL );
+
+               $match = preg_match( 
'/^(-)?(\d{1,3}°)(\d{1,2}(\′|\'))?((\d{1,2}(″|"))?|(\d{1,2}\.\d{1,20}(″|"))?)'
+                       . $sep . 
'(-)?(\d{1,3}°)(\d{1,2}(\′|\'))?((\d{1,2}(″|"))?|(\d{1,2}\.\d{1,20}(″|"))?)$/i',
 $coordinates ) // Non-directional
+                       || preg_match( 
'/^(\d{1,3}°)(\d{1,2}(\′|\'))?((\d{1,2}(″|"))?|(\d{1,2}\.\d{1,20}(″|"))?)(N|S)'
+                               . $sep . 
'(\d{1,3}°)(\d{1,2}(\′|\'))?((\d{1,2}(″|"))?|(\d{1,2}\.\d{1,20}(″|"))?)(E|W)$/i',
 $coordinates ); // Directional
+
+               return $match;
+       }
+
+}
diff --git a/ValueParsers/tests/includes/parsers/DmsCoordinateParserTest.php 
b/ValueParsers/tests/includes/parsers/DmsCoordinateParserTest.php
new file mode 100644
index 0000000..e485793
--- /dev/null
+++ b/ValueParsers/tests/includes/parsers/DmsCoordinateParserTest.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace ValueParsers\Test;
+
+use DataValues\GeoCoordinateValue;
+use ValueParsers\DmsCoordinateParser;
+
+/**
+ * Unit tests for the ValueParsers\DmsGeoCoordinateValue 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 < [email protected] >
+ */
+class DmsGeoCoordinateParserTest extends StringValueParserTest {
+
+       /**
+        * @see ValueParserTestBase::validInputProvider
+        *
+        * @since 0.1
+        *
+        * @return array
+        */
+       public function validInputProvider() {
+               $argLists = array();
+
+               // TODO: test with different parser options
+
+               $valid = array(
+                       // DMS
+                       '55° 45\' 20.8296", 37° 37\' 3.4788"' => array( 
55.755786, 37.617633 ),
+                       '55° 45\' 20.8296", -37° 37\' 3.4788"' => array( 
55.755786, -37.617633 ),
+                       '-55° 45\' 20.8296", -37° 37\' 3.4788"' => array( 
-55.755786, -37.617633 ),
+                       '-55° 45\' 20.8296", 37° 37\' 3.4788"' => array( 
-55.755786, 37.617633 ),
+                       '55° 0\' 0", 37° 0\' 0"' => array( 55, 37 ),
+                       '55° 30\' 0", 37° 30\' 0"' => array( 55.5, 37.5 ),
+                       '55° 0\' 18", 37° 0\' 18"' => array( 55.005, 37.005 ),
+                       '0° 0\' 0", 0° 0\' 0"' => array( 0, 0 ),
+                       '0° 0\' 18" N, 0° 0\' 18" E' => array( 0.005, 0.005 ),
+                       ' 0° 0\' 18" S  , 0°  0\' 18"  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\DmsCoordinateParser';
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/58925
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: If5cf6004f67dad1424e82a9f0164005af3dc45cc
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/DataValues
Gerrit-Branch: master
Gerrit-Owner: Jeroen De Dauw <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to