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