Skip to content

a_star_search maps out-of-bounds points to interior pixels and truncates instead of rounding in _get_pixel_id #3629

Description

@brendancol

Describe the bug

_get_pixel_id() in xrspatial/pathfinding.py converts a (y, x) point to a pixel index with

py = int(abs(point[0] - y_coords[0]) / cellsize_y)
px = int(abs(point[1] - x_coords[0]) / cellsize_x)

Two problems come out of this:

  1. abs() folds out-of-bounds points back inside the raster. A point past the edge on the coords[0] side gets a positive offset and maps to a mirrored interior pixel. a_star_search then runs from the wrong start pixel instead of raising the "start location outside the surface graph" error it raises for points past the opposite edge. multi_stop_search's waypoint bounds check has the same blind spot.

  2. int() truncates instead of rounding to the nearest cell center. Any point in the upper half of a cell is assigned to the neighboring lower-index cell (a half-cell bias), and a point that should land exactly on a cell center can flip to the previous cell through float noise: on a grid with 0.1 resolution, x=0.3 maps to pixel 2 because the pixel-3 center is 0.30000000000000004.

Reproduction (run on this host):

import numpy as np
import xarray as xr
from xrspatial import a_star_search
from xrspatial.pathfinding import _get_pixel_id

h = w = 5
agg = xr.DataArray(np.ones((h, w)), dims=['y', 'x'], attrs={'res': (1.0, 1.0)})
agg['y'] = np.linspace(h - 1, 0, h)   # [4, 3, 2, 1, 0]
agg['x'] = np.linspace(0, w - 1, w)

# 1) start y=5.0 is OUTSIDE the raster (top edge is y=4.0)
path = a_star_search(agg, (5.0, 0.0), (0.0, 4.0))
print(np.argwhere(path.values == 0))   # -> [[1 0]]  path starts at row 1, no error

# 2) truncation
agg2 = xr.DataArray(np.ones((2, 10)), dims=['y', 'x'])
agg2['y'] = [0.1, 0.0]
agg2['x'] = np.arange(10) * 0.1
print(_get_pixel_id((0.0, 0.3), agg2, 'x', 'y'))   # -> (0, 2), pixel-3 center is 0.30000000000000004
print(_get_pixel_id((0.0, 2.6), agg, 'x', 'y'))    # -> (?, 2) on a unit grid, nearest center is 3

Observed output:

[[1 0]]
(0, 2)
(2, 2)

Expected behavior

  • A point outside the raster bounds raises ValueError ("start location outside the surface graph") regardless of which side it falls on.
  • A point maps to the cell whose center is nearest, so x=0.3 on a 0.1-resolution grid resolves to pixel 3 and x=2.6 on a unit grid resolves to pixel 3.

The fix is to compute a signed offset using the coordinate direction and round to the nearest index; negative indices then fail the existing _is_inside check and raise as intended.

Additional context

Found by the accuracy sweep (Cat 1/Cat 3). The folding half is rated HIGH (silent wrong path from a mis-mapped start/goal), the truncation half MEDIUM. Both a_star_search and multi_stop_search route through _get_pixel_id. Existing tests only use points that sit exactly on cell centers, so they pass under either convention.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions