Skip to content

Data

Formats

Every format, with how to add it and its limits, is listed on Supported Data Formats.

Format Read Write
GeoJSON (.geojson, .json) Yes Yes
Delimited text (.csv, .tsv, .txt) with x/y columns in any coordinate system, or WKT geometry Yes Yes
Shapefile (.shp + .dbf, or zipped in a .zip) Yes (the web app opens zips) Yes (share.exportLayer)
GeoPackage (.gpkg, every feature table) Yes Yes (desktop)
GeoParquet (.parquet, .geoparquet, WKB geometry) Yes Yes
FlatGeobuf (.fgb) Yes Yes (share.exportLayer)
KML/KMZ (with their styles), GPX Yes Yes
Spreadsheets (.xlsx, .xls, .ods), GeoRSS feeds, LandXML, encoded polylines Yes No
GML, MapInfo MIF/MID, OpenStreetMap PBF, TopoJSON, AutoCAD DXF (ASCII) Yes No
LiDAR point clouds: LAS, LAZ, COPC (an evenly spread sample) Yes No
Geotagged photos (JPEG with EXIF GPS): points with their thumbnails Yes No
Esri File Geodatabase (.gdb folder; 3D multipatches as footprints) Yes (desktop) No
Through GDAL: AutoCAD DWG, MapInfo TAB, OpenStreetMap XML, File Geodatabases RustGIS can't read itself… Yes (desktop, when GDAL is installed) No
PostgreSQL/PostGIS databases, read live (large tables a view at a time) Yes (desktop) No
MySQL and MariaDB databases, read live (large tables a view at a time) Yes (desktop) No
DuckDB databases (.duckdb, .ddb) and SQL queries Yes (desktop, when DuckDB is installed) No
KML, GPX, CSV with WKT geometry No Yes (Analyze › Workflows › Data Interop)
GeoTIFF and cloud-optimised GeoTIFF (.tif, .tiff) Yes Yes
ASCII grid (.asc, with an optional .prj) Yes No
Through GDAL: JPEG 2000, ERDAS Imagine, SRTM HGT, DTED, NetCDF, HDF, ENVI, SAGA… Yes (desktop, when GDAL is installed) No
Web tiles: XYZ/TMS/WMS rasters, Mapbox Vector Tiles, MapLibre styles, PMTiles and MBTiles archives Yes Yes (Analyze › Create Hosted Imagery)
RustGIS project (.rgis; older .rgisx files open too) Yes Yes

Add data with Home › Add Data (From URL… adds a file by its web address, reading a cloud-optimised GeoTIFF, GeoParquet or FlatGeobuf in place, a view at a time, and downloading others; see Files on the web), by dropping files on the window, or by passing them on the command line (rustgis places.geojson). Home › Add Data › Delimited Text… asks for a CSV or a spreadsheet first, then lists its columns to pick the x and y (or WKT) ones from, with the coordinate system (such as EPSG:2263) and the separator (changing it reads the columns again); info.fileColumns lists them for scripts. Add Data finds them itself (columns named like longitude/latitude, easting/northing or geometry, and EWKT's SRID=) and says when coordinates aren't longitude/latitude. A CSV without coordinate columns, or a layer without geometry, opens as a standalone table, listed under the map in Layers; right-click it to open or remove it. Tables can share a name: commands and tools take a table's id (info.project lists them) as well as its name, which stands for the first table of that name. An id given as a number is never taken for a name. The Tools pane and Geocode Addresses send the id of the table picked; a tool's run keeps the table's name beside it (input_name). The XY Table To Point tool (Geoprocessing, and a table's right-click menu) makes points of a table already in the project, from two fields of coordinates and their coordinate system. In a text field a single comma is the decimal separator (1,234 is 1.234); thousands separators are not read. Home › Add Data › Graphics Layer adds a layer for drawn graphics, and Data › Layer Templates adds empty layers with ready-made schemas to draw into.

Vector coordinates are WGS 84 longitude/latitude and are displayed in Web Mercator. GeoPackage, GeoParquet and FlatGeobuf files in another coordinate system (any EPSG code) are converted as they are read. Rasters keep their own coordinate system (any EPSG code, such as UTM zones or national grids) and are reprojected on the fly as they draw.

Formats through GDAL

When GDAL is installed (its ogr2ogr and ogrinfo programs on the PATH, or the folder holding them in RUSTGIS_GDAL_BIN; GDAL 3.7 or later), the desktop app also opens the vector formats GDAL reads: DWG, MapInfo TAB, OpenStreetMap XML, File Geodatabases RustGIS can't read itself (compressed ones, or ones with raster fields) and more. RustGIS doesn't link to GDAL: each layer is converted once, by ogr2ogr, into a FlatGeobuf with a spatial index in WGS 84 (GDAL reprojects it), kept in the cache folder (~/.cache/rustgis/gdal on Linux, ~/Library/Caches/RustGIS/gdal on macOS, %LOCALAPPDATA%\RustGIS\cache\gdal on Windows, or RUSTGIS_GDAL_CACHE). Adding the file again reuses the conversion until the file changes, and large layers stream like any large FlatGeobuf (below). Every layer of the file is added; a layer mixing geometry types is added as one layer per type. Curves become straight segments, and 3D surfaces (an Esri multipatch, such as the buildings of swissBUILDINGS3D, is read as a TIN) become flat polygons, one part per face, without their heights. Projects remember the original file and convert it again if the cached copy is gone.

