Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
12.50% |
9 / 72 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
| QueryGeoSearchDb | |
12.50% |
9 / 72 |
|
25.00% |
1 / 4 |
346.24 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| run | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
272 | |||
| addCoordFilter | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
| intRange | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace GeoData\Api; |
| 4 | |
| 5 | use GeoData\BoundingBox; |
| 6 | use GeoData\Coord; |
| 7 | use GeoData\Globe; |
| 8 | use MediaWiki\Api\ApiPageSet; |
| 9 | use MediaWiki\Api\ApiQuery; |
| 10 | use MediaWiki\Title\Title; |
| 11 | |
| 12 | class QueryGeoSearchDb extends QueryGeoSearch { |
| 13 | public function __construct( |
| 14 | ApiQuery $query, |
| 15 | string $moduleName, |
| 16 | ) { |
| 17 | parent::__construct( $query, $moduleName ); |
| 18 | } |
| 19 | |
| 20 | /** |
| 21 | * @param ApiPageSet|null $resultPageSet |
| 22 | */ |
| 23 | protected function run( $resultPageSet = null ): void { |
| 24 | parent::run( $resultPageSet ); |
| 25 | $params = $this->extractRequestParams(); |
| 26 | |
| 27 | if ( $params['sort'] === 'relevance' ) { |
| 28 | $this->dieWithError( 'apierror-geodata-norelevancesort', 'no-relevance-sort' ); |
| 29 | } |
| 30 | |
| 31 | $this->addTables( 'geo_tags' ); |
| 32 | $this->addFields( [ 'gt_lat', 'gt_lon', 'gt_primary' ] ); |
| 33 | foreach ( $params['prop'] as $prop ) { |
| 34 | if ( isset( Coord::FIELD_MAPPING[$prop] ) ) { |
| 35 | $this->addFields( Coord::FIELD_MAPPING[$prop] ); |
| 36 | } |
| 37 | } |
| 38 | $this->addWhereFld( 'gt_globe', $this->coord->globe ); |
| 39 | $this->addWhere( 'gt_page_id = page_id' ); |
| 40 | $dbr = $this->getDB(); |
| 41 | if ( $this->idToExclude ) { |
| 42 | $this->addWhere( $dbr->expr( 'gt_page_id', '!=', $this->idToExclude ) ); |
| 43 | } |
| 44 | if ( isset( $params['maxdim'] ) ) { |
| 45 | $this->addWhere( $dbr->expr( 'gt_dim', '<', intval( $params['maxdim'] ) ) ); |
| 46 | } |
| 47 | $primary = $params['primary']; |
| 48 | $this->addWhereIf( [ 'gt_primary' => intval( $primary === 'primary' ) ], $primary !== 'all' ); |
| 49 | |
| 50 | $this->addCoordFilter(); |
| 51 | |
| 52 | $limit = $params['limit']; |
| 53 | |
| 54 | $res = $this->select( __METHOD__ ); |
| 55 | |
| 56 | $rows = []; |
| 57 | foreach ( $res as $row ) { |
| 58 | $row->dist = $this->coord->distanceTo( Coord::newFromRow( $row ) ); |
| 59 | $rows[] = $row; |
| 60 | } |
| 61 | // sort in PHP because sorting via SQL would involve a filesort |
| 62 | usort( $rows, static function ( $row1, $row2 ) { |
| 63 | return $row1->dist - $row2->dist; |
| 64 | } ); |
| 65 | $result = $this->getResult(); |
| 66 | foreach ( $rows as $row ) { |
| 67 | if ( !$limit-- ) { |
| 68 | break; |
| 69 | } |
| 70 | if ( $resultPageSet === null ) { |
| 71 | $title = Title::newFromRow( $row ); |
| 72 | $vals = [ |
| 73 | 'pageid' => intval( $row->page_id ), |
| 74 | 'ns' => $title->getNamespace(), |
| 75 | 'title' => $title->getPrefixedText(), |
| 76 | 'lat' => floatval( $row->gt_lat ), |
| 77 | 'lon' => floatval( $row->gt_lon ), |
| 78 | 'dist' => round( $row->dist, 1 ), |
| 79 | 'primary' => boolval( $row->gt_primary ), |
| 80 | ]; |
| 81 | foreach ( $params['prop'] as $prop ) { |
| 82 | $column = Coord::FIELD_MAPPING[$prop] ?? null; |
| 83 | if ( $column && isset( $row->$column ) ) { |
| 84 | // Don't output default globe |
| 85 | if ( !( $prop === 'globe' && $row->$column === Globe::EARTH ) ) { |
| 86 | $vals[$prop] = $row->$column; |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals ); |
| 91 | if ( !$fit ) { |
| 92 | break; |
| 93 | } |
| 94 | } else { |
| 95 | $resultPageSet->processDbRow( $row ); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | protected function addCoordFilter(): void { |
| 101 | $bbox = $this->bbox ?: BoundingBox::newFromRadius( $this->coord, $this->radius ); |
| 102 | $coord1 = $bbox->topLeft(); |
| 103 | $coord2 = $bbox->bottomRight(); |
| 104 | $this->addWhereFld( 'gt_lat_int', $this->intRange( $coord1->lat, $coord2->lat ) ); |
| 105 | $this->addWhereFld( 'gt_lon_int', $this->intRange( $coord1->lon, $coord2->lon ) ); |
| 106 | |
| 107 | $this->addWhereRange( 'gt_lat', 'newer', (string)$coord1->lat, (string)$coord2->lat, false ); |
| 108 | if ( $coord1->lon > $coord2->lon ) { |
| 109 | $this->addWhere( "gt_lon < {$coord2->lon} OR gt_lon > {$coord1->lon}" ); |
| 110 | } else { |
| 111 | $this->addWhereRange( 'gt_lon', 'newer', (string)$coord1->lon, (string)$coord2->lon, false ); |
| 112 | } |
| 113 | $this->addOption( 'USE INDEX', [ 'geo_tags' => 'gt_spatial' ] ); |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Returns a range of tenths of degree |
| 118 | * |
| 119 | * @param float $start |
| 120 | * @param float $end |
| 121 | * @return int[] |
| 122 | */ |
| 123 | public function intRange( float $start, float $end ): array { |
| 124 | $granularity = $this->getConfig()->get( 'GeoDataIndexGranularity' ); |
| 125 | $start = round( $start * $granularity ); |
| 126 | $end = round( $end * $granularity ); |
| 127 | // @todo: works only on Earth |
| 128 | if ( $start > $end ) { |
| 129 | return array_merge( |
| 130 | range( -180 * $granularity, $end ), |
| 131 | range( $start, 180 * $granularity ) |
| 132 | ); |
| 133 | } else { |
| 134 | return range( $start, $end ); |
| 135 | } |
| 136 | } |
| 137 | } |