File Coverage

blib/lib/Data/Douglas_Peucker.pm
Criterion Covered Total %
statement 12 82 14.6
branch 0 10 0.0
condition 0 3 0.0
subroutine 4 10 40.0
pod 0 6 0.0
total 16 111 14.4


line stmt bran cond sub pod time code
1             #
2             #
3             # Douglas - Peucker algorithm
4             # Author. John D. Coryat 01/2007 USNaviguide.com
5             # http://www.usnaviguide.com/douglas-peucker.htm
6             # Maintainer: Mike Flannigan (temp4@mflan.com)
7             #
8             #
9             # Douglas-Peucker polyline simplification algorithm. First draws single line
10             # from start to end. Then finds largest deviation from this straight line, and if
11             # greater than tolerance, includes that point, splitting the original line into
12             # two new lines. Repeats recursively for each new line created.
13             #
14             #
15             package Data::Douglas_Peucker;
16             require 5.003;
17 1     1   53111 use strict;
  1         2  
  1         28  
18              
19             BEGIN {
20 1     1   4 use Exporter;
  1         2  
  1         30  
21 1     1   4 use vars qw ( $VERSION @ISA @EXPORT);
  1         1  
  1         74  
22 1     1   3 $VERSION = 0.01;
23 1         18 @ISA = qw ( Exporter );
24 1         480 @EXPORT = qw (
25             &Douglas_Peucker
26             &perp_distance
27             &haversine_distance_meters
28             &angle3points
29             );
30             }
31              
32             #
33             # Call as: @Opoints = &Douglas_Peucker( , );
34             # Returns: Array of points
35             # Points Array Format:
36             # ([lat1,lng1],[lat2,lng2],...[latn,lngn])
37             #
38              
39             sub Douglas_Peucker {
40 0     0 0   my $href = shift;
41 0           my $tolerance = shift;
42 0           my @Ipoints = @$href;
43 0           my @Opoints = ( );
44 0           my @stack = ( );
45 0           my $fIndex = 0;
46 0           my $fPoint = '';
47 0           my $aIndex = 0;
48 0           my $anchor = '';
49 0           my $max = 0;
50 0           my $maxIndex = 0;
51 0           my $point = '';
52 0           my $dist = 0;
53 0           my $polygon = 0; # Line Type
54            
55 0           $anchor = $Ipoints[0]; # save first point
56            
57 0           push( @Opoints, $anchor );
58            
59 0           $aIndex = 0; # Anchor Index
60            
61             # Check for a polygon: At least 4 points and the first point == last point...
62            
63 0 0 0       if ( $#Ipoints >= 4 and $Ipoints[0] == $Ipoints[$#Ipoints] ) {
64 0           $fIndex = $#Ipoints - 1; # Start from the next to last point
65 0           $polygon = 1; # It's a polygon
66             }
67             else {
68 0           $fIndex = $#Ipoints; # It's a path (open polygon)
69             }
70            
71 0           push( @stack, $fIndex );
72            
73             # Douglas - Peucker algorithm...
74            
75 0           while(@stack) {
76 0           $fIndex = $stack[$#stack];
77 0           $fPoint = $Ipoints[$fIndex];
78 0           $max = $tolerance; # comparison values
79 0           $maxIndex = 0;
80            
81             # Process middle points...
82            
83 0           for (($aIndex+1) .. ($fIndex-1)) {
84 0           $point = $Ipoints[$_];
85 0           $dist = &perp_distance ($anchor, $fPoint, $point);
86            
87 0 0         if( $dist >= $max ) {
88 0           $max = $dist;
89 0           $maxIndex = $_;
90             }
91             }
92            
93 0 0         if( $maxIndex > 0 ) {
94 0           push( @stack, $maxIndex );
95             }
96             else {
97 0           push( @Opoints, $fPoint );
98 0           $anchor = $Ipoints[(pop @stack)];
99 0           $aIndex = $fIndex;
100             }
101             }
102            
103 0 0         if ( $polygon ) { # Check for Polygon
104 0           push( @Opoints, $Ipoints[$#Ipoints] ); # Add the last point
105            
106             # Check for collapsed polygons, use original data in that case...
107            
108 0 0         if( $#Opoints < 4 ) {
109 0           @Opoints = @Ipoints;
110             }
111             }
112            
113 0           return ( @Opoints );
114             }
115              
116             # Calculate Perpendicular Distance in meters between a line (two points) and a point...
117             # my $dist = &perp_distance( , , );
118              
119             sub perp_distance { # Perpendicular distance in meters
120              
121 0     0 0   my $lp1 = shift;
122 0           my $lp2 = shift;
123 0           my $p = shift;
124 0           my $dist = &haversine_distance_meters( $lp1, $p );
125 0           my $angle = &angle3points( $lp1, $lp2, $p );
126              
127 0           return ( sprintf("%0.6f", abs($dist * sin($angle)) ) );
128             }
129              
130             # Calculate Distance in meters between two points...
131              
132 0           sub haversine_distance_meters {
133 0     0 0   my $p1 = shift;
134 0           my $p2 = shift;
135              
136 0           my $O = 3.141592654/180;
137 0           my $b = $$p1[0] * $O;
138 0           my $c = $$p2[0] * $O;
139 0           my $d = $b - $c;
140 0           my $e = ($$p1[1] * $O) - ($$p2[1] * $O);
141 0           my $f = 2 * &asin( sqrt( (sin($d/2) ** 2) + cos($b) * cos($c) * (sin($e/2) ** 2)));
142              
143 0           return sprintf("%0.4f",$f * 6378137); # Return meters
144              
145             sub asin {
146 0     0 0   atan2($_[0], sqrt(1 - $_[0] * $_[0]));
147             }
148             }
149              
150             # Calculate Angle in Radians between three points...
151              
152 0           sub angle3points { # Angle between three points in radians
153              
154 0     0 0   my $p1 = shift;
155 0           my $p2 = shift;
156 0           my $p3 = shift;
157 0           my $m1 = &slope( $p2, $p1 );
158 0           my $m2 = &slope( $p3, $p1 );
159            
160 0           return ($m2 - $m1);
161              
162             sub slope { # Slope in radians
163              
164 0     0 0   my $p1 = shift;
165 0           my $p2 = shift;
166 0           return( sprintf("%0.6f",atan2( (@$p2[1] - @$p1[1]),( @$p2[0] - @$p1[0] ))) );
167             }
168             }
169              
170             1;
171              
172             __END__