1package Geo::ShapeFile;
2
3use strict;
4use warnings;
5use Carp;
6use IO::File;
7use Geo::ShapeFile::Shape;
8use Config;
9use List::Util qw /min max/;
10use Scalar::Util qw/weaken/;
11use Tree::R;
12
13use constant ON_WINDOWS => ($^O eq 'MSWin32');
14use if ON_WINDOWS, 'Win32::LongPath';
15
16our $VERSION = '3.01';
17
18my $little_endian_sys = unpack 'b', (pack 'S', 1 );
19
20# Preloaded methods go here.
21sub new {
22    my $proto    = shift;
23    my $filebase = shift || croak "Must specify filename!";
24    my $args     = shift || {};  #  should check it's a haashref
25
26    my $class = ref($proto) || $proto;
27    my $self = {};
28
29    $self->{filebase} = $filebase;
30    #  should use a proper file name handler
31    #  so we can deal with fred.ext referring to fred.ext.shp
32    $self->{filebase} =~ s/\.\w{3}$//;
33
34    $self->{_enable_caching} = {
35        shp            => 1,
36        dbf            => 1,
37        shx            => 1,
38        shapes_in_area => 1,
39    };
40    $self->{has_shx} = 0;
41    $self->{has_shp} = 0;
42    $self->{has_dbf} = 0;
43
44    bless $self, $class;
45
46    #  control overall caching
47    if ($args->{no_cache}) {
48        $self->{_no_cache} = 1;
49    }
50
51    #  not sure what this does - possible residual from early plans
52    $self->{_change_cache} = {
53        shape_type => undef,
54        records    => undef,
55        shp        => {},
56        dbf        => {},
57        shx        => {},
58    };
59    $self->{_object_cache} = {
60        shp    => {},
61        dbf    => {},
62        shx    => {},
63        shapes_in_area => {},
64    };
65
66    if ($self->file_exists ($self->{filebase} . '.shx')) {
67        $self->_read_shx_header();
68        $self->{has_shx} = 1;
69    }
70
71    if ($self->file_exists ($self->{filebase} . '.shp')) {
72        $self->_read_shp_header();
73        $self->{has_shp} = 1;
74    }
75
76    if ($self->file_exists ($self->{filebase} . '.dbf')) {
77        $self->_read_dbf_header();
78        $self->{has_dbf} = 1;
79    }
80
81    if (!$self->{has_dbf}) {
82        croak "$self->{filebase}: shp and/or shx file do not exist or are invalid"
83          if !($self->{has_shp} && $self->{has_shx});
84
85        croak "$self->{filebase}.dbf does not exist or is invalid";
86    }
87
88    return $self;
89}
90
91sub get_file_size {
92    my ($self, $file_name) = @_;
93
94    my $file_size;
95
96    if (-e $file_name) {
97        $file_size = -s $file_name;
98    }
99    elsif (ON_WINDOWS) {
100        my $stat = statL ($file_name)
101          or die ("unable to get stat for $file_name ($^E)");
102        $file_size = $stat->{size};
103    }
104    else {
105        croak "$file_name does not exist or cannot be read, cannot get file size\n";
106    }
107
108    return $file_size;
109}
110
111sub file_exists {
112    my ($self, $file_name) = @_;
113
114    return 1 if -e $file_name;
115
116    if (ON_WINDOWS) {
117        return testL ('e', $file_name);
118    }
119
120    return;
121}
122
123
124sub _disable_all_caching {
125    my $self = shift;
126    #  a bit nuclear...
127    foreach my $type (qw/shp shx dbf shapes_in_area/) {
128        $self->{_enable_caching}{$type} = 0;
129        $self->{_object_cache} = {};
130        #$self->{_change_cache} = {};  #  need to work out what this is for
131    }
132    return;
133}
134
135sub caching {
136    my $self = shift;
137    my $what = shift;
138    my $flag = shift;
139
140    if (defined $flag) {
141        $self->{_enable_caching}->{$what} = $flag;
142    }
143    return $self->{_enable_caching}->{$what};
144}
145
146sub cache {
147    my ($self, $type, $obj, $cache) = @_;
148
149    return if $self->{_no_cache};
150
151    return $self->{_change_cache}->{$type}->{$obj}
152      if $self->{_change_cache}->{$type} && $self->{_change_cache}->{$type}->{$obj};
153
154    return if !$self->caching($type);
155
156    if ($cache) {
157        $self->{_object_cache}->{$type}->{$obj} = $cache;
158    }
159    return $self->{_object_cache}->{$type}->{$obj};
160}
161
162#  This will trigger the various caching
163#  so we end up with the file in memory.
164#  Not an issue for most files.
165sub get_all_shapes {
166    my $self = shift;
167
168    my @shapes;
169
170    foreach my $id (1 .. $self->shapes()) {
171        my $shape = $self->get_shp_record($id);
172        push @shapes, $shape;
173    }
174
175    return wantarray ? @shapes : \@shapes;
176}
177
178sub get_shapes_sorted {
179    my $self   = shift;
180    my $shapes = shift;
181    my $sub    = shift;
182
183    if (!defined $sub) {
184        $sub = sub {
185            my ($s1, $s2) = @_;
186            return $s1->{shp_record_number} <=> $s2->{shp_record_number};
187        };
188    }
189
190    if (!defined $shapes) {
191        $shapes = $self->get_all_shapes;
192    }
193
194    my @sorted = sort {$sub->($a, $b)} @$shapes;
195
196    return wantarray ? @sorted : \@sorted;
197}
198
199sub get_shapes_sorted_spatially {
200    my $self   = shift;
201    my $shapes = shift;
202    my $sub    = shift;
203
204    if (!defined $sub) {
205        $sub = sub {
206            my ($s1, $s2) = @_;
207            return
208                    $s1->x_min <=> $s2->x_min
209                 || $s1->y_min <=> $s2->y_min
210                 || $s1->x_max <=> $s2->x_max
211                 || $s1->y_max <=> $s2->y_max
212                 || $s1->shape_id <=> $s2->shape_id
213                 ;
214        };
215    }
216
217    return $self->get_shapes_sorted ($shapes, $sub);
218}
219
220sub build_spatial_index {
221    my $self = shift;
222
223    my $shapes = $self->get_all_shapes;
224
225    my $rtree = Tree::R->new();
226    foreach my $shape (@$shapes) {
227        my @bbox = ($shape->x_min, $shape->y_min, $shape->x_max, $shape->y_max);
228        $rtree->insert($shape, @bbox);
229    }
230
231    $self->{_spatial_index} = $rtree;
232
233    return $rtree;
234}
235
236sub get_spatial_index {
237    my $self = shift;
238    return $self->{_spatial_index};
239}
240
241
242sub _read_shx_header {
243    shift()->_read_shx_shp_header('shx', @_);
244}
245
246sub _read_shp_header {
247    shift()->_read_shx_shp_header('shp', @_);
248}
249
250sub _read_shx_shp_header {
251    my $self  = shift;
252    my $which = shift;
253    my $doubles;
254
255    $self->{$which . '_header'} = $self->_get_bytes($which, 0, 100);
256    (
257        $self->{$which . '_file_code'}, $self->{$which . '_file_length'},
258        $self->{$which . '_version'},   $self->{$which . '_shape_type'}, $doubles
259    ) = unpack 'N x20 N V2 a64', $self->{$which . '_header'};
260
261    (
262        $self->{$which . '_x_min'}, $self->{$which . '_y_min'},
263        $self->{$which . '_x_max'}, $self->{$which . '_y_max'},
264        $self->{$which . '_z_min'}, $self->{$which . '_z_max'},
265        $self->{$which . '_m_min'}, $self->{$which . '_m_max'},
266    ) = (
267        $little_endian_sys
268            ? (unpack 'd8', $doubles )
269            : (reverse unpack 'd8', scalar reverse $doubles)
270    );
271
272    return 1;
273}
274
275sub type_is {
276    my $self = shift;
277    my $type = shift;
278
279    #  numeric code
280    return $self->shape_type == $type
281      if ($type =~ /^[0-9]+$/);
282
283    return (lc $self->type($self->shape_type)) eq (lc $type);
284}
285
286sub get_dbf_field_names {
287    my $self = shift;
288
289    croak 'dbf field names not loaded yet'
290      if !defined $self->{dbf_field_names};
291
292    #  make sure we return a copy
293    my @fld_names = @{$self->{dbf_field_names}};
294
295    return wantarray ? @fld_names : \@fld_names;
296}
297
298sub _read_dbf_header {
299    my $self = shift;
300
301    $self->{dbf_header} = $self->_get_bytes('dbf', 0, 12);
302    (
303        $self->{dbf_version},
304        $self->{dbf_updated_year},
305        $self->{dbf_updated_month},
306        $self->{dbf_updated_day},
307        $self->{dbf_num_records},
308        $self->{dbf_header_length},
309        $self->{dbf_record_length},
310    ) = unpack 'C4 V v v', $self->{dbf_header};
311    # unpack changed from c4 l s s to fix endianess problem
312    # reported by Daniel Gildea
313
314    my $ls = $self->{dbf_header_length}
315           + $self->{dbf_num_records} * $self->{dbf_record_length};
316    my $li = $self->get_file_size($self->{filebase} . '.dbf');
317
318    # some shapefiles (such as are produced by the NOAA NESDIS) don't
319    # have a end-of-file marker in their dbf files, Aleksandar Jelenak
320    # says the ESRI tools don't have a problem with this, so we shouldn't
321    # either
322    my $last_byte = $self->_get_bytes('dbf', $li-1, 1);
323    $ls ++ if ord $last_byte == 0x1A;
324
325    croak "dbf: file wrong size (should be $ls, but found $li)"
326      if $ls != $li;
327
328    my $header = $self->_get_bytes('dbf', 32, $self->{dbf_header_length} - 32);
329    my $count = 0;
330    $self->{dbf_header_info} = [];
331
332    while ($header) {
333        my $tmp = substr $header, 0, 32, '';
334        my $chr = substr $tmp, 0, 1;
335
336        last if ord $chr == 0x0D;
337        last if length ($tmp) < 32;
338
339        my %tmp = ();
340        (
341            $tmp{name},
342            $tmp{type},
343            $tmp{size},
344            $tmp{decimals}
345        ) = unpack 'Z11 Z x4 C2', $tmp;
346
347        $self->{dbf_field_info}->[$count] = {%tmp};
348
349        $count++;
350    }
351
352    $self->{dbf_fields} = $count;
353    croak "dbf: Not enough fields ($count < 1)"
354      if $count < 1;
355
356    my @template = ();
357    foreach (@{$self->{dbf_field_info}}) {
358        croak "dbf: Field $_->{name} too short ($_->{size} bytes)"
359          if $_->{size} < 1;
360
361        croak "dbf: Field $_->{name} too long ($_->{size} bytes)"
362          if $_->{size} > 4000;
363
364        push @template, 'A' . $_->{size};
365    }
366    $self->{dbf_record_template} = join ' ', @template;
367
368    my @field_names = ();
369    foreach (@{$self->{dbf_field_info}}) {
370        push @field_names, $_->{name};
371    }
372    $self->{dbf_field_names} = [@field_names];
373
374    #  should return field names?
375    return 1;
376}
377
378#  needed now there is Geo::ShapeFile::Writer?
379sub _generate_dbf_header {
380    my $self = shift;
381
382    #$self->{dbf_header} = $self->_get_bytes('dbf',0,12);
383    (
384        $self->{dbf_version},
385        $self->{dbf_updated_year},
386        $self->{dbf_updated_month},
387        $self->{dbf_updated_day},
388        $self->{dbf_num_records},
389        $self->{dbf_header_length},
390        $self->{dbf_record_length},
391    ) = unpack 'C4 V v v', $self->{dbf_header};
392
393    $self->{_change_cache}->{dbf_cache}->{header}
394      = pack
395        'C4 V v v',
396        3,
397        (localtime)[5],
398        (localtime)[4]+1,
399        (localtime)[3],
400        0, # TODO - num_records,
401        0, # TODO - header_length,
402        0, # TODO - record_length,
403    ;
404}
405
406sub get_dbf_field_info {
407    my $self = shift;
408
409    my $header = $self->{dbf_field_info};
410
411    return if !$header;
412
413    #  Return a deep copy to avoid callers
414    #  messing up the internals
415    my @hdr;
416    foreach my $field (@$header) {
417        my %h = %$field;
418        push @hdr, \%h;
419    }
420
421    return wantarray ? @hdr : \@hdr;
422}
423
424sub get_dbf_record {
425    my $self  = shift;
426    my $entry = shift;
427
428    my $dbf = $self->cache('dbf', $entry);
429
430    if (!$dbf) {
431        $entry--; # make entry 0-indexed
432
433        my $record = $self->_get_bytes(
434            'dbf',
435            $self->{dbf_header_length}+($self->{dbf_record_length} * $entry),
436            $self->{dbf_record_length}+1, # +1 for deleted flag
437        );
438        my ($del, @data) = unpack 'c' . $self->{dbf_record_template}, $record;
439
440        map { s/^\s*//; s/\s*$//; } @data;
441
442        my %record;
443        @record{@{$self->{dbf_field_names}}} = @data;
444        $record{_deleted} = (ord $del == 0x2A);
445        $dbf = {%record};
446        $self->cache('dbf', $entry + 1, $dbf);
447    }
448
449    return wantarray ? %{$dbf} : $dbf;
450}
451
452#  needed?  not called anywhere
453sub _set_dbf_record {
454    my $self   = shift;
455    my $entry  = shift;
456    my %record = @_;
457
458    $self->{_change_cache}->{dbf}->{$entry} = {%record};
459}
460
461sub _get_shp_shx_header_value {
462    my $self = shift;
463    my $val  = shift;
464
465    if (!defined($self->{'shx_' . $val}) && !defined($self->{'shp_' . $val})) {
466        $self->_read_shx_header();  #  ensure we load at least one of the headers
467    }
468
469    return defined($self->{'shx_' . $val})
470      ? $self->{'shx_' . $val}
471      : $self->{'shp_' . $val};
472}
473
474#  factory these
475sub x_min { shift()->_get_shp_shx_header_value('x_min'); }
476sub x_max { shift()->_get_shp_shx_header_value('x_max'); }
477sub y_min { shift()->_get_shp_shx_header_value('y_min'); }
478sub y_max { shift()->_get_shp_shx_header_value('y_max'); }
479sub z_min { shift()->_get_shp_shx_header_value('z_min'); }
480sub z_max { shift()->_get_shp_shx_header_value('z_max'); }
481sub m_min { shift()->_get_shp_shx_header_value('m_min'); }
482sub m_max { shift()->_get_shp_shx_header_value('m_max'); }
483
484sub upper_left_corner {
485    my $self = shift;
486
487    return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_max);
488}
489
490sub upper_right_corner {
491    my $self = shift;
492
493    return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_max);
494}
495
496sub lower_right_corner {
497    my $self = shift;
498
499    return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_min);
500}
501
502sub lower_left_corner {
503    my $self = shift;
504
505    return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_min);
506}
507
508sub height {
509    my $self = shift;
510
511    return if !$self->records;
512
513    return $self->y_max - $self->y_min;
514}
515
516sub width {
517    my $self = shift;
518
519    return if !$self->records;
520
521    return $self->x_max - $self->x_min;
522}
523
524sub corners {
525    my $self = shift;
526
527    return (
528        $self->upper_left_corner,
529        $self->upper_right_corner,
530        $self->lower_right_corner,
531        $self->lower_left_corner,
532    );
533}
534
535sub area_contains_point {
536    my $self  = shift;
537    my $point = shift;
538
539    my ($x_min, $y_min, $x_max, $y_max) = @_;
540
541    my $x = $point->get_x;
542    my $y = $point->get_y;
543
544    my $result =
545        ($x >= $x_min) &&
546        ($x <= $x_max) &&
547        ($y >= $y_min) &&
548        ($y <= $y_max);
549
550    return $result;
551}
552
553sub bounds_contains_point {
554    my $self  = shift;
555    my $point = shift;
556
557    return $self->area_contains_point (
558        $point,
559        $self->x_min, $self->y_min,
560        $self->x_max, $self->y_max,
561    );
562}
563
564sub file_version {
565    shift()->_get_shp_shx_header_value('version');
566}
567
568sub shape_type {
569    my $self = shift;
570
571    return $self->{_change_cache}->{shape_type}
572      if defined $self->{_change_cache}->{shape_type};
573
574    return $self->_get_shp_shx_header_value('shape_type');
575}
576
577sub shapes {
578    my $self = shift;
579
580    return $self->{_change_cache}->{records}
581      if defined $self->{_change_cache}->{records};
582
583    if (!$self->{shx_file_length}) {
584        $self->_read_shx_header();
585    }
586
587    my $filelength = $self->{shx_file_length};
588    $filelength   -= 50; # don't count the header
589
590    return $filelength / 4;
591}
592
593sub records {
594    my $self = shift;
595
596    return $self->{_change_cache}->{records}
597      if defined $self->{_change_cache}->{records};
598
599    if ($self->{shx_file_length}) {
600        my $filelength = $self->{shx_file_length};
601        $filelength   -= 50; # don't count the header
602        return $filelength / 4;
603    }
604    #  should perhaps just return dbf_num_records if we get this far?
605    elsif ($self->{dbf_num_records}) {
606        return $self->{dbf_num_records};
607    }
608
609    return 0;
610}
611
612sub shape_type_text {
613    my $self = shift;
614
615    return $self->type($self->shape_type());
616}
617
618sub get_shx_record_header {
619    shift()->get_shx_record(@_);
620}
621
622sub get_shx_record {
623    my $self  = shift;
624    my $entry = shift;
625
626    croak 'must specify entry index'
627      if !$entry;
628
629    my $shx = $self->cache('shx', $entry);
630
631    if (!$shx) {
632        my $record = $self->_get_bytes('shx', (($entry - 1) * 8) + 100, 8);
633        $shx = [unpack 'N N', $record];
634        $self->cache('shx', $entry, $shx);
635    }
636
637    return @{$shx};
638}
639
640sub get_shp_record_header {
641    my $self = shift;
642    my $entry = shift;
643
644    my($offset) = $self->get_shx_record($entry);
645
646    my $record = $self->_get_bytes('shp', $offset * 2, 8);
647    my ($number, $content_length) = unpack 'N N', $record;
648
649    return ($number, $content_length);
650}
651
652
653#  returns indexes, not objects - need to change that or add method for shape_objects_in_area
654sub shapes_in_area {
655    my $self = shift;
656    my @area = @_; # x_min, y_min, x_max, y_max,
657
658    if (my $sp_index = $self->get_spatial_index) {
659        my $shapes = [];
660        $sp_index->query_partly_within_rect (@area, $shapes);
661        my @indexes;
662        foreach my $shape (@$shapes) {
663            push @indexes, $shape->shape_id;
664        }
665        return wantarray ? @indexes : \@indexes;
666    }
667
668    my @results = ();
669    SHAPE:
670    foreach my $shp_id (1 .. $self->shapes) {
671        my ($offset, $content_length) = $self->get_shx_record($shp_id);
672        my $type = unpack 'V', $self->_get_bytes ('shp', $offset * 2 + 8, 4);
673
674        next SHAPE if $self->type($type) eq 'Null';
675
676        if ($self->type($type) =~ /^Point/) {
677            my $bytes = $self->_get_bytes('shp', $offset * 2 + 12, 16);
678            my ($x, $y) = (
679                $little_endian_sys
680                    ? (unpack 'dd', $bytes )
681                    : (reverse unpack 'dd', scalar reverse $bytes)
682            );
683            my $pt = Geo::ShapeFile::Point->new(X => $x, Y => $y);
684            if ($self->area_contains_point($pt, @area)) {
685                push @results, $shp_id;
686            }
687        }
688        elsif ($self->type($type) =~ /^(PolyLine|Polygon|MultiPoint|MultiPatch)/) {
689            my $bytes = $self->_get_bytes('shp', ($offset * 2) + 12, 32);
690            my @p = (
691                $little_endian_sys
692                    ? (unpack 'd4', $bytes )
693                    : (reverse unpack 'd4', scalar reverse $bytes )
694            );
695            if ($self->check_in_area(@p, @area)) {
696                push @results, $shp_id;
697            }
698        }
699        else {
700            print 'type=' . $self->type($type) . "\n";
701        }
702    }
703
704    return wantarray ? @results : \@results;
705}
706
707sub check_in_area {
708    my $self = shift;
709    my (
710        $x1_min, $y1_min, $x1_max, $y1_max,
711        $x2_min, $y2_min, $x2_max, $y2_max,
712    ) = @_;
713
714    my $result = !(
715           $x1_min > $x2_max
716        or $x1_max < $x2_min
717        or $y1_min > $y2_max
718        or $y1_max < $y2_min
719    );
720
721    return $result;
722}
723
724#  SWL: not used anymore - remove?
725sub _between {
726    my $self  = shift;
727    my $check = shift;
728
729    #  ensure min then max
730    if ($_[0] > $_[1]) {
731        @_ = reverse @_;
732    }
733
734    return ($check >= $_[0]) && ($check <= $_[1]);
735}
736
737sub bounds {
738    my $self = shift;
739
740    return (
741        $self->x_min, $self->y_min,
742        $self->x_max, $self->y_max,
743    );
744}
745
746# is this ever called?
747sub _extract_ints {
748    my $self = shift;
749    my $end = shift;
750    my @what = @_;
751
752    my $template = ($end =~ /^l/i) ? 'V': 'N';
753
754    $self->_extract_and_unpack(4, $template, @what);
755    foreach (@what) {
756        $self->{$_} = $self->{$_};
757    }
758}
759
760sub get_shp_record {
761    my $self  = shift;
762    my $entry = shift;
763
764    my $shape = $self->cache('shp', $entry);
765    if (!$shape) {
766        my($offset, $content_length) = $self->get_shx_record($entry);
767
768        my $record = $self->_get_bytes('shp', $offset * 2, $content_length * 2 + 8);
769
770        $shape = Geo::ShapeFile::Shape->new();
771        $shape->parse_shp($record);
772        $self->cache('shp', $entry, $shape);
773    }
774
775    return $shape;
776}
777
778sub shx_handle {
779    shift()->_get_handle('shx');
780}
781
782sub shp_handle {
783    shift()->_get_handle('shp');
784}
785
786sub dbf_handle {
787    shift()->_get_handle('dbf');
788}
789
790sub _get_handle {
791    my $self  = shift;
792    my $which = shift;
793
794    my $han = $which . '_handle';
795
796    if (!$self->{$han}) {
797        my $file = join '.', $self->{filebase}, $which;
798        if (-e $file) {
799            $self->{$han} = IO::File->new;
800            croak "Couldn't get file handle for $file: $!"
801              if not $self->{$han}->open($file, O_RDONLY | O_BINARY);
802        }
803        elsif (ON_WINDOWS) {
804            my $fh;
805            openL (\$fh, '<', $file)
806              or croak ("unable to open $file ($^E)");
807            #$fh = IO::File->new_from_fd ($fh);
808            $self->{$han} = $fh;
809        }
810        binmode $self->{$han}; # fix windows bug reported by Patrick Dughi
811    }
812
813    return $self->{$han};
814}
815
816sub _get_bytes {
817    my $self   = shift;
818    my $file   = shift;
819    my $offset = shift;
820    my $length = shift;
821
822    my $handle = $file . '_handle';
823    my $h = $self->$handle();
824    $h->seek ($offset, 0)
825      || croak "Couldn't seek to $offset for $file";
826
827    my $tmp;
828    my $res = $h->read($tmp, $length);
829
830    croak "Couldn't read $length bytes from $file at offset $offset ($!)"
831      if !defined $res;
832
833    croak "EOF reading $length bytes from $file at offset $offset"
834      if $res == 0;
835
836    return $tmp;
837}
838
839
840sub type {
841    my $self  = shift;
842    my $shape = shift;
843
844    #  should make this a package lexical
845    my %shape_types = qw(
846        0   Null
847        1   Point
848        3   PolyLine
849        5   Polygon
850        8   MultiPoint
851        11  PointZ
852        13  PolyLineZ
853        15  PolygonZ
854        18  MultiPointZ
855        21  PointM
856        23  PolyLineM
857        25  PolygonM
858        28  MultiPointM
859        31  MultiPatch
860    );
861
862    return $shape_types{$shape};
863}
864
865sub find_bounds {
866    my $self    = shift;
867    my @objects = @_;
868
869    return if !scalar @objects;
870
871    my $obj1 = shift @objects;
872
873    #  assign values from first object to start
874    my $x_min = $obj1->x_min();
875    my $y_min = $obj1->y_min();
876    my $x_max = $obj1->x_max();
877    my $y_max = $obj1->y_max();
878
879
880    foreach my $obj (@objects) {
881        $x_min = min ($x_min, $obj->x_min());
882        $y_min = min ($y_min, $obj->y_min());
883        $x_max = max ($x_max, $obj->x_max());
884        $y_max = max ($y_max, $obj->y_max());
885    }
886
887    my %bounds = (
888        x_min => $x_min,
889        y_min => $y_min,
890        x_max => $x_max,
891        y_max => $y_max,
892    );
893
894    return %bounds;
895}
896
897# XML::Generator::SVG::ShapeFile fails because it is calling this method
898# and it does not exist in 2.52 and earlier
899sub DESTROY {}
900
901
9021;
903__END__
904
905=head1 NAME
906
907Geo::ShapeFile - Perl extension for handling ESRI GIS Shapefiles.
908
909=head1 SYNOPSIS
910
911  use Geo::ShapeFile;
912
913  my $shapefile = Geo::ShapeFile->new('roads');
914
915  #  note that IDs are 1-based
916  foreach my $id (1 .. $shapefile->shapes()) {
917    my $shape = $shapefile->get_shp_record($id);
918    # see Geo::ShapeFile::Shape docs for what to do with $shape
919
920    my %db = $shapefile->get_dbf_record($id);
921  }
922
923  #  As before, but do not cache any data.
924  #  Useful if you have large files and only need to access
925  #  each shape once or a small nmber of times.
926  my $shapefile = Geo::ShapeFile->new('roads', {no_cache => 1});
927
928
929=head1 ABSTRACT
930
931The Geo::ShapeFile module reads ESRI ShapeFiles containing GIS mapping
932data, it has support for shp (shape), shx (shape index), and dbf (data
933base) formats.
934
935=head1 DESCRIPTION
936
937The Geo::ShapeFile module reads ESRI ShapeFiles containing GIS mapping
938data, it has support for shp (shape), shx (shape index), and dbf (data
939base) formats.
940
941=head1 METHODS
942
943=over 4
944
945=item new ($filename_base)
946
947=item new ($filename_base, {no_cache => 1})
948
949Creates a new shapefile object.  The first argument is the basename
950for your data (there is no need to include the extension, the module will automatically
951find the extensions it supports).  For example if you have data files called
952roads.shp, roads.shx, and roads.dbf, use C<< Geo::ShapeFile->new("roads"); >> to
953create a new object, and the module will load the data it needs from the
954files as it needs it.
955
956The second (optional) argument is a hashref.
957Currently only no_cache is supported.
958If specified then data will not be cached in memory and the system will
959read from disk each time you access a shape.
960It will save memory for large files, though.
961
962=item type_is ($type)
963
964Returns true if the major type of this data file is the same as the type
965passed to type_is().
966
967The $type argument can be either the numeric code (see L</shape_type>),
968or the string code (see L</shape_type_text>).
969
970=item get_dbf_record ($record_index)
971
972Returns the data from the dbf file associated with the specified record index
973(shapefile indexes start at 1).  If called in a list context, returns a hash,
974if called in a scalar context, returns a hashref.
975
976=item x_min() x_max() y_min() y_max()
977
978=item m_min() m_max() z_min() z_max()
979
980Returns the minimum and maximum values for x, y, z, and m fields as indicated
981in the shp file header.
982
983=item upper_left_corner() upper_right_corner()
984
985=item lower_left_corner() lower_right_corner()
986
987Returns a L<Geo::ShapeFile::Point> object indicating the respective corners.
988
989=item height() width()
990
991Returns the height and width of the area contained in the shp file.  Note that
992this likely does not return miles, kilometers, or any other useful measure, it
993simply returns x_max - x_min, or y_max - y_min.  Whether this data is a useful
994measure or not depends on your data.
995
996=item corners()
997
998Returns a four element array consisting of the corners of the area contained
999in the shp file.  The corners are listed clockwise starting with the upper
1000left.
1001(upper_left_corner, upper_right_corner, lower_right_corner, lower_left_corner)
1002
1003=item area_contains_point ($point, $x_min, $y_min, $x_max, $y_max)
1004
1005Utility function that returns true if the Geo::ShapeFile::Point object in
1006point falls within the bounds of the rectangle defined by the area
1007indicated.  See bounds_contains_point() if you want to check if a point falls
1008within the bounds of the current shp file.
1009
1010=item bounds_contains_point ($point)
1011
1012Returns true if the specified point falls within the bounds of the current
1013shp file.
1014
1015=item file_version()
1016
1017Returns the ShapeFile version number of the current shp/shx file.
1018
1019=item shape_type()
1020
1021Returns the shape type contained in the current shp/shx file.  The ESRI spec
1022currently allows for a file to contain only a single type of shape (null
1023shapes are the exception, they may appear in any data file).  This returns
1024the numeric value for the type, use type() to find the text name of this
1025value.
1026
1027=item shapes()
1028
1029Returns the number of shapes contained in the current shp/shx file.  This is
1030the value that allows you to iterate through all the shapes using
1031C<< for(1 .. $obj->shapes()) { >>.
1032
1033=item records()
1034
1035Returns the number of records contained in the current data.  This is similar
1036to shapes(), but can be used even if you don't have shp/shx files, so you can
1037access data that is stored as dbf, but does not have shapes associated with it.
1038
1039=item shape_type_text()
1040
1041Returns the shape type of the current shp/shx file (see shape_type()), but
1042as the human-readable string type, rather than an integer.
1043
1044=item get_shx_record ($record_index)
1045
1046=item get_shx_record_header ($record_index)
1047
1048Get the contents of an shx record or record header (for compatibility with
1049the other get_* functions, both are provided, but in the case of shx data,
1050they return the same information).  The return value is a two element array
1051consisting of the offset in the shp file where the indicated record begins,
1052and the content length of that record.
1053
1054=item get_shp_record_header ($record_index)
1055
1056Retrieve an shp record header for the specified index.  Returns a two element
1057array consisting of the record number and the content length of the record.
1058
1059=item get_shp_record ($record_index)
1060
1061Retrieve an shp record for the specified index.  Returns a
1062Geo::ShapeFile::Shape object.
1063
1064=item shapes_in_area ($x_min, $y_min, $x_max, $y_max)
1065
1066Returns an array of integers listing which shape IDs have
1067bounding boxes that overlap with the area specified.
1068
1069=item check_in_area ($x1_min, $y1_min, $x1_max, $y1_max, $x2_min, $x2_max, $y2_min, $y2_max)
1070
1071Returns true if the two specified areas overlap.
1072
1073=item bounds()
1074
1075Returns the bounds for the current shp file.
1076(x_min, y_min, x_max, y_max)
1077
1078=item shx_handle() shp_handle() dbf_handle()
1079
1080Returns the file handles associated with the respective data files.
1081
1082=item type ($shape_type_number)
1083
1084Returns the name of the type associated with the given type id number.
1085
1086=item find_bounds (@shapes)
1087
1088Takes an array of Geo::ShapeFile::Shape objects, and returns a hash, with
1089keys of x_min, y_min, x_max, y_max, with the values for each of those bounds.
1090
1091=item get_dbf_field_names()
1092
1093Returns an array of the field names in the dbf file, in file order.
1094Returns an array reference if used in scalar context.
1095
1096=item get_all_shapes()
1097
1098Returns an array (or arrayref in scalar context) with all shape objects in the
1099shapefile.
1100
1101=item get_shapes_sorted()
1102
1103=item get_shapes_sorted (\@shapes, \&sort_sub)
1104
1105Returns an array (or arrayref in scalar context) of shape objects sorted by ID.
1106Defaults to all shapes, but will also take an array of Geo::ShapeFile::Shape objects.
1107Sorts by record number by default, but you can pass your own sub for more fancy work.
1108
1109=item get_shapes_sorted_spatially()
1110
1111=item get_shapes_sorted_spatially (\@shapes, \&sort_sub)
1112
1113Convenience wrapper around get_shapes_sorted to sort spatially (south-west to north-east)
1114then by record number.  You can pass your own shapes and sort sub.
1115The sort sub does not need to be spatial since it will sort by whatever you say,
1116but it is your code so do what you like.
1117
1118
1119=item build_spatial_index()
1120
1121Builds a spatial index (a L<Tree::R> object) and returns it.  This will be used internally for
1122many of the routines, but you can use it directly if useful.
1123
1124=item get_spatial_index()
1125
1126Returns the spatial index object, or C<undef> if one has not been built.
1127
1128=item get_dbf_field_info()
1129
1130Returns an array of hashes containing information about the fields.
1131Useful if you are modifying the shapes and then writing them out to a
1132new shapefile using L<Geo::Shapefile::Writer>.
1133
1134=back
1135
1136=head1 REPORTING BUGS
1137
1138Please send any bugs, suggestions, or feature requests to
1139  L<https://github.com/shawnlaffan/Geo-ShapeFile/issues>.
1140
1141=head1 SEE ALSO
1142
1143L<Geo::ShapeFile::Shape>,
1144L<Geo::ShapeFile::Point>,
1145L<Geo::Shapefile::Writer>,
1146L<Geo::GDAL::FFI>
1147
1148=head1 AUTHOR
1149
1150Jason Kohles, E<lt>email@jasonkohles.comE<gt>
1151
1152Shawn Laffan, E<lt>shawnlaffan@gmail.comE<gt>
1153
1154
1155=head1 COPYRIGHT AND LICENSE
1156
1157Copyright 2002-2013 by Jason Kohles (versions up to and including 2.52)
1158
1159Copyright 2014 by Shawn Laffan (versions 2.53 -)
1160
1161
1162This library is free software; you can redistribute it and/or modify
1163it under the same terms as Perl itself.
1164
1165=cut
1166