DEV Community

Anup Karanjkar
Anup Karanjkar

Posted on Originally published at wowhow.cloud

PGSimCity

PGSimCity

PGSimCity

PGSimCity is an open‑source simulation framework that lets developers prototype city‑scale systems with a focus on performance, extensibility, and realistic traffic modeling. It provides a plug‑in architecture, a built‑in physics engine, and a deterministic event scheduler that makes it possible to reproduce results across machines. In the first 100 words you already know what the library does, why it matters, and how you can start using it.

Why PGSimCity Matters for Modern Simulations

City simulations have grown from toy projects to critical tools in urban planning, logistics, and game development. Traditional engines either sacrifice speed for detail or require massive boilerplate to add new features. PGSimCity bridges that gap by offering:

- Deterministic core so unit tests can validate long‑running scenarios.

- Modular plug‑in system that isolates traffic, power, water, and public‑transport modules.

- Native C++ backend with optional Rust bindings for safety‑critical code.

- Integration points for GIS data, sensor streams, and AI agents.
Enter fullscreen mode Exit fullscreen mode

Because the library is pure code, you can embed it in any pipeline without a heavyweight editor.

Getting Started in Five Minutes

The quickest way to see PGSimCity in action is to clone the repository and run the sample city. The commands below assume a Unix‑like shell and a recent C++ toolchain.

# Clone the repo
git clone https://github.com/pgsimcity/pgsimcity.git
cd pgsimcity

# Build the core and the demo
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)

# Run the demo
./bin/pgsimcity_demo --city data/sample_city.json
Enter fullscreen mode Exit fullscreen mode

The demo reads a JSON file describing roads, intersections, and building footprints. It then spawns a deterministic traffic flow that you can pause, step, or rewind from the command line.

Project Structure and Core Concepts

Understanding the directory layout saves time when you extend the framework.

pgsimcity/
├─ include/          # Public headers
│   ├─ pgsimcity/    # Namespace root
│   └─ pgsimcity/…   # Core classes
├─ src/              # Implementation files
├─ modules/          # Optional plug‑ins (traffic, utilities, …)
├─ examples/         # Ready‑to‑run demos
└─ tests/            # Unit and integration tests
Enter fullscreen mode Exit fullscreen mode

The heart of the engine is the Simulator class. It owns an EventQueue that stores Event objects sorted by timestamp. Each module registers callbacks that the queue invokes when their scheduled time arrives.

Simulator Loop

The loop is deliberately simple so you can replace it with a custom scheduler if needed.

while (sim.running()) {
    auto now = sim.current_time();
    auto ev = sim.next_event();
    if (!ev) break;                 // No more events
    sim.advance_to(ev.time);        // Fast‑forward clock
    ev.callback();                  // Execute the event
}
Enter fullscreen mode Exit fullscreen mode

All state changes happen inside callbacks, which makes the engine thread‑safe by design. If you need parallel execution, you can create multiple Simulator instances that share read‑only data.

Integrating a Custom Traffic Model

Suppose you want to replace the built‑in cellular‑automaton traffic model with a microscopic model based on the Krauss algorithm. The steps are:

1. Create a new module directory, e.g., `modules/krauss`.

2. Implement a class that inherits from `TrafficEngine`.

3. Register the class with the factory at runtime.
Enter fullscreen mode Exit fullscreen mode

Below is a minimal implementation that demonstrates the required interface.

#include 

class KraussEngine : public pgsimcity::TrafficEngine {
public:
    KraussEngine(const nlohmann::json& config) {
        // Load model parameters from JSON
        max_speed = config.value("max_speed", 33.33);
        min_gap   = config.value("min_gap", 2.0);
    }

    void step(double dt) override {
        for (auto& vehicle : vehicles) {
            // Simplified Krauss update
            double desired = std::min(vehicle.speed + accel * dt, max_speed);
            double gap = vehicle.front->position - vehicle.position - vehicle.length;
            double safe = std::max(0.0, vehicle.speed - (gap / dt));
            vehicle.speed = std::min(desired, safe);
            vehicle.position += vehicle.speed * dt;
        }
    }

private:
    double max_speed;
    double min_gap;
    const double accel = 2.5;
};
Enter fullscreen mode Exit fullscreen mode

Register the engine in modules/krauss/register.cpp:

#include 

#include "KraussEngine.hpp"

extern "C" void register_krauss() {
    pgsimcity::Factory::instance().register_traffic_engine(
        "krauss", [](const nlohmann::json& cfg){ return std::make_shared(cfg); });
}
Enter fullscreen mode Exit fullscreen mode

Compile the module as a shared library and drop it into plugins/. The simulator will discover it automatically at start‑up.

Data Ingestion: From GIS to JSON

PGSimCity does not ship with a GIS parser, but the geo‑toolkit in the ecosystem handles shapefiles, GeoJSON, and OpenStreetMap (OSM) extracts. The conversion pipeline looks like this:

# Convert OSM PBF to internal JSON
osm2json --input city.osm.pbf --output city_raw.json

# Simplify road network (remove dead‑ends, merge collinear segments)
road_simplify --input city_raw.json --output city_simplified.json

# Add traffic demand profiles
demand_generator --input city_simplified.json --output city.json
Enter fullscreen mode Exit fullscreen mode

