The gps-osmand-tracker is a Java Software to simulate a standard hardware device that collects GPS locations, optionally buffer and send them to a server asap. Reporting should be configurable for time- and distance intervals, change of direction etc.

Tracker Components

Note

Note that we are citing original texts from GPS Tracking with Java EE Components

What exactly is a tracking device and what is its core functionality?
A tracker hardware is basically a combination of a GPS (processing) unit and a (GSM) communication unit. The GPS receives the location via satellite and the communication unit sends tracking messages to a server.

In the end every tracker should be able to send Coordinates, preferably WGS84 latitude and longitude and universal time, which the Server can use to localize the device.
— GPS Tracking with Java EE Components
Chapter 8.1

The main components are the GPS receiver and GSM transmitter chips for receiving and sending locations. Since both chips have to deal with a lot of information and technical challenges they are synchronized on a Controller Board. A Message Buffer serves as a local database to keep the best location results and wait for a good connection to transmit them to the Tracking System.

The fourth component, the Energy Source is vital for the hardware Tracker. And similar to a mobile phone the battery is the most space consuming component, but luckily is not needed for the software simulation.

JeeTS Tracker
A tracking hardware combines a GPS- and GSM unit on a Controller Board. The Controller evaluates sensor data to create event messages in the protocol format. NMEA data is constantly validated to provide the best position available. The messages can be sent whenever appropriate.
— GPS Tracking with Java EE Components
Figure 8.1

GPS receiver

A GPS chip (with antenna) acquires signals from orbiting satellites to calculate the location on Earth - a highly mathematical, trigonomic number cruncher. The location is continuously calculated by trilateration from (the best available) three satelites. Note that a fourth satelite is required to provide the precise time.

It is useful to understand that a GPS unit is primarily concerned to deliver latitude, longitude and time. The altitude is more of a side effect and not as reliable due to geometric reasons.

This implies that the GPS unit primarily provides point information. speed and bearing can be provided, but most Tracking Systems calculate them from the previous to the current positon.

GSM transmitter

The GSM or any other network technology is basically the entry point of GPS data to the internet. It can send the locations over the connected network (if available) and can communicate via TCP and UDP low level protocols and higher protocols, like http/s, on top. This unit is also working all the time to get a good connection over the air and transmitting whenever possible.

Message Buffer

As both GPS- and GSM units are busy all the time the Message Buffer can collect all valid and accurate locations from the GPS, while independantly pushing them to the server.

Communication Protocol

It is important to realize that a Tracking Device is not an application client and the data transmission has nothing to do with the REST API. It is simply some thing (Internet of Things, IoT) that sends information, very often via proprietary protocols. A protocol is subdivided into sentences to report different concerns. The most primitive GPS Protocol is NMEA 0183 which actually describes the primitives of GPS acquisition, like the satelite constellation. The protocol is described in more detail in The ROAF, Part II - Chapter 4.5 NAVSTAR GPS, 4.5.1 NMEA 0183

From the Tracker perspective the server is not the Traccar Platform, but simply a port to a Device Communication Server (DCS) which does the ETL work. The DCS is implemented with Netty Pipelines and the Decoder does the actual parsing targeting Traccar.

OsmAnd Protocol

Message Sentences combine a number of values that belong together. A complete protocol defines the available attributes for an entire system - or scenario. We chose the OsmAnd protocol because it is very simple and it is implemented on the Traccar Server (for the Traccar Client App). Or more general the Traccar Server offers a DCS for this protocol on a configurable port (default 5055). Technically "The OsmAnd protocol processes HTTP requests with either query parameters or POST parameters." — and the protocol allows to add attributes!

You can find the format (or protocol spec) here and here.

Geo Coordinates

For Global Positioning will use the GeoTools library as our interface to process GPS Positions, for dealing with Digital Maps and Geographic Information Systems - GIS. This way the Software is compatible to almost any geoengineering tool and to components like an embedded h2GIS server. usefull!

Yet, in geographic processing the most important performance aspect is mathematical.
Imagine you need to compare thousands of distances to operate in a scenario.
To calculate the exact distance on the globe we have to use a projection system. We will look at the performances differences by comparing the formulas for a geographical versus a cartesian distance below.

