Data-SpatialHash-Shared
view release on metacpan or search on metacpan
lib/Data/SpatialHash/Shared.pm view on Meta::CPAN
=head3 Region query cost
The cost of a region query scales with the number of grid cells covering
the query region, not with the number of matching points. For
C<query_radius>, C<each_in_radius>, and each C<query_radius_many> sub-query that
is roughly S<(2 * radius / cell_size) ** dims> cells (similarly for C<query_aabb>,
which scans the cells spanning the box). The scan runs while holding a
read lock. An over-large radius relative to C<cell_size> therefore scans
many empty cells, wasting time and stalling concurrent writers that are
waiting for the lock. Size C<cell_size> on the order of your typical
query radius so each query touches only a handful of cells.
As a safety net, any region query that would scan more than approximately
67 million cells (2**26) -- a C<query_radius>, C<query_aabb>,
C<each_in_radius>, or a C<query_radius_many> sub-query whose region spans that
many cells, a C<query_knn> that must walk that many cells across its expanding
shells, or a C<each_pair_within> / C<each_colliding_pair> whose per-entry
neighbourhood spans that many cells -- croaks with
a message containing the word "cells" rather than scanning unbounded. If
your use case genuinely requires regions that large, increase C<cell_size>
so the same physical region maps to fewer cells.
=head2 Spherical worlds
For points on or above a sphere (planets, globes), construct with a body radius
and use the geo helpers; a separate cube-sphere scheme gives stable hierarchical
cell ids for chunking and level-of-detail. Curvature needs no special handling:
with C<sphere> set, geo coordinates are converted to and from Cartesian in C, so
proximity is exact straight-line distance -- correct for surface and air entities
alike -- not a great-circle approximation.
=head3 Geo proximity
my $s = Data::SpatialHash::Shared->new(undef, $max, 0, $cell, sphere => $R);
my $h = $s->insert_geo($lat, $lon, $alt, $value); # radians; alt above the surface
$s->move_geo($h, $lat, $lon, $alt);
my ($lat, $lon, $alt) = $s->position_geo($h);
my @vals = $s->query_geo_radius($lat, $lon, $alt, $dist); # $dist in world units
C<sphere> is the body radius -- distinct from a per-entry interaction radius --
and must be finite and greater than zero; it is stored in the map and restored
on reopen. C<sphere> and C<wrap> are mutually exclusive (a sphere is not a flat
torus); passing both croaks. Latitude and longitude are in B<radians> (C<lat>
in S<-pi/2 .. pi/2>, C<lon> in S<-pi .. pi>); C<alt> is height above the sphere
of radius C<$R>, so an entity lies at distance C<$R + $alt> from the centre.
Each geo method converts to Cartesian and delegates to the ordinary 3D engine,
so C<$dist> in C<query_geo_radius> is a true straight-line distance and must be
finite and non-negative. At a pole, longitude is undefined and C<position_geo> reports it
as 0. Calling a geo method on a map created without C<sphere> croaks.
C<insert_geo> returns a handle or C<undef> if the pool is exhausted (test with
C<defined> -- handle 0 is valid but false); C<move_geo> returns true, or false
for a freed/invalid handle; C<position_geo> croaks on a freed or invalid handle.
=head3 Cube-sphere cells
A direction (or lat/lon) maps to a hierarchical cell id on a cube-sphere: six
cube faces, each an equal-angle grid subdivided to a chosen B<level> (level 0 =
the whole face, level C<L> = C<2**L> cells per face edge, up to level 24). The
ids are stateless integers independent of any stored data -- useful as chunk keys
for streaming and level-of-detail.
my $cell = $s->cube_cell($x, $y, $z, $level); # direction (need not be unit length)
my $cell = $s->cube_cell_geo($lat, $lon, $level);
my @adj = $s->cube_neighbors($cell); # 4 edge-adjacent cells, seam-aware
my $up = $s->cube_parent($cell); # coarser cell (undef at level 0)
my @kids = $s->cube_children($cell); # 4 finer cells (empty at level 24)
my $lvl = $s->cube_level($cell);
my ($x, $y, $z) = $s->cube_center($cell); # cell centre as a unit vector
my ($lat, $lon) = $s->cube_center_geo($cell);
A cell id packs C<(level, face, i, j)> into an unsigned integer, so cells at
different levels are different ids. C<cube_neighbors> returns the four
edge-adjacent cells, correct across face seams; diagonal/corner neighbours are
not included. These methods read no map state (any handle provides them), and a
malformed cell id croaks; a zero or non-finite direction yields an arbitrary but
valid cell. The grid is near-uniform (equal-angle), not equal-area.
=head2 Introspection
$s->count; # live entry count
$s->max_entries; # capacity
$s->num_buckets; # bucket table size
$s->cell_size; # cell size in world units
my @w = $s->world; # wrap extents: (Wx,Wy) or (Wx,Wy,Wz); empty if not toroidal
my $R = $s->sphere; # body radius (sphere => $R), or 0 if not a sphere map
$s->stats; # diagnostic hashref (see STATS)
=head2 Lifecycle
$s->path; # backing file path, or undef for anon/memfd
$s->memfd; # memfd fd (-1 for file-backed/anon)
$s->sync; # msync mmap to backing store
$s->unlink; # remove backing file
Class->unlink($path); # class-method form
C<sync> and C<unlink> croak on OS failure.
=head2 Event Loop Integration
my $fd = $s->eventfd; # lazy-create eventfd, returns fd
$s->eventfd_set($fd); # attach an external eventfd
my $fd = $s->fileno; # current eventfd fd, or -1
$s->notify; # write 1 to eventfd (signal update)
my $n = $s->eventfd_consume; # read+reset eventfd counter
C<eventfd_set> attaches an external eventfd, closing the previously-attached fd
(such as one created by C<eventfd>) unless it is the same descriptor.
C<notify> returns false if no eventfd is attached, true after writing the
signal. C<eventfd_consume> returns the counter as an integer, or C<undef> if
no eventfd is attached or nothing is pending (a spurious wakeup).
=head1 TUNING
Choose C<cell_size> close to your typical query radius. A value too
small means many cells are scanned per radius query; a value too large
packs many entries into each cell and increases false-positive tests.
Choose C<num_buckets> to be a power of two slightly above the expected
peak live count; any value you pass is rounded up to the next power of
two. The default (0 = auto) picks a power of two near C<max_entries>,
( run in 1.152 second using v1.01-cache-2.11-cpan-9581c071862 )