How to Analyse Load-Posted and Closed Bridges in the NBI

On this page

Problem statement

A bridge that cannot safely carry legal truck loads gets a sign: a weight limit, a lane restriction, or a closure. The National Bridge Inventory records which state each bridge is in, open, posted or closed, in a one-letter code. Posted bridges matter for freight routes, school buses, fire engines and farm equipment, and the detour when one closes can be long in rural areas. This guide counts posted and closed bridges in the 2025 inventory by owner, state and material, measures their traffic and detours, and relates posting to condition.

Quick answer

Map OPEN_CLOSED_POSTED_041 (upper-cased) through its code list: A open, P posted for load, R other restriction, K closed, and four rarer codes. Posted bridges are P and R together.

POST = {"A": "open", "B": "open, would be posted", "D": "open, temporary shoring", "E": "open, temporary structure",
        "G": "new, not yet open", "K": "closed", "P": "posted for load", "R": "posted, other restriction"}
s = nbi.OPEN_CLOSED_POSTED_041.str.upper().map(POST)
t = s.value_counts()
print(pd.DataFrame({"bridges": t, "share": (t / t.sum()).round(4)}).to_string())
                           bridges   share
OPEN_CLOSED_POSTED_041                    
open                        553840  0.8873
posted for load              62441  0.1000
closed                        3587  0.0057
posted, other restriction     2112  0.0034
open, temporary shoring        791  0.0013
open, would be posted          605  0.0010
open, temporary structure      465  0.0007
new, not yet open              352  0.0006

88.73% of bridges are open without restriction. 62,441 (10.00%) are posted for load and 2,112 have other restrictions; 3,587 are closed. A few hundred carry temporary shoring or are temporary structures, and 352 are new and not yet open.

Map of the lower 48 with load-posted bridges in orange and closed bridges in red, dense across the Plains and Midwest Map of the lower 48 with load-posted bridges in orange and closed bridges in red, dense across the Plains and Midwest
Posted and closed bridges are concentrated on the Plains and in the Midwest.

Who posts bridges, and what it costs

1. Codes and a lower-case one

print("raw codes:", nbi.OPEN_CLOSED_POSTED_041.value_counts().to_dict())
raw codes: {'A': 553839, 'P': 62441, 'K': 3587, 'R': 2112, 'D': 791, 'B': 605, 'E': 465, 'G': 352, 'a': 1}

One bridge is coded with a lower-case a; without .str.upper() it would fall out of every count. Small as it is, it is the kind of inconsistency that makes a total disagree with a published one.

2. Posted bridges by owner

OWN = {"01": "state highway agency", "02": "county", "03": "town or township", "04": "city or municipal"}
own = nbi.OWNER_022.str.strip().map(OWN).fillna("other")
posted = nbi.OPEN_CLOSED_POSTED_041.str.upper().isin(["P", "R"])
print("share posted by owner:", posted.groupby(own).mean().sort_values(ascending=False).round(3).to_dict())
print("posted bridges by owner:", posted.groupby(own).sum().sort_values(ascending=False).to_dict())
share posted by owner: {'county': 0.189, 'town or township': 0.101, 'city or municipal': 0.086, 'other': 0.074, 'state highway agency': 0.042}
posted bridges by owner: {'county': 42809, 'state highway agency': 12351, 'city or municipal': 4318, 'town or township': 3149, 'other': 1926}

Posting is a local-road problem. 18.9% of county-owned bridges are posted, against 4.2% of state-owned ones; counties own 42,809 of the posted bridges, states 12,351.

3. Posted bridges and detours

det = pd.to_numeric(nbi.DETOUR_KILOS_019, errors="coerce")
print(f"median detour if a posted bridge were closed: {det[posted].median():.0f} km; over 30 km: {(det[posted] > 30).mean():.1%}")
print(f"posted bridges' median daily traffic: {nbi.adt[posted].median():.0f} vehicles (all bridges: {nbi.adt.median():.0f})")
median detour if a posted bridge were closed: 6 km; over 30 km: 14.3%
posted bridges' median daily traffic: 115 vehicles (all bridges: 954)

If a posted bridge had to close, the median detour would be 6 km, but 14.3% would force a detour of more than 30 km. Posted bridges carry little traffic β€” a median of 115 vehicles a day, against 954 for all bridges β€” which is why many stay posted for years rather than being replaced.

Horizontal bars of the share of bridges posted by owner type, counties highest and state highway agencies lowest Horizontal bars of the share of bridges posted by owner type, counties highest and state highway agencies lowest
18.9% of county-owned bridges are posted, against 4.2% of state-owned ones.

4. Condition of posted and closed bridges

k = nbi.OPEN_CLOSED_POSTED_041.str.upper()
print(pd.crosstab(k.where(k.isin(["A", "P", "K"])), nbi.cond, normalize="index").round(3).to_string())
cond                        F      G      P
OPEN_CLOSED_POSTED_041                     
A                       0.486  0.479  0.035
K                       0.169  0.045  0.786
P                       0.608  0.111  0.281

78.6% of closed bridges are rated poor, and 28.1% of posted bridges; most posted bridges (60.8%) are fair. A bridge is posted because its load rating β€” a structural calculation β€” falls below legal loads, which can happen to a fair bridge built for lighter trucks.

States and timber

