package URI::geo; use warnings; use strict; use Carp; use URI::Split qw( uri_split uri_join ); use base qw( URI ); =head1 NAME URI::geo - The geo URI scheme. =head1 VERSION This document describes URI::geo version 0.05 =cut our $VERSION = '0.05'; =head1 SYNOPSIS use URI; # GeoURI from textual uri my $guri = URI->new( 'geo:54.786989,-2.344214' ); # From coordinates my $guri = URI::geo->new( 54.786989, -2.344214 ); # Decode my ( $lat, $lon, $alt ) = $guri->location; my $latitude = $guri->latitude; # Update $guri->location( 55, -1 ); $guri->longitude( -43.23 ); =head1 DESCRIPTION From L: More and more protocols and data formats are being extended by methods to add geographic information. However, all of those options are tied to that specific protocol or data format. A dedicated Uniform Resource Identifier (URI) scheme for geographic locations would be independent from any protocol, usable by any software/data format that can handle generich URIs. Like a "mailto:" URI launches your favourite mail application today, a "geo:" URI could soon launch your favourite mapping service, or queue that location for a navigation device. =cut { my $num = qr{-?\d{1,3}(?:\.\d+)?}; sub _parse { my ( $class, $path ) = @_; croak "Badly formed geo uri" unless $path =~ /^$num(?:,$num){1,2}$/; return my ( $lat, $lon, $alt ) = split /,/, $path; } } # Try hard to extract location information from something. We handle lat, # lon, alt as scalars, arrays containing lat, lon, alt, hashes with # suitably named keys and objects with suitably named methods. sub _location_of_pointy_thing { my $class = shift; my @lat = ( 'lat', 'latitude' ); my @lon = ( 'lon', 'long', 'longitude' ); my @ele = ( 'ele', 'alt', 'elevation', 'altitude' ); if ( ref $_[0] ) { my $pt = shift; croak "Too many arguments" if @_; if ( UNIVERSAL::can( $pt, 'can' ) ) { for my $m ( qw( location latlong ) ) { return $pt->$m() if $pt->can( $m ); } my $can = sub { my ( $pt, @keys ) = @_; for my $key ( @keys ) { return $key if $pt->can( $key ); } return; }; my $latk = $can->( $pt, @lat ); my $lonk = $can->( $pt, @lon ); my $elek = $can->( $pt, @ele ); if ( defined $latk && defined $lonk ) { return $pt->$latk(), $pt->$lonk(), defined $elek ? $pt->$elek() : undef; } } elsif ( 'ARRAY' eq ref $pt ) { return $class->_location_of_pointy_thing( @$pt ); } elsif ( 'HASH' eq ref $pt ) { my $has = sub { my ( $pt, @keys ) = @_; for my $key ( @keys ) { return $key if exists $pt->{$key}; } return; }; my $latk = $has->( $pt, @lat ); my $lonk = $has->( $pt, @lon ); my $elek = $has->( $pt, @ele ); if ( defined $latk && defined $lonk ) { return $pt->{$latk}, $pt->{$lonk}, defined $elek ? $pt->{$elek} : undef; } } croak "Don't know how to convert point"; } else { croak "Need lat, lon or lat, lon, alt" if @_ < 2 || @_ > 3; return my ( $lat, $lon, $alt ) = @_; } } sub _num { my ( $class, $n ) = @_; ( my $rep = sprintf '%f', $n ) =~ s/\.0*$//; return $rep; } sub _format { my ( $class, $lat, $lon, $alt ) = @_; croak "Missing or undefined latitude" unless defined $lat; croak "Missing or undefined longitude" unless defined $lon; return join ',', map { $class->_num( $_ ) } grep { defined } $lat, $lon, $alt; } sub _path { my $class = shift; my ( $lat, $lon, $alt ) = $class->_location_of_pointy_thing( @_ ); croak "Latitude out of range" if $lat < -90 || $lat > 90; croak "Longitude out of range" if $lon < -180 || $lon > 180; $lon = 0 if $lat == -90 || $lon == 90; return $class->_format( $lat, $lon, $alt ); } =head1 INTERFACE =head2 C<< new >> Create a new URI::geo. The arguments should be either =over =item * latitude, longitude and optionally altitude =item * a reference to an array containing lat, lon, alt =item * a reference to a hash with suitably named keys or =item * a reference to an object with suitably named accessors =back To maximise the likelyhood that you can pass in some object that represents a geographical location and have URI::geo do the right thing we try a number of different accessor names. If the object has a C method (eg L) we'll use that. If there's a C method we call that. Otherwise we look for accessors called C, C, C, C, C, C, C, C or C and use them. Often if you have an object or hash reference that represents a point you can pass it directly to C; so for example this will work: use URI::geo; use Geo::Point; my $pt = Geo::Point->latlong( 48.208333, 16.372778 ); my $guri = URI::geo->new( $pt ); As will this: my $guri = URI::geo->new( { lat => 55, lon => -1 } ); and this: my $guri = URI::geo->new( 55, -1 ); Note that you can also create a new C by passing a GeoURI to C: use URI; my $guri = URI->new( 'geo:55,-1' ); =cut sub new { my $self = shift; my $class = ref $self || $self; my $uri = uri_join 'geo', undef, $class->_path( @_ ); return bless \$uri, $class; } sub _init { my ( $class, $uri, $scheme ) = @_; my $self = $class->SUPER::_init( $uri, $scheme ); # Normalise at poles. my $lat = $self->latitude; $self->longitude( 0 ) if $lat == 90 || $lat == -90; return $self; } =head2 C Get or set the location of this geo URI. my ( $lat, $lon, $alt ) = $guri->location; $guri->location( 55.3, -3.7, 120 ); When setting the location it is possible to pass any of the argument types that can be passed to C. =cut sub location { my $self = shift; my ( $scheme, $auth, $path, $query, $frag ) = uri_split $$self; if ( @_ ) { $path = $self->_path( @_ ); $$self = uri_join 'geo', $auth, $path, $query, $frag; } return $self->_parse( $path ); } sub _patch { my $self = shift; my $idx = shift; my @part = $self->location; if ( @_ ) { $part[$idx] = shift; $self->location( @part ); } return $part[$idx]; } =head2 C Get or set the latitude of this geo URI. =head2 C Get or set the longitude of this geo URI. =head2 C Get or set the altitude of this geo URI. To delete the altitude set it to C. =cut sub latitude { shift->_patch( 0, @_ ) } sub longitude { shift->_patch( 1, @_ ) } sub altitude { shift->_patch( 2, @_ ) } 1; __END__ =head1 DEPENDENCIES L =head1 BUGS AND LIMITATIONS Please report any bugs or feature requests to C, or through the web interface at L. =head1 AUTHOR Andy Armstrong C<< >> =head1 LICENCE AND COPYRIGHT Copyright (c) 2009, Andy Armstrong C<< >>. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L.