Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
63.64% |
42 / 66 |
|
58.33% |
7 / 12 |
CRAP | |
0.00% |
0 / 1 |
Coord | |
63.64% |
42 / 66 |
|
58.33% |
7 / 12 |
69.44 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
newFromRow | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
getGlobeObj | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
equalsTo | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
fullyEqualsTo | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
7 | |||
isValid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
bboxAround | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
3.02 | |||
distanceTo | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRow | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getAsArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
jsonSerialize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromJson | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace GeoData; |
4 | |
5 | use JsonSerializable; |
6 | |
7 | /** |
8 | * Class representing coordinates |
9 | */ |
10 | class 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 | } |