Index: trunk/extensions/GeoData/tests/ParseCoordTest.php |
— | — | @@ -7,17 +7,18 @@ |
8 | 8 | /** |
9 | 9 | * @dataProvider getCases |
10 | 10 | */ |
11 | | - public function testParseCoordinates( $parts, $result ) { |
| 11 | + public function testParseCoordinates( $parts, $result, $globe = 'earth' ) { |
12 | 12 | $formatted = '"' . implode( $parts, '|' ) . '"'; |
13 | | - $s = GeoData::parseCoordinates( $parts ); |
| 13 | + $s = GeoData::parseCoordinates( $parts, $globe ); |
14 | 14 | $val = $s->value; |
15 | 15 | if ( $result === false ) { |
16 | 16 | $this->assertFalse( $s->isGood(), "Parsing of $formatted was expected to fail" ); |
17 | 17 | } else { |
18 | | - $this->assertTrue( $s->isGood(), "Parsing of $formatted was expected to suceed, but it failed" ); |
| 18 | + $msg = $s->isGood() ? '' : $s->getWikiText(); |
| 19 | + $this->assertTrue( $s->isGood(), "Parsing of $formatted was expected to succeed, but it failed: $msg" ); |
19 | 20 | $this->assertTrue( $val->equalsTo( $result ), |
20 | | - "Parsing of $formatted was expected to yield something close to" |
21 | | - . " ({$result->lat}, {$result->lon}), but yielded ({$val->lat}, {$val->lon})" |
| 21 | + "Parsing of $formatted was expected to yield something close to" |
| 22 | + . " ({$result->lat}, {$result->lon}), but yielded ({$val->lat}, {$val->lon})" |
22 | 23 | ); |
23 | 24 | } |
24 | 25 | } |
— | — | @@ -57,6 +58,21 @@ |
58 | 59 | array( array( 1, 2, 3, 'N', 'E' ), false ), |
59 | 60 | array( array( 1, 2, 3, 'N', 1, 'E' ), false ), |
60 | 61 | array( array( 1, 2, 3, 'N', 1, 2, 'E' ), false ), |
| 62 | + // coordinate validation (Earth) |
| 63 | + array( array( -90, 180 ), new Coord( -90, 180 ) ), |
| 64 | + array( array( 90.0000001, -180.00000001 ), false ), |
| 65 | + array( array( 90, 1, 180, 0 ), false ), |
| 66 | + array( array( 10, -1, 20, 0 ), false ), |
| 67 | + array( array( 25, 60, 10, 0 ), false ), |
| 68 | + array( array( 25, 0, 0, 10, 0, 60 ), false ), |
| 69 | + // @todo: only the last component of the coordinate should be non-integer |
| 70 | + //array( array( 10.5, 0, 20, 0 ), false ), |
| 71 | + //array( array( 10, 30.5, 0, 20, 0, 0 ), false ), |
| 72 | + // coordinate validation and normalisation (non-Earth) |
| 73 | + array( array( 10, 20 ), new Coord( 10, 20 ), 'mars' ), |
| 74 | + array( array( 110, 20 ), false, 'mars' ), |
| 75 | + array( array( 47, 0, 'S', 355, 3, 'W' ), new Coord( -47, 4.95 ), 'mars' ), // Asimov Crater |
| 76 | + array( array( 68, 'S', 357, 'E' ), new Coord( -68, 357 ), 'venus' ), // Quetzalpetlatl Corona |
61 | 77 | ); |
62 | 78 | } |
63 | 79 | } |
\ No newline at end of file |
Index: trunk/extensions/GeoData/CoordinatesParserFunction.php |
— | — | @@ -58,10 +58,11 @@ |
59 | 59 | $this->addArg( $value ); |
60 | 60 | } |
61 | 61 | } |
62 | | - $status = GeoData::parseCoordinates( $this->unnamed ); |
| 62 | + $this->parseTagArgs(); |
| 63 | + $status = GeoData::parseCoordinates( $this->unnamed, $this->named['globe'] ); |
63 | 64 | if ( $status->isGood() ) { |
64 | 65 | $coord = $status->value; |
65 | | - $status = $this->parseTagArgs( $coord ); |
| 66 | + $status = $this->applyTagArgs( $coord ); |
66 | 67 | if ( $status->isGood() ) { |
67 | 68 | $status = $this->applyCoord( $coord ); |
68 | 69 | if ( $status->isGood() ) { |
— | — | @@ -128,14 +129,21 @@ |
129 | 130 | * |
130 | 131 | * @param Coord $coord |
131 | 132 | */ |
132 | | - private function parseTagArgs( Coord $coord ) { |
133 | | - global $wgDefaultGlobe, $wgContLang, $wgTypeToDim, $wgDefaultDim; |
| 133 | + private function parseTagArgs() { |
| 134 | + global $wgDefaultGlobe, $wgContLang; |
| 135 | + // fear not of overwriting the stuff we've just received from the geohack param, it has minimum precedence |
| 136 | + if ( isset( $this->named['geohack'] ) ) { |
| 137 | + $this->named = array_merge( $this->parseGeoHackArgs( $this->named['geohack'] ), $this->named ); |
| 138 | + } |
| 139 | + $this->named['globe'] = isset( $this->named['globe'] ) |
| 140 | + ? $wgContLang->lc( $this->named['globe'] ) |
| 141 | + : $wgDefaultGlobe; |
| 142 | + } |
| 143 | + |
| 144 | + private function applyTagArgs( Coord $coord ) { |
| 145 | + global $wgContLang, $wgTypeToDim, $wgDefaultDim; |
134 | 146 | $result = Status::newGood(); |
135 | 147 | $args = $this->named; |
136 | | - // fear not of overwriting the stuff we've just received from the geohack param, it has minimum precedence |
137 | | - if ( isset( $args['geohack'] ) ) { |
138 | | - $args = array_merge( $this->parseGeoHackArgs( $args['geohack'] ), $args ); |
139 | | - } |
140 | 148 | $coord->primary = isset( $args['primary'] ); |
141 | 149 | $coord->globe = isset( $args['globe'] ) ? $wgContLang->lc( $args['globe'] ) : $wgDefaultGlobe; |
142 | 150 | $coord->dim = $wgDefaultDim; |
Index: trunk/extensions/GeoData/ApiQueryGeoSearch.php |
— | — | @@ -30,7 +30,7 @@ |
31 | 31 | $this->requireOnlyOneParameter( $params, 'coord', 'page' ); |
32 | 32 | if ( isset( $params['coord'] ) ) { |
33 | 33 | $arr = explode( '|', $params['coord'] ); |
34 | | - if ( count( $arr ) != 2 || !GeoData::validateCoord( $arr[0], $arr[1] ) ) { |
| 34 | + if ( count( $arr ) != 2 || !GeoData::validateCoord( $arr[0], $arr[1], $params['globe'] ) ) { |
35 | 35 | $this->dieUsage( 'Invalid coordinate provided', '_invalid-coord' ); |
36 | 36 | } |
37 | 37 | $lat = $arr[0]; |
Index: trunk/extensions/GeoData/GeoData.body.php |
— | — | @@ -7,11 +7,16 @@ |
8 | 8 | * @param type $lon |
9 | 9 | * @return Boolean: Whether the coordinate is valid |
10 | 10 | */ |
11 | | - public static function validateCoord( $lat, $lon ) { |
12 | | - return is_numeric( $lat ) |
13 | | - && is_numeric( $lon ) |
14 | | - && abs( $lat ) <= 90 |
15 | | - && abs( $lon ) <= 180; |
| 11 | + public static function validateCoord( $lat, $lon, $globe ) { |
| 12 | + global $wgGlobes; |
| 13 | + if ( !is_numeric( $lat ) || !is_numeric( $lon ) || abs( $lat ) > 90 ) { |
| 14 | + return false; |
| 15 | + } |
| 16 | + if ( !isset( $wgGlobes[$globe] ) ) { |
| 17 | + return abs( $lon ) <= 360; |
| 18 | + } else { |
| 19 | + return $lon >= $wgGlobes[$globe]['min'] && $lon <= $wgGlobes[$globe]['max']; |
| 20 | + } |
16 | 21 | } |
17 | 22 | |
18 | 23 | /** |
— | — | @@ -50,11 +55,12 @@ |
51 | 56 | * Parses coordinates |
52 | 57 | * See https://en.wikipedia.org/wiki/Template:Coord for sample inputs |
53 | 58 | * |
54 | | - * @param String $str: |
| 59 | + * @param Array $parts: Array of coordinate components |
| 60 | + * @param String $globe: Globe name |
55 | 61 | * @returns Status: Status object, in case of success its value is a Coord object. |
56 | 62 | */ |
57 | | - public static function parseCoordinates( $parts ) { |
58 | | - global $wgContLang; |
| 63 | + public static function parseCoordinates( $parts, $globe ) { |
| 64 | + global $wgContLang, $wgGlobes; |
59 | 65 | |
60 | 66 | $count = count( $parts ); |
61 | 67 | if ( !is_array( $parts ) || $count < 2 || $count > 8 || ( $count % 2 ) ) { |
— | — | @@ -67,7 +73,16 @@ |
68 | 74 | if ( $lat === false ) { |
69 | 75 | return Status::newFatal( 'geodata-bad-latitude' ); |
70 | 76 | } |
71 | | - $lon = self::parseOneCoord( $lonArr, $coordInfo['lon'] ); |
| 77 | + $lonInfo = isset( $wgGlobes[$globe] ) |
| 78 | + ? $wgGlobes[$globe] |
| 79 | + : array( |
| 80 | + 'min' => -360, |
| 81 | + 'mid' => 0, |
| 82 | + 'max' => 360, |
| 83 | + 'abbr' => array( 'E' => 1, 'W' => -1 ), |
| 84 | + 'wrap' => true, |
| 85 | + ); |
| 86 | + $lon = self::parseOneCoord( $lonArr, $lonInfo ); |
72 | 87 | if ( $lon === false ) { |
73 | 88 | return Status::newFatal( 'geodata-bad-longitude' ); |
74 | 89 | } |
— | — | @@ -96,8 +111,8 @@ |
97 | 112 | } |
98 | 113 | } |
99 | 114 | $part = $wgContLang->parseFormattedNumber( $part ); |
100 | | - $min = $i == 0 ? -$coordInfo['range'] : 0; |
101 | | - $max = $i == 0 ? $coordInfo['range'] : 59.999999; |
| 115 | + $min = $i == 0 ? $coordInfo['min'] : 0; |
| 116 | + $max = $i == 0 ? $coordInfo['max'] : 59.999999; |
102 | 117 | if ( !is_numeric( $part ) |
103 | 118 | || $part < $min |
104 | 119 | || $part > $max ) { |
— | — | @@ -106,7 +121,10 @@ |
107 | 122 | $value += $part * $multiplier * GeoMath::sign( $value ); |
108 | 123 | $multiplier /= 60; |
109 | 124 | } |
110 | | - if ( abs( $value ) > $coordInfo['range'] ) { |
| 125 | + if ( $coordInfo['wrap'] && $value < 0 ) { |
| 126 | + $value = $coordInfo['max'] + $value; |
| 127 | + } |
| 128 | + if ( $value < $coordInfo['min'] || $value > $coordInfo['max'] ) { |
111 | 129 | return false; |
112 | 130 | } |
113 | 131 | return $value; |
— | — | @@ -122,17 +140,7 @@ |
123 | 141 | private static function parseSuffix( $str, $coordInfo ) { |
124 | 142 | global $wgContLang; |
125 | 143 | $str = $wgContLang->uc( trim( $str ) ); |
126 | | - foreach ( $coordInfo['-'] as $suffix ) { |
127 | | - if ( $suffix == $str ) { |
128 | | - return -1; |
129 | | - } |
130 | | - } |
131 | | - foreach ( $coordInfo['+'] as $suffix ) { |
132 | | - if ( $suffix == $str ) { |
133 | | - return 1; |
134 | | - } |
135 | | - } |
136 | | - return 0; |
| 144 | + return isset( $coordInfo['abbr'][$str] ) ? $coordInfo['abbr'][$str] : 0; |
137 | 145 | } |
138 | 146 | |
139 | 147 | public static function getCoordInfo() { |
— | — | @@ -141,19 +149,16 @@ |
142 | 150 | if ( !$result ) { |
143 | 151 | $result = array( |
144 | 152 | 'lat' => array( |
145 | | - 'range' => 90, |
146 | | - '+' => array( 'N' ), |
147 | | - '-' => array( 'S' ), |
| 153 | + 'min' => -90, |
| 154 | + 'mid' => 0, |
| 155 | + 'max' => 90, |
| 156 | + 'abbr' => array( 'N' => 1, 'S' => -1 ), |
| 157 | + 'wrap' => false, |
148 | 158 | ), |
149 | | - 'lon' => array( |
150 | | - 'range' => 180, |
151 | | - '+' => array( 'E' ), |
152 | | - '-' => array( 'W' ), |
153 | | - ), |
154 | 159 | 'primary' => array( 'primary' ), |
155 | 160 | ); |
156 | 161 | if ( $wgContLang->getCode() != 'en' ) { |
157 | | - $result['primary'][] = wfMessage( 'geodata-primary-coordinate' )->plain(); |
| 162 | + $result['primary'][] = wfMessage( 'geodata-primary-coordinate' )->inContentLanguage()->plain(); |
158 | 163 | } |
159 | 164 | $result['primary'] = array_flip( $result['primary'] ); |
160 | 165 | } |
Index: trunk/extensions/GeoData/GeoData.php |
— | — | @@ -82,3 +82,47 @@ |
83 | 83 | * Default value of dim if it is unknown |
84 | 84 | */ |
85 | 85 | $wgDefaultDim = 1000; |
| 86 | + |
| 87 | +$earth = array( 'min' => -180, 'mid' => 0, 'max' => 180, 'abbr' => array( 'E' => +1, 'W' => -1 ), 'wrap' => false ); |
| 88 | +$east360 = array( 'min' => 0, 'mid' => 180, 'max' => 360, 'abbr' => array( 'E' => +1, 'W' => -1 ), 'wrap' => true ); |
| 89 | +$west360 = array( 'min' => 0, 'mid' => 180, 'max' => 360, 'abbr' => array( 'E' => -1, 'W' => +1 ), 'wrap' => true ); |
| 90 | + |
| 91 | +/** |
| 92 | + * Description of coordinate systems, mostly taken from http://planetarynames.wr.usgs.gov/TargetCoordinates |
| 93 | + */ |
| 94 | +$wgGlobes = array( |
| 95 | + 'earth' => $earth, |
| 96 | + 'mercury' => $west360, |
| 97 | + 'venus' => $east360, |
| 98 | + 'moon' => $earth, |
| 99 | + 'mars' => $east360, |
| 100 | + 'phobos' => $west360, |
| 101 | + 'deimos' => $west360, |
| 102 | + //'ceres' => ???, |
| 103 | + //'vesta' => ???, |
| 104 | + 'ganymede' => $west360, |
| 105 | + 'callisto' => $west360, |
| 106 | + 'io' => $west360, |
| 107 | + 'europa' => $west360, |
| 108 | + 'mimas' => $west360, |
| 109 | + 'enceladus' => $west360, |
| 110 | + 'tethys' => $west360, |
| 111 | + 'dione' => $west360, |
| 112 | + 'rhea' => $west360, |
| 113 | + 'titan' => $west360, |
| 114 | + 'hyperion' => $west360, |
| 115 | + 'iapetus' => $west360, |
| 116 | + 'phoebe' => $west360, |
| 117 | + 'miranda' => $east360, |
| 118 | + 'ariel' => $east360, |
| 119 | + 'umbriel' => $east360, |
| 120 | + 'titania' => $east360, |
| 121 | + 'oberon' => $east360, |
| 122 | + 'triton' => $east360, |
| 123 | + 'pluto' => $east360, // ??? |
| 124 | +); |
| 125 | + |
| 126 | +unset( $earth ); |
| 127 | +unset( $east360 ); |
| 128 | +unset( $west360 ); |
| 129 | + |