File Coverage

blib/lib/Geo/Location/TimeZone.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             ###################################################################
2             # Geo::Location::TimeZone
3             # $Id: TimeZone.pm,v 1.4 2007/02/06 22:29:01 bc Exp $
4             # Copyright (C) 2007 Bruce Campbell
5             # ( Change the 'beecee' in the address above to the name of the package )
6             #
7             # This is a perl library intended to provide basic timezone information
8             # about a given geographic location.
9             #
10             ###########################################################################
11             #
12             #
13              
14              
15             =head1 NAME
16              
17             Geo::Location::TimeZone - Find the timezone for a given location.
18              
19             =head1 SYNOPSIS
20              
21             use Geo::Location::TimeZone;
22             my $gltzobj = Geo::Location::TimeZone->new();
23             # 54.3 degrees North, 4.8 degrees East - Amsterdam-ish.
24             my $tzname = $gltzobj->lookup( lat => 54.3, lon => 4.8 );
25             print "$tzname\n";
26              
27             =head1 DESCRIPTION
28              
29             Geo::Location::TimeZone provides a basic lookup of timezone information
30             based on a geographic location. The boundaries in the internal database
31             are relatively coarse in order to keep the size (and lookup speed) of this
32             library low.
33              
34             The lookup is done in two parts; first a fall-back timezone is calculated,
35             based on the 15 degree intervals of longitude. Secondly, the internal
36             database is consulted to see if more specific data is available.
37              
38             The names of the timezones returned are according to the 'posix' directory
39             of the author's zoneinfo directory. Some of these are usable with the
40             L library.
41              
42             =cut
43              
44             package Geo::Location::TimeZone;
45              
46 2     2   17307 use strict;
  2         5  
  2         85  
47 2     2   3426 use Math::Polygon;
  0            
  0            