SQL queries and DuckDB databases

When DuckDB is installed (the duckdb program on the PATH, or the folder holding it in RUSTGIS_DUCKDB_BIN; DuckDB 1.5 or later), the desktop app adds the result of an SQL query as a layer, with the layer.addSql command (Add Data From SQL in command search):

{"sql": "SELECT name, geometry FROM read_parquet('places.parquet') WHERE country = 'FR'", "name": "French places"}

Analyze › SQL opens the SQL pane, a workspace for these queries: an editor (Ctrl+Enter runs the query) that completes table and column names of the database named above it and DuckDB Spatial's common functions, the result's first 200 rows as a table, Add to Map for a result with a geometry column, and a history of the queries run (kept by the app with its preferences, without the database's password; a query that seems to hold a credential, such as user:password@ in an address, a signed URL or a secret, isn't kept). Queries run in the background, so the app keeps drawing while DuckDB works. The commands behind it are sql.query (a query's first rows, up to 1,000, without adding a layer) and sql.schema (a database's tables and their columns); both take "database" as layer.addSql does. The web app has no DuckDB, so the pane says to open the project in the desktop app.

The query is one DuckDB SELECT (or FROM …, WITH …), with DuckDB Spatial's ST_ functions: it can read Parquet, CSV, JSON and GeoParquet files, anything ST_Read reads, and web addresses (https://, s3://), so a bounding box of Overture Maps is one query:

SELECT id, names.primary AS name, geometry
FROM read_parquet('s3://overturemaps-us-west-2/release/2026-09-23.1/theme=places/type=place/*', hive_partitioning = 1)
WHERE bbox.xmin BETWEEN -83.95 AND -83.89 AND bbox.ymin BETWEEN 35.94 AND 35.98

The first geometry column becomes the layer's geometry; other columns become its attributes (types RustGIS doesn't read, such as dates and lists, as text). A geometry that says its coordinate system is converted to longitude/latitude; give "crs" (such as EPSG:2263) for one that doesn't. A result mixing geometry types becomes a layer per type, and rows without a geometry are left out. "database" opens a DuckDB database (read only) so the query names its tables plainly; the database is trusted like the query, since its views run as part of it.

Home › Add Data opens a DuckDB database file (.duckdb, .ddb): every table with a geometry column becomes a layer ("layer" picks one).

PostgreSQL and PostGIS

Home › Add Data › Database… (layer.addDatabase) connects to a PostgreSQL database with PostGIS and reads its tables live. RustGIS talks to the server itself (nothing else to install; TLS as libpq does it). sslmode=prefer (the default) and require encrypt the connection but don't check the server's certificate, so they don't protect against a server in the middle: use verify-full (or verify-ca, which here checks the host name too) on networks you don't trust. The certificate is checked against sslrootcert (a private authority's PEM file) or the web's roots. disable turns TLS off. The connection is a libpq connection string (host=localhost port=5432 dbname=gis user=postgres) or a postgresql://user@host/dbname address; what it leaves out comes from PGHOST, PGPORT, PGUSER and PGDATABASE, as with libpq. List Tables (info.databaseTables) shows the tables and views with a geometry or geography column, and every one of them becomes a layer unless one is chosen (table, schema.table, or table:column for one of several geometry columns). The connection is read only.

A table of more than 100,000 rows (or any, with "stream": true) is read in place a view at a time, like a large GeoParquet: the view's box is matched against the column's spatial index, and when more rows match than a view draws, an evenly spread sample of them is numbered on the server, so only those cross the network. Smaller tables are loaded whole, so they can be edited. The server converts geometry to longitude/latitude (ST_Transform, so every coordinate system in its spatial_ref_sys works) and curves to lines; give "crs" (such as EPSG:2263) for a column whose geometry doesn't say its coordinate system. A column of mixed geometry types becomes a layer per type, and empty tables (such as the TIGER geocoder's) are left out. Features keep their ids between views: the table's integer primary key, else the row's place in the table (the rows of a view or a partitioned table without a key are numbered as read, so theirs can change between views).

The password is kept until RustGIS closes and never written to the project, which names the connection without it. Without one, PGPASSWORD or libpq's password file (~/.pgpass, or %APPDATA%\postgresql\pgpass.conf) serve when you add a table. A project keeps a streamed table's connection and reads it again when it opens, sending only a password given in this session: a project file could name any host, and must never get your own credentials. When the server wants a password, connect again with Add Data › Database… (the layer reads again when the project is next opened). A small table's features are kept in the project as they were when added.

layer.addSql takes a connection as its "database" too, through DuckDB's postgres extension (installed the first time, like DuckDB Spatial), so a query names its tables plainly; the password reaches DuckDB in its environment (PGPASSWORD), never on its command line:

rustgis-cli run --cmd 'layer.addSql={"sql":"SELECT name, geom FROM roads WHERE lanes > 2","database":"postgres:host=localhost dbname=gis user=postgres","password":"…"}'