The GeoTools library is tightly coupled with the Java Topology Suite (JTS) library. The Coordinate is a core class from the GeoTools / JTS library, which is the standard geometry model GeoTools is built on. Note that the Java Topology Suite is actually not working with geocoordinates and optimized for trigonometric performance.

It is distinct from Point, which is a subclass of Geometry. Unlike objects of type Point (which contain additional information such as an envelope, a precision model, and spatial reference system information), a Coordinate only contains ordinate values and accessor methods.
— JTS Coordinate javadoc

Since the cartesian method is much faster we want to be able to ignore projection systems.
Therefore we are using the Coordinate to store the latitude, longitude and altitude with double precision.
It has many useful features like the usage 2d or 3d coordinate:

// 2D Koordinate (JTS)
Coordinate coord2D = new Coordinate(10.0, 20.0);
Coordinate coord3D = new Coordinate(10.0, 20.0, 500.0); // x, y, z

The GeneralDirectPosition is constructed the same way

// GeoTools Position (3D)
GeneralDirectPosition pos3D = new GeneralDirectPosition(10.0, 20.0, 500.0);

but belongs to the more complex GeoTools Referencing-Framework and carries metadata of coordinate systems etc.

We can always transform the JTS coordinates to geocoordinates (at a cost). With the GeoTools library you can tune the PrecisionModel and the built-in GeodeticCalculator does not require geodetic expertise.

At this point it doesn’t seam like a big deal, because we simply storing three double values in one Coordinate.
Let’s have a look at the formulas to get a sense when to use which.

Haversine Distance

The geographically calculation has to consider the globe’s radius, the curvature etc.
The Haversine Formula determines the great-circle distance between two points on a sphere:

\[a = \sin^2\left(\frac{\Delta\phi}{2}\right) + \cos(\phi_1) \cdot \cos(\phi_2) \cdot \sin^2\left(\frac{\Delta\lambda}{2}\right)\]
\[c = 2 \cdot \operatorname{atan2}\left(\sqrt{a}, \sqrt{1-a}\right)\]
\[d = R \cdot c\]
  • d is the distance between the two points.

  • \(\phi\) is latitude, \(\lambda\) is longitude.

  • \(\Delta\phi = \phi_2 - \phi_1\)

  • \(\Delta\lambda = \lambda_2 - \lambda_1\)

  • \(R\) is the radius of the sphere (Earth’s mean radius is approx. 6,371 km).

Tip

Most programming languages provide trigonometric functions that require input in radians.
To convert degrees to radians, use the formula: radians = degrees * pi /180.

When comparing distances their precise value does not matter, only relativity counts.
To calculate a distance in a cartesian plane we can also apply Pythagoras:

Pythagorean Distance

The Pythagorean Theorem is a fundamental principle in Euclidean geometry relating the three sides of a right triangle. The theorem states that in a right-angled triangle, the area of the square whose side is the hypotenuse (the side opposite the right angle) is equal to the sum of the areas of the squares on the other two sides.

\[a^2 + b^2 = c^2\]

Or simply speaking a and b are the orthogonal deltas of lat and lon and c is the distance, i.e. length of the hypotenuse. Calculating a comparable distance c is as simple as

\[distance = \sqrt{\Delta lat^2 + \Delta lon^2}\]

By simply comparing this with the Haversine Formula you can sense the differences in performance. While the distance is simply calculating a length the real number crunching takes place when overlapping Geofences come into play. Note that geofence has many edges and each one has to be taken into account, yet should not slow down the real time scenario.

The bm Geodesy Standard

We don’t want to be distracted by reference systems.

The most conservative way to deal with the definition of a CoordinateReferenceSystem is not to.
Instead make use of an authority that provides complete definitions defined by a simple code.
— docs.geotools.org/latest/userguide/library/referencing/crs.html

Throughout this bm repository we will use identical geodetic formulas.
Then the distances from location 1 to location 2
and from location 2 to location 1 are guaranteed to be identical.
This is very important for synchronicity.

Check out the JTS feature list to get an idea of geometric operations.

