File Coverage

blib/lib/Geo/GeoNames/Record.pm
Criterion Covered Total %
statement 57 108 52.7
branch 4 24 16.6
condition 0 12 0.0
subroutine 18 44 40.9
pod 12 33 36.3
total 91 221 41.1


line stmt bran cond sub pod time code
1             package Geo::GeoNames::Record;
2              
3             =head1 NAME
4              
5             Geo::GeoNames::Record - Perl module for handling GeoNames.org records
6              
7             =head1 DESCRIPTION
8              
9             Provides a Perl extension for handling GeoNames.org records.
10              
11             =head1 AUTHOR
12              
13             Xiangrui Meng
14              
15             =head1 LINKS
16              
17             GoeNames:
18             http://www.geonames.org/
19              
20             This package is part of the metadata generation and remediation suite:
21             http://cads.stanford.edu/
22              
23             =head1 COPYRIGHT
24              
25             Copyright (C) 2009 by Xiangrui Meng
26              
27             This library is free software; you can redistribute it and/or modify
28             it under the same terms as Perl itself, either Perl version 5.8.8 or,
29             at your option, any later version of Perl 5 you may have available.
30              
31             =cut
32              
33 3     3   38089 use 5.008007;
  3         11  
  3         123  
34 3     3   15 use strict;
  3         5  
  3         96  
35 3     3   16 use warnings;
  3         4  
  3         399  
36              
37             require Exporter;
38              
39             our @ISA = qw(Exporter);
40              
41             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
42              
43             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
44              
45             our @EXPORT = qw( );
46              
47             our $VERSION = '0.11';
48              
49 3     3   16 use Carp ();
  3         5  
  3         53  
50 3     3   2345 use Data::Dumper ();
  3         26624  
  3         173  
51 3     3   24 use File::Basename ();
  3         5  
  3         48  
52 3     3   8217 use Storable ();
  3         13317  
  3         211  
53              
54             ## overloadings ##
55              
56             use overload
57 3         38 q("") => \&as_string,
58             q(eq) => \&op_eq,
59             q(==) => \&op_eq,
60 3     3   27 ;
  3         6  
