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

Draft point cloud STAC extension #369

Merged
merged 11 commits into from
Jan 8, 2019
1 change: 1 addition & 0 deletions extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ stable for over a year and are used in twenty or more implementations.
| [Single Item](single-item/README.md) (`item`) | Item | Provides a way to specify several fields in individual Items that usually reside on the collection-level such as license and providers. | *Proposal* |
| [Scientific](scientific//README.md) (`sci`) | Item | Scientific metadata is considered to be data that indicate from which publication a collection originates and how the collection itself should be cited or referenced. | *Proposal* |
| [Transaction](transaction//README.md) | API | Provides an API extension to support the creation, editing, and deleting of items on a specific WFS3 collection. | *Pilot* |
| [Point Cloud](pointcloud/README.md) (`pc`) | Item | Provides a way to describe point cloud datasets. The point clouds can come from either active or passive sensors, and data is frequently acquired using tools such as LiDAR or coincidence-matched imagery. | *Proposal* |

## Third-party / vendor extensions

Expand Down
51 changes: 51 additions & 0 deletions extensions/pointcloud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Point Cloud Extension Specification (`pc`)

**Extension [Maturity Classification](../README.md#extension-maturity): Proposal**

This document explains the fields of the Point Cloud Extension to a STAC Item,
which allows STAC to more fully describe point cloud datasets. The point clouds can
come from either active or passive sensors, and data is frequently acquired using
tools such as LiDAR or coincidence-matched imagery.

- [Example](example-alaska.json)
- JSON Schema is missing. PRs are welcome.

## Item Fields

| Field Name | Type | Description |
| ------------- | ------------------- | ----------- |
| pc:count | integer | The number of points in the item. |
| pc:density | number | Number of points per square unit area. |
| pc:encoding | string | Content encoding or format of the data. |
| pc:schema | [Schema Object] | A sequential array of items that define the dimensions and their types. |
| pc:statistics | [Statistics Object] | A sequential array of items mapping to `pc:schema` defines per-channel statistics. |
| pc:type | string | Phenomenology type for the point cloud. Valid values include `lidar`, `eopc`, `radar`, `sonar`. |

### Schema Object

A sequential array of items that define the dimensions and their types.

| Field Name | Type | Description |
| ---------- | ------- | -------------------------- |
| name | string | The name of the dimension. |
| size | integer | |
| type | string | |

### Statistics Object

A sequential array of items mapping to `pc:schema` defines per-channel statistics.

| Field Name | Type | Description |
| ---------- | ------- | ----------- |
| average | number | The average of the channel. |
| count | integer | The number of elements in the channel. |
| maximum | number | The maximum value of the channel. |
| minimum | number | The minimum value of the channel. |
| name | string | The name of the channel. |
| position | integer | |
| stddev | number | The standard deviation of the channel. |
| variance | number | The variance of the channel. |

## Implementations

None yet, still in proposal stage.
261 changes: 261 additions & 0 deletions extensions/pointcloud/example-alaska.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
{
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
"assets": {},
"bbox": {
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
"maxx": -149.2527639,
"maxy": 61.78936655,
"maxz": 1981.200833,
"minx": -149.3006585,
"miny": 61.76668361,
"minz": 11.48480563
},
"geometry": {
"coordinates": [
[
[
[
-149.30045496703195,
61.76682620765713
],
[
-149.25327277445712,
61.76657763000873
],
[
-149.25272374552367,
61.78921108684322
],
[
-149.30023535632952,
61.7894132390075
],
[
-149.30080489041393,
61.766924315502195
],
[
-149.30045496703195,
61.76682620765713
]
]
]
],
"type": "MultiPolygon"
},
"id": "AK_MatanuskaSusitna-Lot2_2011_000227.laz",
"properties": {
"c:description": "USGS 3DEP LiDAR",
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
"c:id": "MODIFICATION ",
"datetime": "2013-05-14T00:00:00",
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
"license": "LICENSE",
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
"pc:avg_pt_per_sq_unit": 3.163692583,
"pc:count": 64473760,
"pc:density": 0.9482590115,
"pc:encoding": "LASzip",
"pc:schema": [
{
"name": "X",
"size": 8,
"type": "floating"
},
{
"name": "Y",
"size": 8,
"type": "floating"
},
{
"name": "Z",
"size": 8,
"type": "floating"
},
{
"name": "Intensity",
"size": 2,
"type": "unsigned"
},
{
"name": "ReturnNumber",
"size": 1,
"type": "unsigned"
},
{
"name": "NumberOfReturns",
"size": 1,
"type": "unsigned"
},
{
"name": "ScanDirectionFlag",
"size": 1,
"type": "unsigned"
},
{
"name": "EdgeOfFlightLine",
"size": 1,
"type": "unsigned"
},
{
"name": "Classification",
"size": 1,
"type": "unsigned"
},
{
"name": "ScanAngleRank",
"size": 4,
"type": "floating"
},
{
"name": "UserData",
"size": 1,
"type": "unsigned"
},
{
"name": "PointSourceId",
"size": 2,
"type": "unsigned"
},
{
"name": "GpsTime",
"size": 8,
"type": "floating"
}
],
"pc:statistics": [
{
"average": 1765797.419,
"count": 64473760,
"maximum": 1769746,
"minimum": 1761544.01,
"name": "X",
"position": 0,
"stddev": 2340.346622,
"variance": 5477222.31
},
{
"average": 2842320.787,
"count": 64473760,
"maximum": 2846625,
"minimum": 2838423.01,
"name": "Y",
"position": 1,
"stddev": 2446.005498,
"variance": 5982942.898
},
{
"average": 3760.301515,
"count": 64473760,
"maximum": 6499.99,
"minimum": 37.68,
"name": "Z",
"position": 2,
"stddev": 953.5224496,
"variance": 909205.062
},
{
"average": 176.7857038,
"count": 64473760,
"maximum": 255,
"minimum": 0,
"name": "Intensity",
"position": 3,
"stddev": 92.00839536,
"variance": 8465.544817
},
{
"average": 1.214258343,
"count": 64473760,
"maximum": 5,
"minimum": 1,
"name": "ReturnNumber",
"position": 4,
"stddev": 0.7263398082,
"variance": 0.527569517
},
{
"average": 1.376616394,
"count": 64473760,
"maximum": 5,
"minimum": 1,
"name": "NumberOfReturns",
"position": 5,
"stddev": 1.070825579,
"variance": 1.146667421
},
{
"average": 0.5003097849,
"count": 64473760,
"maximum": 1,
"minimum": 0,
"name": "ScanDirectionFlag",
"position": 6,
"stddev": 0.4999999683,
"variance": 0.2499999683
},
{
"average": 0.000881211209,
"count": 64473760,
"maximum": 1,
"minimum": 0,
"name": "EdgeOfFlightLine",
"position": 7,
"stddev": 0.02967213003,
"variance": 0.0008804353008
},
{
"average": 17.21186159,
"count": 64473760,
"maximum": 129,
"minimum": 2,
"name": "Classification",
"position": 8,
"stddev": 31.34383656,
"variance": 982.4360901
},
{
"average": 0.3183486119,
"count": 64473760,
"maximum": 24,
"minimum": -22,
"name": "ScanAngleRank",
"position": 9,
"stddev": 9.934022398,
"variance": 98.684801
},
{
"average": 0,
"count": 64473760,
"maximum": 0,
"minimum": 0,
"name": "UserData",
"position": 10,
"stddev": 0,
"variance": 0
},
{
"average": 10733.9377,
"count": 64473760,
"maximum": 12042,
"minimum": 1084,
"name": "PointSourceId",
"position": 11,
"stddev": 3522.808499,
"variance": 12410179.72
},
{
"average": 26389458.29,
"count": 64473760,
"maximum": 30299322.58,
"minimum": -2450342.801,
"name": "GpsTime",
"position": 12,
"stddev": 10532949.21,
"variance": 110943019000000.0
}
],
"pc:type": "lidar",
"pc:units": {
"horizontal": "US survey foot",
"vertical": "US survey foot"
},
"provider": "USGS"
m-mohr marked this conversation as resolved.
Show resolved Hide resolved
},
"type": "Feature"
}
69 changes: 69 additions & 0 deletions extensions/pointcloud/pdal-to-stac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python
m-mohr marked this conversation as resolved.
Show resolved Hide resolved

# Read 'pdal info --all' output and emit a STAC pc object

import sys
import json
import os


data = sys.stdin.read()

j = json.loads(data)

def capture_date(pdalinfo):
import datetime
year = pdalinfo['metadata']['creation_year']
day = pdalinfo['metadata']['creation_doy']
date = datetime.datetime(int(year), 1, 1) + datetime.timedelta(int(day) - 1)
return date.isoformat('T')

def convertGeometry(geom, srs):
import ogr
import osr
in_ref = osr.SpatialReference()
in_ref.SetFromUserInput(srs)
out_ref = osr.SpatialReference()
out_ref.SetFromUserInput('EPSG:4326')

g = ogr.CreateGeometryFromJson(json.dumps(geom))
g.AssignSpatialReference(in_ref)
g.TransformTo(out_ref)
return json.loads(g.ExportToJson())



output = {}

try:
output['geometry'] = convertGeometry(j['boundary']['boundary_json'],j['metadata']['comp_spatialreference'])
except KeyError:
output['geometry'] = j['stats']['bbox']['EPSG:4326']['boundary']

output['bbox'] = j['stats']['bbox']['EPSG:4326']['bbox']
output['id'] = os.path.basename(j['filename'])
output['type'] = 'Feature'

assets = {}
#assets['thumbnail'] =
properties = {}

properties['pc:schema'] = j['schema']['dimensions']
properties['pc:statistics'] = j['stats']['statistic']
properties['c:id'] = os.path.basename(j['filename'])
properties['c:description'] = "USGS 3DEP LiDAR"
properties['provider'] = "USGS"
properties['license'] = 'LICENSE'
properties['pc:type'] = 'lidar' # eopc, lidar, radar, sonar
properties['pc:density'] = j['boundary']['avg_pt_per_sq_unit']
properties['pc:count'] = j['metadata']['count']

properties['pc:encoding'] = 'LASzip' if bool(j['metadata']['compressed']) else 'None'

properties['datetime'] = capture_date(j)

output['properties'] = properties
output['assets'] = assets

sys.stdout.write(json.dumps(output,sort_keys=True,
indent=2, separators=(',', ': ')))