Skip to content

02 Tilgang til ontologien

Sigve Martin Pettersen edited this page May 11, 2020 · 22 revisions

Nedladdning

Nedladdning av ontologi kan göras genom att använda ontologins URI, dvs http://rdf.vegdata.no/V440/v440-owl eller http://rdf.vegdata.no/V440/v440-brudata-owl med header accept = text/turtle (när andra format, t ex json-ld finns tillgängliga ska även motsvarande accept-headers fungera). Om man bara använder URI:n i en web-läsare eller använder accept = text/html fås en html-representation av ontologin. Ett kodexempel för nedladdning med C# visas nedan.

Eksempel fra nedlastning i Protegé

  1. Last ned og installer Protegé
  2. Åpne og velg Open from URL fra File menyen (NB Ikke velg bare Open når du skal hente fra URI).

protege1

  1. Lim inn "http://rdf.vegdata.no/V440/v440-owl.ttl" som URI i adressefeltet (NB hvis du ikke spesifiserer .ttl fil vil du få en feilmelding.)

protege1

SPARQL endpoint

Ett triple-store finns här: http://rdfspatial.vegdata.no:7200/. Denna ger läsåtkomst via ett web-gränssnitt. Om man ska komma åt motsvarande SPARQL endpoint från ett program så gäller att man kompletterar URI:n med "/repositories/v440" . Den fullständiga URI:n för denna SPARQL endpoint blir alltså http://rdfspatial.vegdata.no:7200/repositories/V440.

SPARQL-protokollet körs över http och beskrivs bland annat här: https://www.w3.org/TR/sparql11-protocol/

Några typiska SPARQL-frågor

Nedan följer ett antal frågor som ger en uppfattning om hur ontologin kan hanteras via SPARQL.

Alla klasser som är byggverk (alla nivåer - för endast första nivån ta bort '+')

prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix owl: <http://www.w3.org/2002/07/owl#>
prefix onto: <http://www.ontotext.com/>
prefix v440: <http://rdf.vegdata.no/V440/v440-owl#>
prefix v440bd: <http://rdf.vegdata.no/V440/v440-brudata-owl#>
select distinct ?class ?code ?name
where
{
	?class rdfs:subClassOf+ v440:Byggverk .
	?class rdfs:label ?name .
	?class v440:kode ?code .
}

Hitta alla (relevanta) properties för en klass (exempel för v440:Vegbru)

prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix owl: <http://www.w3.org/2002/07/owl#>
prefix onto: <http://www.ontotext.com/>
prefix v440: <http://rdf.vegdata.no/V440/v440-owl#>
prefix v440bd: <http://rdf.vegdata.no/V440/v440-brudata-owl#>
select distinct ?property ?name ?type
where
{
    ?property rdfs:domain ?class .
    ?property rdfs:label ?name .
    v440:Vegbru rdfs:subClassOf* ?class.
    OPTIONAL {?property rdfs:range ?type .}
    OPTIONAL {?property rdfs:subPropertyOf*/rdfs:range ?type .}
}

Hitta alla (relevanta) properties för ett komplext property (exempel för v440bd:Vegidentifisering)

prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix owl: <http://www.w3.org/2002/07/owl#>
prefix onto: <http://www.ontotext.com/>
prefix v440: <http://rdf.vegdata.no/V440/v440-owl#>
prefix v440bd: <http://rdf.vegdata.no/V440/v440-brudata-owl#>
select distinct ?property ?name ?descr ?type
where
{
    ?property rdfs:domain ?class .
    ?property rdfs:label ?name .
    ?property rdfs:comment ?descr .
    v440bd:Vegidentifisering rdfs:subClassOf* ?class.
    OPTIONAL {?property rdfs:range ?type .}
    OPTIONAL {?property rdfs:subPropertyOf*/rdfs:range ?type .}
    OPTIONAL {?type rdf:type ?dt .}
}

Hämta innehåll i kodlista (exempel för v440:Vegdata_Kode_for_vegkategori)

prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix owl: <http://www.w3.org/2002/07/owl#>
prefix onto: <http://www.ontotext.com/>
prefix v440: <http://rdf.vegdata.no/V440/v440-owl#>
prefix v440bd: <http://rdf.vegdata.no/V440/v440-brudata-owl#>
select distinct ?uri ?code ?name
where
{
    ?uri a v440:Vegdata_Kode_for_vegkategori .
    ?uri v440:kode ?code.
    ?uri rdfs:label ?name.
}

