Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.36% covered (success)
96.36%
53 / 55
91.67% covered (success)
91.67%
11 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Coord
96.36% covered (success)
96.36%
53 / 55
91.67% covered (success)
91.67%
11 / 12
29
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
4
 newFromRow
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 getGlobeObj
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sameGlobe
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 distanceTo
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getRow
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 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    /** Name of planet or other astronomic body on which the coordinates reside */
36    public string $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 meters */
62    public $distance;
63
64    /**
65     * @param float $lat
66     * @param float $lon
67     * @param Globe|string $globe
68     * @param array<string,mixed> $extraFields
69     */
70    public function __construct( $lat, $lon, $globe = Globe::EARTH, $extraFields = [] ) {
71        $this->lat = (float)$lat;
72        $this->lon = (float)$lon;
73        $this->globe = $globe instanceof Globe ? $globe->getName() : $globe;
74
75        foreach ( $extraFields as $key => $value ) {
76            if ( isset( self::FIELD_MAPPING[$key] ) ) {
77                $this->$key = $value;
78            }
79        }
80    }
81
82    /**
83     * Constructs a Coord object from a database row
84     *
85     * @param \stdClass $row
86     * @return self
87     */
88    public static function newFromRow( $row ): self {
89        $c = new self(
90            (float)$row->gt_lat,
91            (float)$row->gt_lon,
92            $row->gt_globe
93        );
94
95        $c->id = $row->gt_id ?? 0;
96        $c->primary = (bool)( $row->gt_primary ?? false );
97        $c->dim = ( $row->gt_dim ?? null ) === null ? null : (int)$row->gt_dim;
98        $c->type = $row->gt_type ?? null;
99        $c->name = $row->gt_name ?? null;
100        $c->country = $row->gt_country ?? null;
101        $c->region = $row->gt_region ?? null;
102
103        return $c;
104    }
105
106    public function getGlobeObj(): Globe {
107        return new Globe( $this->globe );
108    }
109
110    /**
111     * @param self|Globe|string $other
112     * @return bool
113     */
114    public function sameGlobe( $other ): bool {
115        if ( $other instanceof self ) {
116            $other = $other->globe;
117        }
118        return $this->getGlobeObj()->equalsTo( $other );
119    }
120
121    /**
122     * Compares this coordinates with the given coordinates
123     *
124     * @param self|null $other Coordinate to compare with
125     * @param int $precision Comparison precision
126     * @return bool
127     */
128    public function equalsTo( $other, $precision = 6 ): bool {
129        return $other !== null
130            && round( $this->lat, $precision ) == round( $other->lat, $precision )
131            && round( $this->lon, $precision ) == round( $other->lon, $precision )
132            && $this->sameGlobe( $other );
133    }
134
135    /**
136     * Compares all the fields of this object with the given coordinates object
137     *
138     * @param self $coord Coordinate to compare with
139     * @param int $precision Comparison precision
140     * @return bool
141     */
142    public function fullyEqualsTo( $coord, $precision = 6 ): bool {
143        return $this->equalsTo( $coord, $precision )
144            && $this->primary == $coord->primary
145            && $this->dim === $coord->dim
146            && $this->type === $coord->type
147            && $this->name === $coord->name
148            && $this->country === $coord->country
149            && $this->region === $coord->region;
150    }
151
152    /**
153     * Checks whether current coordinates are within current globe's allowed range
154     */
155    public function isValid(): bool {
156        return $this->getGlobeObj()->coordinatesAreValid( $this->lat, $this->lon );
157    }
158
159    /**
160     * Calculates the distance between this and another pair of coordinates. Assumes the two are on
161     * the same globe. No exception is thrown if this is not the case!
162     *
163     * @param Coord $coord
164     * @return float Distance in meters
165     */
166    public function distanceTo( Coord $coord ): float {
167        return Math::distance( $this->lat, $this->lon, $coord->lat, $coord->lon,
168            $this->getGlobeObj()->getRadius() );
169    }
170
171    /**
172     * Returns this object's representation suitable for insertion into the DB via Databse::insert()
173     *
174     * @param int $pageId ID of page associated with this coordinate
175     * @param int|null $indexGranularity E.g. 10 for 1/10 of a degree
176     * @return array Associative array in format 'field' => 'value'
177     */
178    public function getRow( $pageId, $indexGranularity ): array {
179        $row = [ 'gt_page_id' => $pageId ];
180        foreach ( self::FIELD_MAPPING as $field => $column ) {
181            $row[$column] = $this->$field;
182        }
183        if ( $indexGranularity ) {
184            $row['gt_lat_int'] = (int)round( $this->lat * $indexGranularity );
185            $row['gt_lon_int'] = (int)round( $this->lon * $indexGranularity );
186        }
187        return $row;
188    }
189
190    /**
191     * Returns these coordinates as an associative array
192     */
193    public function getAsArray(): array {
194        $result = [];
195        foreach ( self::FIELD_MAPPING as $field => $_ ) {
196            $result[$field] = $this->$field;
197        }
198        return $result;
199    }
200
201    public function jsonSerialize(): array {
202        return $this->getAsArray();
203    }
204
205    /**
206     * Instantiate a Coord from $json array created with self::jsonSerialize.
207     *
208     * @internal
209     * @see jsonSerialize
210     */
211    public static function newFromJson( array $json ): self {
212        return new self(
213            $json['lat'],
214            $json['lon'],
215            $json['globe'],
216            $json
217        );
218    }
219}