Introduction
This is a Perl version of the original C++ algorithm which can be found here.
Background
Please refer to the original C++ algorithm here.
Using the Code
The algorithm is wrapped into a Perl module library folder GeoProc
. The main test program LASProc
reads point cloud data from a LAS file, then writes all inside points into a new LAS file. A LAS file is able to be displayed with a freeware FugroViewer.
To consume the GeoProc
module library, prepare a GeoPolygonProc
object first like this:
my $ptsArray = [$p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8];
my $polygon = new GeoPolygon($ptsArray);
my $procObj = new GeoPolygonProc($polygon);
Here is how to check if a point is inside the polygon:
if ($x > $procObj->{x0} && $x < $procObj->{x1} &&
$y > $procObj->{y0} && $x < $procObj->{y1} &&
$z > $procObj->{z0} && $x < $procObj->{z1} )
{
if ($procObj->PointInside3DPolygon($x, $y, $z))
Here are all GeoProc
modules:
Please refer to PHP version for the module explanation and comments.
GeoPoint.pm
package GeoPoint;
use strict;
use warning;
sub new
{
my $class = shift;
my $self = {
x => shift,
y => shift,
z => shift,
};
bless $self, $class;
return $self;
}
use overload "+" => \&Add, "-" => \&Subtract;
sub Add
{
my $p0 = shift;
my $p1 = shift;
return new GeoPoint($p0->{x} + $p1->{x}, $p0->{y} + $p1->{y}, $p0->{z} + $p1->{z});
}
sub Subtract
{
my $p0 = shift;
my $p1 = shift;
return new GeoPoint($p0->{x} - $p1->{x}, $p0->{y} - $p1->{y}, $p0->{z} - $p1->{z});
}
1;
GeoVector.pm
package GeoVector;
use strict;
use warnings;
sub new
{
my $class = shift;
my $self = {
p0 => shift,
p1 => shift,
};
bless $self, $class;
return $self;
}
sub getX
{
my $self = shift;
return $self->{p1}->{x} - $self->{p0}->{x};
}
sub getY
{
my $self = shift;
return $self->{p1}->{y} - $self->{p0}->{y};
}
sub getZ
{
my $self = shift;
return $self->{p1}->{z} - $self->{p0}->{z};
}
use overload "*" => \&Multiple;
sub Multiple
{
my $u = shift;
my $v = shift;
my $x = $u->getY() * $v->getZ() - $u->getZ() * $v->getY();
my $y = $u->getZ() * $v->getX() - $u->getX() * $v->getZ();
my $z = $u->getX() * $v->getY() - $u->getY() * $v->getX();
my $p0 = $v->{p0};
my $p1 = $p0 + new GeoPoint($x, $y, $z);
return new GeoVector($p0, $p1);
}
1;
GeoPlane.pm
package GeoPlane;
use strict;
use warnings;
use GeoVector;
sub new
{
my $class = shift;
my $self = {
a => shift,
b => shift,
c => shift,
d => shift,
};
bless $self, $class;
return $self;
}
sub Create
{
my $self = shift;
my $p0 = shift;
my $p1 = shift;
my $p2 = shift;
my $v = new GeoVector($p0, $p1);
my $u = new GeoVector($p0, $p2);
my $n = $u * $v;
my $a = $n->getX();
my $b = $n->getY();
my $c = $n->getZ();
my $d = -($a * $p0->{x} + $b * $p0->{y} + $c * $p0->{z});
return new GeoPlane($a, $b, $c, $d);
}
use overload "neg" => \&Negative;
sub Negative
{
my $self = shift;
return new GeoPlane(-$self->{a}, -$self->{b}, -$self->{c}, -$self->{d});
}
sub Multiple
{
my $pl = shift;
my $pt = shift;
return $pt->{x} * $pl->{a} + $pt->{y} * $pl->{b} + $pt->{z} * $pl->{c} + $pl->{d};
}
1;
GeoPolygon.pm
package GeoPolygon;
use strict;
use warnings;
sub new
{
my ($class, $pts) = @_;
my $this = {};
my @pts_ref = @$pts;
$this->{n} = scalar @pts_ref;
$this->{v} = [];
$this->{idx} = [];
for(my $i=0; $i<$this->{n}; $i++)
{
$this->{v}[$i] = $pts_ref[$i];
$this->{idx}[$i] = $i;
}
bless $this, $class;
return $this;
}
1;
GeoFace.pm
package GeoFace;
use strict;
use warnings;
sub new
{
my ($class, $pts, $ptsIdx) = @_;
my $this = {};
my @pts_ref = @$pts;
my @ptsIdx_ref = @$ptsIdx;
$this->{n} = scalar @pts_ref;
$this->{v} = [];
$this->{idx} = [];
for(my $i=0; $i<$this->{n}; $i++)
{
push(@{$this->{v}}, $pts_ref[$i]);
push(@{$this->{idx}}, $ptsIdx_ref[$i]);
}
bless $this, $class;
return $this;
}
1;
Utility.pm
package Utility;
use strict;
use warnings;
use experimental 'smartmatch';
use constant false => 0;
use constant true => 1;
sub ContainsArray
{
my ($this, $arr2d, $arr1d) = @_;
my @arr2d_ref = @$arr2d;
my @arr1d_ref = @$arr1d;
my $count = scalar @arr2d_ref;
my @sortedItem = sort { $a <=> $b } @arr1d_ref;
for (my $i = 0; $i < $count; $i++)
{
my $temp = $arr2d_ref[$i];
my @currItem = sort { $a <=> $b } @$temp;
if(@currItem ~~ \@sortedItem)
{
return true;
}
}
return false;
}
1;
GeoPolygonProc.pm
package GeoPolygonProc;
use strict;
use warnings;
use Data::Dumper;
use constant false => 0;
use constant true => 1;
my $MaxUnitMeasureError = 0.001;
sub new
{
my ($class, $polygon) = @_;
my $this = {};
bless $this, $class;
$this->{polygon} = $polygon;
$this->SetBoundary();
$this->SetMaxError();
$this->SetFacePlanes();
return $this;
}
sub SetBoundary
{
my ($this) = @_;
my $n = $this->{polygon}->{n};
my $v = $this->{polygon}->{v};
$this->{x0} = @$v[0]->{x};
$this->{y0} = @$v[0]->{y};
$this->{z0} = @$v[0]->{z};
$this->{x1} = @$v[0]->{x};
$this->{y1} = @$v[0]->{y};
$this->{z1} = @$v[0]->{z};
for (my $i = 1; $i < $n; $i++)
{
if (@$v[$i]->{x} < $this->{x0}) {$this->{x0} = @$v[$i]->{x};}
if (@$v[$i]->{y} < $this->{y0}) {$this->{y0} = @$v[$i]->{y};}
if (@$v[$i]->{z} < $this->{z0}) {$this->{z0} = @$v[$i]->{z};}
if (@$v[$i]->{x} > $this->{x1}) {$this->{x1} = @$v[$i]->{x};}
if (@$v[$i]->{y} > $this->{y1}) {$this->{y1} = @$v[$i]->{y};}
if (@$v[$i]->{z} > $this->{z1}) {$this->{z1} = @$v[$i]->{z};}
}
}
sub SetMaxError
{
my ($this) = @_;
$this->{maxDisError} = (abs($this->{x0}) + abs($this->{x1}) +
abs($this->{y0}) + abs($this->{y1}) +
abs($this->{z0}) + abs($this->{z1})) / 6 * $MaxUnitMeasureError;
}
sub SetFacePlanes
{
my ($this) = @_;
$this->{Faces} = [];
$this->{FacePlanes} = [];
$this->{NumberOfFaces} = 0;
my $vertices = $this->{polygon}->{v};
my $n = $this->{polygon}->{n};
my @faceVerticeIndex = ();
my $fpOutward = [];
for(my $i=0; $i< $n; $i++)
{
my $p0 = @$vertices[$i];
for(my $j= $i+1; $j< $n; $j++)
{
my $p1 = @$vertices[$j];
for(my $k = $j + 1; $k<$n; $k++)
{
my $p2 = @$vertices[$k];
my $trianglePlane = GeoPlane->Create($p0, $p1, $p2);
my $onLeftCount = 0;
my $onRightCount = 0;
my $pointInSamePlaneIndex = [];
for(my $l = 0; $l < $n ; $l++)
{
if($l != $i && $l != $j && $l != $k)
{
my $pt = @$vertices[$l];
my $dis = $trianglePlane->Multiple($pt);
if(abs($dis) < $this->{maxDisError})
{
push(@$pointInSamePlaneIndex, $l);
}
else
{
if($dis < 0)
{
$onLeftCount++;
}
else
{
$onRightCount++;
}
}
}
}
if($onLeftCount == 0 || $onRightCount == 0)
{
my $verticeIndexInOneFace = [];
push(@$verticeIndexInOneFace, $i);
push(@$verticeIndexInOneFace, $j);
push(@$verticeIndexInOneFace, $k);
my $m = scalar @$pointInSamePlaneIndex;
if($m > 0)
{
for(my $p = 0; $p < $m; $p++)
{
push(@$verticeIndexInOneFace, @$pointInSamePlaneIndex[$p]);
}
}
if (!Utility->ContainsArray(\@faceVerticeIndex, $verticeIndexInOneFace))
{
push(@faceVerticeIndex, $verticeIndexInOneFace);
if ($onRightCount == 0)
{
push(@$fpOutward, $trianglePlane);
}
elsif ($onLeftCount == 0)
{
push(@$fpOutward, -$trianglePlane);
}
}
}
else
{
}
}
}
}
$this->{NumberOfFaces} = scalar @faceVerticeIndex;
for (my $i = 0; $i < $this->{NumberOfFaces}; $i++)
{
my $facePlane = new GeoPlane(@$fpOutward[$i]->{a}, @$fpOutward[$i]->{b},
@$fpOutward[$i]->{c}, @$fpOutward[$i]->{d});
push(@{$this->{FacePlanes}}, $facePlane);
my $v = [];
my $idx = [];
my $faceIdx = $faceVerticeIndex[$i];
my $count = scalar @$faceIdx;
for (my $j = 0; $j < $count; $j++)
{
push(@$idx, @$faceIdx[$j]);
my $k = @$idx[$j];
my $pt = new GeoPoint(@$vertices[$k]->{x}, @$vertices[$k]->{y}, @$vertices[$k]->{z});
push(@$v, $pt);
}
my $face = new GeoFace($v, $idx);
push(@{$this->{Faces}}, $face);
}
}
sub UpdateMaxError
{
my ($this, $maxError) = @_;
$this->{maxDisError} = $maxError;
}
sub PointInside3DPolygon
{
my ($this, $x, $y, $z) = @_;
my $pt = new GeoPoint($x, $y, $z);
for (my $i = 0; $i < $this->{NumberOfFaces}; $i++)
{
my $pl = $this->{FacePlanes}[$i];
my $dis = $pl->Multiple($pt);
if ($dis > 0)
{
return false;
}
}
return true;
}
1;
Here is the main test program.
LASProc.pl
use strict;
use warnings;
use Data::Dumper;
use Fcntl qw(:seek);
use lib '../GeoProc';
use GeoPoint;
use GeoVector;
use GeoPlane;
use GeoPolygon;
use GeoFace;
use Utility;
use GeoPolygonProc;
my $p1 = new GeoPoint( - 27.28046, 37.11775, - 39.03485);
my $p2 = new GeoPoint( - 44.40014, 38.50727, - 28.78860);
my $p3 = new GeoPoint( - 49.63065, 20.24757, - 35.05160);
my $p4 = new GeoPoint( - 32.51096, 18.85805, - 45.29785);
my $p5 = new GeoPoint( - 23.59142, 10.81737, - 29.30445);
my $p6 = new GeoPoint( - 18.36091, 29.07707, - 23.04144);
my $p7 = new GeoPoint( - 35.48060, 30.46659, - 12.79519);
my $p8 = new GeoPoint( - 40.71110, 12.20689, - 19.05819);
my $ptsArray = [$p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8];
my $polygon = new GeoPolygon($ptsArray);
my $procObj = new GeoPolygonProc($polygon);
my $rb;
my $wb;
my $wt;
my $value;
my $rbFileName = "../LASInput/cube.las";
my $wbFileName = "../LASOutput/cube_inside.las";
my $wtFileName = "../LASOutput/cube_inside.txt";
open($rb, "<", $rbFileName)
|| die "can't open $rbFileName: $!";
binmode($rb);
open($wb, ">", $wbFileName)
|| die "can't open $wbFileName: $!";
binmode($wb);
open($wt, ">", $wtFileName)
|| die "can't open $wtFileName: $!";
sysseek($rb, 96, 0);
sysread($rb, $value, 4);
my $offset_to_point_data_value = unpack('I', $value);
sysread($rb, $value, 4);
sysread($rb, $value, 1);
sysread($rb, $value, 2);
my $record_len = unpack('v', $value);
sysread($rb, $value, 4);
my $record_num = unpack('L', $value);
sysseek($rb, 131, 0);
sysread($rb, $value, 8);
my $x_scale = unpack('d', $value);
sysread($rb, $value, 8);
my $y_scale = unpack('d', $value);
sysread($rb, $value, 8);
my $z_scale = unpack('d', $value);
sysread($rb, $value, 8);
my $x_offset = unpack('d', $value);
sysread($rb, $value, 8);
my $y_offset = unpack('d', $value);
sysread($rb, $value, 8);
my $z_offset = unpack('d', $value);
sysseek($rb, 0, 0);
sysread($rb, $value, $offset_to_point_data_value);
syswrite($wb, $value);
my $insideCount = 0;
my ($i, $record_loc, $xi, $yi, $zi, $x, $y, $z);
for($i=0; $i<$record_num; $i++)
{
$record_loc = $offset_to_point_data_value + $record_len * $i;
sysseek($rb, $record_loc, 0);
sysread($rb, $value, 4);
$xi = unpack('l', $value);
sysread($rb, $value, 4);
$yi = unpack('l', $value);
sysread($rb, $value, 4);
$zi = unpack('l', $value);
$x = $xi * $x_scale + $x_offset;
$y = $yi * $y_scale + $y_offset;
$z = $zi * $z_scale + $z_offset;
if ($x > $procObj->{x0} && $x < $procObj->{x1} &&
$y > $procObj->{y0} && $x < $procObj->{y1} &&
$z > $procObj->{z0} && $x < $procObj->{z1} )
{
if ($procObj->PointInside3DPolygon($x, $y, $z))
{
sysseek($rb, $record_loc, 0);
sysread($rb, $value, $record_len);
syswrite($wb, $value);
$wt->printf("%15.6f %15.6f %15.6f\n", $x, $y, $z);
$insideCount++;
}
}
}
sysseek($wb, 107, 0);
syswrite($wb, pack('I', $insideCount));
$rb->close();
$wb->close();
$wt->close();
Here is a module test program for all GeoProc module library functions and usages.
Test.pl
use strict;
use warnings;
use Data::Dumper;
use lib '../GeoProc';
use GeoPoint;
use GeoVector;
use GeoPlane;
use GeoPolygon;
use GeoFace;
use Utility;
use GeoPolygonProc;
my $p1 = new GeoPoint( - 27.28046, 37.11775, - 39.03485);
my $p2 = new GeoPoint( - 44.40014, 38.50727, - 28.78860);
my $p3 = new GeoPoint( - 49.63065, 20.24757, - 35.05160);
my $p4 = new GeoPoint( - 32.51096, 18.85805, - 45.29785);
my $p5 = new GeoPoint( - 23.59142, 10.81737, - 29.30445);
my $p6 = new GeoPoint( - 18.36091, 29.07707, - 23.04144);
my $p7 = new GeoPoint( - 35.48060, 30.46659, - 12.79519);
my $p8 = new GeoPoint( - 40.71110, 12.20689, - 19.05819);
my $ptsArray = [$p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8];
my $polygon = new GeoPolygon($ptsArray);
print "GeoPoint Test:\n";
my $pt = $p1 + $p2;
print(Dumper($pt));
print $pt->{x} .",". $pt->{y} .",". $pt->{z} ."\n";
print "GeoVector Test:\n";
my $v1 = new GeoVector($p1, $p2);
my $v2 = new GeoVector($p1, $p3);
my $v3 = $v2 * $v1;
printf("%.3f, %.3f, %.3f\n", $v3->getX(), $v3->getY(), $v3->getZ());
print "GeoPlane Test:\n";
my $pl = GeoPlane->Create($p1, $p2, $p3);
printf("%.3f, %.3f, %.3f, %.3f\n", $pl->{a}, $pl->{b}, $pl->{c}, $pl->{d});
$pl = new GeoPlane(1.0, 2.0, 3.0, 4.0);
printf("%.3f, %.3f, %.3f, %.3f\n", $pl->{a}, $pl->{b}, $pl->{c}, $pl->{d});
$pl = -$pl;
printf("%.3f, %.3f, %.3f, %.3f\n", $pl->{a}, $pl->{b}, $pl->{c}, $pl->{d});
my $dis = $pl->Multiple(new GeoPoint(1.0, 2.0, 3.0));
printf("%.3f\n", $dis);
print "GeoPolygon Test:\n";
for(my $i=0; $i<$polygon->{n}; $i++)
{
print $polygon->{idx}[$i] .": (". $polygon->{v}[$i]->{x} .", ".
$polygon->{v}[$i]->{y} .", ". $polygon->{v}[$i]->{z} .")\n";
}
print "GeoFace Test:\n";
my $faceVerticesArray = [$p1, $p2, $p3, $p4];
my $faceVerticeIndexArray = [1, 2, 3, 4];
my $face = new GeoFace($faceVerticesArray, $faceVerticeIndexArray);
for(my $i=0; $i<$face->{n}; $i++)
{
print $face->{idx}[$i] .": (". $face->{v}[$i]->{x} .", ".
$face->{v}[$i]->{y} .", ". $face->{v}[$i]->{z} .")\n";
}
print "Utility Test:\n";
my $arr1 = [1, 2, 3, 4];
my $arr2 = [4, 5, 6, 7];
my $arr2d = [];
push(@$arr2d, $arr1);
push(@$arr2d, $arr2);
my $arr3 = [2, 3, 1, 4];
my $arr4 = [2, 3, 1, 5];
my $b1 = Utility->ContainsArray($arr2d, $arr3);
my $b2 = Utility->ContainsArray($arr2d, $arr4);
print $b1 .", ". $b2 ."\n";
print "GeoPolygonProc Test:\n";
my $procObj = new GeoPolygonProc($polygon);
print $procObj->{x0} .", ". $procObj->{x1} .", ". $procObj->{y0} .", ".
$procObj->{y1} .", ". $procObj->{z0} .", ". $procObj->{z1} ."\n";
printf("%.6f\n", $procObj->{maxDisError});
$procObj->UpdateMaxError(0.0125);
printf("%.6f\n", $procObj->{maxDisError});
print $procObj->{NumberOfFaces}."\n";
print "Face Planes:\n";
for(my $i=0; $i<$procObj->{NumberOfFaces}; $i++)
{
my $facePlane = $procObj->{FacePlanes}[$i];
printf("%d%s\n", $i+1, ": ".$facePlane->{a}.", ".$facePlane->{b}.", ".
$facePlane->{c}.", ".$facePlane->{d});
}
print "Faces:\n";
for(my $j=0; $j<$procObj->{NumberOfFaces}; $j++)
{
printf("%s%d:\n", "Face ", $j + 1);
my $face = $procObj->{Faces}[$j];
for(my $i=0; $i<$face->{n}; $i++)
{
printf("%d%s\n", $face->{idx}[$i], ": (". $face->{v}[$i]->{x} .", ".
$face->{v}[$i]->{y} .", ". $face->{v}[$i]->{z} .")");
}
}
my $insidePoint = new GeoPoint(-28.411750, 25.794500, -37.969000);
my $outsidePoint = new GeoPoint(-28.411750, 25.794500, -50.969000);
$b1 = $procObj->PointInside3DPolygon($insidePoint->{x}, $insidePoint->{y}, $insidePoint->{z});
$b2 = $procObj->PointInside3DPolygon($outsidePoint->{x}, $outsidePoint->{y}, $outsidePoint->{z});
print $b1.", ".$b2;
Here is the output of the module test program:
GeoPoint Test:
$VAR1 = bless( {
'x' => '-71.6806',
'z' => '-67.82345',
'y' => '75.62502'
}, 'GeoPoint' );
-71.6806,75.62502,-67.82345
GeoVector Test:
-178.391, 160.814, -319.868
GeoPlane Test:
-178.391, 160.814, -319.868, -23321.631
1.000, 2.000, 3.000, 4.000
-1.000, -2.000, -3.000, -4.000
-18.000
GeoPolygon Test:
0: (-27.28046, 37.11775, -39.03485)
1: (-44.40014, 38.50727, -28.7886)
2: (-49.63065, 20.24757, -35.0516)
3: (-32.51096, 18.85805, -45.29785)
4: (-23.59142, 10.81737, -29.30445)
5: (-18.36091, 29.07707, -23.04144)
6: (-35.4806, 30.46659, -12.79519)
7: (-40.7111, 12.20689, -19.05819)
GeoFace Test:
1: (-27.28046, 37.11775, -39.03485)
2: (-44.40014, 38.50727, -28.7886)
3: (-49.63065, 20.24757, -35.0516)
4: (-32.51096, 18.85805, -45.29785)
Utility Test:
1, 0
GeoPolygonProc Test:
-49.63065, -18.36091, 10.81737, 38.50727, -45.29785, -12.79519
0.029235
0.012500
6
Face Planes:
1: -178.390887365, 160.8136689275, -319.8681191512, -23321.6310778083
2: 104.6099805132, 365.1940004963, 125.2599754664, -5811.8668695958
3: 342.39346482, -27.7903996799999, -204.924901278, 2372.9555463542
4: -342.393647417, 27.7906119191, 204.9249816848, -10372.9631493284
5: -104.609966618, -365.193886771, -125.2600697684, -2188.13771525579
6: 178.3908734698, -160.8139027544, 319.8683017482, 15321.6421625963
Faces:
Face 1:
0: (-27.28046, 37.11775, -39.03485)
1: (-44.40014, 38.50727, -28.7886)
2: (-49.63065, 20.24757, -35.0516)
3: (-32.51096, 18.85805, -45.29785)
Face 2:
0: (-27.28046, 37.11775, -39.03485)
1: (-44.40014, 38.50727, -28.7886)
5: (-18.36091, 29.07707, -23.04144)
6: (-35.4806, 30.46659, -12.79519)
Face 3:
0: (-27.28046, 37.11775, -39.03485)
3: (-32.51096, 18.85805, -45.29785)
4: (-23.59142, 10.81737, -29.30445)
5: (-18.36091, 29.07707, -23.04144)
Face 4:
1: (-44.40014, 38.50727, -28.7886)
2: (-49.63065, 20.24757, -35.0516)
6: (-35.4806, 30.46659, -12.79519)
7: (-40.7111, 12.20689, -19.05819)
Face 5:
2: (-49.63065, 20.24757, -35.0516)
3: (-32.51096, 18.85805, -45.29785)
4: (-23.59142, 10.81737, -29.30445)
7: (-40.7111, 12.20689, -19.05819)
Face 6:
4: (-23.59142, 10.81737, -29.30445)
5: (-18.36091, 29.07707, -23.04144)
6: (-35.4806, 30.46659, -12.79519)
7: (-40.7111, 12.20689, -19.05819)
1, 0
Points of Interest
TMTOWTDI means "There's more than one way to do it" according to Wall.
History
- February 06, 2016: Initial date