civilpy.transportation package
Roadway geometric design, curve calculations, alignments and terrain models (OGRIP/3DEP LiDAR with a notebook map picker), and FHWA National Bridge Inventory utilities.
Subpackages
Submodules
civilpy.transportation.alignment module
Composed roadway alignment: a chained horizontal geometry (tangents and circular curves) with a vertical profile, exposing the station/offset placement contract every bridge object is located by.
This is the native placement object for the bridge/analysis pipeline. The
individual curve primitives live in civilpy.transportation.curves
(HorizontalCurve, VerticalCurve); this module chains them head to
tail so a component can ask for a 3D point at (station, offset).
Conventions
Plan coordinates are
(x=East, y=North)in feet.Bearings are azimuths in degrees, clockwise from North (+y); the unit direction of increasing station is
d = (sin az, cos az).Positive offset is to the right of the direction of increasing station (
r = (cos az, -sin az)), matching the usual roadway right-is-positive offset sense.Elevation comes from the vertical profile; when a profile is omitted the alignment is flat at
z = 0.
Superelevation/cross-slope is not yet applied to offset elevations (offsets sit at the centerline profile elevation).
Examples
A tangent, a 500-ft-radius curve turning right through 30 deg, then a tangent, starting at the origin headed due north, stationed from 10+00:
>>> from civilpy.transportation.curves import HorizontalCurve
>>> al = Alignment(
... start_point=(0.0, 0.0), start_bearing_deg=0.0, start_station_ft=1000.0,
... elements=[Tangent(200.0),
... Curve(radius_ft=500.0, delta_deg=30.0, direction="R"),
... Tangent(200.0)])
>>> round(al.length_ft, 3)
661.799
>>> x, y, z = al.point_at(1000.0, 0.0) # start, on centerline
>>> round(x, 6), round(y, 6), round(z, 6)
(0.0, 0.0, 0.0)
>>> round(al.bearing_at(1100.0), 6) # still on first tangent
0.0
>>> x, y, _ = al.point_at(1100.0, 25.0) # 25 ft right of a north tangent
>>> round(x, 6), round(y, 6)
(25.0, 100.0)
>>> sta, off = al.station_offset_of((25.0, 100.0))
>>> round(sta, 6), round(off, 6)
(1100.0, 25.0)
- class civilpy.transportation.alignment.Alignment(start_point: tuple[float, float], start_bearing_deg: float, elements: list, profile: VerticalProfile | None = None, start_station_ft: float = 0.0)[source]
Bases:
objectA chained horizontal alignment with an optional vertical profile.
- Parameters:
start_point ((float, float)) – Plan
(x, y)of the alignment start, feet.start_bearing_deg (float) – Azimuth of increasing station at the start (deg, cw from North).
elements (list[Tangent | Curve]) – Ordered horizontal elements laid head to tail.
profile (VerticalProfile, optional) – Vertical profile; when omitted the alignment is flat at
z = 0.start_station_ft (float) – Station value at
start_point.
- bearing_at(station_ft: float) float[source]
Azimuth (deg, cw from North) of increasing station at
station.
- classmethod between_points(p0: tuple[float, float], p1: tuple[float, float], *, start_station_ft: float = 0.0, radius_ft: float | None = None, direction: Literal['R', 'L'] = 'R', start_elev_ft: float | None = None, end_elev_ft: float | None = None, terrain=None) Alignment[source]
Build an alignment between two plan points, deriving bearing, length, and grade – the conceptual-design workflow where you can’t drape an alignment on the ground but can pick two endpoints and a curvature.
With no
radius_ftthe horizontal alignment is a single tangent fromp0top1(bearing = the azimuth p0->p1, length = the distance). Giveradius_ftand the two points are joined by one circular arc of that radius turningdirection(“R”/”L”); the required deflection and start bearing are back-computed from the chord.Grade comes from the endpoint elevations: pass
start_elev_ft/end_elev_ftdirectly, or aterrain(anything withelevation_at(x, y)) to sample the ground at each point. When no elevations are available the alignment is flat (z = 0). The profile is a single straight grade; add crest/sagVerticalProfilePVIs later for a real vertical curve.
- frame_at(station_ft: float) dict[source]
Local frame at
station:point(x, y, z), unittangentand unitright(both plan) for sweeping templates.
- class civilpy.transportation.alignment.Curve(radius_ft: float, delta_deg: float, direction: Literal['R', 'L'] = 'R')[source]
Bases:
objectA circular arc of
radius_ftsweepingdelta_degto the left (direction='L') or right (direction='R').- property hcurve: HorizontalCurve
The underlying
HorizontalCurve(for T, L, D, E, M).
- class civilpy.transportation.alignment.Tangent(length_ft: float)[source]
Bases:
objectA straight run of length
length_ftalong the current bearing.
- class civilpy.transportation.alignment.VerticalProfile(pvis: list[tuple[float, float, float]])[source]
Bases:
objectElevation as a function of station from a list of PVIs.
pvisis an ordered list of(station_ft, elevation_ft, curve_len_ft). The first and last entries are the profile ends and should usecurve_len_ft = 0. Between consecutive PVIs the grade is straight; interior PVIs with a non-zero curve length carry an equal-tangent parabola (built onVerticalCurve).
civilpy.transportation.curves module
Roadway geometric design: vertical (parabolic) and horizontal (circular) curves with the standard plan/profile sketches.
Vertical curves use the equal-tangent parabola: elevations from the
grades and curve length, K = L/|A|, and the high/low point where the
grade passes through zero. Horizontal curves use the arc definition:
T, L, C, E, M from radius and deflection angle, plus the point-mass
superelevation relation e + f = V^2/(15 R).
US units: feet, percent grades, mph, degrees.
Examples
A 600-ft crest curve, +3% to -2%, PVI at sta 25+00 el 482.00:
>>> vc = VerticalCurve(g1_pct=3.0, g2_pct=-2.0, length_ft=600.0,
... pvi_station_ft=2500.0, pvi_elevation_ft=482.0)
>>> round(vc.k_value, 0)
120.0
>>> round(vc.elevation_at(vc.bvc_station), 2) # BVC elevation
473.0
>>> sta, el = vc.high_low_point()
>>> station_str(sta), round(el, 2)
('25+60.00', 478.4)
>>> hc = HorizontalCurve(radius_ft=1145.92, delta_deg=24.0,
... pi_station_ft=11500.0)
>>> round(hc.tangent_ft, 1), round(hc.length_ft, 1)
(243.6, 480.0)
>>> round(hc.degree_of_curve_deg, 1)
5.0
- class civilpy.transportation.curves.HorizontalCurve(radius_ft: float, delta_deg: float, pi_station_ft: float = 0.0)[source]
Bases:
objectSimple circular curve (arc definition) with deflection angle
delta_degbetween tangents.- static min_radius(speed_mph: float, e_max: float, f_max: float) float[source]
Point-mass minimum radius R = V^2 / (15 (e + f)).
- plot(ax=None, n: int = 200)[source]
Plan view with the back tangent entering along +x: tangents dashed to the PI, the curve arc, and PC/PI/PT labeled with their stations. Returns the figure.
- class civilpy.transportation.curves.VerticalCurve(g1_pct: float, g2_pct: float, length_ft: float, pvi_station_ft: float = 0.0, pvi_elevation_ft: float = 0.0)[source]
Bases:
objectEqual-tangent parabolic vertical curve between grades
g1_pctandg2_pct(percent, signed), centered on the PVI.- property a_pct: float
Algebraic grade difference A = g2 - g1 (percent); negative for a crest, positive for a sag.
- elevation_at(station_ft: float) float[source]
Profile elevation: on the tangents outside BVC/EVC, on the parabola between them.
civilpy.transportation.roadway module
AASHTO roadway geometric and roadside design functions.
Sight distance, vertical-curve length, superelevation transition, and
roadside-barrier length-of-need calculations from AASHTO A Policy on
Geometric Design of Highways and Streets (the “Green Book”) and the
Roadside Design Guide (RDG). These complement the curve geometry in
civilpy.transportation.curves.
Only equations and the standard design-control tables are reproduced here (no policy prose). The design tables (SSD, crest/sag K) are cross-checked against their generating equations in the test suite.
US customary units throughout: speeds in mph, distances in feet, grades as
a decimal (0.04 = 4%) unless a name says _pct, cross slopes / relative
gradients as a decimal.
- civilpy.transportation.roadway.DEFAULT_DECELERATION = 11.2
AASHTO default deceleration for sight-distance braking, ft/s^2 (Green Book): a comfortable deceleration most drivers can achieve.
- civilpy.transportation.roadway.DEFAULT_REACTION_TIME = 2.5
AASHTO default brake-reaction time for SSD, seconds.
- civilpy.transportation.roadway.DRIVER_EYE_HEIGHT = 3.5
Driver eye height and object heights for crest curves, feet (Green Book).
- civilpy.transportation.roadway.FLARE_RATE_RIGID = {40: 13, 45: 16, 50: 18, 55: 18, 60: 20, 65: 22, 70: 24, 75: 26, 80: 26}
lateral) inside the shy line by design speed (Roadside Design Guide Table 5-7), rigid barriers.
- Type:
Suggested barrier flare rates (longitudinal
- civilpy.transportation.roadway.G_FT_S2 = 32.2
Standard gravitational acceleration, ft/s^2.
- civilpy.transportation.roadway.K_CREST = {15: 3, 20: 7, 25: 12, 30: 19, 35: 29, 40: 44, 45: 61, 50: 84, 55: 114, 60: 151, 65: 193, 70: 247, 75: 312, 80: 384}
Design crest-curve rate of vertical curvature K = L/A for SSD, ft per percent (Green Book Table 3-34, rounded).
- civilpy.transportation.roadway.K_SAG = {15: 10, 20: 17, 25: 26, 30: 37, 35: 49, 40: 64, 45: 79, 50: 96, 55: 115, 60: 136, 65: 157, 70: 181, 75: 206, 80: 231}
Design sag-curve rate of vertical curvature K = L/A for SSD, ft per percent (Green Book Table 3-36, rounded).
- civilpy.transportation.roadway.MAX_RELATIVE_GRADIENT_PCT = {15: 0.78, 20: 0.74, 25: 0.7, 30: 0.66, 35: 0.62, 40: 0.58, 45: 0.54, 50: 0.5, 55: 0.47, 60: 0.45, 65: 0.43, 70: 0.4, 75: 0.38, 80: 0.35}
Maximum relative gradient between profile of edge and centerline, percent, by design speed (Green Book Table 3-15).
- civilpy.transportation.roadway.PSD_DESIGN = {20: 400, 25: 450, 30: 500, 35: 550, 40: 600, 45: 700, 50: 800, 55: 900, 60: 1000, 65: 1100, 70: 1200, 75: 1300, 80: 1400}
Design passing sight distance for two-lane highways, ft (Green Book Table 3-4, 2011/2018).
- civilpy.transportation.roadway.SSD_DESIGN = {15: 80, 20: 115, 25: 155, 30: 200, 35: 250, 40: 305, 45: 360, 50: 425, 55: 495, 60: 570, 65: 645, 70: 730, 75: 820, 80: 910}
Design stopping sight distance on level grade, ft (Green Book Table 3-1).
- civilpy.transportation.roadway.barrier_length_of_need(runout_length: float, lateral_extent: float, barrier_offset: float, flare_rate: float | None = None, back_offset: float | None = None) float[source]
Length of need X (ft) of a roadside barrier upstream of a hazard (Roadside Design Guide length-of-need method).
runout_lengthLR is the runout length for the design speed/volume (RDG Table 5-39),lateral_extentLA the distance from the edge of the travelled way to the far side of the area of concern, andbarrier_offsetL1 the lateral offset of the barrier at the hazard.For a barrier parallel to the road (
flare_rate=None): X = LR (LA - L1) / LA.For a flared barrier give
flare_rateas the longitudinal:lateral run b/a andback_offsetL2 (lateral offset where the barrier meets the length of need): X = (LA + (b/a) L2 - L1) / ((b/a) + LA/LR).
- civilpy.transportation.roadway.braking_distance(speed_mph: float, friction: float | None = None, grade: float = 0.0, deceleration: float = 11.2) float[source]
Braking distance (ft) from
speed_mph.With
frictiongiven, uses d = V^2 / (30 (f +/- G)); otherwise uses the AASHTO deceleration form d = V^2 / (30 (a/g +/- G)).gradeis a decimal, positive uphill (which shortens braking).
- civilpy.transportation.roadway.crest_curve_length(a_pct: float, sight_distance: float, h1: float = 3.5, h2: float = 2.0) float[source]
Minimum crest vertical-curve length (ft), Green Book Eq. 3-43/3-44.
a_pctis the algebraic grade difference|G2 - G1|in percent. Uses the larger of the two branches so the result is valid whether the sight distance is shorter or longer than the curve.
- civilpy.transportation.roadway.decision_sight_distance(speed_mph: float, maneuver: str) float[source]
Decision sight distance (ft), Green Book Eq. 3-3/3-4.
maneuveris one of the five avoidance maneuvers:"A"stop on rural road,"B"stop on urban road (both DSD = 1.47 V t + braking);"C"/"D"/"E"speed/path/ direction change on rural / suburban / urban roads (DSD = 1.47 V t, pre-maneuver + maneuver time).
- civilpy.transportation.roadway.intersection_sight_distance(speed_mph_major: float, time_gap: float) float[source]
Intersection sight distance along the major road (ft), Green Book Eq. 9-1: ISD = 1.47 V_major t_g.
time_gap(s) is the critical gap for the maneuver (e.g. 7.5 s left turn from stop, passenger car).
- civilpy.transportation.roadway.min_radius(speed_mph: float, e_max: float, f_max: float) float[source]
Minimum horizontal curve radius (ft), Green Book Eq. 3-8: R = V^2 / (15 (e_max + f_max)).
e_max/f_maxare decimals.
- civilpy.transportation.roadway.runoff_adjustment_factor(lanes_rotated: float) float[source]
Adjustment factor bw for multilane rotation (Green Book Table 3-16): bw = (1 + 0.5 (n1 - 1)) / n1.
- civilpy.transportation.roadway.sag_curve_length(a_pct: float, sight_distance: float) float[source]
Minimum sag vertical-curve length (ft) by the headlight-sight- distance criterion, Green Book Eq. 3-47/3-48 (headlight height 2.0 ft, 1-degree upward beam): denominator 400 + 3.5 S.
- civilpy.transportation.roadway.stopping_sight_distance(speed_mph: float, grade: float = 0.0, reaction_time: float = 2.5, deceleration: float = 11.2) float[source]
Stopping sight distance (ft), Green Book Eq. 3-1/3-2.
SSD = 1.47 V t + V^2 / (30 (a/g +/- G)).
gradedecimal, positive uphill.
- civilpy.transportation.roadway.superelevation_runoff_length(superelevation: float, lane_width: float, lanes_rotated: float, max_relative_gradient: float, adjustment_factor: float = 1.0) float[source]
Superelevation runoff length Lr (ft), Green Book Eq. 3-23.
Lr = (w n1 ed / Delta) bw, where
superelevationed and themax_relative_gradientDelta are decimals (use the Table 3-15 value divided by 100),lane_widthw in feet,lanes_rotatedn1, andadjustment_factorbw (Table 3-16, = (1 + 0.5(n1-1))/n1 for the common case).
civilpy.transportation.terrain module
Terrain: a source-agnostic ground surface that answers elevation_at.
The bridge/analysis pipeline places objects by station and offset along an
Alignment; the vertical position of
anything that meets grade (abutment seats, wingwall foreslopes, footing cutoff,
approach grades) comes from the ground surface, which this object models as a
triangulated irregular network (TIN).
The query core (elevation_at by barycentric interpolation over the TIN)
is pure numpy + scipy so it runs and tests anywhere. The heavier
ingestion paths are lazy:
from_las()reads OGRIP LiDAR.las/.laz(importslaspyonly when called) — the early-design / demo source.from_landxml()reads a survey-shot TIN (LandXMLSurface) with its own faces/breaklines — the Stage-3 production source.to_open3d_mesh()exports a mesh (importsopen3donly when called).
Coordinates are (x=East, y=North, z=Elevation) in feet, matching
civilpy.transportation.alignment.
Examples
>>> import numpy as np
>>> # a plane tilted 2% east, 1% north, sampled on a coarse grid
>>> gx, gy = np.meshgrid(np.linspace(0, 100, 6), np.linspace(0, 100, 6))
>>> z = 500.0 + 0.02 * gx + 0.01 * gy
>>> pts = np.column_stack([gx.ravel(), gy.ravel(), z.ravel()])
>>> t = Terrain.from_points(pts)
>>> round(t.elevation_at(50.0, 50.0), 6) # 500 + 1.0 + 0.5
501.5
>>> t.elevation_at(-10.0, 50.0) is None # outside the hull
True
- class civilpy.transportation.terrain.BboxPicker(center=(40.004, -83.005), zoom=14, height='480px')[source]
Bases:
objectInteractive notebook map for picking a WGS84 bounding box.
Draw a rectangle on the map and
.bboxholds(xmin, ymin, xmax, ymax)in decimal degrees (lon, lat) — the tupleTerrain.from_ogrip()andTerrain.from_ohio_dem()take. Drawing a new rectangle replaces the previous pick. Displaying the picker (last expression in a cell) shows the map; a corner readout updates live so the chosen bbox is also visible on screen.Navigation: hybrid imagery by default — Esri satellite with Esri’s transparent road and place-label reference tiles on top (both can be unticked, and a plain streets base map selected, from the layer toggle at top right) — plus scroll-wheel zoom, a fullscreen control, and a search box (top left, Nominatim) that flies to a typed address or place name — the quick way to land on a jobsite before drawing the rectangle.
Requires
ipyleaflet(pip install ipyleaflet), which gives the two-way widget link a staticfoliummap cannot: the drawn geometry lands in the Python kernel with no copy/paste.
- class civilpy.transportation.terrain.Terrain(points, faces=None)[source]
Bases:
objectA triangulated ground surface.
- Parameters:
points (array_like) –
(N, 3)array of(x, y, z)ground points, feet.faces (array_like, optional) –
(M, 3)triangle vertex indices (a supplied TIN, e.g. from a survey with breaklines). When omitted the XY projection is Delaunay-triangulated.
- OH_DEM_IMAGESERVER = 'https://gis.ohiodnr.gov/image/rest/services/OH_DEM_test/ImageServer'
Ohio DNR statewide DEM (OSIP LiDAR-derived, 2.5 ft, F32), an ArcGIS ImageServer that returns real elevations by query – no gigabyte tile downloads. Native SR is EPSG:3754 (NAD83 Ohio South State Plane, ft).
- OH_DEM_SR = 3754
- clip_to_bbox(xmin: float, ymin: float, xmax: float, ymax: float) Terrain[source]
Return a new re-triangulated
Terrainof the points inside the box (used to keep just the project corridor).
- clip_to_corridor(alignment, half_width_ft: float, *, step_ft: float = 25.0) Terrain[source]
Return a new
Terrainof points withinhalf_width_ftof the alignment (a station-sampled corridor), for trimming LiDAR to a site.
- elevation_along(alignment, station_ft: float, offset_ft: float = 0.0) float | None[source]
Ground elevation at
(station, offset)onalignment.
- elevation_at(x: float, y: float) float | None[source]
Ground elevation at plan
(x, y)by barycentric interpolation on the TIN;Nonewhen the point is outside the triangulated area.
- classmethod from_landxml(path, *, surface: str | None = None) Terrain[source]
Build from a LandXML
SurfaceTIN (survey deliverable).Honors the supplied faces (breaklines preserved). LandXML point coordinates are
northing easting elevation; they are stored as(easting, northing, elevation). Faces are 1-indexed; faces with a negative index (LandXML’s deleted/invisible marker) are dropped.
- classmethod from_las(path, *, ground_only: bool = True, bbox=None, thin: int = 1, preprocess: bool = False, voxel_size: float | None = None, nb_neighbors: int = 20, std_ratio: float = 2.0) Terrain[source]
Build from an OGRIP LiDAR
.las/.lazfile (lazylaspy).ground_onlykeeps only ASPRS class 2 (ground) returns;bboxis an optional(xmin, ymin, xmax, ymax)clip;thinkeeps everythin-th point to cap density.If
preprocessis True, use Open3D for statistical outlier removal and voxel downsampling (requiresopen3d).
- classmethod from_ogrip(bbox_wgs84, out_dir='temp_las', **kwargs) Terrain[source]
Fetch OGRIP/3DEP LiDAR tiles for a WGS84 bbox and build a Terrain.
Requires
requests. Tiles are ZIP archives (~50-120 MB each, holding one LAS) downloaded toout_dirand extracted there; both survive for reuse on the next call. When flights overlap (OSIP and 3DEP cover the same ground) only the newest collection year is used. Remainingkwargsare passed tofrom_las().
- classmethod from_ohio_dem(bbox, *, spacing_ft: float = 50.0, wgs84: bool = True, service: str | None = None, chunk: int = 400) Terrain[source]
Build a Terrain from real Ohio LiDAR by sampling the ODNR OH_DEM ImageServer over
bbox– the practical alternative to downloading LAS tiles.- Parameters:
bbox (tuple) –
(xmin, ymin, xmax, ymax). Whenwgs84(default) these are longitude/latitude degrees, projected to Ohio South State Plane feet (EPSG:3754,pyprojrequired); otherwise they are already in EPSG:3754 feet.spacing_ft (float) – Grid spacing of the elevation samples, feet.
service (str, optional) – Override the ImageServer URL (defaults to
OH_DEM_IMAGESERVER).chunk (int) – Points per
getSamplesrequest (batched multipoint POST).
- Returns:
Points
(easting_ft, northing_ft, elevation_ft)in EPSG:3754 – a feet frame consistent with the rest of the bridge stack. Cells the DEM marks NoData are dropped.- Return type:
Notes
Requires
requests(andpyprojwhenwgs84). Needs network access to the ODNR service; unlikefrom_ogripthere is no local file, so keep it out of import-time paths.
- classmethod from_points(points, faces=None) Terrain[source]
Build from an
(N, 3)array of ground points.
- classmethod from_xyz_file(path, *, skiprows: int = 0, cols: tuple[int, int, int] = (0, 1, 2)) Terrain[source]
Build from a whitespace/CSV
.xyz/.txtfile of point rows.
- civilpy.transportation.terrain.bbox_picker(center=(40.004, -83.005), zoom=14) BboxPicker[source]
A
BboxPickercentered oncenter(lat, lon) — display it, draw a rectangle, then readpicker.bbox.
Module contents
Transportation engineering package.
Roadway geometric design (roadway),
horizontal/vertical curve calculations (curves),
and FHWA National Bridge Inventory utilities (civilpy.transportation.FHWA).