Automating Shapefile to GeoJSON Conversion in QGIS
Automating shapefile to GeoJSON conversion in QGIS is best handled via the QgsVectorFileWriter API. For QGIS 3.16+, writeAsVectorFormatV3() safely manages coordinate reference systems, encoding, and geometry validation. This method runs headless in the Python Console, standalone scripts, or plugins, making it ideal for reproducible Vector Data Manipulation workflows.
The script loads the .shp as a QgsVectorLayer, configures export options, and writes the output. GeoJSON RFC 7946 mandates WGS84 (EPSG:4326); if your source layer uses a projected CRS, pass the destination CRS in the save options to trigger automatic reprojection during export.
import os
from pathlib import Path
from qgis.core import (
QgsVectorLayer,
QgsVectorFileWriter,
QgsCoordinateTransformContext,
QgsCoordinateReferenceSystem,
QgsProject,
)
def convert_shp_to_geojson(input_path: str, output_path: str) -> None:
# Load shapefile
layer = QgsVectorLayer(input_path, "temp_layer", "ogr")
if not layer.isValid():
raise RuntimeError(f"Failed to load shapefile: {input_path}")
# Configure export options
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.driverName = "GeoJSON"
opts.fileEncoding = "UTF-8"
# GeoJSON RFC 7946 requires WGS84; reproject if the source differs
opts.ct = None # PyQGIS will reproject automatically when destCRS is set below
# Write to GeoJSON
err_code, err_msg, _, _ = QgsVectorFileWriter.writeAsVectorFormatV3(
layer,
output_path,
QgsProject.instance().transformContext(),
opts,
)
if err_code != QgsVectorFileWriter.NoError:
raise RuntimeError(f"Export error ({err_code}): {err_msg}")
print(f"Success: {Path(input_path).name} -> {Path(output_path).name}")
Configuration & Environment Notes
- QGIS Version:
writeAsVectorFormatV3requires QGIS 3.16+. For 3.10–3.14, usewriteAsVectorFormatV2with identical parameters. - Return Value:
writeAsVectorFormatV3returns a 4-tuple(error_code, error_message, new_filename, new_layer_name). Always unpack all four values. - Runtime Environment: Must execute inside QGIS's bundled Python (
python-qgisor the Python Console). Standalone Python requiresQGIS_PREFIX_PATHandPYTHONPATHto be explicitly configured andQgsApplication.initQgis()to be called first. - CRS Enforcement: GeoJSON RFC 7946 mandates EPSG:4326. If your source uses a projected CRS, PyQGIS will reproject during export when the QGIS transform context is available. Always verify output coordinates if your source uses a local projected CRS.
- Format Limits: Shapefiles truncate field names at 10 characters and cap at 2 GB. GeoJSON has no hard size limit but degrades in browser performance past ~50 MB.
Fallback Methods
If PyQGIS throws driver errors or environment conflicts, use these proven alternatives:
- GDAL/OGR CLI: Bypass QGIS entirely with
ogr2ogr. Faster for headless servers and avoids Python overhead.ogr2ogr -f GeoJSON -t_srs EPSG:4326 -lco RFC7946=YES -overwrite output.geojson input.shp - QGIS Processing Toolbox (GUI Batch): Open the Processing Toolbox (
Ctrl+Alt+T), search Convert format, right-click → Execute as Batch Process. Load multiple shapefiles, set target format toGeoJSON, and run. Best for non-programmers but lacks programmatic error handling.
Troubleshooting Common Failures
Driver not found: Verify GDAL is bundled with your QGIS install. On Windows, use the OSGeo4W installer. On Linux, installpython3-gdalalongside QGIS.- Attribute truncation/encoding errors: Shapefiles use legacy codepages. Keep
opts.fileEncoding = "UTF-8"and ensure a matching.cpgfile exists alongside the shapefile. If corruption persists, convert to GeoPackage first, then to GeoJSON. - Geometry validation failures: Invalid polygons (self-intersections, unclosed rings) cause silent feature drops. Run
processing.run("native:fixgeometries", {'INPUT': layer, 'OUTPUT': 'memory:'})before export. - Memory limits on large exports: GeoJSON loads entirely into RAM on read. For datasets >100 MB, split by attribute or use
ogr2ogrwith-gt 10000to stream features in batches.
Automating shapefile to GeoJSON conversion eliminates manual overhead and standardizes data delivery for web mapping and API integrations. When integrated into broader Spatial Data Processing & Automation pipelines, PyQGIS delivers reliable, version-controlled transformations that scale from single files to enterprise datasets. Always validate output with a lightweight GeoJSON linter before deployment.
Frequently Asked Questions
Does my GeoJSON output need to be in WGS84 (EPSG:4326)?
The GeoJSON RFC 7946 spec mandates WGS84, and most web mapping libraries assume it. If your source shapefile uses a projected CRS, pass the destination CRS through the save options so PyQGIS reprojects during export, or use ogr2ogr -t_srs EPSG:4326. Skipping this step leaves coordinates in projected units that web maps will misplace.
Which writer method should I use on QGIS 3.34 LTR?
Use writeAsVectorFormatV3, which is available from QGIS 3.16 onward and is the current standard on 3.34 LTR. It returns a four-tuple (error_code, error_message, new_filename, new_layer_name)—always unpack all four and check error_code against QgsVectorFileWriter.NoError. The V2 method is only needed on releases between 3.10 and 3.14.
Why are my field names truncated or my characters garbled after conversion?
Shapefiles cap field names at 10 characters and use legacy codepages, so issues surface during the read, not the write. Keep opts.fileEncoding = "UTF-8" and make sure a matching .cpg file sits beside the shapefile. If corruption persists, convert the shapefile to GeoPackage first, then export that to GeoJSON.
How do I convert an entire folder of shapefiles in one run?
Wrap the convert_shp_to_geojson function in a loop that globs the directory with pathlib.Path.glob("*.shp"), deriving each output path from the input stem. For headless servers you can also batch with ogr2ogr, or use the Processing Toolbox "Convert format" tool's Execute as Batch Process option for a no-code approach.
My export runs but the GeoJSON is missing features—what happened?
Invalid geometries such as self-intersections or unclosed rings are dropped silently during export. Run processing.run("native:fixgeometries", {'INPUT': layer, 'OUTPUT': 'memory:'}) before writing so the geometries are repaired first. Comparing feature counts before and after export is a quick way to catch silent drops.