Data-SpatialHash-Shared

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

   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
    "query_radius", "each_in_radius", and each "query_radius_many" sub-query
    that is roughly (2 * radius / cell_size) ** dims cells (similarly for
    "query_aabb", which scans the cells spanning the box). The scan runs
    while holding a read lock. An over-large radius relative to "cell_size"
    therefore scans many empty cells, wasting time and stalling concurrent
    writers that are waiting for the lock. Size "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 "query_radius",
    "query_aabb", "each_in_radius", or a "query_radius_many" sub-query whose
    region spans that many cells, a "query_knn" that must walk that many
    cells across its expanding shells, or a "each_pair_within" /
    "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 "cell_size" so the same physical region maps to fewer
    cells.

  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 "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.

   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

    "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. "sphere" and "wrap" are mutually exclusive
    (a sphere is not a flat torus); passing both croaks. Latitude and
    longitude are in radians ("lat" in -pi/2 .. pi/2, "lon" in -pi .. pi);
    "alt" is height above the sphere of radius $R, so an entity lies at
    distance "$R + $alt" from the centre. Each geo method converts to
    Cartesian and delegates to the ordinary 3D engine, so $dist in
    "query_geo_radius" is a true straight-line distance and must be finite
    and non-negative. At a pole, longitude is undefined and "position_geo"
    reports it as 0. Calling a geo method on a map created without "sphere"
    croaks. "insert_geo" returns a handle or "undef" if the pool is
    exhausted (test with "defined" -- handle 0 is valid but false);
    "move_geo" returns true, or false for a freed/invalid handle;
    "position_geo" croaks on a freed or invalid handle.

   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 level (level 0 = the whole face, level "L" = "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 "(level, face, i, j)" into an unsigned integer, so cells
    at different levels are different ids. "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.

  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)

  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

    "sync" and "unlink" croak on OS failure.

  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

    "eventfd_set" attaches an external eventfd, closing the
    previously-attached fd (such as one created by "eventfd") unless it is
    the same descriptor. "notify" returns false if no eventfd is attached,
    true after writing the signal. "eventfd_consume" returns the counter as
    an integer, or "undef" if no eventfd is attached or nothing is pending
    (a spurious wakeup).

TUNING
    Choose "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 "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 "max_entries",
    which is a safe starting point. After loading real data, inspect



( run in 0.498 second using v1.01-cache-2.11-cpan-9581c071862 )