Short answer
Preparing GIS data for the web means turning heavy analyst files into something a browser can fetch and draw fast: reproject to the web frame (GeoJSON in EPSG:4326, tiles in EPSG:3857), simplify geometry to the zoom levels you serve, cut coordinate precision and unused attributes, and package as GeoJSON for small layers or vector tiles for large ones. The browser is not a desktop GIS — it has a tiny memory and time budget, and a 200 MB shapefile of full-precision vertices will hang it.
Below: the CRS rules, simplification with real numbers, the GeoJSON-vs-tiles decision, a worked GDAL + tippecanoe pipeline, and the pitfalls.
The coordinate system rule
Two systems are in play and they have distinct jobs:
- EPSG:4326 (WGS84, longitude/latitude in degrees) is the storage and interchange frame for web vector data. RFC 7946 (GeoJSON) mandates 4326 with longitude first, latitude second. Ship your GeoJSON in 4326.
- EPSG:3857 (Web Mercator, meters) is the display frame. Slippy-map libraries (MapLibre, Leaflet, OpenLayers) render in 3857, and tile pyramids — raster XYZ tiles and vector tiles — are cut in 3857.
So the workflow is: data arrives in some projected analyst CRS (say EPSG:32633), you reproject to 4326 for GeoJSON, and the map library or your tiler handles the 3857 display projection. Reproject explicitly:
ogr2ogr -t_srs EPSG:4326 -f GeoJSON layer_web.geojson layer_utm.gpkg
Skipping this leaves coordinates in meters that the web library interprets as degrees, dropping your data into the Gulf of Guinea (the 0,0 null-island symptom).
Simplify geometry to the zoom you serve
Analyst geometry is far more detailed than any screen pixel can show. A coastline digitized at 1:5,000 carries vertices spaced sub-meter; at a country-level web zoom, hundreds of those vertices fall inside one pixel. Sending them all wastes bandwidth and CPU.
Simplify with a tolerance tied to ground resolution. The Douglas-Peucker algorithm (ogr2ogr -simplify, QGIS native:simplifygeometries) removes vertices that deviate less than a tolerance from the line:
ogr2ogr -simplify 10 -t_srs EPSG:4326 out.geojson in_utm.gpkg
Here -simplify 10 uses a 10 m tolerance in the source CRS units (so run it before reprojecting out of a metric CRS, or the tolerance becomes degrees). Pick tolerance from the coarsest zoom you serve: at Web Mercator zoom z, ground resolution at the equator is roughly 156543 / 2^z meters per pixel, so a tolerance near 0.5-1 pixel at your minimum zoom is a sensible floor. For topology-preserving simplification across shared borders (so adjacent polygons do not develop gaps), use Mapshaper (Visvalingam with keep-shapes) or build tiles with a tiler that simplifies per zoom.
Cut precision and attributes
Two cheap, large wins:
- Coordinate precision. Six decimal degrees is roughly 0.11 m at the equator — far finer than any web map needs. Trimming from 15 to 6 decimals shrinks GeoJSON dramatically:
ogr2ogr -f GeoJSON -lco COORDINATE_PRECISION=6 out.geojson in.gpkg
- Attributes. The browser only needs the fields the map actually styles or shows in popups. Drop the rest:
ogr2ogr -select "name,category,value" out.geojson in.gpkg
Then serve gzip/brotli compressed — GeoJSON is highly compressible text and a web server should compress it in transit.
GeoJSON or vector tiles?
- GeoJSON suits small, fairly static layers — roughly under a few MB and a few thousand features. The browser loads the whole file once and renders it. Simple, no tiler, easy to host.
- Vector tiles (Mapbox Vector Tiles, served as MBTiles or PMTiles) suit large datasets, many zoom levels, or anything that stalls the browser as one file. Tiles deliver only the features in the current view at the current zoom, with per-zoom simplification baked in.
Switch to tiles when a single GeoJSON exceeds a few MB, when pan/zoom feels sluggish, or when you have nationwide/global coverage.
Worked pipeline: GeoPackage to PMTiles
For a large vector layer destined for a MapLibre web map:
# 1. Reproject + simplify + trim in one ogr2ogr pass, output GeoJSON Lines
ogr2ogr -f GeoJSONSeq \
-t_srs EPSG:4326 \
-simplify 5 \
-lco COORDINATE_PRECISION=6 \
-select "name,class" \
features.geojsonl features_utm.gpkg
# 2. Build vector tiles with tippecanoe (per-zoom simplification, dropping)
tippecanoe -o features.pmtiles \
-Z4 -z14 \
--drop-densest-as-needed \
--coalesce-densest-as-needed \
-l features \
features.geojsonl
-Z4 -z14 sets the min/max zoom; --drop-densest-as-needed thins features where a tile would exceed the size limit; PMTiles is a single-file archive servable from static/object storage with HTTP range requests. The MapLibre style then points its source at features.pmtiles and styles the features layer.
For a smaller layer, stop after step 1 and serve the GeoJSON directly.
Common pitfalls and why they happen
- Data lands at null island (0,0). Coordinates left in a metric CRS but served as if 4326. Reproject to 4326 first.
- Browser hangs / huge download. One enormous GeoJSON of full-precision vertices. Simplify, trim precision, and move to tiles.
- Gaps between adjacent polygons after simplifying. Per-feature Douglas-Peucker breaks shared borders. Use topology-aware simplification (Mapshaper or a tiler).
- Popups show no data / wrong fields. Attributes were stripped too aggressively or names changed in conversion. Keep exactly the fields the map references.
- Simplify tolerance in the wrong units. Running
-simplifyafter reprojecting to 4326 makes the tolerance degrees, over-simplifying wildly. Simplify in the metric CRS, then reproject. - Date-line / pole artifacts. Web Mercator cannot represent the poles and wraps at ±180; clip or handle features crossing the antimeridian.
QA and validation
- Visual overlay on a known basemap at min and max zoom — alignment and no null-island.
- Feature count and bbox preserved (
ogrinfo -so -al) through the pipeline, allowing for intentional tile-level dropping. - File size budget: GeoJSON ideally a few MB gzipped; otherwise tiles.
- Attribute audit: every field the style/popup uses is present and correctly named.
- Geometry validity after simplification (no self-intersections introduced); run a make-valid pass if needed.
Bathyl perspective
A web map is a performance product as much as a cartographic one: the same dataset that an analyst trusts must be repackaged for a browser's tight memory and time budget without distorting the geography. Bathyl reprojects to 4326, simplifies and trims to the served zoom range, and picks GeoJSON or vector tiles by data size — so the published map loads fast and still lines up with the source.
Related reading
- Shapefile vs GeoPackage vs GeoJSON
- Why Does a Shapefile Have Multiple Files?
- Why Your GIS Layers Do Not Line Up
- Spatial data products