RustGIS doesn't link to DuckDB: it runs duckdb, which writes the result as GeoParquet in the cache folder (~/.cache/rustgis/duckdb on Linux, ~/Library/Caches/RustGIS/duckdb on macOS, %LOCALAPPDATA%\RustGIS\cache\duckdb on Windows, or RUSTGIS_DUCKDB_CACHE), sorted along a Hilbert curve with a bounding-box covering, so large results stream like any large GeoParquet (below). DuckDB Spatial is installed the first time (a download from DuckDB's extension repository). A database table is converted once and reused until the file changes; a query runs afresh each time. Projects convert a table again if the cached result is gone, but never run a query when they open: run it again. A database is read on its own, shut out of other files and web addresses, so a view in it that reaches elsewhere fails rather than reading them.

A query can read any file the app can and fetch any web address, and a database is a file or a server, so the AI Assistant asks before it runs a query or reads a database, and plugins need both the files and network permissions.

MySQL and MariaDB

Home › Add Data › Database… (layer.addDatabase) also connects to a MySQL (8 or later) or MariaDB (10.2 or later) database and reads its spatial columns live, as it does PostGIS tables: RustGIS talks to the server itself, with nothing else to install. The connection is an address, mysql://user@host:3306/database (or mariadb://…); what it leaves out is, as with the mysql client, localhost, port 3306 and your login name. List Tables (info.databaseTables) shows the database's tables and views with a geometry column, and every one becomes a layer unless one is chosen (table, or table:column for one of several geometry columns). An address without a database lists the tables of every database but the server's own, as database.table. The session is read only, and a server asking for a local file (LOAD DATA LOCAL INFILE) is sent none.

ssl-mode in the address's query sets the encryption, as the mysql client names it: PREFERRED (the default) encrypts, connecting unencrypted only when the server offers no encryption (the layer then says so), and REQUIRED always encrypts; neither checks the server's certificate, so neither protects the password from a server in the middle. On networks you don't trust, use VERIFY_IDENTITY, which checks the certificate and the host name (against ssl-ca, a private authority's PEM file, or the web's roots); VERIFY_CA checks the certificate only. DISABLED turns encryption off:

rustgis-cli run --cmd 'layer.addDatabase={"database":"mysql://gis@db.example:3306/city?ssl-mode=VERIFY_IDENTITY","password":"…"}' \
  --cmd 'share.exportMap={"path":"city.png"}'

Large tables are read a view at a time through the column's spatial index (MBRIntersects), and a capped view is an evenly spread sample numbered on the server; smaller ones are loaded whole. RustGIS converts the geometry to longitude/latitude itself (MariaDB has no ST_Transform), from the column's SRID (MySQL's per-column SRID, MariaDB's REF_SYSTEM_ID) or else its rows'; give "crs" (such as EPSG:2263) for a column whose SRID is 0. A GEOMETRY column of mixed types becomes a layer per type. Features keep their ids between views from the table's integer primary key; a table without one has its rows numbered as read, so theirs can change between views.

The password is kept until RustGIS closes and never written to the project; MYSQL_PWD serves when you add a table without one, and a project opening sends only a password given in this session, as for PostGIS. SQL queries (layer.addSql, the SQL pane) don't run on MySQL or MariaDB databases.

SQL Server and Azure SQL

Home › Add Data › Database… (layer.addDatabase) reads the geometry and geography columns of SQL Server 2012 or later and Azure SQL tables and views directly, on the desktop. Use mssql://user@host:1433/database; an empty host means localhost, and omitting the database uses the login's default database. List Tables (info.databaseTables) lists spatial columns. Choose a table as table, schema.table or table:column, or leave the choice empty to add all. For a named instance, use ?instance=SQLEXPRESS without a port so SQL Browser can discover it.

The connection is encrypted and its certificate checked against the platform's trust store by default (encrypt=mandatory, also required, true or yes). encrypt=strict selects TDS 8.0 strict encryption on servers that support it. A private authority can be supplied as ?ca=/path/to/ca.pem, and hostname-in-certificate overrides the expected certificate name. For a self-signed server, ?trust-server-certificate=true disables certificate checking; the layer reports that it is unchecked. ?encrypt=false (also optional or no) requests encryption for login only, not subsequent traffic; certificate validation still applies during login unless explicitly disabled with trust-server-certificate=true. connect-timeout sets a positive connection deadline in seconds (default: 10, maximum: 300).

rustgis-cli run --cmd 'layer.addDatabase={"database":"mssql://gis@db.example:1433/city","password":"…"}' \
  --cmd 'share.exportMap={"path":"city.png"}'

SQL Server logins work on every desktop platform. On Windows, DOMAIN%5Cuser with a password uses a Windows login: %5C is the URL encoding of \, and a literal DOMAIN\user is also accepted in the address (escape it as DOMAIN\\user inside JSON). Leaving the user out signs in as the current Windows user. These Windows logins are not supported on macOS or Linux: give a SQL Server login instead. Passwords can be supplied in user-info or the password, pass, pwd, or passwd query parameters; the separate password field overrides an address's password.

Large tables read a view at a time using their spatial index, with a server-numbered sample when more rows match than a view draws; small tables are loaded whole. Curves are linearized and coordinates converted to longitude/latitude. Only rows with the first non-null geometry's SRID are read; give "crs" for SRID 0. Mixed geometry families become separate layers. An integer primary key provides stable feature ids; without one (including views), ids are numbered as read.

At database compatibility level 100 or lower, streamed geography layers filter their whole-table read or sample locally instead of sending a viewport polygon to the server. A small view can therefore miss features in a large sampled table. geometry layers and higher compatibility levels retain server-side viewport filtering.

