File Coverage

blib/lib/Geo/MapInfo/MIF/Writer/Region.pm
Criterion Covered Total %
statement 18 96 18.7
branch 0 36 0.0
condition 0 7 0.0
subroutine 6 12 50.0
pod 4 4 100.0
total 28 155 18.0


line stmt bran cond sub pod time code
1             package Geo::MapInfo::MIF::Writer::Region;
2 1     1   23966 use strict;
  1         2  
  1         32  
3 1     1   4 use warnings;
  1         1  
  1         25  
4 1     1   6 use base qw{Package::New};
  1         6  
  1         933  
5 1     1   1768 use DateTime;
  1         247760  
  1         56  
6 1     1   1495 use Path::Class qw{file};
  1         62557  
  1         90  
7 1     1   1687 use Text::CSV_XS qw{};
  1         11746  
  1         1319  
8              
9             our $VERSION='0.05';
10              
11             =head1 NAME
12              
13             Geo::MapInfo::MIF::Writer::Region - Perl extension for writing MapInfo Interchange Format (MIF) Region files.
14              
15             =head1 SYNOPSIS
16              
17             use Geo::MapInfo::MIF::Writer::Region;
18             my $map=Geo::MapInfo::MIF::Writer::Region->new(basename=>$basename);
19             $map->addSimpleRegion(
20             data => {col1=>"val1", col2=>"val2"},
21             region => [[$lon1, $lat1], [$lon2, $lat2], [$lon3, $lat3], [$lon4, $lat4]],
22             );
23             $map->addMultipartRegion(
24             data => {col1=>"val1", col2=>"val2"},
25             regions => [ #note the "s" in regions
26             [[$lon1a, $lat1a], [$lon2a, $lat2a], [$lon3a, $lat3a], [$lon4a, $lat4a]],
27             [[$lon1b, $lat1b], [$lon2b, $lat2b], [$lon3b, $lat3b], [$lon4b, $lat4b]],
28             [[$lon1c, $lat1c], [$lon2c, $lat2c], [$lon3c, $lat3c], [$lon4c, $lat4c]],
29             ],
30             );
31             $map->save;
32              
33             =head1 DESCRIPTION
34              
35             Perl extension for writing MapInfo Interchange Format (MIF) Region files.
36              
37             Note: This package stores data in memory before writing so it may not be appropriate for every use.
38              
39             =head1 USAGE
40              
41             =head2 new
42              
43             Creates a new object.
44              
45             my $map=Geo::MapInfo::MIF::Writer::Region->new;
46              
47             =head2 basename
48              
49             Sets and returns the basename of the mid/mif files.
50              
51             $map->basename("basename");
52             $map->basename("./path/basename");
53             $map->basename("/path/basename");
54             $map->basename(undef); #default is "mapinfo-yyyymmddhhmiss"
55              
56             =cut
57              
58             sub basename {
59 0     0 1   my $self=shift;
60 0 0         $self->{"basename"}=shift if @_;
61 0 0         unless (defined($self->{"basename"})) {
62 0           my $dt=DateTime->now;
63 0           $self->{"basename"}=sprintf("mapinfo-%s%s", $dt->ymd(""), $dt->hms(""));
64             }
65 0           return $self->{"basename"};
66             }
67              
68             =head2 save
69              
70             Writes mid and mif files to the name indicated by basename.
71              
72             $map->save;
73              
74             Note: This method overwrites files if they exist.
75              
76             =cut
77              
78             sub save {
79 0     0 1   my $self=shift;
80 0           my @column=$self->_columns; #before we open files
81 0           my @row=$self->_rows; #before we open files
82 0           my $mid=file(join(".", $self->basename, "mid"))->openw;
83 0           my $mif=file(join(".", $self->basename, "mif"))->openw;
84 0           print $mif qq{Version 300\r\n};
85 0           print $mif qq{Charset "WindowsLatin1"\r\n};
86 0           print $mif qq{Delimiter ","\r\n};
87 0           print $mif qq{CoordSys Earth Projection 1, 0\r\n}; #WGS-84 only!
88 0           print $mif sprintf("Columns %s\r\n", scalar(@column));
89 0           foreach my $col (@column) {
90 0           my $name=$col->{"name"};
91 0           my $type=$col->{"type"};
92 0 0         $type=sprintf("%s(%s)", $col->{"type"}, $col->{"length"})
93             if $col->{"type"} eq "Char";
94 0           print $mif sprintf(" %s %s\r\n", $name, $type);
95             }
96 0           print $mif "Data\r\n\r\n";
97 0           my $count=1;
98 0           foreach my $row (@row) {
99             #Debug
100             #use Data::Dumper qw{Dumper};
101             #print Dumper($row);
102             #add to mid
103 0           my $csv=Text::CSV_XS->new;
104 0   0       my $data=$row->{"data"} || {id=>$count++};
105 0           my @col=map {$_->{"name"}} @column;
  0            
106 0           my $status=$csv->combine(@{$data}{@col});
  0            
107 0 0         warn sprintf("Text::CSV_XS: %s", $csv->error_input) unless $status == 1;
108 0           print $mid $csv->string."\r\n"; #\r\n per RFC 4180
109             #add to mif
110 0   0       my $regions=$row->{"regions"} || [];
111 0 0         if (scalar(@$regions) == 0) {
112 0           print $mif "none\r\n";
113             } else {
114 0           print $mif sprintf("Region %s\r\n", scalar(@$regions));
115 0           foreach my $region (@$regions) {
116 0 0         die("Error: Region must be an array reference.")
117             unless ref($region) eq "ARRAY";
118 0           print $mif sprintf(" %s\r\n", scalar(@$region));
119 0           foreach my $point (@$region) {
120 0 0         die("Error: Point must be an array reference.")
121             unless ref($point) eq "ARRAY";
122 0 0         die("Error: Point must have two values.")
123             unless @$point == 2 ;
124 0           printf $mif "%s %s\r\n", @$point,
125             }
126             }
127             #add optional pen, brush, center to mif
128             }
129             }
130 0           return "1";
131             }
132              
133             # _columns Format
134             # [
135             # name => "", # m/[a-z_][a-z0-9_}{0,30}/i
136             # type => "", # Char, Integer
137             # length => 1-254, # for string types
138             # ]
139              
140             sub _columns {
141 0     0     my $self=shift;
142 0           my %data=();
143 0           my @column=();
144 0           foreach my $row ($self->_rows) {
145 0           my %column=map {$_->{"name"}=>$_} @column; #updatable reference
  0            
146 0           my $data=$row->{"data"};
147 0 0         $data={} unless ref($row->{"data"}) eq "HASH";
148 0           foreach my $key (sort keys %$data) {
149 0 0         $data->{$key}="" unless defined $data->{$key};
150 0 0         unless ($key =~ m/[a-z_]{1}[a-z0-9_]{0,30}/i) {
151 0           warn "MapInfo: A field name can contain only letters, numbers, and '_'. It cannot contain spaces or punctuation marks, and it cannot begin with a number.";
152 0           next;
153             }
154 0 0         if (exists $column{$key}) {
155             #update max lenght
156 0           my $length=length($data->{$key});
157 0 0         $column{$key}->{"length"}=$length if $length>$column{$key}->{"length"};
158             } else {
159 0           my $type="Char";
160 0 0 0       if ($data->{$key}=~m/^[-]?\d{1,10}$/ and abs($data->{$key}) < 2 ** 31) {
161 0           $type="Integer";
162             }
163 0           push @column, {
164             name => $key,
165             type => $type,
166             length => length($data->{$key}),
167             };
168             }
169             }
170             }
171 0 0         push @column, {name=>"id", type=>"Integer", length=>0} if @column == 0;
172 0 0         return wantarray ? @column : \@column;
173             }
174              
175             =head2 addSimpleRegion
176              
177             Adds a new object to the in memory array.
178              
179             $map->addSimpleRegion(
180             data => {id=>1, col2=>"Foo", col3=>"Bar"}, #default is id=>$index.
181             region => [[$x1,$y1], [$x2,$y2], [$x3,$y3]], #default is "none" which means no geocoded data
182             );
183              
184             =cut
185              
186             sub addSimpleRegion {
187 0     0 1   my $self=shift;
188 0           my %data=@_;
189 0           $data{"regions"}=[delete($data{"region"})];
190 0           return $self->addMultipartRegion(%data);
191             }
192              
193             =head2 addMultipartRegion
194              
195             Adds a new object to the in memory array.
196              
197             $map->addMultipartRegion(
198             data => {id=>1, col2=>"Foo", col3=>"Bar"},
199             regions => [
200             [[$x1,$y1], [$x2,$y2], [$x3,$y3]],
201             \@r2, #can be island or lake but MapInfo figures that out for you.
202             \@r3,
203             ],
204             );
205              
206             =cut
207              
208             sub addMultipartRegion {
209 0     0 1   my $self=shift;
210 0           my %data=@_;
211 0           return push @{$self->_rows}, \%data,
  0            
212             }
213              
214             sub _rows {
215 0     0     my $self=shift;
216 0 0         $self->{"_rows"}=[] unless ref($self->{"_rows"}) eq "ARRAY";
217 0 0         return wantarray ? @{$self->{"_rows"}} : $self->{"_rows"};
  0            
218             }
219              
220             =head1 LIMITATIONS
221              
222             Currently this package only supports Regions since points and circles are trival to support in MapInfo.
223              
224             Currently we only support string and integer types.
225              
226             =head1 BUGS
227              
228             Please log on RT and send an email to the author.
229              
230             Patches accepted!
231              
232             =head1 SUPPORT
233              
234             DavisNetworks.com supports all Perl applications including this package.
235              
236             =head1 AUTHOR
237              
238             Michael R. Davis
239             CPAN ID: MRDVT
240             DavisNetworks.com
241             davis@davisnetworks.com
242             http://www.davisnetworks.com/
243              
244             =head1 COPYRIGHT
245              
246             This program is free software licensed under the...
247              
248             The BSD License
249              
250             The full text of the license can be found in the LICENSE file included with this module.
251              
252             =head1 SEE ALSO
253              
254             L - MapInfo Interchange Format (MIF) File Reader
255              
256             =cut
257              
258             1;