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
Add JSON-LD representations #246
Conversation
- improved terms_of_service and contact.url for SEO - fixes typo for contact.instructions
Oh, and I haven't considered testing yet. |
Just an example of one place this is interesting to me. With a configuration like: datasets:
obs:
title: Observations
description: Observations
keywords:
- observations
- monitoring
crs:
- CRS84
context:
- datetime: https://schema.org/DateTime
- vocab: https://example.com/vocab#
stn_id: "vocab:stn_id"
value: "vocab:value"
- schema: "https://schema.org/"
name:
"@id": "schema:name"
"@language": "en"
name-fr:
"@id": "schema:name"
"@language": "fr"
links:
- type: text/csv
rel: canonical
title: data
href: https://github.com/mapserver/mapserver/blob/branch-7-0/msautotest/wxs/data/obs.csv
hreflang: en-US
- type: text/csv
rel: alternate
title: data
href: https://raw.githubusercontent.com/mapserver/mapserver/branch-7-0/msautotest/wxs/data/obs.csv
hreflang: en-US
extents:
spatial:
bbox: [-180, -90, 180, 90]
temporal:
begin: 2000-10-30T18:24:39Z
end: 2007-10-30T08:57:29Z
provider:
name: CSV
data: tests/data/obs.csv
id_field: id
geometry:
x_field: long
y_field: lat You can produce output that is explicit about the languages property values are expressed in; e.g. this configuration can produce: {
"@context": [
"https://geojson.org/geojson-ld/geojson-context.jsonld",
{
"datetime": "https://schema.org/DateTime"
},
{
"vocab": "https://example.com/vocab#",
"stn_id": "vocab:stn_id",
"value": "vocab:value"
},
{
"schema": "https://schema.org/",
"name": {
"@id": "schema:name",
"@language": "en"
},
"name-fr": {
"@id": "schema:name",
"@language": "fr"
}
}
],
"type": "Feature",
"id": "http://172.19.0.2:5000/collections/obs/items/371",
"geometry": {
"type": "Point",
"coordinates": [
-75,
45
]
},
"properties": {
"stn_id": "35",
"datetime": "2001-10-30T14:24:55Z",
"value": "89.9",
"name": "Foobar",
"name-fr": "Foobaré"
},
"links": [
{
"rel": "alternate",
"type": "application/geo+json",
"title": "This document as GeoJSON",
"href": "http://172.19.0.2:5000/collections/obs/items/371?f=json"
},
{
"rel": "self",
"type": "application/ld+json",
"title": "This document as RDF (JSON-LD)",
"href": "http://172.19.0.2:5000/collections/obs/items/371?f=jsonld"
},
{
"rel": "alternate",
"type": "text/html",
"title": "This document as HTML",
"href": "http://172.19.0.2:5000/collections/obs/items/371?f=html"
},
{
"rel": "collection",
"type": "application/json",
"title": "Observations",
"href": "http://172.19.0.2:5000/collections/obs"
},
{
"rel": "prev",
"type": "application/geo+json",
"href": "http://172.19.0.2:5000/collections/obs/items/371"
},
{
"rel": "next",
"type": "application/geo+json",
"href": "http://172.19.0.2:5000/collections/obs/items/371"
}
]
} Compacted: {
"@id": "http://172.19.0.2:5000/collections/obs/items/371",
"@type": "https://purl.org/geojson/vocab#Feature",
"https://purl.org/geojson/vocab#geometry": {
"@type": "https://purl.org/geojson/vocab#Point",
"https://purl.org/geojson/vocab#coordinates": {
"@list": [
-75,
45
]
}
},
"https://purl.org/geojson/vocab#properties": {
"https://example.com/vocab#stn_id": "35",
"https://example.com/vocab#value": "89.9",
"https://schema.org/DateTime": "2001-10-30T14:24:55Z",
"https://schema.org/name": [
{
"@language": "en",
"@value": "Foobar"
},
{
"@language": "fr",
"@value": "Foobaré"
}
]
}
} |
@alpha-beta-soup I'll start taking a deeper look in a week or so, but this is great work! As a first step I would recommend bringing the PR up to date with recent changes in master branch (#244) - which includes moving temporal extent types from |
Hi @alpha-beta-soup, thank you for this big work. At geonetwork we also decided to move to embedded json-ld. Maintaining schema.org annotations within html has quite regression risk and is probably also less flexible. Now that json-ld gets fortunately more common in the geo apis, this is a sensible step. Some comments on the work:
|
I didn't even consider that, but I think that's an excellent idea. I haven't worked with structured/linked data for long, so I really appreciate that pointer. I'll look at making that adjustment, once I've heard some other feedback also.
Yes, and I experimented with that successfully. I was uneasy about the trade-off: an asynchronous call to the API for the JSON-LD representation should mean the Time to Interactive is smaller than if the server also had to wait for the JSON-LD before returning anything. I figured that since Googlebot
Yes, I'm all for the ability to include multiple representations of the geometry, for a number of reasons: generalisation, scale, topology, dimensionality, perhaps even supporting true curves. My intention at first is to simply annotate what exists rather than to extend it, and I suggest that this is something to seriously consider at a later point once we're over the first bar. That said, maybe using |
@alpha-beta-soup Could you join us in the gitter (https://gitter.im/geopython/pygeoapi) we started to discuss the PR |
I've been considering how to represent more complex information in pygeoapi/JSON-LD, leveraging the PostgreSQL driver as a read input source. Given a table view defined as: CREATE OR REPLACE VIEW elfie.samples AS
SELECT
sf.gml_identifier AS "@id",
sf.featuretype_title AS "@type",
sf.gml_name AS "name",
sf.sams_shape_xy AS geom,
json_build_object(
'@type', 'gsp:Geometry',
'asWKT', (ST_AsEWKT(sf.sams_shape_xy))
) AS "hasGeometry",
json_build_object(
'@id', so.gml_identifier,
'@type', so.featuretype_title,
'name', null --TODO so.gml_name
) AS "isSampleOf",
json_agg(
DISTINCT jsonb_build_object(
'@id', om.gml_identifier,
'@type', om.featuretype_title,
'phenomenonTime', json_build_object(
'@id', om.om_phenomenontime_href,
'name', om.om_phenomenontime_title
),
'resultTime', json_build_object(
'@id', om.om_resulttime_href,
'name', om.om_resulttime_title
),
'usedProcedure', json_build_object(
'@id', om.om_procedure_href,
'name', om.om_procedure_title
),
'observedProperty', json_build_object(
'@id', om.om_observedproperty_href,
'name', om.om_observedproperty_title
),
'hasFeatureOfInterest', json_build_object(
'@id', sf.gml_identifier,
'@type', sf.featuretype_title
),
'hasResult',
CASE WHEN om.om_result_concept_id IS NOT NULL THEN json_build_object(
'@id', cl.gml_identifier,
'name', cl.gml_name
) WHEN om.om_result_string IS NOT NULL THEN json_build_object(
'@value', om.om_result_string
) WHEN om.om_result_measure IS NOT NULL THEN json_build_object(
'@value', om.om_result_measure,
'uom', om.om_result_uom
) ELSE NULL END
)
) AS "isFeatureOfInterestOf",
json_agg(
DISTINCT jsonb_build_object(
'natureOfRelationship', json_build_object(
'@id', 'https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample',
'name', 'sample'
),
'relatedSample', json_build_object(
'@id', sp.gml_identifier,
'name', sp.gml_name
)
)
) AS "hasSampleRelationship"
FROM elfie.sf_spatialsamplingfeature AS sf
JOIN elfie.om_observation_site AS om
ON sf._id = om.sf_samplingfeature_id
JOIN elfie.so_soil AS so
ON sf._id = so.sf_samplingfeature_id
JOIN elfie.sam_samplingfeaturecomplex
AS sfc ON sf._id = sfc.sourcefeature_id
JOIN elfie.sf_specimen AS sp
ON sp._id = sfc.targetfeature_id
LEFT JOIN elfie.cl_concept AS cl
ON om.om_result_concept_id = cl._id
AND so.soclassifier_id = cl._id
WHERE sf.sams_shape_xy IS NOT NULL
GROUP BY (sf.gml_identifier, sf.featuretype_title, sf.gml_name, sf.sams_shape_xy),
(so.gml_identifier, so.featuretype_title) --isSampleOf
ORDER BY sf.gml_identifier ASC; And pygeoapi config like: soilsamples:
title: Landcare Spatial Sampling Features
description: Spatial sampling features
keywords:
- Sampling
- Samples
- Landcare Research
- Manaaki Whenua
- Soil
- New Zealand
crs:
- CRS84
links:
- type: text/html
rel: canonical
title: information
href: https://lab.scinfo.org.nz
hreflang: en-NZ
extents:
spatial:
bbox: [169.890834606643, -44.6476190560794, 178.761531832564, -35.5201500812183]
crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
temporal:
begin:
end:
context:
- https://opengeospatial.github.io/ELFIE/json-ld/sosa.jsonld
- https://opengeospatial.github.io/ELFIE/json-ld/soilie.jsonld
- https://opengeospatial.github.io/ELFIE/json-ld/skos.jsonld
- gsp: http://www.opengis.net/ont/geosparql#
schema: "https://schema.org/"
name: "schema:name"
asWKT: gsp:asWKT
provider:
name: PostgreSQL
data:
host: redacted
dbname: redacted
user: redacted
password: redacted
port: redacted
schema: public,elfie # See issue: https://github.com/geopython/pygeoapi/issues/269
id_field: "@id"
table: samples
context: query (At this point there is an apparent bug, but it's not critical: #269) Then I can return some rather complex JSON-LD, expressing relationships as well as ordinary properties: Click to expand long (Geo)JSON-LD FeatureCollection{
"@context": [
"https://geojson.org/geojson-ld/geojson-context.jsonld",
"https://opengeospatial.github.io/ELFIE/json-ld/sosa.jsonld",
"https://opengeospatial.github.io/ELFIE/json-ld/soilie.jsonld",
"https://opengeospatial.github.io/ELFIE/json-ld/skos.jsonld",
{
"gsp": "http://www.opengis.net/ont/geosparql#",
"schema": "https://schema.org/",
"name": "schema:name",
"asWKT": "gsp:asWKT"
}
],
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
174.882920346082,
-37.0781263435251
]
},
"properties": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00001",
"@type": "sams:SF_SpatialSamplingFeature",
"name": "nsdr:55.42.00001",
"isSampleOf": {
"@id": "https://lab.scinfo.org.nz/elfie/id/soil/so_soil/47-55-42-00001-140253",
"@type": "soil:SO_Soil",
"name": null
},
"hasGeometry": {
"@type": "gsp:Geometry",
"asWKT": "SRID=4326;POINT(174.882920346082 -37.0781263435251)"
},
"hasSampleRelationship": [
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-00011",
"name": "nsdr:82.42.00011"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-10001a",
"name": "nsdr:82.42.10001a"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-10001b",
"name": "nsdr:82.42.10001b"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-10001c",
"name": "nsdr:82.42.10001c"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-0415a180-8032-4942-8ac3-33f19fb94aa0",
"name": "nsdr:82.45.15373.19439.348"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-47178795-5fac-4cae-9a39-839e6c1df774",
"name": "nsdr:82.45.15373.19442.039"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-617520b3-a110-4348-9f88-3c06d4cbbf70",
"name": "nsdr:82.45.15373.19439.205"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-83c99cd8-18ee-4228-baae-74bdb073852f",
"name": "nsdr:82.45.15373.19442.182"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-8bcbbc4c-b9b1-4a86-9379-a0550a242c18",
"name": "nsdr:82.45.15373.19440.091"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-c0b3fb93-8f91-4a5c-ac98-e67c02d0e5cb",
"name": "nsdr:82.45.15373.19439.952"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-dcc5d412-faa2-447f-929a-c6cb55684c64",
"name": "nsdr:82.45.15373.19439.482"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-f1cfff76-d963-472c-8020-0bc0c52550f8",
"name": "nsdr:82.45.15373.19439.811"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
}
],
"isFeatureOfInterestOf": [
{
"@id": "https://lab.scinfo.org.nz/elfie/id/om/om_observation/47-42-00001-1664-3110",
"@type": "om:OM_Observation",
"hasResult": {
"@id": "https://lab.scinfo.org.nz/elfie/def/nsdr/cl_concept/1048-140253",
"name": "Typic Orthic Allophanic Soil"
},
"resultTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"usedProcedure": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"phenomenonTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"observedProperty": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/property/soclassifier",
"name": "classifier"
},
"hasFeatureOfInterest": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00001",
"@type": "sams:SF_SpatialSamplingFeature"
}
},
{
"@id": "https://lab.scinfo.org.nz/elfie/id/om/om_observation/78-42-00001-1726",
"@type": "om:OM_Observation",
"hasResult": {
"uom": "degrees",
"@value": 0
},
"resultTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"usedProcedure": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"phenomenonTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"observedProperty": {
"@id": "https://lab.scinfo.org.nz/soil/def/property/slope-angle",
"name": "slope angle"
},
"hasFeatureOfInterest": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00001",
"@type": "sams:SF_SpatialSamplingFeature"
}
},
{
"@id": "https://lab.scinfo.org.nz/elfie/id/om/om_observation/88-42-00001-1669",
"@type": "om:OM_Observation",
"hasResult": {
"@value": "Poor pasture, hay paddock"
},
"resultTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"usedProcedure": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"phenomenonTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"observedProperty": {
"@id": "http://purl.org/dc/terms/description",
"name": "description"
},
"hasFeatureOfInterest": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00001",
"@type": "sams:SF_SpatialSamplingFeature"
}
}
]
},
"id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00001"
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
174.882920346082,
-37.0781263435251
]
},
"properties": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00002",
"@type": "sams:SF_SpatialSamplingFeature",
"name": "nsdr:55.42.00002",
"isSampleOf": {
"@id": "https://lab.scinfo.org.nz/elfie/id/soil/so_soil/47-55-42-00002-140253",
"@type": "soil:SO_Soil",
"name": null
},
"hasGeometry": {
"@type": "gsp:Geometry",
"asWKT": "SRID=4326;POINT(174.882920346082 -37.0781263435251)"
},
"hasSampleRelationship": [
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-00012",
"name": "nsdr:82.42.00012"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-10002a",
"name": "nsdr:82.42.10002a"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-10002b",
"name": "nsdr:82.42.10002b"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-42-10002c",
"name": "nsdr:82.42.10002c"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-0dc39d56-453b-428f-8133-388efaebe083",
"name": "nsdr:82.45.15373.19442.739"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-3c017728-5345-4552-8d3d-6cc2b843af45",
"name": "nsdr:82.45.15373.19442.461"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-5e08e3a0-7ce5-4c34-a654-176d8af8c615",
"name": "nsdr:82.45.15373.19442.602"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-baa2fc38-80bb-4cd2-b6ed-bdb454b5a999",
"name": "nsdr:82.45.15373.19443.031"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-ca6c63dd-2ae8-4352-b694-2e136da404bf",
"name": "nsdr:82.45.15373.19443.319"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-d8682ca6-14a2-49bd-89c7-683fcfa6629d",
"name": "nsdr:82.45.15373.19443.177"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-dda07e70-86b5-41ce-ae60-c23f6e6ec664",
"name": "nsdr:82.45.15373.19443.461"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
},
{
"relatedSample": {
"@id": "https://lab.scinfo.org.nz/elfie/id/spec/sf_specimen/82-lab-fdd2f2dd-57ae-447c-9d04-151619e5b548",
"name": "nsdr:82.45.15373.19442.881"
},
"natureOfRelationship": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/voc/sf-complex-role/sample",
"name": "sample"
}
}
],
"isFeatureOfInterestOf": [
{
"@id": "https://lab.scinfo.org.nz/elfie/id/om/om_observation/47-42-00002-1664-3110",
"@type": "om:OM_Observation",
"hasResult": {
"@id": "https://lab.scinfo.org.nz/elfie/def/nsdr/cl_concept/1048-140253",
"name": "Typic Orthic Allophanic Soil"
},
"resultTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"usedProcedure": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"phenomenonTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"observedProperty": {
"@id": "https://lab.scinfo.org.nz/soil-data-ie/def/property/soclassifier",
"name": "classifier"
},
"hasFeatureOfInterest": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00002",
"@type": "sams:SF_SpatialSamplingFeature"
}
},
{
"@id": "https://lab.scinfo.org.nz/elfie/id/om/om_observation/78-42-00002-1726",
"@type": "om:OM_Observation",
"hasResult": {
"uom": "degrees",
"@value": 0
},
"resultTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"usedProcedure": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"phenomenonTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"observedProperty": {
"@id": "https://lab.scinfo.org.nz/soil/def/property/slope-angle",
"name": "slope angle"
},
"hasFeatureOfInterest": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00002",
"@type": "sams:SF_SpatialSamplingFeature"
}
},
{
"@id": "https://lab.scinfo.org.nz/elfie/id/om/om_observation/88-42-00002-1669",
"@type": "om:OM_Observation",
"hasResult": {
"@value": "Rye grass-white clover pasture"
},
"resultTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"usedProcedure": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"phenomenonTime": {
"@id": "http://www.opengis.net/def/nil/OGC/0/unknown",
"name": "unknown"
},
"observedProperty": {
"@id": "http://purl.org/dc/terms/description",
"name": "description"
},
"hasFeatureOfInterest": {
"@id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00002",
"@type": "sams:SF_SpatialSamplingFeature"
}
}
]
},
"id": "https://lab.scinfo.org.nz/elfie/id/sams/sf_spatialsamplingfeature/55-42-00002"
}
],
"links": [
{
"type": "application/geo+json",
"rel": "alternate",
"title": "This document as GeoJSON",
"href": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures/items?f=json"
},
{
"rel": "self",
"type": "application/ld+json",
"title": "This document as RDF (JSON-LD)",
"href": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures/items?f=jsonld"
},
{
"type": "text/html",
"rel": "alternate",
"title": "This document as HTML",
"href": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures/items?f=html"
},
{
"type": "application/geo+json",
"rel": "prev",
"title": "items (prev)",
"href": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures/items/?startindex=0"
},
{
"type": "application/geo+json",
"rel": "next",
"title": "items (next)",
"href": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures/items/?startindex=10"
},
{
"type": "application/json",
"title": "Landcare Spatial Sampling Features",
"rel": "collection",
"href": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures"
}
],
"timeStamp": "2019-10-04T00:45:35.631872Z",
"id": "http://172.19.0.2:5000/collections/sf_spatialsamplingfeatures/items"
} (Note, the The ordinary GeoJSON FeatureCollection view is fine, and the collection renders successfully as HTML (with embedded JSON-LD), as expected: The response is still fast. The one "trick" here really is that each feature has an All that to say, I'm so far pretty happy with this experiment and once again thanks for a great project. |
@alpha-beta-soup if you are happy with the experiment, then I am in ecstasy with the jsonl-ld SQL generation |
I noticed the idea of referencing an external context file to define the ontology of a featuretype. I like the idea from a perspective of introducing ontology without the complexities of managing rdf output. On the other hand, I also like the getting-started-with-linked-data approach in ldproxy, which allows users to annotate feature attributes with concepts from an ontology (such as schema.org) and the tool generating the relevant context. But sure, embedding an external context is also far more powerfull in the long run. A relevant question could be if we should consider to at some point also support other rdf encodings, such as rdf+xml, ttl? If so, does it make sense to use json-ld as a base and generate other encodings from that? |
To-do:
|
I'm happy with this PR now. There's more that could be considered, but I think this is a minimum loveable implementation of structured data that is compatible with the core of pygeoapi, that affords a great deal of power without sacrificing anything. I'll just go through a few remaining issues and comments that I think are worth mentioning. I don't know how best to describe the
I don't know whether pygeoapi should support other encodings; I also don't know how that should be supported. I would err on the side of supporting only JSON-LD for RDF. Automated converters could do the rest for a client who insists on consuming In the JSON-LD tests, so far there's only a test for the I'm still unsure whether the features in a collection should be assigned an |
Something else to be updated when/if this is merged: https://github.com/geopython/pygeoapi/wiki/SEO |
@@ -56,7 +56,7 @@ metadata: | |||
- data | |||
- api | |||
keywords_type: theme | |||
terms_of_service: None | |||
terms_of_service: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except for the null
and context def updates, can you revert the rest of the changes in this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but can I request that we leave the hours of service one? I wanted to emphasise that the value there is not just unstructured text, but rather will be interpreted: https://schema.org/openingHours
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also a value of Lastname, Firstname
suggests a structure that isn't there, suggest changing that to Full Name
as in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lastname, Firstname is as per ISO 19115 rules. Is there a fullname property we can model after on schema.org?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, in that case Lastname, Firstname
is fine. It's merely that https://schema.org/name is just unadulterated text, doesn't imply a person, etc. Happy to leave as is.
Thank you @alpha-beta-soup for this awesome feature! Perhaps document your description of this functionality (as you described on gitter) in the docs in a subsequent PR if you have time. Thanks again for your patience and addressing the PR comments/iterations. |
* Changes to sample pygeopai-config.yml - improved terms_of_service and contact.url for SEO - fixes typo for contact.instructions * whitespace * dynamically embed JSON-LD representation in head * json-led representation for root, collections, and collection * updates sample configuration with metadata that won't cause JSON-LD validation issues * adds support for feature-level JSON-LD representations of collections and items * improves consistency between microdata and JSON-LD root metadata * valid Dataset spatial; and better use of @id * role → instructions * adds jsonld f param to open api definition * working with new temporal extent interface * removes comment * identify and retain NIR/HTTP IDs rather than construct them with reference to pygeoapi * don't pop a feature.properties id, to retain JSON-LD reference integrity * add/update tests for inclusion of json-ld responses * bug fixes * fix bug where id was assumed to be string * better url checking * renames format variable to avoid reserved word * removes top-level CRS in obs sample config * add json-ld textMimeType for serverless * adds flask-cors dependency to allow cors * moves json-ld-requesting script to bottom of body * corrects schema.org url * fixes urls * adds pyld dependency * adds tests for json-ld representations (incomplete) * more tests * make pyld a dev requirement only * linting * changes from revision * removes merge artifact
This is something I've been considering for a little while. It's great for SEO that pygeoapi includes microdata read from the config in the HTML templates. However I think we could do one better by supporting JSON-LD representations of the API itself, and also extensible GeoJSON-LD representations of actual data.
I had a few goals in mind here:
head
, so that spatial data is indexable by search engines.?f=jsonld
format endpoint, so the JSON-LD data can also be obtained independently, with a response of typeapplication/ld+json
.geojson:Feature
) and collections (geojson:FeatureCollection
) themselves.@context
annotation for describing features unambiguously. e.g. in the samplecollections/obs
collection features, what dovalue
,datetime
, andstn_id
represent? At present there's no way to describe these within pygeoapi. Can we allow a data provider to describe this without touching their input data?This could be taken a lot further, and I'm not proposing that that's a good idea for the core of pygeoapi. However, I think these goals are good, and generic, and that what I have here is a good first attempt at achieving them. I hope this generates some useful discussion and thank you for the consideration.
I really wasn't sure the best way to implement this, and am not at all attached to the implementation. I'm willing to re-implement it in whichever way you advise, if indeed you think conceptually this is, in whole or in part, a good idea.
API root
/collections
JSON-LD representation
/collections/obs
Note that here I have included an additional property in the dataset configuration, which, in its entirety, looks like:
The only additional part is the
context
. It produces a JSON-LD representation like this:The GeoJSON default vocabulary is included compulsorily.
If we compact that with the JSON-LD Playground, we get:
Thus we can see finally that what was originally
datetime
in the input CSV is now fully elaborated ashttps://schema.org/DateTime
.