Skip to main content

mockgeo

PyPI version Python version support License: MIT Code style: Ruff

Deterministic synthetic geospatial test data for unit tests, demos, tutorials and benchmarks.

mockgeo builds a believable small city from a seed: a noded street network, blocks, parcels, building footprints with heights and uses, named POIs, parks and ponds. On top of it you get simulated GPS tracks, trip and OD tables, a GTFS-like transit feed, a census table, fractal DEMs and land-cover grids. Everything is topologically valid, reproducible byte-for-byte, and written in pure Python with no required dependencies.

pip install mockgeo
mockgeo city --seed 1 --size 2km -o city.geojson
  • Zero dependencies. The core uses only the standard library, so it installs fast in any CI job. NumPy, Shapely/GeoPandas and Faker are optional extras.
  • Deterministic. The same seed and arguments give identical bytes, independent of PYTHONHASHSEED and of the order in which you call generators.
  • Valid by construction, and checked. Rings are closed and counter-clockwise, no ring self-intersects, parcels lie inside blocks and buildings inside parcels, POIs inside buildings, GPS ground truth on the streets. City.validate() checks all of it again.
  • Batteries for testing. A pytest plugin with ready fixtures (mock_city, mock_points, mock_tracks), a Faker provider, and validators for GeoJSON, GPX and GTFS that you can reuse in your own tests.
  • Obviously fake. Cities are centred in the open North Atlantic (-35.0, 42.0) by default and names are recombined from small built-in word lists, so mock data cannot be mistaken for a real place, business or person. Pass origin=(lon, lat) to move it.

Installation

pip install mockgeo                 # standard library only
pip install "mockgeo[numpy]"        # Raster.to_numpy(), interop.to_numpy()
pip install "mockgeo[geo]"          # Shapely / GeoPandas bridges
pip install "mockgeo[faker]"        # mockgeo.faker.GeoProvider
pip install "mockgeo[dev]"          # pytest, pytest-cov, ruff, mypy

Python 3.9 to 3.13 on Windows, macOS and Linux.

Quickstart

import mockgeo

city = mockgeo.generate_city(seed=1, size="2km", layout="organic", locale="en_GB")
print(city.name, city.summary())
assert city.validate() == []                 # topology re-checked

city.to_geojson("city.geojson")              # all layers, WGS84 lon/lat (RFC 7946)
buildings = city.features(["buildings"])     # list of GeoJSON-like dicts
local = city.features(["parcels"], crs="local")  # metres from the city centre

tracks = mockgeo.gps_tracks(city, n=5, seed=1, mode="bike", noise=4.0)
open("tracks.gpx", "w", encoding="utf-8").write(mockgeo.dumps_gpx(tracks))

feed = mockgeo.transit_feed(city, n_routes=4, seed=1)
assert mockgeo.validate_gtfs(feed) == []
feed.write("feed.zip")

dem = mockgeo.dem(257, cell_size=10, seed=1, hills=3, valley=True)
dem.to_ascii_grid("dem.asc")                 # opens in QGIS / GDAL / rasterio

What you can generate

Points

Function Pattern
uniform_points(n, bbox=..., polygon=...) Complete spatial randomness in a box or any polygon
thomas_points(n_clusters, mean_size, sigma, ...) Thomas cluster process (Gaussian offspring)
matern_points(n_clusters, mean_size, radius, ...) Matern cluster process (uniform disc offspring)
points_along_line(line, n= / spacing=, jitter=) Even, random or jittered positions along a polyline
density_weighted_points(n, density, bbox) Intensity from a function f(x, y) or a 2D weight grid

Clustered processes simulate parents in an enlarged window to avoid edge effects, and accept n= to return exactly that many points. points_to_features() wraps any of them as GeoJSON features.

Synthetic city

generate_city(seed, size, layout, locale, origin, block_size, irregularity, name)

Layout Street pattern
grid Manhattan grid with arterials every few blocks; irregularity jitters the nodes
organic Voronoi diagram of a perturbed lattice, irregular junctions, perimeter road
radial Concentric ring roads and spokes; spokes double outwards to keep blocks small

Layers (city.layers[...], or city.features(layers) for export copies):

Layer Geometry Main attributes
streets LineString per segment name, highway (primary / secondary / tertiary / residential), maxspeed, lanes, width, way_id
intersections Point degree, traffic_signals
blocks Polygon land_use, zone (downtown / inner / outer), postcode, area_m2
parcels Polygon block_id, street, housenumber, address
buildings Polygon use, floors, height, roof, year_built, population, address
pois Point category, name, address, opening_hours, within
parks Polygon name, area_m2
water Polygon park_id, water = pond

How it stays valid: every street face is convex; blocks are faces shrunk by half the right-of-way of each bounding street; parcels come from recursive splits of the block across its longest edge (convex pieces of a convex polygon); buildings are set back and scaled inside their parcel, with some L shapes that are validated before use. House numbers run odd on the left and even on the right, increasing along each named street.

A building as exported (city.features(["buildings"])[0], coordinates shortened):

