Proximity

The Proximity tools let you perform analyses using one of three distance metrics: Euclidean (straight-line), Manhattan (with path obstacles, i.e., taxicab distance), and Great Cricle (distance on the surface of a sphere).

  • Proximity Distance: Calculates the distance to the nearest of a set of target - or source - points for each point in the input raster.

  • Proximity Allocation: Identifies the nearest source (target) point (the ‘allocation’ point) for each cell in the input raster.

  • Proximity Direction: Returns the direction to the nearest source point (the ‘allocation’) for each cell in the input raster.

Importing Packages

[1]:
import numpy as np
import pandas as pd

import datashader as ds
from datashader.transfer_functions import shade
from datashader.transfer_functions import stack
from datashader.transfer_functions import dynspread
from datashader.transfer_functions import set_background
from datashader.colors import Elevation

import xrspatial

Proximity Distance

The xrspatial.proximity function operates on a given values aggregate to produce a new distance aggregate based on a set of target values and using a distance metric. The metric is used to calculate the distance at each each array cell (pixel) in the values aggregate from the nearest of the target points. For proximity, this smallest distance is set as each cell’s value in the returned aggregate.

A powerful feature of proximity is that you can target specific points, the target_values parameter, in the aggregate for distance calculation and ignore the rest. If this isn’t set, then all non-zero pixels are set as targets. Feel free to play with the parameter and see the difference between using target_values=[1,2,3,4] vs. the default option.

Let’s set up a values aggregate raster and try out the proximity function.

  • We can set up a regular cell-grid aggregate with datashader’s Canvas.points.

  • We’ll set this one up in reverse to make it easier.

  • We define the target points in a pandas DataFrame

  • We aggregate those with Canvas.points into an aggregates raster, which embeds the target points into a large grid of nan’s.

  • We change all the non-target points from nan to 0.

Now we have an aggregate raster full of zeros but with a few non-zero target/source points at our desired locations and each with a unique ‘id’ value.

We’ll set a quick shade and background to visualize this.

[2]:
from xrspatial import proximity
import pandas as pd

df = pd.DataFrame(
    {
        "x": [-13, -11, -5, 4, 9, 11, 18, 6],
        "y": [-13, -5, 0, 10, 7, 2, 5, -5],
        "id": [1, 2, 3, 4, 5, 6, 7, 8],
    }
)

W = 800
H = 600

cvs = ds.Canvas(plot_width=W, plot_height=H, x_range=(-20, 20), y_range=(-20, 20))

points_agg = cvs.points(df, x="x", y="y", agg=ds.min("id"))
points_agg.data[~np.isfinite(points_agg.data)] = 0

points_shaded = dynspread(
    shade(points_agg, cmap="salmon", min_alpha=0, span=(0, 1), how="linear"),
    threshold=1,
    max_px=5,
)
set_background(points_shaded, "black")
[2]:

Apply Proximity

Now we can apply the proximity function to our points aggregate.

Set Targets

We can set the target_values parameter to a subset of the points, for example, [1, 2, 3, 4], to only measure distance everywhere from the nearest of these four points.

Let’s do that, and then shade it for visualization.

Note: feel free to remove or add to the targets from among the digits 1-8 and see how the image changes.

[3]:
targets = [1, 2, 3, 4]
target_proximity_agg = proximity(
    points_agg, target_values=targets, distance_metric="GREAT_CIRCLE"
)

stack(
    shade(target_proximity_agg, cmap=["#bbeb9e", "black"], how="eq_hist"), points_shaded
)
[3]:

Note the brighter areas nearer the target points and the black lines along areas that are equal distance to two points.

Proximity with Default Targets: All Non-zero Points

Leaving out the target_values parameter entirely defaults it to all non-zero points.

[4]:
proximity_agg = proximity(points_agg, distance_metric="GREAT_CIRCLE")

stack(shade(proximity_agg, cmap=["#bbeb9e", "black"], how="eq_hist"), points_shaded)
[4]:

Apply proximity to a line

Proximity can be applied to any shape appregate, so applying it to a raster aggregate of lines works as well.

For a quick example we can once again aggregate the points DataFrame from above, but, using Canvas.line, into lines rather than isolated points.

Take a look below.

[5]:
line_agg = cvs.line(df, x="x", y="y")
line_shaded = dynspread(
    shade(line_agg, cmap=["salmon", "salmon"]), threshold=1, max_px=2
)
set_background(line_shaded, "black")
[5]:

Apply proximity

Now we can apply proximity and it gives us the smallest distance at every cell from any point on the line.

[6]:
line_proximity = proximity(line_agg)
stack(shade(line_proximity, cmap=["#bbeb9e", "black"], how="linear"), line_shaded)
[6]:

Apply Proximity with transformations

Since Xarray-spatial is built on Xarray and Numpy, we can also use proximity and then apply some nifty transformations from the wide range of transformations available through the xarray DataArray API.

Here’s an example using DataArray.where(): - We’ll bring in the line_proximity we calculated above. - Then we’ll put a minimum and maximum distance clause into DataArray.where().

The result is shaded and visualized below. As you can see, this provides a neat ouline around the line.

[7]:
where_clause = (line_proximity > 1) & (line_proximity < 1.1)
proximity_shaded = shade(
    line_proximity.where(where_clause), cmap=["darkturquoise", "darkturquoise"]
)
proximity_shaded = set_background(proximity_shaded, "black")
stack(proximity_shaded, line_shaded)
[7]:

Proximity Allocation

Similar to xrspatial.proximity, the xrspatial.allocation function operates on an aggregate and finds the smallest distance from each cell to any one of the target, or source, points. But, instead of the distance, it returns the value at that source point and sets the current cell to that value. So, the result is a raster with block areas, with all cells in each area set to the value of that area’s nearest source.

In the same way as for proximity, we can set the target points with the target_values parameter.

Let’s apply allocation to our aggregate from above, leaving the default all target points active.

[8]:
from xrspatial import allocation

allocation_agg = allocation(points_agg, distance_metric="GREAT_CIRCLE")

stack(
    shade(allocation_agg, cmap=["darkturquoise", "black"], how="linear"), points_shaded
)
[8]:

Notice the blocks: each of the differently shaded blocks contains all of the points that have the target point in the center as their nearest target point.

Proximity Direction

Also similar to xrspatial.proximity, xrspatial.direction also starts by calculating the smallest distance for each cell to a source point. But, instead of distance, it returns the direction in degrees to that nearest source point. The output values range from 0 to 360: - 0 is for the source cell itself - 90 is the east - 180 is the south - 270 is the west - 360 is the north

Once again, you can target specific values with the target_values parameter.

Let’s apply it to our aggrega from above, leaving all the default target points active.

(Note: the image is from a north point-of-view; i.e. north is down.)

[9]:
from xrspatial import direction

direction_agg = direction(points_agg, distance_metric="GREAT_CIRCLE")

stack(
    shade(direction_agg, cmap=["darkturquoise", "black"], how="linear"), points_shaded
)
[9]:

References

An overview of the Distance toolset: https://pro.arcgis.com/en/pro-app/tool-reference/spatial-analyst/an-overview-of-the-distance-tools.htm