diff --git a/Makefile b/Makefile index 9aabeda6..564ad887 100644 --- a/Makefile +++ b/Makefile @@ -45,3 +45,10 @@ cache/ssif-2025.csv: cache/Nyckel_SSIF2011_SSIF2025_digg.xlsx # Fix incomplete change type sed -i '/^60411.*\tBytt benämning\t/ s/Bytt benämning/Ny kod, Bytt benämning/' "cache/Nyckel_SSIF2011_SSIF2025_digg-Nyckel SSIF2011-SSIF25.csv" cp "cache/Nyckel_SSIF2011_SSIF2025_digg-Nyckel SSIF2011-SSIF25.csv" $@ + +cache/geocore.ttl: cache/remote-geocore.ttl + cat $^ | scripts/fmt.sh > $@ + #scripts/construct.py source/geo/modify-geocore.rq $^ > $@ + +cache/remote-geocore.ttl: source/geo/construct-geocore.rq + scripts/rq.sh https://query.wikidata.org/sparql $^ > $@ diff --git a/lxltools/datacompiler.py b/lxltools/datacompiler.py index 22dbb3b8..31ef4163 100644 --- a/lxltools/datacompiler.py +++ b/lxltools/datacompiler.py @@ -178,8 +178,12 @@ def _compile_dataset(self, name, result): meta = node.pop('meta', None) if meta: + if meta.get('@id', "").startswith('_:'): + del meta['@id'] + if 'created' in meta: created_ms = timeutil.w3c_dtz_to_ms(meta.pop('created')) + if 'modified' in meta: modified_ms = timeutil.w3c_dtz_to_ms(meta.pop('modified')) diff --git a/lxltools/timeutil.py b/lxltools/timeutil.py index 4b829cc4..8d34bbb2 100644 --- a/lxltools/timeutil.py +++ b/lxltools/timeutil.py @@ -25,4 +25,4 @@ def to_w3c_dtz(ms: float): def to_http_date(s: float): - return datetime.utcfromtimestamp(s).strftime('%a, %d %b %Y %H:%M:%S GMT') + return datetime.fromtimestamp(s, tz=timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT') diff --git a/scripts/fmt.sh b/scripts/fmt.sh new file mode 100755 index 00000000..cc4d40bf --- /dev/null +++ b/scripts/fmt.sh @@ -0,0 +1,2 @@ +#!/bin/bash +trld -ittl -e -f -c $(dirname $0)/../build/sys/context/kbv.jsonld -B -ottl | sed 's/rdf:type/a/' diff --git a/scripts/rq.sh b/scripts/rq.sh new file mode 100755 index 00000000..637a0579 --- /dev/null +++ b/scripts/rq.sh @@ -0,0 +1,5 @@ +#!/bin/bash +endpoint=$1 +queryfile=$2 + +curl -s $endpoint -HAccept:text/turtle --data-urlencode "query@$queryfile" diff --git a/source/construct-libraries.rq b/source/construct-libraries.rq index 39e9a43c..68db048e 100644 --- a/source/construct-libraries.rq +++ b/source/construct-libraries.rq @@ -8,85 +8,86 @@ base construct { ?library a ?libtype ; - :meta _:meta ; + :meta ?meta ; owl:sameAs ?bdb_id, ?sameas ; :sigel ?sigel ; :name ?name ; :url ?url ; :qualifier ?dept ; - :category ?registranturl . - #:place ?place . - - _:meta :created ?created ; :modified ?modified . - - # TODO: Model a proper place/adress relation. - # ?place a :Place ; - # :label ?region ; - # :code ?municipality_code ; - # :isPartOf ?country ; - # :latitude ?lat ; - # :longitude ?long . - # - # ?country a :Country ; - # :code ?country_code . + :category ?registranturl ; + :locatedIn ?municipality ; + :geo ?geo ; + :country ?country . + + ?meta :created ?created ; :modified ?modified . + + ?geo :latitude ?lat ; :longitude ?long . } where { ?bdb_id bibdb:sigel ?sigel ; sdo:name ?name . - # :organisation, sdo:address - optional { - ?bdb_id bibdb:libris_reg ?reg . filter ( ?reg = true ) - bind (iri('https://id.kb.se/term/bibdb/Registrant') as ?registranturl) - } + bind(encode_for_uri(replace(str(?sigel), "\\s+", "")) as ?sigelslug) - optional { - ?bdb_id bibdb:dept ?dept - } + optional { ?bdb_id a ?type } + bind(if(?type = sdo:Library, :Library, :Bibliography) as ?libtype) - optional { - ?bdb_id bibdb:date_created ?raw_created . - bind(concat(?raw_created, '.000Z') as ?created) - } - optional { - ?bdb_id bibdb:date_modified ?raw_modified . - bind(concat(?raw_modified, '.000Z') as ?modified) - } + bind(iri(concat(str(), ?sigelslug)) as ?library) - bind(encode_for_uri(replace(str(?sigel), "\\s+", "")) as ?sigelslug) + { - optional { ?bdb_id a ?type } + optional { ?bdb_id :organisation ?org } + optional { ?bdb_id bibdb:dept ?dept } + optional { + ?bdb_id bibdb:libris_reg ?reg . filter ( ?reg = true ) + bind ( as ?registranturl) + } + + optional { + ?bdb_id bibdb:date_created ?raw_created . + bind(concat(?raw_created, '.000Z') as ?created) + } + optional { + ?bdb_id bibdb:date_modified ?raw_modified . + bind(concat(?raw_modified, '.000Z') as ?modified) + } + bind(bnode(coalesce(?created, ?modified)) as ?meta) + + optional { + ?bdb_id sdo:url ?url . + FILTER(?url != "" && ?url != "http://") + } + + optional { + ?bdb_id bibdb:country_code ?country_code . + FILTER(?country_code != "") + } + # TODO: tr, fi, ... + bind(if(?country_code = 'se', , ?NO_country) as ?country) + + } union { + + ?bdb_id sdo:address [ + sdo:streetAddress ?streetAddress ; + sdo:addressLocality ?city ; + sdo:postalCode ?zipCode + ] . + FILTER(!STRSTARTS(?streetAddress, "FE ") && !STRSTARTS(?streetAddress, "FE ")) + + } union { + + ?bdb_id bibdb:municipality_code ?municipality_code . + FILTER(?municipality_code not in ('', '-')) + bind(IRI(CONCAT(STR(), ?municipality_code)) as ?municipality) + + } union { + + ?bdb_id sdo:latitude ?lat ; sdo:longitude ?long . + FILTER(?lat > 0 && ?long > 0) + bind(concat('POINT(', STR(?long), ' ', STR(?lat), ')') as ?geo) - optional { - ?bdb_id sdo:url ?url . - FILTER(?url != "" && ?url != "http://") } - bind(if(?type = sdo:Library, :Library, :Bibliography) as ?libtype) - # optional { - # ?bdb_id bibdb:country_code ?country_code . - # FILTER(?country_code != "") - # } - # - # optional { - # ?bdb_id bibdb:municipality_code ?municipality_code . - # FILTER(?municipality_code != "") - # } - # - # optional { - # ?bdb_id bibdb:region ?region - # } - # - # optional { - # ?bdb_id sdo:latitude ?lat ; sdo:longitude ?long . - # FILTER(?lat > 0 && ?long > 0) - # } - - # TODO: coalesce should not be necessary here, due to the else clause. RDFLib bug? - bind(iri(concat(str(coalesce(?uribase, )), ?sigelslug)) as ?library) - - # bind(if(bound(?region) || bound(?lat), bnode(), ?NO_place) as ?place) - # bind(if(bound(?place) && bound(?country_code), bnode(), ?NO_country) as ?country) } diff --git a/source/datasets/geo.ttl b/source/datasets/geo.ttl new file mode 100644 index 00000000..72ae6053 --- /dev/null +++ b/source/datasets/geo.ttl @@ -0,0 +1,8 @@ +prefix xsd: +prefix : +base + + a :Dataset ; + :sourceData [ :uri "cache/geocore.ttl" ] ; + :uriSpace "https://libris.kb.se/dataset/geo/" ; + :created "2024-03-07T12:09:24Z"^^xsd:dateTime . diff --git a/source/geo/construct-geocore.rq b/source/geo/construct-geocore.rq new file mode 100644 index 00000000..7636f309 --- /dev/null +++ b/source/geo/construct-geocore.rq @@ -0,0 +1,88 @@ +BASE +PREFIX : +PREFIX idkbse: +PREFIX rdfs: +PREFIX wdt: +PREFIX wd: +PREFIX p: +PREFIX pq: +PREFIX ps: +PREFIX psv: +PREFIX wikibase: + +CONSTRUCT { + + ?type a :Concept ; :label ?typelabel . + + ?place a :Place ; + :sameAs ?sameAs ; + :label ?label ; + :category ?type ; + :code ?swedishMunicipalityCode ; + :geo ?geo ; + :locatedIn ?locatedIn ; + :principalPlace ?capital ; + :country ; + :startDate ?foundingDate ; + :image ?image . + + ?geo a :EarthGlobeCoordinates ; + :value ?geocode ; + :geoLatitude ?lat ; + :geoLongitude ?long ; + :geoPrecision ?geoPrecision . + +} WHERE { + ?wdplace a|wdt:P31 ?type ; + wdt:P17 wd:Q34 . # :country Sweden + + FILTER EXISTS { + { + ?type wdt:P279* wd:Q914262 # administrative territorial entity of Sweden + } UNION { + ?type wdt:P279* wd:Q12813115 # urban area of Sweden + } + } + + ?wdplace rdfs:label ?label . + + FILTER NOT EXISTS { ?wdplace wdt:P576 ?dissolutionDate } + OPTIONAL { ?wdplace wdt:P571 ?foundingDate } + + ?type rdfs:label ?typelabel . FILTER(lang(?typelabel) IN ('sv', 'en')) + + FILTER(lang(?label) IN ('sv', 'en')) + + OPTIONAL { + ?wdplace wdt:P625 ?geocode . # ^^:wktLiteral + OPTIONAL { + ?wdplace p:P625 ?qualifiedgeo . + ?qualifiedgeo psv:P625 [ + wikibase:geoLatitude ?lat ; + wikibase:geoLongitude ?long ; + wikibase:geoPrecision ?geoPrecision ] . + } + BIND(BNODE(STR(COALESCE(?qualifiedgeo, ?geocode))) AS ?geo) + } + + ?wdplace wdt:P131 ?wdLocatedIn . + OPTIONAL { + #?wdplace wdt:P525 ?swedishMunicipalityCode . + ?wdplace p:P525 ?qualifiedP525 . + ?qualifiedP525 ps:P525 ?swedishMunicipalityCode . + FILTER NOT EXISTS { ?qualifiedP525 pq:P582 ?endDate } + BIND(IRI(CONCAT(STR(), ENCODE_FOR_URI(?swedishMunicipalityCode))) AS ?dsId) + BIND(?wdplace AS ?sameAs) + } + + OPTIONAL { + ?wdplace wdt:P36 ?capital . + # :capital :label "centralort"@sv + # (Cf. wdt:P1376 == huvudstad.) + } + + BIND(IF(?wdLocatedIn = wd:Q34, idkbse:country\/sw, ?wdLocatedIn) AS ?locatedIn) + BIND(IF(BOUND(?dsId), ?dsId, ?wdplace) AS ?place) + + OPTIONAL { ?wdplace wdt:P18 ?image } +} diff --git a/source/vocab/details.ttl b/source/vocab/details.ttl index b67cbb9a..43988fde 100644 --- a/source/vocab/details.ttl +++ b/source/vocab/details.ttl @@ -21,6 +21,8 @@ @prefix rdaz: . @prefix rdau: . +@prefix wikibase: . + @prefix : . @prefix kbrel: . @prefix bulk: . @@ -1540,6 +1542,31 @@ rdfs:label "Geographic coverage"@en, "Geografisk täckning"@sv; owl:equivalentClass bf2:GeographicCoverage . +:geo a owl:ObjectProperty; + rdfs:label "Geographic coordinates"@en, "Geografiska koordinater"@sv; + sdo:domainIncludes :Place, :Library; + rdfs:range :GlobeCoordinates; + owl:equivalentProperty sdo:geo . + +:GlobeCoordinates a owl:Class ; + rdfs:subClassOf sdo:GeoCoordinates, wikibase:GlobecoordinateValue . + +:geoLatitude owl:equivalentProperty sdo:latitude, wikibase:geoLatitude . +:geoLongitude owl:equivalentProperty sdo:longitude, wikibase:geoLongitude . +:geoPrecision owl:equivalentProperty wikibase:geoPrecision . + +:EarthGlobeCoordinates a owl:Class ; + rdfs:label "Earth globe coordinates"@en, "jordglobskoordinater"@sv; + rdfs:subClassOf :GlobeCoordinates, [ a owl:Restriction; + owl:onProperty wikibase:geoGlobe ; + owl:hasValue ] . + +:principalPlace a owl:ObjectProperty; + rdfs:label "Principal place"@en, "Centralort"@sv; + rdfs:domain :Place; + rdfs:range :Place . + #owl:equivalentProperty wdt:P36 . + :geographicCoverage a owl:ObjectProperty; rdfs:label "Geographic coverage"@en, "Geografisk täckning"@sv; rdfs:domain :Creation; diff --git a/source/vocab/display.jsonld b/source/vocab/display.jsonld index 42afffc9..b5694d97 100644 --- a/source/vocab/display.jsonld +++ b/source/vocab/display.jsonld @@ -843,7 +843,7 @@ "@id": "Place-cards", "@type": "fresnel:Lens", "classLensDomain": "Place", - "showProperties": [ "prefLabel", "locatedIn", "country", "description" ] + "showProperties": [ {"alternateProperties": ["prefLabel", "label"]}, "locatedIn", "country", "description" ] }, "DescriptionConventions": { "@id": "DescriptionConventions-cards",