File Coverage

blib/lib/Geo/OGC/Geometry.pm
Criterion Covered Total %
statement 716 1471 48.6
branch 220 604 36.4
condition 36 129 27.9
subroutine 124 269 46.1
pod 34 42 80.9
total 1130 2515 44.9


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             Geo::OGC::Geometry - Perl extension for OGC simple feature geometries
6              
7             =head1 SYNOPSIS
8              
9             use Geo::OGC::Geometry;
10              
11             my $point = Geo::OGC::Point->new(Text => 'POINT 1 1');
12              
13             =head1 DESCRIPTION
14              
15             OGC simple feature geometries is a class hierarchy for geographic
16             information. This module conforms to the document OGC 06-103r4, which
17             is the current document describing the standard. The document is
18             available from L.
19              
20             This module is currently mostly an interface and storage classes and
21             does not contain implementations of most of the methods for testing
22             spatial relations etc.
23              
24             =head1 CLASSES
25              
26             =head2 Geo::OGC::Geometry
27              
28             Geo::OGC::Geometry is the root class and it represents an arbitrary
29             geospatial geometry.
30              
31             =cut
32              
33             package Geo::OGC::Geometry;
34              
35 3     3   80334 use strict;
  3         6  
  3         74  
36 3     3   2237 use POSIX;
  3         23534  
  3         19  
37 3     3   9653 use Carp;
  3         15  
  3         181  
38              
39             BEGIN {
40 3     3   17 use Exporter 'import';
  3         5  
  3         94  
41 3     3   14 use vars qw /$SNAP_DISTANCE_SQR/;
  3         5  
  3         405  
42 3     3   31 POSIX::setlocale( &POSIX::LC_NUMERIC, "C" ); # Force to "C" locale
43 3         11 our %EXPORT_TAGS = ( 'all' => [ qw( &ccw &intersect &distance_point_line_sqr
44             $SNAP_DISTANCE_SQR ) ] );
45 3         5 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
  3         10  
46 3         6 our $VERSION = '0.06';
47 3         10018 $SNAP_DISTANCE_SQR = 1E-6;
48             }
49              
50             =pod
51              
52             =over
53              
54             =item new(%params)
55              
56             Create a new Geometry object or a new object belonging to a concrete
57             subclass of Geometry if initialization data is given.
58              
59             %param is a hash containing named parameters. The keywords are
60             (subclasses of Geometry may add new keywords):
61              
62             =over
63              
64             =item Text
65              
66             A well-known text to initialize an object of a concrete class.
67              
68             =item SRID
69              
70             Sets the Spatial Reference System ID of the object, default is -1.
71              
72             =item Precision
73              
74             If specified, has the effect that numeric comparisons in the Equals
75             method is is preceded with a rounding operation (using sprintf "%.pe",
76             where p is the Precision-1, i.e the number of meaningful numbers is
77             Precision). Affects also AsText.
78              
79             =back
80              
81             =cut
82              
83             sub new {
84 278     278 1 5327 my($package, %params) = @_;
85 278 100       626 return parse_wkt($params{Text}) if exists $params{Text};
86 269         460 my $self = {};
87 269   66     993 bless $self => (ref($package) or $package);
88 269         705 $self->init(%params);
89 269         575 return $self;
90             }
91              
92             # methods beginning with lower case letter are private
93             # and thus not in pod
94              
95             # Override in new classes but call $self->SUPER::init(%params);
96             sub init {
97 269     269 0 532 my($self, %params) = @_;
98 269 50       779 $self->{SRID} = exists $params{SRID} ? $params{SRID} : -1;
99 269 50       751 $self->{Precision} = $params{Precision}-1 if exists $params{Precision};
100             }
101              
102             # Copies the attributes of this object to the other object,
103             # which is a newly created object of same class.
104             sub copy {
105 6     6 0 10 my($self, $clone) = @_;
106 6         9 $clone->{SRID} = $self->{SRID};
107 6 50       12 $clone->{Precision} = $self->{Precision} if exists $self->{Precision};
108             }
109              
110             # parse well known text and construct respective geometry
111             sub parse_wkt {
112 90     90 0 138 my $text = shift;
113 90         99 my $self;
114             my $type;
115 0         0 my $prefix;
116 90         157 for my $t (qw/POINT MULTIPOINT LINESTRING MULTILINESTRING
117             LINEARRING POLYGON POLYHEDRALSURFACE MULTIPOLYGON
118             GEOMETRYCOLLECTION/) {
119 226 100       2636 next unless $text =~ /^\s*$t/i;
120 90         142 $type = $t;
121 90         769 $text =~ s/^\s*$t\s*//i;
122 90 100       350 if ($text =~ /^EMPTY/i) {
123 1         2 $text = undef;
124             } else {
125 89         291 $text =~ s/^([ZM]*)\s*\(\s*//i;
126 89         224 $prefix = lc($1);
127 89         362 $text =~ s/\s*\)\s*$//;
128             }
129 90         174 last;
130             }
131 90 100       284 if ($type eq 'POINT') {
    100          
    50          
    50          
    100          
    100          
    100          
    100          
    50          
132 62 50       129 return Geo::OGC::Point->new unless $text;
133 62         231 my @point = split /\s+/, $text;
134 62         204 $self = Geo::OGC::Point->new("point$prefix"=>\@point);
135             } elsif ($type eq 'MULTIPOINT') {
136 1 50       3 return Geo::OGC::MultiPoint->new unless $text;
137 1         2 $text =~ s/^\(\s*//;
138 1         2 $text =~ s/\)\s*$//;
139 1         11 my @points = split /\s*,\s*/, $text;
140 1         12 $self = Geo::OGC::MultiPoint->new();
141 1         3 for my $p (@points) {
142 5         30 $self->AddGeometry(parse_wkt("POINT $prefix ($p)"));
143             }
144             } elsif ($type eq 'LINESTRING') {
145 0 0       0 return Geo::OGC::LineString->new unless $text;
146 0         0 $text =~ s/^\(\s*//;
147 0         0 $text =~ s/\)\s*$//;
148 0         0 my @points = split /\s*,\s*/, $text;
149 0         0 $self = Geo::OGC::LineString->new();
150 0         0 for my $p (@points) {
151 0         0 $self->AddPoint(parse_wkt("POINT $prefix ($p)"));
152             }
153             } elsif ($type eq 'MULTILINESTRING') {
154 0 0       0 return Geo::OGC::MultiLineString->new unless $text;
155 0         0 $text =~ s/^\(\s*//;
156 0         0 $text =~ s/\)\s*$//;
157 0         0 my @strings = split /\)\s*,\s*\(/, $text;
158 0         0 $self = Geo::OGC::MultiLineString->new();
159 0         0 for my $s (@strings) {
160 0         0 $self->AddGeometry(parse_wkt("LINESTRING $prefix ($s)"));
161             }
162             } elsif ($type eq 'LINEARRING') {
163 11 50       25 return Geo::OGC::LinearRing->new unless $text;
164 11         19 $text =~ s/^\(\s*//;
165 11         18 $text =~ s/\)\s*$//;
166 11         78 my @points = split /\s*,\s*/, $text;
167 11         35 $self = Geo::OGC::LinearRing->new();
168 11         21 for my $p (@points) {
169 54         204 $self->AddPoint(parse_wkt("POINT $prefix ($p)"));
170             }
171             } elsif ($type eq 'POLYGON') {
172 11 50       26 return Geo::OGC::Polygon->new unless $text;
173 11         36 $text =~ s/^\(\s*//;
174 11         39 $text =~ s/\)\s*$//;
175 11         35 my @rings = split /\)\s*,\s*\(/, $text;
176 11         43 $self = Geo::OGC::Polygon->new();
177 11         54 $self->ExteriorRing(parse_wkt("LINEARRING $prefix (".shift(@rings).")"));
178 11         29 for my $r (@rings) {
179 0         0 $self->AddInteriorRing(parse_wkt("LINEARRING $prefix ($r)"));
180             }
181             } elsif ($type eq 'POLYHEDRALSURFACE') {
182 1 50       3 return Geo::OGC::PolyhedralSurface->new unless $text;
183 1         9 $text =~ s/^\(\s*//;
184 1         7 $text =~ s/\)\s*$//;
185 1         13 my @patches = split /\)\s*,\s*\(/, $text;
186 1         8 $self = Geo::OGC::PolyhedralSurface->new();
187 1         2 for my $p (@patches) {
188 6         20 $self->AddPatch(parse_wkt("POLYGON $prefix ($p)"));
189             }
190             } elsif ($type eq 'MULTIPOLYGON') {
191 2 50       6 return Geo::OGC::MultiPolygon->new unless $text;
192 2         8 $text =~ s/^\(\s*\(\s*//;
193 2         8 $text =~ s/\)\s*\)\s*$//;
194 2         9 my @polygons = split /\)\s*\)\s*,\s*\(\s*\(/, $text;
195 2         10 $self = Geo::OGC::MultiPolygon->new();
196 2         10 for my $p (@polygons) {
197 3         13 $self->AddGeometry(parse_wkt("POLYGON $prefix (($p))"));
198             }
199             } elsif ($type eq 'GEOMETRYCOLLECTION') {
200 2 100       26 return Geo::OGC::GeometryCollection->new unless $text;
201 1         11 my @b = $text =~ /,([A-Z])/g;
202 1         4 unshift @b,'';
203 1         8 my @geometries = split /,[A-Z]/, $text;
204 1         5 $self = Geo::OGC::GeometryCollection->new();
205 1         6 for my $i (0..$#geometries) {
206 2         12 $self->AddGeometry(parse_wkt($b[$i].$geometries[$i]));
207             }
208             } else {
209 0         0 my $b = substr $text, 0, 20;
210 0         0 croak "can't parse text beginning as: $b";
211             }
212 89         492 return $self;
213             }
214              
215             # counterclockwise from Sedgewick: Algorithms in C
216             sub ccw {
217 608     608 0 812 my($x0, $y0, $x1, $y1, $x2, $y2) = @_;
218 608         701 my $dx1 = $x1 - $x0;
219 608         650 my $dy1 = $y1 - $y0;
220 608         726 my $dx2 = $x2 - $x0;
221 608         662 my $dy2 = $y2 - $y0;
222 608 100       2026 return +1 if $dx1*$dy2 > $dy1*$dx2;
223 216 100       788 return -1 if $dx1*$dy2 < $dy1*$dx2;
224 55 100 66     224 return -1 if ($dx1*$dx2 < 0) or ($dy1*$dy2 < 0);
225 49 100       127 return +1 if ($dx1*$dx1+$dy1*$dy1) < ($dx2*$dx2+$dy2*$dy2);
226 42         127 return 0;
227             }
228              
229             # Test intersection of two lines from Sedgewick: Algorithms in C
230             sub intersect {
231 248     248 0 427 my($x11, $y11, $x12, $y12, $x21, $y21, $x22, $y22) = @_;
232 248   100     391 return ((ccw($x11, $y11, $x12, $y12, $x21, $y21)
233             *ccw($x11, $y11, $x12, $y12, $x22, $y22)) <= 0)
234             && ((ccw($x21, $y21, $x22, $y22, $x11, $y11)
235             *ccw($x21, $y21, $x22, $y22, $x12, $y12)) <= 0);
236             }
237              
238             sub intersection_point {
239 0     0 0 0 my($x11, $y11, $x12, $y12, $x21, $y21, $x22, $y22) = @_;
240 0         0 my $dy1 = $y12 - $y11;
241 0         0 my $dx1 = $x12 - $x11;
242 0         0 my $dy2 = $y22 - $y21;
243 0         0 my $dx2 = $x22 - $x21;
244             # (dy1*dx2 - dy2*dx1)*x = dx1*dx2*(y21-y11) - dy2*dx1*x21 + dy1*dx2*x11
245             # (dy1*dx1 - dy2*dx2)*y = dy1*dy2*(x21-x11) - dy1*dx2*y21 + dy2*dx1*y11
246 0         0 my $x = ($dx1*$dx2*($y21-$y11) - $dy2*$dx1*$x21 + $dy1*$dx2*$x11)/($dy1*$dx2 - $dy2*$dx1);
247 0         0 my $y = ($dy1*$dy2*($x21-$x11) - $dy1*$dx2*$y21 + $dy2*$dx1*$y11)/($dy1*$dx1 - $dy2*$dx2);
248 0         0 return ($x, $y);
249             }
250              
251             # Compute the distance of a point to a line.
252             sub distance_point_line_sqr {
253 118     118 0 179 my($x, $y, $x1, $y1, $x2, $y2) = @_;
254 118         141 my $dx = $x2-$x1;
255 118         130 my $dy = $y2-$y1;
256 118         171 my $l2 = $dx*$dx + $dy*$dy;
257 118         202 my $u = (($x - $x1) * $dx + ($y - $y1) * $dy) / $l2;
258 118 100       287 if ($u < 0) { # distance to point 1
    100          
259 11         29 return (($x-$x1)*($x-$x1) + ($y-$y1)*($y-$y1));
260             } elsif ($u > 1) { # distance to point 2
261 20         58 return (($x-$x2)*($x-$x2) + ($y-$y2)*($y-$y2));
262             } else {
263 87         117 my $ix = $x1 + $u * $dx;
264 87         116 my $iy = $y1 + $u * $dy;
265 87         219 return (($x-$ix)*($x-$ix) + ($y-$iy)*($y-$iy));
266             }
267             }
268              
269             =pod
270              
271             =item Clone()
272              
273             Clones this object, i.e., creates a spatially exact copy.
274              
275             =cut
276              
277             sub Clone {
278 6     6 1 6909 my($self) = @_;
279 6         12 my $clone = Geo::OGC::Geometry::new($self);
280 6         22 $self->copy($clone);
281 6         15 return $clone;
282             }
283              
284             =pod
285              
286             =item Precision($precision)
287              
288             Sets or gets the precision.
289              
290             Not in the specification.
291              
292             =cut
293              
294             sub Precision {
295 2     2 1 11 my($self, $Precision) = @_;
296             defined $Precision ?
297 2 50       17 $self->{Precision} = $Precision-1 : $self->{Precision}+1;
298             }
299              
300             =pod
301              
302             =item Dimension()
303              
304             The dimension (2 or 3) of this geometric object. In non-homogeneous
305             collections, this will return the largest topological dimension of the
306             contained objects.
307              
308             =cut
309              
310             sub Dimension {
311 0     0 1 0 my($self) = @_;
312 0         0 croak "Dimension method for class ".ref($self)." is not implemented yet";
313             }
314              
315             =pod
316              
317             =item GeometryType()
318              
319             Returns the geometry type of this object.
320              
321             =cut
322              
323             sub GeometryType {
324 0     0 1 0 my($self) = @_;
325 0         0 croak "GeometryType method for class ".ref($self)." is not implemented yet";
326             }
327              
328             =pod
329              
330             =item SRID($srid)
331              
332             Get or set Spatial Reference System ID for this object.
333              
334             =cut
335              
336             sub SRID {
337 0     0 1 0 my($self, $SRID) = @_;
338             defined $SRID ?
339 0 0       0 $self->{SRID} = $SRID : $self->{SRID};
340             }
341              
342             =pod
343              
344             =item Envelope()
345              
346             Compute the minimum bounding box for this geometry as a ring.
347              
348             =cut
349              
350             sub Envelope {
351 0     0 1 0 my($self) = @_;
352 0         0 croak "Envelope method for class ".ref($self)." is not implemented yet";
353             }
354              
355             # A helper method used by AsText
356             sub as_text {
357 0     0 0 0 my($self, $force_parens, $include_tag) = @_;
358 0         0 croak "as_text method for class ".ref($self)." is not implemented yet";
359             }
360              
361             =pod
362              
363             =item AsText()
364              
365             This object in Well-known Text representation.
366              
367             =cut
368              
369             sub AsText {
370 4     4 1 2519 my($self) = @_;
371 4 100       17 return uc($self->GeometryType).' '.'EMPTY' if $self->IsEmpty;
372 3         13 return $self->as_text(1, 1);
373             }
374              
375             =pod
376              
377             =item AsBinary()
378              
379             This object in Well-known Binary representation.
380              
381             =cut
382              
383             sub AsBinary {
384 0     0 1 0 my($self) = @_;
385 0         0 croak "AsBinary method for class ".ref($self)." is not implemented yet";
386             }
387              
388             =pod
389              
390             =item IsEmpty()
391              
392             Returns true if this object is empty, i.e. contains no points.
393              
394             =cut
395              
396             sub IsEmpty {
397 0     0 1 0 my($self) = @_;
398 0         0 croak "IsEmpty method for class ".ref($self)." is not implemented yet";
399             }
400              
401             =pod
402              
403             =item IsSimple()
404              
405             Returns true if this geometric object has no anomalous geometric
406             points, such as self intersection or self tangency.
407              
408             =cut
409              
410             sub IsSimple {
411 0     0 1 0 my($self) = @_;
412 0         0 croak "IsSimple method for class ".ref($self)." is not implemented yet";
413             }
414              
415             =pod
416              
417             =item Is3D()
418              
419             Returns true if this geometric object has z coordinate values.
420              
421             =cut
422              
423             sub Is3D {
424 0     0 1 0 my($self) = @_;
425 0         0 croak "Is3D method for class ".ref($self)." is not implemented yet";
426             }
427              
428             =pod
429              
430             =item IsMeasured()
431              
432             Returns true if this object has m coordinate values.
433              
434             =cut
435              
436             sub IsMeasured {
437 0     0 1 0 my($self) = @_;
438 0         0 croak "IsMeasured method for class ".ref($self)." is not implemented yet";
439             }
440              
441             =pod
442              
443             =item Boundary()
444              
445             Returns the closure of the combinatorial boundary of this object.
446              
447             =cut
448              
449             sub Boundary {
450 0     0 1 0 my($self) = @_;
451 0         0 croak "Boundary method for class ".ref($self)." is not implemented yet";
452             }
453              
454             =pod
455              
456             =item Equals($geometry)
457              
458             Returns true if this object is "spatially equal" to the other
459             geometry.
460              
461             =cut
462              
463             sub Equals {
464 0     0 1 0 my($self, $geom) = @_;
465 0         0 croak "Equals method for class ".ref($self)." is not implemented yet";
466             }
467              
468             =pod
469              
470             =item Disjoint($geometry)
471              
472             Returns true if this object is "spatially disjoint" from the other geometry.
473              
474             =cut
475              
476             sub Disjoint {
477 0     0 1 0 my($self, $geom) = @_;
478 0         0 croak "Disjoint method for class ".ref($self)." is not implemented yet";
479             }
480              
481             =pod
482              
483             =item Intersects($geometry)
484              
485             Returns true if this object "spatially intersects" the other geometry.
486              
487             =cut
488              
489             sub Intersects {
490 0     0 1 0 my($self, $geom) = @_;
491 0         0 croak "Intersects method for class ".ref($self)." is not implemented yet";
492             }
493              
494             =pod
495              
496             =item Touches($geometry)
497              
498             Returns true if this object "spatially touches" the other geometry.
499              
500             =cut
501              
502             sub Touches {
503 0     0 1 0 my($self, $geom) = @_;
504 0         0 croak "Touches method for class ".ref($self)." is not implemented yet";
505             }
506              
507             =pod
508              
509             =item Crosses($geometry)
510              
511             Returns true if this object "spatially crosses" the other geometry.
512              
513             =cut
514              
515             sub Crosses {
516 0     0 1 0 my($self, $geom) = @_;
517 0         0 croak "Crosses method for class ".ref($self)." is not implemented yet";
518             }
519              
520             =pod
521              
522             =item Within($geometry)
523              
524             Returns true if this object is "spatially within" the other geometry.
525              
526             =cut
527              
528             sub Within {
529 0     0 1 0 my($self, $geom) = @_;
530 0         0 croak "Within method for class ".ref($self)." is not implemented yet";
531             }
532              
533             =pod
534              
535             =item Contains($geometry)
536              
537             Returns true if this object "spatially contains" the other geometry.
538              
539             =cut
540              
541             sub Contains {
542 0     0 1 0 my($self, $geom) = @_;
543 0         0 croak "Contains method for class ".ref($self)." is not implemented yet";
544             }
545              
546             =pod
547              
548             =item Overlaps($geometry)
549              
550             Returns true if this object "spatially overlaps" the other geometry.
551              
552             =cut
553              
554             sub Overlaps {
555 0     0 1 0 my($self, $geom) = @_;
556 0         0 croak "Overlaps method for class ".ref($self)." is not implemented yet";
557             }
558              
559             =pod
560              
561             =item Relate($geometry, $intersectionPatternMatrix)
562              
563             Returns true if this object is spatially related to the other geometry
564             by testing for intersections between the interior, boundary and
565             exterior of the two geometric objects as specified by the values in
566             the intersectionPatternMatrix. This returns FALSE if all the tested
567             intersections are empty except exterior (this) intersect exterior
568             (another).
569              
570             =cut
571              
572             sub Relate {
573 0     0 1 0 my($self, $geom, $int_pat) = @_;
574 0         0 croak "Relate method for class ".ref($self)." is not implemented yet";
575             }
576              
577             =pod
578              
579             =item LocateAlong($mValue)
580              
581             Returns a derived geometry collection value that matches the
582             specified m coordinate value.
583              
584             =cut
585              
586             sub LocateAlong {
587 0     0 1 0 my($self, $mValue) = @_;
588 0         0 croak "LocateAlong method for class ".ref($self)." is not implemented yet";
589             }
590              
591             =pod
592              
593             =item LocateBetween($mStart, $mEnd)
594              
595             Returns a derived geometry collection value that matches the specified
596             range of m coordinate values inclusively.
597              
598             =cut
599              
600             sub LocateBetween {
601 0     0 1 0 my($self, $mStart, $mEnd) = @_;
602 0         0 croak "LocateBetween method for class ".ref($self)." is not implemented yet";
603             }
604              
605             =pod
606              
607             =item Distance($geometry)
608              
609             Returns the shortest distance between any two points between this object and the other geometry.
610              
611             =cut
612              
613             sub Distance {
614 0     0 1 0 my($self, $geom) = @_;
615 0         0 croak "Distance method for class ".ref($self)." is not implemented yet";
616             }
617              
618             =pod
619              
620             =item Buffer($distance)
621              
622             Returns a surface whose points are closer than or at the given distance from this object.
623              
624             =cut
625              
626             sub Buffer {
627 0     0 1 0 my($self, $distance) = @_;
628 0         0 croak "Buffer method for class ".ref($self)." is not implemented yet";
629             }
630              
631             =pod
632              
633             =item ConvexHull()
634              
635             Returns a convex hull of this object.
636              
637             =cut
638              
639             sub ConvexHull {
640 0     0 1 0 my($self) = @_;
641 0         0 croak "ConvexHull method for class ".ref($self)." is not implemented yet";
642             }
643              
644             =pod
645              
646             =item Intersection($geometry)
647              
648             Returns a point set intersection of this object with the other geometry.
649              
650             =cut
651              
652             sub Intersection {
653 0     0 1 0 my($self, $geom) = @_;
654 0         0 croak "Intersection method for class ".ref($self)." is not implemented yet";
655             }
656              
657             =pod
658              
659             =item Union($geometry)
660              
661             Returns a point set union of this object with the other geometry.
662              
663             =cut
664              
665             sub Union {
666 0     0 1 0 my($self, $geom) = @_;
667 0         0 croak "Union method for class ".ref($self)." is not implemented yet";
668             }
669              
670             =pod
671              
672             =item Difference($geometry)
673              
674             Returns a point set difference of this object with the other geometry.
675              
676             =cut
677              
678             sub Difference {
679 0     0 1 0 my($self, $geom) = @_;
680 0         0 croak "Difference method for class ".ref($self)." is not implemented yet";
681             }
682              
683             =pod
684              
685             =item SymDifference($geometry)
686              
687             Returns a point set symmetric difference of this object with the other geometry.
688              
689             =cut
690              
691             sub SymDifference {
692 0     0 1 0 my($self, $geom) = @_;
693 0         0 croak "SymDifference method for class ".ref($self)." is not implemented yet";
694             }
695              
696             =pod
697              
698             =item MakeCollection()
699              
700             Creates a collection which contains this geometry.
701              
702             =cut
703              
704             sub MakeCollection {
705 0     0 1 0 my($self) = @_;
706 0         0 croak "MakeCollection method for class ".ref($self)." is not implemented";
707             }
708              
709             =pod
710              
711             =item ApplyTransformation($transformation)
712              
713             transf = A point transformation method which will be applied for all
714             the points in the geometry as:
715              
716             ($new_x, $new_y, $new_z) = $transformation->($x, $y, $z)
717              
718             Not in the specification.
719              
720             =cut
721              
722             sub ApplyTransformation {
723 0     0 1 0 my($self, $transf) = @_;
724 0         0 croak "ApplyTransformation method for class ".ref($self)." is not implemented";
725             }
726              
727             =pod
728              
729             =back
730              
731             =head2 Geo::OGC::SpatialReferenceSystem
732              
733             =cut
734              
735             package Geo::OGC::SpatialReferenceSystem;
736              
737 3     3   23 use strict;
  3         5  
  3         120  
