-
Notifications
You must be signed in to change notification settings - Fork 232
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing JSON Schema for Provider (#53)
The following commit implements JSON Schema for Provider. Agency TK * Use official FeatureCollection schema. * Use custom FeatureCollection spec. * Add semver-aware version. * Move propulsion_type into a common definition. * Add vehicle_type definition. * Make event_position a `Point`. Reuse the `Point` schema. * Test schema. * Fix name of schema. * Auto-getnerate schemas without nonlocal refs. * Add some docstrings. * Rename directory. * Inadvertantly added this file. * generate and reference a FeatureMDS definition
- Loading branch information
1 parent
8fc0720
commit 71412b5
Showing
7 changed files
with
1,303 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
{ | ||
"$id": "https://raw.githubusercontent.com/CityofLosAngeles/mobility-data-specification/master/provider/common.json", | ||
"$schema": "http://json-schema.org/draft-06/schema#", | ||
"title": "The MDS Provider Schema, common definitions", | ||
"type": "object", | ||
"definitions": { | ||
"version": { | ||
"$id": "#/definitions/version", | ||
"type": "string", | ||
"title": "The MDS Provider version this data represents", | ||
"examples": [ | ||
"0.2.0" | ||
], | ||
"pattern": "^0\\.2\\.[0-9]+$" | ||
}, | ||
"uuid": { | ||
"$id": "#/definitions/uuid", | ||
"type": "string", | ||
"title": "A UUID 4 used to uniquely identifty an object", | ||
"default": "", | ||
"examples": [ | ||
"3c9604d6-b5ee-11e8-96f8-529269fb1459" | ||
], | ||
"format": "uri", | ||
"pattern": "^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$" | ||
}, | ||
"propulsion_type": { | ||
"$id": "#/definitions/propulsion_type", | ||
"type": "array", | ||
"description": "The type of propulsion; allows multiple values", | ||
"items": { | ||
"type": "string", | ||
"enum": [ | ||
"combustion", | ||
"electric", | ||
"electric_assist", | ||
"human" | ||
] | ||
}, | ||
"minItems": 1 | ||
}, | ||
"vehicle_type": { | ||
"$id": "#/definitions/vehicle_type", | ||
"type": "string", | ||
"description": "The type of vehicle", | ||
"enum": [ | ||
"bicycle", | ||
"scooter" | ||
] | ||
}, | ||
"links": { | ||
"$id": "#/definitions/links", | ||
"type": "object", | ||
"properties": { | ||
"first": { | ||
"$id": "#/definitions/links/first", | ||
"type": "string", | ||
"title": "The URL to the first page of data", | ||
"examples": [ | ||
"https://data.provider.co/trips/first" | ||
], | ||
"format": "uri", | ||
"pattern": "^(.*)$" | ||
}, | ||
"last": { | ||
"$id": "#/definitions/links/last", | ||
"type": "string", | ||
"title": "The URL to the last page of data", | ||
"examples": [ | ||
"https://data.provider.co/trips/last" | ||
], | ||
"format": "uri", | ||
"pattern": "^(.*)$" | ||
}, | ||
"prev": { | ||
"$id": "#/definitions/links/prev", | ||
"type": "string", | ||
"title": "The URL to the previous page of data", | ||
"examples": [ | ||
"https://data.provider.co/trips/prev" | ||
], | ||
"format": "uri", | ||
"pattern": "^(.*)$" | ||
}, | ||
"next": { | ||
"$id": "#/definitions/links/next", | ||
"type": "string", | ||
"title": "The URL to the next page of data", | ||
"default": "", | ||
"examples": [ | ||
"https://data.provider.co/trips/next" | ||
], | ||
"format": "uri", | ||
"pattern": "^(.*)$" | ||
} | ||
}, | ||
"additionalProperties": false | ||
}, | ||
"timestamp": { | ||
"$id": "#/definitions/timestamp", | ||
"title": "Floating-point seconds since Unix epoch", | ||
"type": "number", | ||
"minimum": 0.0 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
""" | ||
generate_provider_schema.py | ||
When run, makes standalone schemas for the MDS provider API. | ||
""" | ||
|
||
import json | ||
import requests | ||
|
||
POINT = "Point" | ||
MDS_FEATURE_POINT = "MDS_Feature_Point" | ||
MDS_FEATURECOLLECTION_ROUTE = "MDS_FeatureCollection_Route" | ||
|
||
def get_definition(id): | ||
return "#/definitions/{}".format(id) | ||
|
||
def get_point_schema(): | ||
""" | ||
Get the canonical schema for a GeoJSON point. | ||
""" | ||
p = requests.get("http://geojson.org/schema/Point.json") | ||
point = p.json() | ||
# Modify some metadata | ||
point.pop("$schema") | ||
point["$id"] = get_definition(POINT) | ||
return point | ||
|
||
def get_feature_schema(id=None, title=None, geometry=None, properties=None, required=None): | ||
""" | ||
Get the canonical schema for a GeoJSON Feature, | ||
and make any given modifications. | ||
:id: overrides the `$id` metadata | ||
:title: overrides the `title` metadata | ||
:geometry: overrides the allowed `geometry` for the Feature | ||
:properties: overrides the `properties` definitions for this Feature | ||
:required: is a list of required :properties: | ||
""" | ||
# Get the canonical Feature schema | ||
f = requests.get("http://geojson.org/schema/Feature.json") | ||
feature = f.json() | ||
|
||
# Modify some metadata | ||
feature.pop("$schema") | ||
if id is not None: | ||
feature["$id"] = id | ||
if title is not None: | ||
feature["title"] = title | ||
|
||
if geometry is not None: | ||
feature["properties"]["geometry"] = geometry | ||
|
||
f_properties = feature["properties"]["properties"] | ||
if required is not None: | ||
f_properties["required"] = required | ||
if properties is not None: | ||
f_properties["properties"] = properties | ||
|
||
return feature | ||
|
||
def get_feature_collection_schema(id=None, title=None, features=None): | ||
""" | ||
Get the canonical schema for a GeoJSON Feature Collection, | ||
and make any given modifications. | ||
:id: overrides the `$id` metadata | ||
:title: overrides the `title` metadata | ||
:features: overrides the allowed `features` for the FeatureCollection | ||
""" | ||
# Get the canonical FeatureCollection schema | ||
fc = requests.get("http://geojson.org/schema/FeatureCollection.json") | ||
feature_collection = fc.json() | ||
# Modify some metadata | ||
feature_collection.pop("$schema") | ||
if id is not None: | ||
feature_collection["$id"] = id | ||
if title is not None: | ||
feature_collection["title"] = title | ||
|
||
if features is not None: | ||
fc_features = feature_collection["properties"]["features"] | ||
feature_collection["properties"]["features"] = { **fc_features, **features } | ||
|
||
return feature_collection | ||
|
||
def get_json_file(path): | ||
""" | ||
Load a JSON file from disk. | ||
""" | ||
with open(path) as f: | ||
data = json.load(f) | ||
return data | ||
|
||
|
||
if __name__ == '__main__': | ||
# Load common data | ||
common = get_json_file('common.json') | ||
point = get_point_schema() | ||
# Craft the MDS specific types | ||
mds_feature_point = get_feature_schema( | ||
id = get_definition(MDS_FEATURE_POINT), | ||
title = "MDS GeoJSON Feature Point", | ||
# Only allow GeoJSON Point feature geometry | ||
geometry = { "$ref": get_definition(POINT) }, | ||
# Point features *must* include a `timestamp` property. | ||
properties = { "timestamp": { "$ref": get_definition("timestamp") } }, | ||
required = ["timestamp"] | ||
) | ||
mds_feature_collection_route = get_feature_collection_schema( | ||
id = get_definition(MDS_FEATURECOLLECTION_ROUTE), | ||
title = "MDS GeoJSON FeatureCollection Route", | ||
# 1. Only allow MDS Feature Points | ||
# 2. There must be *at least* two Features in the FeatureCollection. | ||
features = { "items": { "$ref": get_definition(MDS_FEATURE_POINT) }, "minItems": 2 } | ||
) | ||
|
||
|
||
# Create the standalone trips JSON schema by including the needed definitions | ||
trips = get_json_file('provider/trips.json') | ||
trips["definitions"] = { | ||
POINT: point, | ||
MDS_FEATURE_POINT: mds_feature_point, | ||
MDS_FEATURECOLLECTION_ROUTE: mds_feature_collection_route, | ||
"links": common["definitions"]["links"], | ||
"propulsion_type": common["definitions"]["propulsion_type"], | ||
"vehicle_type": common["definitions"]["vehicle_type"], | ||
"version": common["definitions"]["version"], | ||
"uuid": common["definitions"]["uuid"], | ||
} | ||
# Write to the `provider` directory. | ||
with open("../provider/trips.json", "w") as tripfile: | ||
tripfile.write(json.dumps(trips, indent=2)) | ||
|
||
|
||
# Create the standalone status_changes JSON schema by including the needed definitions | ||
status_changes = get_json_file('provider/status_changes.json') | ||
status_changes["definitions"] = { | ||
POINT: point, | ||
MDS_FEATURE_POINT: mds_feature_point, | ||
"links": common["definitions"]["links"], | ||
"propulsion_type": common["definitions"]["propulsion_type"], | ||
"vehicle_type": common["definitions"]["vehicle_type"], | ||
"version": common["definitions"]["version"], | ||
"uuid": common["definitions"]["uuid"], | ||
} | ||
# Write to the `provider` directory. | ||
with open("../provider/status_changes.json", "w") as statusfile: | ||
statusfile.write(json.dumps(status_changes, indent=2)) |
Oops, something went wrong.