| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A curated, example-driven reference for PostGIS ST_* functions and spatial operators — usable from the terminal, as a single self-contained HTML page, or as structured data.
Ships a hand-written dataset of 92 PostGIS functions and operators in data/functions/*.yaml. Every entry carries more than a signature:
A few interfaces read that one dataset:
| Command | Purpose |
|---|---|
| python -m st_cheatsheet <query> | ranked terminal search |
| python -m st_cheatsheet show <name> | full syntax-highlighted card |
| python -m st_cheatsheet build --out dist/ | one self-contained, searchable HTML file |
| python -m st_cheatsheet export --format json | structured data for other tools |
| python -m st_cheatsheet validate | schema-check the dataset (CI-ready) |
| python -m st_cheatsheet verify --dsn ... | run every SQL example against a live PostGIS and check the stated result |
The official PostGIS reference is complete and accurate, and that is exactly the problem when you are mid-query. It tells you that ST_DWithin(geometry, geometry, double precision) returns a boolean. It does not lead with the three things that actually cost you an afternoon:
This tool front-loads those three things. Each entry is built around the SRID behaviour, the index behaviour, and the mistakes people actually make — with a worked example whose printed result you can check against your own database.
Cloned and run in place; there is no package to install.
git clone <this repo> cd st-function-cheatsheet python3 -m venv .venv .venv/bin/pip install -r requirements.txt
Python 3.10 or newer. Runtime dependencies are PyYAML and rich. For the test suite, use requirements-dev.txt instead.
The verify subcommand additionally needs a PostgreSQL driver, kept out of the base requirements so that everything else works without one:
.venv/bin/pip install -r requirements-verify.txt
A bare argument is treated as a query. Search covers name, summary and tags, and tolerates typos:
$ python -m st_cheatsheet knn 4 match(es) for 'knn' ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓ ┃ name ┃ category ┃ idx ┃ summary ┃ matched ┃ ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩ │ <#> │ operators │ yes │ Returns the distance between the bounding box… │ tag │ │ <-> │ operators │ yes │ The KNN distance operator. │ tag │ │ ST_DWithin │ relationships │ yes │ Returns true when the two geometries are with… │ summary │ │ ST_ClosestPoint │ processing │ no │ Returns the point on the first geometry that … │ summary │ └─────────────────┴───────────────┴─────┴────────────────────────────────────────────────┴─────────┘
The idx column is the quick answer to "can this use my GiST index?". Misspellings still land:
$ python -m st_cheatsheet buffr 1 match(es) for 'buffr' ┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓ ┃ name ┃ category ┃ idx ┃ summary ┃ matched ┃ ┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩ │ ST_Buffer │ processing │ no │ Returns a polygon covering every … │ fuzzy name (91%) │ └───────────┴────────────┴─────┴────────────────────────────────────┴──────────────────┘
An unknown name exits non-zero and suggests neighbours:
$ python -m st_cheatsheet show ST_Buffr error: no entry named 'ST_Buffr' did you mean: ST_Buffer, ST_Boundary? $ echo $? 1
$ python -m st_cheatsheet show '<->'
╭─ <-> ────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ Signature │
│ geometry <-> geometry -> double precision │
│ geography <-> geography -> double precision │
│ │
│ Summary │
│ The KNN distance operator. In an ORDER BY it lets a GiST index return rows in order │
│ of increasing distance from a reference point, which is the only way to get a │
│ nearest-neighbour query that does not read the whole table. Since PostGIS 2.2 on │
│ PostgreSQL 9.5+ it returns true geometry-to-geometry distance, not box distance. │
│ │
│ Category operators │
│ Returns double precision │
│ Since PostGIS 2.0 (true distance since 2.2) │
│ Index GiST-indexable - sargable │
│ Tags knn, nearest-neighbour, distance, order-by, index, operator │
│ │
│ Arguments │
│ name type description │
│ A geometry Left operand. For index use, this must be the indexed │
│ column. │
│ B geometry Right operand, normally a constant reference geometry. │
│ │
│ SQL │
│ SELECT ROUND(('POINT(0 0)'::geometry <-> 'POINT(3 4)'::geometry)::numeric, 3) AS d; │
│ Result │
│ d │
│ ------- │
│ 5.000 │
│ │
│ psycopg │
│ with conn.cursor() as cur: │
│ cur.execute( │
│ """ │
│ SELECT id, name, geom <-> ST_SetSRID(ST_MakePoint(%s, %s), 4326) AS dist │
│ FROM stops │
│ ORDER BY geom <-> ST_SetSRID(ST_MakePoint(%s, %s), 4326) │
│ LIMIT 10 │
│ """, │
│ (lon, lat, lon, lat), │
│ ) │
│ nearest = cur.fetchall() │
│ │
│ GeoAlchemy2 │
│ from sqlalchemy import select │
│ from geoalchemy2.functions import ST_MakePoint, ST_SetSRID │
│ │
│ here = ST_SetSRID(ST_MakePoint(lon, lat), 4326) │
│ stmt = select(Stop.id, │
│ Stop.name).order_by(Stop.geom.distance_centroid(here)).limit(10) │
│ │
│ SRID notes │
│ On geometry the result is in SRID units, so on 4326 you get degrees - useless as a │
│ distance but perfectly valid as an ordering key at small extents. For metres either │
│ cast both sides to geography (the geography <-> is also index-assisted) or store a │
│ projected column. Ordering by degrees and ordering by metres differ noticeably at │
│ high latitudes. │
│ │
│ Index usage │
│ Index-assisted only in an ORDER BY with a LIMIT, where one side is a constant and │
│ the other is the indexed column. Put it in a WHERE clause and you get a sequential │
│ scan; use ST_DWithin there instead. │
│ │
│ Common mistakes │
│ - Using <-> in WHERE (geom <-> point < 1000). That is not index-assisted - use │
│ ST_DWithin for a radius filter. │
│ - Omitting LIMIT, which makes the planner prefer a sort over the index scan and │
│ reads every row. │
│ - Reading the geometry result as metres on SRID 4326, where it is degrees. │
│ │
│ See also <#>, ST_DWithin, ST_Distance, ST_ClosestPoint │
│ Guide https://www.postgis-python.com/mastering-core-spatial-query-patterns/ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────╯
--snippet prints the raw text with no framing, so it pipes cleanly into an editor or the clipboard:
$ python -m st_cheatsheet show ST_Subdivide --snippet geoalchemy
from sqlalchemy import select
from geoalchemy2.functions import ST_Subdivide
stmt = select(Country.id, ST_Subdivide(Country.geom, 128).label("part"))
$ python -m st_cheatsheet show ST_DWithin --snippet sql | pbcopy
$ python -m st_cheatsheet categories Categories ┏━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ category ┃ n ┃ description ┃ ┡━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ constructors │ 10 │ Build geometry values from coordinates, text, binary or GeoJSON. │ │ accessors │ 13 │ Inspect a geometry: type, dimension, SRID, coordinates, validity. │ │ measurement │ 10 │ Distances, areas, lengths and perimeters. │ │ relationships │ 13 │ Boolean spatial predicates and the DE-9IM model behind them. │ │ processing │ 19 │ Derive new geometries: buffers, unions, simplification, clustering. │ │ editors │ 9 │ Modify an existing geometry in place: SRID, validity, densification. │ │ output │ 8 │ Serialise geometry to text, JSON, binary or vector tiles. │ │ operators │ 6 │ Bounding-box and KNN operators that drive index access. │ │ utility │ 4 │ Version, configuration and housekeeping helpers. │ └─────────────────┴─────┴──────────────────────────────────────────────────────────────────────────┘
--index-only narrows any listing to the functions a GiST index can actually serve — 17 of the 92, which is itself the useful lesson:
$ python -m st_cheatsheet list --index-only --category operators 6 entries ┏━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ name ┃ category ┃ since ┃ returns ┃ summary ┃ ┡━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ && │ operators │ 1.0 │ boolean │ Returns true when the 2D bounding boxes of… │ │ &&& │ operators │ 2.0 │ boolean │ The n-dimensional counterpart of &&: it re… │ │ <#> │ operators │ 2.0 │ double precision │ Returns the distance between the bounding … │ │ <-> │ operators │ 2.0 (true d… │ double precision │ The KNN distance operator. │ │ @ │ operators │ 1.0 │ boolean │ Returns true when the bounding box of A is… │ │ ~ │ operators │ 1.0 │ boolean │ Returns true when the bounding box of A co… │ └──────┴───────────┴──────────────┴──────────────────┴─────────────────────────────────────────────┘
$ python -m st_cheatsheet build --out dist/ wrote dist/index.html (490 kB, 92 functions, no external requests)
That is the one command that produces the page; build output is not committed. The result is a single file you can open with file://, drop on an intranet, or commit to a wiki:
Every card is rendered server-side, so the page still reads, prints and deep-links with JavaScript disabled; the script only filters and reorders nodes that already exist.
$ python -m st_cheatsheet validate dataset is valid checked 92 entries across 9 categories
validate exits 1 on a bad dataset, which makes it a one-line CI gate. export emits the whole structured dataset, honouring --category and --index-only:
$ python -m st_cheatsheet export --format json --category operators --out examples/operators.json wrote examples/operators.json (6 functions)
examples/ holds two such artefacts: operators.json (all six spatial operators) and gist-indexable.json (every entry a GiST index can serve). --format ndjson emits one compact object per line for streaming consumers.
validate proves the dataset is well-formed. verify proves it is true: it executes every entry's example.sql against a real server, each in its own rolled-back transaction, and compares the output to the example.result printed beside it.
$ python -m st_cheatsheet verify --dsn postgresql://postgres:pw@127.0.0.1:5432/verify server PostGIS 3.4.3 / GEOS 3.9.0-CAPI-1.16.2 / 16.4 (Debian 16.4-1.pgdg110+2) matched 90 mismatched 0 failed 0 since-suspect 0 skipped 2 92 entries verified against PostGIS 3.4.3
The DSN may also come from $ST_CHEATSHEET_DSN or $DATABASE_URL. --verbose lists the skipped entries and the reason each was exempted; --category and --index-only narrow the run.
Values are compared as the server prints them. Once a first execution reveals the column types, a passthrough text loader is registered for each, so a boolean arrives as t rather than True — which is what the result blocks contain.
Not every entry can match everywhere, and the exemptions are declared per-entry in the data rather than hardcoded in the verifier (see the verify field below). Five outcomes are possible:
| Outcome | Meaning | Fails the build |
|---|---|---|
| matched | output reproduced the stated result exactly | — |
| mismatched | output differs and the entry claimed exact | yes |
| failed | the SQL errored on a server new enough to run it | yes |
| since-suspect | it ran and matched on a server older than the entry claims to need | only with --strict-since |
| skipped | exempt by mode, or unavailable below the entry's declared floor | — |
An example that errors on a server older than its own floor is skipped as unavailable, because that is evidence for the dataset rather than against it. The reverse case is the interesting one: if an entry claims since: 3.4 but its example runs and matches on PostGIS 3.3, the version claim is not supported by the software, and verify reports it as since-suspect. That is a documentation bug worth a human's attention, so it is surfaced by default but only fails the build under --strict-since.
The dataset is the product. data/functions/*.yaml is hand-written, one file per category, and the Python is a thin loader / searcher / renderer over it.
Validation is two-phase. Per-entry checks happen during parsing, in FunctionEntry.from_dict: required fields, types, known category, at least one signature, at least two common mistakes, https://-only docs_url, and — deliberately — rejection of unknown keys, so that a typo like retruns: fails loudly instead of silently dropping content. Errors name the file, index and function (measurement.yaml#3 [ST_Area]: ...). Cross-entry checks then run over the whole set in validate_dataset: duplicate names, see_also targets that do not resolve, and self-references. build refuses to run on a dataset that fails.
The verify field. An optional per-entry mapping that tells verify how to treat the stated result. It is optional and defaults to {mode: exact}, so a newly added entry is checked strictly unless someone deliberately exempts it — and every exemption has to say why.
| Key | Values | Meaning |
|---|---|---|
| mode | exact (default) | the stated result must reproduce on every supported server |
| version-string | the output is a version banner, so it can never equal a fixed literal; the call must still succeed and return something non-empty, but the value is not compared | |
| geos-sensitive | the output is geometry whose exact form depends on the linked GEOS version; a difference is reported with a diff for a human to read, but is not a build failure | |
| reason | free text | why the entry is exempt. Required for any non-exact mode and for min_version; an unexplained exemption is indistinguishable from a bug being hidden, so the schema rejects one |
| min_version | dotted version | the PostGIS version this example needs, when that is higher than the leading token of since. Normally unset — since is the floor |
min_version exists for the case where an example deliberately exercises an overload newer than the function itself. ST_Point dates from 1.0, but its example passes the SRID argument added in 3.2; without the override, servers between 1.0 and 3.2 would report a false failure instead of skipping the entry as unavailable. It is a narrow escape hatch, not a way to lower the bar: since remains the source of truth everywhere else.
Search ranking is explainable, not statistical. Score bands, best first: exact name (an st_ prefix is optional, so dwithin matches ST_DWithin), name prefix, name substring, exact tag, tag substring, summary substring, and finally a difflib similarity fallback above 0.62 to catch typos. Ties break on shorter name, so simplify returns ST_Simplify before ST_SimplifyPreserveTopology. Every result reports why it matched in the matched column. The bands are spaced widely enough that no lower-band bonus can outrank a higher band.
The HTML page embeds a JSON island of just the fields the client needs (name, slug, category, summary, tags, GiST flag) and reimplements the same band scoring in JavaScript, with a subsequence matcher in place of difflib. Ranking therefore differs slightly between terminal and page for typo-heavy queries; exact, prefix and substring behaviour is identical.
Limitations, honestly:
There is no config file; behaviour is entirely by flag.
| Flag | Effect |
|---|---|
| --data-dir DIR | load a different dataset directory (useful for testing your own entries) |
| --category NAME | restrict to one of the nine categories |
| --index-only | only functions a GiST index can serve |
| --snippet {sql,psycopg,geoalchemy} | print one raw snippet from show |
| --limit N | cap search results (default 20) |
| --format {json,ndjson} | export shape |
| --out PATH | build/export destination |
| --dsn DSN | verify target; falls back to $ST_CHEATSHEET_DSN, then $DATABASE_URL |
| --verbose | verify also lists the skipped entries and why each was exempted |
| --strict-since | verify treats a contradicted since value as a failure, not a warning |
| --no-color, --width N | force plain output or a fixed width, for piping and CI logs |
Exit codes: 0 success, 1 no results, validation failure, or a real verify mismatch, 2 usage error, 3 the dataset could not be loaded, the site could not be built, or verify could not reach a server.
Adding an entry is a matter of appending to the relevant YAML file and running validate; the schema is enforced, so an incomplete entry cannot reach the page.
$ .venv/bin/pip install -r requirements-dev.txt $ .venv/bin/python -m pytest 249 passed in 3.61s
The suite runs offline and needs no PostgreSQL, PostGIS, Docker or network — including the verify tests, which fake the connection but assert against psycopg's real exception hierarchy. It covers:
Tests prove the dataset is well-formed. They cannot prove it is true, so it is worth being explicit about where each field's authority comes from.
Every entry was checked field-by-field against its official PostGIS documentation page, which produced 24 corrections — mostly since versions (Availability: and Changed: mean different things, and a rename is not an introduction) and missing overloads in signatures. Every stated SQL result was recomputed by hand and none needed changing.
All 92 SQL examples are executed on every push by the verify job, each in its own rolled-back transaction, across five PostGIS releases. That check used to be an uncommitted local script against a single server; it is now a committed subcommand and a CI matrix, which is what makes the numbers below reproducible rather than remembered.
| Image | PostGIS | GEOS | matched | mismatched | failed | skipped |
|---|---|---|---|---|---|---|
| 14-3.3 | 3.3.4 | 3.9.0 | 90 | 0 | 0 | 2 |
| 15-3.4 | 3.4.3 | 3.9.0 | 90 | 0 | 0 | 2 |
| 16-3.4 | 3.4.3 | 3.9.0 | 90 | 0 | 0 | 2 |
| 17-3.5 | 3.5.2 | 3.9.0 | 90 | 0 | 0 | 2 |
| 18-3.6 | 3.6.4 | 3.13.1 | 89 | 0 | 0 | 3 |
The examples are written defensively — rounded magnitudes, feature counts and boolean assertions rather than raw coordinate dumps — which is why 89 of 92 are byte-identical across a GEOS 3.9 to 3.13 span. Three entries are exempt, each with its reason recorded in the data:
One since-related correction came out of this. ST_Point's example passes the three-argument SRID form, which the entry's own since string dates to 3.2, while its leading version is 1.0 — so the example had a higher floor than the function. Confirmed by running it against postgis/postgis:13-3.1, which fails with function st_point(numeric, numeric, integer) does not exist. The since prose was already correct and was left alone; the example now declares verify.min_version: "3.2", so servers below 3.2 skip it as unavailable instead of reporting a false failure.
No since value was contradicted in the other direction. The since-suspect check — an entry whose example runs and matches on a server older than it claims to need — fired zero times, but that is a weak result rather than a clean bill of health: the highest floor in the dataset is ST_TileEnvelope at 3.0, and the oldest image in the matrix is 3.3, so there is currently nothing in range for it to catch. The check earns its place on the next entry that claims 3.4 or later. Note also that it can only test the leading version in a since string; parenthetical claims like "geography since 2.0" describe overloads the examples do not exercise, and remain doc-sourced.
What that verification can and cannot back:
If you find something wrong, an issue with the doc sentence that settles it is the fastest way to get it fixed.
The docs_url on an entry points at a deeper guide for that specific topic. The ones referenced by this dataset:
MIT — see LICENSE.
| Back | FazBrowse Home | New Git URL |