File Coverage

blib/lib/Geo/Ov2.pm
Criterion Covered Total %
statement 18 227 7.9
branch 0 118 0.0
condition 0 81 0.0
subroutine 6 18 33.3
pod 10 10 100.0
total 34 454 7.4


line stmt bran cond sub pod time code
1             package Geo::Ov2;
2              
3             our @ISA = qw(IO::File);
4              
5 1     1   29156 use warnings;
  1         3  
  1         39  
6 1     1   5 use strict;
  1         3  
  1         42  
7             require IO::File;
8 1     1   5 use Carp;
  1         6  
  1         120  
9 1     1   1006 use POSIX;
  1         9943  
  1         9  
10 1     1   8159 use locale;
  1         326  
  1         7  
11 1     1   1153 use Locale::TextDomain 'net.suteren.POI.todevice';
  1         33190  
  1         9  
12              
13              
14             =head1 NAME
15              
16             Geo::Ov2 - Library for reading and writing TomTom Navigator .ov2 POI files.
17              
18             Extends L
19              
20             =head1 VERSION
21              
22             Version 0.91
23              
24             =cut
25              
26             our $VERSION = '0.91';
27              
28             our %defaults = ( repart_size=> 10, repartition => 1, deareize => 0 );
29             our %params;
30              
31             =head1 SYNOPSIS
32              
33             Because this is a child of L, all functions of L are accessibe. No overriding is done.
34              
35             The core of this module is done by two main methods poiread and poiwrite.
36              
37             There are also another supporting functions, such as area_envelope, deareizator, split_area which works with ov2 record 0x01 - area and makes TTN working faster.
38              
39             The third sort of methods are getters/setters, which controls behavior of the module. These are deareize, repartition and repart_size.
40              
41             And at the end thera are poireadall and poiwriteall, which reads and writes array of pois. Poiwriteall do also rearealization, if repartition flag is set and stripes original 0x01 records if deareize flag is set.
42              
43             Perhaps a little code snippet.
44              
45             use Geo::Ov2;
46              
47             my $ov2 = Geo::Ov2->new( "
48            
49             while ( my $poi = $ov2->poiread() ) {
50             printf "type: %d; longitude: %f; latitude: %f; description: %s\n", ${$poi}{type}, ${$poi}{longitude}, ${$poi}{latitude}, ${$poi}{description};
51             }
52              
53             my @pois = @{$ov2->poireadall()};
54             foreach $poi (@pois) {
55             printf "type: %d; longitude: %f; latitude: %f; description: %s\n", ${$poi}{type}, ${$poi}{longitude}, ${$poi}{latitude}, ${$poi}{description};
56             }
57              
58             $ov2->poiwrite( { type => 2, longitude => 4000000, latitude => 1200000, descrption => "my POI" } );
59             $ov2->poiwriteall( @pois );
60              
61             @pois = @{$self->deareizator( @pois )};
62             @pois = @{$self->split_area( 0, @pois )};
63              
64             =head1 EXPORT
65              
66             A list of functions that can be exported. You can delete this section
67             if you don't export anything, such as for a purely object-oriented module.
68              
69             =head1 FUNCTIONS
70              
71             =head2 _params
72              
73             This is an internal function, which allows set and get parameters for each instance. This is required because of parrent L uses fle descriptor as $self, so it can not be used for storing other data.
74              
75             =cut
76              
77             sub _params {
78 0     0     my $self = shift;
79 0           my $params = shift;
80 0 0         $params{$self} = $params if defined $params;
81 0 0         $params{$self} = {} unless exists $params{$self};
82 0           return $params{$self};
83             }
84              
85             =head2 deareize
86              
87             This is a getter and setter of deareize flag for specific instance of object.
88              
89             =cut
90              
91             sub deareize {
92 0     0 1   my $self = shift;
93 0           return $self->_param( "deareize", $_[0] );
94             }
95              
96             =head2 repartition
97              
98             This is a getter and setter of repartition flag for specific instance of object.
99              
100             =cut
101              
102             sub repartition {
103 0     0 1   my $self = shift;
104 0           return $self->_param( "repartition", $_[0] );
105             }
106              
107             =head2 repart_size
108              
109             This is a getter and setter of repart_size value for specific instance of object.
110              
111             =cut
112              
113             sub repart_size {
114 0     0 1   my $self = shift;
115 0           return $self->_param( "repart_size", $_[0] );
116             }
117              
118             =head2 _param
119              
120             This is an internal function, which allows set and get parameters for each instance. This is required because of parrent L uses fle descriptor as $self, so it can not be used for storing other data.
121              
122             =cut
123              
124             sub _param {
125 0     0     my $self = shift;
126 0           my $key = shift;
127 0 0         croak __"undefined parameter." unless defined $key;
128 0           my $new_value = shift;
129 0           my %params = %{$self->_params};
  0            
130 0 0         if ( defined $new_value ) {
131 0           $params{$key} = $new_value;
132 0           $self->_params( \%params );
133             }
134 0 0         unless ( defined $params{$key} ) {
135 0           $params{$key} = $defaults{$key};
136 0           $self->_params( \%params );
137             }
138 0           return $params{$key};
139             }
140              
141             =head2 poiwrite
142              
143             This method writes data referenced by hashref into ov2 file.
144             if "data" atribute is provided, it is written into a file, otherwise method pack data supplied in other attributes, fills "data" attribude and then it is written.
145              
146             =head3 input
147              
148             inpus is a hashref, which has following structure:
149              
150             {
151             type => 2,
152             longitude => 5000000,
153             latitude => 1100000,
154             description => "some text",
155             data => "packed above data into binary form of ov2"
156             }
157              
158             =cut
159              
160             sub poiwrite {
161 0     0 1   my $self = shift;
162 0           my $poi = shift;
163 0           my %poi = %$poi;
164 0           my $data = $poi{data};
165 0 0         unless ( exists $poi{data} ) {
166 0           my $type = $poi{type};
167 0           my $longitude = $poi{longitude};
168 0           my $latitude = $poi{latitude};
169 0 0 0       if ( $type == 0x01 ) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
170 0           my $longitude2 = $poi{longitude2};
171 0           my $latitude2 = $poi{latitude2};
172 0           my $size = $poi{size};
173 0           $data = pack "CVVVVV", $type, $size, $longitude, $latitude,
174             $longitude2, $latitude2;
175             } elsif ( $type == 0x02 ) {
176 0           my $description = $poi{description};
177 0           $data = pack "CVVV", $type, 13 + lenght $description, $latitude,
178             $longitude;
179 0           $data = $data . $description;
180             } elsif ( $type == 0x04 ) {
181 0           $data = pack "C", $type;
182 0           $data = $data . substr( pack( "V", $longitude ), 1 );
183 0           $data = $data . substr( pack( "V", $latitude ), 1 );
184             } elsif ( $type == 0x05 or $type == 0x15 ) {
185 0           my $description = $poi{description};
186 0           $data = pack "C", $type;
187 0           $data = $data . substr( pack( "V", $longitude ), 1 );
188 0           $data = $data . substr( pack( "V", $latitude ), 1 );
189 0           $data = $data . substr( $description, 0, 2 ); # TODO
190             } elsif ( $type == 0x06 ) {
191 0           my $description = $poi{description};
192 0           $data = pack "C", $type;
193 0           $data = $data . substr( pack( "V", $longitude ), 1 );
194 0           $data = $data . substr( pack( "V", $latitude ), 1 );
195 0           $data = $data . substr( $description, 0, 3 ); # TODO
196             } elsif ( $type == 0x07
197             or $type == 0x08
198             or $type == 0x18
199             or $type == 0x09
200             or $type == 0x19
201             or $type == 0x0a
202             or $type == 0x1a
203             or $type == 0x0c )
204             {
205 0           my $description = $poi{description};
206 0           $data = pack "CC", $type, length $description;
207 0           $data = $data . substr( pack( "V", $longitude ), 1 );
208 0           $data = $data . substr( pack( "V", $latitude ), 1 );
209 0           $data = $data . $description;
210             } else {
211 0           croak "Unknown type of POI.";
212             }
213             }
214 0           print {$self} $data;
  0            
215              
216             }
217              
218             =head2 poiread
219              
220             This method reads data from ov2 file and returns hashref into POI structure:
221              
222             {
223             type => 2,
224             longitude => 5000000,
225             latitude => 1100000,
226             description => "some text",
227             data => "packed above data into binary form of ov2"
228             }
229              
230             =cut
231              
232             sub poiread {
233 0     0 1   my $self = shift;
234 0           my $res = read( $self, my $buff, 1);
235 0 0 0       return undef unless $res and $res == 1;
236 0           my $data = $buff;
237 0           my $type = unpack "C", $buff;
238 0           my %poi;
239 0 0 0       if ( $type == 0x01 ) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
240 0 0         read( $self, $buff, 20 ) == 20
241             or croak __"Unexpected end of ov2 file.";
242             #next if $repartition;
243 0           $data = $data . $buff;
244 0           my ( $size, $longitude, $latitude, $longitude2, $latitude2 ) =
245             unpack "VVVVV", $buff;
246 0           %poi = (
247             type => $type,
248             size => $size,
249             longitude => $longitude,
250             latitude => $latitude,
251             longitude2 => $longitude2,
252             latitude2 => $latitude2,
253             data => $data
254             );
255             } elsif ( $type == 0x02 ) {
256 0 0         read( $self, $buff, 4 ) == 4
257             or croak __"Unexpected end of ov2 file.";
258 0           $data = $data . $buff;
259 0           my $size = unpack "V", $buff;
260 0 0         read( $self, $buff, 8 ) == 8
261             or croak __"Unexpected end of ov2 file.";
262 0           $data = $data . $buff;
263 0           my ( $longitude, $latitude ) = unpack "VV", $buff;
264 0 0         read( $self, $buff, $size - 13 ) == $size - 13
265             or croak __"Unexpected end of ov2 file.";
266 0           $data = $data . $buff;
267 0           %poi = (
268             type => $type,
269             size => $size,
270             longitude => $longitude,
271             latitude => $latitude,
272             data => $data,
273             description => $buff
274             );
275             } elsif ( $type == 0x04 ) {
276 0 0         read( $self, $buff, 3 ) == 3
277             or croak __"Unexpected end of ov2 file.";
278 0           $data = $data . $buff;
279 0           my $tmp = "00" . $buff;
280 0 0         read( $self, $buff, 3 ) == 3
281             or croak __"Unexpected end of ov2 file.";
282 0           $data = $data . $buff;
283 0           $tmp = $tmp . "00" . $buff;
284 0           my ( $longitude, $latitude ) = unpack "VV", $tmp;
285 0           %poi = (
286             type => $type,
287             longitude => $longitude,
288             latitude => $latitude,
289             data => $data
290             );
291             } elsif ( $type == 0x05 or $type == 0x15 ) {
292 0 0         read( $self, $buff, 3 ) == 3
293             or croak __"Unexpected end of ov2 file.";
294 0           $data = $data . $buff;
295 0           my $tmp = "00" . $buff;
296 0 0         read( $self, $buff, 3 ) == 3
297             or croak __"Unexpected end of ov2 file.";
298 0           $data = $data . $buff;
299 0           $tmp = $tmp . "00" . $buff;
300 0           my ( $longitude, $latitude ) = unpack "VV", $buff;
301 0 0         read( $self, $buff, 2 ) == 2
302             or croak __"Unexpected end of ov2 file.";
303 0           $data = $data . $buff;
304 0           %poi = (
305             type => $type,
306             longitude => $longitude,
307             latitude => $latitude,
308             data => $data,
309             description => $buff
310             );
311             } elsif ( $type == 0x06 ) {
312 0 0         read( $self, $buff, 3 ) == 3
313             or croak __"Unexpected end of ov2 file.";
314 0           $data = $data . $buff;
315 0           my $tmp = "00" . $buff;
316 0 0         read( $self, $buff, 3 ) == 3
317             or croak __"Unexpected end of ov2 file.";
318 0           $data = $data . $buff;
319 0           $tmp = $tmp . "00" . $buff;
320 0           my ( $longitude, $latitude ) = unpack "VV", $buff;
321 0 0         read( $self, $buff, 3 ) == 3
322             or croak __"Unexpected end of ov2 file.";
323 0           $data = $data . $buff;
324 0           %poi = (
325             type => $type,
326             longitude => $longitude,
327             latitude => $latitude,
328             description => $buff,
329             data => $data
330             );
331             } elsif ( $type == 0x07
332             or $type == 0x08
333             or $type == 0x18
334             or $type == 0x09
335             or $type == 0x19
336             or $type == 0x0a
337             or $type == 0x1a
338             or $type == 0x0c )
339             {
340 0 0         read( $self, $buff, 1 ) == 1
341             or croak __"Unexpected end of ov2 file.";
342 0           $data = $data . $buff;
343 0           my $size = unpack "C", $buff;
344 0 0         read( $self, $buff, 3 ) == 3
345             or croak __"Unexpected end of ov2 file.";
346 0           $data = $data . $buff;
347 0           my $tmp = "00" . $buff;
348 0 0         read( $self, $buff, 3 ) == 3
349             or croak __"Unexpected end of ov2 file.";
350 0           $data = $data . $buff;
351 0           $tmp = $tmp . "00" . $buff;
352 0           my ( $longitude, $latitude ) = unpack "VV", $buff;
353 0 0         read( $self, $buff, $size ) == $size
354             or croak __"Unexpected end of ov2 file.";
355 0           $data = $data . $buff;
356 0           %poi = (
357             type => $type,
358             size => $size,
359             longitude => $longitude,
360             latitude => $latitude,
361             description => $buff,
362             data => $data
363             );
364             } else {
365 0           croak __"Unknown type of POI.";
366             }
367 0           return \%poi;
368             }
369              
370             =head2 poiwriteall
371              
372             Method gets array of hashrefs into POIs and writes it into ov2 file.
373             When deareize is set, it also strips all 0x01 records (area) befor writting.
374             When repartition is set, it does deareize and then it creates own area structure for POIs in array. Then all it is written to ov2 file.
375              
376             =cut
377              
378             sub poiwriteall {
379 0     0 1   my $self = shift;
380 0           my @pois = @_;
381             #printf STDERR "%d %d\n", $self->repartition, $self->deareize;
382 0 0 0       @pois = @{$self->deareizator( @pois )} if ( $self->repartition or $self->deareize );
  0            
383 0 0         @pois = @{$self->split_area( 0, @pois )} if $self->repartition;
  0            
384 0           foreach my $poi (@pois) {
385 0           $self->poiwrite($poi);
386             }
387             }
388              
389             =head2 poireadall
390              
391             This method reads the whole ov2 file and returns array of hashrefs into POI structures.
392              
393             =cut
394              
395             sub poireadall {
396 0     0 1   my $self = shift;
397 0           my @pois;
398 0           while ( my $poi = $self->poiread() ) {
399 0           my %poi = %$poi;
400 0           push @pois, \%poi;
401             }
402 0           return \@pois;
403             }
404              
405             =head2 area_envelope
406              
407             This method expects array of hashrefs into POIs and returns structure of 0x01 record, which is area for these POIs.
408              
409             =cut
410              
411             sub area_envelope {
412 0     0 1   my $self = shift;
413 0           my @pois = @_;
414 0           my ( $longitude2, $longitude, $latitude2, $latitude ) = ( undef, undef, undef, undef );
415 0           my $size = 0;
416 0           my $atleastone = 0;
417 0           foreach my $i (@pois) {
418 0           my %poi = %$i;
419 0           $atleastone = 1;
420 0 0         $longitude2 = $poi{longitude} unless defined $longitude2;
421 0 0         $longitude = $poi{longitude} unless defined $longitude;
422 0 0         $latitude2 = $poi{latitude} unless defined $latitude2;
423 0 0         $latitude = $poi{latitude} unless defined $latitude;
424 0 0         $longitude2 = $poi{longitude} if $poi{longitude} < $longitude2;
425 0 0         $latitude2 = $poi{latitude} if $poi{latitude} < $latitude2;
426 0 0         $longitude = $poi{longitude} if $poi{longitude} > $longitude;
427 0 0         $latitude = $poi{latitude} if $poi{latitude} > $latitude;
428 0           my $type = $poi{type};
429 0 0 0       if ( $type == 0x01 ) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
430 0           $size += 21;
431             } elsif ( $type == 0x02 ) {
432 0 0         $poi{size} = 13 + length $poi{description} unless $poi{size};
433 0           $size += $poi{size};
434             } elsif ( $type == 0x04 ) {
435 0           $size += 7;
436             } elsif ( $type == 0x05 or $type == 0x15 ) {
437 0           $size += 9;
438             } elsif ( $type == 0x06 ) {
439 0           $size += 10;
440             } elsif ( $type == 0x07
441             or $type == 0x08
442             or $type == 0x18
443             or $type == 0x09
444             or $type == 0x19
445             or $type == 0x0a
446             or $type == 0x1a
447             or $type == 0x0c )
448             {
449 0 0         $poi{size} = length $poi{description} unless $poi{size};
450 0           $size += $poi{size} + 8;
451             } else {
452 0           croak __"Unknown type of POI.";
453             }
454             }
455 0           $size += 21;
456 0           my $data = "";
457 0 0         $data = pack( "CVVVVV", 1, $size, $longitude, $latitude, $longitude2, $latitude2 ) if $atleastone;
458 0 0         $size = 21 if $atleastone;
459 0           my %poi = ( type => 1, size => $size, longitude => $longitude, latitude => $latitude, longitude2 => $longitude2, latitude2 => $latitude2, data => $data );
460             #printf "debug: %d %d %d %d\n", $longitude, $latitude, $longitude2, $latitude2;
461 0           return \%poi;
462             }
463              
464             =head2 deareizator
465              
466             This method expects array of POI hashrefs on input and rturns reference to array which is copy of source array, but without 0x01 records.
467              
468             =cut
469              
470             sub deareizator {
471 0     0 1   my $self = shift;
472 0           my @pois = @_;
473 0           my @poiout;
474 0           foreach my $i ( @pois ) {
475 0 0         if ( ${$i}{type} != 1 ) {
  0            
476 0           push @poiout, \%{$i}
  0            
477             }
478             }
479 0           return \@poiout;
480             }
481              
482             =head2 split_area
483              
484             On input is array of POI hashrefs. This array must be without 0x01 records - use deareizator.
485             Output is reference to array which contains POIs organized into tree of areas.
486             This can significantly improve speed of displaying POIs in TTN.
487              
488             =cut
489              
490             sub split_area {
491 0     0 1   my $self = shift;
492 0           my $orientation = shift;
493 0           my @pois = @_;
494 0           $orientation++;
495             #@pois = @{_sortpois( $orientation, @pois)};
496 0           my $dimension = "longitude";
497 0 0         $dimension = "latitude" if $orientation % 2;
498 0           @pois = sort { ${$a}{$dimension} <=> ${$b}{$dimension} } @pois;
  0            
  0            
  0            
499              
500             =pod
501              
502             foreach my $i ( @pois ) {
503             printf STDERR "sort: %s: %d\n", $dimension, ${$i}{$dimension};
504             }
505             printf STDERR "========================\n";
506              
507             =cut
508 0           my $blocksize = ( ( $#pois + 1 ) / ( $self->repart_size - 1 ) ) + 1;
509 0 0 0       if ( $#pois > $self->repart_size and $orientation < 10 ) {
510 0           my @poiout;
511 0           my $i = 0;
512 0           while ( ( $i + $blocksize - 1 ) <= $#pois ) {
513 0           my $tmp = $i;
514 0           $i += $blocksize;
515 0           my @tmp = @pois[$tmp .. $i - 1 ];
516 0           my $pois = $self->split_area($orientation, @tmp );
517 0           push @poiout, @$pois;
518             }
519 0 0         if ( $i < $#pois + 1) {
520 0           my @tmp = @pois[$i .. $#pois];
521 0           my $pois = $self->split_area($orientation, @tmp );
522 0           push @poiout, @$pois;
523             }
524 0           @pois = @poiout;
525             }
526 0           my %poi = %{$self->area_envelope( @pois )};
  0            
527 0           unshift @pois, \%poi;
528 0           return \@pois;
529             }
530              
531             =head1 SEE ALSO
532              
533             L,
534             L,
535             L,
536              
537             =head1 AUTHOR
538              
539             Petr Vranik, C<< >>
540              
541             =head1 BUGS
542              
543             Please report any bugs or feature requests to
544             C, or through the web interface at
545             L.
546             I will be notified, and then you'll automatically be notified of progress on
547             your bug as I make changes.
548              
549             =head1 TODO
550              
551             =over 4
552              
553             =item 1) Implement reading and writing poi.dat
554              
555             It means operating in two modes. In mode of ov2 behavior stays the same as now. In poi.dat mode it will return hash of arrays on read and expect hash of arrays on write. The top level hash will contain categories and in each category there will be array of POIs, as returned nowadays.
556              
557             =item 2) Make Czech translations of README and INSTALL.
558              
559             =item 3) Implement seekability on POI basis.
560              
561             =item 4) Implement other IO::File methods on POI basis.
562              
563             =back
564              
565             =head1 SUPPORT
566              
567             You can find documentation for this module with the perldoc command.
568              
569             perldoc Geo::Ov2
570              
571             You can also look for information at:
572              
573             =over 4
574              
575             =item * AnnoCPAN: Annotated CPAN documentation
576              
577             L
578              
579             =item * CPAN Ratings
580              
581             L
582              
583             =item * RT: CPAN's request tracker
584              
585             L
586              
587             =item * Search CPAN
588              
589             L
590              
591             =back
592              
593             =head1 ACKNOWLEDGEMENTS
594              
595             =head1 COPYRIGHT & LICENSE
596              
597             Copyright 2007 Petr Vranik, all rights reserved.
598              
599             This program is free software; you can redistribute it and/or modify it
600             under the same terms as Perl itself.
601              
602             =cut
603              
604             1; # End of Geo::Ov2