Skip to content

Commit 346ff74

Browse files
gadomskijsignell
andauthored
Implement classification v2.0.0 (#1359)
* feat: implement classification v2.0.0 * Update pystac/extensions/classification.py Co-authored-by: Julia Signell <[email protected]> * fix: don't change arg order * fix: allow None when returning description * fix(docs): docstring for nodata didn't make sense * fix: check for `None` name --------- Co-authored-by: Julia Signell <[email protected]>
1 parent a9eb8f6 commit 346ff74

File tree

6 files changed

+365
-321
lines changed

6 files changed

+365
-321
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
### Changed
66

7-
- Allow object ID as input for getting APILayoutStrategy hrefs and add `items`, `collections`, `search`, `conformance`, `service_desc` and `service_doc` href methods. ([#1335](https://github.com/stac-utils/pystac/pull/1335))
8-
- Update docstring of `name` argument to `Classification.apply` and `Classification.create` to agree with extension specification. ([#1356](https://github.com/stac-utils/pystac/pull/1356))
7+
- Allow object ID as input for getting APILayoutStrategy hrefs and add `items`, `collections`, `search`, `conformance`, `service_desc` and `service_doc` href methods ([#1335](https://github.com/stac-utils/pystac/pull/1335))
8+
- Updated classification extension to v2.0.0 ([#1359](https://github.com/stac-utils/pystac/pull/1359))
9+
- Update docstring of `name` argument to `Classification.apply` and `Classification.create` to agree with extension specification ([#1356](https://github.com/stac-utils/pystac/pull/1356))
910

1011
### Fixed
1112

pystac/extensions/classification.py

Lines changed: 99 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
SCHEMA_URI_PATTERN: str = (
3333
"https://stac-extensions.github.io/classification/v{version}/schema.json"
3434
)
35-
DEFAULT_VERSION: str = "1.1.0"
36-
SUPPORTED_VERSIONS: list[str] = ["1.1.0", "1.0.0"]
35+
DEFAULT_VERSION: str = "2.0.0"
36+
SUPPORTED_VERSIONS: list[str] = ["2.0.0", "1.1.0", "1.0.0"]
3737

3838
# Field names
3939
PREFIX: str = "classification:"
@@ -58,9 +58,12 @@ def __init__(self, properties: dict[str, Any]) -> None:
5858
def apply(
5959
self,
6060
value: int,
61-
description: str,
61+
description: str | None = None,
6262
name: str | None = None,
6363
color_hint: str | None = None,
64+
nodata: bool | None = None,
65+
percentage: float | None = None,
66+
count: int | None = None,
6467
) -> None:
6568
"""
6669
Set the properties for a new Classification.
@@ -69,21 +72,29 @@ def apply(
6972
value: The integer value corresponding to this class
7073
description: The description of this class
7174
name: Short name of the class for machine readability. Must consist only
72-
of letters, numbers, -, and _ characters.
75+
of letters, numbers, -, and _ characters. Required as of v2.0 of
76+
this extension.
7377
color_hint: An optional hexadecimal string-encoded representation of the
7478
RGB color that is suggested to represent this class (six hexadecimal
7579
characters, all capitalized)
80+
nodata: If set to true classifies a value as a no-data value.
81+
percentage: The percentage of data values that belong to this class
82+
in comparison to all data values, in percent (0-100).
83+
count: The number of data values that belong to this class.
7684
"""
7785
self.value = value
86+
# TODO pystac v2.0: make `name` non-optional, move it before
87+
# `description` in the arg list, and remove this check
88+
if name is None:
89+
raise Exception(
90+
"As of v2.0.0 of the classification extension, 'name' is required"
91+
)
7892
self.name = name
7993
self.description = description
8094
self.color_hint = color_hint
81-
82-
if color_hint is not None:
83-
match = COLOR_HINT_PATTERN.match(color_hint)
84-
assert (
85-
color_hint is None or match is not None and match.group() == color_hint
86-
), "Must format color hints as '^([0-9A-F]{6})$'"
95+
self.nodata = nodata
96+
self.percentage = percentage
97+
self.count = count
8798

8899
if color_hint is not None:
89100
match = COLOR_HINT_PATTERN.match(color_hint)
@@ -95,28 +106,39 @@ def apply(
95106
def create(
96107
cls,
97108
value: int,
98-
description: str,
109+
description: str | None = None,
99110
name: str | None = None,
100111
color_hint: str | None = None,
112+
nodata: bool | None = None,
113+
percentage: float | None = None,
114+
count: int | None = None,
101115
) -> Classification:
102116
"""
103117
Create a new Classification.
104118
105119
Args:
106120
value: The integer value corresponding to this class
107-
name: Short name of the class for machine readability. Must consist only
108-
of letters, numbers, -, and _ characters.
109121
description: The optional long-form description of this class
122+
name: Short name of the class for machine readability. Must consist only
123+
of letters, numbers, -, and _ characters. Required as of v2.0 of
124+
this extension.
110125
color_hint: An optional hexadecimal string-encoded representation of the
111126
RGB color that is suggested to represent this class (six hexadecimal
112127
characters, all capitalized)
128+
nodata: If set to true classifies a value as a no-data value.
129+
percentage: The percentage of data values that belong to this class
130+
in comparison to all data values, in percent (0-100).
131+
count: The number of data values that belong to this class.
113132
"""
114133
c = cls({})
115134
c.apply(
116135
value=value,
117136
name=name,
118137
description=description,
119138
color_hint=color_hint,
139+
nodata=nodata,
140+
percentage=percentage,
141+
count=count,
120142
)
121143
return c
122144

@@ -134,33 +156,38 @@ def value(self, v: int) -> None:
134156
self.properties["value"] = v
135157

136158
@property
137-
def description(self) -> str:
159+
def description(self) -> str | None:
138160
"""Get or set the description of the class
139161
140162
Returns:
141163
str
142164
"""
143-
return get_required(self.properties.get("description"), self, "description")
165+
return self.properties.get("description")
144166

145167
@description.setter
146-
def description(self, v: str) -> None:
147-
self.properties["description"] = v
168+
def description(self, v: str | None) -> None:
169+
if v is not None:
170+
self.properties["description"] = v
171+
else:
172+
self.properties.pop("description", None)
148173

149174
@property
150-
def name(self) -> str | None:
175+
def name(self) -> str:
151176
"""Get or set the name of the class
152177
153178
Returns:
154179
Optional[str]
155180
"""
156-
return self.properties.get("name")
181+
return get_required(self.properties.get("name"), self, "name")
157182

158183
@name.setter
159-
def name(self, v: str | None) -> None:
160-
if v is not None:
161-
self.properties["name"] = v
162-
else:
163-
self.properties.pop("name", None)
184+
def name(self, v: str) -> None:
185+
if v is None:
186+
raise Exception(
187+
"`name` was converted to a required attribute in classification"
188+
" version v2.0, so cannot be set to None"
189+
)
190+
self.properties["name"] = v
164191

165192
@property
166193
def color_hint(self) -> str | None:
@@ -185,6 +212,54 @@ def color_hint(self, v: str | None) -> None:
185212
else:
186213
self.properties.pop("color_hint", None)
187214

215+
@property
216+
def nodata(self) -> bool | None:
217+
"""Get or set the nodata value for this class.
218+
219+
Returns:
220+
bool | None
221+
"""
222+
return self.properties.get("nodata")
223+
224+
@nodata.setter
225+
def nodata(self, v: bool | None) -> None:
226+
if v is not None:
227+
self.properties["nodata"] = v
228+
else:
229+
self.properties.pop("nodata", None)
230+
231+
@property
232+
def percentage(self) -> float | None:
233+
"""Get or set the percentage value for this class.
234+
235+
Returns:
236+
Optional[float]
237+
"""
238+
return self.properties.get("percentage")
239+
240+
@percentage.setter
241+
def percentage(self, v: float | None) -> None:
242+
if v is not None:
243+
self.properties["percentage"] = v
244+
else:
245+
self.properties.pop("percentage", None)
246+
247+
@property
248+
def count(self) -> int | None:
249+
"""Get or set the count value for this class.
250+
251+
Returns:
252+
Optional[int]
253+
"""
254+
return self.properties.get("count")
255+
256+
@count.setter
257+
def count(self, v: int | None) -> None:
258+
if v is not None:
259+
self.properties["count"] = v
260+
else:
261+
self.properties.pop("count", None)
262+
188263
def to_dict(self) -> dict[str, Any]:
189264
"""Returns the dictionary encoding of this class
190265

tests/data-files/classification/collection-item-assets-raster-bands.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,6 @@
115115
"https://stac-extensions.github.io/projection/v1.1.0/schema.json",
116116
"https://stac-extensions.github.io/eo/v1.1.0/schema.json",
117117
"https://stac-extensions.github.io/raster/v1.1.0/schema.json",
118-
"https://stac-extensions.github.io/classification/v1.1.0/schema.json"
118+
"https://stac-extensions.github.io/classification/v2.0.0/schema.json"
119119
]
120120
}

0 commit comments

Comments
 (0)