{
  "type": "Feature",
  "geometry": {"type": "Polygon", "coordinates": [[[-35.0118464, 41.9912307],
    [-35.0117906, 41.9912307], [-35.0117906, 41.991193], [-35.0116767, 41.991193],
    [-35.0116767, 41.9913104], [-35.0118464, 41.9913104], [-35.0118464, 41.9912307]]]},
  "properties": {
    "layer": "buildings", "id": "bldg-0", "parcel_id": "parcel-0", "block_id": "block-0",
    "use": "residential", "floors": 2, "height": 8.7, "roof": "gabled", "year_built": 1958,
    "area_m2": 163.7, "population": 8, "street": "Pine Avenue", "housenumber": 1,
    "postcode": "37996", "address": "1 Pine Avenue, Fairdale 37996"
  }
}

A POI and a street segment:

{"type": "Feature", "geometry": {"type": "Point", "coordinates": [-35.0073403, 41.9915604]},
 "properties": {"layer": "pois", "id": "poi-0", "category": "restaurant",
  "name": "The Highland Kitchen", "within": "bldg-20",
  "address": "4 Lake Terrace, Fairdale 37996", "opening_hours": "Mo-Fr 09:00-17:00; Sa 10:00-14:00"}}

{"type": "Feature", "geometry": {"type": "LineString",
  "coordinates": [[-35.0121015, 41.9910068], [-35.0105889, 41.9910068]]},
 "properties": {"layer": "streets", "id": "street-0", "name": "Pine Avenue", "highway": "primary",
  "maxspeed": 50, "lanes": 4, "width": 24.0, "length_m": 125.0, "way_id": 0,
  "from_node": 0, "to_node": 1}}

city.network is a StreetNetwork with shortest_path(u, v, weight="length"|"time"), nearest_node, nearest_street, distance_to_network and is_connected.

Mobility

  • gps_tracks(city, n, seed, mode="car"|"bike"|"walk", interval, noise, stops, gaps, outlier_rate) simulates driving rather than interpolating: vehicles accelerate to the street's speed limit, slow for sharp turns, stop at red lights and in short congestion, and arrive at rest. The receiver adds AR(1)-correlated noise, can lose the signal for 15 to 90 s (the track is split into segments) and can emit multipath outliers. Each TrackPoint keeps true_x/true_y, which lie exactly on the network, so you can score map matching.
  • generate_trips(city, n) gives person trips from homes (weighted by population) to activity places (weighted by floor area) with purpose, mode, times and network distance.
  • od_matrix(city, trips, k) aggregates trips to a k x k zone grid (zones(city, k)).
  • transit_feed(city, n_routes, headway_min) writes agency, stops, routes, trips, stop_times, calendar, shapes and feed_info; validate_gtfs() checks required columns, unique ids, references, increasing sequences and times. Zips are byte-reproducible.

Track rows (track.rows(), as written by mockgeo tracks -o tracks.csv):

track_id,seq,time,lon,lat,speed_ms,segment,outlier
track-0,0,2026-01-05T08:46:08Z,-35.00757,42.0082671,0.0,0,0
track-0,1,2026-01-05T08:46:09Z,-35.0075445,42.0082774,1.8,0,0
track-0,2,2026-01-05T08:46:10Z,-35.0075311,42.0082745,3.6,0,0

Rasters

  • dem(size, cell_size, seed, roughness, relief, base, hills, valley): diamond-square fractal terrain, optional Gaussian hills/basins and a meandering river valley.
  • fractal_noise(size, seed, octaves, persistence, scale): value-noise fBm in [0, 1].
  • landcover(size, seed, elevation=None): water, wetland, grassland, cropland, forest, urban and bare rock (LANDCOVER_CLASSES) driven by elevation and moisture noise.
  • Raster offers sample(x, y) (bilinear), stats(), to_ascii_grid() (Esri ASCII), to_xyz() and to_numpy().

Attributes and names

NameGenerator(locale) builds street, city, park, person and POI names, postcodes and addresses in the local order for en_US, en_GB, de_DE, fr_FR, es_ES, it_IT and tr_TR. For example, tr_TR gives Bahar Eczanesi | Defne Sokak No:4, 37095 Yeşilköy. census_blocks(city) returns block polygons with population (consistent with the buildings), households, three age bands, jobs and density.

Output formats

Format Functions CLI extension
GeoJSON (RFC 7946) dumps_geojson, write_geojson, City.to_geojson .geojson, .json
GeoJSONSeq dumps_geojsonseq(features, rs=False) (rs=True for RFC 8142) .geojsonl, .ndjson
CSV dumps_csv (x/y for points, WKT otherwise; plain rows too) .csv
WKT to_wkt, dumps_wkt .wkt
GPX 1.1 dumps_gpx(tracks, waypoints=pois) .gpx
GTFS TransitFeed.write("feed.zip") or a directory mockgeo gtfs
Esri ASCII grid / XYZ Raster.to_ascii_grid, Raster.to_xyz .asc, .xyz
GeoPandas / Shapely interop.to_geodataframe, interop.city_to_geodataframes, interop.to_shapely

validate_geojson() and validate_gpx() return a list of problems, so they double as assertions in your own tests.