61              
62             ## public static variables ##
63              
64             our @fields = qw( geonameid name asciiname alternatenames latitude
65             longitude feature_class feature_code country_code cc2
66             admin1_code admin2_code admin3_code admin4_code
67             population elevation gtopo30 timezone modification_date
68             );
69              
70             our $n_fields = @fields;
71              
72             =head1 VARIABLES
73              
74             Each Geo::GeoNames::Record instance has the following fields defined in
75             http://download.geonames.org/export/dump/readme.txt:
76              
77             geonameid : integer id of record in geonames database
78              
79             name : name of geographical point (utf8) varchar(200)
80              
81             asciiname : name of geographical point in plain ascii characters, varchar(200)
82              
83             alternatenames : alternatenames, comma separated varchar(4000) (varchar(5000) for SQL Server)
84              
85             latitude : latitude in decimal degrees (wgs84)
86              
87             longitude : longitude in decimal degrees (wgs84)
88              
89             feature_class : see http://www.geonames.org/export/codes.html, char(1)
90              
91             feature_code : see http://www.geonames.org/export/codes.html, varchar(10)
92              
93             country_code : ISO-3166 2-letter country code, 2 characters
94              
95             cc2 : alternate country codes, comma separated, ISO-3166 2-letter country code, 60 characters
96              
97             admin1_code : fipscode (subject to change to iso code), isocode for the us and ch, see file admin1Codes.txt for display names of this code; varchar(20)
98              
99             admin2_code : code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
100              
101             admin3_code : code for third level administrative division, varchar(20)
102              
103             admin4_code : code for fourth level administrative division, varchar(20)
104              
105             population : bigint (4 byte int)
106              
107             elevation : in meters, integer
108              
109             gtopo30 : average elevation of 30'x30' (ca 900mx900m) area in meters, integer
110              
111             timezone : the timezone id (see file timeZone.txt)
112              
113             modification_date : date of last modification in yyyy-MM-dd format
114              
115             For each member variable, we defined a member function to save the curly
116             brackets from your code. For example, you can get the value of
117             $rec->{geonameid} by
118              
119             $rec->geonameid;
120              
121             or set its value by
122              
123             $rec->geonameid = 123456;
124              
125             =cut
126              
127             ## public variables and corresponding member functions ##
128              
129             my @_PUBLIC_VARIABLES = @fields;
130              
131             foreach (@_PUBLIC_VARIABLES)
132             {
133 0     0 0 0 eval "sub $_ : lvalue { shift->{$_}; }";
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  2     2 0 47  
  0     0 0 0  
  0     0 0 0  
134             }
135              
136             =head1 VARIABLE ALIASES
137              
138             We defined several aliases as followed:
139              
140             id : geonameid;
141              
142             coordinates : (latitude,longitude);
143              
144             =cut
145              
146             my %_PUBLIC_VARIABLE_ALIASES = ( id => 'geonameid' );
147              
148             foreach (keys %_PUBLIC_VARIABLE_ALIASES)
149             {
150 25     25 0 789 eval "sub $_ : lvalue { shift->{$_PUBLIC_VARIABLE_ALIASES{$_}}; }";
151             }
152              
153             sub coordinates : lvalue
154             {
155 0     0 0 0 my $self = shift;
156 0         0 ($self->{latitude}, $self->{longitude});
157             }
158              
159             ## private static variables ##
160              
161             my $_admins_loaded;
162             my $_admin_code_to_record;
163              
164             =head1 METHODS
165              
166             =over
167              
168             =item new()
169              
170             Constructor for Geo::GeoNames::Record.
171              
172             my $rec = Geo::GeoNames::Record->new();
173              
174             It returns an empty Geo::GeoNames::Record object.
175              
176             my $rec = Geo::GeoNames::Record->new( $str_record );
177              
178             You may also pass a GeoNames record string. It returns the corresponding
179             Geo::GeoNames::Record object or undef if the input is incorrect.
180              
181             =cut
182              
183             sub new
184             {
185 31     31 1 54 my $class = shift;
186              
187 31         78 my $self = bless {}, $class;
188              
189 31         57 @{$self}{@fields} = ();
  31         509  
190              
191 31 50       75 if ( @_ )
192             {
193 31         69 $self->parse( @_ );
194             }
195              
196 31         80 return $self;
197             }
198              
199             =item parse()
200              
201             $rec->parse( $line );
202              
203             Parses a record line from a GeoNames.org data file and updates the current
204             object.
205              
206             =cut
207              
208             sub parse
209             {
210 31     31 1 54 my ( $self, $str ) = @_;
211              
212 31         53 chomp($str);
213              
214 31         377 my @data = split /\t/, $str;
215              
216 31 50       90 if ( @data == $n_fields )
217             {
218 31         77 for ( my $i = 0; $i < $n_fields; $i++ )
219             {
220 589         1995 $self->{$fields[$i]} = $data[$i];
221             }
222             }
223             else
224             {
225 0         0 Carp::croak "Wrong number of GeoNames fields";
226             }
227              
228 31         98 return $self;
229             }
230              
231             =item names()
232              
233             Returns all unique names.
234              
235             =cut
236              
237             sub names
238             {
239 14     14 1 25 my $self = shift;
240              
241 14         20 my %saw;
242 14         427 @saw{ ( $self->{name}, $self->{asciiname}, split(/,/, $self->{alternatenames}) ) } = ();
243              
244 14         197 return keys %saw;
245             }
246              
247             =item has_name( $name )
248              
249             Returns true if the record has a name matching the $name argument.
250              
251             =cut
252              
253             sub has_name
254             {
255 3     3 1 6 my ($self, $name) = @_;
256 3 100       9 return ( grep { $_ eq $name; } $self->names() ) ? 1 : 0;
  135         199  
257             }
258              
259             =item country()
260              
261             Return the country of the record as a Geo::GeoNames::Record object.
262              
263             =cut
264              
265             sub country
266             {
267 0 0   0 1 0 _load_admins() unless $_admins_loaded;
268              
269 0         0 my $self = shift;
270            
271 0 0 0     0 if ( $self->{country_code} && exists( $_admin_code_to_record->{$self->{country_code}} ) )
272             {
273 0         0 return $_admin_code_to_record->{$self->{country_code}};
274             }
275            
276 0         0 return;
277             }
278              
279             =item admin1()
280              
281             Return the admin1 of the record as a Geo::GeoNames::Record object.
282              
283             =cut
284              
285             sub admin1
286             {
287 0 0   0 1 0 _load_admins() unless $_admins_loaded;
288              
289 0         0 my $self = shift;
290            
291 0 0       0 if ( $self->{admin1_code} )
292             {
293 0         0 my $admin1_key = $self->{country_code} . "." . $self->{admin1_code};
294              
295 0 0       0 if ( exists( $_admin_code_to_record->{$admin1_key} ) )
296             {
297 0         0 return $_admin_code_to_record->{$admin1_key};
298             }
299             }
300              
301 0         0 return;
302             }
303              
304             =item admin2
305              
306             Return the admin2 of the record as a Geo::GeoNames::Record object.
307              
308             =cut
309              
310             sub admin2
311             {
312 0 0   0 1 0 _load_admins() unless $_admins_loaded;
313              
314 0         0 my $self = shift;
315              
316 0 0       0 if ( $self->{admin2_code} )
317             {
318 0         0 my $admin2_key = $self->{country_code} . "." . $self->{admin1_code} . "." . $self->{admin2_code};
319              
320 0 0       0 if ( exists( $_admin_code_to_record->{$admin2_key} ) )
321             {
322 0         0 return $_admin_code_to_record->{$admin2_key};
323             }
324             }
325              
326 0         0 return;
327             }
328              
329             =item as_string()
330              
331             Convert the record to a GeoNames.org record line.
332              
333             =cut
334              
335             sub as_string
336             {
337 25     25 1 46 my $self = shift;
338              
339 25         43 return join( "\t", @{$self}{@fields} );
  25         295  
340             }
341              
342             =item op_eq()
343              
344             Compare records based on their geonameids.
345              
346             =cut
347              
348             sub op_eq
349             {
350 1     1 1 36 return ($_[0]->id eq $_[1]->id);
351             }
352              
353             =item is_country()
354              
355             Return true if the record is a country.
356              
357             =cut
358              
359             sub is_country
360             {
361 0     0 1   my $self = shift;
362 0   0       return ( $self->{feature_class} eq 'A' ) && ( $self->{feature_code} =~ /^P/ );
363             }
364              
365             =item is_admin1()
366              
367             Return true if the record is a primary administrative division.
368              
369             =cut
370              
371             sub is_admin1
372             {
373 0     0 1   my $self = shift;
374 0   0       return ( $self->{feature_class} eq 'A' ) && ( $self->{feature_code} eq 'ADM1' );
375             }
376              
377             =item is_admin2()
378              
379             Return true if the record is a second-order administrative division.
380              
381             =cut
382              
383             sub is_admin2
384             {
385 0     0 1   my $self = shift;
386 0   0       return ( $self->{feature_class} eq 'A' ) && ( $self->{feature_code} eq 'ADM2' );
387             }
388              
389             ## private functions ##
390              
391             # load data files for decoding country_code, admin1_code and admin2_code
392              
393             sub _load_admins
394             {
395 3     3   7555 use File::HomeDir;
  3         15571  
  3         235  
396 3     3   31 use File::Spec;
  3         5  
  3         435  
397            
398 0     0     my $admins_filename = File::Spec->catfile( File::HomeDir->my_home(),
399             ".Geo-GeoNames-Record",
400             "admin_code_to_record.hash" );
401            
402 0 0         if( -e $admins_filename )
403             {
404 0           $_admin_code_to_record = Storable::retrieve( $admins_filename );
405 0           $_admins_loaded = 1;
406             }
407             else
408             {
409 0           Carp::croak( "$admins_filename doesn't exist. Please run gn_update_admins first." );
410             }
411             }
412              
413             =back
414              
415             =cut
416              
417             1;
418             __END__