Metadata-Version: 2.5
Name: opencloud3d
Version: 0.1.0
Summary: Query hosted point clouds by radius, without downloading them.
Project-URL: Homepage, https://app.opencloud3d.com
Project-URL: Documentation, https://app.opencloud3d.com/docs
Author: OpenCloud3D
License: MIT
Keywords: copc,geospatial,las,laz,lidar,point cloud
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.27
Requires-Dist: laspy<3,>=2.5
Requires-Dist: lazrs<1,>=0.5
Requires-Dist: numpy>=1.21
Description-Content-Type: text/markdown

# opencloud3d

Query hosted point clouds by radius, without downloading them.

```bash
pip install opencloud3d
```

A survey tile is gigabytes. The twenty metres around one pole is kilobytes. This
client asks for the second without moving the first: the clouds are stored as
[COPC](https://copc.io), so the server range-reads only the octree nodes your
radius touches and hands back a LAZ.

## Use it

```python
from opencloud3d import Client

with Client() as oc:                    # reads OPENCLOUD3D_API_KEY
    ds = oc.dataset("053025e6-c73f-4bfc-9066-dcaeeadb6122")

    ds.extent
    # {'epsg': 25832, 'bounds': [...], 'wgs84_bounds': [...], 'point_count': 15787378}

    pts = ds.query(lon=5.8672, lat=50.9784, radius=20)
    len(pts)          # 13394
    pts.xyz           # (13394, 3) numpy array, in the dataset's own CRS
    pts.points        # the full laspy object — intensity, classification, header
    pts.save("neighbourhood.laz")
```

Get a key at [app.opencloud3d.com/api-keys](https://app.opencloud3d.com/api-keys).

## The shape of a query

**Centre.** Either `lon`/`lat` (WGS84, what a map gives you) or `x`/`y` in the
dataset's own CRS (what a survey file gives you) — one pair, never both. There is
no default on purpose: a coordinate read in the wrong frame comes back *empty*,
which looks like "nothing there" rather than like the mistake it is.

**Radius is always metres**, in both spellings, whatever the CRS is measured in.
`radius=20` means twenty metres on a cloud in degrees and on one in US survey
feet, so the number in your script means the same thing on every dataset.

**Cylinder by default, sphere on request.** Leaving `z` out asks for every height
at that spot — which is what "within 20 m of this pole" means. Passing `z` makes
it a true sphere; a sphere centred on a guessed height silently misses the ground
beneath it, so it is opt-in.

```python
ds.query(lon=..., lat=..., radius=20)                    # cylinder, all heights
ds.query(lon=..., lat=..., radius=20, z=95.0)            # sphere about z = 95
ds.query(lon=..., lat=..., radius=20, z_min=90, z_max=100)   # a 10 m band
```

**Classes** are ASPRS codes; `2` is ground.

```python
ground = ds.query(lon=..., lat=..., radius=50, classes=[2])
```

⚠️ This only means anything on a cloud that has been **classified**. Most raw
uploads carry class 0 or 1 for every point, and filtering one of those on `[2]`
returns nothing — correctly, and confusingly. Check the classification histogram
in `ds.detail["stats"]` first.

## Empty answers are not errors

```python
from opencloud3d import NoPointsFound

try:
    pts = ds.query(lon=..., lat=..., radius=5, classes=[2])
except NoPointsFound:
    ...   # the radius fell outside the survey, or nothing matched the filter
```

## From the command line

```bash
export OPENCLOUD3D_API_KEY=oc3d_live_…
opencloud3d datasets
opencloud3d extent 053025e6-c73f-4bfc-9066-dcaeeadb6122
opencloud3d query 053025e6-… --lon 5.8672 --lat 50.9784 --radius 20 -o out.laz
```

## Limits

A query is bounded by how many points it would **return**, not by its radius —
twenty metres of empty countryside and twenty metres of dense urban survey are
three orders of magnitude apart. The ceiling is two million points per query,
because a query has to finish inside an HTTP round trip; a larger area is a job
for a bbox extract or a download. Ask for a smaller radius, or narrow it with
`z_min`/`z_max` or `classes`, if you are told it is too large.

For scale, on a 15.8 M-point airborne tile served from object storage: a 20 m
radius returns ~15 k points in ~4 s, and a 200 m radius ~1.7 M points in ~30 s.
Most of the first four seconds is storage latency rather than your radius.

API access requires a plan that includes it, and each key is rate limited.

Full reference: [app.opencloud3d.com/docs](https://app.opencloud3d.com/docs)