48              
49             use vars qw/$VERSION/;
50             $VERSION = "0.1";
51              
52             =head1 METHODS
53              
54             =head2 new
55              
56             This creates a new object.
57              
58             =cut
59              
60             sub basename {
61             my $self = shift;
62             return( "Geo::Location::TimeZone" );
63              
64             }
65              
66             sub new {
67             my $proto = shift;
68             my $class = ref($proto) || $proto;
69             my $self = { };
70              
71             $self->{VERSION} = $VERSION;
72              
73             bless($self, $proto);
74              
75             return( $self );
76             }
77              
78             # The child libraries call this via ISA inheritance and Class::Singleton's
79             # _new_instance
80             sub _init {
81             my $proto = shift;
82             my $class = ref($proto) || $proto;
83             my $self = { @_ };
84              
85             bless($self, $proto);
86              
87             return( $self );
88             }
89              
90              
91             =head2 lookup
92              
93             This performs a lookup, and returns a text string of the timezone that the
94             supplied location is within (or undef). No offset is returned, as that
95             involves doing daylight savings calculations which are better done inside
96             other modules.
97              
98             A %hash is taken as arguments, being 'lat' and 'lon', corresponding to the
99             latitude and longitude of the location, expressed in decimal degrees in the
100             WGS84 datum. If a third argument, 'copyright' is supplied, the return
101             value will be the copyright string attached to that particular item of data.
102              
103             =cut
104              
105             # Note (version 0.2) that as the data is stored in the child libraries in
106             # binary form, the first lookup in a given segment will take slightly longer
107             # while the data is unpacked into a usable form. Following lookups within the
108             # same segment will run much faster. The size of each segment is 15 by 15
109             # degrees.
110              
111             =pod
112              
113             Note that you may not get the timezone that you were expecting, due to
114             a shortcut taken within the code. For example, if you looked up the
115             location for Narbonne, France, you might get back 'Europe/Andorra' instead
116             of 'Europe/Paris'. This is because the GeoData source for the library
117             has major timezone boundaries, not country boundaries. The coordinates
118             of major areas are known to the library, and it finds the 'closest' one
119             to the supplied location.
120              
121             =cut
122              
123             sub lookup {
124             my $self = shift;
125              
126             my %args = ( @_ );
127              
128             my $retval = undef;
129             my $retcopy = undef;
130              
131             # Boundaries.
132             my %checkbounds = ( "lat" => [-90,90],
133             "lon" => [-180,180],
134             );
135              
136             my $fkeys = 0;
137             my $gkeys = 0;
138              
139             # Make sure that the arguments supplied are within expected boundaries.
140             # We don't wrap the coordinates around.
141             foreach my $kkey( keys %checkbounds ){
142             next unless( defined( $checkbounds{"$kkey"} ) );
143             $fkeys++;
144             next unless( defined( $args{"$kkey"} ) );
145             next if( $args{"$kkey"} !~ /^\s*(\-|\+)?\d+(\.\d+)?\s*$/ );
146             next if( $args{"$kkey"} < ${$checkbounds{"$kkey"}}[0] );
147             next if( $args{"$kkey"} > ${$checkbounds{"$kkey"}}[1] );
148             $gkeys++;
149             }
150              
151             if( $fkeys > 0 && $gkeys == $fkeys ){
152             # Everything matched. Calculate the initial timezone,
153             # and incidentally the polygon limit for longitude.
154             my $lonoff = int( ( abs($args{"lon"}) + 7.5 ) /15 );
155              
156             # Set 'GMT-foo', 'GMT+foo' or 'GMT'; do not return
157             # 'GMT-0' or 'GMT+0'. Not enough systems know about
158             # UTC.
159             if( $args{"lon"} < -7.5 ){
160             $lonoff = "-" . $lonoff;
161             $retval = "Etc/GMT" . $lonoff;
162             $retcopy = "Calculated";
163             }elsif( $args{"lon"} > 7.5 ){
164             $retval = "Etc/GMT+" . $lonoff;
165             $retcopy = "Calculated";
166             }else{
167             $retval = "Etc/GMT";
168             $retcopy = "Calculated";
169             }
170              
171             # Calculate a similar offset for the latitude.
172             my $latoff = int( ( abs($args{"lat"}) + 7.5 ) /15 );
173             if( $args{"lat"} < -7.5 ){
174             $latoff = "-" . $latoff;
175             }
176              
177             # Get a list of polygons in that area.
178             # This is good to prove the process. Really need
179             # to seperate the data into seperate files.
180             my $zulu = $self->zulu( $lonoff );
181              
182             my $toload = $self->basename . "::" . $zulu;
183              
184             my %data = ();
185             my $dataref = undef;
186              
187             # Class::Singleton.
188             if( $self->loadclass( $toload ) ){
189             my $doload = $toload . "::instance";
190             # eval { %data = %{*{"$doload"}}; };
191             {
192             # no strict 'refs';
193             # $dataref = ($doload)->();
194             $dataref = $toload->instance();
195              
196             if( defined( $dataref ) ){
197             my $tref = ref $dataref;
198             # print STDERR "Something in 0 - $doload - $tref - $dataref \n";
199             }else{
200             # print STDERR "Nothing in 0 - $doload\n";
201             }
202             }
203             }else{
204             # print STDERR "Unable to load library $toload - $lonoff, $latoff\n";
205             }
206              
207             if( defined( $dataref->{'data'}{"$lonoff"}{"$latoff"} ) ){
208             # print STDERR "Found data for $lonoff and $latoff\n";
209              
210              
211             # Remember which matching polygon has the smallest
212             # area, as we want to return the 'best' match.
213             my $smallarea = -1;
214             my $smallname = undef;
215              
216             my %foundzs = ();
217             foreach my $kkey( keys %{$dataref->{'data'}{"$lonoff"}{"$latoff"}} ){
218             if( $kkey =~ /^def/ ){
219             $foundzs{"$kkey"}++ if( $kkey =~ /^def_z/ );
220             next;
221             }
222             # New method to save on space in the library.
223             # Store in the library pack()'d versions of
224             # the floating point numbers, then unpack
225             # into the polygon variable, but only do so
226             # for the bits that are checked.
227             if( defined( $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$kkey"}{"f"} ) ){
228             $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$kkey"}{"p"} = $dataref->do_unpack( string => $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$kkey"}{"f"}, return => "listpoints" );
229             delete( $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$kkey"}{"f"} );
230             # Time to unpack the numbers for this
231             # one, then remove it. Since the doc
232             # for pack mentions that precision of
233             # floats may not be preserved or
234             # readable across various machines,
235             # we store each number as a short
236             # and long for a total of 48 bits per
237             # number, 96 bits per point; 12 bytes.
238             # To ensure that this library is
239             # usable on all platforms, we use
240             # network byte order. We then run
241             # into having only unsigned numbers,
242             # so we subtract 360 from the short
243             # to get the original number.
244             # Work through the 'f' string, taking
245             # 12 bytes at a time until it is all
246             # gone.
247              
248             }
249              
250             # new wants a list of [x,y],[x,y] . how can I
251             # get that from a list of [x,y,x,y,x,y] ? Not
252             # easily. Better to incur the expense in
253             # build-data.
254             my $poly = Math::Polygon->new( @{$dataref->{'data'}{"$lonoff"}{"$latoff"}{"$kkey"}{"p"}} );
255             # print STDERR "Random number $kkey with data for " . $args{"lon"} . " and " . $args{"lat"} . " poly has " . $poly->nrPoints . " points X - " . $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$kkey"}{"z"} . " X\n";
256              
257             if( $poly->contains( [ $args{"lon"}, $args{"lat"} ] ) ){
258             my $curarea = $poly->area;
259             if( $smallarea != - 1 ){
260             if( $curarea < $smallarea ){
261             $smallarea = $curarea;
262             $smallname = $kkey;
263             }
264             }else{
265             $smallname = $kkey;
266             $smallarea = $curarea;
267             }
268             # print STDERR "Centroid - Is within - $curarea, $smallarea, $smallname!\n";
269             }
270             }
271              
272             # Did anything get found?
273             if( defined( $smallname ) ){
274             # See if there is a timezone for the whole
275             # polygon, or whether we should find the
276             # closest matchin point.
277             $retcopy = $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$smallname"}{"c"};
278             if( defined( $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$smallname"}{"z"} ) ){
279             $retval = $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$smallname"}{"z"};
280             }else{
281             # Must work through them.
282             my $c_dist = -1;
283             my $c_name = undef;
284             my $d_dist = -1;
285             my $d_name = undef;
286             foreach my $curtz( keys %{$dataref->{'data'}{"$lonoff"}{"$latoff"}{"$smallname"}} ){
287             next unless( $curtz =~ /^z/ );
288             my @tsplit = split( ',', $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$smallname"}{"$curtz"} );
289             my $curdist = $self->distance( [ $args{"lon"}, $args{"lat"} ], [ $tsplit[0], $tsplit[1] ] );
290             if( $curdist < $c_dist || $c_dist == -1 ){
291              
292             # If a rough effective
293             # radius has been
294             # supplied, disregard
295             # this point. BUT, if
296             # there wasn't a better
297             # match, we'll still
298             # use it.
299             if( defined( $tsplit[3] ) ){
300             if( $curdist < $tsplit[3] ){
301             $c_dist = $curdist;
302             $c_name = $tsplit[2];
303             }elsif( $curdist < $d_dist || $d_dist == -1 ){
304             $d_dist = $curdist;
305             $d_name = $tsplit[2];
306             }
307             }else{
308             $c_dist = $curdist;
309             $c_name = $tsplit[2];
310             }
311             }
312             }
313              
314             # Return something.
315             if( defined( $c_name ) ){
316             $retval = $c_name;
317             }elsif( defined( $d_name ) ){
318             $retval = $d_name;
319             }
320             }
321             }else{
322             # See if there is a default timezone known;
323             # this overrides the calculated value.
324             if( defined( $dataref->{'data'}{"$lonoff"}{"$latoff"}{"def_z"} ) ){
325             $retval = $dataref->{'data'}{"$lonoff"}{"$latoff"}{"def_z"};
326             if( defined( $dataref->{'data'}{"$lonoff"}{"$latoff"}{"def_c"} ) ){
327             $retcopy = $dataref->{'data'}{"$lonoff"}{"$latoff"}{"def_c"};
328             }
329             }else{
330             # Must work through them.
331             my $c_dist = -1;
332             my $c_name = undef;
333             foreach my $curtz( keys %foundzs ){
334             my @tsplit = split( ',', $dataref->{'data'}{"$lonoff"}{"$latoff"}{"$curtz"} );
335             my $curdist = $self->distance( [ $args{"lon"}, $args{"lat"} ], [ $tsplit[0], $tsplit[1] ] );
336             if( $curdist < $c_dist || $c_dist == -1 ){
337             $c_dist = $curdist;
338             $c_name = $tsplit[2];
339             }
340             }
341             if( defined( $c_name ) ){
342             $retval = $c_name;
343             # Most coordinates came from
344             # Wikipedia.
345             $retcopy = "GPL";
346             }
347             }
348             }
349             }else{
350             # print STDERR "No matches found\n";
351             }
352             }
353              
354             if( defined( $args{"copyright"} ) ){
355             return( $retcopy );
356             }else{
357             return( $retval );
358             }
359             }
360              
361             # 0.2 stuff.
362             # =head2 datetime_str
363             #
364             # This takes a given string returned from
365             #
366             # The text string can be used against the L
367             # module.
368              
369              
370             # =head2 boundary
371             #
372             # This provides the boundaries of the supplied timezone (single argument),
373             # where that data is within the database. Note that as the database only
374             # stores exceptions to the calculated zones (15 degree increments, offset
375             # by 7.5 degrees), this will produce some unexpected results. Eg, a request
376             # for the boundaries of 'Etc/GMT' will B produce an outline of Western
377             # Europe, even though it protrudes into the 15 degree band between longitude
378             # -7.5 and 7.5 and keeps a different timezone.
379             #
380             # As all the known data is checked for the matching timezone, this routine
381             # may take some time to return. The return is a %hash of polygons matching,
382             # in lon,lat notation (X,Y).
383             #
384             # =cut
385              
386             sub boundary {
387             my $self = shift;
388              
389             my $match = shift;
390             my %rethash = ();
391              
392             # This is going to be intensive.
393             for ( my $offs = -12 ; $offs <= 12 ; $offs++ ){
394             my $zulu = $self->zulu( $offs );
395              
396             my $toload = $self->basename . "::" . $zulu;
397             next unless( $self->loadclass( $toload ) );
398            
399             my $dataref = $toload->instance();
400              
401             foreach my $lonkey( keys %{$dataref->{"data"}} ){
402             next unless( defined( $match ) );
403             foreach my $latkey( keys %{$dataref->{"data"}{"$lonkey"}} ){
404             foreach my $rkey( keys %{$dataref->{"data"}{"$lonkey"}{"$latkey"}} ){
405             if( $dataref->{"data"}{"$lonkey"}{"$latkey"}{"$rkey"}{"z"} eq $match ){
406             if( defined( $dataref->{"data"}{"$lonkey"}{"$latkey"}{"$rkey"}{"f"} ) ){
407             $dataref->{'data'}{"$lonkey"}{"$latkey"}{"$rkey"}{"p"} = $dataref->do_unpack( string => $dataref->{'data'}{"$lonkey"}{"$latkey"}{"$rkey"}{"f"}, return => "listpoints" );
408             delete( $dataref->{'data'}{"$lonkey"}{"$latkey"}{"$rkey"}{"f"} );
409             }
410             push @{$rethash{"$rkey"}}, @{$dataref->{"data"}{"$lonkey"}{"$latkey"}{"$rkey"}{"p"}};
411             }
412             }
413             }
414             }
415             }
416              
417             return( %rethash );
418              
419             }
420              
421             =head2 zulu
422              
423             Returns the letter code for the supplied hour offset (eg, 2 will return B).
424             This is used to work out which sub-library to load into memory to perform
425             the lookup.
426              
427             =cut
428              
429             sub zulu {
430             my $self = shift;
431              
432             my $arg = shift;
433              
434             my %zulus = ( "0", "Z",
435             "-0", "Z",
436             "+0", "Z",
437             "1", "A",
438             "+1", "A",
439             "2", "B",
440             "+2", "B",
441             "3", "C",
442             "+3", "C",
443             "4", "D",
444             "+4", "D",
445             "5", "E",
446             "+5", "E",
447             "6", "F",
448             "+6", "F",
449             "7", "G",
450             "+7", "G",
451             "8", "H",
452             "+8", "H",
453             "9", "I",
454             "+9", "I",
455             "10", "K",
456             "+10", "K",
457             "11", "L",
458             "+11", "L",
459             "12", "M",
460             "+12", "M",
461             "-1", "N",
462             "-2", "O",
463             "-3", "P",
464             "-4", "Q",
465             "-5", "R",
466             "-6", "S",
467             "-7", "T",
468             "-8", "U",
469             "-9", "V",
470             "-10", "W",
471             "-11", "X",
472             "-12", "Z",
473             );
474              
475             if( defined( $zulus{"$arg"} ) ){
476             return( $zulus{"$arg"} );
477             }else{
478             return( undef );
479             }
480             }
481              
482             # Work out the distance between two points. Classic A^2 + B^2 = C^2 routine.
483             sub distance {
484             my $self = shift;
485             my ($point1, $point2) = (@_);
486              
487             my $diffX = 0;
488             my $diffY = 0;
489              
490             if( ${$point1}[0] > ${$point2}[0] ){
491             $diffX = ${$point1}[0] - ${$point2}[0];
492             }else{
493             $diffX = ${$point2}[0] - ${$point1}[0];
494             }
495             if( ${$point1}[1] > ${$point2}[1] ){
496             $diffY = ${$point1}[1] - ${$point2}[1];
497             }else{
498             $diffY = ${$point2}[1] - ${$point1}[1];
499             }
500              
501             my $tans = ( $diffX * $diffX ) + ( $diffY * $diffY );
502              
503             if( $tans != 0 ){
504             return( sqrt( abs( $tans ) ) );
505             }else{
506             # sqrt of 0
507             return( 0 );
508             }
509             }
510              
511             sub version {
512             my $self = shift;
513              
514             return( $self->{'VERSION'} );
515             }
516              
517             sub loadclass {
518             my $self = shift;
519             my $arg = shift;
520              
521             my $retval = 0;
522              
523             eval "require $arg";
524              
525             if( $@ ){
526             # print STDERR "Return message was $@\n";
527             }else{
528             $retval++;
529             }
530              
531             return( $retval );
532             }
533              
534             # 0.2 stuff
535             # =head2 do_pack
536             #
537             # This is a helper routine used in the compression of GeoData so the overall
538             # size of the child libraries is kept low. It takes a %hash of arguments,
539             # comprising either of a Math::Polygon object as 'poly', or a lat/lon pair
540             # as 'lat' and 'lon' (decimal degrees). It returns a single binary string
541             # representing the data stored.
542             #
543             # Each point supplied is converted to two shorts and two longs, in 'network'
544             # byte order, for a total of 12 bytes per point. Clueful people will note
545             # that pack() does not support signed shorts and longs, and will read the
546             # comments in the library code next.
547             #
548             # =cut
549             #
550             # This uses pack to store a given point (supplied as two signed floating
551             # point numbers in the hash; lat,lon) into 96 bits (12 bytes). To ensure
552             # that this library is usable on all platforms, we store the numbers in
553             # network order as an unsigned short (whole number portion) and an unsigned
554             # long (fraction portion). To get around the issue of negative numbers being
555             # passed to this routine (as is the case in 3 out of four corners of the
556             # world), all numbers are bumped up at least once until they are positive
557             # (increments of 180 for lat, 360 for lon).
558             sub do_pack {
559             my $self = shift;
560             my %args = ( @_ );
561              
562             my $retstr = undef;
563              
564             # If we have a polygon to deal with.
565             if( defined( $args{"poly"} ) ){
566             # Walk through the points that are returned, and call ourselves
567             # again on each point. Math::Polygon returns points in X,Y
568             # order, but since this is a Geo-related application, the
569             # data is stored in lat,lon order.
570             foreach my $point( $args{"poly"}->points ){
571             $retstr .= $self->do_pack( lat => ${$point}[1], lon => ${$point}[0] );
572             }
573              
574             }elsif( defined( $args{"lat"} ) && defined( $args{"lon"} ) ){
575              
576             # Push them into positive space so we can store them as
577             # unsigned numbers.
578             $args{"lat"} += 180;
579             $args{"lon"} += 360;
580              
581             # Keep bumping them to positive.
582             while( $args{"lat"} < 0 ){
583             $args{"lat"} += 180;
584             }
585             while( $args{"lon"} < 0 ){
586             $args{"lon"} += 360;
587             }
588              
589             # Seperate the numbers out.
590             foreach my $workkey( "lat", "lon" ){
591             next unless( $args{"$workkey"} =~ /^(\d+)(\.(\d+))?$/ );
592             my $large = $1;
593             my $frac = defined( $3 ) ? $3 : 0;
594              
595             $retstr .= pack "nN", $large, $frac;
596             }
597             }else{
598             # Someone hasn't read the documentation. Either a poly
599             # or lat/lon are supplied.
600             }
601              
602             return( $retstr );
603             }
604              
605             # =head2 do_unpack
606             #
607             # This reverses the packing done by do_pack. It takes a hash of arguments
608             # being:
609             #
610             # =over
611             #
612             # =item string
613             #
614             # The binary string to unpack. This should be a multiple of 12 bytes.
615             #
616             # =item return
617             #
618             # How to return the data. Possible return types are 'latlon', which will
619             # return a @list of the latitude and longitude, 'point', which will return
620             # a @list of X and Y values, 'listpoints', which will return a @list of
621             # points (each a sub-@list), and 'poly' which will return a prepared
622             # Math::Polygon object. Note that the 'latlon' and 'point' returns will only
623             # process the first 12 bytes.
624             #
625             # =over
626             #
627             # =cut
628              
629             sub do_unpack {
630             my $self = shift;
631              
632             my %args = ( @_ );
633              
634             my @retlist = ();
635             my $retobj = undef;
636              
637              
638             # The polygon preparation is done at the end.
639             my $dopoly = 0;
640             my $listpoints = 0;
641             my $latlon = 0;
642             if( $args{"return"} eq "poly" ){
643             $dopoly = 1;
644             $args{"return"} = "listpoints";
645             }elsif( $args{"return"} eq "listpoints" ){
646             $listpoints = 1;
647             }elsif( $args{"return"} eq "latlon" ){
648             $latlon = 1;
649             }
650              
651             # Run through the data that we have.
652             my $stillgoing = 1;
653             while( $stillgoing ){
654             $stillgoing = 0;
655             my $thisdata = undef;
656              
657             # Split the data into 12byte segments.
658             ( $thisdata, $args{"string"} ) = split( /............/s, $args{"string"}, 2);
659              
660             # Skip if there is not enough data left.
661             next unless( defined( $thisdata ) );
662             next unless( length( $thisdata ) == 12 );
663             $stillgoing = $listpoints;
664              
665             # Unpack the data.
666             my ( $latwhole, $latfrac, $lonwhole, $lonfrac ) = unpack( "nNnN", $thisdata );
667              
668             # Add the values together. Gotta love perl at times, being
669             # able to treat numbers as strings then as numbers.
670             $latwhole = $latwhole . "." . $latfrac;
671             $lonwhole = $lonwhole . "." . $lonfrac;
672              
673             # Apply the decrements to get signed values again.
674             $latwhole -= 180;
675             $lonwhole -= 360;
676              
677             # Make the numbers reasonable.
678             while( $latwhole > 180 ){
679             $latwhole -= 180;
680             }
681             while( $lonwhole > 360 ){
682             $lonwhole -= 360;
683             }
684              
685             # Work out how to return them.
686             if( $listpoints ){
687             push @retlist, [$lonwhole, $latwhole];
688             }elsif( $latlon ){
689             push @retlist, $latwhole, $lonwhole;
690             }else{
691             push @retlist, $lonwhole, $latwhole;
692             }
693            
694             }
695              
696             # Decide what to return.
697             if( $dopoly ){
698             $retobj = Math::Polygon->new( @retlist );
699             return( $retobj );
700             }else{
701             return( @retlist );
702             }
703             }
704              
705             =head1 AUTHOR
706              
707             Bruce Campbell, 2007. See http://cpan.zerlargal.org/Geo::Location::TimeZone
708              
709             =head1 INTELLECTUAL PROPERTIES AND COPYRIGHT
710              
711             In finding the Geodata used for this, the author ran into the common problem
712             of Geographic data being held under very restrictive usage licenses, or
713             being unavailable for free (as in price). Hence, we have this listing
714             to avoid any issues.
715              
716             =over
717              
718             =item CODE
719              
720             Copyright (c) 2007 Bruce Campbell. All rights reserved. This program is
721             free software; you can redistribute it and/or modify it under the same
722             terms as perl itself.
723              
724             =item Base Zones
725              
726             Based on the work of the 1884 International Prime Meridian Conference. No
727             copyright is claimed.
728              
729             =item Derived data
730              
731             A number of boundaries have been derived from direct observation, or laws
732             defining administrative boundaries. Where this is the case, no copyright
733             is claimed on the data.
734              
735             =item Australia
736              
737             To be sourced from official seperation of states.
738              
739             =item UK/Ireland (GMT)
740              
741             To be sourced from UK boundaries on international waters.
742              
743             =item Spain/Portugal
744              
745             To be sourced from water boundaries, and border line.
746              
747             =item Central European Time
748              
749             To be sourced from water boundaries, German/Polish border.
750              
751             =item USA
752              
753             To be sourced from decrees in Congress.
754              
755             =item All other zones
756              
757             Sourced from ESRI's timezone collection, which lists the following sources:
758              
759             ArcWorld 1:3M 20020218, ArcUSA 1:2M, ArcAtlas, Rand McNally Int., www.nunavutcourtofjustice.ca, www.nunavut.com, www.nrc.ca, DMTI Spatial Inc. - 2 to 50 .
760              
761             The following paragraph within the source data seems to cover the release
762             of Geodata within this package:
763              
764             Geodata is redistributable without a Value-Added Software Application (i.e., adding the sample data to an existing, [non]commercial data set for redistribution) with proper metadata and source/copyright attribution to the respective data vendor(s).
765              
766             =back
767              
768             =cut
769              
770             # The master data for this library lives in a hash called 'data' in
771             # sub libraries. The hash is 4-levels,
772             # consisting of lonoff, latoff, random-key, and finally,
773             # 'p' (for poly),'z' (for zone), and 'c' (for copyright)
774             # lonoff and latoff are the result of putting the lat/lon into 15 degree
775             # increments, from -12 to 12, and -6 to 6.
776             # random-key is just that; a random-key.
777             # The 'p'oly is a @list of X,Y values that Math::Polygon likes for input.
778             # The 'z'one is a text string that DateTime::TimeZone hopefully likes.
779             # The 'c'opyright is a text string by which people can lookup where the
780             # data came from.
781             # The script which generates this data is in b/build-data.pl
782             1;