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.005';
4             }
5              
6             # $Id: Writer.pm 16 2014-07-30 08:16:24Z xliosha@gmail.com $
7              
8             # NAME: Geo::Shapefile::Writer
9             # ABSTRACT: simple pureperl shapefile writer
10              
11              
12 2     2   129989 use 5.010;
  2         8  
  2         87  
13 2     2   13 use strict;
  2         4  
  2         68  
14 2     2   11 use warnings;
  2         5  
  2         65  
15              
16 2     2   9 use utf8;
  2         3  
  2         16  
17 2     2   1954 use autodie;
  2         41263  
  2         15  
18 2     2   13760 use Carp;
  2         5  
  2         164  
19              
20 2     2   3788 use XBase;
  2         41737  
  2         101  
21 2     2   27 use List::Util qw/ min max /;
  2         6  
  2         4088  
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   11 my ($format) = @_;
40              
41 5 50       29 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       37 croak 'Bad format description' if !$descr[0];
47              
48 4 100       55 @descr[1,2] = @default_attr_format if !$descr[1];
49 4         14 return \@descr;
50             }
51             }
52              
53             sub new {
54 4     4 1 4692 my ($class, $name, $type, @attrs) = @_;
55              
56 4   50     28 my $shape_type = $shape_type{ uc($type || q{}) };
57 4 100       39 croak "Invalid shape type: $type" if !defined $shape_type;
58              
59 3         24 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         18 my $header_data = $self->_get_header('SHP');
68              
69 3         35 open $self->{SHP}, '>:raw', "$name.shp";
70 3         7490 print {$self->{SHP}} $header_data;
  3         35  
71              
72 3         20 open $self->{SHX}, '>:raw', "$name.shx";
73 3         512 print {$self->{SHX}} $header_data;
  3         40  
74              
75 3 50       74 unlink "$name.dbf" if -f "$name.dbf";
76              
77 3         12 my @fields = map { _get_attr_format($_) } @attrs;
  5         13  
78 4         11 $self->{DBF} = XBase->create(
79             name => "$name.dbf",
80 4         11 field_names => [ map { $_->[0] } @fields ],
81 4         8 field_types => [ map { $_->[1] } @fields ],
82 4         41 field_lengths => [ map { $_->[2] } @fields ],
83 2         8 field_decimals => [ map { $_->[3] } @fields ],
84             );
85              
86 2         2617 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   15 my ($self, $file_type) = @_;
106              
107 56         107 my @use_fields =
108 56   66     461 grep { defined $_->[2] }
      100        
109 7         18 map {[ $_->[0], $_->[1], $_->[2] && ($self->{$_->[2]} // $self->{"$file_type$_->[2]"}) // $_->[3] ]}
110             @header_fields;
111              
112 7         32 my $pack_string = join q{ }, map { sprintf '@%d%s', @$_ } (@use_fields, [$header_size, q{}]);
  51         149  
113 7         22 return pack $pack_string, map { $_->[2] } @use_fields;
  44         117  
114             }
115             }
116              
117              
118              
119             sub add_shape {
120 4     4 1 50 my ($self, $data, @attributes) = @_;
121              
122 4         6 my ($xmin, $ymin, $xmax, $ymax);
123              
124 0         0 my $rdata;
125 4         11 my $type = $self->{TYPE};
126              
127 4 50 33     33 if ($type == $shape_type{NULL} ) {
    100          
    50          
128 0         0 $rdata = pack( 'L', $self->{TYPE} );
129             }
130             elsif ($type == $shape_type{POINT} ) {
131 2         8 $rdata = pack( 'Ldd', $self->{TYPE}, @$data );
132 2         6 ($xmin, $ymin, $xmax, $ymax) = ( @$data, @$data );
133             }
134             elsif ($type == $shape_type{POLYLINE} || $type == $shape_type{POLYGON} ) {
135 2         4 my $rpart = q{};
136 2         3 my $rpoint = q{};
137 2         3 my $ipoint = 0;
138              
139 2         5 for my $line ( @$data ) {
140 3         7 $rpart .= pack 'L', $ipoint;
141 3         5 for my $point ( @$line ) {
142 7         10 my ($x, $y) = @$point;
143 7         16 $rpoint .= pack 'dd', $x, $y;
144 7         13 $ipoint ++;
145             }
146             }
147              
148 2         5 $xmin = min map {$_->[0]} map {@$_} @$data;
  7         28  
  3         7  
149 2         5 $ymin = min map {$_->[1]} map {@$_} @$data;
  7         13  
  3         6  
150 2         5 $xmax = max map {$_->[0]} map {@$_} @$data;
  7         13  
  3         6  
151 2         5 $ymax = max map {$_->[1]} map {@$_} @$data;
  7         13  
  3         4  
152              
153 2         10 $rdata = pack 'LddddLL', $self->{TYPE}, $xmin, $ymin, $xmax, $ymax, scalar @$data, $ipoint;
154 2         6 $rdata .= $rpart . $rpoint;
155             }
156            
157              
158 4         8 my $attr0 = $attributes[0];
159 4 100       29 if ( ref $attr0 eq 'HASH' ) {
    50          
160 1         5 $self->{DBF}->set_record_hash( $self->{RCOUNT}, map {( uc($_) => $attr0->{$_} )} keys %$attr0 );
  2         25  
161             }
162             elsif ( ref $attr0 eq 'ARRAY' ) {
163 0         0 $self->{DBF}->set_record( $self->{RCOUNT}, @$attr0 );
164             }
165             else {
166 3         23 $self->{DBF}->set_record( $self->{RCOUNT}, @attributes );
167             }
168              
169 4         1080 $self->{RCOUNT} ++;
170              
171 4         6 print {$self->{SHX}} pack 'NN', $self->{SHP_SIZE}, length($rdata)/2;
  4         21  
172 4         9 $self->{SHX_SIZE} += 4;
173              
174 4         6 print {$self->{SHP}} pack 'NN', $self->{RCOUNT}, length($rdata)/2;
  4         16  
175 4         6 print {$self->{SHP}} $rdata;
  4         8  
176 4         9 $self->{SHP_SIZE} += 4+length($rdata)/2;
177              
178 4         10 $self->{XMIN} = min grep {defined} ($xmin, $self->{XMIN});
  8         38  
179 4         8 $self->{YMIN} = min grep {defined} ($ymin, $self->{YMIN});
  8         23  
180 4         10 $self->{XMAX} = max grep {defined} ($xmax, $self->{XMAX});
  8         21  
181 4         8 $self->{YMAX} = max grep {defined} ($ymax, $self->{YMAX});
  8         20  
182              
183 4         14 return $self;
184             }
185              
186              
187              
188             sub finalize {
189 2     2 1 18 my $self = shift;
190              
191 2         5 my $shp = $self->{SHP};
192 2         11 seek $shp, 0, 0;
193 2         1480 print {$shp} $self->_get_header('SHP');
  2         11  
194 2         10 close $shp;
195              
196 2         2000 my $shx = $self->{SHX};
197 2         11 seek $shx, 0, 0;
198 2         164 print {$shx} $self->_get_header('SHX');
  2         11  
199 2         11 close $shx;
200              
201 2         142 $self->{DBF}->close();
202              
203 2         77 return;
204             }
205              
206             1;
207              
208             __END__