City Generator 1.0.1
Procedural city generation for C++23
Loading...
Searching...
No Matches
Usage Guide

This guide covers every feature of the city-generator library in detail. For a quick overview, see the README. For the full API reference, run doxygen Doxyfile from the repository root and open doc/api/html/index.html.

Quick Start

#include <iostream>
int main()
{
auto& gen = dasmig::cg::instance();
// Random city (population-weighted).
auto c = gen.get_city();
std::cout << c.name << ", " << c.country_code << "\n";
// City from a specific country.
auto br = gen.get_city("BR");
std::cout << br.name << " (pop. " << br.population << ")\n";
}
City generator library — procedural city generation for C++23.
static cg & instance()
Access the global singleton instance.
Definition citygen.hpp:159

Installation

  1. Copy dasmig/citygen.hpp and dasmig/random.hpp into your include path.
  2. Copy the resources/ folder (containing full/ and/or lite/ subdirectories) so it is accessible at runtime.
  3. Compile with C++23 enabled: -std=c++23.

Loading Resources

The library ships two dataset tiers:

Tier Enum Cities Source
lite dasmig::dataset::lite ~25k (pop ≥ 15,000) cities15000.zip
full dasmig::dataset::full ~200k (pop ≥ 500) cities500.zip

Resources are stored as resources/lite/cities.tsv and resources/full/cities.tsv.

Automatic loading (singleton)

On first access the singleton constructor probes these base paths:

Priority Base path
1 resources/
2 ../resources/
3 city-generator/resources/

It loads lite/cities.tsv if found, otherwise falls back to full/cities.tsv.

Explicit tier loading

gen.load(dasmig::dataset::lite); // ~25k major cities
gen.load(dasmig::dataset::full); // ~200k cities (can combine)
City generator that produces random cities from GeoNames data.
Definition citygen.hpp:140
void load(const std::filesystem::path &tsv_path)
Load city data from a TSV file.
Definition citygen.hpp:323

If your resources are elsewhere, call load() with a direct path:

dasmig::cg::instance().load("/opt/data/cities.tsv");

Calling load() multiple times is safe — each call adds to the existing data.

Generating Cities

Random (population-weighted)

std::string text = c; // implicit conversion — returns the city name
city get_city()
Generate a random city.
Definition citygen.hpp:171

Country-filtered

auto c = dasmig::cg::instance().get_city("US");
// c.country_code == "US"

City Fields

Every generated city includes all 16 GeoNames fields:

Field Type Description
geonameid uint32_t GeoNames primary key
name string UTF-8 city name
asciiname string ASCII transliteration
latitude double WGS84 decimal degrees
longitude double WGS84 decimal degrees
feature_code string PPL, PPLA, PPLC, etc.
country_code string ISO-3166 two-letter
cc2 string Alternate country codes
admin1_code string State/province
admin2_code string County/district
admin3_code string Township/commune
admin4_code string Sub-district
population uint64_t City population
elevation int16_t Meters (-9999 if unknown)
dem int16_t Digital elevation model
timezone string IANA timezone ID
auto c = gen.get_city();
std::cout << c.name << " (" << c.country_code << ")\n";
std::cout << "Coordinates: " << c.latitude << ", " << c.longitude << "\n";
std::cout << "Population: " << c.population << "\n";
std::cout << "Timezone: " << c.timezone << "\n";
std::string name
UTF-8 city name.
Definition citygen.hpp:49

Country Filtering

Pass an ISO-3166 two-letter country code to restrict generation:

auto jp = gen.get_city("JP"); // Japanese city
auto de = gen.get_city("DE"); // German city

Country-filtered selection is also population-weighted within the country. Throws std::invalid_argument if no cities match the code.

Population Weighting

By default, cities are selected with probability proportional to their population. A city with 1,000,000 inhabitants is ~1000× more likely to be selected than one with 1,000. Cities with zero population are assigned a minimum weight of 1, ensuring they can still appear.

Uniform Selection

Switch to equal-probability selection where every loaded city has the same chance of being picked, regardless of population:

auto& gen = dasmig::cg::instance();
gen.weighted(false); // uniform random
auto c = gen.get_city(); // any city equally likely
auto j = gen.get_city("JP"); // uniform within Japan
gen.weighted(true); // restore population-weighted (default)
cg & weighted(bool enable)
Set whether generation is population-weighted or uniform.
Definition citygen.hpp:288

The weighted() setter returns *this for chaining:

gen.weighted(false).seed(42); // uniform + deterministic
cg & seed(std::uint64_t seed_value)
Seed the internal random engine for deterministic sequences.
Definition citygen.hpp:263

Query the current mode with:

bool is_weighted = gen.weighted(); // true by default

Seeding and Deterministic Generation

Per-call seeding

Pass an explicit seed to get_city() to produce the same city every time:

// Always produces the same city for the same data + seed.

Seed replay

Every city records its seed. Retrieve it with city::seed() and pass it back to reproduce the exact same result:

auto saved_seed = c.seed();
// Later, replay:
auto replay = dasmig::cg::instance().get_city(saved_seed);
// replay.geonameid == c.geonameid
std::uint64_t seed() const
Retrieve the random seed used to generate this city.
Definition citygen.hpp:96

Sequence seeding

Seed the generator's engine for a reproducible sequence:

auto& gen = dasmig::cg::instance();
gen.seed(100);
auto a = gen.get_city();
auto b = gen.get_city();
gen.seed(100); // reset
auto a2 = gen.get_city(); // identical to a
auto b2 = gen.get_city(); // identical to b
gen.unseed(); // restore non-deterministic behavior
cg & unseed()
Reseed the engine with a non-deterministic source.
Definition citygen.hpp:273

Country filter with seed

auto c = gen.get_city("BR", 42);
// Deterministic within Brazilian cities.

Note: To replay a country-filtered city, pass both the country code and the seed: gen.get_city("BR", c.seed()). Using just c.seed() without the country filter will select from the full dataset and likely produce a different city.

Multi-Instance Support

Construct independent generators with their own data and engine:

dasmig::cg my_gen;
my_gen.load("path/to/cities.tsv");
auto c = my_gen.get_city();

Data Pipeline

The shipped datasets are generated from GeoNames using the included Python script:

python scripts/prepare_geonames.py # generate both tiers
python scripts/prepare_geonames.py --tier full # full only (~200k cities)
python scripts/prepare_geonames.py --tier lite # lite only (~25k cities)

This produces resources/full/cities.tsv and resources/lite/cities.tsv.

The GeoNames data is licensed under CC BY 4.0. See LICENSE_DATA.txt.

Thread Safety

Each cg instance is independent. The static instance() singleton uses a local static for safe initialization.

Operation Thread-safe?
instance() Yes (static local)
get_city() on different instances Yes
get_city() on the same instance No — requires external synchronization
load() No — must not be called concurrently with get_city() on the same instance

Call load() once during initialization before spawning threads. For concurrent generation, give each thread its own cg instance.

Error Reference

Exception Condition
std::runtime_error get_city() called with no data loaded
std::invalid_argument get_city(country) with unknown country code