Projection & CRS Quick Reference for Energy GIS
This page is the fast lookup the rest of the Core Energy-GIS Data & Spatial Fundamentals knowledge base links back to whenever a workflow needs to pick a coordinate frame. It answers one recurring question — which EPSG code do I reproject to, given what I am about to compute? — with three comparison tables, a decision matrix, and one runnable helper. It is deliberately terse; the conceptual treatment of datum shifts, transformation chains, and audit gating lives in coordinate reference systems for energy projects, and the specific web-tiling case in aligning EPSG:4326 and EPSG:3857 for solar site mapping.
The single rule that governs every table below: you never measure on a geographic coordinate system. Latitude and longitude are angles, not lengths. A degree of latitude is close to constant, but a degree of longitude shrinks with latitude, so on EPSG:4326 the east–west metre value of one “unit” is
At that is roughly m, and at only m — while a degree of latitude stays near m throughout. Feed that anisotropy into geometry.buffer(5000) or geometry.distance() and the result is not off by a rounding error, it is off by a latitude-dependent scale factor. Project into a metric frame first, then measure.
Common EPSG codes for energy work
These are the codes that cover the overwhelming majority of US and global renewable siting, grid, and resource work. “Property preserved” is what the projection keeps true at the expense of everything else — no map projection preserves distance, area, and shape simultaneously.
| EPSG | Name | Property preserved | Units | When to use |
|---|---|---|---|---|
EPSG:4326 |
WGS 84 (geographic) | none — angular position | degrees | Storage, interchange, GPS feeds, join keys. Never for .area/.length/.buffer. |
EPSG:3857 |
WGS 84 / Pseudo-Mercator | shape/angle (conformal); area badly inflated | metres | Slippy-map tiles, basemaps, drone orthomosaics. Visualization only — not area or distance. |
EPSG:32610 |
WGS 84 / UTM zone 10N | shape/angle, locally near-true distance | metres | Distance, buffers, routing in the US West (≈126°W–120°W band). |
EPSG:32618 |
WGS 84 / UTM zone 18N | shape/angle, locally near-true distance | metres | Same, for the US East (≈78°W–72°W band). |
EPSG:2277 |
NAD83 / Texas Central (State Plane) | conformal, survey-grade local distance | US survey feet | Engineering/survey distance within one state zone; matches civil CAD drawings. |
EPSG:5070 |
NAD83 / CONUS Albers | area (equal-area) | metres | Acreage, land-cover, setback area across the contiguous US. |
EPSG:6933 |
WGS 84 / NSIDC EASE-Grid 2.0 Global | area (equal-area) | metres | Continental or global area analysis outside CONUS. |
UTM is a family of 60 six-degree zones; EPSG:32610 and EPSG:32618 are two examples. Pick the zone that contains your project’s centroid — measured distances stay within centimetres near the central meridian and degrade toward the zone edges, where the point scale factor at the meridian grows past the outer boundary. State Plane zones (such as EPSG:2277) are tuned tighter than UTM for a single state and are what surveyors and permitting authorities expect on stamped drawings, but note the US survey foot unit — carry it explicitly or a buffer distance will be silently wrong by a factor of .
Projection family versus task
Choose the family from the operation, not from what the data happened to arrive in. Reproject at the boundary of the analysis and reproject back only for delivery.
| Task | Use family | Avoid | Example EPSG |
|---|---|---|---|
| Distance, buffers, nearest-feature, routing | Conformal local (UTM, State Plane) | EPSG:4326, EPSG:3857 |
EPSG:32610, EPSG:32618, EPSG:2277 |
| Area, acreage, land-cover, setback footprints | Equal-area (Albers, EASE-Grid) | EPSG:3857 above all |
EPSG:5070, EPSG:6933 |
| Web map, tile serving, visualization | Conformal global (Web Mercator) | measuring anything on it | EPSG:3857 |
| Storage, interchange, join keys, GPS | Geographic | any measurement | EPSG:4326 |
The one combination that ruins the most energy analyses is computing area in EPSG:3857. Web Mercator’s areal scale factor is , so a solar-array footprint or a habitat-loss polygon evaluated at is overstated by roughly 100% before any other error enters. Capacity (MW) and land-cost estimates derived from a Web Mercator area are not conservative or optimistic — they are meaningless. Move to EPSG:5070 for anything inside the lower 48, and to EPSG:6933 for wider extents.
pyproj and geopandas gotchas
The failures below rarely raise a clean exception at the point of the mistake; most surface as a plausible-but-wrong number several steps later.
| Symptom | Cause | Fix |
|---|---|---|
| Transformed coordinates come out swapped (lat/lon) | pyproj honours the authority axis order, and EPSG:4326 is defined as (lat, lon) |
Build the transformer with always_xy=True; geopandas already stores x=lon, y=lat |
buffer(5000) returns a hemisphere-sized polygon |
Buffering in EPSG:4326 — 5000 is read as 5000 degrees |
to_crs() to a metric CRS before .buffer() |
UserWarning: Geometry is in a geographic CRS. Results ... will be incorrect |
Called .area or .length on EPSG:4326 |
Reproject to equal-area (EPSG:5070) for area, or UTM for length, first |
to_crs raises about “naive geometries” |
gdf.crs is None |
set_crs(epsg) to label the true source, then to_crs() to reproject |
| Features off by a metre or two after reprojection | Datum shift skipped (NAD27/NAD83 → WGS 84 by relabelling) | Let pyproj select a transformation grid; never overwrite the CRS label in place |
The set_crs versus to_crs distinction is the most common data-corrupting mistake for newcomers: set_crs asserts what CRS the coordinates are already in (it moves no points), while to_crs reprojects the coordinates into a new CRS (the numbers change). Calling set_crs on data that needs reprojecting silently mislabels it; calling to_crs on data with a wrong or absent source label reprojects from the wrong origin. When in doubt about which UTM zone a mixed-extent layer belongs to, let geopandas derive it from the data with gdf.estimate_utm_crs() rather than hard-coding a zone.
Decision matrix: task → family → EPSG
A CRS-selection and reproject helper
This helper encodes the decision matrix directly: pass a GeoDataFrame and the task you are about to perform, and it reprojects into the correct frame. For distance work it derives a datum-correct UTM zone from the data’s own extent rather than hard-coding one; for area and visualization it maps to fixed CONUS targets. It refuses to run on a layer whose CRS is undefined — the one condition guaranteed to produce a silent wrong answer.
import geopandas as gpd
import pyproj
# Fixed targets for CONUS-scale energy work (distance is derived per-layer)
TASK_CRS = {
"area": "EPSG:5070", # NAD83 / CONUS Albers — equal-area, metres
"webmap": "EPSG:3857", # Web Mercator — tiles/visualisation ONLY
"storage": "EPSG:4326", # WGS 84 — interchange / join key
}
def project_for_task(sites_gdf: gpd.GeoDataFrame, task: str) -> gpd.GeoDataFrame:
"""Reproject a layer into the CRS appropriate for `task`.
task="distance" derives the UTM zone covering the layer's centroid so
buffers and nearest-feature search run in near-true metres; every other
task maps to a fixed target in TASK_CRS.
"""
if sites_gdf.crs is None:
raise ValueError(
"Input CRS is undefined — call set_crs(<true source EPSG>) "
"before reprojecting; to_crs() cannot transform naive geometries."
)
if task == "distance":
target_crs = sites_gdf.estimate_utm_crs() # e.g. EPSG:32610 in NorCal
elif task in TASK_CRS:
target_crs = pyproj.CRS.from_user_input(TASK_CRS[task])
else:
raise ValueError(
f"Unknown task {task!r}; expected 'distance' or one of {list(TASK_CRS)}"
)
projected = sites_gdf.to_crs(target_crs)
# Guard the classic mistake: measuring on a geographic frame
if task in {"distance", "area"} and projected.crs.is_geographic:
raise ValueError(f"{target_crs} is geographic; {task} requires a projected CRS")
return projected
# Usage: buffer a substation layer in true metres
substation_gdf = gpd.read_file("substations.geojson") # arrives as EPSG:4326
substation_gdf = project_for_task(substation_gdf, "distance") # -> UTM, metres
buffers_gdf = substation_gdf.assign(geometry=substation_gdf.buffer(5000)) # 5 km
Two details make this production-safe rather than merely correct. First, estimate_utm_crs() returns a single zone for the whole layer, which is right for a project-scale footprint but wrong for a layer spanning several UTM zones — for a national portfolio, partition by zone or switch to EPSG:5070/EPSG:6933 and accept the equal-area trade-off. Second, when you build a raw pyproj.Transformer outside geopandas — for example to reproject bare coordinate tuples — always pass always_xy=True so the transformer emits (lon, lat) regardless of the authority axis order, matching what geopandas and Shapely expect.
Guidance notes
- Store in
EPSG:4326, analyse in a projected frame, deliver in whatever the consumer needs. A permitting portal usually wantsEPSG:4326GeoJSON; a civil engineer wants the local State Plane zone; a tile server wantsEPSG:3857. Keep the analytical CRS separate from the delivery CRS. - Tag every layer on ingestion. An undefined CRS is not a neutral default — it is a landmine that only detonates at the first
to_crs. Assert the source EPSG withset_crsthe moment data enters the pipeline. - Match units, not just codes.
EPSG:2277is in US survey feet; a5000-unit buffer there is 1524 m, not 5 km. Readcrs.axis_info[0].unit_namebefore trusting any distance literal. - Reproject before spatial indexing and distance search. The nearest-substation and buffer routines in proximity distance calculations assume a metric CRS; feeding them
EPSG:4326returns degrees, not metres. - Keep terrain and vector layers on the same frame. Hillshade, slope, and horizon-angle rasters in terrain shadow analysis pipelines must share the analysis CRS with the turbine and array vectors they mask, or the shading loss lands on the wrong pixels.
Related
- Coordinate Reference Systems for Energy Projects — the full treatment of datum shifts, transformation chains, and audit gating behind this lookup.
- Aligning EPSG:4326 and EPSG:3857 for Solar Site Mapping — the specific web-tiling case where conformal-vs-equal-area confusion bites.
- Core Energy-GIS Data & Spatial Fundamentals — the six-stage pipeline this reference supports, with CRS alignment as stage two.
- Proximity Distance Calculations — where the projected-CRS discipline here becomes a hard precondition for correct grid distances.
- Terrain Shadow Analysis Pipelines — raster-vector work that depends on a shared, metric analysis CRS.