Index: trunk/extensions/GeoData/GeoData.sql |
— | — | @@ -0,0 +1,37 @@ |
| 2 | +-- SQL schema for GeoData extension |
| 3 | + |
| 4 | +-- Stores information about geographical coordinates in articles |
| 5 | +CREATE TABLE /*_*/geo_tags ( |
| 6 | + -- page_id |
| 7 | + gt_page_id int unsigned NOT NULL, |
| 8 | + -- Whether this coordinate is primary (defines the principal location of article subject) |
| 9 | + -- or secondary (just mentioned in text) |
| 10 | + gt_primary bool NOT NULL, |
| 11 | + -- Latitude of the point in degrees |
| 12 | + gt_lat float NOT NULL, |
| 13 | + -- Longitude of the point in degrees |
| 14 | + gt_lon float NOT NULL, |
| 15 | + -- Approximate viewing radius in meters, gives an idea how large the object is |
| 16 | + gt_dim int default 1 |
| 17 | +)/*$wgDBTableOptions*/; |
| 18 | + |
| 19 | +-- @todo |
| 20 | +CREATE INDEX /*i*/gt_page_id ON /*_*/geo_tags ( gt_page_id ); |
| 21 | +CREATE INDEX /*i*/gt_lat_lon ON /*_*/geo_tags ( gt_lat, gt_lon ); |
| 22 | + |
| 23 | +DELIMITER // |
| 24 | +-- Calculates distance in meters between two points |
| 25 | +-- See https://en.wikipedia.org/wiki/Haversine_formula |
| 26 | +CREATE FUNCTION /*_*/gd_distance( lat1 double, lon1 double, lat2 double, lon2 double ) |
| 27 | + RETURNS double DETERMINISTIC |
| 28 | +BEGIN |
| 29 | + SET lat1 = radians( lat1 ); |
| 30 | + SET lon1 = radians( lon1 ); |
| 31 | + SET lat2 = radians( lat2 ); |
| 32 | + SET lon2 = radians( lon2 ); |
| 33 | + SET @sin1 = sin( ( lat2 - lat1 ) / 2 ); |
| 34 | + SET @sin2 = sin( ( lon2 - lon1 ) / 2 ); |
| 35 | + RETURN 2 * 6371010 * asin( sqrt( @sin1 * @sin1 + cos( lat1 ) * cos( lat2 ) * @sin2 * @sin2 ) ); |
| 36 | +END// |
| 37 | + |
| 38 | +DELIMITER ; |
Property changes on: trunk/extensions/GeoData/GeoData.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 39 | + native |
Index: trunk/extensions/GeoData/tests/ParseCoordTest.php |
— | — | @@ -0,0 +1,62 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * @group GeoData |
| 6 | + */ |
| 7 | +class ParseCoordTest extends MediaWikiTestCase { |
| 8 | + /** |
| 9 | + * @dataProvider getCases |
| 10 | + */ |
| 11 | + public function testParseCoordinates( $parts, $result ) { |
| 12 | + $formatted = '"' . implode( $parts, '|' ) . '"'; |
| 13 | + $s = GeoData::parseCoordinates( $parts ); |
| 14 | + $val = $s->value; |
| 15 | + if ( $result === false ) { |
| 16 | + $this->assertFalse( $s->isGood(), "Parsing of $formatted was expected to fail" ); |
| 17 | + } else { |
| 18 | + $this->assertTrue( $s->isGood(), "Parsing of $formatted was expected to suceed, but it failed" ); |
| 19 | + $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})" |
| 22 | + ); |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + public function getCases() { |
| 27 | + return array( |
| 28 | + // basics |
| 29 | + array( array( 0, 0 ), new Coord( 0, 0 ) ), |
| 30 | + array( array( 75, 25 ), new Coord( 75, 25 ) ), |
| 31 | + array( array( '20.0', '-15.5' ), new Coord( 20, -15.5 ) ), |
| 32 | + array( array( -20, 30, 40, 45 ), new Coord( -20.5, 40.75 ) ), |
| 33 | + array( array( 20, 30, 40, 40, 45, 55 ), new Coord( 20.511111111111, 40.765277777778 ) ), |
| 34 | + // NESW |
| 35 | + array( array( 20, 'N', 30, 'E' ), new Coord( 20, 30 ) ), |
| 36 | + array( array( 20, 'N', 30, 'W' ), new Coord( 20, -30 ) ), |
| 37 | + array( array( 20, 'S', 30, 'E' ), new Coord( -20, 30 ) ), |
| 38 | + array( array( 20, 'S', 30, 'W' ), new Coord( -20, -30 ) ), |
| 39 | + array( array( 20, 30, 40, 'S', 40, 45, 55, 'E' ), new Coord( -20.511111111111, 40.765277777778 ) ), |
| 40 | + array( array( 20, 30, 40, 'N', 40, 45, 55, 'W' ), new Coord( 20.511111111111, -40.765277777778 ) ), |
| 41 | + array( array( 20, 'E', 30, 'W' ), false ), |
| 42 | + array( array( 20, 'S', 30, 'N' ), false ), |
| 43 | + array( array( -20, 'S', 30, 'E' ), false ), |
| 44 | + array( array( 20, 'S', -30, 'W' ), false ), |
| 45 | + // wrong number of parameters |
| 46 | + array( array(), false ), |
| 47 | + array( array( 1 ), false ), |
| 48 | + array( array( 1, 2, 3 ), false ), |
| 49 | + array( array( 1, 2, 3, 4, 5 ), false ), |
| 50 | + array( array( 1, 2, 3, 4, 5, 6, 7 ), false ), |
| 51 | + array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9 ), false ), |
| 52 | + array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ), false ), |
| 53 | + // unbalanced NESW |
| 54 | + array( array( 'N', 'E' ), false ), |
| 55 | + array( array( 12, 'N', 'E' ), false ), |
| 56 | + array( array( 'N', 15, 'E' ), false ), |
| 57 | + array( array( 1, 2, 3, 'N', 1, 'E' ), false ), |
| 58 | + array( array( 1, 2, 3, 'N', 'E' ), false ), |
| 59 | + array( array( 1, 2, 3, 'N', 1, 'E' ), false ), |
| 60 | + array( array( 1, 2, 3, 'N', 1, 2, 'E' ), false ), |
| 61 | + ); |
| 62 | + } |
| 63 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/GeoData/tests/ParseCoordTest.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 64 | + native |
Index: trunk/extensions/GeoData/tests/GeoMathTest.php |
— | — | @@ -0,0 +1,30 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * @group GeoData |
| 6 | + */ |
| 7 | +class GeoMathTest extends MediaWikiTestCase { |
| 8 | + /** |
| 9 | + * @dataProvider getDistanceData |
| 10 | + */ |
| 11 | + public function testDistance( $lat1, $lon1, $lat2, $lon2, $dist, $name ) { |
| 12 | + $this->assertEquals( $dist, GeoMath::distance( $lat1, $lon1, $lat2, $lon2 ), "testDistance(): $name", $dist / 1000 ); |
| 13 | + } |
| 14 | + |
| 15 | + public function getDistanceData() { |
| 16 | + // just run against a few values from teh internets... |
| 17 | + return array( |
| 18 | + array( 55.75, 37.6167, 59.95, 30.3167, 635000, 'Moscow to St. Bumtown' ), |
| 19 | + array( 51.5, -0.1167, 52.35, 4.9167, 357520, 'London to Amsterdam' ), |
| 20 | + array( 40.7142, -74.0064, 37.775, -122.418, 4125910, 'New York to San Francisco' ), |
| 21 | + ); |
| 22 | + } |
| 23 | + |
| 24 | + public function testRectAround() { |
| 25 | + for ( $i = 0; $i < 90; $i += 5 ) { |
| 26 | + $r = GeoMath::rectAround( $i, $i, 5000 ); |
| 27 | + $this->assertEquals( 10000, GeoMath::distance( $i, $r['minLon'], $i, $r['maxLon'] ), 'rectAround(): test longitude', 1 ); |
| 28 | + $this->assertEquals( 10000, GeoMath::distance( $r['minLat'], $i, $r['maxLat'], $i ), 'rectAround(): test latitude', 1 ); |
| 29 | + } |
| 30 | + } |
| 31 | +} |
Property changes on: trunk/extensions/GeoData/tests/GeoMathTest.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 32 | + native |
Index: trunk/extensions/GeoData/GeoData.body.php |
— | — | @@ -0,0 +1,211 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class GeoData { |
| 5 | + /** |
| 6 | + * |
| 7 | + * @param type $lat |
| 8 | + * @param type $lon |
| 9 | + * @return Boolean: Whether the coordinate is valid |
| 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; |
| 16 | + } |
| 17 | + |
| 18 | + public static function getPageCoordinates( Title $title ) { |
| 19 | + $dbr = wfGetDB( DB_SLAVE ); |
| 20 | + $row = $dbr->selectRow( 'geo_tags', |
| 21 | + array( 'gt_lat', 'gt_lon' ), |
| 22 | + array( 'gt_page_id' => $title->getArticleID(), 'gt_primary' => 1 ) |
| 23 | + ); |
| 24 | + if ( !$row ) { |
| 25 | + return false; |
| 26 | + } |
| 27 | + return Coord::newFromRow( $row ); |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * Parses coordinates |
| 32 | + * See https://en.wikipedia.org/wiki/Template:Coord for sample inputs |
| 33 | + * |
| 34 | + * @param String $str: |
| 35 | + * @returns Status: Status object, in case of success its value is a Coord object. |
| 36 | + */ |
| 37 | + public static function parseCoordinates( $parts ) { |
| 38 | + global $wgContLang; |
| 39 | + |
| 40 | + $count = count( $parts ); |
| 41 | + if ( !is_array( $parts ) || $count < 2 || $count > 8 || ( $count % 2 ) ) { |
| 42 | + return Status::newFatal( 'geodata-bad-input' ); |
| 43 | + } |
| 44 | + list( $latArr, $lonArr ) = array_chunk( $parts, $count / 2 ); |
| 45 | + $coordInfo = self::getCoordInfo(); |
| 46 | + |
| 47 | + $lat = self::parseOneCoord( $latArr, $coordInfo['lat'] ); |
| 48 | + if ( $lat === false ) { |
| 49 | + return Status::newFatal( 'geodata-bad-latitude' ); |
| 50 | + } |
| 51 | + $lon = self::parseOneCoord( $lonArr, $coordInfo['lon'] ); |
| 52 | + if ( $lon === false ) { |
| 53 | + return Status::newFatal( 'geodata-bad-longitude' ); |
| 54 | + } |
| 55 | + return Status::newGood( new Coord( $lat, $lon ) ); |
| 56 | + } |
| 57 | + |
| 58 | + private static function parseOneCoord( $parts, $coordInfo ) { |
| 59 | + global $wgContLang; |
| 60 | + |
| 61 | + $count = count( $parts ); |
| 62 | + $multiplier = 1; |
| 63 | + $value = 0; |
| 64 | + |
| 65 | + for ( $i = 0; $i < $count; $i++ ) { |
| 66 | + $part = $parts[$i]; |
| 67 | + if ( $i > 0 && $i == $count - 1 ) { |
| 68 | + $suffix = self::parseSuffix( $part, $coordInfo ); |
| 69 | + if ( $suffix ) { |
| 70 | + if ( $value < 0 ) { |
| 71 | + return false; // "-60°S sounds weird, isn't it? |
| 72 | + } |
| 73 | + $value *= $suffix; |
| 74 | + break; |
| 75 | + } elseif ( $i == 3 ) { |
| 76 | + return false; |
| 77 | + } |
| 78 | + } |
| 79 | + $part = $wgContLang->parseFormattedNumber( $part ); |
| 80 | + $min = $i == 0 ? -$coordInfo['range'] : 0; |
| 81 | + $max = $i == 0 ? $coordInfo['range'] : 59.999999; |
| 82 | + if ( !is_numeric( $part ) |
| 83 | + || $part < $min |
| 84 | + || $part > $max ) { |
| 85 | + return false; |
| 86 | + } |
| 87 | + $value += $part * $multiplier * GeoMath::sign( $value ); |
| 88 | + $multiplier /= 60; |
| 89 | + } |
| 90 | + if ( abs( $value ) > $coordInfo['range'] ) { |
| 91 | + return false; |
| 92 | + } |
| 93 | + return $value; |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Parses coordinate suffix such as N, S, E or W |
| 98 | + * |
| 99 | + * @param String $str: String to test |
| 100 | + * @param Array $coordInfo |
| 101 | + * @return int: Sign modifier or 0 if not a suffix |
| 102 | + */ |
| 103 | + private static function parseSuffix( $str, $coordInfo ) { |
| 104 | + global $wgContLang; |
| 105 | + $str = $wgContLang->uc( trim( $str ) ); |
| 106 | + foreach ( $coordInfo['-'] as $suffix ) { |
| 107 | + if ( $suffix == $str ) { |
| 108 | + return -1; |
| 109 | + } |
| 110 | + } |
| 111 | + foreach ( $coordInfo['+'] as $suffix ) { |
| 112 | + if ( $suffix == $str ) { |
| 113 | + return 1; |
| 114 | + } |
| 115 | + } |
| 116 | + return 0; |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * |
| 121 | + * @param Coord $coord |
| 122 | + * @param Array $args |
| 123 | + */ |
| 124 | + public static function parseTagArgs( Coord $coord, $args ) { |
| 125 | + $result = $args; |
| 126 | + // fear not of overwriting stuff we've just received from the geohack param, it has minimum precedence |
| 127 | + if ( isset( $args['geohack'] ) ) { |
| 128 | + $result = array_merge( self::parseGeoHackArgs( $args['geohack'] ), $result ); |
| 129 | + } |
| 130 | + if ( isset( $args['dim'] ) && is_numeric( $args['dim'] ) && $args['dim'] > 0 ) { |
| 131 | + $coord->dim = $args['dim']; |
| 132 | + } else { |
| 133 | + $coord->dim = null; |
| 134 | + } |
| 135 | + $coord->primary = isset( $args['primary'] ); |
| 136 | + return Status::newGood( $result ); |
| 137 | + } |
| 138 | + |
| 139 | + public static function parseGeoHackArgs( $str ) { |
| 140 | + $result = array(); |
| 141 | + $parts = explode( '_', $str ); |
| 142 | + foreach ( $parts as $arg ) { |
| 143 | + if ( !preg_match( '/(\\S+?):(.*)/', $arg, $matches ) ) { |
| 144 | + continue; |
| 145 | + } |
| 146 | + $key = $m[1]; |
| 147 | + $value = $m[2]; |
| 148 | + if ( $key == 'dim' ) { |
| 149 | + $result['dim'] = $value; |
| 150 | + } |
| 151 | + } |
| 152 | + return $result; |
| 153 | + } |
| 154 | + |
| 155 | + private static function getCoordInfo() { |
| 156 | + //@todo: internationalisation? |
| 157 | + return array( |
| 158 | + 'lat' => array( |
| 159 | + 'range' => 90, |
| 160 | + '+' => array( 'N' ), |
| 161 | + '-' => array( 'S' ), |
| 162 | + ), |
| 163 | + 'lon' => array( |
| 164 | + 'range' => 180, |
| 165 | + '+' => array( 'E' ), |
| 166 | + '-' => array( 'W' ), |
| 167 | + ), |
| 168 | + ); |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +/** |
| 173 | + * CLass representing one coordinate |
| 174 | + */ |
| 175 | +class Coord { |
| 176 | + public $lat, |
| 177 | + $lon, |
| 178 | + $primary, |
| 179 | + $dim, |
| 180 | + $params; |
| 181 | + |
| 182 | + public function __construct( $lat, $lon ) { |
| 183 | + $this->lat = $lat; |
| 184 | + $this->lon = $lon; |
| 185 | + } |
| 186 | + |
| 187 | + public static function newFromRow( $row ) { |
| 188 | + return new Coord( $row->gt_lat, $row->gt_lon ); |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Compares this coordinate with the given coordinate |
| 193 | + * |
| 194 | + * @param Coord $coord: Coordinate to compare with |
| 195 | + * @param int $precision: Comparison precision |
| 196 | + * @return Boolean |
| 197 | + */ |
| 198 | + public function equalsTo( Coord $coord, $precision = 6 ) { |
| 199 | + return round( $this->lat, $precision ) == round( $coord->lat, $precision ) |
| 200 | + && round( $this->lon, $precision ) == round( $coord->lon, $precision ); |
| 201 | + } |
| 202 | + |
| 203 | + public function getRow( $pageId = null ) { |
| 204 | + return array( |
| 205 | + 'gt_page_id' => $pageId, |
| 206 | + 'gt_primary' => $this->primary, |
| 207 | + 'gt_lat' => $this->lat, |
| 208 | + 'gt_lon' => $this->lon, |
| 209 | + 'gt_dim' => $this->dim, |
| 210 | + ); |
| 211 | + } |
| 212 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/GeoData/GeoData.body.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 213 | + native |
Index: trunk/extensions/GeoData/GeoData.php |
— | — | @@ -0,0 +1,36 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * API sandbox extension. Initial author Max Semenik |
| 5 | + * License: WTFPL 2.0 |
| 6 | + */ |
| 7 | +$wgExtensionCredits['other'][] = array( |
| 8 | + 'path' => __FILE__, |
| 9 | + 'name' => 'GeoData', |
| 10 | + 'author' => array( 'Max Semenik' ), |
| 11 | + 'url' => 'https://mediawiki.org/wiki/Extension:GeoData', |
| 12 | + 'descriptionmsg' => 'geodata-desc', |
| 13 | +); |
| 14 | + |
| 15 | +$dir = dirname( __FILE__ ); |
| 16 | + |
| 17 | +$wgAutoloadClasses['Coord'] = "$dir/GeoData.body.php"; |
| 18 | +$wgAutoloadClasses['GeoData'] = "$dir/GeoData.body.php"; |
| 19 | +$wgAutoloadClasses['GeoDataHooks'] = "$dir/GeoDataHooks.php"; |
| 20 | +$wgAutoloadClasses['GeoMath'] = "$dir/GeoMath.php"; |
| 21 | +$wgAutoloadClasses['ApiQueryGeoSearch'] = "$dir/ApiQueryGeoSearch.php"; |
| 22 | +$wgAutoloadClasses['ApiQueryCoordinates'] = "$dir/ApiQueryCoordinates.php"; |
| 23 | + |
| 24 | +$wgAPIListModules['geosearch'] = 'ApiQueryGeoSearch'; |
| 25 | +$wgAPIPropModules['coordinates'] = 'ApiQueryCoordinates'; |
| 26 | + |
| 27 | +$wgHooks['ParserFirstCallInit'][] = 'GeoDataHooks::onParserFirstCallInit'; |
| 28 | +$wgHooks['UnitTestsList'][] = 'GeoDataHooks::onUnitTestsList'; |
| 29 | +$wgHooks['LanguageGetMagic'][] = 'GeoDataHooks::onLanguageGetMagic'; |
| 30 | +$wgHooks['ArticleDeleteComplete'][] = 'GeoDataHooks::onArticleDeleteComplete'; |
| 31 | +$wgHooks['LinksUpdate'][] = 'GeoDataHooks::onLinksUpdate'; |
| 32 | + |
| 33 | +/** |
| 34 | + * Maximum radius for geospatial searches. |
| 35 | + * The greater this variable is, the louder your server ouches. |
| 36 | + */ |
| 37 | +$wgMaxGeoSearchRadius = 10000; // 10km |
Property changes on: trunk/extensions/GeoData/GeoData.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 38 | + native |
Index: trunk/extensions/GeoData/GeoMath.php |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class that performs basic coordinate calculations |
| 6 | + * Note that the formulas are useful only for our specific purposes, some of them may be |
| 7 | + * inaccurate for long distances. Oh well. |
| 8 | + * |
| 9 | + * All the functions that accept coordinates assume that they're in degrees, not radians. |
| 10 | + */ |
| 11 | +/* static */class GeoMath { |
| 12 | + const EARTH_RADIUS = 6371010; |
| 13 | + |
| 14 | + /** |
| 15 | + * Calculates distance between two coordinates |
| 16 | + * See https://en.wikipedia.org/wiki/Haversine_formula |
| 17 | + * |
| 18 | + * @param float $lat1 |
| 19 | + * @param float $lon1 |
| 20 | + * @param float $lat2 |
| 21 | + * @param float $lon2 |
| 22 | + * @return float Distance in meters |
| 23 | + */ |
| 24 | + public static function distance( $lat1, $lon1, $lat2, $lon2 ) { |
| 25 | + $lat1 = deg2rad( $lat1 ); |
| 26 | + $lon1 = deg2rad( $lon1 ); |
| 27 | + $lat2 = deg2rad( $lat2 ); |
| 28 | + $lon2 = deg2rad( $lon2 ); |
| 29 | + $sin1 = sin( ( $lat2 - $lat1 ) / 2 ); |
| 30 | + $sin2 = sin( ( $lon2 - $lon1 ) / 2 ); |
| 31 | + return 2 * self::EARTH_RADIUS * asin( sqrt( $sin1 * $sin1 + cos( $lat1 ) * cos( $lat2 ) * $sin2 * $sin2 ) ); |
| 32 | + } |
| 33 | + |
| 34 | + /** |
| 35 | + * Returns a bounding rectangle around a given point |
| 36 | + * |
| 37 | + * @param float $lat |
| 38 | + * @param float $lon |
| 39 | + * @param float $radius |
| 40 | + * @return Array |
| 41 | + */ |
| 42 | + public static function rectAround( $lat, $lon, $radius ) { |
| 43 | + if ( !$radius ) { |
| 44 | + return array( |
| 45 | + 'minLat' => $lat, 'maxLat' => $lat, |
| 46 | + 'minLon' => $lon, 'maxLon' => $lon |
| 47 | + ); |
| 48 | + } |
| 49 | + $r2lat = rad2deg( $radius / self::EARTH_RADIUS ); |
| 50 | + // @todo: doesn't work around poles, should we care? |
| 51 | + if ( abs( $lat ) < 89.9 ) { |
| 52 | + $r2lon = rad2deg( $radius / cos( deg2rad( $lat ) ) / self::EARTH_RADIUS ); |
| 53 | + } else { |
| 54 | + $r2lon = 0.1; |
| 55 | + } |
| 56 | + return array( |
| 57 | + 'minLat' => $lat - $r2lat, |
| 58 | + 'maxLat' => $lat + $r2lat, |
| 59 | + 'minLon' => $lon - $r2lon, |
| 60 | + 'maxLon' => $lon + $r2lon |
| 61 | + ); |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Sign function |
| 66 | + * |
| 67 | + * @param Float $x: Value to get sinng of |
| 68 | + * @return int |
| 69 | + */ |
| 70 | + public static function sign( $x ) { |
| 71 | + if ( $x >= 0 ) { |
| 72 | + return 1; |
| 73 | + } |
| 74 | + return -1; |
| 75 | + } |
| 76 | +} |
Property changes on: trunk/extensions/GeoData/GeoMath.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 77 | + native |
Index: trunk/extensions/GeoData/GeoDataHooks.php |
— | — | @@ -0,0 +1,154 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class GeoDataHooks { |
| 5 | + public static function onUnitTestsList( &$files ) { |
| 6 | + $dir = dirname( __FILE__ ) . "/tests"; |
| 7 | + $files[] = "$dir/ParseCoordTest.php"; |
| 8 | + $files[] = "$dir/GeoMathTest.php"; |
| 9 | + return true; |
| 10 | + } |
| 11 | + |
| 12 | + /** |
| 13 | + * ParserFirstCallInit hook handler |
| 14 | + * @see: https://www.mediawiki.org/wiki/Manual:Hooks/ParserFirstCallInit |
| 15 | + * @param Parser $parser |
| 16 | + */ |
| 17 | + public static function onParserFirstCallInit( &$parser ) { |
| 18 | + $parser->setFunctionHook( 'coordinate', 'GeoDataHooks::coordinateHandler', SFH_OBJECT_ARGS ); |
| 19 | + return true; |
| 20 | + } |
| 21 | + |
| 22 | + /** |
| 23 | + * LanguageGetMagic hook handler |
| 24 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/LanguageGetMagic |
| 25 | + * @param Array $magicWords |
| 26 | + * @param String $langCode |
| 27 | + */ |
| 28 | + public static function onLanguageGetMagic( &$magicWords, $langCode ) { |
| 29 | + $magicWords['coordinate'] = array( 0, 'coordinate' ); |
| 30 | + return true; |
| 31 | + } |
| 32 | + |
| 33 | + /** |
| 34 | + * Handler for the #coordinate parser function |
| 35 | + * |
| 36 | + * @param Parser $parser |
| 37 | + * @param PPFrame $frame |
| 38 | + * @param Array $args |
| 39 | + * @return Mixed |
| 40 | + */ |
| 41 | + public static function coordinateHandler( $parser, $frame, $args ) { |
| 42 | + $output = $parser->getOutput(); |
| 43 | + self::prepareOutput( $output ); |
| 44 | + |
| 45 | + $unnamed = array(); |
| 46 | + $named = array(); |
| 47 | + $first = trim( $frame->expand( array_shift( $args ) ) ); |
| 48 | + if ( $first !== '' ) { |
| 49 | + $unnamed[] = $first; |
| 50 | + } |
| 51 | + foreach ( $args as $arg ) { |
| 52 | + $bits = $arg->splitArg(); |
| 53 | + $value = trim( $frame->expand( $bits['value'] ) ); |
| 54 | + if ( $bits['index'] === '' ) { |
| 55 | + $named[trim( $frame->expand( $bits['name'] ) )] = $value; |
| 56 | + } else { |
| 57 | + $unnamed[] = $value; |
| 58 | + } |
| 59 | + } |
| 60 | + $status = GeoData::parseCoordinates( $unnamed ); |
| 61 | + if ( $status->isGood() ) { |
| 62 | + $coord = $status->value; |
| 63 | + $status = GeoData::parseTagArgs( $coord, $named ); |
| 64 | + if ( $status->isGood() ) { |
| 65 | + $status = self::applyCoord( $output, $coord, $status->value ); |
| 66 | + if ( $status->isGood() ) { |
| 67 | + return ''; |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + // Apply tracking category |
| 72 | + if ( !$output->geoData['failures'] ) { |
| 73 | + $output->geoData['failures'] = true; |
| 74 | + $output->addCategory( |
| 75 | + wfMessage( 'geodata-broken-tags-category' )->inContentLanguage()->text(), |
| 76 | + $parser->getTitle()->getText() |
| 77 | + ); |
| 78 | + } |
| 79 | + return array( "<span class=\"error\">{$status->getWikiText()}</span>", 'noparse' => false ); |
| 80 | + } |
| 81 | + |
| 82 | + /** |
| 83 | + * Make sure that parser output has our storage array |
| 84 | + * @param ParserOutput $output |
| 85 | + */ |
| 86 | + private static function prepareOutput( ParserOutput $output ) { |
| 87 | + if ( !isset( $output->geoData ) ) { |
| 88 | + $output->geoData = array( |
| 89 | + 'primary' => false, |
| 90 | + 'secondary' => array(), |
| 91 | + 'failures' => false, |
| 92 | + ); |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Applies a coordinate to parser output |
| 98 | + * |
| 99 | + * @param ParserOutput $output |
| 100 | + * @param Coord $coord |
| 101 | + * @return Status: whether save went OK |
| 102 | + */ |
| 103 | + private static function applyCoord( ParserOutput $output, Coord $coord ) { |
| 104 | + if ( $coord->primary ) { |
| 105 | + if ( $output->geoData['primary'] ) { |
| 106 | + $output->geoData['secondary'][] = $coord; |
| 107 | + return Status::newFatal( 'geodata-multiple-primary' ); |
| 108 | + } else { |
| 109 | + $output->geoData['primary'] = $coord; |
| 110 | + } |
| 111 | + } else { |
| 112 | + $output->geoData['secondary'][] = $coord; |
| 113 | + } |
| 114 | + return Status::newGood(); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * ArticleDeleteComplete hook handler |
| 119 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/ArticleDeleteComplete |
| 120 | + * |
| 121 | + * @param Article $article |
| 122 | + * @param User $user |
| 123 | + * @param String $reason |
| 124 | + * @param int $id |
| 125 | + */ |
| 126 | + public static function onArticleDeleteComplete( &$article, User &$user, $reason, $id ) { |
| 127 | + $dbw = wfGetDB( DB_MASTER ); |
| 128 | + $dbw->delete( 'geo_tags', array( 'gt_page_id' => $id ), __METHOD__ ); |
| 129 | + return true; |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * LinksUpdate hook handler |
| 134 | + * @see https://www.mediawiki.org/wiki/Manual:Hooks/LinksUpdate |
| 135 | + * @param LinksUpdate $linksUpdate |
| 136 | + */ |
| 137 | + public static function onLinksUpdate( &$linksUpdate ) { |
| 138 | + $out = $linksUpdate->mParserOutput; |
| 139 | + //@todo: less dumb update |
| 140 | + $dbw = wfGetDB( DB_MASTER ); |
| 141 | + $dbw->delete( 'geo_tags', array( 'gt_page_id' => $linksUpdate->mId ), __METHOD__ ); |
| 142 | + if ( isset( $out->geoData ) ) { |
| 143 | + $geoData = $out->geoData; |
| 144 | + $data = array(); |
| 145 | + if ( $geoData['primary'] ) { |
| 146 | + $data[] = $geoData['primary']->getRow( $linksUpdate->mId ); |
| 147 | + } |
| 148 | + foreach ( $geoData['secondary'] as $coord ) { |
| 149 | + $data[] = $coord->getRow( $linksUpdate->mId ); |
| 150 | + } |
| 151 | + $dbw->insert( 'geo_tags', $data, __METHOD__ ); |
| 152 | + } |
| 153 | + return true; |
| 154 | + } |
| 155 | +} |
Property changes on: trunk/extensions/GeoData/GeoDataHooks.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 156 | + native |