DouglasPeucker.pm 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package Slic3r::Geometry::DouglasPeucker;
  2. use strict;
  3. use warnings;
  4. BEGIN {
  5. use Exporter ;
  6. use vars qw ( $VERSION @ISA @EXPORT) ;
  7. $VERSION = 1.0 ;
  8. @ISA = qw ( Exporter ) ;
  9. @EXPORT = qw (
  10. Douglas_Peucker
  11. perp_distance
  12. haversine_distance_meters
  13. angle3points
  14. ) ;
  15. }
  16. # Call as: @Opoints = &Douglas_Peucker( <reference to input array of points>, <tolerance>) ;
  17. # Returns: Array of points
  18. # Points Array Format:
  19. # ([lat1,lng1],[lat2,lng2],...[latn,lngn])
  20. #
  21. sub Douglas_Peucker
  22. {
  23. my $href = shift ;
  24. my $tolerance = shift ;
  25. my @Ipoints = @$href ;
  26. my @Opoints = ( ) ;
  27. my @stack = ( ) ;
  28. my $fIndex = 0 ;
  29. my $fPoint = '' ;
  30. my $aIndex = 0 ;
  31. my $anchor = '' ;
  32. my $max = 0 ;
  33. my $maxIndex = 0 ;
  34. my $point = '' ;
  35. my $dist = 0 ;
  36. my $polygon = 0 ; # Line Type
  37. $anchor = $Ipoints[0] ; # save first point
  38. push( @Opoints, $anchor ) ;
  39. $aIndex = 0 ; # Anchor Index
  40. # Check for a polygon: At least 4 points and the first point == last point...
  41. if ( $#Ipoints >= 4 and $Ipoints[0] == $Ipoints[$#Ipoints] )
  42. {
  43. $fIndex = $#Ipoints - 1 ; # Start from the next to last point
  44. $polygon = 1 ; # It's a polygon
  45. } else
  46. {
  47. $fIndex = $#Ipoints ; # It's a path (open polygon)
  48. }
  49. push( @stack, $fIndex ) ;
  50. # Douglas - Peucker algorithm...
  51. while(@stack)
  52. {
  53. $fIndex = $stack[$#stack] ;
  54. $fPoint = $Ipoints[$fIndex] ;
  55. $max = $tolerance ; # comparison values
  56. $maxIndex = 0 ;
  57. # Process middle points...
  58. for (($aIndex+1) .. ($fIndex-1))
  59. {
  60. $point = $Ipoints[$_] ;
  61. $dist = &perp_distance($anchor, $fPoint, $point);
  62. if( $dist >= $max )
  63. {
  64. $max = $dist ;
  65. $maxIndex = $_;
  66. }
  67. }
  68. if( $maxIndex > 0 )
  69. {
  70. push( @stack, $maxIndex ) ;
  71. } else
  72. {
  73. push( @Opoints, $fPoint ) ;
  74. $anchor = $Ipoints[(pop @stack)] ;
  75. $aIndex = $fIndex ;
  76. }
  77. }
  78. if ( $polygon ) # Check for Polygon
  79. {
  80. push( @Opoints, $Ipoints[$#Ipoints] ) ; # Add the last point
  81. # Check for collapsed polygons, use original data in that case...
  82. if( $#Opoints < 4 )
  83. {
  84. @Opoints = @Ipoints ;
  85. }
  86. }
  87. return ( @Opoints ) ;
  88. }
  89. # Calculate Perpendicular Distance in meters between a line (two points) and a point...
  90. # my $dist = &perp_distance( <line point 1>, <line point 2>, <point> ) ;
  91. sub perp_distance # Perpendicular distance in meters
  92. {
  93. my $lp1 = shift ;
  94. my $lp2 = shift ;
  95. my $p = shift ;
  96. my $dist = &haversine_distance_meters( $lp1, $p ) ;
  97. my $angle = &angle3points( $lp1, $lp2, $p ) ;
  98. return ( sprintf("%0.6f", abs($dist * sin($angle)) ) ) ;
  99. }
  100. # Calculate Distance in meters between two points...
  101. sub haversine_distance_meters
  102. {
  103. my $p1 = shift ;
  104. my $p2 = shift ;
  105. my $O = 3.141592654/180 ;
  106. my $b = $$p1[0] * $O ;
  107. my $c = $$p2[0] * $O ;
  108. my $d = $b - $c ;
  109. my $e = ($$p1[1] * $O) - ($$p2[1] * $O) ;
  110. my $f = 2 * &asin( sqrt( (sin($d/2) ** 2) + cos($b) * cos($c) * (sin($e/2) ** 2)));
  111. return sprintf("%0.4f",$f * 6378137) ; # Return meters
  112. sub asin
  113. {
  114. atan2($_[0], sqrt(1 - $_[0] * $_[0])) ;
  115. }
  116. }
  117. # Calculate Angle in Radians between three points...
  118. sub angle3points # Angle between three points in radians
  119. {
  120. my $p1 = shift ;
  121. my $p2 = shift ;
  122. my $p3 = shift ;
  123. my $m1 = &slope( $p2, $p1 ) ;
  124. my $m2 = &slope( $p3, $p1 ) ;
  125. return ($m2 - $m1) ;
  126. sub slope # Slope in radians
  127. {
  128. my $p1 = shift ;
  129. my $p2 = shift ;
  130. return( sprintf("%0.6f",atan2( (@$p2[1] - @$p1[1]),( @$p2[0] - @$p1[0] ))) ) ;
  131. }
  132. }
  133. 1;