summaryrefslogtreecommitdiffstats
path: root/lib/Cepis/Geo.pm
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 14:40:05 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 14:40:05 -0300
commit0bc578d6d334c7f1ccaf329cd61bb8bce76b7f59 (patch)
tree86f73108f0432fd8330edcaab8b8ae7018322dee /lib/Cepis/Geo.pm
downloadperl-0.1.0.tar.gz
Initial importv0.1.0
Diffstat (limited to 'lib/Cepis/Geo.pm')
-rw-r--r--lib/Cepis/Geo.pm148
1 files changed, 148 insertions, 0 deletions
diff --git a/lib/Cepis/Geo.pm b/lib/Cepis/Geo.pm
new file mode 100644
index 0000000..bee2515
--- /dev/null
+++ b/lib/Cepis/Geo.pm
@@ -0,0 +1,148 @@
+package Cepis::Geo;
+
+use strict;
+use warnings;
+
+use Scalar::Util ();
+
+our $VERSION = '0.1.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<undef>; the API omits the whole block rather
+than sending null.
+
+The point is the B<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.
+
+B<Always check C<precisao> before using C<lat>/C<lon> 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<raio_m> lands around 100–150 m. Good for
+maps and for distance.
+
+=item C<"localidade">
+
+The CEP I<is> the whole municipality (a city-wide general CEP), and the point is
+its centroid. C<raio_m> runs from 7 km to 40 km. Computing shipping from this is
+wrong by tens of kilometres.
+
+=back
+
+C<raio_m> 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<Cepis::Address> for why.
+
+=head1 CONSTRUCTOR
+
+=head2 from_json
+
+ my $geo = Cepis::Geo->from_json( $hashref );
+
+Builds from the C<geo> block the API returned. Returns C<undef> when the block
+is missing, is not a hash, or is malformed — a bad C<geo> 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<Cepis>, L<Cepis::Address>
+
+=head1 LICENCE
+
+ISC. Copyright (c) 2026 Murilo Ijanc E<lt>[email protected]<gt>.
+
+=cut