| File: | lib/Geo/Coder/Free/Utils.pm |
| Coverage: | 51.5% |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Geo::Coder::Free::Utils; | ||||||
| 2 | |||||||
| 3 | # VWF is licensed under GPL2.0 for personal use only | ||||||
| 4 | # njh@bandsman.co.uk | ||||||
| 5 | |||||||
| 6 - 14 | =head1 NAME Geo::Coder::Free::Utils - Random subroutines for Geo::Coder::Free =head1 VERSION Version 0.41 =cut | ||||||
| 15 | |||||||
| 16 | our $VERSION = '0.41'; | ||||||
| 17 | |||||||
| 18 | 2 2 2 | 101901 1 19 | use strict; | ||||
| 19 | 2 2 2 | 3 0 37 | use warnings; | ||||
| 20 | |||||||
| 21 | 2 2 2 | 2 2 30 | use Exporter qw(import); | ||||
| 22 | |||||||
| 23 | our @EXPORT = qw(create_disc_cache create_memory_cache distance); | ||||||
| 24 | |||||||
| 25 | 2 2 2 | 354 29924 27 | use CHI; | ||||
| 26 | 2 2 2 | 7 2 40 | use Data::Dumper; | ||||
| 27 | 2 2 2 | 1300 13202 56 | use DBI; | ||||
| 28 | 2 2 2 | 172 1423 6 | use Error; | ||||
| 29 | 2 2 2 | 293 284 3 | use Log::Any::Adapter; | ||||
| 30 | 2 2 2 | 116 3388 35 | use Params::Get; | ||||
| 31 | |||||||
| 32 | BEGIN { | ||||||
| 33 | 2 | 4 | Log::Any::Adapter->set('Log4perl'); | ||||
| 34 | } | ||||||
| 35 | |||||||
| 36 - 43 | =head1 SUBROUTINES/METHODS =head2 create_disc_cache Initialise a disc-based cache using the CHI module. Supports multiple cache drivers, including BerkeleyDB, DBI, and Redis. =cut | ||||||
| 44 | |||||||
| 45 | sub create_disc_cache { | ||||||
| 46 | 2 | 1 | 96263 | my $args = Params::Get::get_params(undef, @_); | |||
| 47 | |||||||
| 48 | 2 | 29 | my $config = $args->{'config'}; | ||||
| 49 | 2 | 4 | throw Error::Simple('config is not optional') unless($config); | ||||
| 50 | |||||||
| 51 | 2 | 2 | my $logger = $args->{'logger'}; | ||||
| 52 | 2 | 3 | my $driver = $config->{disc_cache}->{driver}; | ||||
| 53 | 2 | 3 | unless(defined($driver)) { | ||||
| 54 | 1 | 7 | my $root_dir = $ENV{'root_dir'} || $args->{'root_dir'} || $config->{disc_cache}->{root_dir} || $config->{'root_dir'}; | ||||
| 55 | 1 | 12 | throw Error::Simple('root_dir is not optional') unless($root_dir); | ||||
| 56 | |||||||
| 57 | 0 | 0 | if($logger) { | ||||
| 58 | 0 | 0 | $logger->debug(Data::Dumper->new([$config])->Dump()); | ||||
| 59 | 0 | 0 | $logger->info('disc_cache not defined in ', $config->{'config_path'}, ' falling back to BerkeleyDB'); | ||||
| 60 | } | ||||||
| 61 | 0 | 0 | return CHI->new(driver => 'BerkeleyDB', root_dir => $root_dir, namespace => $args->{'namespace'}); | ||||
| 62 | } | ||||||
| 63 | 1 | 1 | if($logger) { | ||||
| 64 | 0 | 0 | $logger->debug('disc cache via ', $config->{disc_cache}->{driver}, ', namespace: ', $args->{'namespace'}); | ||||
| 65 | } | ||||||
| 66 | |||||||
| 67 | my %chi_args = ( | ||||||
| 68 | on_get_error => 'warn', | ||||||
| 69 | on_set_error => 'die', | ||||||
| 70 | driver => $driver, | ||||||
| 71 | 1 | 3 | namespace => $args->{'namespace'} | ||||
| 72 | ); | ||||||
| 73 | |||||||
| 74 | # Don't do this because it takes a lot of complex configuration | ||||||
| 75 | # if($logger) { | ||||||
| 76 | # $chi_args{'on_set_error'} = 'log'; | ||||||
| 77 | # $chi_args{'on_get_error'} = 'log'; | ||||||
| 78 | # } | ||||||
| 79 | |||||||
| 80 | 1 | 5 | if($config->{disc_cache}->{server}) { | ||||
| 81 | 0 | 0 | my @servers; | ||||
| 82 | 0 | 0 | if($config->{disc_cache}->{server} =~ /,/) { | ||||
| 83 | 0 | 0 | @servers = split /,/, $config->{disc_cache}->{server}; | ||||
| 84 | } else { | ||||||
| 85 | 0 | 0 | $servers[0] = $config->{disc_cache}->{server}; | ||||
| 86 | 0 | 0 | if($config->{disc_cache}->{'port'}) { | ||||
| 87 | 0 | 0 | $servers[0] .= ':' . $config->{disc_cache}->{port}; | ||||
| 88 | } else { | ||||||
| 89 | 0 | 0 | throw Error::Simple('port is not optional in ' . $config->{'config_path'}); | ||||
| 90 | } | ||||||
| 91 | 0 | 0 | $chi_args{'server'} = $servers[0]; | ||||
| 92 | 0 | 0 | if($logger) { | ||||
| 93 | 0 | 0 | $logger->debug("First server: $servers[0]"); | ||||
| 94 | } | ||||||
| 95 | } | ||||||
| 96 | 0 | 0 | $chi_args{'servers'} = \@servers; | ||||
| 97 | } elsif($driver eq 'DBI') { | ||||||
| 98 | # Use the cache connection details in the configuration file | ||||||
| 99 | 0 | 0 | $chi_args{'dbh'} = DBI->connect($config->{disc_cache}->{connect}); | ||||
| 100 | 0 | 0 | if(!defined($chi_args{'dbh'})) { | ||||
| 101 | 0 | 0 | if($logger) { | ||||
| 102 | 0 | 0 | $logger->error($DBI::errstr); | ||||
| 103 | } | ||||||
| 104 | 0 | 0 | throw Error::Simple($DBI::errstr); | ||||
| 105 | } | ||||||
| 106 | 0 | 0 | $chi_args{'create_table'} = 1; | ||||
| 107 | } elsif($driver eq 'Redis') { | ||||||
| 108 | 0 | 0 | my %redis_options = ( | ||||
| 109 | reconnect => 60, | ||||||
| 110 | every => 1_000_000 | ||||||
| 111 | ); | ||||||
| 112 | 0 | 0 | $chi_args{'redis_options'} = \%redis_options; | ||||
| 113 | } elsif($driver ne 'Null') { | ||||||
| 114 | 1 | 5 | $chi_args{'root_dir'} = $ENV{'root_dir'} || $args->{'root_dir'} || $config->{disc_cache}->{root_dir}; | ||||
| 115 | 1 | 4 | throw Error::Simple('root_dir is not optional') unless($chi_args{'root_dir'}); | ||||
| 116 | 1 | 2 | if($logger) { | ||||
| 117 | 0 | 0 | $logger->debug("root_dir: $chi_args{root_dir}"); | ||||
| 118 | } | ||||||
| 119 | } | ||||||
| 120 | 1 | 4 | return CHI->new(%chi_args); | ||||
| 121 | } | ||||||
| 122 | |||||||
| 123 - 128 | =head2 create_memory_cache Initialise a memory-based cache using the CHI module. Supports multiple cache drivers, including SharedMem, Memory, and Redis. =cut | ||||||
| 129 | |||||||
| 130 | sub create_memory_cache { | ||||||
| 131 | 4 | 1 | 736 | my $args = Params::Get::get_params(undef, @_); | |||
| 132 | |||||||
| 133 | 4 | 43 | my $config = $args->{'config'}; | ||||
| 134 | 4 | 6 | throw Error::Simple('config is not optional') unless($config); | ||||
| 135 | |||||||
| 136 | 4 | 4 | my $logger = $args->{'logger'}; | ||||
| 137 | 4 | 8 | my $driver = $config->{'memory_cache'}->{driver}; | ||||
| 138 | 4 | 18 | unless(defined($driver)) { | ||||
| 139 | 0 | 0 | if($logger) { | ||||
| 140 | 0 | 0 | $logger->debug(Data::Dumper->new([$config])->Dump()); | ||||
| 141 | 0 | 0 | $logger->info('memory_cache not defined in ', $config->{'config_path'}, ' falling back to memory'); | ||||
| 142 | } | ||||||
| 143 | # return CHI->new(driver => 'Memcached', servers => [ '127.0.0.1:11211' ], namespace => $args->{'namespace'}); | ||||||
| 144 | # return CHI->new(driver => 'File', root_dir => '/tmp/cache', namespace => $args->{'namespace'}); | ||||||
| 145 | # return CHI->new(driver => 'SharedMem', max_size => 1024, shm_size => 16 * 1024, shm_key => 98766789, namespace => $args->{'namespace'}); | ||||||
| 146 | 0 | 0 | return CHI->new(driver => 'Memory', global => 1); | ||||
| 147 | } | ||||||
| 148 | 4 | 5 | if($logger) { | ||||
| 149 | 3 | 10 | $logger->debug('memory cache via ', $config->{memory_cache}->{driver}, ', namespace: ', $args->{'namespace'}); | ||||
| 150 | } | ||||||
| 151 | |||||||
| 152 | my %chi_args = ( | ||||||
| 153 | on_get_error => 'warn', | ||||||
| 154 | on_set_error => 'die', | ||||||
| 155 | driver => $driver, | ||||||
| 156 | 4 | 48 | namespace => $args->{'namespace'} | ||||
| 157 | ); | ||||||
| 158 | |||||||
| 159 | 4 | 7 | if($logger) { | ||||
| 160 | 3 | 2 | $chi_args{'on_set_error'} = 'log'; | ||||
| 161 | 3 | 3 | $chi_args{'on_get_error'} = 'log'; | ||||
| 162 | } | ||||||
| 163 | |||||||
| 164 | 4 | 18 | if($config->{memory_cache}->{server}) { | ||||
| 165 | 0 | 0 | my @servers; | ||||
| 166 | 0 | 0 | if($config->{memory_cache}->{server} =~ /,/) { | ||||
| 167 | 0 | 0 | @servers = split /,/, $config->{memory_cache}->{server}; | ||||
| 168 | } else { | ||||||
| 169 | 0 | 0 | $servers[0] = $config->{memory_cache}->{server}; | ||||
| 170 | 0 | 0 | if($config->{memory_cache}->{'port'}) { | ||||
| 171 | 0 | 0 | $servers[0] .= ':' . $config->{memory_cache}->{port}; | ||||
| 172 | } else { | ||||||
| 173 | 0 | 0 | throw Error::Simple('port is not optional in ' . $config->{'config_path'}); | ||||
| 174 | } | ||||||
| 175 | 0 | 0 | $chi_args{'server'} = $servers[0]; | ||||
| 176 | 0 | 0 | if($logger) { | ||||
| 177 | 0 | 0 | $logger->debug("First server: $servers[0]"); | ||||
| 178 | } | ||||||
| 179 | } | ||||||
| 180 | 0 | 0 | $chi_args{'servers'} = \@servers; | ||||
| 181 | } elsif($driver eq 'SharedMem') { | ||||||
| 182 | 0 | 0 | $chi_args{'shm_key'} = $args->{'shm_key'} || $config->{memory_cache}->{shm_key}; | ||||
| 183 | 0 | 0 | if(my $shm_size = ($args->{'shm_size'} || $config->{'memory_cache'}->{'shm_size'})) { | ||||
| 184 | 0 | 0 | $chi_args{'shm_size'} = $shm_size; | ||||
| 185 | } | ||||||
| 186 | 0 | 0 | if(my $max_size = ($args->{'max_size'} || $config->{'memory_cache'}->{'max_size'})) { | ||||
| 187 | 0 | 0 | $chi_args{'max_size'} = $max_size; | ||||
| 188 | } | ||||||
| 189 | } elsif($driver eq 'Memory') { | ||||||
| 190 | 0 | 0 | $chi_args{'global'} = $config->{'memory_cache'}->{'global'} || 0; | ||||
| 191 | } elsif($driver ne 'Null') { | ||||||
| 192 | 0 | 0 | $chi_args{'root_dir'} = $ENV{'root_dir'} || $args->{'root_dir'} || $config->{memory_cache}->{root_dir} || $config->{'root_dir'}; | ||||
| 193 | 0 | 0 | throw Error::Simple('root_dir is not optional') unless($chi_args{'root_dir'}); | ||||
| 194 | 0 | 0 | if($logger) { | ||||
| 195 | 0 | 0 | $logger->debug("root_dir: $chi_args{root_dir}"); | ||||
| 196 | } | ||||||
| 197 | } elsif($driver eq 'Redis') { | ||||||
| 198 | 0 | 0 | my %redis_options = ( | ||||
| 199 | reconnect => 60, | ||||||
| 200 | every => 1_000_000 | ||||||
| 201 | ); | ||||||
| 202 | 0 | 0 | $chi_args{'redis_options'} = \%redis_options; | ||||
| 203 | } | ||||||
| 204 | 4 | 14 | return CHI->new(%chi_args); | ||||
| 205 | } | ||||||
| 206 | |||||||
| 207 - 215 | =head2 distance Calculate the distance between two geographical points using latitude and longitude. Supports distance in kilometres (K), nautical miles (N), or miles. From L<http://www.geodatasource.com/developers/perl> FIXME: use Math::Trig =cut | ||||||
| 216 | |||||||
| 217 | sub distance { | ||||||
| 218 | 3 | 1 | 6597 | my ($lat1, $lon1, $lat2, $lon2, $unit) = @_; | |||
| 219 | 3 | 3 | my $theta = $lon1 - $lon2; | ||||
| 220 | 3 | 3 | my $dist = sin(_deg2rad($lat1)) * sin(_deg2rad($lat2)) + cos(_deg2rad($lat1)) * cos(_deg2rad($lat2)) * cos(_deg2rad($theta)); | ||||
| 221 | 3 | 5 | $dist = _acos($dist); | ||||
| 222 | 3 | 5 | $dist = _rad2deg($dist); | ||||
| 223 | 3 | 3 | $dist = $dist * 60 * 1.1515; | ||||
| 224 | 3 | 6 | if ($unit eq 'K') { | ||||
| 225 | 1 | 1 | $dist = $dist * 1.609344; # number of kilometres in a mile | ||||
| 226 | } elsif ($unit eq 'N') { | ||||||
| 227 | 1 | 1 | $dist = $dist * 0.8684; | ||||
| 228 | } | ||||||
| 229 | 3 | 3 | return ($dist); | ||||
| 230 | } | ||||||
| 231 | |||||||
| 232 | my $pi = atan2(1,1) * 4; | ||||||
| 233 | |||||||
| 234 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||||||
| 235 | #::: This function get the arccos function using arctan function ::: | ||||||
| 236 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||||||
| 237 | sub _acos { | ||||||
| 238 | 3 | 2 | my ($rad) = @_; | ||||
| 239 | 3 | 6 | my $ret = atan2(sqrt(1 - $rad**2), $rad); | ||||
| 240 | 3 | 2 | return $ret; | ||||
| 241 | } | ||||||
| 242 | |||||||
| 243 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||||||
| 244 | #::: This function converts decimal degrees to radians ::: | ||||||
| 245 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||||||
| 246 | sub _deg2rad { | ||||||
| 247 | 15 | 5 | my ($deg) = @_; | ||||
| 248 | 15 | 21 | return ($deg * $pi / 180); | ||||
| 249 | } | ||||||
| 250 | |||||||
| 251 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||||||
| 252 | #::: This function converts radians to decimal degrees ::: | ||||||
| 253 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||||||
| 254 | sub _rad2deg { | ||||||
| 255 | 3 | 1 | my ($rad) = @_; | ||||
| 256 | 3 | 3 | return ($rad * 180 / $pi); | ||||
| 257 | } | ||||||
| 258 | |||||||
| 259 | 1; | ||||||