Skip to content

Note

Click here to download the full example code

outlines

a drilling program needs an outline: to clip a surface, to report what lies within the drilled area, or to limit an estimate to it. outline draws one around points. the convex hull bridges every gap; a concave outline follows the edge of the drilling, and a buffer pads it by a fixed distance.

import sys
from pathlib import Path

HERE = Path(__file__).parent if "__file__" in globals() else Path.cwd()
sys.path.insert(0, str(HERE.parents[1]))
import boitata as bt
import matplotlib.pyplot as plt
import numpy as np
from common import ACCENT, GRAY, HIGHLIGHT, map_axes, save

table = bt.datasets.stacked_sulphide_lenses()["collars"]
collars = bt.PointSet.from_table(table)
spacing = np.nanmedian(bt.data_spacing(collars, collars, None))
print(f"{len(collars)} collars, median spacing {spacing:.0f} m")

Out:

289 collars, median spacing 55 m

a concave outline erodes the convex hull from its longest boundary edge inward. max_edge stops it: boundary edges up to that length stay. at 3 times the median spacing the outline follows the dense west side and cuts into the sparser east, where holes lie further apart. every collar stays inside or on the outline.

convex = bt.outline(collars)
concave = bt.outline(collars, method="concave", max_edge=3 * spacing)
padded = bt.outline(collars, method="concave", max_edge=3 * spacing, buffer=spacing)
for name, lines in (("convex", convex), ("concave", concave), ("concave + buffer", padded)):
    print(f"{name:17} {lines.area()[0] / 1e6:.2f} km²")
assert (concave.contains(collars) | (concave.distance(collars) < 1e-6)).all()

fig, ax = plt.subplots(figsize=(6.5, 6), layout="constrained")
ax.scatter(*collars.coords[:, :2].T, s=6, color=GRAY, zorder=3)
for lines, color, style, label in (
    (convex, GRAY, "--", "convex"),
    (concave, ACCENT, "-", "concave"),
    (padded, HIGHLIGHT, "-", f"concave + {spacing:.0f} m"),
):
    ring = lines.parts[0]
    ax.plot(*np.vstack([ring, ring[:1]])[:, :2].T, color=color, ls=style, lw=1.2, label=label)
map_axes(ax, "Collar outlines")
ax.legend(loc="lower right")
save(fig, "outlines")

Collar outlines

Out:

convex            0.94 km²
concave           0.78 km²
concave + buffer  1.03 km²

categories draws one outline per label: here the diamond and reverse-circulation programs. the result is one feature per label, ready for contains or locate.

programs = bt.outline(collars, method="concave", max_edge=3 * spacing, categories="TYPE")
for label, area in zip(programs["TYPE"], programs.area(), strict=True):
    print(f"{label}: {area / 1e6:.2f} km²")

Out:

DD: 0.78 km²
RC: 0.03 km²

Total running time of the script: ( 0 minutes 0.177 seconds)

Download Python source code: example_02_18.py

Download Jupyter notebook: example_02_18.ipynb

Gallery generated by mkdocs-gallery