The provider only reads data; edits are not written back to SQL Server. The password is kept until RustGIS closes, never in the project. Secret-named query values such as pwd and access_token are removed from saved connection addresses. SQLCMDPASSWORD serves only when adding a database, not when reopening a project. A streamed layer reopening sends only credentials given this session; Windows integrated sign-in also requires that same address to have been added this session. Reconnect with Add Data › Database… when needed. SQL queries (layer.addSql, the SQL pane) do not run on SQL Server connections.

Web Data

Home › Web Data gathers public web services and catalogs: STAC imagery catalogs, Overture Maps, OpenStreetMap, NASA Earthdata, open-data portals, satellite embeddings and more. They have a page of their own: Web Data.

Geocoding addresses

Home › Add Data › Geocode Addresses… (layer.geocode) turns a table's addresses into points: a CSV of addresses without coordinates is added as a standalone table, then each row's address fields ("address": a field, or several such as street, city and country, joined with commas) are looked up, each distinct address once. The result is a new point layer holding every field plus geocoded, what the geocoder called the place; rows that weren't found stay in its table without a point.

The geocoder is OpenStreetMap's public Nominatim by default, or Photon ("provider": "photon"). Their usage policies allow light use, so RustGIS sends them at most one request a second and 100 addresses a run; give "url" of a Nominatim or Photon server of your own for more (up to 10,000, with no wait between requests). In the app the requests go out in the background, one at a time, and the layer is added when the last answer lands; layer.geocode on the command line waits for them.

rustgis-cli run --cmd 'layer.addData={"path":"stores.csv"}' --cmd 'layer.geocode={"table":"stores","address":["street","city","country"]}'

The addresses are sent to the geocoder's server, so the AI Assistant asks before it geocodes, and plugins need the network permission.

S3 buckets

An s3://bucket/key address works wherever data is added by its address: Add Data › From URL… and layer.addUrl (a cloud-optimised GeoTIFF is read in place by range, other files are downloaded), PMTiles archives, ?data= links, and STAC assets whose address is an s3:// one. A public bucket needs nothing more: it is read anonymously from AWS.

Home › Add Data › S3… (s3.connect) connects to a private bucket, or to an S3-compatible service (MinIO, Cloudflare R2, Wasabi, source.coop…) by its endpoint, and browses a bucket a folder at a time (s3.list), adding an object with Add. A connection names its endpoint (AWS when empty), its region and the buckets it serves: the ones it is given ("buckets") and the ones browsed through it this session. An s3:// address of one of them is read through that connection, signed (AWS Signature Version 4) with its keys; an address of any other bucket (a project's, a link's) is read anonymously from AWS, never with your keys or from your endpoint. Keys are typed, or, on the desktop and only when chosen, read from an AWS profile ("profile": ~/.aws/credentials and ~/.aws/config) or from the environment ("environment": true: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION); without any, the connection is anonymous. AWS, anonymous in the dialog browses with no keys at all.

rustgis-cli run --cmd 's3.connect={"name":"minio","endpoint":"https://minio.example.org","buckets":["my-bucket"],"accessKeyId":"…","secretAccessKey":"…"}' \
  --cmd 's3.list={"url":"s3://my-bucket/rasters/"}' --cmd 'layer.addUrl={"url":"s3://my-bucket/rasters/dem.tif"}'

