Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.64% covered (warning)
63.64%
42 / 66
58.33% covered (warning)
58.33%
7 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Coord
63.64% covered (warning)
63.64%
42 / 66
58.33% covered (warning)
58.33%
7 / 12
69.44
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 newFromRow
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 getGlobeObj
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 equalsTo
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 fullyEqualsTo
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
7
 isValid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bboxAround
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
3.02
 distanceTo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRow
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getAsArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 jsonSerialize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newFromJson
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace GeoData;
4
5use JsonSerializable;
6
7/**
8 * Class representing coordinates
9 */
10class Coord implements JsonSerializable {
11
12    /** Mapping from properties of this class to database columns */
13    public const FIELD_MAPPING = [
14        'id' => 'gt_id',
15        'lat' => 'gt_lat',
16        'lon' => 'gt_lon',
17        'globe' => 'gt_globe',
18        'primary' => 'gt_primary',
19        'dim' => 'gt_dim',
20        'type' => 'gt_type',
21        'name' => 'gt_name',
22        'country' => 'gt_country',
23        'region' => 'gt_region',
24    ];
25
26    /** @var float Latitude of the point in degrees */
27    public $lat;
28
29    /** @var float Longitude of the point in degrees */
30    public $lon;
31
32    /** @var int Tag id, needed for selective replacement and paging */
33    public $id;
34
35    /** @var string Name of planet or other astronomic body on which the coordinates reside */
36    public $globe;
37
38    /** @var bool Whether this coordinate is primary
39     * (defines the principal location of article subject) or secondary (just mentioned in text)
40     */
41    public $primary = false;
42
43    /** @var int|null Approximate viewing radius in meters, gives an idea how large the object is */
44    public $dim;
45
46    /** @var string|null Type of the point */
47    public $type;
48
49    /** @var string|null Point name on the map */
50    public $name;
51
52    /** @var string|null Two character ISO 3166-1 alpha-2 country code */
53    public $country;
54
55    /** @var string|null Second part of ISO 3166-2 region code, up to 3 alphanumeric chars */
56    public $region;
57
58    /** @var int */
59    public $pageId;
60
61    /** @var float Distance in metres */
62    public $distance;
63
64    /**
65     * @param float $lat
66     * @param float $lon
67     * @param string|null $globe
68     * @param array $extraFields
69     */
70    public function __construct( $lat, $lon, $globe = null, $extraFields = [] ) {
71        global $wgDefaultGlobe;
72
73        $this->lat = (float)$lat;
74        $this->lon = (float)$lon;
75        $this->globe = $globe ?? $wgDefaultGlobe;
76
77        foreach ( $extraFields as $key => $value ) {
78            if ( isset( self::FIELD_MAPPING[$key] ) ) {
79                $this->$key = $value;
80            }
81        }
82    }
83
84    /**
85     * Constructs a Coord object from a database row
86     *
87     * @param \stdClass $row
88     * @return self
89     */
90    public static function newFromRow( $row ): self {
91        $c = new self(
92            (float)$row->gt_lat,
93            (float)$row->gt_lon,
94            $row->gt_globe ?? null
95        );
96
97        $c->id = $row->gt_id ?? 0;
98        $c->primary = (bool)( $row->gt_primary ?? false );
99        $c->dim = ( $row->gt_dim ?? null ) === null ? null : (int)$row->gt_dim;
100        $c->type = $row->gt_type ?? null;
101        $c->name = $row->gt_name ?? null;
102        $c->country = $row->gt_country ?? null;
103        $c->region = $row->gt_region ?? null;
104
105        return $c;
106    }
107
108    /**
109     * @return Globe
110     */
111    public function getGlobeObj(): Globe {
112        return new Globe( $this->globe );
113    }
114
115    /**
116     * Compares this coordinates with the given coordinates
117     *
118     * @param self|null $coord Coordinate to compare with
119     * @param int $precision Comparison precision
120     * @return bool
121     */
122    public function equalsTo( $coord, $precision = 6 ): bool {
123        return $coord !== null
124            && round( $this->lat, $precision ) == round( $coord->lat, $precision )
125            && round( $this->lon, $precision ) == round( $coord->lon, $precision )
126            && $this->globe === $coord->globe;
127    }
128
129    /**
130     * Compares all the fields of this object with the given coordinates object
131     *
132     * @param self $coord Coordinate to compare with
133     * @param int $precision Comparison precision
134     * @return bool
135     */
136    public function fullyEqualsTo( $coord, $precision = 6 ): bool {
137        return $this->equalsTo( $coord, $precision )
138            && $this->primary == $coord->primary
139            && $this->dim === $coord->dim
140            && $this->type === $coord->type
141            && $this->name === $coord->name
142            && $this->country === $coord->country
143            && $this->region === $coord->region;
144    }
145
146    /**
147     * Checks whether current coordinates are within current globe's allowed range
148     *
149     * @return bool
150     */
151    public function isValid(): bool {
152        return $this->getGlobeObj()->coordinatesAreValid( $this->lat, $this->lon );
153    }
154
155    /**
156     * Returns a bounding rectangle around this coordinate
157     *
158     * @param float $radius
159     * @return BoundingBox
160     */
161    public function bboxAround( $radius ): BoundingBox {
162        if ( $radius <= 0 ) {
163            return new BoundingBox( $this->lat, $this->lon, $this->lat, $this->lon, $this->globe );
164        }
165        $r2lat = rad2deg( $radius / Math::EARTH_RADIUS );
166        // @todo: doesn't work around poles, should we care?
167        if ( abs( $this->lat ) < 89.9 ) {
168            $r2lon = rad2deg( $radius / cos( deg2rad( $this->lat ) ) / Math::EARTH_RADIUS );
169        } else {
170            $r2lon = 0.1;
171        }
172        $res = new BoundingBox( $this->lat - $r2lat,
173            $this->lon - $r2lon,
174            $this->lat + $r2lat,
175            $this->lon + $r2lon,
176            $this->globe
177        );
178        Math::wrapAround( $res->lat1, $res->lat2, -90, 90 );
179        Math::wrapAround( $res->lon1, $res->lon2, -180, 180 );
180        return $res;
181    }
182
183    /**
184     * Returns a distance from these coordinates to another ones
185     *
186     * @param Coord $coord
187     * @return float Distance in metres
188     */
189    public function distanceTo( Coord $coord ) {
190        return Math::distance( $this->lat, $this->lon, $coord->lat, $coord->lon );
191    }
192
193    /**
194     * Returns this object's representation suitable for insertion into the DB via Databse::insert()
195     * @param int $pageId ID of page associated with this coordinate
196     * @return array Associative array in format 'field' => 'value'
197     */
198    public function getRow( $pageId ): array {
199        global $wgGeoDataIndexGranularity, $wgGeoDataBackend;
200        $row = [ 'gt_page_id' => $pageId ];
201        foreach ( self::FIELD_MAPPING as $field => $column ) {
202            $row[$column] = $this->$field;
203        }
204        if ( $wgGeoDataBackend == 'db' ) {
205            $row['gt_lat_int'] = round( $this->lat * $wgGeoDataIndexGranularity );
206            $row['gt_lon_int'] = round( $this->lon * $wgGeoDataIndexGranularity );
207        }
208        return $row;
209    }
210
211    /**
212     * Returns these coordinates as an associative array
213     * @return array
214     */
215    public function getAsArray(): array {
216        $result = [];
217        foreach ( self::FIELD_MAPPING as $field => $_ ) {
218            $result[$field] = $this->$field;
219        }
220        return $result;
221    }
222
223    /**
224     * @return array
225     */
226    public function jsonSerialize(): array {
227        return $this->getAsArray();
228    }
229
230    /**
231     * Instantiate a Coord from $json array created with self::jsonSerialize.
232     *
233     * @internal
234     * @see jsonSerialize
235     * @param array $json
236     * @return self
237     */
238    public static function newFromJson( array $json ): self {
239        return new self(
240            $json['lat'],
241            $json['lon'],
242            $json['globe'],
243            $json
244        );
245    }
246}