Stream Location Data to Solid
In 2022, we presented a prototype for storing the output of a positioning system in Solid. While the demonstration served its purpose, its implementation was not ideal for a real-world scenario. First, the data was stored in a single file, which is not scalable. Second, the implementation broke the design principles of OpenHPS, which aims to create a flow of information from source to sink.
Over the years, we have been fine-tuning the OpenHPS framework to support a more scalable and robust way of storing location data in Solid. In this interactive blog post, we will demonstrate how to stream location data directly to Solid using OpenHPS. The main concepts we will discuss in this blog post are:
SolidPropertyService
: A service that allows you to createproperties
and store the observations of those properties in Solid. Unlike our previous implementation, we structure the data over multiple containers, which allows for a more scalable solution.SolidPropertySink
: In conjunction with theSolidPropertyService
, theSolidPropertySink
allows you to stream location data to Solid by using this sink node within your positioning system model.SolidPropertySource
: In conjunction with theSolidPropertyService
, this source node allows you to stream location data from Solid.
Our property service is modified to store data in Linked Data Event Streams (LDES) over multiple LDP containers, similar to the paper Writing Linked Data Event Streams in LDP Basic Containers. However, we still make use of the POSO ontology and observable properties linked from the WebID.
The image above shows an example of the data stored in Solid. The data is structured over multiple containers, which allows for a more scalable solution.
Getting Started
It is as simple as three steps. In the first step, we will create a positioning system model. In the second step, we will authenticate the user with the Solid provider. Finally, in the third step, we will start the positioning system by pulling fake location data. The property service and sink node will handle the structuring and storing of the location data in Solid.
NOTE: Hover with your mouse over the code snippets to see the IntelliSense in action.
Step 1: Create a Positioning System Model
For the scope of this example, our processing network will be simple. We will create a model that reads location data from a source and pushes it to a sink. We add two services to the model: a SolidPropertyService
and a SolidClientService
. The Solid client service provides the basic API and authentication for interfacing with a Solid provider and internally keeps track of multiple sessions. The property service handles the creation of properties and the storage of observations in Solid.
Finally, we add a SolidPropertySink
to the model. This sink node will transform data frames into observations and store them in the Pod of a user.
ts
import {ModelBuilder ,GeographicalPosition ,DataObject ,DataFrame ,CallbackSourceNode } from '@openhps/core';import '@openhps/rdf';import {SolidPropertyService ,SolidClientService ,SolidPropertySink ,PropertyType } from '@openhps/solid';// Fake location dataletidx = 0;constlocationData = [{latitude : 50.8503,longitude : 4.3517,timestamp : newDate ('2025-02-14T10:00:00Z') },{latitude : 50.8504,longitude : 4.3520,timestamp : newDate ('2025-02-14T10:05:00Z') },{latitude : 50.8505,longitude : 4.3525,timestamp : newDate ('2025-02-14T10:10:00Z') }];ModelBuilder .create ().addService (newSolidClientService ({autoLogin : false})).addService (newSolidPropertyService ((node ) => {// Determine when to split (bucketizing strategy)// Ideally, use the strategy proposed by the LDES stream// Example: Split when the number of observations exceeds 50returnnode .collection .members .length < 50;})).from (newCallbackSourceNode (() => {// Return fake location dataif (idx >=locationData .length ) {returnundefined ;}constmaxim = newDataObject ("mvdewync");maxim .setPosition (newGeographicalPosition (locationData [idx ].latitude ,locationData [idx ].longitude ));constframe = newDataFrame (maxim );// Set the timestampframe .createdTimestamp =locationData [idx ].timestamp .getTime ();returnframe ;})).via (/* Add your processing nodes here */).to (newSolidPropertySink ({// Specify which properties to extract and storeproperties : [PropertyType .POSITION ]})).build ().then (model => {/* Do something */(window as any).positioningModel =model ;});
Step 2: Authenticate the User
Once you have created the model, you need to authenticate the user. This can be done by interacting with the Solid service. The Solid service will redirect the user to the Solid provider to authenticate the user. Once authenticated, all nodes in the positioning system will be able to access the user's Pod.
ts
functionauthenticate () {// Authenticate the userconstservice :SolidClientService = (window as any).positioningModel .findService (SolidClientService );service .login ().then (() => {console .log ('User authenticated successfully');}).catch ((error ) => {console .error ('Error during authentication:',error );});}
Step 3: Start the Positioning System
Once the user is authenticated, you can start the positioning system. In a real-world scenario, this would be done by allowing the source to capture and process sensor information. In this example, we push new data through the model every 5 seconds. The SolidPropertySink
will handle the transformation of the data frame into an observation and store it in Solid.
ts
setInterval (() => {constmodel :Model <any, any> = (window as any).positioningModel ;if (model ) {// Pull new datamodel .pull ();}}, 5000);
- Previous: OpenHPS Protocol Buffers