Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update pointcloud to use latests Pdal and Pystac dependencies #3

Merged
merged 13 commits into from
Aug 28, 2023
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.8", "3.9", "3.10", "3.11"]
defaults:
run:
shell: bash -l {0}
Expand Down
53 changes: 40 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
# stactools-pointcloud

Template repostitory for [stactools](https://github.com/stac-utils/stactools) packages.

## How to use

1. Clone this repository and name it `stactools-{NAME}`, where `NAME` is your package name.
This name should be short, memorable, and a valid Python package name (i.e. it shouldn't start with a number, etc).
2. Update `setup.cfg` with your package name, description, and such.
3. Rename `src/stactools.pointcloud` to `src/stactools/{NAME}`.
4. Rewrite this README to provide information about how to use your package.
5. Update the LICENSE with your company's information (or whomever holds the copyright).
6. Update the environment name in `environment.yml`.
7. Update the environment variables in `.github/workflows/release.yml` to the appropriate values to publish for your organization.
8. Update all scripts in the `docker` directory to refer to `stactools-{NAME}` and `stactools-{NAME}-dev`.
Creates a STAC Item based on the header of a pointcloud.

## Dependencies

PDAL

## Installation
```bash
pip install stactools-pointcloud
```

## Usage

```
stac pointcloud create-item [OPTIONS] HREF DST


HREF is the pointcloud file. DST is directory that a STAC Item JSON file
will be created in.

Options:
-r, --reader TEXT Override the default PDAL reader.
-t, --pointcloud-type TEXT Set the pointcloud type (default: lidar)
--compute-statistics / --no-compute-statistics
Compute statistics for the pointcloud (could
take a while)
-p, --providers TEXT Path to JSON file containing array of
additional providers
--help Show this message and exit.
stactools package for Pointcloud data.

```

## Example

LAZ archive:

```bash
stac pointcloud create-item https://maps1.vcgov.org/LIDAR/LAZ/USGS_LPC_FL_Peninsular_2018_D18_LID2019_241594_E.laz .
```
7 changes: 5 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ channels:
- conda-forge
- defaults
dependencies:
- gdal
- pdal
- conda-forge::gdal>=3.3
- conda-forge::pdal>=2.5
- conda-forge::geos>=3.3
- conda-forge::rasterio>=1.3
- conda-forge::libstdcxx-ng # gdal dependency. Make sure it's from the same channel as gdal
8 changes: 4 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ keywords =
classifiers =
Development Status :: 4 - Beta
License :: OSI Approved :: Apache Software License
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11

[options]
package_dir =
= src
packages = find_namespace:
install_requires =
stactools ~= 0.1
pdal ~= 2.4
stactools ~= 0.5
pdal ~= 3.2

[options.packages.find]
where = src
1 change: 1 addition & 0 deletions src/stactools/pointcloud/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def create_pointcloud_command(cli):
"""Creates a command group for commands working with
pointclouds.
"""

@cli.group('pointcloud',
short_help=("Commands for working with "
"pointclouds."))
Expand Down
62 changes: 32 additions & 30 deletions src/stactools/pointcloud/stac.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@

from pdal import Pipeline
from pyproj import CRS
from pystac import Item, Asset
from pystac.extensions.pointcloud import PointcloudStatistic, PointcloudSchema
from shapely.geometry import shape, box, mapping

from pystac import Asset, Item
from pystac.extensions.pointcloud import PointcloudExtension, Schema, Statistic
from pystac.extensions.projection import ProjectionExtension
from shapely.geometry import box, mapping, shape
from stactools.core.projection import reproject_geom


def create_item(href,
pdal_reader=None,
compute_statistics=False,
pointcloud_type="lidar",
additional_providers=None):
def create_item(
href,
pdal_reader=None,
compute_statistics=False,
pointcloud_type="lidar",
additional_providers=None,
):
"""Creates a STAC Item from a point cloud.

Args:
Expand All @@ -41,7 +43,7 @@ def create_item(href,
"count": 0
}]))
pipeline.execute()
metadata = json.loads(pipeline.get_metadata())["metadata"]
metadata = pipeline.metadata["metadata"]
try:
reader_key = next(key for key in metadata.keys()
if key.startswith("readers"))
Expand All @@ -53,7 +55,7 @@ def create_item(href,
except StopIteration:
raise Exception("could not find reader key in pipeline metadata")
metadata = metadata[reader_key]
schema = pipeline.get_schema()["schema"]["dimensions"]
schema = pipeline.schema["schema"]["dimensions"]

id = os.path.splitext(os.path.basename(href))[0]
encoding = os.path.splitext(href)[1][1:]
Expand All @@ -80,18 +82,19 @@ def create_item(href,

item.add_asset(
"pointcloud",
Asset(href=href,
media_type="application/octet-stream",
roles=["data"],
title="{} point cloud".format(encoding)))
Asset(
href=href,
media_type="application/octet-stream",
roles=["data"],
title="{} point cloud".format(encoding),
),
)

item.ext.enable("pointcloud")
item.ext.pointcloud.count = metadata["count"]
item.ext.pointcloud.type = pointcloud_type
item.ext.pointcloud.encoding = encoding
item.ext.pointcloud.schemas = [
PointcloudSchema(schema) for schema in schema
]
pointcloud_ext = PointcloudExtension.ext(item, add_if_missing=True)
pointcloud_ext.count = metadata["count"]
pointcloud_ext.type = pointcloud_type
pointcloud_ext.encoding = encoding
pointcloud_ext.schemas = [Schema(schema) for schema in schema]
# TODO compute density.
#
# Do we just divide point clount by bounding box area? That's too low. But
Expand All @@ -100,22 +103,21 @@ def create_item(href,
# quick-and-dirty? But then densities mean different things depending on
# the processing history of the STAC file, which seems inconsistant.
if compute_statistics:
item.ext.pointcloud.statistics = _compute_statistics(reader)
pointcloud_ext.statistics = _compute_statistics(reader)

projection_ext = ProjectionExtension.ext(item, add_if_missing=True)
epsg = spatialreference.to_epsg()
if epsg:
item.ext.enable("projection")
item.ext.projection.epsg = epsg
item.ext.projection.wkt2 = spatialreference.to_wkt()
item.ext.projection.bbox = list(original_bbox.bounds)
projection_ext.epsg = epsg
projection_ext.wkt2 = spatialreference.to_wkt()
projection_ext.bbox = list(original_bbox.bounds)

return item


def _compute_statistics(reader):
pipeline = Pipeline(json.dumps([reader, {"type": "filters.stats"}]))
pipeline.execute()
stats = json.loads(
pipeline.get_metadata())["metadata"]["filters.stats"]["statistic"]
stats = [PointcloudStatistic(stats) for stats in stats]
stats = pipeline.metadata["metadata"]["filters.stats"]["statistic"]
stats = [Statistic(stats) for stats in stats]
return stats
18 changes: 11 additions & 7 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import os
import unittest

from tempfile import TemporaryDirectory

import pystac

from click import Group
from click.testing import CliRunner
from stactools.pointcloud.commands import create_pointcloud_command
from tests.utils import (TestData, CliTestCase)


class CreateItemTest(CliTestCase):
def create_subcommand_functions(self):
return [create_pointcloud_command]
class CreateItemTest(unittest.TestCase):

def test_create_item(self):
href = TestData.get_path('data-files/pointcloud/autzen_trim.las')
href = f"{os.path.dirname(__file__)}/data-files/autzen_trim.las"
with TemporaryDirectory() as directory:
cmd = ['pointcloud', 'create-item', href, directory]
self.run_command(cmd)
runner = CliRunner()
runner.invoke(
create_pointcloud_command(Group()),
["create-item", href, directory],
)
jsons = [p for p in os.listdir(directory) if p.endswith('.json')]
self.assertEqual(len(jsons), 1)
item_path = os.path.join(directory, jsons[0])
Expand Down
28 changes: 15 additions & 13 deletions tests/test_stac.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import datetime
import os
import unittest

from tests.utils import TestData
from pystac.extensions.pointcloud import PointcloudExtension
from pystac.extensions.projection import ProjectionExtension
from stactools.pointcloud.stac import create_item


class StacTest(unittest.TestCase):

def test_create_item(self):
path = TestData.get_path("data-files/pointcloud/autzen_trim.las")
path = f"{os.path.dirname(__file__)}/data-files/autzen_trim.las"
item = create_item(path)
self.assertEqual(item.id, "autzen_trim")
self.assertEqual(item.datetime, datetime.datetime(2015, 9, 10))

item.ext.enable("projection")
self.assertEqual(item.ext.projection.epsg, 2994)

item.ext.enable("pointcloud")
self.assertEqual(item.ext.pointcloud.count, 110000)
self.assertEqual(item.ext.pointcloud.type, "lidar")
self.assertEqual(item.ext.pointcloud.encoding, "las")
self.assertEqual(item.ext.pointcloud.statistics, None)
self.assertTrue(item.ext.pointcloud.schemas)
ProjectionExtension.validate_has_extension(item)
pointcloud_ext = PointcloudExtension.ext(item)
self.assertEqual(pointcloud_ext.count, 110000)
self.assertEqual(pointcloud_ext.type, "lidar")
self.assertEqual(pointcloud_ext.encoding, "las")
self.assertEqual(pointcloud_ext.statistics, None)
self.assertTrue(pointcloud_ext.schemas)

item.validate()

def test_create_item_with_statistic(self):
path = TestData.get_path("data-files/pointcloud/autzen_trim.las")
path = f"{os.path.dirname(__file__)}/data-files/autzen_trim.las"
item = create_item(path, compute_statistics=True)
self.assertNotEqual(item.ext.pointcloud.statistics, None)
pointcloud_ext = PointcloudExtension.ext(item, add_if_missing=True)
self.assertNotEqual(pointcloud_ext.statistics, None)

def test_create_item_from_url(self):
url = "https://github.com/PDAL/PDAL/raw/2.2.0/test/data/las/autzen_trim.las"
Expand Down
Loading