Technical Note

How I Split APIs for a Traffic Digital Twin Screen

  • Digital Twin
  • Flask API
  • Unity/Cesium

Created and reviewed on July 13, 2026. This is a working note, not a claim that the prototype is a production service.

In a digital twin, the backend and the 3D screen should not do the same work. The backend should collect, organize, and return data. The client should request that data and turn it into a useful view.

I used this idea in a Seoul traffic digital twin prototype. I built the Flask backend and worked on the Unity side that requests road data, loads roads, and shows the road view in Unity/Cesium.

The API was easier to manage when I separated three different questions.

1. What roads should the screen create?

The road endpoint returns the reference information that Unity needs to build the road layer.

GET /api/segments

This response is about road identity and structure. It can include a linkId, road name, geometry, and other fields needed by the client.

Unity sends a Request to the Flask server, reads the response, and stores the road data. The road-loading flow then creates or updates the road objects. The road-view flow uses the geometry to show them on the Cesium map.

This is different from live traffic. A road can exist even when a current speed record is missing. Loading the road structure first gives the screen a stable base.

2. What is happening on those roads now?

The real-time endpoint returns changing traffic information.

GET /api/realtime-traffic

The client matches each traffic row to an existing road with the same linkId. It then updates values such as speed and congestion state. The road view can use those values to change the road color.

This keeps the update smaller than sending every road shape again. The geometry stays in the client, while the changing traffic values can be refreshed.

The split also makes errors easier to find. If a road is not visible, I check the road-loading path. If a road is visible but has no live color, I check the real-time response and the linkId match.

3. What may happen at a selected road and time?

Prediction is a different action from loading or refreshing.

GET or POST /api/predict

The user selects a road and a time. Unity sends the selected road ID and time to Flask. The backend prepares the required features, runs the current prediction flow, and returns the result in a response that the screen can display.

The project includes model training and validation work, but I do not describe the model as accurate without verified results. The API boundary is still useful before that final evaluation. It lets the screen, backend, and model flow be tested as separate parts.

Why three boundaries were useful

The three endpoints have different change patterns.

Responsibility Main data When the client needs it
Road loading Road identity and geometry At the first load or a full reload
Real-time update Speed and congestion by road During refreshes
Prediction Selected road, time, and result When the user requests it

This separation reduced hidden dependencies. The Unity road view did not need to know how public data was collected. The Flask backend did not need to know how a Cesium road mesh was drawn. They only needed a clear response contract.

My implementation flow

My work covered both sides of this boundary.

On the backend side, I built the Flask server, organized public and real-time traffic data, prepared the machine-learning training and validation flow, and returned current or predicted values through APIs.

On the Unity side, I worked with the Request flow that receives Flask responses. I also handled the road data, road loading, and road view parts that turn the response into visible roads on the Unity/Cesium screen.

The flow can be summarized like this:

Public and real-time data
  -> Flask data processing
  -> road / traffic / prediction API
  -> Unity Request
  -> road data and road loading
  -> Unity/Cesium road view

A boundary is also documentation

An API is not only a URL. It is an agreement about fields, timing, empty results, and errors.

While building this prototype, I learned to document:

  • which endpoint owns each responsibility;
  • which key connects the response to a road;
  • which values can be missing;
  • when the client should load, refresh, or request;
  • which model results are still under validation.

That documentation made the system easier to explain and review. It also gave me a practical way to connect backend work with a visual client instead of treating them as two unrelated projects.