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 create properties 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 the SolidPropertyService, the SolidPropertySink allows you to stream location data to Solid by using this sink node within your positioning system model.
  • SolidPropertySource: In conjunction with the SolidPropertyService, 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.

Example of position data stored in Solid 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 data
let idx = 0;
const locationData = [
{ latitude: 50.8503, longitude: 4.3517, timestamp: new Date('2025-02-14T10:00:00Z') },
{ latitude: 50.8504, longitude: 4.3520, timestamp: new Date('2025-02-14T10:05:00Z') },
{ latitude: 50.8505, longitude: 4.3525, timestamp: new Date('2025-02-14T10:10:00Z') }
];
 
ModelBuilder.create()
.addService(new SolidClientService({
autoLogin: false
}))
.addService(new SolidPropertyService((node) => {
// Determine when to split (bucketizing strategy)
// Ideally, use the strategy proposed by the LDES stream
 
// Example: Split when the number of observations exceeds 50
return node.collection.members.length < 50;
}))
.from(new CallbackSourceNode(() => {
// Return fake location data
if (idx >= locationData.length) {
return undefined;
}
const maxim = new DataObject("mvdewync");
maxim.setPosition(new GeographicalPosition(locationData[idx].latitude, locationData[idx].longitude));
const frame = new DataFrame(maxim);
// Set the timestamp
frame.createdTimestamp = locationData[idx].timestamp.getTime();
return frame;
}))
.via(/* Add your processing nodes here */)
.to(new SolidPropertySink({
// Specify which properties to extract and store
properties: [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
function authenticate() {
// Authenticate the user
const service: 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(() => {
const model: Model<any, any> = (window as any).positioningModel;
if (model) {
// Pull new data
model.pull();
}
}, 5000);