Managing Large Geospatial Datasets Locally with DuckDB and the Spatial Extension
Introduction

Large geospatial datasets can expose the limitations of conventional desktop GIS workflows. Opening a multi-gigabyte vector file may involve parsing geometry, decoding attributes, building rendering structures, and loading data into memory before an analyst can begin meaningful exploration. When a project contains millions of GPS observations, road segments, building footprints, or environmental measurements, the result may be slow rendering, high RAM consumption, and an unresponsive interface.
A more efficient approach for many analytical workloads is to separate data processing from visualization.
DuckDB is an in-process analytical SQL database that executes queries directly within the application or process using it. Its columnar execution engine supports parallel scans, aggregation, joins, and larger-than-memory processing. The Spatial extension adds geospatial functions and GDAL-based integration with vector formats such as GeoJSON, GeoPackage, and Shapefile.
Unlike a traditional client-server spatial database, DuckDB does not require a separate database server. An analyst can query a file, filter millions of records, perform a spatial join, and export the result without first importing the entire source dataset into a permanent database table.
The architecture is straightforward:
Large geospatial files
|
v
DuckDB SQL
|
+-- Column and attribute filtering
|
+-- Geometry processing
|
+-- Spatial joins
|
+-- Spatial aggregation
|
v
Filtered analytical results
|
+-- GeoParquet
+-- GeoPackage
+-- Shapefile
|
v
QGIS / ArcGIS Pro / Python
The important distinction is that DuckDB does not eliminate the cost of reading data or calculating geometric relationships. Rather, it minimizes unnecessary materialization, takes advantage of columnar storage where possible, and executes analytical operations inside a database engine.
Some workloads involving millions of records can finish in seconds. Complex geometries, inefficient joins, slow disks, and memory-intensive operations can still require substantially longer.
This guide develops a reproducible local workflow using GeoParquet, GeoJSON, spatial joins, point-in-polygon aggregation, and GIS-format exports. It also explains when DuckDB is preferable to Python-based processing or a persistent PostGIS database.
1. Setting Up DuckDB Spatial Extension Large Datasets GIS
1.1 Install DuckDB
There are two convenient ways to use DuckDB locally: the command-line interface (CLI) and the Python API.
For Python:
python -m pip install duckdb
For the CLI, download the appropriate executable from the official DuckDB installation guide.
After installing the CLI and ensuring it is available on your system path, create or open a persistent local database:
duckdb gis_analytics.duckdb
The database file stores database objects and any tables you choose to create. Reading an external file with a query does not automatically copy that file into the database.
A typical project layout is:
gis_project/
├── data/
│ ├── observations.geoparquet
│ ├── buildings.geojson
│ └── districts.gpkg
├── output/
├── temp_duckdb/
└── gis_analytics.duckdb
The filenames and directory structure are illustrative; replace them with your own data paths.
1.2 Install and load the Spatial extension
Run the following SQL in the DuckDB shell:
-- Install the extension for this DuckDB environment.
INSTALL spatial;
-- Activate spatial functions in the current session.
LOAD spatial;
-- Confirm the running DuckDB version.
SELECT version();
INSTALL spatial downloads and installs the extension if it is not already available. LOAD spatial activates its functions for the current session. The extension is not automatically loaded in every session, so explicit loading is a reliable practice for reusable scripts.
The extension supplies functions such as:
| Function | Purpose |
|---|---|
ST_Read() |
Read supported vector formats through GDAL |
ST_Intersects() |
Test whether geometries intersect |
ST_Within() |
Test whether a geometry lies within another geometry |
ST_Area() |
Calculate planar geometry area |
ST_Distance() |
Calculate planar distance |
ST_Transform() |
Transform coordinates between coordinate reference systems |
ST_GeomFromWKB() |
Construct a geometry from Well-Known Binary data |
Consult the current Spatial extension overview and spatial function reference for version-specific capabilities and function signatures.
Version note: DuckDB 1.5 introduced native GEOMETRY support and GeoParquet reading and writing in core DuckDB. Spatial operations such as ST_Intersects() and GDAL-based vector-format integration still use the Spatial extension. Earlier versions can require additional geometry conversion when reading GeoParquet. See the GeoParquet tooling guidance.
1.3 Configure memory and temporary storage
A laptop must reserve RAM for the operating system, the terminal, the browser, and possibly QGIS or ArcGIS Pro.
Configure DuckDB to use a conservative memory budget and a temporary directory on a drive with sufficient free space:
-- Example values for a laptop with 8–16 GB RAM.
-- Adjust these settings for your actual hardware.
SET memory_limit = '4GB';
SET temp_directory = 'D:/duckdb_temp';
SET threads = 4;
-- Useful when importing or exporting very large datasets.
SET preserve_insertion_order = false;
Create the temporary directory before executing the SQL. On Linux, for example, it could be /mnt/fastssd/duckdb_temp/.
These settings are starting points, not universal defaults. DuckDB’s memory limit applies to its buffer manager, and some operations or external libraries can require additional memory. The temporary directory must have enough free space for intermediate results and spill files.
The official DuckDB memory and workload guide explains these controls and the limitations of out-of-core execution.
2. Reading Large GeoParquet Files Without Importing Them
GeoParquet is a strong choice for analytical vector processing. It combines Parquet’s columnar storage with metadata describing geometry columns, geometry encodings, and coordinate reference systems.
Because Parquet stores columns separately, DuckDB can often avoid reading fields that a query does not require. It can also push eligible attribute filters down to the Parquet scan. These properties are central to fast GeoParquet queries in DuckDB.
2.1 Inspect the schema before analyzing the file
Start with a schema inspection:
DESCRIBE
SELECT *
FROM read_parquet('data/observations.geoparquet');
Then inspect a small sample:
SELECT *
FROM read_parquet('data/observations.geoparquet')
LIMIT 10;
Suppose the source contains these fields:
observation_id
observed_at
measurement
land_use
geometry
The geometry field’s type depends on the DuckDB version and file encoding. In a current, standards-conforming GeoParquet workflow it may already be a GEOMETRY value. Older versions or differently encoded files may expose WKB as a binary value.
Inspect the type rather than assuming it.
For additional metadata inspection, DuckDB exposes Parquet key-value metadata:
SELECT
decode(key) AS metadata_key,
decode(value)->>'$.version' AS geoparquet_version,
decode(value)->>'$.primary_column' AS primary_geometry,
decode(value)->>'$.columns.geometry.encoding'
AS geometry_encoding
FROM parquet_kv_metadata(
'data/observations.geoparquet'
)
WHERE decode(key) = 'geo';
This example assumes the primary geometry column is named geometry. If the metadata reports a different name, update the relevant JSON path.
The metadata function returns the geometry encoding and the GeoParquet metadata version. See the official Parquet metadata documentation.
2.2 Query only the necessary columns
Suppose the task is to count agricultural observations by year.
SELECT
YEAR(observed_at) AS observation_year,
COUNT(*) AS observation_count
FROM read_parquet('data/observations.geoparquet')
WHERE land_use = 'agriculture'
GROUP BY YEAR(observed_at)
ORDER BY observation_year;
This query does not request the geometry column at all.
That matters because geometry values can be large, particularly when they contain detailed linework or complex polygons. If the analytical question only concerns attributes, carrying the geometry through every stage introduces unnecessary I/O and memory overhead.
The same principle applies to numerical summaries:
SELECT
AVG(measurement) AS mean_measurement,
MIN(measurement) AS minimum_measurement,
MAX(measurement) AS maximum_measurement
FROM read_parquet('data/observations.geoparquet')
WHERE observed_at >= TIMESTAMP '2025-01-01 00:00:00'
AND observed_at < TIMESTAMP '2026-01-01 00:00:00';
This query filters the records before calculating the statistics.
2.3 Query multiple Parquet files
Large datasets are often partitioned into several files by year, region, survey, or another attribute.
DuckDB can scan multiple compatible Parquet files using a glob pattern:
SELECT
COUNT(*) AS total_observations
FROM read_parquet('data/observations/*.parquet');
To aggregate across files:
SELECT
YEAR(observed_at) AS observation_year,
land_use,
COUNT(*) AS record_count,
AVG(measurement) AS mean_measurement
FROM read_parquet('data/observations/*.parquet')
GROUP BY
YEAR(observed_at),
land_use
ORDER BY
observation_year,
record_count DESC;
For efficient repeat queries, organize files so common filters align with useful partitioning or row-group statistics. Files with incompatible schemas may need to be normalized before combining them.
3. Reading Large GeoJSON Files Directly
GeoJSON is convenient for interoperability, but its text representation is generally less efficient for repeated large-scale analytical scans than columnar formats.
The Spatial extension’s GDAL-backed ST_Read() function can read a GeoJSON file directly:
SELECT *
FROM ST_Read('data/buildings.geojson')
LIMIT 10;
Summarize building types without first creating a permanent database table:
SELECT
building_type,
COUNT(*) AS building_count
FROM ST_Read('data/buildings.geojson')
GROUP BY building_type
ORDER BY building_count DESC;
The exact attribute names depend on the input dataset. Use DESCRIBE SELECT * FROM ST_Read(...) to inspect them before adapting the query.
For files containing only the records needed for a specific study area, a spatial filter can sometimes be pushed into the reader:
SELECT *
FROM ST_Read(
'data/buildings.geojson',
spatial_filter_box := {
'min_x': 36.70,
'min_y': -1.40,
'max_x': 37.10,
'max_y': -1.10
}::BOX_2D
);
This example assumes the input coordinates use longitude and latitude in the WGS 84 coordinate system. Change the bounds and coordinate reference system to match your data.
The filter is a bounding-box filter, not a substitute for an exact polygon predicate. In addition, whether the filter is pushed down efficiently depends on support in the underlying GDAL driver. The ST_Read() reader is documented as single-threaded, so very large GeoJSON inputs may remain slower to scan than well-organized GeoParquet inputs.
When a GeoJSON source will be queried repeatedly, a useful preprocessing strategy is to convert it once into GeoParquet. You can then use the resulting columnar file for recurring SQL analytics.
4. Preparing Spatial Views for Analysis
The next examples combine point observations with administrative polygons.
Assume the following source structure:
observations.geoparquet
observation_idobserved_atmeasurementgeometry
districts.gpkg
district_iddistrict_name- a polygon geometry column
Create reusable temporary views:
CREATE OR REPLACE TEMP VIEW observations AS
SELECT
observation_id,
observed_at,
measurement,
geometry AS geom
FROM read_parquet('data/observations.geoparquet');
CREATE OR REPLACE TEMP VIEW districts AS
SELECT
district_id,
district_name,
geom
FROM ST_Read('data/districts.gpkg');
The code assumes that the GeoParquet geometry column is already typed as GEOMETRY and that GDAL returns the polygon geometry under the name geom. Inspect the actual schemas and rename the geometry expression as needed.
For an older DuckDB version that exposes the GeoParquet geometry as WKB, change the relevant expression to:
ST_GeomFromWKB(geometry) AS geom
Use this conversion only when the input is actually WKB data.
These are temporary views, not copies of the full source datasets. DuckDB evaluates their underlying queries when they are used.
Before running any spatial join, confirm that both datasets use compatible coordinate reference systems. Spatial predicates work on coordinates; longitude and latitude cannot be compared directly with coordinates measured in projected metres.
5. Executing Fast Spatial Joins

