package Cepis::Geo; use strict; use warnings; use Scalar::Util (); our $VERSION = '0.2.0'; sub from_json { my ( $class, $data ) = @_; return undef unless ref $data eq 'HASH'; for my $key (qw(lat lon raio_m precisao)) { return undef unless defined $data->{$key}; } for my $key (qw(lat lon raio_m)) { return undef unless Scalar::Util::looks_like_number( $data->{$key} ); } return undef if ref $data->{precisao}; return bless { lat => 0 + $data->{lat}, lon => 0 + $data->{lon}, raio_m => int( 0 + $data->{raio_m} ), precisao => "$data->{precisao}", }, $class; } sub lat { return $_[0]->{lat} } sub lon { return $_[0]->{lon} } sub raio_m { return $_[0]->{raio_m} } sub precisao { return $_[0]->{precisao} } sub is_precise { return $_[0]->{precisao} eq 'logradouro' ? 1 : 0 } sub to_hash { my ($self) = @_; return { lat => $self->{lat}, lon => $self->{lon}, raio_m => $self->{raio_m}, precisao => $self->{precisao}, }; } 1; __END__ =encoding UTF-8 =head1 NAME Cepis::Geo - the coordinate of a CEP =head1 SYNOPSIS my $address = Cepis::lookup('01310100'); if ( my $geo = $address->geo ) { printf "%s %s\n", $geo->lat, $geo->lon; # -23.567202 -46.649659 print $geo->raio_m, "\n"; # 1084 print $geo->precisao, "\n"; # logradouro } =head1 DESCRIPTION The CEP's coordinate, when the database has one — about 57% of them. When there is none, C<< $address->geo >> is C; the API omits the whole block rather than sending null. The point is the B 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. B before using C/C for distance.> There are two kinds of point and the difference between them is tens of kilometres: =over 4 =item C<"logradouro"> The point is a stretch of street. C lands around 100–150 m. Good for maps and for distance. =item C<"localidade"> The CEP I the whole municipality (a city-wide general CEP), and the point is its centroid. C runs from 7 km to 40 km. Computing shipping from this is wrong by tens of kilometres. =back C is the radius containing 90% of that CEP's addresses. It exists precisely so that error cannot pass unnoticed. Field names are the API's own, in Portuguese. See L for why. =head1 CONSTRUCTOR =head2 from_json my $geo = Cepis::Geo->from_json( $hashref ); Builds from the C block the API returned. Returns C when the block is missing, is not a hash, or is malformed — a bad C is not worth losing the whole address over, since the rest of the response is still useful. =head1 ACCESSORS =head2 lat Latitude, as a number. =head2 lon Longitude, as a number. =head2 raio_m Radius in metres containing 90% of that CEP's addresses, as an integer. =head2 precisao Either C<"logradouro"> or C<"localidade">. Read the warning above before using the coordinate for anything metric. =head2 is_precise True when the coordinate points at a street rather than a municipality. Shorthand for C<< $geo->precisao eq 'logradouro' >>, which is the check to make before using the point to measure distance. =head2 to_hash The four fields as a plain hashref. =head1 SEE ALSO L, L =head1 LICENCE ISC. Copyright (c) 2026 Murilo Ijanc Emurilo@ijanc.orgE. =cut