The rustgis package runs the RustGIS engine inside Python, headless. It opens data and
projects, runs engine commands and geoprocessing tools, styles layers, renders maps and saves
projects that the desktop app opens as they are. There is no app to install and no server to run:
the engine is a native module for Python 3.12 and later. Release wheels cover Windows x64,
macOS Intel and Apple Silicon, and Linux x64 and arm64 (glibc 2.28 or later).
pip install rustgis # pip install "rustgis[geopandas]" for GeoDataFramesA getting-started notebook walks through what follows; open it in Colab to try it without installing anything.
import rustgis
s = rustgis.Session(sample=True) # the World Atlas sample
s.layer_names() # ['Cities', 'Rivers', 'Lakes', 'Countries']
buffers = s.run_tool("buffer", input=s["Cities"], distance=100)
s["Countries"].style("graduated", field="pop_est")
s.zoom_to(buffers)
s.to_png("map.png", width=1600, height=900)
s.save("atlas.rgis")In Jupyter, a session on the last line of a cell shows its map as a picture. For the app itself,
use a Map.
s["Cities"] (or s.layer("Cities")) is a Layer: one layer of the active map. It is kept by
its id, so it is still the same layer after a rename, and its properties read the session each
time.
cities = s["Cities"] # by name (any case) or id
cities.count, cities.type, cities.fields, cities.bounds
"Cities" in s # True
[layer.name for layer in s] # every layer, top first
cities.style("proportional", field="pop_max") # layer.symbology; returns the layer
cities.visible = False
cities.set_transparency(30)
cities.filter("pop_max > 1000000") # a definition query; None clears it
cities.rename("Towns")
s.undo() # restore the name "Cities"
cities.zoom_to()
cities.remove()
s.undo() # restore the removed layerSelections work by SQL where clause:
n = cities.select("pop_max > 10000000") # mode="add", "remove" or "subset"
cities.to_geojson(selected=True) # only the selected features
cities.clear_selection() # clears selections in every layerrustgis.Map shows the RustGIS app (ribbon, panes, map) in the cell, kept in step with its
session: what Python adds or styles appears in the app, and what you do in the app (an edit, a new
layer, a pan) is back in the session for Python to read. It needs
anywidget, and works in JupyterLab, Notebook, VS Code and Colab.
pip install "rustgis[jupyter]"m = rustgis.Map(sample=True) # height="800px"; lang="de" sets the language
m # the app, in the cell
m.add_geojson("sites.geojson", name="Sites") # appears in the app
m["Sites"].style("unique", field="name")
m.set_center(10, 50, scale=5_000_000)
m.add_tiles("https://tile.openstreetmap.org/{z}/{x}/{y}.png", name="OpenStreetMap")
m.info()["center"] # where the app's map is now
m.save("sites.rgis") # with what was done in the app
m.run_in_app("ui.language", code="de") # a command only the app hasMap shares the command, data and view methods of Session (run, run_tool, add_data,
m["Sites"], to_png, ...); its layers' changes also appear in the app. m.session is the
underlying session; Map(session=s) displays an existing one. Make changes through m, rather
than directly through m.session, to send them to the app.
Supported files up to 2 MiB added through Map travel with the project. Larger files and formats
that cannot be embedded are read from a path only Python can see: they draw in to_png but not
in the app. Use a zip for a Shapefile. The whole project goes across after each change, so
simultaneous changes do not merge: the later one wins. A change made in the app replaces Python's
project, so m.undo() undoes only what Python did since.
The app is the web app, loaded from https://web.rustgis.app, so the
notebook needs to be online. Map(url=...) or the RUSTGIS_WEB_URL environment variable points
it at another copy (a self-hosted one, or trunk serve in apps/rustgis-web). Settings you
change in the widget last until the cell is run again: an embedded app keeps nothing in the
browser.
The project, with the data that travels with it, goes to that app through the browser
(postMessage); nothing goes to a server. The app runs in your browser, so with the default
address your data stays on your machine. Point url= only at a copy of the app you trust.
Engine actions are commands with an id and JSON parameters: the same ids the ribbon, the
command line and the AI Assistant use.
Session.run(id, ...) runs them, with parameters as keyword arguments, a positional dict, or
both (keyword arguments win). It returns the decoded command result and raises
rustgis.RustGISError when the command fails. UI-only commands (ui.*) need
Map.run_in_app, as in the notebook example above.
s.commands() # every command, with its parameters
s.tools() # the geoprocessing tools
s.describe("layer.symbology") # one command's (or tool's) parameters
s.run("labels.field", layer="Cities", field="name")
s.run("map.goToXY", {"lon": 114.1, "lat": 22.4, "scale": 2_000_000})
s.notices() # drains (text, is_error) pairs since last call
s.undo()
s.redo()run_tool runs a geoprocessing tool and returns the layer it made (a list when it made several),
or the tool's own result for one that selects or makes a table. Layers can be given as Layer
objects or by name; tools take layers by name, so of two layers with the same name the upper one
is used:
s.describe("buffer")["params"] # input, distance, units, dissolve, output
rings = s.run_tool("buffer", input=s["Cities"], distance=50, units="Kilometers")
rings.style("single").set_transparency(50)The common view commands have shortcuts too:
s.set_center(114.1, 22.4, scale=2_000_000) # bearing= and pitch= tilt and turn it
s.zoom_to(s["Cities"]) # or a box: (west, south, east, north)
s.bounds # what the map shows now
s.set_basemap("paper") # paper, midnight, ocean, atlas, blank
s.add_tiles("https://tile.openstreetmap.org/{z}/{x}/{y}.png", name="OpenStreetMap")The GeoLibre and Whitebox tools run as they do in the app, through the geolibre program
(see Analysis). rustgis.load_plugins(), or
Session(plugins=True), loads the plugins installed for the desktop app.
s = rustgis.Session(sample=True, data=["parcels.gpkg"])
roads = s.add_data("roads.fgb", name="Roads")
sites = s.add_geojson({
"type": "FeatureCollection",
"features": [{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10, 50]},
"properties": {"name": "Site A"}}],
}, name="Sites")
gdf = s["Cities"].to_geopandas() # a GeoDataFrame in EPSG:4326
big = s.add_geopandas(gdf[gdf.pop_max > 5_000_000], name="Big cities")
s["Cities"].to_geojson() # a FeatureCollection dict
big.to_file("big-cities.gpkg", crs="EPSG:3857") # GeoJSON, CSV, FlatGeobuf, GeoParquet, …add_geojson also takes any object with __geo_interface__ (a Shapely geometry, a GeoSeries).
add_data, add_geojson and add_geopandas return a Layer for a single layer, a list for
multiple layers, or the command result for standalone tables. For example, a CSV without
geometry is a table. Extra add_data keyword arguments go to layer.addData, such as layer=
to choose a GeoPackage table, or x= and y= to choose coordinate columns.
Every format the app reads works here: GeoJSON, CSV, Shapefile, GeoPackage, GeoParquet,
FlatGeobuf, KML/KMZ, GPX, GeoTIFF and the rest of Supported Data
Formats. A large file read in place keeps only the features of the
last view in memory; run layer.loadAll on it before to_geojson or to_geopandas to get all
of them.
Open an existing project with Session(project="atlas.rgis") or s.open("atlas.rgis").
s.project_json() returns its JSON text; s.run("project.openText", text=...) opens project
JSON without an intermediate file. to_png() returns PNG bytes even when it also writes a
file; omit the path to keep the image in memory.
The package lives in apps/rustgis-python, built with maturin.
Install the source-build prerequisites and use a Python 3.12+
virtual environment. From the repository root:
python -m pip install "./apps/rustgis-python[test]"
python -m pytest apps/rustgis-python/testsFor an editable development install, activate the virtual environment, then:
python -m pip install "maturin>=1.9.3,<2"
cd apps/rustgis-python
maturin develop --releaseIts version is the RustGIS version, and each release publishes wheels to PyPI.