738 3     3   15 use Carp;
  3         6  
  3         372  
739              
740             sub new {
741 0     0   0 my($package, %params) = @_;
742 0         0 my $self = {};
743 0   0     0 bless $self => (ref($package) or $package);
744             }
745              
746             =pod
747              
748             =head2 Geo::OGC::Point
749              
750             A 0-dimensional geometric object.
751              
752             =cut
753              
754             package Geo::OGC::Point;
755              
756 3     3   47 use strict;
  3         6  
  3         60  
757 3     3   14 use Carp;
  3         5  
  3         252  
758 3     3   49 use Geo::OGC::Geometry qw/:all/;
  3         4  
  3         8615  
759              
760             our @ISA = qw( Geo::OGC::Geometry );
761              
762             =pod
763              
764             =over
765              
766             =item new()
767              
768             A new point object can be created (besides using WKT) with
769              
770             $point = Geo::OGC::Point->new($x, $y);
771             $point = Geo::OGC::Point->new($x, $y, $z);
772             $point = Geo::OGC::Point->new(point => [$x, $y]);
773             $point = Geo::OGC::Point->new(point => [$x, $y, $z]);
774             $point = Geo::OGC::Point->new(point => [$x, $y, $z, $m]);
775             $point = Geo::OGC::Point->new(pointz => [$x, $y, $z]);
776             $point = Geo::OGC::Point->new(pointz => [$x, $y, $z, $m]);
777             $point = Geo::OGC::Point->new(pointm => [$x, $y, $m]);
778             $point = Geo::OGC::Point->new(pointm => [$x, $y, $z, $m]);
779             $point = Geo::OGC::Point->new(pointzm => [$x, $y, $z, $m]);
780             $point = Geo::OGC::Point->new(X => $x, Y => $y);
781             $point = Geo::OGC::Point->new(X => $x, Y => $y, Z => $z);
782             $point = Geo::OGC::Point->new(X => $x, Y => $y, Z => $z, M => $m);
783              
784             =cut
785              
786             sub new {
787 205     205   6073 my $package = shift;
788 205         233 my %params;
789 205 100 100     1238 if (@_ == 2 and !($_[0] =~ /^[XYZMpP]/)) { # allow syntax Point->new($x, $y);
    100          
790 95         178 $params{X} = shift;
791 95         162 $params{Y} = shift;
792             } elsif (@_ == 3) { # allow syntax Point->new($x, $y, $z);
793 1         3 $params{X} = shift;
794 1         2 $params{Y} = shift;
795 1         2 $params{Z} = shift;
796             } else {
797 109         248 %params = @_;
798             }
799             # support comma as decimal point, and space in numbers
800 205         400 for my $k (keys %params) {
801 304 100       582 if (ref($params{$k})) {
802 108         118 for my $p (@{$params{$k}}) {
  108         197  
803 257         356 $p =~ s/,/./g;
804 257         435 $p =~ s/ //g;
805             #print STDERR "point: $_\n";
806             }
807             } else {
808 196         359 $params{$k} =~ s/,/./g;
809 196         384 $params{$k} =~ s/ //g;
810             #print STDERR "point: $_ => $params{$_}\n";
811             }
812             }
813 205         510 my $self = Geo::OGC::Geometry::new($package, %params);
814 205         701 return $self;
815             }
816              
817             sub init {
818 205     205   414 my($self, %params) = @_;
819 205         458 $self->SUPER::init(%params);
820             # +0 catches non-numeric error, if warnings are on
821 205 100       647 if ($params{point}) {
    100          
    100          
    50          
822 67         150 $self->{X} = $params{point}[0]+0;
823 67         110 $self->{Y} = $params{point}[1]+0;
824 67 50       89 $self->{Z} = $params{point}[2]+0 if @{$params{point}} > 2;
  67         277  
825 67 50       82 $self->{M} = $params{point}[3]+0 if @{$params{point}} > 3;
  67         246  
826             } elsif ($params{pointz}) {
827 30         58 $self->{X} = $params{pointz}[0]+0;
828 30         41 $self->{Y} = $params{pointz}[1]+0;
829 30         43 $self->{Z} = $params{pointz}[2]+0;
830 30 50       28 if (@{$params{pointz}} == 4) {
  30         83  
831 0         0 $self->{M} = $params{pointz}[3]+0;
832             }
833             } elsif ($params{pointm}) {
834 11         21 $self->{X} = $params{pointm}[0]+0;
835 11         16 $self->{Y} = $params{pointm}[1]+0;
836 11 50       14 if (@{$params{pointm}} == 3) {
  11 0       23  
837 11         26 $self->{M} = $params{pointm}[2]+0;
838 0         0 } elsif (@{$params{pointm}} == 4) {
839 0         0 $self->{Z} = $params{pointm}[2]+0;
840 0         0 $self->{M} = $params{pointm}[3]+0;
841             }
842             } elsif ($params{pointzm}) {
843 0         0 $self->{X} = $params{pointm}[0]+0;
844 0         0 $self->{Y} = $params{pointm}[1]+0;
845 0         0 $self->{Z} = $params{pointm}[2]+0;
846 0         0 $self->{M} = $params{pointm}[3]+0;
847             } else {
848 97 50       284 $self->{X} = $params{X}+0 if exists $params{X};
849 97 50       290 $self->{Y} = $params{Y}+0 if exists $params{Y};
850 97 100       173 $self->{Z} = $params{Z}+0 if exists $params{Z};
851 97 100       303 $self->{M} = $params{M}+0 if exists $params{M};
852             }
853             }
854              
855             sub copy {
856 0     0   0 my($self, $clone) = @_;
857 0         0 $self->SUPER::copy($clone);
858 0         0 for my $a (qw/X Y Z M/) {
859 0 0       0 $clone->{$a} = $self->{$a} if exists $self->{$a};
860             }
861             }
862              
863             # Return a reference to an anonymous array that contains the point data.
864             # Note that there is no difference between [x,y,z] and [x,y,m]
865             sub point {
866 30     30   38 my($self) = @_;
867 30         60 my @point = ($self->{X}, $self->{Y});
868 30 50       61 push @point, $self->{Z} if exists $self->{Z};
869 30 50       55 push @point, $self->{M} if exists $self->{M};
870 30         97 return [@point];
871             }
872              
873             sub GeometryType {
874 0     0   0 return 'Point';
875             }
876              
877             sub Dimension {
878 0     0   0 return 0;
879             }
880              
881             sub Clone {
882 30     30   44 my($self) = @_;
883 30 50       65 my $m = exists $self->{M} ? 'm' : '';
884 30         64 return Geo::OGC::Point::new($self, "point$m" => $self->point);
885             }
886              
887             sub IsEmpty {
888 0     0   0 my($self) = @_;
889 0         0 return !(exists $self->{X});
890             }
891              
892             # A point is always simple.
893             sub IsSimple {
894 0     0   0 my($self) = @_;
895 0         0 return 1;
896             }
897              
898             sub Is3D {
899 46     46   63 my($self) = @_;
900 46         172 return exists $self->{Z};
901             }
902              
903             sub IsMeasured {
904 41     41   57 my($self) = @_;
905 41         135 return exists $self->{M};
906             }
907              
908             sub Boundary {
909 0     0   0 my($self) = @_;
910 0         0 return $self->Clone;
911             }
912              
913             sub X {
914 0     0   0 my($self, $X) = @_;
915             defined $X ?
916 0 0       0 $self->{X} = $X : $self->{X};
917             }
918              
919             sub Y {
920 0     0   0 my($self, $Y) = @_;
921             defined $Y ?
922 0 0       0 $self->{Y} = $Y : $self->{Y};
923             }
924              
925             =pod
926              
927             =item Z()
928              
929             Get or set the z coordinate.
930              
931             Setting is not in the specification.
932              
933             =cut
934              
935             sub Z {
936 0     0   0 my($self, $Z) = @_;
937             defined $Z ?
938 0 0       0 $self->{Z} = $Z : (exists $self->{Z} ? $self->{Z} : undef);
    0          
939             }
940              
941             =pod
942              
943             =item M()
944              
945             Get or set the measure.
946              
947             Setting is not in the specification.
948              
949             =cut
950              
951             sub M {
952 0     0   0 my($self, $M) = @_;
953             defined $M ?
954 0 0       0 $self->{M} = $M : (exists $self->{M} ? $self->{M} : undef);
    0          
955             }
956              
957             sub as_text {
958 16     16   27 my($self, $force_parens, $include_tag) = @_;
959 16         20 my @coords;
960 16 50       47 my $ZM = exists $self->{Z} ? 'Z' : '';
961 16 50       38 if (exists $self->{Precision}) {
962 0         0 for my $a (qw/X Y Z/) {
963 0 0       0 last unless exists $self->{$a};
964 0         0 my $s = sprintf('%.'.$self->{Precision}.'e', $self->{$a});
965 0         0 push @coords, $s;
966             }
967             } else {
968 16         30 for my $a (qw/X Y Z/) {
969 48 100       189 push @coords, $self->{$a} if exists $self->{$a};
970             }
971             }
972 16 50       45 if (exists $self->{M}) {
973 0         0 push @coords, $self->{M};
974 0         0 $ZM .= 'M';
975             }
976 16         39 my $text = join(' ', @coords);
977 16         33 $text =~ s/,/./g; # setting POSIX numeric locale does not seem to work??
978 16 100       43 $text = '('.$text.')' if $force_parens;
979 16 100       43 $text = "POINT$ZM ".$text if $include_tag;
980 16         63 return $text;
981             }
982              
983             # what should we do with z?
984             sub Equals {
985 166     166   258 my($self, $geom) = @_;
986 166 50       571 return 0 unless $geom->isa('Geo::OGC::Point');
987 166 100       360 if (exists $self->{Precision}) {
988 4         16 for my $a (qw/X Y Z/) {
989 10 100 66     79 last unless exists $self->{$a} and exists $geom->{$a};
990 7         116 my $s = sprintf('%.'.$self->{Precision}.'e', $self->{$a});
991 7         62 my $g = sprintf('%.'.$self->{Precision}.'e', $geom->{$a});
992 7 100       77 return 0 if $s != $g;
993             }
994 3         28 return 1;
995             }
996 162         870 return (($self->{X}-$geom->{X})**2+($self->{Y}-$geom->{Y})**2) < $SNAP_DISTANCE_SQR;
997             }
998              
999             sub DistanceToLineStringSqr {
1000 24     24   34 my($self, $linestring) = @_;
1001 24         54 my($x, $y) = ($self->{X}, $self->{Y});
1002 24         32 my $p1 = $linestring->{Points}[0];
1003 24 50       56 return unless $p1;
1004 24         36 my $p2 = $linestring->{Points}[1];
1005 24 50       49 return (($x-$p1->{X})**2+($y-$p1->{Y})**2) unless $p2;
1006 24         72 my $distance = distance_point_line_sqr($x, $y, $p1->{X}, $p1->{Y}, $p2->{X}, $p2->{Y});
1007 24         32 for my $i (2..$#{$linestring->{Points}}) {
  24         73  
1008 12         13 $p1 = $p2;
1009 12         13 $p2 = $linestring->{Points}[$i];
1010 12         26 my $d = distance_point_line_sqr($x, $y, $p1->{X}, $p1->{Y}, $p2->{X}, $p2->{Y});
1011 12 100       27 $distance = $d if $d < $distance;
1012             }
1013 24         110 return $distance;
1014             }
1015              
1016             sub Distance {
1017 0     0   0 my($self, $geom) = @_;
1018 0 0       0 if ($geom->isa('Geo::OGC::Point')) {
    0          
    0          
    0          
1019 0         0 return sqrt(($self->{X}-$geom->{X})**2 + ($self->{Y}-$geom->{Y})**2);
1020             } elsif ($geom->isa('Geo::OGC::LineString')) {
1021 0         0 return sqrt($self->DistanceToLineStringSqr($geom));
1022             } elsif ($geom->isa('Geo::OGC::Polygon')) {
1023 0 0       0 if ($geom->{ExteriorRing}->IsPointIn($self)) {
1024 0         0 for my $ring (@{$geom->{InteriorRings}}) {
  0         0  
1025 0 0       0 return sqrt($self->DistanceToLineStringSqr($ring)) if $ring->IsPointIn($self);
1026             }
1027 0         0 return 0;
1028             } else {
1029 0         0 return sqrt($self->DistanceToLineStringSqr($geom->{ExteriorRing}));
1030             }
1031             } elsif ($geom->isa('Geo::OGC::GeometryCollection')) {
1032 0         0 my $dist = $self->Distance($geom->{Geometries}[0]);
1033 0         0 for my $g (@{$geom->{Geometries}}[1..$#{$geom->{Geometries}}]) {
  0         0  
  0         0  
1034 0         0 my $d = $self->Distance($g);
1035 0 0       0 $dist = $d if $d < $dist;
1036             }
1037 0         0 return $dist;
1038             } else {
1039 0         0 croak "can't compute distance between a ".ref($geom)." and a point";
1040             }
1041             }
1042              
1043             # what should this be?
1044             sub Envelope {
1045 0     0   0 my($self) = @_;
1046 0         0 my $r = Geo::OGC::LinearRing->new;
1047 0         0 $r->AddPoint(Geo::OGC::Point->new($self->{X}, $self->{Y}));
1048 0         0 $r->AddPoint(Geo::OGC::Point->new($self->{X}, $self->{Y}));
1049 0         0 $r->AddPoint(Geo::OGC::Point->new($self->{X}, $self->{Y}));
1050 0         0 $r->AddPoint(Geo::OGC::Point->new($self->{X}, $self->{Y}));
1051 0         0 $r->Close;
1052 0         0 return $r;
1053             }
1054              
1055             sub Area {
1056 0     0   0 return 0;
1057             }
1058              
1059             sub Intersection {
1060 0     0   0 my($self, $geom) = @_;
1061 0 0       0 return $self->Clone if $self->Within($geom);
1062 0         0 return undef;
1063             }
1064              
1065             sub Within {
1066 43     43   56 my($self, $geom) = @_;
1067 43 100       191 if ($geom->isa('Geo::OGC::Point')) {
    50          
    0          
    0          
1068 23 100       44 return $self->Equals($geom) ? 1 : 0;
1069             } elsif ($geom->isa('Geo::OGC::LineString')) {
1070 20 100       47 return $self->DistanceToLineStringSqr($geom) < $SNAP_DISTANCE_SQR ? 1 : 0;
1071             } elsif ($geom->isa('Geo::OGC::Polygon')) {
1072 0 0       0 if (!($geom->{ExteriorRing}->IsPointStricktlyOut($self))) {
1073 0         0 for my $ring (@{$geom->{InteriorRing}}) {
  0         0  
1074 0 0       0 return 0 if $ring->IsPointStricktlyIn($self);
1075             }
1076 0         0 return 1;
1077             }
1078 0         0 return 0;
1079             } elsif ($geom->isa('Geo::OGC::GeometryCollection')) {
1080 0         0 for my $g (@{$geom->{Geometries}}) {
  0         0  
1081 0 0       0 return 1 if $self->Within($g);
1082             }
1083 0         0 return 0;
1084             } else {
1085 0         0 croak "point within ".ref($geom)." is not implemented yet";
1086             }
1087             }
1088              
1089             sub MakeCollection {
1090 0     0   0 my($self) = @_;
1091 0         0 my $coll = Geo::OGC::MultiPoint->new;
1092 0         0 $coll->AddGeometry($self);
1093 0         0 return $coll;
1094             }
1095              
1096             sub ApplyTransformation {
1097 1     1   11 my($self, $transf) = @_;
1098 1 50       3 if (@_ > 2) {
1099 0         0 ($self->{X}, $self->{Y}, $self->{Z}) = $transf->($self->{X}, $self->{Y}, $self->{Z});
1100             } else {
1101 1         6 ($self->{X}, $self->{Y}) = $transf->($self->{X}, $self->{Y});
1102             }
1103             }
1104              
1105             sub ClosestVertex {
1106 0     0   0 my($self, $x, $y) = @_;
1107 0         0 return (($self->{X}-$x)**2 + ($self->{Y}-$y)**2);
1108             }
1109              
1110             sub VertexAt {
1111 0     0   0 my $self = shift;
1112 0         0 return ($self);
1113             }
1114              
1115             sub ClosestPoint {
1116 0     0   0 my($self, $x, $y) = @_;
1117 0         0 return (($self->{X}-$x)**2 + ($self->{Y}-$y)**2);
1118             }
1119              
1120       0     sub AddVertex {
1121             }
1122              
1123       0     sub DeleteVertex {
1124             }
1125              
1126             =pod
1127              
1128             =back
1129              
1130             =head2 Geo::OGC::Curve
1131              
1132             A 1-dimensional geometric object.
1133              
1134             =cut
1135              
1136             package Geo::OGC::Curve;
1137              
1138 3     3   22 use strict;
  3         11  
  3         66  
1139 3     3   26 use Carp;
  3         11  
  3         184  
1140 3     3   22 use Geo::OGC::Geometry qw/:all/;
  3         4  
  3         5390  
1141              
1142             our @ISA = qw( Geo::OGC::Geometry );
1143              
1144             sub new {
1145 24     24   50 my($package, %params) = @_;
1146 24         62 my $self = Geo::OGC::Geometry::new($package, %params);
1147 24         49 return $self;
1148             }
1149              
1150             sub init {
1151 26     26   47 my($self, %params) = @_;
1152 26         73 $self->SUPER::init(%params);
1153 26         50 $self->{Points} = [];
1154 26 100       94 if ($params{points}) {
    50          
1155 4         10 for my $p (@{$params{points}}) {
  4         10  
1156 11         34 $self->AddPoint(Geo::OGC::Point->new(point=>$p));
1157             }
1158             } elsif ($params{pointsm}) {
1159 0         0 for my $p (@{$params{pointsm}}) {
  0         0  
1160 0         0 $self->AddPoint(Geo::OGC::Point->new(pointm=>$p));
1161             }
1162             }
1163             }
1164              
1165             sub copy {
1166 2     2   3 my($self, $clone) = @_;
1167 2         6 $self->SUPER::copy($clone);
1168 2         3 for my $p (@{$self->{Points}}) {
  2         4  
1169 10         19 $clone->AddPoint($p->Clone);
1170             }
1171             }
1172              
1173             sub IsEmpty {
1174 0     0   0 my($self) = @_;
1175 0 0 0     0 return 1 if !$self->{Points} or @{$self->{Points}} == 0;
  0         0  
1176 0         0 return 0;
1177             }
1178              
1179             sub GeometryType {
1180 0     0   0 return 'Curve';
1181             }
1182              
1183             sub Dimension {
1184 0     0   0 return 1;
1185             }
1186              
1187             sub as_text {
1188 3     3   5 my($self, $force_parens, $include_tag) = @_;
1189 3         5 my $text = join(',', map {$_->as_text} @{$self->{Points}});
  15         40  
  3         9  
1190 3         10 $text = '('.$text.')';
1191 3 50       15 my $ZM = $self->Is3D ? 'Z' : '';
1192 3 50       12 $ZM .= 'M' if $self->IsMeasured;
1193 3 50       9 $text = uc($self->GeometryType).$ZM.' '.$text if $include_tag;
1194 3         9 return $text;
1195             }
1196              
1197             =pod
1198              
1199             =over
1200              
1201             =item AddPoint($point, $i)
1202              
1203             Adds a point to the end (N+1) of the curve by default.
1204              
1205             point = A Point object
1206             i [optional] = The location in the sequence (1..N+1) where to add the Point.
1207              
1208             Not in the specification.
1209              
1210             =cut
1211              
1212             sub AddPoint {
1213 121     121   222 my($self, $point, $i) = @_;
1214 121 50 33     712 croak 'usage: Curve->AddPoint($point) '
1215             unless $point and $point->isa('Geo::OGC::Point');
1216 121         184 my $points = $self->{Points};
1217 121 100       221 if (defined $i) {
1218 4         6 my $temp = $points->[$i-1];
1219 4         15 splice @$points, $i-1, 1, ($point, $temp);
1220             } else {
1221 117         382 push @$points, $point;
1222             }
1223             }
1224              
1225             =pod
1226              
1227             =item DeletePoint($i)
1228              
1229             Delete a point from the curve.
1230              
1231             i = The location in the sequence (1..N) from where to delete the Point.
1232              
1233             Not in the specification.
1234              
1235             =cut
1236              
1237             sub DeletePoint {
1238 5     5   10 my($self, $i) = @_;
1239 5         6 my $points = $self->{Points};
1240 5         12 splice @$points, $i-1, 1;
1241             }
1242              
1243             sub StartPoint {
1244 25     25   257 my($self) = @_;
1245 25         32 my $points = $self->{Points};
1246 25 50       341 return $points->[0] if @$points;
1247             }
1248              
1249             sub EndPoint {
1250 25     25   36 my($self) = @_;
1251 25         30 my $points = $self->{Points};
1252 25 50       313 return $points->[$#$points] if @$points;
1253             }
1254              
1255             =pod
1256              
1257             =item NumPoints()
1258              
1259             Return the number of points in the sequence.
1260              
1261             =cut
1262              
1263             sub NumPoints {
1264 1     1   2 my($self) = @_;
1265 1         2 scalar(@{$self->{Points}});
  1         5  
1266             }
1267              
1268             =pod
1269              
1270             =item PointN($N, $point)
1271              
1272             Get or set the point of the sequence.
1273              
1274             N = A location in the sequence.
1275             point [optional] = A Point object, if defined sets the point to index N.
1276              
1277             The first point has the index 1 as OGC SF SQL conformance test uses 1-based indexing.
1278              
1279             =cut
1280              
1281             sub PointN {
1282 9     9   15 my($self, $N, $point) = @_;
1283 9         13 my $points = $self->{Points};
1284 9 50       20 $points->[$N-1] = $point if defined $point;
1285 9         37 return $points->[$N-1];
1286             }
1287              
1288             sub Is3D {
1289 10     10   15 my($self) = @_;
1290 10         15 for my $p (@{$self->{Points}}) {
  10         25  
1291 45 100       97 return 1 if $p->Is3D;
1292             }
1293 9         34 return 0;
1294             }
1295              
1296             sub IsMeasured {
1297 8     8   15 my($self) = @_;
1298 8         10 for my $p (@{$self->{Points}}) {
  8         17  
1299 40 50       83 return 1 if $p->IsMeasured;
1300             }
1301 8         29 return 0;
1302             }
1303              
1304             sub IsClosed {
1305 24     24   46 my($self) = @_;
1306 24         293 $self->StartPoint()->Equals($self->EndPoint());
1307             }
1308              
1309             =pod
1310              
1311             =item Close()
1312              
1313             Close the curve by adding the first point also as the last point.
1314              
1315             Not in the specification.
1316              
1317             =cut
1318              
1319             sub Close {
1320 6     6   12 my($self) = @_;
1321 6         271 push @{$self->{Points}}, $self->{Points}[0];
  6         24  
1322             }
1323              
1324             =pod
1325              
1326             =item IsRing($upgrade)
1327              
1328             Tests whether this curve is a ring, i.e., closed and simple.
1329              
1330             upgrade [optional, not in the specification] = Upgrades this curve into a Ring if this really could be a ring.
1331              
1332             =cut
1333              
1334             sub IsRing {
1335 2     2   4 my($self, $upgrade) = @_;
1336 2   33     6 my $ret = ($self->IsClosed and $self->IsSimple);
1337 2 50 33     17 bless($self, 'Geo::OGC::LinearRing') if $ret and $upgrade;
1338 2         8 return $ret;
1339             }
1340              
1341             # should use Precision if one exists
1342             sub Equals {
1343 0     0   0 my($self, $geom) = @_;
1344 0 0       0 return 0 unless $geom->isa('Geo::OGC::Curve');
1345 0 0       0 return 0 unless $#{$self->{Points}} == $#{$geom->{Points}};
  0         0  
  0         0  
1346 0         0 for my $i (0..$#{$self->{Points}}) {
  0         0  
1347 0 0       0 return 0 unless $self->{Points}[$i]->Equals($geom->{Points}[$i]);
1348             }
1349 0         0 return 1;
1350             }
1351              
1352             sub Area {
1353 0     0   0 return 0;
1354             }
1355              
1356             sub MakeCollection {
1357 0     0   0 my($self) = @_;
1358 0         0 my $coll = Geo::OGC::MultiCurve->new;
1359 0         0 $coll->AddGeometry($self);
1360 0         0 return $coll;
1361             }
1362              
1363             sub ApplyTransformation {
1364 0     0   0 my($self, $transf) = @_;
1365 0         0 for my $p (@{$self->{Points}}) {
  0         0  
1366 0         0 $p->ApplyTransformation($transf);
1367             }
1368             }
1369              
1370             =pod
1371              
1372             =item Reverse()
1373              
1374             Reverse the order of the points in the sequence.
1375              
1376             Not in the specification.
1377              
1378             =cut
1379              
1380             sub Reverse {
1381 1     1   343 my($self) = @_;
1382 1         4 @{$self->{Points}} = reverse @{$self->{Points}};
  1         4  
  1         6  
1383             }
1384              
1385             sub ClosestVertex {
1386 0     0   0 my($self, $x, $y) = @_;
1387 0 0       0 return unless @{$self->{Points}};
  0         0  
1388 0         0 my($dmin) = $self->{Points}[0]->ClosestVertex($x, $y);
1389 0         0 my $i = 0;
1390 0         0 for my $j (1..$#{$self->{Points}}) {
  0         0  
1391 0         0 my($d) = $self->{Points}[$j]->ClosestVertex($x, $y);
1392 0 0       0 ($i, $dmin) = ($j, $d) if $d < $dmin;
1393             }
1394 0         0 return ($i, $dmin);
1395             }
1396              
1397             sub VertexAt {
1398 0     0   0 my($self, $i) = @_;
1399 0         0 return ($self->{Points}[0], $self->{Points}[$#{$self->{Points}}])
1400 0 0 0     0 if (($i == 0 or $i == $#{$self->{Points}}) and $self->isa('Geo::OGC::LinearRing'));
      0        
1401 0         0 return ($self->{Points}[$i]);
1402             }
1403              
1404             sub _closest_point {
1405 0     0   0 my($x0, $y0, $x1, $y1, $x, $y) = @_;
1406 0         0 my $ab2 = ($x1-$x0)*($x1-$x0) + ($y1-$y0)*($y1-$y0);
1407 0         0 my $ap_ab = ($x-$x0)*($x1-$x0) + ($y-$y0)*($y1-$y0);
1408 0         0 my $t = $ap_ab/$ab2;
1409 0 0       0 if ($t < 0) {$t = 0} elsif ($t > 1) {$t = 1}
  0 0       0  
  0         0  
1410 0         0 my $xp = $x0+$t*($x1-$x0);
1411 0         0 my $yp = $y0+$t*($y1-$y0);
1412 0         0 return ($xp, $yp, ($x-$xp)*($x-$xp)+($y-$yp)*($y-$yp));
1413             }
1414              
1415             sub ClosestPoint {
1416 0     0   0 my($self, $x, $y) = @_;
1417 0 0       0 return unless @{$self->{Points}};
  0         0  
1418 0         0 my($i, $pmin, $dmin);
1419 0         0 for my $j (1..$#{$self->{Points}}) {
  0         0  
1420             my($xp, $yp, $d) =
1421             _closest_point($self->{Points}[$j-1]{X}, $self->{Points}[$j-1]{Y},
1422 0         0 $self->{Points}[$j]{X}, $self->{Points}[$j]{Y}, $x, $y);
1423 0 0 0     0 ($i, $pmin, $dmin) = ($j, Geo::OGC::Point->new($xp, $yp), $d)
1424             if (!defined($dmin) or $d < $dmin);
1425             }
1426 0         0 return ($i, $pmin, $dmin)
1427             }
1428              
1429             sub AddVertex {
1430 0     0   0 my($self, $i, $p) = @_;
1431 0         0 splice @{$self->{Points}}, $i, 0, $p;
  0         0  
1432             }
1433              
1434             sub DeleteVertex {
1435 0     0   0 my($self, $i) = @_;
1436 0         0 splice @{$self->{Points}}, $i, 1;
  0         0  
1437             }
1438              
1439             =pod
1440              
1441             =back
1442              
1443             =head2 Geo::OGC::LineString
1444              
1445             =cut
1446              
1447             package Geo::OGC::LineString;
1448              
1449 3     3   20 use strict;
  3         5  
  3         56  
1450 3     3   12 use Carp;
  3         4  
  3         160  
1451 3     3   15 use Geo::OGC::Geometry qw/:all/;
  3         5  
  3         10199  
1452              
1453             our @ISA = qw( Geo::OGC::Curve );
1454              
1455             # de Berg et al p. 25
1456             sub FindIntersections {
1457             # @_ contains a list of linestrings that make up the S
1458 0     0   0 my @linestrings = @_;
1459             #my $precision =
1460              
1461             # event queue
1462 0         0 my @Q = (); # [ymax,index1,index2], ..., index1 is index to @_ index2 is index to Points
1463 0         0 for my $index1 (0 .. $#linestrings) {
1464 0         0 my $s = $linestrings[$index1]->{Points};
1465 0         0 for my $index2 (0 .. $#$s-1) {
1466 0         0 my($y1, $y2) = ( $s->[$index2]{Y}, $s->[$index2+1]{Y} );
1467 0 0       0 if ($y1 > $y2) {
1468 0         0 push @Q, [$y1, $index1, $index2];
1469 0         0 push @Q, [$y2];
1470             } else {
1471 0         0 push @Q, [$y2, $index1, $index2];
1472 0         0 push @Q, [$y1];
1473             }
1474             }
1475             }
1476            
1477             # process event points in descending ymax order
1478 0         0 @Q = sort {$b->[0] <=> $a->[0]} @Q;
  0         0  
1479            
1480 0         0 my $T = Tree::Binary::Search->new(); # the status structure
1481             $T->setComparisonFunction
1482             (sub {
1483 0     0   0 my($a, $b) = @_; # two keys
1484            
1485 0         0 });
1486            
1487 0         0 my $i = 0;
1488 0         0 while ($i < @Q) {
1489 0         0 my $j = $i+1;
1490 0   0     0 while ($j < @Q and sqrt(($Q[$i][0]-$Q[$j][0])**2) < $SNAP_DISTANCE_SQR) {
1491 0         0 $j++;
1492             }
1493             # $i .. $j-1 are the event points in @Q
1494 0         0 for my $k ($i .. $j-1) {
1495 0 0       0 if (@{$Q[$k]} == 3) {
  0         0  
1496 0         0 my $i1 = $Q[$k][1];
1497 0         0 my $i2 = $Q[$k][2];
1498 0         0 my $s = $linestrings[$i1]->{Points};
1499 0         0 $T->insert( "$i1,$i2" => [$s->[$i2]{X}, $s->[$i2]{Y}, $s->[$i2+1]{X}, $s->[$i2+1]{Y}] );
1500             }
1501             }
1502             }
1503             }
1504              
1505             sub new {
1506 23     23   693 my($package, %params) = @_;
1507 23         132 my $self = Geo::OGC::Curve::new($package, %params);
1508 23         46 return $self;
1509             }
1510              
1511             sub GeometryType {
1512 0     0   0 return 'LineString';
1513             }
1514              
1515             sub IsSimple {
1516 13     13   25 my($self) = @_;
1517 13         15 my $edges = @{$self->{Points}} - 1;
  13         26  
1518 13 50       28 return 1 if $edges < 2;
1519 13         27 my $closed = $self->IsClosed;
1520 13         19 my $simple = 1;
1521 13         30 for my $i (0..$edges-1) {
1522             # check a case of self tangency
1523 60 50 66     203 return 0 if $i < $edges-1 and $self->{Points}[$i+2]->Equals($self->{Points}[$i]);
1524 60         133 for my $j ($i+2..$edges-1) {
1525 127 100 100     550 next if $closed and $i == 0 and $j == $edges-1;
      100        
1526             return 0 if intersect
1527             (
1528             $self->{Points}[$i]{X}, $self->{Points}[$i]{Y},
1529             $self->{Points}[$i+1]{X}, $self->{Points}[$i+1]{Y},
1530             $self->{Points}[$j]{X}, $self->{Points}[$j]{Y},
1531             $self->{Points}[$j+1]{X},$self->{Points}[$j+1]{Y}
1532 118 100       423 );
1533             }
1534             }
1535 11         40 return 1;
1536             }
1537              
1538             sub Envelope {
1539 0     0   0 my($self) = @_;
1540 0         0 my($minx, $miny, $maxx, $maxy);
1541 0         0 for my $p (@{$self->{Points}}) {
  0         0  
1542 0 0 0     0 $minx = $p->{X} if !defined($minx) or $minx > $p->{X};
1543 0 0 0     0 $miny = $p->{Y} if !defined($miny) or $miny > $p->{Y};
1544 0 0 0     0 $maxx = $p->{X} if !defined($maxx) or $maxx > $p->{X};
1545 0 0 0     0 $maxy = $p->{Y} if !defined($maxy) or $maxy > $p->{Y};
1546             }
1547 0         0 my $r = Geo::OGC::LinearRing->new;
1548 0         0 $r->AddPoint(Geo::OGC::Point->new($minx, $miny));
1549 0         0 $r->AddPoint(Geo::OGC::Point->new($maxx, $miny));
1550 0         0 $r->AddPoint(Geo::OGC::Point->new($maxx, $maxy));
1551 0         0 $r->AddPoint(Geo::OGC::Point->new($minx, $maxy));
1552 0         0 $r->Close;
1553 0         0 return $r;
1554             }
1555              
1556             =pod
1557              
1558             =over
1559              
1560             =item Length()
1561              
1562             The length of this LineString in its associated spatial reference.
1563              
1564             Currently computed as a simple euclidean distance.
1565              
1566             =cut
1567              
1568             sub Length {
1569 1     1   2 my($self) = @_;
1570 1         2 my $l = 0;
1571 1         9 my($x0, $y0) = ($self->{Points}[0]{X}, $self->{Points}[0]{Y});
1572 1         2 for my $i (1..$#{$self->{Points}}) {
  1         5  
1573 3         6 my($x1, $y1) = ($self->{Points}[$i]{X}, $self->{Points}[$i]{Y});
1574 3         7 $l += sqrt(($x1 - $x0)**2+($y1 - $y0)**2);
1575 3         6 ($x0, $y0) = ($x1, $y1);
1576             }
1577 1         5 return $l;
1578             }
1579              
1580             sub Distance {
1581 0     0   0 my($self, $geom) = @_;
1582 0 0       0 if ($geom->isa('Geo::OGC::Point')) {
    0          
    0          
    0          
1583 0         0 return $geom->DistanceToLineStringSqr($self);
1584             } elsif ($geom->isa('Geo::OGC::LineString')) {
1585 0         0 my $dist;
1586 0         0 for my $p (@{$self->{Points}}) {
  0         0  
1587 0         0 my $d = $p->DistanceToLineStringSqr($geom);
1588 0 0 0     0 $dist = $d if !(defined $dist) or $d < $dist;
1589             }
1590 0         0 return $dist;
1591             } elsif ($geom->isa('Geo::OGC::Polygon')) {
1592 0         0 my $dist;
1593 0         0 for my $p (@{$self->{Points}}) {
  0         0  
1594 0         0 my $d = $p->Distance($geom);
1595 0 0 0     0 $dist = $d if !(defined $dist) or $d < $dist;
1596             }
1597 0         0 return $dist;
1598             } elsif ($geom->isa('Geo::OGC::GeometryCollection')) {
1599 0         0 my $dist = $self->Distance($geom->{Geometries}[0]);
1600 0         0 for my $g (@{$geom->{Geometries}}[1..$#{$geom->{Geometries}}]) {
  0         0  
  0         0  
1601 0         0 my $d = $self->Distance($g);
1602 0 0       0 $dist = $d if $d < $dist;
1603             }
1604 0         0 return $dist;
1605             } else {
1606 0         0 croak "can't compute distance between a ".ref($geom)." and a line string";
1607             }
1608             }
1609              
1610             sub LinesWhereWithin {
1611 6     6   10 my($self, $point) = @_;
1612 6         11 my($x, $y) = ($point->{X}, $point->{Y});
1613 6         7 my @ret;
1614 6         11 my $p1 = $self->{Points}[0];
1615 6 50       15 return @ret unless $p1;
1616 6         9 my $p2 = $self->{Points}[1];
1617 6 50       12 return @ret unless $p1;
1618             push @ret, 1 if
1619 6 100       16 distance_point_line_sqr($x, $y, $p1->{X}, $p1->{Y}, $p2->{X}, $p2->{Y}) < $SNAP_DISTANCE_SQR;
1620 6         14 for my $i (2..$#{$self->{Points}}) {
  6         17  
1621 0         0 $p1 = $p2;
1622 0         0 $p2 = $self->{Points}[$i];
1623             push @ret, $i if
1624 0 0       0 distance_point_line_sqr($x, $y, $p1->{X}, $p1->{Y}, $p2->{X}, $p2->{Y}) < $SNAP_DISTANCE_SQR;
1625             }
1626 6         16 return @ret;
1627             }
1628              
1629             sub Within {
1630 27     27   39 my($self, $geom) = @_;
1631 27 100       116 if ($geom->isa('Geo::OGC::Point')) {
    50          
    0          
1632 22         31 for my $p (@{$self->{Points}}) {
  22         49  
1633 31 100       69 return 0 unless $p->Equals($geom);
1634             }
1635 0         0 return 1;
1636             } elsif ($geom->isa('Geo::OGC::LineString')) {
1637 5         9 my @w1 = ();
1638 5         6 for my $p (@{$self->{Points}}) {
  5         11  
1639 6         19 my @w2 = $geom->LinesWhereWithin($p);
1640 6 100       24 return 0 unless @w2;
1641 2 50       7 next unless @w1;
1642 0         0 my $overlap = 0;
1643 0         0 for my $w1 (@w1) {
1644 0         0 for my $w2 (@w2) {
1645 0 0       0 $overlap = 1 if $w1 == $w2;
1646             }
1647             }
1648 0 0       0 return 0 unless $overlap;
1649 0         0 @w1 = @w2;
1650             }
1651 1         4 return 1;
1652             } elsif ($geom->isa('Geo::OGC::Polygon')) {
1653 0         0 for my $p (@{$self->{Points}}) {
  0         0  
1654 0 0       0 return 0 if $geom->{ExteriorRing}->IsPointStricktlyOut($p);
1655 0         0 for my $ring (@{$geom->{InteriorRings}}) {
  0         0  
1656 0 0       0 return 0 if $ring->IsPointStricktlyIn($p);
1657             }
1658             }
1659 0         0 my $i = $self->Intersection($geom->{ExteriorRing});
1660 0         0 for my $g (@{$i->{Geometries}}) {
  0         0  
1661 0 0       0 next unless $g->isa('Geo::OGC::Line');
1662             # does the line go out of the polygon?
1663             # yes, if its start and end points are on different lines
1664 0         0 my @s = $geom->{ExteriorRing}->LinesWhereWithin($g->StartPoint);
1665 0         0 my @e = $geom->{ExteriorRing}->LinesWhereWithin($g->EndPoint);
1666 0         0 my $overlap = 0;
1667 0         0 for my $s (@s) {
1668 0         0 for my $e (@e) {
1669 0 0       0 $overlap = 1 if $s == $e;
1670             }
1671             }
1672 0 0       0 return 0 unless $overlap;
1673             }
1674 0         0 for my $ring (@{$geom->{InteriorRings}}) {
  0         0  
1675 0         0 my $i = $self->Intersection($ring);
1676 0         0 for my $g (@{$i->{Geometries}}) {
  0         0  
1677 0 0       0 next unless $g->isa('Geo::OGC::Line');
1678             # does the line go into the interior ring?
1679             # yes, if its start and end points are on different lines
1680 0         0 my @s = $ring->LinesWhereWithin($g->StartPoint);
1681 0         0 my @e = $ring->LinesWhereWithin($g->EndPoint);
1682 0         0 my $overlap = 0;
1683 0         0 for my $s (@s) {
1684 0         0 for my $e (@e) {
1685 0 0       0 $overlap = 1 if $s == $e;
1686             }
1687             }
1688 0 0       0 return 0 unless $overlap;
1689             }
1690             }
1691 0         0 return 1;
1692             } else {
1693 0         0 croak "linestring within ".ref($geom)." is not yet implemented";
1694             }
1695             }
1696              
1697             # assuming simple lines
1698             # z and m coords!
1699             sub Intersection {
1700 5     5   8 my($self, $geom) = @_;
1701 5 50       39 if ($geom->isa('Geo::OGC::Point')) {
    50          
1702 0 0       0 return $geom->Clone if $geom->DistanceToLineStringSqr($self) < $SNAP_DISTANCE_SQR;
1703             } elsif ($geom->isa('Geo::OGC::LineString')) {
1704             #my $i = Geo::OGC::GeometryCollection->new;
1705 5         7 my %i;
1706 5         5 my $index = 1;
1707 5         6 my $p1;
1708 5         5 for my $p2 (@{$self->{Points}}) {
  5         11  
1709 22 100       47 $p1 = $p2, next unless $p1;
1710 17         21 my $q1;
1711 17         19 for my $q2 (@{$geom->{Points}}) {
  17         34  
1712 147 100       286 $q1 = $q2, next unless $q1;
1713 130         288 my @p = ($p1->{X}, $p1->{Y}, $p2->{X}, $p2->{Y});
1714 130         243 my @q = ($q1->{X}, $q1->{Y}, $q2->{X}, $q2->{Y});
1715             #print STDERR "lines: @p; @q\n";
1716 130 100       236 if (intersect( @p, @q )) {
1717 19         46 my $p1q = distance_point_line_sqr(@p[0..1], @q);
1718 19         43 my $p2q = distance_point_line_sqr(@p[2..3], @q);
1719 19         46 my $q1p = distance_point_line_sqr(@q[0..1], @p);
1720 19         41 my $q2p = distance_point_line_sqr(@q[2..3], @p);
1721             #print STDERR "p1=q $p1q p2=q $p2q q1=p $q1p q2=p $q2p\n";
1722 19 100       43 if ($p1q < $SNAP_DISTANCE_SQR) {
    50          
    0          
    0          
1723 10 100       21 if ($q1p < $SNAP_DISTANCE_SQR) {
1724 5 50       30 if ($p1->Equals($q1)) {
1725 5         12 $i{$index++} = $p1->Clone;
1726             } else {
1727 0         0 $i{$index++} = Geo::OGC::Line->new( points=>[[@p[0..1]],[@q[0..1]]] );
1728             }
1729             }
1730 10 100       27 if ($q2p < $SNAP_DISTANCE_SQR) {
1731 7 100       16 if ($p1->Equals($q2)) {
1732 5         15 $i{$index++} = $p1->Clone;
1733             } else {
1734 2         21 $i{$index++} = Geo::OGC::Line->new( points=>[[@p[0..1]],[@q[2..3]]] );
1735             }
1736             }
1737 10 100       28 if ($p2q < $SNAP_DISTANCE_SQR) {
1738 1         6 $i{$index++} = Geo::OGC::Line->new( points=>[[@p[0..1]],[@p[2..3]]] );
1739             } else {
1740 9         22 $i{$index++} = Geo::OGC::Point->new( @p[0..1] );
1741             }
1742             } elsif ($p2q < $SNAP_DISTANCE_SQR) {
1743 9 100       94 if ($q1p < $SNAP_DISTANCE_SQR) {
1744 5 50       12 if ($p2->Equals($q1)) {
1745 5         10 $i{$index++} = $p2->Clone;
1746             } else {
1747 0         0 $i{$index++} = Geo::OGC::Line->new( points=>[[@p[2..3]],[@q[0..1]]] );
1748             }
1749             }
1750 9 100       28 if ($q2p < $SNAP_DISTANCE_SQR) {
1751 4 50       11 if ($p2->Equals($q2)) {
1752 4         11 $i{$index++} = $p2->Clone;
1753             } else {
1754 0         0 $i{$index++} = Geo::OGC::Line->new( points=>[[@p[2..3]],[@q[2..3]]] );
1755             }
1756             }
1757 9         30 $i{$index++} = Geo::OGC::Point->new( @p[2..3] );
1758             } elsif ($q1p < $SNAP_DISTANCE_SQR) {
1759 0 0       0 if ($q2p < $SNAP_DISTANCE_SQR) {
1760 0         0 $i{$index++} = Geo::OGC::Line->new( points=>[[@q[0..1]],[@q[2..3]]] );
1761             } else {
1762 0         0 $i{$index++} = Geo::OGC::Point->new( @q[0..1] );
1763             }
1764             } elsif ($q2p < $SNAP_DISTANCE_SQR) {
1765 0         0 $i{$index++} = Geo::OGC::Point->new( @q[2..3] );
1766             } else {
1767 0         0 $i{$index++} = Geo::OGC::Point->new( Geo::OGC::Geometry::intersection_point(@p, @q) );
1768             }
1769             }
1770 130         284 $q1 = $q2;
1771             }
1772 17         29 $p1 = $p2;
1773             }
1774             #$i->Simplify;
1775            
1776             # delete unnecessary points and lines
1777             # comparisons are done unnecessarily twice??
1778 5         16 for my $g1 (keys %i) {
1779 40         97 for my $g2 (keys %i) {
1780 101 100       246 next if $g1 == $g2;
1781 70 100       229 if ($i{$g1}->Within($i{$g2})) {
1782             #print STDERR "delete ",$i{$g1}->AsText," because it is in ",$i{$g2}->AsText,"\n";
1783 35         84 delete $i{$g1};
1784 35         83 last;
1785             }
1786             }
1787             }
1788              
1789 5         22 my $i = Geo::OGC::GeometryCollection->new;
1790 5         9 for my $g1 (keys %i) {
1791 5         15 $i->AddGeometry($i{$g1});
1792             }
1793 5         18 return $i;
1794             } else {
1795 0         0 croak "intersection between a ".ref($geom)." and a line string is not yet implemented";
1796             }
1797             }
1798              
1799             sub MakeCollection {
1800 0     0   0 my($self) = @_;
1801 0         0 my $coll = Geo::OGC::MultiLineString->new;
1802 0         0 $coll->AddGeometry($self);
1803 0         0 return $coll;
1804             }
1805              
1806             # from http://everything2.com/index.pl?node_id=859282
1807             sub pt_to_seg_dist {
1808             # distance of p to segment p1-p2, v12 is the vector p1p2
1809 0     0   0 my ($p1, $v12, $p) = @_;
1810              
1811 0         0 my $m12 = $v12->[0] * $v12->[0] + $v12->[1] * $v12->[1];
1812 0         0 my $v1p = [];
1813 0         0 $v1p->[0] = $p->{X} - $p1->{X};
1814 0         0 $v1p->[1] = $p->{Y} - $p1->{Y};
1815 0         0 my $dot = $v1p->[0] * $v12->[0] + $v1p->[1] * $v12->[1];
1816 0 0       0 if ($dot <= 0.0)
1817             {
1818 0         0 return sqrt ($v1p->[0] * $v1p->[0] + $v1p->[1] * $v1p->[1]);
1819             }
1820             else
1821             {
1822 0 0       0 if ($dot >= $m12)
1823             {
1824 0         0 $v1p->[0] = $v1p->[0] + $v12->[0];
1825 0         0 $v1p->[1] = $v1p->[1] + $v12->[1];
1826 0         0 return sqrt ($v1p->[0] * $v1p->[0] + $v1p->[1] * $v1p->[1]);
1827             }
1828             else
1829             {
1830 0         0 my $slash = $v1p->[0] * $v12->[1] - $v1p->[1] * $v12->[0];
1831 0         0 return abs ($slash / sqrt ($m12));
1832             }
1833             }
1834             }
1835              
1836             sub simplify_part {
1837 0     0   0 my($self, $first, $last, $simple, $tolerance) = @_;
1838 0 0       0 if ($last > $first + 1)
1839             {
1840 0         0 my $p1 = $self->{Points}[$first];
1841             my $vfl = [$self->{Points}[$last]{X} - $self->{Points}[$first]{X},
1842 0         0 $self->{Points}[$last]{Y} - $self->{Points}[$first]{Y}];
1843             # find the intermediate point
1844             # furthest from the segment
1845             # connecting first and last
1846 0         0 my $b = $first+1;
1847 0         0 my $db = pt_to_seg_dist ($p1, $vfl, $self->{Points}[$b]);
1848 0         0 my $i = $b + 1;
1849 0         0 while ($i < $last)
1850             {
1851 0         0 my $di = pt_to_seg_dist ($p1, $vfl, $self->{Points}[$i]);
1852 0 0       0 if ($di > $db)
1853             {
1854 0         0 $b = $i;
1855 0         0 $db = $di;
1856             }
1857 0         0 $i++;
1858             }
1859             # if the furthest distance beats the tolerance,
1860             # recursively simplify the rest of the array.
1861 0 0       0 if ($db >= $tolerance)
1862             {
1863 0         0 simplify_part ($self, $first, $b, $simple, $tolerance);
1864 0         0 $simple->AddPoint($self->{Points}[$b]);
1865 0         0 simplify_part ($self, $b, $last, $simple, $tolerance);
1866             }
1867             }
1868             }
1869              
1870             # Simplifies the linestring using Douglas-Peucker
1871             sub simplify {
1872 0     0   0 my($self, $tolerance) = @_;
1873 0         0 my $simple = Geo::OGC::LineString->new;
1874 0         0 $simple->AddPoint($self->StartPoint);
1875 0         0 simplify_part ($self, 0, $self->NumPoints-1, $simple, $tolerance);
1876 0         0 $simple->AddPoint($self->EndPoint);
1877 0         0 return $simple;
1878             }
1879              
1880             =pod
1881              
1882             =back
1883              
1884             =head2 Geo::OGC::Line
1885              
1886             =cut
1887              
1888             package Geo::OGC::Line;
1889              
1890 3     3   21 use strict;
  3         6  
  3         59  
1891 3     3   17 use Carp;
  3         6  
  3         419  
1892              
1893             our @ISA = qw( Geo::OGC::LineString );
1894              
1895             sub new {
1896 3     3   21 my($package, %params) = @_;
1897 3         10 my $self = Geo::OGC::LineString::new($package, %params);
1898 3         11 return $self;
1899             }
1900              
1901             sub GeometryType {
1902 0     0   0 return 'Line';
1903             }
1904              
1905             =pod
1906              
1907             =head2 Geo::OGC::LinearRing
1908              
1909             =cut
1910              
1911             package Geo::OGC::LinearRing;
1912              
1913 3     3   14 use strict;
  3         6  
  3         52  
1914 3     3   13 use Carp;
  3         6  
  3         144  
1915 3     3   20 use Geo::OGC::Geometry qw/:all/;
  3         6  
  3         2935  
1916              
1917             our @ISA = qw( Geo::OGC::LineString );
1918              
1919             sub new {
1920 16     16   536 my($package, %params) = @_;
1921 16         54 my $self = Geo::OGC::LineString::new($package, %params);
1922 16         28 return $self;
1923             }
1924              
1925             sub GeometryType {
1926 0     0   0 return 'LinearRing';
1927             }
1928              
1929             =pod
1930              
1931             =over
1932              
1933             =item IsPointIn($point)
1934              
1935             Tests whether the given point is within the ring.
1936              
1937             Uses the pnpoly algorithm from L.
1938              
1939             Assumes a simple closed ring. May or may not return true if the point is on the border.
1940              
1941             Not in the specification.
1942              
1943             =cut
1944              
1945             sub IsPointIn {
1946 32     32   40 my($self, $point) = @_;
1947 32         45 my($x, $y) = ($point->{X}, $point->{Y});
1948 32         46 my $c = 0;
1949 32         32 my $prev;
1950 32         34 for my $p (@{$self->{Points}}) {
  32         52  
1951 239 100       400 $prev = $p, next unless $prev;
1952             $c = !$c if (((( $p->{Y} <= $y ) && ( $y < $prev->{Y} )) ||
1953             (( $prev->{Y} <= $y ) && ( $y < $p->{Y} ))) &&
1954             ( $x < ( $prev->{X} - $p->{X} ) *
1955 207 100 66     1178 ( $y - $p->{Y} ) / ( $prev->{Y} - $p->{Y} ) + $p->{X} ));
      100        
1956 207         247 $prev = $p;
1957             }
1958 32         86 return $c;
1959             }
1960              
1961             # @note not on the border
1962             sub IsPointStricktlyIn {
1963 0     0   0 my($self, $point) = @_;
1964 0 0 0     0 return 1 if ( $self->IsPointIn($point) and
1965             !($point->DistanceToLineStringSqr($self) < $SNAP_DISTANCE_SQR) );
1966 0         0 return 0;
1967             }
1968              
1969             # @note not on the border
1970             sub IsPointStricktlyOut {
1971 0     0   0 my($self, $point) = @_;
1972 0 0 0     0 return 1 unless ( $self->IsPointIn($point) or
1973             $point->DistanceToLineStringSqr($self) < $SNAP_DISTANCE_SQR );
1974 0         0 return 0;
1975             }
1976              
1977             =pod
1978              
1979             =item Area()
1980              
1981             Compute the area of the ring. The area is computed as a simple euclidean area.
1982              
1983             Not in the specification.
1984              
1985             Assumes a simple closed ring
1986              
1987             Returns the area as a negative number if the sense of the rotation of the ring is clockwise.
1988              
1989             =cut
1990              
1991             sub Area {
1992 4     4   12 my($self) = @_;
1993 4         8 my $area = 0;
1994 4         6 my $N = $#{$self->{Points}}-1; # skip the closing point
  4         20  
1995 4         6 my $j = 0;
1996 4         11 for my $i (0..$N) {
1997 14         15 $j++;
1998 14         31 $area += $self->{Points}[$i]{X} * $self->{Points}[$j]{Y};
1999 14         35 $area -= $self->{Points}[$i]{Y} * $self->{Points}[$j]{X};
2000             }
2001 4         36 return $area/2;
2002             }
2003              
2004             sub Centroid {
2005 1     1   2 my($self) = @_;
2006 1         2 my($area, $x, $y) = (0, 0, 0);
2007 1         2 my $N = $#{$self->{Points}}-1; # skip the closing point
  1         3  
2008 1         3 for my $i (0..$N) {
2009             my($xi, $yi, $xj, $yj) = ( $self->{Points}[$i]{X}, $self->{Points}[$i]{Y},
2010 4         20 $self->{Points}[$i+1]{X}, $self->{Points}[$i+1]{Y} );
2011 4         7 my $b = $xi * $yj - $xj * $yi;
2012 4         5 $area += $b;
2013 4         5 $x += ($xi + $xj) * $b;
2014 4         6 $y += ($yi + $yj) * $b;
2015             }
2016 1         3 $x /= abs(3*$area); # 6 but $area is 2 * real area
2017 1         2 $y /= abs(3*$area);
2018 1         4 return Geo::OGC::Point->new($x, $y);
2019             }
2020              
2021             # Returns true if the points in this ring are arranged counterclockwise. Assumes a simple closed ring.
2022             sub IsCCW {
2023 0     0   0 my($self) = @_;
2024             # find the northernmost point
2025 0         0 my $t = 0;
2026 0         0 my $N = $#{$self->{Points}}-1; # skip the closing point
  0         0  
2027 0         0 for my $i (1..$N) {
2028 0 0       0 $t = $i if $self->{Points}[$i]{Y} > $self->{Points}[$t]{Y};
2029             }
2030             # the previous point
2031 0         0 my $p = $t-1;
2032 0 0       0 $p = $N if $p < 0;
2033             # the next point
2034 0         0 my $n = $t+1;
2035 0 0       0 $n = 0 if $n > $N;
2036             return ccw($self->{Points}[$p]{X}, $self->{Points}[$p]{Y},
2037             $self->{Points}[$t]{X}, $self->{Points}[$t]{Y},
2038 0         0 $self->{Points}[$n]{X}, $self->{Points}[$n]{Y}) == 1;
2039             }
2040              
2041             # Makes clockwise from counterclockwise and vice versa.
2042             sub Rotate {
2043 0     0   0 my($self) = @_;
2044 0         0 @{$self->{Points}} = reverse @{$self->{Points}};
  0         0  
  0         0  
2045             }
2046              
2047             =pod
2048              
2049             =back
2050              
2051             =head2 Geo::OGC::Surface
2052              
2053             A 2-dimensional geometric object.
2054              
2055             =cut
2056              
2057             package Geo::OGC::Surface;
2058              
2059 3     3   17 use strict;
  3         8  
  3         83  
2060 3     3   14 use Carp;
  3         5  
  3         962  
2061              
2062             our @ISA = qw( Geo::OGC::Geometry );
2063              
2064             sub new {
2065 21     21   37 my($package, %params) = @_;
2066 21         45 my $self = Geo::OGC::Geometry::new($package, %params);
2067 21         38 return $self;
2068             }
2069              
2070             sub GeometryType {
2071 0     0   0 return 'Surface';
2072             }
2073              
2074             sub Dimension {
2075 0     0   0 return 2;
2076             }
2077              
2078             sub Area {
2079 0     0   0 my($self) = @_;
2080 0         0 croak "Area method for class ".ref($self)." is not implemented yet";
2081             }
2082              
2083             sub Centroid {
2084 0     0   0 my($self) = @_;
2085 0         0 croak "Centroid method for class ".ref($self)." is not implemented yet";
2086             }
2087              
2088             sub PointOnSurface {
2089 0     0   0 my($self) = @_;
2090 0         0 croak "PointOnSurface method for class ".ref($self)." is not implemented yet";
2091             }
2092              
2093             sub MakeCollection {
2094 0     0   0 my($self) = @_;
2095 0         0 my $coll = Geo::OGC::MultiSurface->new;
2096 0         0 $coll->AddGeometry($self);
2097 0         0 return $coll;
2098             }
2099              
2100             =pod
2101              
2102             =head2 Geo::OGC::Polygon
2103              
2104             =cut
2105              
2106             package Geo::OGC::Polygon;
2107              
2108 3     3   15 use strict;
  3         6  
  3         77  
2109 3     3   13 use Carp;
  3         5  
  3         14432  
2110              
2111             our @ISA = qw( Geo::OGC::Surface );
2112              
2113             sub new {
2114 19     19   36 my($package, %params) = @_;
2115 19         53 my $self = Geo::OGC::Surface::new($package, %params);
2116 19         38 return $self;
2117             }
2118              
2119             sub init {
2120 21     21   36 my($self, %params) = @_;
2121 21         64 $self->SUPER::init(%params);
2122 21         40 $self->{ExteriorRing} = undef;
2123 21         43 $self->{InteriorRings} = [];
2124             }
2125              
2126             sub copy {
2127 2     2   4 my($self, $clone) = @_;
2128 2         8 $self->SUPER::copy($clone);
2129 2 50       20 $clone->ExteriorRing($self->{ExteriorRing}->Clone) if $self->{ExteriorRing};
2130 2         3 for my $r (@{$self->{InteriorRings}}) {
  2         6  
2131 0         0 $clone->AddInteriorRing($r->Clone);
2132             }
2133             }
2134              
2135             sub IsEmpty {
2136 1     1   2 my($self) = @_;
2137 1 50       6 return 1 unless $self->{ExteriorRing};
2138 1         4 return undef;
2139             }
2140              
2141             sub GeometryType {
2142 2     2   9 return 'Polygon';
2143             }
2144              
2145             # Test the rules that define valid polygons.
2146             sub Assert {
2147 3     3   5 my($self) = @_;
2148              
2149             # a) Polygons are topologically closed;
2150 3 50       4 croak "not at least triangle" unless @{$self->{ExteriorRing}->{Points}} > 3;
  3         9  
2151 3 50       11 croak "exterior not closed" unless $self->{ExteriorRing}->IsClosed;
2152              
2153             # b) The boundary of a Polygon consists of a set of LinearRings
2154             # that make up its exterior and interior boundaries;
2155 3         6 for my $r (@{$self->{InteriorRings}}) {
  3         8  
2156 3 50       7 croak "an interior is not closed" unless $r->IsClosed;
2157             }
2158              
2159             # c) No two Rings in the boundary cross and the Rings in the
2160             # boundary of a Polygon may intersect at a Point but only as a
2161             # tangent
2162 3         5 for my $ring (@{$self->{InteriorRings}}) {
  3         8  
2163 3         4 for my $p (@{$ring->{Points}}) {
  3         6  
2164 14 50       37 croak "point in interior not within exterior" unless $self->{ExteriorRing}->IsPointIn($p);
2165 14         21 for my $r2 (@{$self->{InteriorRings}}) {
  14         26  
2166 23 100       52 next if $ring == $r2;
2167 9 50       18 croak "point in interior is within another interior" if $r2->IsPointIn($p);
2168             }
2169             }
2170             }
2171              
2172             # d) A Polygon may not have cut lines, spikes or punctures
2173 3 50       13 croak "exterior is not simple" unless $self->{ExteriorRing}->IsSimple;
2174 3         6 for my $r (@{$self->{InteriorRings}}) {
  3         7  
2175 3 50       7 croak "an interior is not simple" unless $r->IsSimple;
2176             }
2177              
2178             # e) The interior of every Polygon is a connected point set
2179              
2180             # f) The exterior of a Polygon with 1 or more holes is not
2181             # connected. Each hole defines a connected component of the
2182             # exterior.
2183            
2184 3         4 for my $i (0..$#{$self->{InteriorRings}}) {
  3         10  
2185 3         5 my $r1 = $self->{InteriorRings}[$i];
2186 3         14 my $r2 = $r1->Intersection($self->{ExteriorRing});
2187             croak "an interior intersects too much with the exterior"
2188 3         24 if @{$r2->{Geometries}} and (@{$r2->{Geometries}} > 1 or
2189 3 50 33     4 !$r2->{Geometries}[0]->isa('Geo::OGC::Point'));
      33        
2190 3         6 for my $j ($i+1..$#{$self->{InteriorRings}}) {
  3         22  
2191 1         3 my $r2 = $self->{InteriorRings}[$j];
2192 1         3 $r2 = $r1->Intersection($r2);
2193             croak "an interior intersects too much with another interior"
2194 1         7 if @{$r2->{Geometries}} and (@{$r2->{Geometries}} > 1 or
2195 1 0 0     3 !$r2->{Geometries}[0]->isa('Geo::OGC::Point'));
      33        
2196             }
2197             }
2198            
2199 3         9 return 1;
2200              
2201             }
2202              
2203             sub Is3D {
2204 5     5   8 my($self) = @_;
2205 5 50       14 return 1 if $self->{ExteriorRing}->Is3D;
2206 5         9 for my $r (@{$self->{InteriorRings}}) {
  5         11  
2207 0 0       0 return 1 if $r->Is3D;
2208             }
2209 5         19 return 0;
2210             }
2211              
2212             sub IsMeasured {
2213 5     5   9 my($self) = @_;
2214 5 50       15 return 1 if $self->{ExteriorRing}->IsMeasured;
2215 5         8 for my $r (@{$self->{InteriorRings}}) {
  5         14  
2216 0 0       0 return 1 if $r->IsMeasured;
2217             }
2218 5         16 return 0;
2219             }
2220              
2221             sub AddInteriorRing {
2222 2     2   3 my($self, $ring, $i) = @_;
2223 2 50 33     21 croak 'usage: Polygon->AddInteriorRing($ring[, $i])'
2224             unless $ring and $ring->isa('Geo::OGC::LinearRing');
2225 2         3 my $rings = $self->{InteriorRings};
2226 2 50       6 $i = @$rings unless defined $i;
2227 2 100       6 if (@$rings) {
2228 1         2 my $temp = $rings->[$i-1];
2229 1         5 splice @$rings,$i-1,1,($temp, $ring);
2230             } else {
2231 1         4 push @$rings, $ring;
2232             }
2233             }
2234              
2235             sub ExteriorRing {
2236 15     15   25 my($self, $ring) = @_;
2237 15 50       35 if (defined $ring) {
2238 15 50       126 croak 'usage: Polygon->ExteriorRing($ring)'
2239             unless $ring->isa('Geo::OGC::LinearRing');
2240 15         30 $self->{ExteriorRing} = $ring;
2241             } else {
2242 0         0 return $self->{ExteriorRing};
2243             }
2244             }
2245              
2246             sub Envelope {
2247 0     0   0 my($self) = @_;
2248 0         0 return $self->{ExteriorRing}->Envelope;
2249             }
2250              
2251             =pod
2252              
2253             =over
2254              
2255             =item NumInteriorRing()
2256              
2257             Return the number of interior rings in the polygon.
2258              
2259             =cut
2260              
2261             sub NumInteriorRing {
2262 0     0   0 my($self) = @_;
2263 0         0 scalar(@{$self->{InteriorRings}});
  0         0  
2264             }
2265              
2266             sub InteriorRingN {
2267 0     0   0 my($self, $N, $ring) = @_;
2268 0         0 my $rings = $self->{InteriorRings};
2269 0 0       0 $rings->[$N-1] = $ring if defined $ring;
2270 0 0       0 return $rings->[$N-1] if @$rings;
2271             }
2272              
2273             # @note Assumes the order of the interior rings is the same.
2274             sub Equals {
2275 0     0   0 my($self, $geom) = @_;
2276 0 0       0 return 0 unless $geom->isa('Geo::OGC::Polygon');
2277 0 0       0 return 0 unless @{$self->{InteriorRings}} == @{$geom->{InteriorRings}};
  0         0  
  0         0  
2278 0 0       0 return 0 unless $self->{ExteriorRing}->Equals($geom->{ExteriorRing});
2279 0         0 for my $i (0..$#{$self->{InteriorRings}}) {
  0         0  
2280 0 0       0 return 0 unless $self->{InteriorRings}[$i]->Equals($geom->{InteriorRings}[$i]);
2281             }
2282 0         0 return 1;
2283             }
2284              
2285             sub Area {
2286 0     0   0 my($self) = @_;
2287 0         0 my $a = $self->{ExteriorRing}->Area;
2288 0         0 for my $ring (@{$self->{InteriorRings}}) {
  0         0  
2289 0         0 $a -= $ring->Area;
2290             }
2291 0         0 return $a;
2292             }
2293              
2294             sub IsPointIn {
2295 9     9   31 my($self, $point) = @_;
2296 9         21 my $c = $self->{ExteriorRing}->IsPointIn($point);
2297 9 100       19 if ($c) {
2298 1         2 for my $ring (@{$self->{InteriorRings}}) {
  1         3  
2299 0 0       0 $c = 0, last if $ring->IsPointIn($point);
2300             }
2301             }
2302 9         33 return $c;
2303             }
2304              
2305             sub Distance {
2306 0     0   0 my($self, $geom) = @_;
2307 0 0       0 if ($geom->isa('Geo::OGC::Point')) {
    0          
    0          
    0          
2308 0         0 return $geom->Distance($self);
2309             } elsif ($geom->isa('Geo::OGC::LineString')) {
2310 0         0 return $geom->Distance($self);
2311             } elsif ($geom->isa('Geo::OGC::Polygon')) {
2312 0         0 my $dist;
2313 0         0 for my $p (@{$self->{ExteriorRing}->{Points}}) {
  0         0  
2314 0 0       0 if ($geom->{ExteriorRing}->IsPointIn($p)) {
2315 0         0 my $c = 1;
2316 0         0 for my $ring (@{$self->{InteriorRings}}) {
  0         0  
2317 0 0       0 if ($ring->IsPointIn($p)) {
2318 0         0 my $d = $p->DistanceToLineStringSqr($ring);
2319 0 0 0     0 $dist = $d if !(defined $dist) or $d < $dist;
2320 0         0 $c = 0;
2321             }
2322             }
2323 0 0       0 return 0 if $c;
2324             } else {
2325 0         0 my $d = $p->DistanceToLineStringSqr($geom->{ExteriorRing});
2326 0 0 0     0 $dist = $d if !(defined $dist) or $d < $dist;
2327             }
2328             }
2329 0         0 return $dist;
2330             } elsif ($geom->isa('Geo::OGC::GeometryCollection')) {
2331 0         0 my $dist = $self->Distance($geom->{Geometries}[0]);
2332 0         0 for my $g (@{$geom->{Geometries}}[1..$#{$geom->{Geometries}}]) {
  0         0  
  0         0  
2333 0         0 my $d = $self->Distance($g);
2334 0 0       0 $dist = $d if $d < $dist;
2335             }
2336 0         0 return $dist;
2337             } else {
2338 0         0 croak "can't compute distance between a ".ref($geom)." and a polygon";
2339             }
2340             }
2341              
2342             sub Within {
2343 0     0   0 my($self, $geom) = @_;
2344 0 0       0 if ($geom->isa('Geo::OGC::Point')) {
    0          
    0          
2345 0         0 for my $p (@{$self->{ExteriorRing}->{Points}}) {
  0         0  
2346 0 0       0 return 0 unless $p->Equals($geom);
2347             }
2348 0         0 return 1;
2349             } elsif ($geom->isa('Geo::OGC::LineString')) {
2350 0         0 for my $p (@{$self->{ExteriorRing}->{Points}}) {
  0         0  
2351 0 0       0 return 0 unless $p->Within($geom);
2352             }
2353 0         0 return 1;
2354             } elsif ($geom->isa('Geo::OGC::Polygon')) {
2355 0         0 croak "polygon within ".ref($geom)." is not yet implemented";
2356             # the exterior and interior rings must be completely within
2357             # and the other's interiors must not be within ...
2358 0         0 for my $p (@{$self->{ExteriorRing}->{Points}}) {
  0         0  
2359 0 0       0 return 0 unless $p->Within($geom);
2360             }
2361 0         0 return 1;
2362             } else {
2363 0         0 croak "polygon within ".ref($geom)." is not yet implemented";
2364             }
2365             }
2366              
2367             sub Intersection {
2368 0     0   0 my($self, $geom) = @_;
2369 0 0       0 if ($geom->isa('Geo::OGC::Point')) {
    0          
    0          
2370 0         0 return $geom->Intersection($self);
2371             } elsif ($geom->isa('Geo::OGC::LineString')) {
2372 0         0 return $geom->Intersection($self);
2373             } elsif ($geom->isa('Geo::OGC::Polygon')) {
2374 0         0 croak "polygon within ".ref($geom)." is not yet implemented";
2375              
2376             # 1st intersection between outer rings $A (this) and $B (other)
2377 0         0 my $A = $self->{ExteriorRing};
2378 0         0 my $B = $geom->{ExteriorRing};
2379 0         0 my $p1 = $A->{Points}[0];
2380 0         0 my $c = $B->IsPointStricktlyIn($p1);
2381              
2382 0         0 my $i = 0;
2383 0         0 my $j;
2384 0         0 while (1) {
2385 0         0 my $p = $A->{Points}[$i];
2386 0 0       0 $j = $i, last if $B->IsPointStricktlyOut($p);
2387 0         0 $i++;
2388 0 0       0 last if $i == @{$A->{Points}};
  0         0  
2389             }
2390 0 0       0 if (defined $j) { # there is at least one point
2391             }
2392             # the list of
2393 0         0 my @A; # lines on A
2394            
2395             } else {
2396 0         0 croak "polygon within ".ref($geom)." is not yet implemented";
2397             }
2398             }
2399              
2400             sub as_text {
2401 3     3   7 my($self, $force_parens, $include_tag) = @_;
2402 3 50       26 my $text .= $self->{ExteriorRing}->as_text if $self->{ExteriorRing};
2403 3         6 for my $ring (@{$self->{InteriorRings}}) {
  3         9  
2404 0         0 $text .= ', ';
2405 0         0 $text .= $ring->as_text;
2406             }
2407 3 50       11 my $ZM = $self->Is3D ? 'Z' : '';
2408 3 50       10 $ZM .= 'M' if $self->IsMeasured;
2409 3 100       11 $text = uc($self->GeometryType).$ZM.' ('.$text.')' if $include_tag;
2410 3         15 return $text;
2411             }
2412              
2413             sub MakeCollection {
2414 1     1   8003 my($self) = @_;
2415 1         8 my $coll = Geo::OGC::MultiPolygon->new;
2416 1         10 $coll->AddGeometry($self);
2417 1         3 return $coll;
2418             }
2419              
2420             sub ClosestVertex {
2421 0     0   0 my($self, $x, $y) = @_;
2422 0 0       0 return unless $self->{ExteriorRing};
2423 0         0 my($imin, $dmin) = $self->{ExteriorRing}->ClosestVertex($x, $y);
2424 0         0 my $iring = -1;
2425 0         0 my $r = 0;
2426 0         0 for my $ring (@{$self->{InteriorRings}}) {
  0         0  
2427 0         0 my($i, $d) = $ring->ClosestVertex($x, $y);
2428 0 0       0 ($iring, $imin, $dmin) = ($r, $i, $d) if $d < $dmin;
2429 0         0 $r++;
2430             }
2431 0         0 return ($iring, $imin, $dmin);
2432             }
2433              
2434             sub VertexAt {
2435 0     0   0 my($self, $iring, $ivertex) = @_;
2436 0 0       0 return $self->{ExteriorRing}->VertexAt($ivertex) if $iring < 0;
2437 0         0 return $self->{InteriorRings}[$iring]->VertexAt($ivertex);
2438             }
2439              
2440             sub ClosestPoint {
2441 0     0   0 my($self, $x, $y) = @_;
2442 0 0       0 return unless $self->{ExteriorRing};
2443 0         0 my($imin, $pmin, $dmin) = $self->{ExteriorRing}->ClosestPoint($x, $y);
2444 0         0 my $iring = -1;
2445 0         0 my $r = 0;
2446 0         0 for my $ring (@{$self->{InteriorRings}}) {
  0         0  
2447 0         0 my($i, $p, $d) = $ring->ClosestPoint($x, $y);
2448 0 0       0 ($iring, $imin, $pmin, $dmin) = ($r, $i, $p, $d) if $d < $dmin;
2449 0         0 $r++;
2450             }
2451 0         0 return ($iring, $imin, $pmin, $dmin);
2452             }
2453              
2454             sub AddVertex {
2455 0     0   0 my($self, $ring, $i, $p) = @_;
2456 0 0       0 $self->{ExteriorRing}->AddVertex($i, $p), return if $ring < 0;
2457 0         0 $self->{InteriorRings}[$ring]->AddVertex($i, $p);
2458             }
2459              
2460             sub DeleteVertex {
2461 0     0   0 my($self, $ring, $i) = @_;
2462 0 0       0 $self->{ExteriorRing}->DeleteVertex($i), return if $ring < 0;
2463 0         0 $self->{InteriorRings}[$ring]->DeleteVertex($i);
2464             }
2465              
2466             ## @method LastPolygon()
2467             # @brief Returns self
2468             sub LastPolygon {
2469 0     0   0 my($self) = @_;
2470 0         0 return $self;
2471             }
2472              
2473             =pod
2474              
2475             =back
2476              
2477             =head2 Geo::OGC::Triangle
2478              
2479             =cut
2480              
2481             package Geo::OGC::Triangle;
2482              
2483 3     3   21 use strict;
  3         6  
  3         312  
2484 3     3   13 use Carp;
  3         4  
  3         3132  
2485              
2486             our @ISA = qw( Geo::OGC::Polygon );
2487              
2488             sub new {
2489 0     0   0 my($package, %params) = @_;
2490 0         0 my $self = Geo::OGC::Polygon::new($package, %params);
2491 0         0 return $self;
2492             }
2493              
2494             sub GeometryType {
2495 0     0   0 return 'Triangle';
2496             }
2497              
2498             =pod
2499              
2500             =head2 Geo::OGC::PolyhedralSurface
2501              
2502             =cut
2503              
2504             package Geo::OGC::PolyhedralSurface;
2505              
2506 3     3   19 use strict;
  3         3  
  3         804  
2507 3     3   250 use Carp;
  3         45  
  3         7213  
2508              
2509             our @ISA = qw( Geo::OGC::Surface );
2510              
2511             sub new {
2512 2     2   43 my($package, %params) = @_;
2513 2         7 my $self = Geo::OGC::Surface::new($package, %params);
2514 2         5 return $self;
2515             }
2516              
2517             sub init {
2518 2     2   4 my($self, %params) = @_;
2519 2         10 $self->SUPER::init(%params);
2520 2         9 $self->{Patches} = []; # polygon
2521 2 100       7 if ($params{patches}) {
    50          
2522 1         3 for my $p (@{$params{patches}}) {
  1         2  
2523 6         14 $self->AddPatch(Geo::OGC::Polygon->new(points=>$p));
2524             }
2525             } elsif ($params{patchesm}) {
2526 0         0 for my $p (@{$params{patches}}) {
  0         0  
2527 0         0 $self->AddPatch(Geo::OGC::Polygon->new(pointsm=>$p));
2528             }
2529             }
2530             }
2531              
2532             sub copy {
2533 0     0   0 my($self, $clone) = @_;
2534 0         0 $self->SUPER::copy($clone);
2535 0         0 for my $p (@{$self->{Patches}}){
  0         0  
2536 0         0 $clone->AddPatch($p->Clone);
2537             }
2538             }
2539              
2540             sub IsEmpty {
2541 0     0   0 my($self) = @_;
2542 0 0 0     0 return 1 if !$self->{Patches} or @{$self->{Patches}} == 0;
  0         0  
2543 0         0 return undef;
2544             }
2545              
2546             sub GeometryType {
2547 0     0   0 return 'PolyhedralSurface';
2548             }
2549              
2550             sub AddPatch {
2551 12     12   14 my($self, $patch, $i) = @_;
2552 12 50 33     80 croak 'usage: PolyhedralSurface->AddPatch($patch[, $i])'
2553             unless $patch and $patch->isa('Geo::OGC::Polygon');
2554 12         15 my $patches = $self->{Patches};
2555 12 50       25 $i = @$patches unless defined $i;
2556 12 100       20 if (@$patches) {
2557 10         15 my $temp = $patches->[$i-1];
2558 10         33 splice @$patches,$i-1,1,($temp, $patch);
2559             } else {
2560 2         5 push @$patches, $patch;
2561             }
2562             }
2563              
2564             sub NumPatches {
2565 0     0   0 my($self) = @_;
2566 0         0 @{$self->{Patches}};
  0         0  
2567             }
2568              
2569             sub PatchN {
2570 0     0   0 my($self, $N, $patch) = @_;
2571 0         0 my $patches = $self->{Patches};
2572 0 0       0 $patches->[$N-1] = $patch if defined $patch;
2573 0 0       0 return $patches->[$N-1] if @$patches;
2574             }
2575              
2576             sub BoundingPolygons {
2577 0     0   0 my($self, $p) = @_;
2578 0         0 croak "BoundingPolygons method for class ".ref($self)." is not implemented yet";
2579             }
2580              
2581             sub IsClosed {
2582 0     0   0 my($self) = @_;
2583 0         0 croak "IsClosed method for class ".ref($self)." is not implemented yet";
2584             }
2585              
2586             sub IsMeasured {
2587 0     0   0 my($self) = @_;
2588 0         0 for my $p (@{$self->{Patches}}) {
  0         0  
2589 0 0       0 return 1 if $p->IsMeasured;
2590             }
2591 0         0 return 0;
2592             }
2593              
2594             sub as_text {
2595 0     0   0 my($self, $force_parens, $include_tag) = @_;
2596 0         0 my $text = '';
2597 0         0 for my $patch (@{$self->{Patches}}) {
  0         0  
2598 0         0 $text .= ', ';
2599 0         0 $text .= $patch->as_text;
2600             }
2601 0 0       0 my $ZM = $self->Is3D ? 'Z' : '';
2602 0 0       0 $ZM .= 'M' if $self->IsMeasured;
2603 0 0       0 $text = uc($self->GeometryType).$ZM.' ('.$text.')' if $include_tag;
2604 0         0 return $text;
2605             }
2606              
2607             =pod
2608              
2609             =head2 Geo::OGC::TIN
2610              
2611             =cut
2612              
2613             package Geo::OGC::TIN;
2614              
2615 3     3   15 use strict;
  3         6  
  3         61  
2616 3     3   12 use Carp;
  3         10  
  3         379  
2617              
2618             our @ISA = qw( Geo::OGC::PolyhedralSurface );
2619              
2620             sub new {
2621 0     0   0 my($package, %params) = @_;
2622 0         0 my $self = Geo::OGC::Surface::new($package, %params);
2623 0         0 return $self;
2624             }
2625              
2626             sub GeometryType {
2627 0     0   0 return 'TIN';
2628             }
2629              
2630             =pod
2631              
2632             =head2 Geo::OGC::GeometryCollection
2633              
2634             =cut
2635              
2636             package Geo::OGC::GeometryCollection;
2637              
2638 3     3   12 use strict;
  3         3  
  3         48  
2639 3     3   12 use Carp;
  3         25  
  3         5019  
2640              
2641             our @ISA = qw( Geo::OGC::Geometry );
2642              
2643             sub new {
2644 13     13   6357 my($package, %params) = @_;
2645 13         37 my $self = Geo::OGC::Geometry::new($package, %params);
2646 13         66 return $self;
2647             }
2648              
2649             sub init {
2650 15     15   31 my($self, %params) = @_;
2651 15         49 $self->SUPER::init(%params);
2652 15         40 $self->{Geometries} = [];
2653             }
2654              
2655             sub copy {
2656 2     2   4 my($self, $clone) = @_;
2657 2         6 $self->SUPER::copy($clone);
2658 2         3 for my $g (@{$self->{Geometries}}) {
  2         6  
2659 3         11 $clone->AddGeometry($g->Clone);
2660             }
2661             }
2662              
2663             sub IsEmpty {
2664 3     3   5 my($self) = @_;
2665 3 100 66     14 return 1 if !$self->{Geometries} or @{$self->{Geometries}} == 0;
  3         20  
2666 2         8 return undef;
2667             }
2668              
2669             sub GeometryType {
2670 2     2   20 return 'GeometryCollection';
2671             }
2672              
2673             sub Dimension {
2674 0     0   0 my($self) = @_;
2675 0         0 my $dim;
2676 0         0 for my $g (@{$self->{Geometries}}) {
  0         0  
2677 0         0 my $d = $g->Dimension;
2678 0 0 0     0 $dim = $d if !(defined $dim) or $d > $dim;
2679             }
2680 0         0 return $dim;
2681             }
2682              
2683             sub Is3D {
2684 2     2   5 my($self) = @_;
2685 2         4 for my $g (@{$self->{Geometries}}) {
  2         7  
2686 3 50       9 return 1 if $g->Is3D;
2687             }
2688 2         10 return 0;
2689             }
2690              
2691             sub IsMeasured {
2692 2     2   4 my($self) = @_;
2693 2         4 for my $g (@{$self->{Geometries}}) {
  2         5  
2694 3 50       10 return 1 if $g->IsMeasured;
2695             }
2696 2         9 return 0;
2697             }
2698              
2699             sub as_text {
2700 1     1   5 my($self, $force_parens, $include_tag) = @_;
2701 1         3 my $text = join(',', map {$_->as_text(1, 1)} @{$self->{Geometries}});
  2         11  
  1         5  
2702 1 50       7 my $ZM = $self->Is3D ? 'Z' : '';
2703 1 50       6 $ZM .= 'M' if $self->IsMeasured;
2704 1 50       16 $text = uc($self->GeometryType).$ZM.' ('.$text.')' if $include_tag;
2705 1         8 return $text;
2706             }
2707              
2708             sub ElementType {
2709 0     0   0 return 'Geometry';
2710             }
2711              
2712             sub AddGeometry {
2713 26     26   42 my($self, $geometry, $i) = @_;
2714 26 50 33     170 croak 'usage: GeometryCollection->AddGeometry($geometry[, $i])'
2715             unless $geometry and $geometry->isa('Geo::OGC::Geometry');
2716 26         40 my $geometries = $self->{Geometries};
2717 26 50       61 $i = @$geometries unless defined $i;
2718 26 100       63 if (@$geometries) {
2719 13         159 my $temp = $geometries->[$i-1];
2720 13         56 splice @$geometries,$i-1,1,($temp, $geometry);
2721             } else {
2722 13         42 push @$geometries, $geometry;
2723             }
2724             }
2725              
2726             =pod
2727              
2728             =over
2729              
2730             =item NumGeometries()
2731              
2732             Return the number of geometries in this collection.
2733              
2734             =cut
2735              
2736             sub NumGeometries {
2737 0     0   0 my($self) = @_;
2738 0         0 @{$self->{Geometries}};
  0         0  
2739             }
2740              
2741             =pod
2742              
2743             =item GeometryN($n)
2744              
2745             Return the Nth geometry from the collection (the index of the first geometry is 1).
2746              
2747             =cut
2748              
2749             sub GeometryN {
2750 0     0   0 my($self, $N, $geometry) = @_;
2751 0         0 my $geometries = $self->{Geometries};
2752 0 0       0 $geometries->[$N-1] = $geometry if defined $geometry;
2753 0 0       0 return $geometries->[$N-1] if @$geometries;
2754             }
2755              
2756             sub Envelope {
2757 0     0   0 my($self) = @_;
2758 0         0 my($minx, $miny, $maxx, $maxy);
2759 0         0 for my $g (@{$self->{Geometries}}) {
  0         0  
2760 0         0 my $e = $g->Envelope;
2761 0         0 my $min = $e->PointN(1);
2762 0         0 my $max = $e->PointN(3);
2763 0 0 0     0 $minx = $min->{X} if !defined($minx) or $minx > $min->{X};
2764 0 0 0     0 $miny = $min->{Y} if !defined($miny) or $miny > $min->{Y};
2765 0 0 0     0 $maxx = $max->{X} if !defined($maxx) or $maxx > $max->{X};
2766 0 0 0     0 $maxy = $max->{Y} if !defined($maxy) or $maxy > $max->{Y};
2767             }
2768 0         0 my $r = new Geo::OGC::LinearRing;
2769 0         0 $r->AddPoint(Geo::OGC::Point->new($minx, $miny));
2770 0         0 $r->AddPoint(Geo::OGC::Point->new($maxx, $miny));
2771 0         0 $r->AddPoint(Geo::OGC::Point->new($maxx, $maxy));
2772 0         0 $r->AddPoint(Geo::OGC::Point->new($minx, $maxy));
2773 0         0 $r->Close;
2774 0         0 return $r;
2775             }
2776              
2777             # Assumes the order is the same.
2778             sub Equals {
2779 0     0   0 my($self, $geom) = @_;
2780 0 0       0 return 0 unless $geom->isa('Geo::OGC::GeometryCollection');
2781 0 0       0 return 0 unless @{$self->{Geometries}} == @{$geom->{Geometries}};
  0         0  
  0         0  
2782 0         0 for my $i (0..$#{$self->{Geometries}}) {
  0         0  
2783 0 0       0 return 0 unless $self->{Geometries}[$i]->Equals($geom->{Geometries}[$i]);
2784             }
2785 0         0 return 1;
2786             }
2787              
2788             sub Distance {
2789 0     0   0 my($self, $geom) = @_;
2790 0 0       0 if ($geom->isa('Geo::OGC::Point')) {
    0          
    0          
    0          
2791 0         0 return $geom->Distance($self);
2792             } elsif ($geom->isa('Geo::OGC::LineString')) {
2793 0         0 return $geom->Distance($self);
2794             } elsif ($geom->isa('Geo::OGC::Polygon')) {
2795 0         0 return $geom->Distance($self);
2796             } elsif ($geom->isa('Geo::OGC::GeometryCollection')) {
2797 0         0 my $dist = $self->Distance($geom->{Geometries}[0]);
2798 0         0 for my $g (@{$geom->{Geometries}}[1..$#{$geom->{Geometries}}]) {
  0         0  
  0         0  
2799 0         0 my $d = $g->Distance($self);
2800 0 0       0 $dist = $d if $d < $dist;
2801             }
2802 0         0 return $dist;
2803             } else {
2804 0         0 croak "can't compute distance between a ".ref($geom)." and a geometry collection";
2805             }
2806             }
2807              
2808             sub ClosestVertex {
2809 0     0   0 my($self, $x, $y) = @_;
2810 0 0       0 return unless @{$self->{Geometries}};
  0         0  
2811 0         0 my @rmin = $self->{Geometries}[0]->ClosestVertex($x, $y);
2812 0         0 my $imin = 0;
2813 0         0 my $i = 0;
2814 0         0 for my $g (@{$self->{Geometries}}) {
  0         0  
2815 0 0       0 $i++, next if $i == 0;
2816 0         0 my @r = $g->ClosestVertex($x, $y);
2817 0 0       0 if ($r[$#r] < $rmin[$#rmin]) {
2818 0         0 @rmin = @r;
2819 0         0 $imin = $i;
2820             }
2821 0         0 $i++;
2822             }
2823 0         0 return ($imin, @rmin);
2824             }
2825              
2826             sub VertexAt {
2827 0     0   0 my $self = shift;
2828 0         0 my $i = shift;
2829 0         0 return $self->{Geometries}[$i]->VertexAt(@_);
2830             }
2831              
2832             sub ClosestPoint {
2833 0     0   0 my($self, $x, $y) = @_;
2834 0 0       0 return unless @{$self->{Geometries}};
  0         0  
2835 0         0 my @rmin = $self->{Geometries}[0]->ClosestPoint($x, $y);
2836 0         0 my $imin = 0;
2837 0         0 my $i = 0;
2838 0         0 for my $g (@{$self->{Geometries}}) {
  0         0  
2839 0 0       0 $i++, next if $i == 0;
2840 0         0 my @r = $g->ClosestPoint($x, $y);
2841 0 0       0 if ($r[$#r] < $rmin[$#rmin]) {
2842 0         0 @rmin = @r;
2843 0         0 $imin = $i;
2844             }
2845 0         0 $i++;
2846             }
2847 0         0 return ($imin, @rmin);
2848             }
2849              
2850             sub AddVertex {
2851 0     0   0 my $self = shift;
2852 0         0 my $i = shift;
2853 0         0 $self->{Geometries}[$i]->AddVertex(@_);
2854             }
2855              
2856             sub DeleteVertex {
2857 0     0   0 my $self = shift;
2858 0         0 my $i = shift;
2859 0         0 $self->{Geometries}[$i]->DeleteVertex(@_);
2860             }
2861              
2862             sub LastPolygon {
2863 0     0   0 my($self) = @_;
2864 0         0 for (my $i = $#{$self->{Geometries}}; $i >= 0; $i--) {
  0         0  
2865 0         0 my $polygon = $self->{Geometries}[$i]->LastPolygon;
2866 0 0       0 return $polygon if $polygon;
2867             }
2868             }
2869              
2870             =pod
2871              
2872             =back
2873              
2874             =head2 Geo::OGC::MultiSurface
2875              
2876             =cut
2877              
2878             package Geo::OGC::MultiSurface;
2879              
2880 3     3   20 use strict;
  3         5  
  3         56  
2881 3     3   13 use Carp;
  3         4  
  3         754  
2882              
2883             our @ISA = qw( Geo::OGC::GeometryCollection );
2884              
2885             sub new {
2886 3     3   6 my($package, %params) = @_;
2887 3         9 my $self = Geo::OGC::GeometryCollection::new($package, %params);
2888 3         6 return $self;
2889             }
2890              
2891             sub GeometryType {
2892 0     0   0 return 'MultiSurface';
2893             }
2894              
2895             sub ElementType {
2896 0     0   0 return 'Surface';
2897             }
2898              
2899             sub Area {
2900 0     0   0 my($self) = @_;
2901 0         0 croak "Area method for class ".ref($self)." is not implemented yet";
2902             }
2903              
2904             sub Centroid {
2905 0     0   0 my($self) = @_;
2906 0         0 croak "Centroid method for class ".ref($self)." is not implemented yet";
2907             }
2908              
2909             sub PointOnSurface {
2910 0     0   0 my($self) = @_;
2911 0         0 croak "PointOnSurface method for class ".ref($self)." is not implemented yet";
2912             }
2913              
2914             =pod
2915              
2916             =head2 Geo::OGC::MultiCurve
2917              
2918             =cut
2919              
2920             package Geo::OGC::MultiCurve;
2921              
2922 3     3   17 use strict;
  3         5  
  3         64  
2923 3     3   12 use Carp;
  3         3  
  3         513  
2924              
2925             our @ISA = qw( Geo::OGC::GeometryCollection );
2926              
2927             sub new {
2928 0     0   0 my($package, %params) = @_;
2929 0         0 my $self = Geo::OGC::GeometryCollection::new($package, %params);
2930 0         0 return $self;
2931             }
2932              
2933             sub GeometryType {
2934 0     0   0 return 'MultiCurve';
2935             }
2936              
2937             sub ElementType {
2938 0     0   0 return 'Curve';
2939             }
2940              
2941             =pod
2942              
2943             =head2 Geo::OGC::MultiPoint
2944              
2945             =cut
2946              
2947             package Geo::OGC::MultiPoint;
2948              
2949 3     3   16 use strict;
  3         5  
  3         53  
2950 3     3   12 use Carp;
  3         5  
  3         1101  
2951              
2952             our @ISA = qw( Geo::OGC::GeometryCollection );
2953              
2954             sub new {
2955 2     2   11 my($package, %params) = @_;
2956 2         8 my $self = Geo::OGC::GeometryCollection::new($package, %params);
2957 2         4 return $self;
2958             }
2959              
2960             sub init {
2961 2     2   5 my($self, %params) = @_;
2962 2         11 $self->SUPER::init(%params);
2963 2 50       8 if ($params{points}) {
    100          
2964 0         0 for my $p (@{$params{points}}) {
  0         0  
2965 0         0 $self->AddGeometry(Geo::OGC::Point->new(point=>$p));
2966             }
2967             } elsif ($params{pointsm}) {
2968 1         3 for my $p (@{$params{pointsm}}) {
  1         3  
2969 5         11 $self->AddGeometry(Geo::OGC::Point->new(pointm=>$p));
2970             }
2971             }
2972             }
2973              
2974             sub GeometryType {
2975 0     0   0 return 'MultiPoint';
2976             }
2977              
2978             sub as_text {
2979 0     0   0 my($self, $force_parens, $include_tag) = @_;
2980 0         0 my $text = join(',', map {$_->as_text(1)} @{$self->{Geometries}});
  0         0  
  0         0  
2981 0 0       0 my $ZM = $self->Is3D ? 'Z' : '';
2982 0 0       0 $ZM .= 'M' if $self->IsMeasured;
2983 0 0       0 $text = uc($self->GeometryType).$ZM.' ('.$text.')' if $include_tag;
2984 0         0 return $text;
2985             }
2986              
2987             sub ElementType {
2988 0     0   0 return 'Point';
2989             }
2990              
2991             sub Boundary {
2992 0     0   0 my($self) = @_;
2993 0         0 return Geo::OGC::GeometryCollection->new();
2994             }
2995              
2996             =pod
2997              
2998             =head2 Geo::OGC::MultiPolygon
2999              
3000             =cut
3001              
3002             package Geo::OGC::MultiPolygon;
3003              
3004 3     3   12 use strict;
  3         6  
  3         52  
3005 3     3   12 use Carp;
  3         4  
  3         808  
3006              
3007             our @ISA = qw( Geo::OGC::MultiSurface );
3008              
3009             sub new {
3010 3     3   9 my($package, %params) = @_;
3011 3         12 my $self = Geo::OGC::MultiSurface::new($package, %params);
3012 3         9 return $self;
3013             }
3014              
3015             sub GeometryType {
3016 1     1   4 return 'MultiPolygon';
3017             }
3018              
3019             sub as_text {
3020 1     1   3 my($self, $force_parens, $include_tag) = @_;
3021 1         2 my $text = join(',', map {$_->as_text(1)} @{$self->{Geometries}});
  1         3  
  1         2  
3022 1 50       7 my $ZM = $self->Is3D ? 'Z' : '';
3023 1 50       8 $ZM .= 'M' if $self->IsMeasured;
3024 1 50       5 $text = uc($self->GeometryType).$ZM.' ('.$text.')' if $include_tag;
3025 1         11 return $text;
3026             }
3027              
3028             sub ElementType {
3029 0     0     return 'Polygon';
3030             }
3031              
3032             =pod
3033              
3034             =over
3035              
3036             =item LastPolygon()
3037              
3038             Returns last (latest added) polygon or undef.
3039              
3040             =cut
3041              
3042             sub LastPolygon {
3043 0     0     return undef;
3044             }
3045              
3046             =pod
3047              
3048             =back
3049              
3050             =head2 Geo::OGC::MultiLineString
3051              
3052             =cut
3053              
3054             package Geo::OGC::MultiLineString;
3055              
3056 3     3   12 use strict;
  3         5  
  3         55  
3057 3     3   17 use Carp;
  3         8  
  3         771  
3058              
3059             our @ISA = qw( Geo::OGC::MultiCurve );
3060              
3061             sub new {
3062 0     0     my($package, %params) = @_;
3063 0           my $self = Geo::OGC::MultiCurve::new($package, %params);
3064 0           return $self;
3065             }
3066              
3067             sub GeometryType {
3068 0     0     return 'MultiLineString';
3069             }
3070              
3071             sub as_text {
3072 0     0     my($self, $force_parens, $include_tag) = @_;
3073 0           my $text = join(',', map {$_->as_text(1)} @{$self->{Geometries}});
  0            
  0            
3074 0 0         my $ZM = $self->Is3D ? 'Z' : '';
3075 0 0         $ZM .= 'M' if $self->IsMeasured;
3076 0 0         $text = uc($self->GeometryType).$ZM.' ('.$text.')' if $include_tag;
3077 0           return $text;
3078             }
3079              
3080             sub ElementType {
3081 0     0     return 'LineString';
3082             }
3083              
3084             =pod
3085              
3086             =head1 ACKNOWLEDGEMENTS
3087              
3088             The OpenGIS (r) Implementation Standard for Geographic information -
3089             Simple feature access - Part 1: Common architecture was heavily used
3090             in preparation of this module.
3091              
3092             =head1 REPOSITORY
3093              
3094             L
3095              
3096             =head1 AUTHOR
3097              
3098             Ari Jolma L
3099              
3100             =head1 COPYRIGHT AND LICENSE
3101              
3102             Copyright (c) 2008- Ari Jolma
3103              
3104             This program is free software; you can redistribute it and/or modify
3105             it under the same terms as Perl 5 itself.
3106              
3107             =cut
3108              
3109             1;
3110             __END__