The data used on this page

The Federal Highway Administration's National Bridge Inventory for 2025: the open/posted/closed status, owner, detour length and traffic items, and Census Bureau 2023 states.

import zipfile
from pathlib import Path

import geopandas as gpd
import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
# FHWA National Bridge Inventory, 2025 submission, highway bridges, all states in one comma-delimited file (53 MB zip).
# The server is slow and drops long downloads, so resume until the file is complete.
URL = "https://www.fhwa.dot.gov/bridge/nbi/2025hwybronefiledel.zip"
f = Path("data/2025hwybronefiledel.zip")
size = int(requests.head(URL, allow_redirects=True, timeout=60).headers["Content-Length"])
while not f.exists() or f.stat().st_size < size:
    have = f.stat().st_size if f.exists() else 0
    try:
        with requests.get(URL, headers={"Range": f"bytes={have}-"}, stream=True, timeout=120) as r, open(f, "ab") as out:
            for chunk in r.iter_content(1 << 20):
                out.write(chunk)
    except requests.RequestException:
        pass                                                   # resume from what arrived
with zipfile.ZipFile(f) as z:
    nbi = pd.read_csv(z.open("2025HwyBridgesDelimitedAllStates.txt"), quotechar="'", dtype=str, low_memory=False)


def dms(s, west=False):
    """NBI packs coordinates as DDMMSSss (latitude) and DDDMMSSss (longitude, positive west)."""
    s = s.str.strip().str.zfill(9 if west else 8)
    d = s.str[:-6].astype(float) + s.str[-6:-4].astype(float) / 60 + s.str[-4:].astype(float) / 100 / 3600
    return -d if west else d


nbi["lat"], nbi["lon"] = dms(nbi.LAT_016), dms(nbi.LONG_017, west=True)
nbi["cond"] = nbi.BRIDGE_CONDITION                                   # G, F or P, derived by FHWA
nbi["adt"] = pd.to_numeric(nbi.ADT_029, errors="coerce")
nbi["built"] = pd.to_numeric(nbi.YEAR_BUILT_027, errors="coerce")
nbi["area"] = pd.to_numeric(nbi.DECK_AREA, errors="coerce")
for name in ["cb_2023_us_state_20m.zip"]:
    if not Path(f"data/{name}").exists():
        Path(f"data/{name}").write_bytes(requests.get(f"https://www2.census.gov/geo/tiger/GENZ2023/shp/{name}", timeout=300).content)
states = gpd.read_file("data/cb_2023_us_state_20m.zip").to_crs(4326)
nbi["state"] = nbi.STATE_CODE_001.map(states.set_index("STATEFP").STUSPS)
print(f"{len(nbi):,} highway bridges, {nbi.shape[1]} fields, {nbi.STATE_CODE_001.nunique()} state codes")
624,193 highway bridges, 130 fields, 53 state codes

Example 1: states with the most posted bridges

st = posted.groupby(nbi.state).agg(["sum", "mean"]).sort_values("sum", ascending=False)
print(st.head(6).round(3).to_string())
        sum   mean
state             
KS     7461  0.300
KY     4443  0.305
IA     4378  0.185
OK     3769  0.164
MO     3198  0.130
NE     3179  0.206

Kansas has the most posted bridges (7,461, 30.0% of its bridges) and Kentucky the highest share in this list (30.5%), followed by Iowa, Oklahoma, Missouri and Nebraska β€” Plains and Midwest states with large county road networks.

Map of the lower 48 states shaded by the share of bridges posted or restricted, darkest in Kansas and Kentucky Map of the lower 48 states shaded by the share of bridges posted or restricted, darkest in Kansas and Kentucky
Kansas (30.0%) and Kentucky (30.5%) post the largest shares of their bridges.

Example 2: timber bridges

timber = nbi.STRUCTURE_KIND_043A == "7"
print(f"timber bridges: {timber.sum():,}; share posted {posted[timber].mean():.1%} against {posted[~timber].mean():.1%} for the rest")
timber bridges: 14,853; share posted 45.7% against 9.5% for the rest

Timber bridges are posted far more often: 45.7% of the 14,853 timber bridges, against 9.5% of the rest.

Posting is about load, not condition

Every bridge has a load rating: the heaviest vehicle it can carry safely, calculated from its design and current condition. When that rating falls below the legal load limits for the road, the owner must post a lower limit or close the bridge. Posting therefore follows strength, which depends on original design as much as deterioration β€” a 1930s bridge designed for lighter trucks may be posted while in fair condition.

That is why posting is concentrated on county roads and older, smaller structures, and why the count of posted bridges is a useful complement to Good/Fair/Poor: it measures how much of the network cannot carry today's legal loads.

Pitfalls with item 41

  • Upper-case the codes before mapping them.
  • B ("would be posted") marks bridges that should be posted but are not; include it in a strict count.
  • Detour length is coded in kilometres and is 0 or very large for some bridges; check the distribution before averaging.
  • G (new, not yet open) bridges are in the inventory; leave them out of condition statistics.

FAQ

How many US bridges are load posted?

62,441 posted for load in the 2025 inventory (10.00%), plus 2,112 with other restrictions; 3,587 were closed.

Who owns most posted bridges?

Counties: 42,809 of the posted bridges, against 12,351 owned by state highway agencies.