Odysseus has built-in features in the spatial package with which spatio-temporal and especially moving objects data can be processed. Here we describe how to use these features and give a few example scenarios.

Starting Odysseus Spatial

You can download Odysseus here: http://odysseus.informatik.uni-oldenburg.de/index.php?id=76

When you have downloaded Odysseus, you need to add a few plugins to your installation. Use "Help" → "Install New Software". Choose the predefined update URL and install every package for the following keywords: "spatial", "keyvalue" and "nmea".

Accessing Example Data

For this example, we use vessel AIS data from the San Francisco bay, which is provided by ais.exploratorium.edu (you can open this in a browser and see the raw AIS data). You use this query to access the data. Just create a new Odysseus Script file within Odysseus Studio and run this query (with the "green arrow down"-button). The query does access the raw AIS data, converts it into key-value objects, filters some data, converts it to a tuple and finally uses latitude and longitude data to create an geo-object. The latter can be used by Odysseus for spatial functionality. If you want to see the data, do the following: after running the query, you should see "System.SanFranciscoAIS" in the Sources-view. Right click and choose "Query source" to view the data.


/// Access the data from San Francisco
input = ACCESS({
              ['host', 'ais.exploratorium.edu'],
              ['port', '80']
/// Filter the AIS messages to those from vessels
vesselsOnly = SELECT({
                  predicate = 'isNull(rateOfTurn) = false'                                                                                

/// Convert the key-value object to a tuple
tuple = TOTUPLE({
            schema = [
/// Create a geoObject from lat/lng
SanFranciscoAIS := MAP({
                        expressions = [
                          ['FromWKT("POINT (" + toString(latitude) + " " + toString(longitude) + ") ")', 'SpatialPoint']
                        keepinput = true,
                        removeattributes = ['latitude','longitude'],
                        name = 'geo_object'                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

A message you receive could look similar to the following:


Filling a Spatio-Temporal Index

This data can now be used to fill a spatio-temporal index for moving objects. To do this, create another Odysseus Script file and run the following query.


/// The index structure
#DEFINE dataStructureName 'sanFranciscoStore'
#DEFINE dataStructureType 'mo_geohash'


/// Store the data in an index structure
movingObjectIndex = MOVINGOBJECTSTORE({
                        /// distance of the trajectory in meters that we keep for every moving object
						distancepermo = 100.0,
                        geometryattribute = 'SpatialPoint',
                        datastructurename = ${dataStructureName},
                        idattribute = 'sourceMmsi',
                        datastructuretype = ${dataStructureType}                                            

Running a Range-Query on the Index

Now we can use this index to run a continuous range query for the vessels. We could reduce the number of vessels where we do the range query for by using another "Select"-operator. Nevertheless, here we calculate the range for every incoming object. The result of the query is within the "neighbors"-field of the tuple and as for now contains a JSON-object.

#DEFINE dataStructureName 'sanFranciscoStore'

/// For every vessel we want to know which vessels are near them all the time
            geometryattribute = 'SpatialPoint',
            datastructurename = ${dataStructureName},
            range = 300

A result could look like this:

12|UnderwayUsingEngine|NotAvailable|223|false|218.2|12.9|374128000|20|false|POINT (37.751635 -122.68389166666667)|{"374128000":[{"trajectoryElement":{"movingObjectID":"374128000","geoHash":{"bits":5589500436528045806,"point":{"longitude":-122.68389166666667,"latitude":37.751635},"boundingBox":{"minLat":37.75163498707116,"maxLat":37.75163502898067,"minLon":-122.68389173783362,"maxLon":-122.68389165401459},"significantBits":64},"distanceToPreviousElement":66.39223462250055,"streamElement":{}},"meters":0.0},{"trajectoryElement":{"movingObjectID":"374128000","geoHash":{"bits":5589500440284126512,"point":{"longitude":-122.682585,"latitude":37.75300333333333},"boundingBox":{"minLat":37.75300333276391,"maxLat":37.753003374673426,"minLon":-122.68258508294821,"maxLon":-122.68258499912918},"significantBits":64},"distanceToPreviousElement":64.17029786044263,"streamElement":{}},"meters":190.5932328122322},{"trajectoryElement":{"movingObjectID":"374128000","geoHash":{"bits":5589500441130161997,"point":{"longitude":-122.68214666666667,"latitude":37.753465},"boundingBox":{"minLat":37.75346496608108,"maxLat":37.7534650079906,"minLon":-122.68214670941234,"maxLon":-122.6821466255933},"significantBits":64},"distanceToPreviousElement":63.10852580037621,"streamElement":{}},"meters":254.76334501265032},{"trajectoryElement":{"movingObjectID":"374128000","geoHash":{"bits":5589500439526094378,"point":{"longitude":-122.68342833333334,"latitude":37.75210666666667},"boundingBox":{"minLat":37.75210663676262,"maxLat":37.752106678672135,"minLon":-122.6834283862263,"maxLon":-122.68342830240726},"significantBits":64},"distanceToPreviousElement":59.35999960990177,"streamElement":{}},"meters":66.39223462250055},{"trajectoryElement":{"movingObjectID":"374128000","geoHash":{"bits":5589500439809071195,"point":{"longitude":-122.683025,"latitude":37.752535},"boundingBox":{"minLat":37.7525349939242,"maxLat":37.752535035833716,"minLon":-122.68302504904568,"maxLon":-122.68302496522665},"significantBits":64},"distanceToPreviousElement":64.85054572839009,"streamElement":{}},"meters":125.74570287102185}]} | META | 1488546619406|oo

Show data on a map

Attention: This step is not available for now when you use the pre-compiled Odysseus version. You can checkout Odysseus via GIT and use this feature that way.

You can visualize the vessels with their trajectories on a map.

Adding a simple stream with location data

To do this, we first need a simple data stream from our vessels. Therefore, we can use this simple query:


/// For the map view
          size = [60, 'MINUTES']

Add a new map

Next, we create a map. Right click on your project / folder in the Project Explorer and choose "New" → "Other ..." → "Odysseus Map". When you created the map, you should see an empty grid. Next, we will add a background map from a tile server. We have a few predefined tile-servers you can use. You should see "Map Layers" and "Map Connections" on the right as you can see in the figure below. If you don't see these, you can add them via "Window" → "Show View" → "Other" and then select them.

Add a tile server

On the layers view, we click "Add" and choose the "http://tile.cloudmade.com/0636cabea78640328462d9d26e2a97e2/1/256/" tile server.

Now we should be able to see a world map. You can now zoom in to San Francisco where the vessels will appear after the next step.

Add the map connection

Change to the "Map Connections"-view. You should already have started the query from above. When you now click on "Connect", a window appears in which you ca select the query ("out = TIMEWINDOW ..."). Select this query and click "OK". The query should now appear in the "Map Connections" view.

Add a new layer

Switch back so the "Map Layers"-view. Click on "Add" to add a new layer. Choose "Thematic", Placement after Layer1 (the tile layer), choose the thematic map type as "Tracemap". The stream should be selected correct (with the map connection from the previous step). The GeometryAttribute is "SpatialPoint" and "Value Attriute" is the unique id of the vessels: "sourceMmsi". Now you can click "Add".

Configuring the Tracemap

A few seconds later you should be able to see the vessels moving on the map.

This map can be configured. Right click on "Layer2" and choose "Map properties". To better see the vessels, you can, for example, choose "Mark end of traces" or change the colors and length of the traces.

Now, the vessels are simpler to see, especially if they are currently not moving.

Usage of the time slider

With the time slider you can choose the time range that you want to visualize.

Future Work

The work on this topic is not finished. Here  list of things that we want to add in future releases.

  • Providing a web-based map view to visualize the queries, e.g. with Spring Boot + Leaflet (or OpenLayers). Can be a bit inspired by the demonstration visualization by Tile38 (e.g. http://tile38.com/ or https://roam.sh/)
  • Adding interpolation functionality to calculate in-between-measurements locations and near-future locations
  • No labels