Skip to content

Commit

Permalink
get_polygon_center variants
Browse files Browse the repository at this point in the history
A new variant "..._forceinside" is available in VectorUtil.pm
which was already used in the main bbbike script.

A variant "..._medialaxis" is prepared, but currently nowhere
used and still in evaluation.

get_polygon_center_best will choose the best implementation
depending on what is installed.

Adapted and extended related tests.
  • Loading branch information
eserte committed Nov 3, 2024
1 parent 4e2f2fd commit 464f6b0
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 23 deletions.
20 changes: 5 additions & 15 deletions bbbike
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ use Route;
## DEBUG_END
use Karte;
use Hooks;
use VectorUtil qw(get_polygon_center point_in_polygon point_in_grid offset_line);
use VectorUtil qw(get_polygon_center_best point_in_polygon point_in_grid offset_line);
## DEBUG_BEGIN
#BEGIN{mymstat("before locale");}
## DEBUG_END
Expand Down Expand Up @@ -6630,7 +6630,7 @@ EOF
# speciality for sights: draw a star
if (!defined $mx) {
if (@coordlist > 2) {
($mx,$my) = get_polygon_center(@coordlist);
($mx,$my) = get_polygon_center_best(@coordlist);
}
if (!defined $mx) {
($mx,$my) = @coordlist[0,1];
Expand Down Expand Up @@ -6672,19 +6672,9 @@ EOF
# $name .= " $add";
#}
$name =~ s/\cK/\n/g; # vert tab -> newline
($mx,$my) = get_polygon_center(@coordlist);
if (!defined $mx || ! do {
my @zipped_coordlist;
for(my $i = 0; $i < $#coordlist; $i+=2) {
push @zipped_coordlist, [$coordlist[$i], $coordlist[$i+1]];
}
point_in_polygon([$mx,$my], \@zipped_coordlist);
}) {
my $middle = int $#coordlist/2;
if ($middle%2 != 0) {
$middle--;
}
($mx,$my) = @coordlist[$middle,$middle+1];
($mx,$my) = get_polygon_center_best(@coordlist);
if (!defined $mx) { # can this happen?
($mx,$my) = @coordlist[0,1];
}

my $abk_fg = $abk;
Expand Down
114 changes: 111 additions & 3 deletions lib/VectorUtil.pm
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# Author: Slaven Rezic
#
# Copyright (C) 1999,2001,2004,2008,2010,2011,2014,2016,2017,2019 Slaven Rezic. All rights reserved.
# Copyright (C) 1999,2001,2004,2008,2010,2011,2014,2016,2017,2019,2024 Slaven Rezic. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
Expand All @@ -15,17 +15,19 @@ package VectorUtil;

use strict;
use vars qw($VERSION $VERBOSE @ISA @EXPORT_OK);
$VERSION = 1.27;
$VERSION = 1.28;

require Exporter;
@ISA = 'Exporter';

@EXPORT_OK = qw(vector_in_grid project_point_on_line distance_point_line
distance_point_rectangle get_polygon_center
distance_point_rectangle
get_polygon_center get_polygon_center_best
point_in_grid point_in_polygon move_point_orthogonal
intersect_rectangles enclosed_rectangle normalize_rectangle
azimuth offset_line bbox_of_polygon combine_bboxes
triangle_area triangle_area_by_lengths
flatten_polygon xy_polygon
);

sub pi () { 4 * atan2(1, 1) } # 3.141592653
Expand Down Expand Up @@ -240,7 +242,12 @@ sub distance_point_rectangle {
$dist;
}

######################################################################

# See https://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt#Polygon
# Calculates the centroid of the given polygon (flat coordinates)
# However, the result may be *outside* of the polygon. See
# get_polygon_center_forceinside and get_polygon_center_medialaxis for alternatives.
sub get_polygon_center {
my(@coords) = @_;

Expand Down Expand Up @@ -269,6 +276,91 @@ sub get_polygon_center {
}
}

# Get the centroid of the given polygon (flat coordinates).
# If the centroid is outside of the polygon then fallback
# to returning the middle point of the polygon outline.
# See get_polygon_center_medialaxis for a better alternative.
sub get_polygon_center_forceinside {
my(@coords) = @_;

my($xs,$ys) = get_polygon_center(@coords);
my @xy_polygon = xy_polygon(@coords);
return ($xs,$ys) if point_in_polygon([$xs,$ys], \@xy_polygon);

my $middle = int $#xy_polygon/2;
return ($xy_polygon[$middle][0],$xy_polygon[$middle][1]);
}

# Calculate a polygon center by using a medial axis.
# Needs the non-standard perl module Math::Geometry::Voronoi.
sub get_polygon_center_medialaxis {
my(@coords) = @_;

require Math::Geometry::Voronoi;

# make closed polygon:
if ($coords[0] != $coords[-2] || $coords[1] != $coords[-1]) {
push @coords, @coords[0,1];
}

my @polygon = xy_polygon(@coords);

my $voronoi = Math::Geometry::Voronoi->new(points=>\@polygon);
$voronoi->compute;

my $midpoint = sub {
my ($point1, $point2) = @_;
return [(($point1->[0] + $point2->[0]) / 2), (($point1->[1] + $point2->[1]) / 2)];
};

my @medial_axis;
# Extract Voronoi edges within the polygon and construct the medial axis.
foreach my $edge (@{$voronoi->edges}) {
my $index1 = $edge->[1];
my $index2 = $edge->[2];

my $start = ($index1 != -1) ? $voronoi->vertices->[$index1] : undef;
my $end = ($index2 != -1) ? $voronoi->vertices->[$index2] : undef;

if ($start && $end) {
# Check if both endpoints are within the polygon.
if (
point_in_polygon($start, \@polygon) &&
point_in_polygon($end, \@polygon)
) {
my $midpoint = $midpoint->($start, $end);
push @medial_axis, $midpoint;
}
}
}

my $middle = $medial_axis[$#medial_axis/2];
if (!$middle) {
warn "WARNING: cannot find medial axis, fallback to $coords[0],$coords[1]...\n";
@coords[0,1];
} else {
@$middle;
}
}

# Use the best available implementation for calculating the
# polygon center.
{
my $IMPLEMENTATION;
sub get_polygon_center_best {
if (!defined $IMPLEMENTATION) {
if (0 && eval { require Math::Geometry::Voronoi; 1 }) { # XXX not activated yet!
$IMPLEMENTATION = \&get_polygon_center_medialaxis;
} else {
$IMPLEMENTATION = \&get_polygon_center_forceinside;
}
}
goto $IMPLEMENTATION;
}
}

######################################################################

# only for parallel rectangles, does not check for enclosed rectangles
sub intersect_rectangles {
my($ax,$ay,$bx,$by, $cx,$cy,$dx,$dy) = @_;
Expand Down Expand Up @@ -586,6 +678,22 @@ sub triangle_area_by_lengths {
sqrt($s * ($s-$a) * ($s-$b) * ($s-$c));
}

# flatten a polygon consisting of ([$x0,$y0],...) into ($x0,$y0,....)
sub flatten_polygon {
my(@xy) = @_;
map { @$_ } @xy;
}

# make a flat polygon consisting of ($x0,$y0,...) into ([$x0,$y0],...)
sub xy_polygon {
my(@coords) = @_;
my @xy;
for(my $i=0; $i<$#coords; $i+=2) {
push @xy, [$coords[$i], $coords[$i+1]];
}
@xy;
}

# Protect from floating point inaccuracies
sub _pos_sqrt { $_[0] < 0 ? 0 : sqrt($_[0]) }

Expand Down
6 changes: 3 additions & 3 deletions t/vector-polygon-center-datatest.t
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use Time::HiRes qw(time);
use Test::More 'no_plan';

use Strassen::Core;
use VectorUtil qw(get_polygon_center bbox_of_polygon);
use VectorUtil qw(get_polygon_center_best bbox_of_polygon);

my @test_files = qw(flaechen wasserstrassen wasserumland wasserumland2 sehenswuerdigkeit); # files which usually have F:... records

Expand All @@ -32,7 +32,7 @@ for my $test_file (@test_files) {
if ($r->[Strassen::CAT] =~ m{^F:}) {
my @coords = map { split /,/, $_ } @{ $r->[Strassen::COORDS] };
my $t0 = time;
my($cx,$cy) = get_polygon_center(@coords);
my($cx,$cy) = get_polygon_center_best(@coords);
$sumtime += (time - $t0); $count++;
my $common_testname = "for $test_file, $r->[Strassen::NAME]";
ok $cx, "x center $common_testname ok";
Expand All @@ -43,7 +43,7 @@ for my $test_file (@test_files) {
});
}

diag sprintf "Average speed of get_polygon_center() call: %.2fms", 1000*($sumtime/$count);
diag sprintf "Average speed of get_polygon_center_best() call: %.2fms", 1000*($sumtime/$count);

sub get_bbox {
my($coords_ref) = @_;
Expand Down
16 changes: 14 additions & 2 deletions t/vectorutil.t
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use Getopt::Long;
use BBBikeTest qw(is_float);
use Strassen::Util qw();

plan tests => 32;
plan tests => 35;

my $do_bench;
my $do_xxx;
Expand All @@ -38,7 +38,9 @@ GetOptions(
use VectorUtil qw(intersect_rectangles normalize_rectangle
enclosed_rectangle bbox_of_polygon combine_bboxes
distance_point_line distance_point_rectangle project_point_on_line
offset_line triangle_area triangle_area_by_lengths);
offset_line triangle_area triangle_area_by_lengths
flatten_polygon xy_polygon
);

goto XXX if $do_xxx;

Expand Down Expand Up @@ -234,4 +236,14 @@ sub test_offset_line {
}
}

{
my @coords = (1,2, 3,4);
my @xy = xy_polygon(@coords);
is_deeply \@xy, [[1,2],[3,4]], 'result of xy_polygon';
my @coords2 = flatten_polygon(@xy);
is_deeply \@coords2, \@coords, 'result of flatten_polygon';

is_deeply [xy_polygon()], [], 'xy_polygon on empty list';
}

__END__

0 comments on commit 464f6b0

Please sign in to comment.