pytest plugin

Installing mockgeo registers a pytest plugin (pytest11 entry point):

def test_my_spatial_join(mock_city, mock_points):
    buildings = mock_city.features(["buildings"])
    assert my_join(buildings, mock_points)

def test_my_map_matcher(mock_tracks, mock_city):
    for track in mock_tracks:
        matched = my_match(track, mock_city.network)
        ...

def test_bigger(mock_city_factory):
    city = mock_city_factory(seed=3, size="1500m", layout="radial")
Fixture Value
mockgeo_seed 42, or --mockgeo-seed, or mockgeo_seed = ... in the ini file
mock_city 600 m grid City
mock_city_factory callable taking generate_city arguments (cached per arguments)
mock_points 200 uniform lon/lat Point features over the city
mock_tracks 3 simulated car tracks on mock_city

Fixtures hand out deep copies of cached objects, so tests can mutate them safely. Disable the plugin with -p no:mockgeo.

Faker provider

from faker import Faker
from mockgeo.faker import GeoProvider

fake = Faker()
fake.add_provider(GeoProvider)
Faker.seed(42)

fake.geo_point()               # {"type": "Point", "coordinates": [lon, lat]}
fake.geo_polygon(n_vertices=7, radius_m=80)   # simple, closed, CCW
fake.building_footprint()      # Feature with floors, height, use
fake.geojson_feature_collection(10, "LineString")
fake.wkt_polygon()             # "POLYGON ((...))"
fake.mock_address("de_DE")     # e.g. "Lindenweg 12, 48151 Neuburg"
fake.poi("fr_FR")              # Feature with category, name, address

Command line

$ mockgeo city --seed 1 --size 2km -o city.geojson --summary
Fairdale: 8286 features -> city.geojson
{
  "streets": 808,
  "intersections": 425,
  "blocks": 384,
  "parcels": 3175,
  "buildings": 2990,
  "pois": 462,
  "parks": 29,
  "water": 13,
  "street_length_km": 84.0,
  "population": 49097
}

$ mockgeo points --n 1000 --bbox 29.0,41.0,29.1,41.1 --process thomas -o pts.csv
1000 points -> pts.csv

$ mockgeo tracks --seed 1 --n 5 --mode bike -o bike.gpx
5 tracks, 694 fixes -> bike.gpx

$ mockgeo dem --size 513 --hills 3 --valley -o dem.asc
DEM 513x513, 0.5-304.5 m -> dem.asc

$ mockgeo gtfs --seed 1 --size 2km -o feed.zip
4 routes, 25 stops, 776 trips -> feed.zip

$ mockgeo trips --size 2km --n 500 --od --zones 3
origin,destination,trips,mean_distance_m
Z00,Z01,1,750.0
Z00,Z02,3,1597.2
...

Sub-commands: city, points, tracks, dem, landcover, gtfs, trips. Without -o the result goes to stdout (UTF-8), the summary line to stderr. Run mockgeo <command> --help for every option; python -m mockgeo works too.

Determinism

Every generator draws from random.Random streams seeded with a SHA-512 hash of (seed, generator name). Seeds may be integers or strings. Output is identical across runs and machines with the same Python version; coordinates are rounded on export (7 decimals for lon/lat, 3 for metres) so last-bit floating point differences between platforms do not show up. Pass seed=None for fresh randomness.

Development

git clone https://github.com/YusufEminoglu/mockgeo_sdk.git
cd mockgeo_sdk
pip install -e ".[dev,numpy,geo,faker]"
python -m pytest --cov=mockgeo
python -m ruff check . && python -m ruff format --check src tests
python -m mypy src

Citation

See CITATION.cff.

License

MIT, see LICENSE.

Metadata

Release files for mockgeo 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mockgeo 0.1.1
File Size Uploaded Provenance Actions
mockgeo-0.1.1.tar.gz 85.6 kB With provenance Details for mockgeo-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mockgeo 0.1.1
File Interpreter ABI Platform Actions
mockgeo-0.1.1-py3-none-any.whl Python 3 none any Details for mockgeo-0.1.1-py3-none-any.whl

Total release size: 156.7 kB

Release files / mockgeo-0.1.1.tar.gz

Download URL mockgeo-0.1.1.tar.gz
Size 85.6 kB
Tags Source
SHA-256 checksum
How to use checksums
f5005b3ffdf5ecdf5a6c8bed7b5437c966de8ec0b28f46a67c52729a3fdeb133
BLAKE2b-256 checksum
How to use checksums
6e62200c414e05c72ef6f48dbd41e2f08cc5742eb4099fe1a65f807eb662c86c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

Release files / mockgeo-0.1.1-py3-none-any.whl

Download URL mockgeo-0.1.1-py3-none-any.whl
Size 71.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6ee461222e5f5e1432ae8f909eb255351cec617d3989f1fc8f15e114de1d9757
BLAKE2b-256 checksum
How to use checksums
862746deb2ea5610119f969a7faae4b62690f9ec89449c94730fbcf7198f18fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

Security for mockgeo 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mockgeo-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page