Skip to content

Commit c84ea45

Browse files
authored
Merge pull request #49 from TaskarCenterAtUW/develop
BUGFIX-2737 to Main
2 parents 7ec2aa8 + 0a7974e commit c84ea45

38 files changed

+735
-17
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change log
22

3+
### 0.3.1 - 2025-02-12
4+
- Restored custom field support across schemas (CustomNode/Edge/Zone) while aligning required fields with original behavior.
5+
- Relaxed edges/zones global required lists to let custom features omit `highway` (kept in branch-specific schemas).
6+
- Enhanced 0.2 compatibility guard: allow `ext:` on nodes, block custom content per dataset with specific messages (e.g., Custom Edge/Point/Polygon), and reject non-point `ext:`.
7+
- Added schema parity tests and fixtures for custom feature branches and 0.2 vs 0.3 behavior, including guard reason checks.
8+
- Improved diff tooling/reporting and parity fixtures to cover custom scenarios.
9+
310
### 0.3.0
411
- Default to OSW 0.3 dataset-specific schemas (edges, lines, nodes, points, polygons, zones) with filename-driven selection; removed legacy monolithic/geometry schema files.
512
- Enforce the six canonical OSW 0.3 filenames inside datasets; reject non-standard names and detect duplicates/missing required files (with new unit tests).

src/python_osw_validation/__init__.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,24 @@ def _schema_key_from_text(self, text: Optional[str]) -> Optional[str]:
124124
return key
125125
return None
126126

127-
def _contains_disallowed_features_for_02(self, geojson_data: Dict[str, Any]) -> bool:
128-
"""Detect Tree coverage or Custom Point/Line/Polygon in legacy 0.2 datasets."""
127+
def _contains_disallowed_features_for_02(self, geojson_data: Dict[str, Any]) -> set:
128+
"""Detect Tree coverage or Custom content in legacy 0.2 datasets.
129+
130+
Returns a set of reason tags, e.g. {"tree", "custom_ext", "custom_token"}.
131+
Empty set means no 0.2-only violations detected.
132+
"""
133+
reasons = set()
129134
for feat in geojson_data.get("features", []):
130135
props = feat.get("properties") or {}
136+
geom = feat.get("geometry") or {}
137+
geom_type = geom.get("type") if isinstance(geom, dict) else None
138+
is_point = isinstance(geom_type, str) and geom_type.lower() == "point"
139+
131140
val = props.get("natural")
132141
if isinstance(val, str) and val.strip().lower() in {"tree", "wood"}:
133-
return True
142+
reasons.add("tree")
134143
if any(k in props for k in ("leaf_cycle", "leaf_type")):
135-
return True
144+
reasons.add("tree")
136145
for k, v in props.items():
137146
target = ""
138147
if isinstance(v, str):
@@ -142,8 +151,8 @@ def _contains_disallowed_features_for_02(self, geojson_data: Dict[str, Any]) ->
142151
if any(tok in target for tok in ["custom point", "custom_point", "custompoint",
143152
"custom line", "custom_line", "customline",
144153
"custom polygon", "custom_polygon", "custompolygon"]):
145-
return True
146-
return False
154+
reasons.add("custom_token")
155+
return reasons
147156

148157
# ----------------------------
149158
# Schema selection
@@ -475,9 +484,25 @@ def validate_osw_errors(self, file_path: str, max_errors: int) -> bool:
475484

476485
schema_url = geojson_data.get('$schema')
477486
if isinstance(schema_url, str) and '0.2/schema.json' in schema_url:
478-
if self._contains_disallowed_features_for_02(geojson_data):
487+
reasons = self._contains_disallowed_features_for_02(geojson_data)
488+
if reasons:
489+
dataset_key = self._schema_key_from_text(file_path) or "data"
490+
custom_label_map = {
491+
"edges": "Custom Edge",
492+
"lines": "Custom Line",
493+
"polygons": "Custom Polygon",
494+
"zones": "Custom Polygon/Zone",
495+
"points": "Custom Point",
496+
"nodes": "Custom Node",
497+
}
498+
parts = []
499+
if "tree" in reasons:
500+
parts.append("Tree coverage")
501+
if "custom_ext" in reasons or "custom_token" in reasons:
502+
parts.append(custom_label_map.get(dataset_key, "Custom content"))
503+
msg = f"0.2 schema does not support " + " and ".join(parts)
479504
self.log_errors(
480-
message="0.2 schema does not support Tree coverage, Custom Point, Custom Line, and Custom Polygon",
505+
message=msg,
481506
filename=os.path.basename(file_path),
482507
feature_index=None,
483508
)

src/python_osw_validation/schema/opensidewalks.edges.schema-0.3.json

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,49 @@
308308
"required": [
309309
"_id",
310310
"_u_id",
311-
"_v_id",
312-
"highway"
311+
"_v_id"
313312
],
314313
"anyOf": [
314+
{
315+
"title": "CustomEdgeFields",
316+
"type": "object",
317+
"additionalProperties": false,
318+
"properties": {
319+
"_id": {
320+
"minLength": 1,
321+
"type": "string"
322+
},
323+
"_u_id": {
324+
"minLength": 1,
325+
"type": "string"
326+
},
327+
"_v_id": {
328+
"minLength": 1,
329+
"type": "string"
330+
},
331+
"foot": {
332+
"description": "A field that indicates whether an edge can be used by pedestrians.",
333+
"enum": [
334+
"designated",
335+
"destination",
336+
"no",
337+
"permissive",
338+
"private",
339+
"use_sidepath",
340+
"yes"
341+
],
342+
"type": "string"
343+
}
344+
},
345+
"required": [
346+
"_id",
347+
"_u_id",
348+
"_v_id"
349+
],
350+
"patternProperties": {
351+
"^ext:.*$": {}
352+
}
353+
},
315354
{
316355
"title": "AlleyFields",
317356
"type": "object",
@@ -1895,4 +1934,4 @@
18951934
}
18961935
}
18971936
}
1898-
}
1937+
}