Distance Example

With the GeoTools GeodeticCalculator we calculate the geodesic distance in meters between two (lat,lon) GPS points in the standard WGS84 (EPSG:4326) system:

  CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");     // (1)

  GeodeticCalculator calculator = new GeodeticCalculator(wgs84); // (2)

  calculator.setStartingGeographicPoint   (lon1, lat1);          // (3)
  calculator.setDestinationGeographicPoint(lon2, lat2);

  return calculator.getOrthodromicDistance();                    // (4)
  1. Define the Coordinate Reference System

  2. Initialize the Geodetic Calculator

  3. Set start- and end coordinates

  4. Get the orthodromic distance (distance along the ellipsoid surface)

Implementation Path

In the long run we want to track every Thing (real physical object) acting in a scenario.
Since every message implies the devices uniqueId, one Message Broker could operate as one single Tracker for a complete scenario. So all simulated devices could send their messages off the same Tracker and could be scaled and sudivided with a Message Broker like RabbitMQ, ActiveMQ etc.
We want to provide an independantly working GPS Tracker (simulation) for each of these things. A single device can be scaled with SEDA to decompose messages for event driven components.

Note

This implementation might help you to choose a Tracker Device Type for your real time scenario. The chosen protocol is a easy as it gets and the creation process can serve as a guideline.

The Model

We have chosen the OsmAnd protocol and defined the OsmAnd GPS Message (targeting Traccar). That is the information and format the server accepts and understands. We will use it as our Model to guide Tracker development. The protocol defines all attributes exchanged with the GTS platform.

We could generate a Java Class from the JSON format listed on the traccar osmand page.
But we want to go our own way and only use GPS platonic values for an initial GPS Tracker. We are primarily interested to transmit place & time and will fix the parameters in a simple record to hold a single GPS snapshot:

package bm.gps;

    public record MessageOsmand(
        // must have
        String id,        // traccar device uniqueId
        double lat,
        double lon,
        long timestamp,
        // optional objects to allow null values
        Double speed,
        Double bearing,
        Double altitude,
        Double battery,
        Double hdop) { }

A record is an immutable data class and much easier to handle than a POJO.

Record classes, which are a special kind of class, help to model plain data aggregates
with less ceremony than normal classes.
— https://docs.oracle.com/en/java/javase/17/language/records.html
Record Classes

Note that the primitive values are used as the "must haves" for GPS Tracking. At a later stage they can be abstracted to a GPS info interface to introduce different trackers to the same scenario.

The other values are modelled as objects in order to make them optional. Currently the traccar osmand page offers 16 different values, which can be introduced while we are prototyping.

  • For example the hdop, i.e. Horizontal dilution of precision is not relevant for us.
    It describes the quality of a message and we are only interested in the result.

  • On the other hand the accuracy can be missused to create a circle on the map,
    which might be interesting for certain scenarios.

No need for final decisions at this point.
Traccar also allows custom attributes, which could be sneeked into a scenario, if needed.

model deviations

The above model is a minimal starting example. A GPS Tracking Platform has to deal with a lot more attributes.
We will use the Traccar GTS, which is build on the following related Entities:

Attribute

Calendar

Command

CommandType

Device

DeviceAccumulators

Driver

Event

Geofence

Group

Maintenance

Notification

NotificationType

Permission

Position

ReportStops

ReportSummary

ReportTrips

Server

Statistics

User

These are not the actual Entities of the Traccar ERM.
These are DTO classes generated from the Traccar REST API openapi.yml file.

You can find the classes in the bm.traccar.generated.model.dto package
and they are available in every traccar-client- implementation.

