please note that this technical note is valid only for Snap4City solutions installed on-premise and not on this cloud https://www.snap4city.org in which you can use the provided services, please ask to snap4city@disit.org
Currently when using IotApp you may need to upload on the KB some triples for data that are currently managed on the KB as Events, POIs, Weather predictions etc.
Currently to add data on virtuoso you can use the REST interface of virtuoso that is not accessible from the IOTApps running on the snap4city cloud but ONLY on local installations. In the future a specific nodered block/node (microservice) will be provided.
For ONPREMISE versions
The triples should be sent in POST to url http://<virtuoso>:8890/sparql-graph-crud-auth?graph-uri=<graph> using the digest authentication (not basic authentication). The triples provided are added to the <graph> specified (e.g. urn:graph:mypoi). To remove completely a graph a DELETE request to the same url will do the work.
Unfortunately the nodered version we are using does not support digest authentication in the http request node, to avoid this problem we prepared a subflow that performs the digest authentication. This flow need to use the md5 functionality that is not available directly and you need to install a specific node.
The subflow triplesToVirtuoso is the following
[{"id":"ed95bc2f.901ac","type":"subflow","name":"triplesToVirtuoso","info":"","in":[{"x":176,"y":78,"wires":[{"id":"6313f766.86f138"}]}],"out":[{"x":817,"y":402,"wires":[{"id":"b7f3af7d.31a2c","port":0}]}]},{"id":"21d3dee6.0a31f2","type":"http request","z":"ed95bc2f.901ac","name":"","method":"GET","ret":"txt","url":"","tls":"","x":477,"y":82,"wires":[[]]},{"id":"6313f766.86f138","type":"function","z":"ed95bc2f.901ac","name":"prepare A1 and A2","func":"//var wwwAuth=msg.headers[\"www-authenticate\"];\n//var a = wwwAuth.split(\"\\\"\")\nvar realm=\"SPARQL\"; //a[1];\nmsg.nonce=\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"; //a[5].replace(\"a\",\"b\");\nmsg.opaque=\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"; //a[7];\nmsg.A1=msg.user+\":\"+realm+\":\"+msg.passw;\n//msg.uri=\"/sparql-graph-crud-auth?graph-uri=urn:graph\";\nmsg.uri=\"\"\nmsg.A2=msg.method+\":\"+msg.uri\nreturn msg;","outputs":1,"noerr":0,"x":506,"y":150,"wires":[["303fe384.7b709c"]]},{"id":"303fe384.7b709c","type":"md5","z":"ed95bc2f.901ac","name":"md5 A1","fieldToHash":"A1","fieldTypeToHash":"msg","hashField":"A1_md5","hashFieldType":"msg","x":473,"y":201,"wires":[["f45f5e3e.86df8"]]},{"id":"f45f5e3e.86df8","type":"md5","z":"ed95bc2f.901ac","name":"md5 A2","fieldToHash":"A2","fieldTypeToHash":"msg","hashField":"A2_md5","hashFieldType":"msg","x":470,"y":251,"wires":[["f368ed11.481c"]]},{"id":"f368ed11.481c","type":"function","z":"ed95bc2f.901ac","name":"prepare A3","func":"msg.A3=msg.A1_md5+\":\"+msg.nonce+\":\"+msg.A2_md5\nreturn msg;","outputs":1,"noerr":0,"x":477,"y":305,"wires":[["b751fb63.391298"]]},{"id":"b751fb63.391298","type":"md5","z":"ed95bc2f.901ac","name":"md5 A3","fieldToHash":"A3","fieldTypeToHash":"msg","hashField":"md5","hashFieldType":"msg","x":469,"y":359,"wires":[["727fdaab.7c1214"]]},{"id":"727fdaab.7c1214","type":"function","z":"ed95bc2f.901ac","name":"","func":"omsg = {};\nomsg.url=msg.url\nomsg.method=msg.method\nomsg.headers={};\nomsg.headers[\"Authorization\"]=\"Digest username=\\\"\"+msg.user+\"\\\", realm=\\\"SPARQL\\\", nonce=\\\"\"+msg.nonce+\"\\\", opaque=\\\"\"+msg.opaque+\"\\\", uri=\\\"\"+msg.uri+\"\\\", response=\\\"\"+msg.md5+\"\\\"\"\nomsg.payload=msg.triples\n\nreturn omsg;","outputs":1,"noerr":0,"x":457,"y":403,"wires":[["b7f3af7d.31a2c"]]},{"id":"b7f3af7d.31a2c","type":"http request","z":"ed95bc2f.901ac","name":"","method":"use","ret":"txt","url":"","tls":"","x":667,"y":402,"wires":[[]]},{"id":"b8726632.e08df8","type":"comment","z":"ed95bc2f.901ac","name":"implementing digest authentication","info":"Virtuoso supports only digest authentication \nand not basic authentication.\nBut in this version of nodered the http request \nsupports only basic authentication, this flow \nimplemnents the digest auth using the md5 node.","x":549,"y":33,"wires":[]},{"id":"4792ca71.b8d664","type":"subflow:ed95bc2f.901ac","z":"500b9179.1f144","name":"","x":756,"y":311.5,"wires":[["cf83e3f5.a2158"]]}]
Copy this JSON and Import it in your nodered iotapp using the Clipboard (Menu > Import > Clipboard)
Paste the JSON in the dialog and press Import.
If you receive an error telling that the md5 node is not present install the node-red-contrib-md5 node using Menu > Manage palette, Install tab, write “md5” and install node-red-contrib-md5) .
Add a function node to provide input, and a debug node to see the result (set the debug node to show the whole message and not only the payload as the http result code is not in the payload)
The input message to be provided to triplesToVirtuoso has to have the following properties:
msg.user="dba" // the user of virtuoso to be used to access to virtuoso
msg.passw="dba" //the password
msg.method="POST" //the http method: GET, POST, DELETE, PUT
msg.url="http://virtuoso-kb:8890/sparql-graph-crud-auth?graph-uri=urn:graph:POIs"
msg.triples= "…"
The triples can be uploaded using the ntriple format (very verbose) or turtle format (more compact)
For example the following example uses the Turtle format (https://www.w3.org/TR/turtle/)
msg.triples=
'@prefix km4c: <http://www.disit.org/km4city/schema#> .\n'+
'@prefix schema: <http://schema.org/> .\n'+
'@prefix geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> .\n'+
'@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n'+
'@prefix virtrdf: <http://www.openlinksw.com/schemas/virtrdf#> .\n'+
'@prefix dcterms: <http://purl.org/dc/terms/> .\n'+
'@prefix skos: <http://www.w3.org/2004/02/skos/core#> .\n'+
'@base <http://www.disit.org/km4city/resource/> .\n' +
'<POI_00001> a km4c:Hotel ;\n'+
' schema:name "Hotel della Pergola" ;\n'+
' geo:lat "43.9771"^^xsd:float ;\n'+
' geo:long "11.2638"^^xsd:float ;\n'+
' geo:geometry "POINT(11.2638 43.9771)"^^virtrdf:Geometry ;\n'+
' schema:streetAddress "VIA DELLA PERGOLA" ;\n'+
' km4c:houseNumber "12" ;\n'+
' schema:addressLocality "Florence" ;\n'+
' schema:addressRegion "FI" ;\n'+
' schema:telephone "055123456789" ;\n'+
' schema:faxNumber "055123456789" ;\n'+
' schema:email "me@email.org" ;\n'+
' schema:url "http://lapergolahotel.it/" ;\n'+
' km4c:multimediaResource "https://lapergolahotel.it/wp-content/uploads/2018/04/img-03-395x325.jpg" ;\n'+
' dcterms:description "a description of this point of interest" ;\n'+
' skos:note "a note of this point of interest" .\n'+
'<POI_00002> a km4c:Restaurant ;\n'+
' schema:name "Ristorante della Pergola" ;\n'+
' geo:lat "43.9775"^^xsd:float ;\n'+
' geo:long "11.2633"^^xsd:float ;\n'+
' geo:geometry "POINT(11.2633 43.9775)"^^virtrdf:Geometry ;\n'+
' schema:streetAddress "VIA DELLA PERGOLA" ;\n'+
' km4c:houseNumber "12" ;\n'+
' schema:addressLocality "Florence" ;\n'+
' schema:addressRegion "FI" ;\n'+
' schema:telephone "055123456789" .\n'
These triples describe two POIs, one identified by URI http://www.disit.org/km4city/resource/POI_00001 and the other by URI http://www.disit.org/km4city/resource/POI_00002, the first is an Hotel and the second is a Restaurant.
Each POI MUST have one name using predicate schema:name and a latitude, a longitude and a geometry using the predicates geo:lat, geo:long and geo:geometry (the geometry have to be a POINT with the longitude and latitude coords using the WKT syntax). The following table reports some general properties that can be associated to a POI:
property |
Mandatory/optional |
description |
schema:name |
Mandatory |
Name of the POI |
geo:lat |
Mandatory |
latitude |
geo:long |
Mandatory |
longitude |
geo:geometry |
Mandatory |
WKT POINT geometry |
schema: streetAddress |
Optional |
Street address |
km4c: houseNumber |
Optional |
Civic number |
schema:addressLocality |
Optional |
Municipality |
schema:addressRegion |
Optional |
Region |
schema:postalCode |
Optional |
Postal code |
schema:telephone |
Optional |
Telephone number |
schema:url |
Optional |
Web site url |
schema:email |
Optional |
|
schema:faxNumber |
Optional |
Fax number |
dcterms:description |
Optional |
A description of the POI |
skos:note |
Optional |
A note |
km4c:multimediaResource |
Optional |
An url to an image of the POI |
Specific POI may have additional properties, in this case ServiceMap can be configured to set specific SPARQL queries to retrieve these properties values on the basis of the class.
At the end of the this page, there is a complete flow with examples to upload triples for events, weather info and POIs, moreover there are examples to delete all data from a graph (a collection of triples). The flow have to be updated to change the virtuoso kb ip address (this flow will work when used using docker compose version https://github.com/disit/snap4city-docker/tree/master/DataCity-Small).
It have to be considered that posting triples to a graph is always additive thus if you want to change something (e.g a street name with a typo) you need to delete the whole graph and upload the whole graph content again.
Full IOTApp example to upload triples to virtuoso
see full example to be copy pasted into the node-RED editor as a ne FLOW to have the above described IOT Application: example-virt-upload.txt
Copy this JSON and Import it in your nodered iotapp using the Clipboard (Menu > Import > Clipboard)