Geo-DNA
view release on metacpan or search on metacpan
lib/Geo/DNA.pm view on Meta::CPAN
_mod( ( $lat + 90.0 + $dy ), 180.0 ) - 90.0,
_mod( ( $lon + 180.0 + $dx ), 360.0 ) - 180.0
);
}
sub normalise {
my ( $lat, $lon ) = @_;
return (
_mod( ( $lat + 90.0 ), 180.0 ) - 90.0,
_mod( ( $lon + 180.0 ), 360.0 ) - 180.0,
);
}
# """
# Return the eight neighboring geodna codes
sub neighbours_geo_dna {
my ( @args ) = @_;
neighbours( @args );
}
sub neighbours {
my ( $geodna ) = @_;
# TODO:kd - this can be optimised
my ( $lati, $loni ) = bounding_box( $geodna );
my $width = abs( $loni->[1] - $loni->[0] );
my $height = abs( $lati->[1] - $lati->[0] );
my $neighbours = [];
foreach my $y ( -1, 0, 1 ) {
foreach my $x ( -1, 0, 1 ) {
next unless ( $x || $y );
push (@$neighbours, encode( add_vector( $geodna, $height * $y, $width * $x ) ) );
}
}
return $neighbours;
}
sub point_from_point_bearing_and_distance {
my ( $geodna, $bearing, $distance, @opts ) = @_;
my $options = { @opts };
my $distance = $distance * 1000; # make it metres instead of kilometres
my $precision = $options->{precision} || length( $geodna );
my ( $lat1, $lon1 ) = decode( $geodna, radians => 1 );
my $lat2 = asin( sin( $lat1 ) * cos( $distance / $RADIUS_OF_EARTH ) +
cos( $lat1 ) * sin( $distance / $RADIUS_OF_EARTH ) * cos( $bearing ) );
my $lon2 = $lon1 + atan2( sin( $bearing ) * sin( $distance / $RADIUS_OF_EARTH ) * cos( $lat1 ),
cos( $distance / $RADIUS_OF_EARTH ) - sin( $lat1 ) * sin( $lat2 ));
return encode( $lat2, $lon2, precision => $precision, radians => 1 );
}
sub distance_in_km {
my ( $ga, $gb ) = @_;
my ( $alat, $alon ) = decode( $ga );
my ( $blat, $blon ) = decode( $gb );
# if a[1] and b[1] have different signs, we need to translate
# everything a bit in order for the formulae to work.
if ( $alon * $blon < 0.0 && abs( $alon - $blon ) > 180.0 ) {
( $alat, $alon ) = add_vector( $ga, 0.0, 180.0 );
( $blat, $blon ) = add_vector( $gb, 0.0, 180.0 );
}
my $x = ( deg2rad( $blon ) - deg2rad( $alon ) ) * cos( ( deg2rad( $alat ) + deg2rad( $blat ) ) / 2 );
my $y = ( deg2rad( $blat ) - deg2rad( $alat ) );
my $d = sqrt( $x * $x + $y * $y ) * $RADIUS_OF_EARTH;
return $d / 1000;
}
# This is experimental!!
# Totally unoptimised - use at your peril!
sub neighbours_within_radius {
my ( $geodna, $radius, @opts) = @_;
my $options = { @opts };
$options->{precision} ||= 12;
my $neighbours = [];
my $rh = $radius * sqrt(2);
my $start = point_from_point_bearing_and_distance( $geodna, -( pi / 4 ), $rh, %$options );
my $end = point_from_point_bearing_and_distance( $geodna, pi / 4, $rh, %$options );
my ( $blat, $blon ) = bounding_box( $start );
my ( $dummy, $slon ) = decode( $start );
my ( $dummy, $elon ) = decode( $end );
my $dheight = abs( $blat->[1] - $blat->[0] );
my $dwidth = abs( $blon->[1] - $blon->[0] );
my ( $nlat, $nlon ) = normalise( 0.0, abs( $elon - $slon ) );
my $delta = abs( $nlon );
my $tlat = 0.0;
my $tlon = 0.0;
my $current = $start;
while ( $tlat <= $delta ) {
while ( $tlon <= $delta ) {
my ( $clat, $clon ) = add_vector( $current, 0.0, $dwidth );
$current = encode( $clat, $clon, %$options );
my $d = distance_in_km( $current, $geodna );
if ( $d <= $radius ) {
push @$neighbours, $current;
}
$tlon = $tlon + $dwidth;
}
$tlat = $tlat + $dheight;
my ( $rlat, $rlon ) = add_vector( $start, -$tlat , 0.0 );
$current = encode( $rlat, $rlon, %$options );
$tlon = 0.0;
}
return $neighbours;
}
# This takes an array of GeoDNA codes and reduces it to its
# minimal set of codes covering the same area.
# Needs a more optimal impl.
sub reduce {
my ( $geodna_codes ) = @_;
# hash all the codes
my $codes = {};
( run in 0.922 second using v1.01-cache-2.11-cpan-d8267643d1d )