Spatial joins associate records according to geographic relationships rather than matching a shared attribute.
Common examples include assigning GPS locations to administrative districts, identifying buildings that intersect flood-risk polygons, and associating roads with land-use zones.
5.1 Use ST_Intersects() to identify matching pairs
SELECT
o.observation_id,
d.district_id,
d.district_name
FROM observations AS o
JOIN districts AS d
ON ST_Intersects(o.geom, d.geom);
For every point-polygon pair whose geometries intersect, the query returns a record.
A critical detail is that ST_Intersects() includes boundary contact. A point lying on the border between adjacent polygons may match both. This is correct for an intersection analysis, but it may be undesirable for an administrative assignment in which every point must belong to exactly one district.
For strict point-in-polygon assignment, ST_Within(point, polygon) excludes a point that lies only on the polygon boundary. Choose the predicate according to the intended analytical rules.
5.2 Why the spatial join can be efficient
A naïve spatial join could compare every point with every polygon. For millions of points and thousands of polygons, the total number of candidate pairs would be prohibitively large.
DuckDB introduced a dedicated SPATIAL_JOIN operator in version 1.3.0. In its documented implementation, the operator builds a temporary R-tree over one input and uses spatial bounding boxes to reduce the number of candidate geometries requiring exact predicate evaluation.
For many workloads, this is substantially more efficient than a nested-loop join.
Inspect the actual execution plan:
EXPLAIN ANALYZE
SELECT
o.observation_id,
d.district_id
FROM observations AS o
JOIN districts AS d
ON ST_Intersects(o.geom, d.geom);
Look for the spatial join operator and review its runtime, input cardinality, and output cardinality. The DuckDB spatial join benchmark explains the operator’s design and the effects of different join strategies.
The documented implementation builds the temporary R-tree using its selected build-side input in memory. That means a very large polygon side may exceed the available memory even when DuckDB can stream the other input. If the build side is too large, restrict it to the study area, process the data in carefully designed partitions, or evaluate whether a persistent spatial database would better serve the workload.
6. Point-in-Polygon Aggregation
A common GIS task is calculating how many observations fall inside each district.
The following query produces a row for every district, including districts with no matching points:
SELECT
d.district_id,
d.district_name,
COUNT(o.observation_id) AS observation_count
FROM districts AS d
LEFT JOIN observations AS o
ON ST_Within(o.geom, d.geom)
GROUP BY
d.district_id,
d.district_name
ORDER BY observation_count DESC;
The use of COUNT(o.observation_id) rather than COUNT(*) is deliberate. A LEFT JOIN returns an unmatched row for a district without points; counting the non-null observation identifier correctly produces zero.
6.1 Calculate multiple statistics
If observations have a measurement field, calculate counts and descriptive statistics in the same query:
SELECT
d.district_id,
d.district_name,
COUNT(o.observation_id) AS observation_count,
AVG(o.measurement) AS mean_measurement,
MIN(o.measurement) AS minimum_measurement,
MAX(o.measurement) AS maximum_measurement
FROM districts AS d
LEFT JOIN observations AS o
ON ST_Within(o.geom, d.geom)
GROUP BY
d.district_id,
d.district_name
ORDER BY mean_measurement DESC;
This approach returns a compact analytical result rather than materializing millions of point-to-polygon matches in Python.
If administrative polygons overlap, or if the analytical rules count boundary observations more than once, the results may not sum to the original observation count. Validate the geographic topology and point-assignment rules before interpreting district totals.
6.2 Reattach geometries to the summary
For a mapping deliverable, aggregate using attribute keys and then join the summary to the district geometry:
WITH district_stats AS (
SELECT
d.district_id,
d.district_name,
COUNT(o.observation_id) AS observation_count,
AVG(o.measurement) AS mean_measurement
FROM districts AS d
LEFT JOIN observations AS o
ON ST_Within(o.geom, d.geom)
GROUP BY
d.district_id,
d.district_name
)
SELECT
s.district_id,
s.district_name,
s.observation_count,
s.mean_measurement,
d.geom
FROM district_stats AS s
JOIN districts AS d
ON s.district_id = d.district_id;
The output now contains both the statistics and the polygon geometry needed for choropleth mapping.
7. Exporting Filtered Results to GeoPackage and Shapefile
The final step is to export only the required results into a format suitable for downstream GIS use.
The Spatial extension supplies a GDAL-backed COPY operation for writing supported vector formats. See the official GDAL integration documentation.
7.1 Export a filtered result to GeoPackage
For example, export observations that intersect a selected district:
COPY (
SELECT
o.observation_id,
d.district_name,
o.geom
FROM observations AS o
JOIN districts AS d
ON ST_Intersects(o.geom, d.geom)
WHERE d.district_name = 'Example District'
)
TO 'output/selected_observations.gpkg'
WITH (
FORMAT GDAL,
DRIVER 'GPKG',
SRS 'EPSG:4326'
);
This statement runs the query and writes the resulting features through GDAL’s GeoPackage driver.
The SRS option sets spatial-reference metadata where supported by the destination driver. It does not transform coordinates. Only specify EPSG:4326 when the coordinate values actually use that reference system.
If the source geometries need reprojection, transform them within the query before exporting. For example, where the source geometry is correctly identified as EPSG:4326 and EPSG:32737 is appropriate for the study area:
ST_Transform(
geom,
'EPSG:4326',
'EPSG:32737'
) AS geom
In production, use the real source CRS rather than assuming the input is EPSG:4326.
GeoPackage is usually a strong default for GIS delivery because it supports long field names, a rich range of attribute types, and multiple vector layers in a single file.
7.2 Export to Shapefile
If a downstream system specifically requires Shapefile, use its GDAL driver:
COPY (
SELECT
observation_id,
district_name,
geom
FROM observations
)
TO 'output/observations.shp'
WITH (
FORMAT GDAL,
DRIVER 'ESRI Shapefile',
SRS 'EPSG:4326'
);
The Shapefile driver writes the associated sidecar files alongside the .shp file.
Shapefile has notable schema limitations, including short field names and restricted attribute types. Inspect the output schema and rename or cast incompatible columns before export. As with GeoPackage, verify the geometry’s actual CRS.
7.3 Export aggregated statistics
For a district-level deliverable, export the compact summary rather than the original observations:
COPY (
WITH district_stats AS (
SELECT
d.district_id,
d.district_name,
COUNT(o.observation_id) AS observation_count,
AVG(o.measurement) AS mean_measurement
FROM districts AS d
LEFT JOIN observations AS o
ON ST_Within(o.geom, d.geom)
GROUP BY
d.district_id,
d.district_name
)
SELECT
s.district_id,
s.district_name,
s.observation_count,
s.mean_measurement,
d.geom
FROM district_stats AS s
JOIN districts AS d
ON s.district_id = d.district_id
)
TO 'output/district_statistics.gpkg'
WITH (
FORMAT GDAL,
DRIVER 'GPKG',
SRS 'EPSG:4326'
);
This is the type of workflow that makes DuckDB useful for desktop GIS users: the database performs the expensive analytical query, and the GIS receives a compact layer prepared for styling, validation, and map production.
8. Out-of-Core Spatial Analytics on Limited RAM
Out-of-core processing enables a system to process datasets or intermediate results larger than available memory by spilling eligible operations to disk.
DuckDB supports larger-than-memory execution for many operations, including grouping, joining, sorting, and windowing. However, support has limits; combinations of memory-intensive operations can still exhaust available resources, and some spatial operators have their own constraints.
8.1 Configure temporary storage
SET memory_limit = '4GB';
SET temp_directory = 'D:/duckdb_temp';
SET threads = 4;
SET preserve_insertion_order = false;
Set the memory limit according to the physical RAM available, and place temporary storage on a fast drive with adequate free space. The preserve_insertion_order setting can reduce memory pressure for some large import and export operations when the original row order is not required.
8.2 Reduce data before processing geometry
The following query filters observations before they enter the spatial join:
WITH filtered_observations AS (
SELECT
observation_id,
measurement,
geom
FROM observations
WHERE observed_at >= TIMESTAMP '2025-01-01 00:00:00'
AND observed_at < TIMESTAMP '2026-01-01 00:00:00'
)
SELECT
d.district_name,
COUNT(o.observation_id) AS observation_count
FROM filtered_observations AS o
JOIN districts AS d
ON ST_Intersects(o.geom, d.geom)
GROUP BY d.district_name;
This reduces the number of input records requiring spatial evaluation, although the exact improvement depends on whether the filter is selective and how the query is planned.
Other effective practices include:
- Select only the columns needed by the analysis.
- Use compatible CRSs so geometry predicates operate on meaningful coordinates.
- Filter the polygon build side where possible.
- Avoid unnecessarily materializing massive join results.
- Partition exceptionally large jobs in a way that preserves cross-boundary matches.
- Measure temporary-disk consumption as well as RAM and CPU utilization.
The most important caveat is the spatial join build side. An operator that constructs an in-memory R-tree may require the entire build-side input to fit in memory. That workload cannot simply be assumed to spill in the same way as a supported external sort or aggregation.
For more details, consult DuckDB’s workload-tuning guide.
9. Benchmarks: DuckDB vs Python and Traditional Spatial Databases
Performance comparisons should distinguish between reading files, converting geometry, performing the spatial calculation, and exporting results. The hardware and data representation also matter.
9.1 Published DuckDB spatial join benchmark
DuckDB’s engineering team tested spatial joins between approximately 58 million New York City bike-trip records and 310 neighborhood polygons. The published comparison used DuckDB v1.3.2 on a MacBook M3 Pro with 36 GB of RAM, with times averaged across three runs.
| Trip records | Nested-loop strategy | Piecewise merge join | Spatial join operator |
|---|---|---|---|
| 1 million | 30.8 s | 2.3 s | 0.5 s |
| 10 million | 310.3 s | 19.6 s | 4.8 s |
| 58 million | 1,799.6 s | 107.6 s | 28.7 s |
Source: Spatial Joins in DuckDB.
This benchmark demonstrates that the join strategy can matter as much as the database engine itself. The spatial join operator greatly reduced execution time relative to the other strategies in that particular workload.
These figures are not a promise that every dataset with 58 million features will finish in 28.7 seconds. Different geometries, hardware, CRS transformations, input formats, output cardinalities, and memory constraints can change the result substantially.
9.2 Architectural comparison
| Approach | Strengths | Trade-offs |
|---|---|---|
| DuckDB + Spatial | Direct file analytics, columnar scans, SQL aggregation, optimized spatial joins | Not a full replacement for every persistent spatial database workload |
| Python + GeoPandas | Flexible integration with scientific and machine-learning libraries | Loading large GeoDataFrames may consume substantial RAM; performance depends on the chosen operations |
| PostgreSQL + PostGIS | Persistent spatial indexes, transactions, concurrent access, mature spatial database capabilities | Requires database setup and data loading for an ordinary local-file workflow |
| Desktop GIS | Interactive exploration, visualization, editing, cartography | Rendering and loading very large layers may be resource-intensive |
There is no universally valid performance ranking across these systems. PostGIS may be preferable for repeated indexed queries against persistent data, while GeoPandas may be attractive when spatial processing must be combined with a wider Python analysis pipeline. DuckDB is especially useful for local analytical queries over files without first building a conventional server database.
9.3 Run a fair local benchmark
To compare DuckDB with GeoPandas, use the same source data, matching coordinate systems, equivalent spatial predicates, and identical result definitions.
For DuckDB, measure a complete representative query:
EXPLAIN ANALYZE
SELECT
d.district_id,
COUNT(o.observation_id) AS observation_count
FROM observations AS o
JOIN districts AS d
ON ST_Intersects(o.geom, d.geom)
GROUP BY d.district_id;
For GeoPandas, use an equivalent spatial join and measure the end-to-end time, including file reading:
from time import perf_counter
import geopandas as gpd
start = perf_counter()
points = gpd.read_parquet(
"data/observations.geoparquet"
)
districts = gpd.read_file(
"data/districts.gpkg"
)
# Spatial predicates must operate on compatible CRSs.
if points.crs != districts.crs:
points = points.to_crs(districts.crs)
joined = gpd.sjoin(
points,
districts[["district_id", "geometry"]],
how="inner",
predicate="intersects",
)
elapsed = perf_counter() - start
print(f"Matched pairs: {len(joined):,}")
print(f"End-to-end time: {elapsed:.2f} seconds")
Adapt the selected attribute and geometry names to your source. The comparison assumes the district layer uses geometry as the GeoPandas geometry column; GeoPackage layer selection may also need to be specified explicitly.
For an equivalent DuckDB timing, execute the spatial join with the same predicate and compare the matched-pair count. Include file reading, CRS conversion where required, and result creation consistently in both timings. Run multiple trials and distinguish cold-cache runs from warm-cache runs.
When evaluating PostGIS, document whether the source tables have already been loaded and spatially indexed. Otherwise, comparing an indexed persistent database query to a direct file scan—or the reverse—can measure different amounts of work.
10. Practical Takeaways for Laptop-Based GIS Work
A reliable local workflow separates analytical processing from cartographic work:
- Use GeoParquet for repeated analytical scans. Its columnar layout can reduce the amount of data read when a query only needs selected attributes.
- Query directly from files. Use
read_parquet()for Parquet analysis andST_Read()for supported GDAL-based vector formats. - Filter early and keep queries narrow. Avoid transferring every geometry and attribute through every stage of the workflow.
- Inspect spatial join plans. Use
EXPLAIN ANALYZEto identify inefficient join strategies, unexpectedly large intermediates, and slow operators. - Treat out-of-core execution realistically. DuckDB supports many larger-than-memory operations, but some spatial joins and combinations of blocking operators can still be constrained by RAM.
- Export only the final deliverable. Write the filtered or aggregated result to GeoPackage or Shapefile for visualization and validation in QGIS or ArcGIS Pro.
The combination of DuckDB, the Spatial extension, and GeoParquet provides a practical alternative to workflows that first load entire multi-gigabyte datasets into a desktop GIS or Python environment. Used with appropriate CRS handling, memory configuration, and query-plan inspection, it offers a powerful way to run serverless spatial SQL and local geospatial analytics on commodity hardware.
The central principle is simple: let the database engine process the large dataset, and let the GIS application handle the final visualization and quality-control tasks.