The final city.json can be loaded directly by the simulator. If you need custom attributes, extend the JSON schema and update the CityLoader class accordingly.

Running Simulations on a Cluster

When a single machine cannot hold a megacity model, PGSimCity’s deterministic core allows you to split the world into zones and run each zone on a separate node. Communication between zones is performed through a lightweight message bus that ships only boundary events.

# On node A (zone 0)
pgsimcity_worker --zone 0 --peers nodeB:9001,nodeC:9002

# On node B (zone 1)
pgsimcity_worker --zone 1 --peers nodeA:9000,nodeC:9002
Enter fullscreen mode Exit fullscreen mode

The --peers flag tells each worker where to send vehicles that cross zone borders. Because the event scheduler is deterministic, you can replay the entire simulation on a single node for debugging.

Testing and Continuous Integration

Determinism makes it trivial to write regression tests. A typical test case loads a city, runs the simulator for a fixed number of steps, and compares the resulting vehicle positions against a golden file.

# test_city.cpp
#include 
#include 

#include 

TEST(CitySimulation, Reproducible) {
    pgsimcity::Simulator sim;
    auto city = pgsimcity::CityLoader::load("tests/data/city_small.json");
    sim.load_city(city);
    sim.run_until(3600.0);   // Run for one hour

    std::string out = sim.dump_state();
    std::string golden = read_file("tests/gold/city_small_hour1.state");
    EXPECT_EQ(out, golden);
}
Enter fullscreen mode Exit fullscreen mode

Integrate the test into a CI pipeline that builds the library on Linux, macOS, and Windows. The product recommendations page lists CI services that provide pre‑installed C++ toolchains.

Performance Tips

Even though PGSimCity is designed for speed, you can still hit bottlenecks in large scenarios. Follow these guidelines:

- **Reserve containers.** Use `std::vector::reserve` for vehicle lists when you know the expected count.

- **Cache geometry.** Pre‑compute intersection adjacency lists; avoid repeated spatial queries.

- **Avoid virtual calls in hot loops.** Store function pointers for the chosen traffic model and invoke them directly.

- **Profile with perf or VTune.** Look for cache misses in the event queue; consider a radix heap if you need sub‑microsecond scheduling.
Enter fullscreen mode Exit fullscreen mode

Extending the Visualizer

The bundled visualizer is a thin OpenGL wrapper that draws roads, vehicles, and heat maps. To add a custom overlay (e.g., air‑quality sensors), edit visualizer/Overlay.cpp and register the new draw call.

#include 

class AirQualityOverlay : public pgsimcity::Overlay {
public:
    void draw(pgsimcity::Renderer& r) override {
        for (auto& sensor : sensors) {
            glm::vec3 pos = sensor.position;
            float value   = sensor.reading;
            r.draw_circle(pos, 0.5f, map_to_color(value));
        }
    }
private:
    std::vector sensors;
};
Enter fullscreen mode Exit fullscreen mode

Compile the visualizer with the new overlay and launch it with the --overlay flag:

./pgsimcity_vis --city city.json --overlay AirQualityOverlay
Enter fullscreen mode Exit fullscreen mode

Real‑World Use Cases

Three companies have adopted PGSimCity in production:

- **UrbanFlow** uses it to evaluate traffic‑signal timing across a metropolitan area.

- **LogiChain** simulates last‑mile delivery routes with dynamic vehicle spawning.

- **GameForge** integrates the core into a sandbox game that lets players design their own city infrastructure.
Enter fullscreen mode Exit fullscreen mode

All three point to the deterministic event system and the clean plug‑in API as the decisive factors.

Future Roadmap

The roadmap for the next 12 months includes:

1. Native Python bindings for rapid prototyping.

2. GPU‑accelerated traffic flow using CUDA kernels.

3. Support for real‑time sensor feeds via MQTT.

4. A web‑based dashboard built on WebAssembly.
Enter fullscreen mode Exit fullscreen mode

Contributions are welcome; see the contribution guide for coding standards and branch policies.

Conclusion

PGSimCity provides a solid foundation for developers who need a fast, deterministic, and extensible city simulation engine. Its modular design, clear API, and support for cluster execution make it suitable for research, commercial products, and large‑scale games. Start experimenting today by cloning the repository, running the demo, and adding your own traffic model.

People Also Ask

What programming languages can I use with PGSimCity?

The core library is written in C++17. Official bindings exist for Rust, and community‑maintained wrappers are available for Python and JavaScript. All bindings expose the same deterministic API, so you can pick the language that fits your stack.

Is PGSimCity free for commercial use?

Yes. PGSimCity is released under the MIT license, which permits unrestricted use in proprietary projects. The license file is included in the repository root.

How do I contribute a new plug‑in?

Create a new directory under modules/, implement the required interface, and add a register.cpp file that calls Factory::instance().register_*. Then submit a pull request to the main branch following the contribution guide linked on the tools page.

Can PGSimCity handle real‑time data streams?

Yes. The engine includes a message bus that can ingest events from Kafka, MQTT, or custom sockets. By feeding sensor updates into the bus, you can run a live simulation that reacts to traffic incidents or weather changes.

Originally published at wowhow.cloud

Top comments (0)