Kodexempel (C#)


Inledning

För C# finns ett antal bibliotek. Ett av dessa är dotNetRDF (https://github.com/dotnetrdf/dotnetrdf) som fungerar bra, har bra dokumentation och en snäll licens (MIT). De exempel som visas nedan använder sig av detta bibliotek.

Hämtning av fil

    private static void GetV440()
    {
        try
        {
            //System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
            var baseAddress = new Uri("http://rdf.vegdata.no/V440/v440-owl");
            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("accept", "text/turtle");
                HttpResponseMessage response = client.GetAsync("http://rdf.vegdata.no/V440/v440-owl").Result;
                string result = response.Content.ReadAsStringAsync().Result;
                TurtleParser ttlparser = new TurtleParser();
                IGraph g = new Graph();
                StringReader sr = new StringReader(result);
                ttlparser.Load(g, sr);
                foreach (Triple t in g.Triples)
                {
                    Console.WriteLine(t.ToString());
                }
                Console.ReadLine();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

Anrop av SPARQL endpoint

    private static void TestQuerySVVV440()
    {
        try
        {
            //Define a remote endpoint
            SparqlRemoteEndpoint endpoint = new SparqlRemoteEndpoint(new Uri("http://rdfspatial.vegdata.no:7200/repositories/V440"));
            //Make a SELECT query against the Endpoint
            SparqlResultSet results = endpoint.QueryWithResultSet("SELECT ?Class FROM <http://www.ontotext.com/explicit> WHERE {?Class rdfs:subClassOf <http://rdf.vegdata.no/V440/v440-owl#Byggverk>}");
            Console.WriteLine("Subklasser till Byggverk...");
            foreach (SparqlResult result in results)
            {
                Console.WriteLine(result.Value("Class"));
            }
            Console.WriteLine("DESCRIBE v440:Vegbru...");
            IGraph g = endpoint.QueryWithResultGraph("DESCRIBE <http://rdf.vegdata.no/V440/v440-owl#Vegbru>");
            foreach (Triple t in g.Triples)
            {
                Console.WriteLine(t.ToString());
            }
            Console.ReadLine();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

Nedan visas även ett exempel på anrop av den SPARQL endpoint som erbjuds av DBPedia (Wikipedia):

    private static void TestQueryDBPedia()
    {
        try
        {
            //Define a remote endpoint
            //Use the DBPedia SPARQL endpoint with the default Graph set to DBPedia
            SparqlRemoteEndpoint endpoint = new SparqlRemoteEndpoint(new Uri("http://dbpedia.org/sparql"), "http://dbpedia.org");

            //Make a SELECT query against the Endpoint
            SparqlResultSet results = endpoint.QueryWithResultSet("SELECT DISTINCT ?Concept WHERE {[] a ?Concept}");
            foreach (SparqlResult result in results)
            {
                Console.WriteLine(result.ToString());
            }
            Console.ReadLine();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

SPARQL-spørring med dotNetRDF for å hente alle koder for klasser som arver fra Byggverkselement laget av @espenshi fra EDR Medeso

using VDS.RDF;
using VDS.RDF.Query;
using VDS.RDF.Query.Builder;
public void GetV440Codes()
{
    SparqlRemoteEndpoint endpoint = new SparqlRemoteEndpoint(new Uri("http://rdfspatial.vegdata.no:7200/repositories/V440"));

    var prefixes = new NamespaceMapper(true);
    prefixes.AddNamespace("rdfs", new Uri("http://www.w3.org/2000/01/rdf-schema#"));
    prefixes.AddNamespace("owl", new Uri("http://www.w3.org/2002/07/owl#"));
    prefixes.AddNamespace("v440", new Uri("http://rdf.vegdata.no/V440/v440-owl#"));
    prefixes.AddNamespace("v440bd", new Uri("http://rdf.vegdata.no/V440/v440-brudata-owl#"));

    string uri = "uri";
    string code = "code";
    string name = "name";
    var queryBuilder = QueryBuilder
        .Select(new string[] { uri, code, name })
        .Where(
            (triplePatternBuilder) =>
            {
                triplePatternBuilder
                    .Subject(uri)
                    .PredicateUri("rdfs:subClassOf+")
                    .Object(new Uri("http://rdf.vegdata.no/V440/v440-owl#Byggverkselement"));
                triplePatternBuilder
                    .Subject(uri)
                    .PredicateUri("rdfs:label")
                    .Object(name);
                triplePatternBuilder
                    .Subject(uri)
                    .PredicateUri("v440:kode")
                    .Object(code);
            });
    queryBuilder.Prefixes = prefixes;

    var query = queryBuilder.BuildQuery().ToString();

    SparqlResultSet results = endpoint.QueryWithResultSet(query);
            
    foreach (SparqlResult result in results)
    {
        Console.WriteLine($"URI: {result.Value(uri).ToString()} Code: {result.Value(code).ToString()} Label: {result.Value(name).ToString()}");
    }
}

Dette skal resultere i følgende query:

PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX v440: <http://rdf.vegdata.no/V440/v440-owl#>
PREFIX v440bd: <http://rdf.vegdata.no/V440/v440-brudata-owl#>

SELECT ?uri ?code ?name WHERE
{ 
  ?uri rdfs:subClassOf+ v440:Byggverkselement . 
  ?uri rdfs:label ?name . 
  ?uri v440:kode ?code . 
}

Kode eksempel (Python)

Introduksjon

Det finnes også mange biblioteker for å jobbe med ontologier i Python. Eksempler er RDFLib og Owlready som lar deg lage og jobbe med ontologier, samt egne biblioteker for å jobbe med sparql som eks. SparqlWrapper. Man kan også bruke kjente HTTP biblioteker slik som Requests.

I følgende eksempler brukes sistnevnte.

Lage en funksjon som søker v440 for et spesifikt konsept.

Den følgende koden er en funksjon som spør v440 endepunktet etter en spesifikk label.

def get_v440_uris(label):
    url = "http://rdfspatial.vegdata.no:7200/repositories/V440"
    query ="""SELECT ?uri ?label
                WHERE {
                ?uri rdfs:label ?label .
                FILTER(?label="%s"@no)
                }"""%label
    r = requests.get(url, params = {"Accept": "application/json", 'query': query})
    return r.json()["results"]["bindings"]

Kjører man denne med eks. "Underbygning" får man følgende resultat

get_v440_uris("Underbygning")

[{'label': {'xml:lang': 'no', 'type': 'literal', 'value': 'Underbygning'},
  'uri': {'type': 'uri',
   'value': 'http://rdf.vegdata.no/v440-owl#Underbygning'}},
 {'label': {'xml:lang': 'no', 'type': 'literal', 'value': 'Underbygning'},
  'uri': {'type': 'uri',
   'value': 'http://rdf.vegdata.no/V440/v440-owl#Underbygning'}}]

Hvis man bruker denne og spør om "Søyle" får man ingenting tilbake. Modifiserer man den, får man langt flere svar.

def get_v440_uris(label):
    url = "http://rdfspatial.vegdata.no:7200/repositories/V440"
    query ="""SELECT ?uri ?label
                WHERE {
                ?uri rdfs:label ?label .
                FILTER(STRSTARTS(?label,"%s"))
                }"""%label
    r = requests.get(url, params = {"Accept": "application/json", 'query': query})
    return r.json()["results"]["bindings"]

Kjører og tester med "Søyle" som input

get_v440_uris("Søyle")

[{'label': {'xml:lang': 'no', 'type': 'literal', 'value': 'Søyle/skive'},
  'uri': {'type': 'uri',
   'value': 'http://rdf.vegdata.no/v440-owl#Pilar_Soyle_skive'}},
 {'label': {'xml:lang': 'no', 'type': 'literal', 'value': 'Søyle/skive'},
  'uri': {'type': 'uri',
   'value': 'http://rdf.vegdata.no/V440/v440-owl#Pilar_Soyle_skive'}}]

Länkning av andra ontologier

Inledning

Som stöd för att kunna "konfigurera" mappningar mellan olika ontologier finns ett antal standardiserade definitioner i owl och rdfs. Data (tripplar) som beskriver mappningar mellan ontologier bör helst läggas som separata ontologier som brukar kallas Alignment ontologies eller Linking Rule Sets (LRS).

Idealt så har man tillgång till en programvara som klarar att tolka dessa beskrivningar, en så kallad reasoner, vilket innebär att man kan få en automatisk mappning att ske baserat på informationen i ett LRS. Eftersom även dessa mappningar uttrycks som tripplar i en ontologi så har man naturligtvis möjlighet att utföra denna "reasoning" på egen hand baserat på den definierade logiken.

Mycket kortfattat går reasoning ut på att, baserat på definierad logik, härleda nya tripplar (inferred triples) baserat på redan existerande tripplar (asserted triples). Som ett enkelt exempel kan vi ha följande definierade tripplar (Asserted triples).

Asserted triples - Dataset:

:obj_1 rdf:type ont1:MyClass .
:obj_1 ont1:myProperty "123" .

Asserted triples - Linking Rule Set:

ont1:MyClass owl:equivalentClass ont2:AnotherClass .
ont1:myProperty owl:equivalentProperty ont2:AnotherProperty .

En reasoner kan baserat på ovanstående härleda följande.

Inferred triples:

:obj_1 rdf:type ont2:AnotherClass .
:obj_1 ont2:AnotherProperty "123" .

Den programvara som för närvarande används som triple store för v440 har inbyggd reasoning. Denna aktiveras eller avaktiveras i program genom att man lägger till följande i sin SPARQL-fråga:

SELECT ?x **FROM** **<http://www.ontotext.com/explicit>** WHERE... : returnerar de tripplar som fördefinierats i databasen (asserted triples)

SELECT ?x FROM http://www.ontotext.com/implicit WHERE... : returnerar de tripplar som härletts (inferred triples)

Motsvarande funktionalitet finns i web-gränssnittet om man klickar på den ikon som markerats i bilden nedan:

Många triple stores stödjer reasoning. Det C#-bibliotek som refereras ovan har ett interface (IInferenceEngine). För närvarande finns implementation av rdfs-, skos- och "simple N3"-resoning.

Man kan även använda SPARQL CONSTRUCT (https://www.w3.org/TR/rdf-sparql-query/#construct) eller exempelvis SPIN (https://spinrdf.org/) för att göra mappningar, men det beskrivs inte närmare här.

Några vägar för länkning av ontologier

Nedan följer några sätt som kan användas för att länka klasser och egenskaper mellan ontologier.

owl:equivalentClass : anger att subjektklass och objektklass har samma extension. Används för att ange att två olika klasser är ekvivalenta. OBS att en klass i OWL också kan definieras som en egenskaps-restriktion med hjälp av owl:Restriction (t ex alla Byggverkselement med material=Stål) vilket innebär att det går att definiera mappningar även baserat på properties.

Exempel: A owl:equivalentClass B betyder att en instans som tillhör A (eller B) även tillhör B (eller A)

rdfs:subClassOf : anger att en instans av subklassen (subjektet) även är en instans av basklassen (objektet). Detta kan t ex användas när man har en mera detaljerad klassdefinition, t ex i ett projekt, av ett objekt som finns definierad i v440 och vill kunna mappa till det närmaste begreppet i v440.

Exempel : A rdfs:subClassOf B betyder att en instans som tillhör A även tillhör B. Däremot kan man inte säga att alla B tillhör A.

Motsvarande kan även göras för properties.

owl:equivalentProperty : anger att subjektproperty och objektproperty har samma extension

Exempel : A owl:equivalentProperty B betyder att A och B är ekvivalenta och att alla resurser som länkats med A även är länkade med B eller vice versa.

rdfs:subPropertyOf : anger att alla resurser som länkats med subjektpropertyt även är länkade med objektpropertyt

Exempel : A rdfs:subPropertyOf B betyder att alla resurser som länkats med A även är länkade med B. Däremot kan man inte säga att alla som länkats med B även är länkade med A.

För att länka instanser kan man beroende på omständigheterna definiera egna properties, om länkarna har en specifik semantik. Ett property som finns som standard i OWL är owl:sameAs som indikerar att subjekt och objekt refererar till samma sak.

Exempel : A owl:sameAs B betyder att A och B refererar till samma sak, t ex en Vegbru i system A och en Vegbru i system B.

owl:sameAs kan även användas för att länka innehållet i kodlistor i v440 (som utgörs av instanser) med motsvarande definitioner någon annanstans.

OBS att ovanstående fungerar så länge man kan hitta enkla mappningar mellan objekt eller egenskaper. I mera komplicerade fall, t ex att det property man ska mappa till är sammansatt av eller beräknas från flera properties så behöver man använda t ex SPARQL CONSTRUCT eller SPIN. En fördel med SPIN är att det bygger på SPARQL men kan lagras som tripplar vilket innebär att mappningsdefinitionerna ligger som data och inte i kod.

Förhoppningsvis kommer det att finnas något eller några mindre exempel på mappningar tillgängliga på samma server som ontologierna.

Vad kan detta vara bra för?

Naturligtvis kan det finnas fördelar i att mappningsdefinitioner definieras på ontologi-nivå och därmed blir konfigurerbara istället för hårdkodade i applikationer.

Uppenbara use-cases för detta kan t ex vara:

  • Mappningar mellan olika standarder, t ex v440 och IFC eller v440 och NVDB
  • Mappningar mellan standard och egen programvara
    • T ex om man har egna begrepp för Byggverk etc så kan det vara till hjälp att definiera hur dessa hänger samman med exempelvis v440