src/python_osw_validation/schema/opensidewalks.nodes.schema-0.3.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,23 @@
196196
"_id"
197197
],
198198
"anyOf": [
199+
{
200+
"title": "CustomNodeFields",
201+
"type": "object",
202+
"additionalProperties": false,
203+
"properties": {
204+
"_id": {
205+
"minLength": 1,
206+
"type": "string"
207+
}
208+
},
209+
"required": [
210+
"_id"
211+
],
212+
"patternProperties": {
213+
"^ext:.*$": {}
214+
}
215+
},
199216
{
200217
"title": "BareNodeFields",
201218
"type": "object",
@@ -417,4 +434,4 @@
417434
}
418435
}
419436
}
420-
}
437+
}

src/python_osw_validation/schema/opensidewalks.zones.schema-0.3.json

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,65 @@
225225
},
226226
"required": [
227227
"_id",
228-
"_w_id",
229-
"highway"
228+
"_w_id"
230229
],
231230
"anyOf": [
231+
{
232+
"title": "CustomZoneFields",
233+
"type": "object",
234+
"additionalProperties": false,
235+
"properties": {
236+
"_id": {
237+
"minLength": 1,
238+
"type": "string"
239+
},
240+
"_w_id": {
241+
"items": {
242+
"type": "string"
243+
},
244+
"type": "array"
245+
},
246+
"foot": {
247+
"description": "A field that indicates whether an edge can be used by pedestrians.",
248+
"enum": [
249+
"designated",
250+
"destination",
251+
"no",
252+
"permissive",
253+
"private",
254+
"use_sidepath",
255+
"yes"
256+
],
257+
"type": "string"
258+
},
259+
"name": {
260+
"description": "A field for a designated name for an entity. Example: an official name for a trail.",
261+
"type": "string"
262+
},
263+
"surface": {
264+
"description": "A field for the surface material of the path.",
265+
"enum": [
266+
"asphalt",
267+
"concrete",
268+
"dirt",
269+
"grass",
270+
"grass_paver",
271+
"gravel",
272+
"paved",
273+
"paving_stones",
274+
"unpaved"
275+
],
276+
"type": "string"
277+
}
278+
},
279+
"required": [
280+
"_id",
281+
"_w_id"
282+
],
283+
"patternProperties": {
284+
"^ext:.*$": {}
285+
}
286+
},
232287
{
233288
"title": "PedestrianZoneFields",
234289
"type": "object",
@@ -302,4 +357,4 @@
302357
}
303358
}
304359
}
305-
}
360+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.3.0'
1+
__version__ = '0.3.1'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "https://sidewalks.washington.edu/opensidewalks/0.3/schema.json",
3+
"type": "FeatureCollection",
4+
"features": [
5+
{
6+
"type": "Feature",
7+
"geometry": {"type": "Point", "coordinates": [0, 0]},
8+
"properties": {
9+
"_id": "edge-4",
10+
"_u_id": "node-f",
11+
"_v_id": "node-g",
12+
"footway": "sidewalk",
13+
"highway": "footway"
14+
}
15+
}
16+
]
17+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"$schema": "https://sidewalks.washington.edu/opensidewalks/0.2/schema.json",
3+
"type": "FeatureCollection",
4+
"features": [
5+
{
6+
"type": "Feature",
7+
"geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]},
8+
"properties": {
9+
"_id": "sw_3234",
10+
"highway": "footway",
11+
"footway": "traffic_island",
12+
"_u_id": "1",
13+
"_v_id": "2"
14+
}
15+
},
16+
{
17+
"type": "Feature",
18+
"geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]},
19+
"properties": {
20+
"_id": "cus_123",
21+
"_u_id": "3",
22+
"_v_id": "5",
23+
"ext:test_field": "custom_value"
24+
}
25+
}
26+
]
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://sidewalks.washington.edu/opensidewalks/0.3/schema.json",
3+
"type": "FeatureCollection",
4+
"features": [
5+
{
6+
"type": "Feature",
7+
"geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]},
8+
"properties": {
9+
"_id": "edge-3",
10+
"_u_id": "node-e",
11+
"footway": "sidewalk"
12+
}
13+
}
14+
]
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "https://sidewalks.washington.edu/opensidewalks/0.3/schema.json",
3+
"type": "FeatureCollection",
4+
"features": [
5+
{
6+
"type": "Feature",
7+
"geometry": {"type": "LineString", "coordinates": [[0, 0], [1, 1]]},
8+
"properties": {
9+
"_id": "custom-edge-1",
10+
"_u_id": "node-1",
11+
"_v_id": "node-2",
12+
"highway": "service",
13+
"foot": "yes"
14+
}
15+
}
16+
]
17+
}

0 commit comments

Comments
 (0)