Let’s compare the Device class and a json devices message sent via websocket:

      class Device {             {devices=[
        id: 638                     {id=638,
        name: runner                 name=runner,
        uniqueId: 10                 uniqueId=10,
        status: offline              status=online,
        disabled: false              disabled=false,
        lastUpdate: null             lastUpdate=2026-02-20T16:15:30.791+00:00,
        positionId: 0                positionId=0,
        groupId: 0                   groupId=0,
        phone: null                  phone=null,
        model: ro                    model=ro,
        contact: null                contact=null,
        category: null               category=null,
        attributes: {}               attributes={},
                                     calendarId=0,
                                     expirationTime=null
                                    }
                                 ]}

Both messages have the devices uniqueId in common to refer to the same device.
Yet the two models deviate by the two attributes calendar and expirationTime.
Important information for ETL stuff like mapping.

The other attributes of our Tracker Model record can be found in the Position class:

package bm.gps;                      bm.traccar.generated.model.dto;

    public record MessageOsmand (        public class Position {
        String id,                           deviceId : Long
        double lat,                          latitude : BigDecimal
        double lon,                         longitude : BigDecimal
        long timestamp,                       fixTime : OffsetDateTime
        Double speed,                           speed : BigDecimal
        Double bearing,                        course : BigDecimal
        Double altitude,                     altitude : BigDecimal
        Double battery,
        Double hdop) { }

geo precision

Another thing worth mentioning is the value- and type safety of decimals.
The openapi generator creates the Position class with BigDecimal types

private BigDecimal latitude, longitude, altitude;

The difference between BigDecimal and double is precision against performance.
For financial computation the precision, i.e. the exact sequence of numbers after the point, is indisputable.
With double the decimal values are not always precise.
For example, there is no precise decimal representation of one Third.
Therefor some tricky algorithm with memory has to make sure 1 / 3 * 3 is exactly 1.

This should be kept in the back of our mind, since it does matter when you want to know, if two points on a map are identical. Or you want to find all identical points in the complete map data. If the values are transfered with a string format like json or as URL parameters they are actually converted into their literal value.

To stay on the save side comparisons should always be made with tolerance.
For the Tracker and the Message we will use double, standard for most geotools and -libs.

Requirements

First we collect the functional requirements for the core features of tracking,
a Minimum Viable Product we can build on.
We have looked at the main Tracker Components earlier to gather requirements for the implementation:

GPS Unit / receiver

  • ✓ Acquire GPS position (latitude, longitude, altitude)

  • ✓ UTC Time

  • ❏ incoming Message Buffer here ?

(GSM) transmitter

  • ✓ Transmit messages (primarily location data) to a remote server (any TCP, UDP protocols)

  • ❏ configurable intervals e.g. every x seconds when moving, every y minutes when stationary etc.

  • ✓ manage connection to server, reconnection

  • ✓ outgoing Message Buffer

Controller

The main controller resides between receiving input and transmitting output. Most features of a Tracker can be implemented in it and it is the place to collect system information continuously.

  • ❏ Store last known position (or short history)

  • ❏ Fallback: store positions locally when no network → send burst when reconnected

  • ❏ Motion detection → change reporting frequency when moving vs. stationary

  • ❏ Determine basic movement data: speed, course/heading

  • ❏ provide basic status events: power on, transmitting, low battery, error.

  • ❏ Low-battery warning (send alert or change behavior)

  • ❏ Geofencing support (enter/exit alerts when crossing virtual boundaries)

This list can provide a guideline for software @Components and is sufficient to create a concrete implementation without surprises.

Message Buffer

  • ❏ longterm nice2have: analyze incoming data in millisecond intervals for real time events

Primary Use Case

the (Java) application developer,
i.e. user of the gps-osmand-tracker-1.1.1x.jar
wants to create a message and send it.
fire & forget

Component Architecture

At this point it is important to prototype the components architecture as an reliable development guideline — and always subject to change. Prototyping is the first implementation to demo the proof of concept for single component to a complete framework. Main Technology- and Design Decision have to be made to fullfill the POC. And many design candidates and technologies are sorted out. This way the stack can be versioned with a bill of materials (BOM) over the complete repo to optimize transparency for (onboarding of business) coders.

implementation steps

  • ❏ create a Springboot application to simulate a GPS Tracker hardware

  • ❏ use the osmand protocol with Traccar ID

  • ❏ use the OpenTools library for GPS coordinates and distance, bearing etc.

  • ❏ setters for location and fixtime

  • ❏ send messages via Camel Route

  • ❏ store messages in a thread-safe queue, if server is unavailable, send later

Receiver

Actually the receiver, i.e. GPS Unit is already completly defined with the MessageOsmand Model.
It specifies which attributes the Tracker can handle. The data acquistion can be "implemented" with getters/setters of the Controller. These will be connectors to simulations, playbacks and live tracking. Energy and UTC time are sponsored by the runtime environment.

We can already check off some requirements :)

Controller

The Controller is the Tracker, i.e. TrackerOsmAnd core. We have chosen Camel to integrate components
and a Camel rider starts software design with a local direct Endpoint:

      // to Sender Component
      from("direct:send-osmand")

From this point we will send messages to the Traccar server. In integration terms it is a Producer Endpoint to send a fixed message. On the other hand we want to leave all options to create messages. This is achieved by using the Camel Producer to send messages to "direct:send-osmand", now being a Consumer Endpoint.

For example you can create a regular Java method and instead of return you end the method with sendBody. Camel will take care of the rest. For the developer the message can be considered as sent.

      // Tracker Component
      public void send(MessageOsmand msg) {

        // evaluations etc.

        // synchronous at dev time
        tracker.sendBody("direct:send-osmand", msg);
        // more traffic
        // tracker.asyncSendBody("direct:send-osmand", msg);
        // even more traffic
        // tracker.asyncSendBody("seda:send-osmand", msg);
      }

Now we are set to start implementing with this Endpoint ..

  • as Consumer of a immutable message and

  • as Producer to send this message.

sounds easy,
'cause it is ;)

Sender

We now have Input and a Controller.
Next is the Output, the messaging/transport layer to the GTS.
The Transmitter is responsible to submit (and acknowledge) the messages to Traccar.
First we need to make sure the controllers call introduced above

// message to Sender
tracker.sendBody("direct:send-osmand", msg);

is intercepted by a transmitter, so we implement a Route with

TrackerSender extends RouteBuilder

which takes care of all the Spring and Camel wiring, autodetection etc.
The transmission route starts with

// send message to server
from("direct:send-osmand")

The route dynamically builds the server URL, manages http headers and properties
and sends the message to Traccar via Dynamic Endpoint .toD( host + parameters ).

load balancing

In the // Tracker listing above different types of sending a message are indicated. This way the prototype leaves some room to scale implementations for a dedicated application. Each send type can get its own method as we widen the Software for production.

Note that in the above sample we are using the routeId send-osmand-route as transmitter. This implies that this very route is re/created in each tracker instance. When Camel adds a route with the same routeId is will stop/replace the existing route.

We are interested to create an independant tracker to track an independant thing.
Therefore the initial implementation will respect the uniqueId inside the tracker implementation.

routeId("send-osmand-route-" + uniqueId)
  from("direct:send-osmand-" + uniqueId).

Per-tracker routes increase Camel route count with resource growth. For many trackers in a single application, we will consider a single shared Transmitter route that handles any device (messages carry device id) to avoid many dynamic routes. The CamelContext provides full flexibility to manage all routes and leaves room for load optimization - any time later.

Once the tracker beta version is running there is still room for a different setup.
In the long run this could be controlled with a switch to be introduced.

Message Buffer

A Message Queue is supposed to collect the message that can not be sent immediately.
Have a look at the Device developed for Netty with JeeTS.
For this Device we created a loop

// All Messages are collected in a MessageQueue and
// the MessageLoop takes care of sending them whenever connectivity allows it.
   private class SendMessageLoop implements Runnable {

which is wrapped around the

// Blocking queue to synchronize sending and callback.
   private BlockingQueue<Object> callbackQueue = new LinkedBlockingQueue<Object>(1);

This was implemented for Demo purposes and can also be perceived as a simple Endpoint implementation.
This time we want a robust and configurable Endpoint as a sender.

SEDA architecture

SEDA or staged event-driven architecture is a general EIP for software architecture.

SEDA refers to an approach to software architecture that decomposes a complex, event-driven architecture (EDA) into a set of stages connected by queues. It avoids the high overhead associated with thread-based concurrency models and decouples event and thread scheduling from application logic.
— en.wikipedia.org/wiki/Staged_event-driven_architecture

In general a SEDA controller is any mechanism that manages the consumption of resources
such as thread pool size, event queue size, scheduling, etc.

"seda:" Component

And, of course, Camel does provide a SEDA component "seda:" out of the box

The SEDA component provides asynchronous SEDA behavior,
so that messages are exchanged on a BlockingQueue and
consumers are invoked in a separate thread from the producer.
— camel.apache.org/components/4.14.x/seda-component.html

Creating a Tracker prototype with SEDA is a walk in the park:

"seda:send-osmand"

Yet SEDA is working in-memory with asynchronous processing within a single JVM to support for highly-concurrent traffic. Which means that our Tracker will loose all messages, if there is a crash.

This component does not implement any kind of persistence or recovery if the JVM terminates while messages are yet to be processed. If you need persistence, reliability or distributed SEDA, try using JMS.
— camel.apache.org/components/4.14.x/seda-component.html

We use the SEDA component to decouple your main process from volatile HTTP calls. We can "fire and forget" messages into an internal queue, let a separate thread pool handle network timeouts and server crashes.

One thing we can do with the "seda:" component is to activate a Dead Letter Queue (DLQ) for development inspection of lost messages and maybe to improve SEDA performance a bit. As as example of a

errorHandler( deadLetterChannel(..

have a look at this JeeTS Device For sake of simplicity, we won’t introduce a DLQ - now.

Instead we will define the error handling inside the route with exponential backoff for the "reconnect" to happen:

    onException(Exception.class)
        .maximumRedeliveries(5)  // Try 5 times
        .redeliveryDelay(2000)   // Wait 2 seconds between tries
        .useExponentialBackOff()
        .backOffMultiplier(2)    // Double the wait time each failure (2s, 4s, 8s...)
        .retryAttemptedLogLevel(LoggingLevel.WARN)
        .handled(true)           // Don't crash the whole route
        .log("Failed to reach server after retries. Giving up on ${body}");

retry / backoff parameters can be tuned or special handling for non-retriable HTTP codes can be added. This is very helpful, since you don’t want to stress the server even more, when it cannot answer. You can also configure Jitter (Collision Avoidance) when working with more Trackers.

Also note that Camel Components can be configured in the application.properties.
For SEDA it is camel.component.seda.*, like these examples:

# Configure SEDA
camel.component.seda.queue-size=1000
camel.component.seda.concurrent-consumers=4
camel.component.seda.default-poll-timeout=1000
camel.component.seda.offer-timeout=0

For Custom Thread Pools you can manage the underlying thread pool more granularly,
you can also configure the Default Thread Profile:

# Configure the thread pool used by SEDA and other components
camel.threadpool.pool-size=10
camel.threadpool.max-pool-size=20
camel.threadpool.max-queue-size=100

level up to Message Broker

Camel makes it easy to (or was made to) level up an existing architecture. The DSL structure remains almost identical. If required you can change SEDA to a full-blown Message Broker like ActiveMQ or RabbitMQ. By switching to JMS or AMQP, you gain persistence, better monitoring, and the ability to scale your consumers across multiple server instances.

One thing you should do to keep all options open is to use a local "direct:" endpoint for your business logic.
Then you can switch from

from("direct:start").to("seda:send-osmand");

to ActiveMQ for Java/Camel ecosystems

from("direct:start").to("activemq:queue:send-osmand");

or to Use RabbitMQ for high performance, polyglot support etc.

from("direct:start").to("rabbitmq:gpsExchange?routingKey=send-osmand");

So we keep in mind that we can add Transaction Support (JTA), Persistence (JPA) and control Backpressure gracefully.

  • ✓ Prepared for really large realtime scenarios.

About performance: after your application invokes

from("direct:start")

and instantly returns to your code in zero time.

Later we will use a realtime-client to confirm that the message is processed on server side.

Disruption Test

The TrackerQueueDisruptionTest simulates the sending of three messages.
For the first message the server handles the test as wanted

18:18:25.229  tracker-4711 sending message
18:18:25.610  Server handling request OK
18:18:25.669  Sender: sent message to http://localhost:5055 via seda-send-osmand-route-4711

Then the server is disrupted before two more messages are sent.
Delivery fails and we can see the reattempts after two seconds:

18:18:25.766  Simulating server disruption (500 responses)
18:18:25.766  tracker-4711 sending message
18:18:25.767  tracker-4711 sending message
18:18:25.771  Server handling request ERROR
18:18:25.786  Failed delivery for (MessageId: 4E2F39A8CFF55AD-0000000000000003
18:18:26.229  Server handling request ERROR
18:18:26.234  Failed delivery for (MessageId: 4E2F39A8CFF55AD-0000000000000005
18:18:27.790  Server handling request ERROR
18:18:27.794  Failed delivery for (MessageId: 4E2F39A8CFF55AD-0000000000000003
18:18:28.239  Server handling request ERROR
18:18:28.244  Failed delivery for (MessageId: 4E2F39A8CFF55AD-0000000000000005

Finally the server is restored and the messages are sent successfully :)

18:18:30.768  Restoring server availability
18:18:31.798  Server handling request OK
18:18:31.799  Sender: sent message to http://localhost:5055 via seda-send-osmand-route-4711
18:18:32.249  Server handling request OK
18:18:32.251  Sender: sent message to http://localhost:5055 via seda-send-osmand-route-4711

Registration

Now how to use the Tracker in Spring Applications?

If a developer creates an actor of a scenario, some Java Object, and wants to track it he should be enabled to create a Tracker on the fly. There is no static configuration and it should happen at runtime.

lifecycle

With Spring and Camel the registration lifecycle register, unregister, reregister can be fully managed at runtime.

We register the tracker as a GenericBeanDefinition so Spring automatically wires lifecycle and destruction callbacks. TrackerRegistration registers a GenericBeanDefinition (constructor arguments uniqueId, osmandHost) with DefaultListableBeanFactory and then pulls the fully-initialized bean from the context.

This makes Spring handle @PostConstruct and @PreDestroy automatically. (Calling destroySingleton(beanName) removes the instance but the bean definition remains.) On subsequent register attempts the code will see the existing bean definition and try to return/get the bean. Removing the bean definition after destroying the singleton fully clears Spring’s state for that bean name so registerTracker can create/register the bean again without conflicts.

With our components being Spring beans, we want to create and register a new bean instance at runtime and then start its associated Camel route. This is achieved using the ApplicationContextAware interface to handle the dynamic creation and registration of a fully managed component. With AutowireCapableBeanFactory the bean is created manually, allowing Spring to handle all the necessary autowiring (like the ProducerTemplate and CamelContext).

usage

And this is how to use the gps-osmand-tracker-1.1.15.jar in your code:

  @Autowired TrackerRegistration registrationService;                       (1)

  void sendMessage() throws Exception {

    // get uniqueId from application context
    TrackerOsmAnd tracker = registrationService.registerTracker(uniqueId);  (2)

    MessageOsmand msg =
    MessageOsmand.now(uniqueId, 52.0, 13.0, 10.0, 20.0, 30.0, null, null);  (3)

    tracker.send(msg);                                                      (4)

    registrationService.unregisterTracker(uniqueId);                        (5)

    ...
  1. provide tracker registration service

  2. create and register tracker for uniqueId

  3. create the immutable record

  4. send, forget and continue application context

  5. unregister tracker bean to clean up (and re-register)

Note that each Tracker bean is named with its Traccar identity

"tracker-" + uniqueId

This is a simple constraint to avoid multiple Trackers with the same id.
Actually the IMEI should be used for any hardware Tracker, since it marks the unique hardware piece.

component wiring

We are using two frameworks to take care of @Autowired Spring components and Camel routes.
The general constellation for all trackers (to come) looks like this

Diagram

Any number of Trackers can be created in the main application (in this case a minimum @SpringBootApplication). Each one can be looked up in the Registration to send Messages with the Tracker( uniqueId ).

sending sequence

sending a GPS snapshot

Diagram

Register a number of Trackers in your application and send messages whenever appropriate. The sending method returns to the application instantly, while the Sender takes care of sending all messages until accepted by the Tracking Server.