File Coverage

blib/lib/Geo/Shapefile/Writer.pm
Criterion Covered Total %
statement 122 125 97.6
branch 19 24 79.1
condition 7 11 63.6
subroutine 13 13 100.0
pod 3 3 100.0
total 164 176 93.1


line stmt bran cond sub pod time code
1             package Geo::Shapefile::Writer;
2             {
3             $Geo::Shapefile::Writer::VERSION = '0.006';
4             }
5              
6             # $Id: Writer.pm 17 2014-11-12 07:16:04Z xliosha@gmail.com $
7              
8             # NAME: Geo::Shapefile::Writer
9             # ABSTRACT: simple pureperl shapefile writer
10              
11              
12 2     2   82997 use 5.010;
  2         7  
  2         78  
13 2     2   9 use strict;
  2         2  
  2         47  
14 2     2   7 use warnings;
  2         2  
  2         45  
15              
16 2     2   6 use utf8;
  2         3  
  2         10  
17 2     2   912 use autodie;
  2         27366  
  2         10  
18 2     2   10251 use Carp;
  2         2  
  2         123  
19              
20 2     2   1219 use XBase;
  2         23174  
  2         85  
21 2     2   21 use List::Util qw/ min max /;
  2         2  
  2         2747  
22              
23              
24              
25             my %shape_type = (
26             # extend
27             NULL => 0,
28             POINT => 1,
29             POLYLINE => 3,
30             POLYGON => 5,
31             );
32              
33              
34              
35             {
36             my @default_attr_format = ( C => 64 );
37              
38             sub _get_attr_format {
39 5     5   6 my ($format) = @_;
40              
41 5 50       23 my @descr = !ref $format ? ($format)
    100          
    100          
42             : ref $format eq 'ARRAY' ? @$format
43             : ref $format eq 'HASH' ? @$format{ qw/ name type length decimals / }
44             : ();
45              
46 5 100       32 croak 'Bad format description' if !$descr[0];
47              
48 4 100       38 @descr[1,2] = @default_attr_format if !$descr[1];
49 4         10 return \@descr;
50             }
51             }
52              
53             sub new {
54 4     4 1 3191 my ($class, $name, $type, @attrs) = @_;
55              
56 4   50     18 my $shape_type = $shape_type{ uc($type || q{}) };
57 4 100       29 croak "Invalid shape type: $type" if !defined $shape_type;
58              
59 3         16 my $self = bless {
60             NAME => $name,
61             TYPE => $shape_type,
62             RCOUNT => 0,
63             SHP_SIZE => 50,
64             SHX_SIZE => 50,
65             }, $class;
66              
67 3         11 my $header_data = $self->_get_header('SHP');
68              
69 3         18 open $self->{SHP}, '>:raw', "$name.shp";
70 3         4511 print {$self->{SHP}} $header_data;
  3         20  
71              
72 3         16 open $self->{SHX}, '>:raw', "$name.shx";
73 3         411 print {$self->{SHX}} $header_data;
  3         31  
74              
75 3 50       62 unlink "$name.dbf" if -f "$name.dbf";
76              
77 3         8 my @fields = map { _get_attr_format($_) } @attrs;
  5         12  
78 4         10 $self->{DBF} = XBase->create(
79             name => "$name.dbf",
80 4         6 field_names => [ map { $_->[0] } @fields ],
81 4         8 field_types => [ map { $_->[1] } @fields ],
82 4         19 field_lengths => [ map { $_->[2] } @fields ],
83 2         7 field_decimals => [ map { $_->[3] } @fields ],
84             );
85              
86 2         1886 return $self;
87             }
88              
89              
90             {
91             my $header_size = 100;
92             # position, pack_type, object_field, default
93             my @header_fields = (
94             [ 0, 'N', undef, 9994 ], # magic
95             [ 24, 'N', _SIZE => $header_size / 2 ], # file size in 16-bit words
96             [ 28, 'L', undef, 1000 ], # version
97             [ 32, 'L', 'TYPE' ],
98             [ 36, 'd', 'XMIN' ],
99             [ 44, 'd', 'YMIN' ],
100             [ 52, 'd', 'XMAX' ],
101             [ 60, 'd', 'YMAX' ],
102             );
103              
104             sub _get_header {
105 7     7   9 my ($self, $file_type) = @_;
106              
107 56         71 my @use_fields =
108 56   66     305 grep { defined $_->[2] }
      100        
109 7         13 map {[ $_->[0], $_->[1], $_->[2] && ($self->{$_->[2]} // $self->{"$file_type$_->[2]"}) // $_->[3] ]}
110             @header_fields;
111              
112 7         18 my $pack_string = join q{ }, map { sprintf '@%d%s', @$_[0,1] } (@use_fields, [$header_size, q{}]);
  51         98  
113 7         13 return pack $pack_string, map { $_->[2] } @use_fields;
  44         80  
114             }
115             }
116              
117              
118              
119             sub add_shape {
120 4     4 1 34 my ($self, $data, @attributes) = @_;
121              
122 4         4 my ($xmin, $ymin, $xmax, $ymax);
123              
124 0         0 my $rdata;
125 4         6 my $type = $self->{TYPE};
126              
127 4 50 33     22 if ($type == $shape_type{NULL} ) {
    100          
    50          
128 0         0 $rdata = pack( 'L', $self->{TYPE} );
129             }
130             elsif ($type == $shape_type{POINT} ) {
131 2         7 $rdata = pack( 'Ldd', $self->{TYPE}, @$data );
132 2         4 ($xmin, $ymin, $xmax, $ymax) = ( @$data, @$data );
133             }
134             elsif ($type == $shape_type{POLYLINE} || $type == $shape_type{POLYGON} ) {
135 2         5 my $rpart = q{};
136 2         2 my $rpoint = q{};
137 2         2 my $ipoint = 0;
138              
139 2         4 for my $line ( @$data ) {
140 3         5 $rpart .= pack 'L', $ipoint;
141 3         4 for my $point ( @$line ) {
142 7         6 my ($x, $y) = @$point;
143 7         10 $rpoint .= pack 'dd', $x, $y;
144 7         10 $ipoint ++;
145             }
146             }
147              
148 2         3 $xmin = min map {$_->[0]} map {@$_} @$data;
  7         14  
  3         7  
149 2         3 $ymin = min map {$_->[1]} map {@$_} @$data;
  7         9  
  3         4  
150 2         4 $xmax = max map {$_->[0]} map {@$_} @$data;
  7         9  
  3         4  
151 2         4 $ymax = max map {$_->[1]} map {@$_} @$data;
  7         8  
  3         3  
152              
153 2         6 $rdata = pack 'LddddLL', $self->{TYPE}, $xmin, $ymin, $xmax, $ymax, scalar @$data, $ipoint;
154 2         4 $rdata .= $rpart . $rpoint;
155             }
156            
157              
158 4         10 my $attr0 = $attributes[0];
159 4 100       13 if ( ref $attr0 eq 'HASH' ) {
    50          
160 1         4 $self->{DBF}->set_record_hash( $self->{RCOUNT}, map {( uc($_) => $attr0->{$_} )} keys %$attr0 );
  2         6  
161             }
162             elsif ( ref $attr0 eq 'ARRAY' ) {
163 0         0 $self->{DBF}->set_record( $self->{RCOUNT}, @$attr0 );
164             }
165             else {
166 3         12 $self->{DBF}->set_record( $self->{RCOUNT}, @attributes );
167             }
168              
169 4         768 $self->{RCOUNT} ++;
170              
171 4         6 print {$self->{SHX}} pack 'NN', $self->{SHP_SIZE}, length($rdata)/2;
  4         16  
172 4         6 $self->{SHX_SIZE} += 4;
173              
174 4         4 print {$self->{SHP}} pack 'NN', $self->{RCOUNT}, length($rdata)/2;
  4         24  
175 4         3 print {$self->{SHP}} $rdata;
  4         7  
176 4         6 $self->{SHP_SIZE} += 4+length($rdata)/2;
177              
178 4         7 $self->{XMIN} = min grep {defined} ($xmin, $self->{XMIN});
  8         28  
179 4         6 $self->{YMIN} = min grep {defined} ($ymin, $self->{YMIN});
  8         13  
180 4         6 $self->{XMAX} = max grep {defined} ($xmax, $self->{XMAX});
  8         14  
181 4         7 $self->{YMAX} = max grep {defined} ($ymax, $self->{YMAX});
  8         20  
182              
183 4         9 return $self;
184             }
185              
186              
187              
188             sub finalize {
189 2     2 1 12 my $self = shift;
190              
191 2         5 my $shp = $self->{SHP};
192 2         6 seek $shp, 0, 0;
193 2         1008 print {$shp} $self->_get_header('SHP');
  2         8  
194 2         8 close $shp;
195              
196 2         1529 my $shx = $self->{SHX};
197 2         6 seek $shx, 0, 0;
198 2         129 print {$shx} $self->_get_header('SHX');
  2         9  
199 2         6 close $shx;
200              
201 2         103 $self->{DBF}->close();
202              
203 2         44 return;
204             }
205              
206             1;
207              
208             __END__