Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
25.00% covered (danger)
25.00%
1 / 4
CRAP
13.04% covered (danger)
13.04%
9 / 69
QueryGeoSearchDb
0.00% covered (danger)
0.00%
0 / 1
25.00% covered (danger)
25.00%
1 / 4
370.83
13.04% covered (danger)
13.04%
9 / 69
 __construct
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 run
0.00% covered (danger)
0.00%
0 / 1
272
0.00% covered (danger)
0.00%
0 / 49
 addCoordFilter
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 9
 intRange
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
9 / 9
<?php
namespace GeoData\Api;
use ApiPageSet;
use ApiQuery;
use GeoData\Coord;
use GeoData\Math;
use Title;
class QueryGeoSearchDb extends QueryGeoSearch {
    /**
     * @param ApiQuery $query
     * @param string $moduleName
     */
    public function __construct( ApiQuery $query, $moduleName ) {
        parent::__construct( $query, $moduleName );
    }
    /**
     * @param ApiPageSet|null $resultPageSet
     */
    protected function run( $resultPageSet = null ): void {
        global $wgDefaultGlobe;
        parent::run( $resultPageSet );
        $params = $this->extractRequestParams();
        if ( $params['sort'] === 'relevance' ) {
            $this->dieWithError( 'apierror-geodata-norelevancesort', 'no-relevance-sort' );
        }
        $this->addTables( 'geo_tags' );
        $this->addFields( [ 'gt_lat', 'gt_lon', 'gt_primary' ] );
        foreach ( $params['prop'] as $prop ) {
            if ( isset( Coord::FIELD_MAPPING[$prop] ) ) {
                $this->addFields( Coord::FIELD_MAPPING[$prop] );
            }
        }
        $this->addWhereFld( 'gt_globe', $this->coord->globe );
        $this->addWhere( 'gt_page_id = page_id' );
        if ( $this->idToExclude ) {
            $this->addWhere( "gt_page_id <> {$this->idToExclude}" );
        }
        if ( isset( $params['maxdim'] ) ) {
            $this->addWhere( 'gt_dim < ' . intval( $params['maxdim'] ) );
        }
        $primary = $params['primary'];
        $this->addWhereIf( [ 'gt_primary' => intval( $primary === 'primary' ) ], $primary !== 'all' );
        $this->addCoordFilter();
        $limit = $params['limit'];
        $res = $this->select( __METHOD__ );
        $rows = [];
        foreach ( $res as $row ) {
            $row->dist = Math::distance( $this->coord->lat, $this->coord->lon, $row->gt_lat, $row->gt_lon );
            $rows[] = $row;
        }
        // sort in PHP because sorting via SQL would involve a filesort
        usort( $rows, static function ( $row1, $row2 ) {
            return $row1->dist - $row2->dist;
        } );
        $result = $this->getResult();
        foreach ( $rows as $row ) {
            if ( !$limit-- ) {
                break;
            }
            if ( $resultPageSet === null ) {
                $title = Title::newFromRow( $row );
                $vals = [
                    'pageid' => intval( $row->page_id ),
                    'ns' => $title->getNamespace(),
                    'title' => $title->getPrefixedText(),
                    'lat' => floatval( $row->gt_lat ),
                    'lon' => floatval( $row->gt_lon ),
                    'dist' => round( $row->dist, 1 ),
                    'primary' => boolval( $row->gt_primary ),
                ];
                foreach ( $params['prop'] as $prop ) {
                    $column = Coord::FIELD_MAPPING[$prop] ?? null;
                    if ( $column && isset( $row->$column ) ) {
                        // Don't output default globe
                        if ( !( $prop === 'globe' && $row->$column === $wgDefaultGlobe ) ) {
                            $vals[$prop] = $row->$column;
                        }
                    }
                }
                $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
                if ( !$fit ) {
                    break;
                }
            } else {
                $resultPageSet->processDbRow( $row );
            }
        }
    }
    protected function addCoordFilter(): void {
        $bbox = $this->bbox ?: $this->coord->bboxAround( $this->radius );
        $this->addWhereFld( 'gt_lat_int', self::intRange( $bbox->lat1, $bbox->lat2 ) );
        $this->addWhereFld( 'gt_lon_int', self::intRange( $bbox->lon1, $bbox->lon2 ) );
        $this->addWhereRange( 'gt_lat', 'newer', (string)$bbox->lat1, (string)$bbox->lat2, false );
        if ( $bbox->lon1 > $bbox->lon2 ) {
            $this->addWhere( "gt_lon < {$bbox->lon2} OR gt_lon > {$bbox->lon1}" );
        } else {
            $this->addWhereRange( 'gt_lon', 'newer', (string)$bbox->lon1, (string)$bbox->lon2, false );
        }
        $this->addOption( 'USE INDEX', [ 'geo_tags' => 'gt_spatial' ] );
    }
    /**
     * Returns a range of tenths of degree
     *
     * @param float $start
     * @param float $end
     * @param int|null $granularity Defaults to $wgGeoDataIndexGranularity
     * @return int[]
     */
    public static function intRange( float $start, float $end, int $granularity = null ): array {
        global $wgGeoDataIndexGranularity;
        if ( !$granularity ) {
            $granularity = $wgGeoDataIndexGranularity;
        }
        $start = round( $start * $granularity );
        $end = round( $end * $granularity );
        // @todo: works only on Earth
        if ( $start > $end ) {
            return array_merge(
                range( -180 * $granularity, $end ),
                range( $start, 180 * $granularity )
            );
        } else {
            return range( $start, $end );
        }
    }
}