File Coverage

blib/lib/Geo/OGC/Geometry.pm
Criterion Covered Total %
statement 701 1449 48.3
branch 203 572 35.4
condition 34 120 28.3
subroutine 122 265 46.0
pod 0 43 0.0
total 1060 2449 43.2


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