Keys are kept until RustGIS closes: never in a project (which keeps the s3:// address), a command's result, a notice or the log. Remember keeps a connection's name, endpoint and region in the app's settings, never its secret; storing secrets in the system keychain isn't supported. Keys go only to the endpoint they're for: a signed request that is redirected isn't followed, and keys from a profile or the environment are used only with AWS or the profile's own endpoint_url. In the web app, the bucket's CORS rules must allow the page's origin (and, for a private bucket, the Authorization and x-amz-* headers). The AI Assistant asks before it connects or lists; plugins can't connect, and need the network permission to list.

Data Sources

The Data Sources pane (View › Windows › Data Sources, ui.pane.sources) gathers the places data comes from:

  • Favourites: any source, right-clicked and added.
  • Recent: the last 20 sources added (files, web addresses, tile layers, services, database tables, Zarr variables, STAC assets, sample data), newest first.
  • Folders: the folders connected to the Browser (Add Folder…; desktop).
  • Services, Databases (desktop) and STAC APIs: addresses saved with the box under each. A click opens Add From Service, Add From Database or STAC Search connected to it, which lists its layers, tables or collections.
  • S3: this session's connections; a click opens the S3 browser on it.
  • Library: well-known public sources: OpenStreetMap tiles, EOX Sentinel-2 cloudless and NASA GIBS (WMTS), USGS imagery, topographic and hydrography maps (ArcGIS), GEBCO bathymetry (WMS), swisstopo's maps and imagery (geo.admin.ch, WMTS), Earth Search and Planetary Computer (STAC), and the Natural Earth sample layers.

A click on a file, a web address, a tile layer or a sample adds it at once. The lists are kept with the app's preferences, never in a project. They hold no secrets: a web address loses its password and any key, token or signature in its query (token=, sig=, X-Amz-*…), and a database is kept without its password. The commands sources.list, sources.save, sources.favourite and sources.remove read and change the lists, for the command line and the AI Assistant. On the command line they last for the run.

Named connections

The connection.* commands keep named, reusable endpoints independently of the project. Each has a stable id (c1, c2, …), a case-insensitively unique name and an optional group. Different names may use the same address. connection.new, connection.edit, connection.duplicate and connection.remove manage them; commands that take id accept either the id or the name. connection.list can filter by kind or group, and reports availability, session login state and the commands to use to add data. Names cannot match a stored id or the reserved c + digits id pattern (case-insensitive). Loaded names that conflict get a numeric suffix; ids and endpoints stay unchanged. Clearing a name with connection.edit chooses a unique default from the address.

rustgis-cli run \
  --cmd 'connection.new={"kind":"wms","address":"https://ows.terrestris.de/osm/service","name":"OSM WMS","group":"Public"}' \
  --cmd 'connection.test={"id":"OSM WMS"}' \
  --cmd 'connection.list={"group":"public"}'

Kinds are postgres, mysql (including MariaDB), sqlserver, service (detected on connect), wms, wmts, wfs, wcs, ogcapi, arcgis, csw, stac and s3. Database connections are used on the desktop only. S3 options are region, buckets (comma-separated), credentials (keys, profile, environment or none) and profile. connection.test lists the endpoint's tables, layers, records, collections or buckets; the web app's network listings still require the existing add dialogs.

Addresses are saved without passwords or tokens. connection.login remembers a database password, or S3 accessKeyId, secretAccessKey and optional sessionToken, for this session only, using the same credential stores as the existing add commands. With login parameters, connection.test logs in first; a failed listing does not undo that session login. S3 profiles and environment credentials remain subject to the existing endpoint restrictions; editing an S3 name, endpoint or options disconnects its session login. S3 shares the name-based session registry used by the S3 dialog and s3.connect, so editing or removing a saved connection also affects a session entry of the same name created there. Renaming clears the runtime entries under both the old and new names, so the renamed connection cannot inherit an unrelated login at its destination name. Web-service logins are not supported. Plugins may list connections but cannot manage, log in to or test them. The in-app assistant asks for approval each time for those operations; external assistants using MCP cannot run them. Database passwords are shared by public DSN for the session, including existing layers and other named connections using that DSN. Editing or removing a named connection does not revoke that shared login; changing its address does not transfer the old password to the new address.

Desktop and web apps save connections with their preferences (connections in desktop prefs.json, rustgis.connections in web localStorage). The first load imports saved services, databases other than DuckDB files, STAC APIs and remembered S3 connections once, without changing the old lists. The Data Sources pane and add dialogs are unchanged. CLI and Python connections last only for the run.

Large files

GeoPackage, GeoParquet, FlatGeobuf and (on the desktop) Shapefile files with more than 100,000 features are read in place rather than loaded whole, avoiding an in-memory copy of the entire dataset. The layer reads features in view in the background as the map moves, using spatial indexes: the GeoPackage R-tree, the FlatGeobuf packed R-tree, GeoParquet row-group bounding boxes, or a cached Shapefile R-tree built from the shapes' bounding boxes. When a view holds more than 250,000 features (100,000 in the web app), the layer draws an evenly spread sample of them and its tooltip in Layers says so: zoom in to see them all. Smaller files load whole; layer.addData with "stream": true or false overrides the choice.

On the desktop, opening a large Shapefile for the first time scans its record headers to build a disposable SQLite index outside the dataset directory. Reopening an unchanged file reuses that index; its canonical path, size and modification time identify the cached generation. The record directory and R-tree stay on disk, with an 8 MiB SQLite page-cache target per open index and memory mapping disabled. Query results retain file order and the same sampling rules. Attributes and the .prj are still read from the source files.

Set RUSTGIS_SHP_CACHE to choose the cache directory and RUSTGIS_SHP_CACHE_MIB to choose the retained index budget (2048 MiB by default, clamped to 64–65536 MiB). Idle indexes are evicted in least-recently-used order. Open readers and builders hold operating-system leases, so another instance cannot evict their indexes; active files can temporarily exceed the budget. An index larger than the budget is session-only. Cache checkout and release reclaim abandoned builds and temporary indexes. A corrupt cache is rebuilt, and an unavailable cache directory falls back to the system temporary directory (a per-user directory on Unix). New Unix cache directories are created with mode 0700. Existing roots must belong to the effective user, forbid group/other writes, and have trusted parents; symlink roots are rejected. These checks also apply to configured locations. Unsafe locations fall back rather than changing their permissions. If neither location works, the Shapefile remains readable by scanning record headers without retaining a full in-memory index. Deleting idle cache files does not modify the dataset.

On the desktop, a GeoJSON FeatureCollection larger than 32 MB is read the same way, with no size limit: opening reads the file once, keeping each feature's place in it and its bounding box (a file over 256 MB through a buffer, so it is never held whole), and each view parses just the features it shows. A single feature, a bare geometry or TopoJSON is loaded whole.

A streamed layer works like any other for symbology, labels, pop-ups, selection and the attribute table (which lists the features read for the view). Geoprocessing tools read the whole file. Edits are refused, since the features are read again as the view moves: right-click the layer in Layers and choose Load All Features (layer.loadAll) to load it into memory and edit it. Projects save the layer's file path, not its features, and read the file again when they open; a browser upload is kept in the project.

File Features A view read
GeoParquet, 0.1° grid (50 MB) 1.6 million polygons 183,000 in 0.15 s
FlatGeobuf, the same grid (335 MB) 1.6 million polygons 183,000 in 0.11 s
GeoPackage, the same grid (344 MB) 1.6 million polygons 183,000 in 0.18 s
GeoParquet, global grid (190 MB) 6.5 million polygons a sample of 136,000 in 0.27 s

Streamed view memory

Interaction windows share a retained-memory budget across all source layers in a map, including hidden layers: 128 MiB on the desktop and 48 MiB in the browser. The budget counts allocated row capacity, attribute strings, nested geometry buffers, projected geometry, extent arrays and optional display geometry. Complex records can therefore reduce the interaction sample even below the existing feature-count limit. Source ids, attributes and file order are retained; the sample is marked as incomplete. An individual feature that cannot fit produces a layer error instead of silently disappearing.

Projection is prepared with the window on the source worker. Optional thinned geometry uses the remaining budget, up to 64 MiB per layer; when it cannot fit, drawing uses the original projected geometry. Explicitly loaded layers retain their full source data and limit only their optional display caches.

This is a budget for installed interaction caches, not a process-RSS or GPU-memory cap. Provider decoding scratch, in-flight reads, shared snapshots and explicit full reads are outside it. The generic provider adapter probes a small sample and trims the returned window; it does not bound the provider's transient decoding allocations. Blocking map exports retain their original row limits and reread instead of reusing a budget-reduced interaction window. Full-source loading, analysis, editing, data exports and project serialization continue to use the original source data and existing read limits.

Rasters

A shaded-relief elevation raster of the Sierra Nevada with the Style tab open
An elevation raster of the Sierra Nevada with shaded-relief symbology.

Add a GeoTIFF or an ASCII grid with Add Data, by dropping it on the window, or in the web app by picking it. An ASCII grid takes its coordinate system from a .prj file next to it (as a Shapefile does); otherwise, and always in the web app, which only sees the one file, it must be in longitude/latitude degrees. On the desktop, a GDAL .aux.xml file next to a raster (dem.tif.aux.xml or dem.aux.xml) names its bands from their descriptions, gives the no-data value when the file has none, and supplies the band statistics when only an overview of the raster is read. Exporting a raster opened from an overview writes that overview; the results of adding and of exporting it (layer.addData, share.exportRaster) give the full size as overviewOf, so scripts and the MCP server see it too. share.exportRaster writes an untiled GeoTIFF; with "cog": true it writes a cloud-optimised one: 512-pixel tiles compressed with Deflate, overviews (each half the size of the last, down to one tile), and every directory ahead of the tiles, so a reader fetches the header once and then only the tiles it needs. Overviews are averaged, except for a palette raster or one with a few whole-number classes, whose overviews take a pixel so that no class is invented; a palette keeps its colour table, though not the table's transparency, which a TIFF colour table can't hold (GDAL drops it too). The file is classic TIFF when it would fit in 4 GiB even uncompressed and BigTIFF when it might not. Exported to a file it is written tile by tile, never held whole in memory (the browser builds it in memory to download it). GeoTIFFs may be regular or cloud-optimised: grey, RGB(A), palette (coloured by the file's colour table, as land cover such as the NLCD is) or multiband, of any pixel type, tiled or striped, and compressed with Deflate, LZW, PackBits, JPEG or Zstandard. The coordinate system is an EPSG code, or the file's own projection described in its GeoKeys (Albers, Lambert conformal conic, transverse Mercator, Mercator, Lambert azimuthal, stereographic, equirectangular and others), which an exported GeoTIFF keeps. A raster too large to hold in memory (more than 120 million samples, 32 million in the web app) opens from its largest overview that fits; one without overviews opens as an overview made as it is read, a strip or tile at a time (every second, fourth… pixel each way), so reading it takes as long as decoding the whole file once. A cloud-optimised GeoTIFF added by its web address is read in place instead, a tile at a time (see Files on the web).

The web app opens a picked or dropped file of up to 256 MB whole. A larger GeoTIFF is read a part at a time instead: its header, then only the tiles of the overview it opens from. A cloud-optimised file opens however large it is, as long as those parts fit in 256 MB (a 436 MB EnMAP hyperspectral scene reads about 20 MB, the 1.6 GB NLCD land cover about 14 MB). The parts read are what a saved project keeps. A file that isn't cloud-optimised (in strips, or without overviews) needs most of itself, and past 256 MB the web app says to open it in the desktop app.

With a raster layer selected in Layers, the Style and Data tabs show its groups:

Tab › Group Controls
Style › Visibility Range, Effects Scale range, transparency
Style › Rendering Symbology: Stretch, RGB, Classify, Unique Values, Colormap, Shaded Relief; Stretch Type: None, Minimum Maximum, Custom Min/Max (your own bounds, one pair per colour channel on an RGB layer), Percent Clip, Standard Deviation, Histogram Equalize; DRA (stretch the pixels in view); Resampling (nearest or bilinear); Band Combination (natural colour, colour infrared, or any bands)
Style › Enhancement Brightness, contrast, gamma
Data Raster information (size, bands, statistics, coordinate system) and Export Raster (GeoTIFF)

Masking, at the foot of the Symbology pane (raster.mask), hides pixel values from view without changing the data: values to draw as no data (a background of 0 or −9999 the file doesn't flag, QA flags, land cover classes such as 0, 254, 255), and values outside, or inside, a range. The stretch, class breaks and legend then use only the values shown. Clear mask shows every value again; the mask is saved with the project.

A single-band raster of continuous values (an elevation model, a temperature grid, an index such as NDVI) is first drawn as an inverted grey stretch clipping 0.5 % at each end. To draw such rasters your own way, style one with the Stretch symbology, then click Save as Default under Resampling: rasters added from then on, opened files and analysis results (a slope, an NDVI) alike, take its color scheme, inversion, stretch type and resampling (a Custom Min/Max stretch is in one raster's own units, so it can't be saved). Reset Default goes back to the grey stretch. The app keeps the setting with its preferences, for every project; colour images, rasters with their own color table and rasters of a few categories keep their own look. Scripts use raster.setDefaultStyle, e.g. {"ramp": "Viridis", "invert": false, "stretch": "standardDeviation", "n": 2, "resampling": "bilinear"}, {"fromLayer": true} or {"reset": true}; with no parameters it returns the current setting.

Tiles of one dataset drawn as separate layers each stretch over their own values, which shows as steps where they meet. raster.stretch with {"like": "<layer>"} gives a layer the value range of another layer's stretch as fixed bounds, so the tiles match; {"type": "custom", "min": …, "max": …} sets the bounds yourself.

Clicking a raster with the Explore tool shows its pixel values. Projects remember a raster's file path and reopen it; rasters added in the browser are stored inside the project.

Zarr stores

Home › Add Data › Zarr… (layer.addZarr) adds a variable of a Zarr store (format 2 or 3), the multidimensional arrays climate and Earth-observation data are kept in: give the store's web address, or (on the desktop) its folder, and Connect lists its variables (raster.zarrInfo). A variable's map is its latitude/longitude (or y/x) dimensions; its other dimensions, such as time or pressure level, are picked a step at a time with a slider, labelled by their coordinates (CF time units as dates, the noleap and 360_day model calendars included). Add to Map adds that step as a raster layer named after the variable and step (tas 2014-07-15), and the dialog stays open to add others. Only the chunks the step needs are fetched, a few at a time, in the background.

Stores are read in pure Rust, in the web app as well as on the desktop: Zarr 2 with consolidated metadata (.zmetadata, as xarray writes) and Zarr 3 (zarr.json, sharded arrays too), chunks compressed with blosc (LZ4, zlib or zstd, byte- or bit-shuffled), zlib, gzip, zstd or LZ4, of integer or float values, in C or Fortran order. A sharded array's shards are fetched whole. _FillValue, missing_value and a Zarr 2 array's fill value are no data; a Zarr 3 array's fill value isn't: chunks never written are left empty, but chunks missing from inside a shard show the fill value. scale_factor and add_offset are applied. The coordinates must be evenly spaced (a curvilinear grid isn't read); longitude/latitude grids of 0–360° are turned to -180–180°, and projected ones take their coordinate system from the variable's grid mapping (crs_wkt, spatial_ref, an EPSG code) or "crs". Blosclz-compressed and transposed chunks and shards inside shards aren't read yet, and say so. A store by web address must allow the browser to fetch it (CORS) for the web app; Google Cloud's CMIP6 bucket does, ARCO-ERA5's doesn't (the desktop reads both).

rustgis-cli run --cmd 'layer.addZarr={"url":"https://storage.googleapis.com/gcp-public-data-arco-era5/ar/1959-2022-full_37-1h-0p25deg-chunk-1.zarr-v2","variable":"2m_temperature","index":{"time":539100}}'

Follow the time slider (desktop; layer.addZarr with "time": true) adds the variable as one layer whose step is the time slider's. The slider turns on over the variable's dates when it is off, and the layer shows the last of its steps in the slider's window, or nothing when none falls in it. Moving or playing the slider reads that step's slice (only its chunks), keeping the last eight read. A web store's next two steps are read ahead in the background. The stretch is fixed at the first slice's range, so a colour means the same value at every step, and Export writes an animation of it. An axis longer than the slider's 20,000 steps (hourly ERA5) takes a period with "start" and "end"; given neither (as the dialog adds it), the layer takes 20,000 steps from the one picked. The project keeps such a layer by its store (a folder or a web address) and selection, not as a slice, and reads the slider's step again when it opens.

rustgis-cli run --cmd 'layer.addZarr={"path":"air.zarr","variable":"t","time":true}' --cmd 'time.export={"path":"t.gif"}'

View › Time Series (desktop; zarr.series) reads a Zarr variable's values through time at a point. Pick a Zarr layer, then Pick a Point on the map. A layer that follows the time slider gives its period, a slice the whole axis, and "start"/"end" any other. Only the chunks holding that point's cell are read. A store chunked one step at a time, such as ARCO-ERA5's map-oriented copy, reads a chunk per step: about 4,000 steps at most, so a long axis needs a shorter period. The chart draws up to six points as lines and marks the time slider's step, and a click on it takes the slider to that date. Export CSV… writes the date and value of each point.

rustgis-cli run --cmd 'zarr.series={"path":"air.zarr","variable":"t","lon":-55.7,"lat":-12.5}'

Summarise over time (in the Time Series pane; zarr.reduce) makes a raster of one statistic of a Zarr variable's steps: mean, min, max, sum, std (the population standard deviation), count of values, or countAbove a threshold (days above 30 °C, say). It reads a layer's period by default, else the "start"/"end" given, and "months" keeps one season across years ([12, 1, 2]). Slices are read one at a time and folded into running totals (12 to 20 bytes a cell, for grids of up to 32 million cells), so memory depends on the grid, not on the period: at most 20,000 steps, with progress and Cancel in the app. A cell keeps no value when it had fewer values than "minValid" (a share, 0 by default) of the steps read; with the default, count and countAbove give 0 where a cell never had a value (land in an ocean variable, say), and a "minValid" above 0 leaves such cells empty. The layer is named after what it holds (tas mean 1991-01-01–2020-12-31) and kept in the project as a GeoTIFF.

rustgis-cli run --cmd 'zarr.reduce={"path":"air.zarr","variable":"t","stat":"mean","months":[12,1,2]}'

A single step (a layer that doesn't follow the slider) from a store in a folder is saved in the project by its folder and slice (2m_temperature[time=539100]) and read again when the project opens, as Add Data on the folder does (its first variable); a single step from a store by web address is kept in the project as the slice read. ?zarr=<address> in a web app link opens the dialog on a store.

Project items

RustGIS saves pieces of a project as files you can share and reuse:

Item Extension Made with Opened with
Map file (a map with its data) .rgismap Publish › Save As › Map File Home › Import
Layer file (a layer with its symbology) .rgislyr Publish › Save As › Layer File Add Data
Styles (saved symbology) .rgisstyle Style › Styles › New Style, then Publish › Save As › Style Items Style › Styles › Add Style File
Color ramps .rgisramp (also reads QGIS .xml/.qml, GDAL color .txt and GMT .cpt) Publish › Save As › Color Ramps, or by hand Style › Styles › Add Color Ramp File
Project template (the setup without features) .rgistemplate Home › Templates, or Publish › Save As Home › Templates › Open Custom Template
Project package (everything, rasters included) .rgispkg Publish › Package › Project Project › Open, or drop it on the window
Map package / mobile map package .rgismpk Publish › Package › Map or Mobile Map Home › Import
Results package (geoprocessing history and outputs) .rgisgpk Publish › Package › Results any zip tool
Task (guided command steps) .rgistask Analyze › Task, Publish › Save As › Task Item Analyze › Task › Import Task
Bookmarks (as longitude, latitude and scale) .rgisbookmarks Home › Navigate › Bookmarks › Export Bookmarks Home › Navigate › Bookmarks › Import Bookmarks
Layout .rgislayout Publish › Save Layout File Home › New Layout › Import Layout

Packages are zip files. A mobile map package keeps only the visible layers and simplifies their geometry, for phones and tablets. Styles can also be imported from the layers of a layer file, map file or project. Data › Layer Templates adds empty layers with ready-made schemas (places, routes, areas, observations…) to draw into. Add Folder connects a folder to the Browser, which also lists the project's layouts, styles and tasks. A task's steps each run a command; New Task From History turns the geoprocessing history into a task you can replay.

Complete streamed Shapefile rendering

The CPU renderer traverses a streamed Shapefile in source order instead of painting only its interaction sample. Shapes and attributes arrive in chunks targeting 8 MiB of owned feature storage; projection and display geometry are temporary copies of one chunk. Whole-file imports, loading for editing and other providers retain their existing read APIs. An indivisible row exceeding the chunk target is emitted alone, preserving its full geometry. A zero budget reports an error.

Styles, definition queries, display filters and label classes use the same painter as an in-memory layer. The renderer chooses geometry simplification from statistics over the whole layer, accumulates point bins across chunks, applies transparency and blend once per layer, and draws selection highlights after the whole layer. Labels share the existing global priority, stable tie ordering, collision rules and 20,000-candidate placement limit. Screen rendering and PNG/layout exports share this traversal. Live redraws of a streamed Shapefile therefore also read the complete source; line/polygon layers currently make an additional statistics pass. Interaction windows remain byte-budgeted, but they are not a shortcut for this complete painter, so redraws can be I/O intensive on large files.

Native flat extrusions use a private disposable SQLite ordering workspace (2 MiB page-cache target, memory mapping disabled, 4 GiB database ceiling), with a global shadow pass followed by globally sorted solids. It is removed when rendering finishes. Failure or incomplete traversal does not publish a partial successful frame. This workspace is not a reusable index.

This is not a fixed bound on total renderer memory: raster targets, label candidates and the existing combined polygon-mask path have separate costs. Upright perspective point markers and polygon extrusions retain the complete upstream depth renderer through an isolated full-source snapshot. Upright points require a known source total of at most 50,000 rows; larger or unknown totals use complete ground-marker traversal, independent of the current interaction-window size. Plain fills, lines and mask sources continue complete chunk traversal per band, including terrain bands; they do not consume that snapshot's feature budget. Retained complete-source feature storage across the snapshot has a separate budget (512 MiB natively, 192 MiB in the browser). Exceeding it fails the whole frame with a source diagnostic, without sampling or truncating features; raster targets, derived geometry caches and decoding scratch are outside that budget. Flat line/polygon rendering makes a statistics pass before painting and currently reads the entire source, so it trades additional I/O for complete rendering and predictable chunk storage. Geometry preprocessing and GPU preparation are separate optimizations.