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.
Worked example: choosing a frame for a three-state portfolio
A portfolio spanning west Texas, southern New Mexico and south-eastern Arizona illustrates every rule on this page at once. The extent crosses UTM zones 12, 13 and 14, so no single UTM zone is defensible for distance work across the whole portfolio. Three workable arrangements exist, and the choice depends on what is being measured.
The first is per-site frames: reproject each site into the UTM zone containing it, measure turbine spacing and setbacks there, and never compare raw coordinates across sites. This keeps every distance measurement inside a zone where the scale factor stays within a few parts per ten thousand, and it is the right choice for engineering-grade measurements. The cost is bookkeeping: each site’s outputs carry their own EPSG code, and any portfolio-level geometry operation has to reproject first.
The second is a single custom Lambert Conformal Conic with standard parallels straddling the portfolio latitude band. Distances are then comparable across the whole portfolio in one frame, at the cost of a scale factor that departs slightly from unity away from the standard parallels. This is the usual choice for corridor studies and transmission routing, where the measurement of interest spans sites rather than sitting inside one.
The third applies only to area: reproject to EPSG:5070 for anything expressed in acres or
hectares, regardless of which frame the distance work used. Equal-area and conformal are mutually
exclusive properties, so a portfolio that reports both buildable acreage and turbine spacing is
using two frames by necessity, not by oversight.
The failure mode all three prevent is the fourth arrangement, which is the one that happens by
accident: everything is loaded in EPSG:4326, one analyst reprojects to a single UTM zone because
estimate_utm_crs() returned it for the portfolio centroid, and every site outside that zone
carries a scale error that grows with distance from its central meridian. The numbers stay
plausible, the map looks right, and the spacing violations only appear at construction staking.
Frequently asked questions
Is EPSG:3857 ever acceptable for analysis?
Only for deciding what a user sees on a slippy map. It is conformal, so shapes and angles are locally correct, which is why it renders well; its areal scale factor is the secant squared of the latitude, so a footprint at 45°N measures twice its true area. Anything that becomes a number in a report — acreage, capacity density, setback area — has to be recomputed in an equal-area frame.
Why does the same parcel report different areas in different frames?
Because area is not preserved by most projections, and the difference is not an error in either
frame. EPSG:5070 preserves area and distorts shape; a UTM zone preserves shape and distorts area
slightly; Web Mercator preserves shape and distorts area badly. The parcel has one true area; the
frames disagree because they are answering different questions. Pick the frame from the measurement,
then state which frame produced the figure.
What does set_crs do that to_crs does not?
set_crs asserts what the existing coordinates already mean and moves nothing; to_crs transforms
the coordinates into a new frame and moves everything. Using set_crs to “convert” a layer relocates
the asset on the ground while leaving the numbers untouched, which is why it is the single most
data-corrupting mistake in this list — nothing about the resulting file looks wrong.
How do I choose between a UTM zone and a State Plane zone?
Take State Plane when the deliverable has to match surveyor or civil-engineering drawings, which are
usually published in it, and accept that most State Plane zones are in US survey feet. Take UTM when
the work is metric and the extent fits one zone. Whichever is chosen, assert the axis unit
explicitly: a 400 threshold means 400 feet in EPSG:2225 and 400 metres in EPSG:32610, and the
geometry checks pass either way.
Do I need to reproject rasters as well as vectors?
Yes, and the raster is the harder one, because reprojection resamples pixel values as well as moving them. Choose the kernel from what the pixels mean — nearest for categorical masks, bilinear or average for continuous surfaces — and prefer reprojecting the vector layer to the raster’s frame when the raster is large, since moving a few thousand geometries is far cheaper than resampling a few billion pixels.
What frame should be used for a national statistic?
An equal-area one — EPSG:5070 for CONUS, EPSG:6933 globally — for anything that aggregates area,
and a per-zone approach for anything that aggregates distance. A national mean of parcel areas
computed in a conformal frame is weighted by latitude in a way nobody intends, because the same
hectare occupies more map area in the north than in the south.
Are the EPSG codes on this page stable?
The codes are; what they refer to can be revised. An EPSG code identifies a CRS definition, and the authority occasionally deprecates one in favour of a new realisation — the NAD83(2011) to NATRF2022 transition being the live case in North America. Pin the PROJ database version alongside the code so a rebuild resolves the same definition it did the first time.
Should the analysis CRS be stored per layer or per project?
Per project, asserted per layer. One declared analysis frame is what lets consumers skip the reprojection question entirely; per-layer frames put that question back into every join. Layers that must differ — the equal-area copy used for acreage — are the deliberate exception and should be named so that the difference is obvious in the filename.
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.