diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-26 14:58:55 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-26 14:58:55 -0300 |
| commit | 64764fb9fac0227f8e0443e7c7519d73aec275b9 (patch) | |
| tree | cb4fc56eeb6ecce53854f9c530609cf01ab3349f /src/Geo.php | |
| download | php-0.1.0.tar.gz | |
Initial importv0.1.0
Diffstat (limited to 'src/Geo.php')
| -rw-r--r-- | src/Geo.php | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/src/Geo.php b/src/Geo.php new file mode 100644 index 0000000..c677622 --- /dev/null +++ b/src/Geo.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +namespace Cepis; + +/** + * The CEP's coordinate, when the database has one. + * + * The point is the **median** of the coordinates of that CEP's addresses, not + * the mean: one badly geocoded address 40 km away shifts a mean and does not + * move a median. + * + * Always check {@see Geo::$precisao} before using the coordinate for distance: + * + * - `"logradouro"`: the point is a stretch of street. `raio_m` lands around + * 100–150 m. Good for maps and for distance. + * - `"localidade"`: the CEP *is* the whole municipality (a city-wide general + * CEP), and the point is its centroid. `raio_m` runs from 7 km to 40 km. + * Computing shipping from this is wrong by tens of kilometres. + * + * `raio_m` is the radius containing 90% of that CEP's addresses. It exists + * precisely so that error cannot pass unnoticed. + */ +final class Geo +{ + /** + * Property names are the API's own JSON keys, `raio_m` included. See + * {@see Address} for why they are not translated or camel-cased. + * + * @param string $precisao Kept a plain string rather than an enum so a + * precision level the API adds later cannot break this client. + */ + public function __construct( + public readonly float $lat, + public readonly float $lon, + public readonly int $raio_m, + public readonly string $precisao, + ) { + } + + /** + * Build from the decoded `geo` block, or `null` when it is missing or + * malformed. + * + * A malformed `geo` is not worth losing the whole address over, since the + * rest of the response is still useful. + */ + public static function fromArray(mixed $data): ?self + { + if (!is_array($data)) { + return null; + } + foreach (['lat', 'lon', 'raio_m', 'precisao'] as $key) { + if (!isset($data[$key]) || !is_scalar($data[$key])) { + return null; + } + } + if (!is_numeric($data['lat']) || !is_numeric($data['lon']) || !is_numeric($data['raio_m'])) { + return null; + } + + return new self( + lat: (float) $data['lat'], + lon: (float) $data['lon'], + raio_m: (int) (float) $data['raio_m'], + precisao: (string) $data['precisao'], + ); + } + + /** + * `true` when the coordinate points at a street, not a municipality. + * + * Shorthand for `precisao === "logradouro"`, which is the check to make + * before using the point to measure distance. + */ + public function isPrecise(): bool + { + return $this->precisao === 'logradouro'; + } + + /** + * @return array{lat: float, lon: float, raio_m: int, precisao: string} + */ + public function toArray(): array + { + return [ + 'lat' => $this->lat, + 'lon' => $this->lon, + 'raio_m' => $this->raio_m, + 'precisao' => $this->precisao, + ]; + } +} |