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",