Corridor modelling on terrain for QGIS: typical cross sections,
TIN, earthwork measurement, profiles and drawings.
Rafael Vázquez · Claude
This plugin solves one concrete problem: extruding a typical cross section along an alignment, clipping it against the terrain and obtaining the resulting surface, with its measurements and its drawings. It is a Civil 3D deliberately reduced to that.
The complete workflow is:
1. You define one or more typical cross sections in the editor 2. You apply them to a line layer (a different section per run if you want) 3. The plugin clips them against any DEM and weaves the TIN 4. You get layers, earthwork volumes and, if you want, the merged terrain 5. You review the result in the profile viewer 6. You produce the drawings: plan, longitudinal, cross sections and earthworks 7. You save the whole corridor in a file to reopen it when needed
It works on QGIS 3.28 and later, including the 4.x series.
Run instalar.bat from the project folder. It copies (or links)
the plugin into the plugins folder of every QGIS profile it finds. Then, in
QGIS: Plugins → Manage and install plugins → Installed, and tick
AQGIS-Sivil3D.
Since version 1.0.0 the plugin is also in the official QGIS repository: Plugins → Manage and install plugins → All, search for «Sivil3D» and install it. There is no need to enable «Show also experimental plugins»: 1.0.0 was released as a stable version.
instalar.bat again every
time the code changes, and reload the plugin (the Plugin Reloader add-on
saves restarting QGIS).Merging with the terrain needs scipy, which ships with standard
QGIS installations. Without it everything else works and the merge warns instead
of failing.
Vector → AQGIS-Sivil3D → Language / Idioma…
The whole interface comes in English (the default) or Spanish. The choice belongs to the plugin and is not tied to the QGIS interface language, so you can keep QGIS in one language and the plugin in another.
The language reaches further than the menus. It also decides:
| What follows the language | Detail |
|---|---|
| the names of the generated layers | cross sections, footprint, axis… or secciones, huella, eje |
| the point codes | L_CARRIAGEWAY, AXIS, SLOPE_DAY… or I_CALZADA, EJE, TALUD_DAY |
| the labels printed on the drawings | the data band, the earthwork table and the scale legend |
| the starting library of typical cross sections | each language has its own, see 4.3 |
| the thousands separator | 1,234.5 in English, 1.234,5 in Spanish |
What is never translated is what gets stored: the keys of the section library, the field names of the output layers and the values written into them. That is what lets a project saved in one language be opened in the other. A corridor computed in Spanish keeps its Spanish layer names and point codes, and an English session still recognises them: it applies the right styles and the profile viewer still finds the axis.
A section is not drawn: it is composed. Each element starts where the previous one ended and produces one or more vertices. That is what lets the same section adapt to a different terrain at every station.
Typical cross section ├─ axis elevation (terrain | alignment Z), height difference, shift ├─ left ────┬─ fixed[] carriageway, shoulder, verge, kerb, ditch… │ ├─ cut[] applies if the terrain is ABOVE │ └─ fill[] applies if it is BELOW └─ right ────┘ independent branch, same structure
The two branches are completely independent: a different slope on each side comes for free, with nothing to configure.
At every station and for each side separately, the plugin looks at the
hinge point (the last element of fixed on that side) and
compares the design elevation with the terrain one:
| At the hinge point | Group that applies |
|---|---|
| design below the terrain | cut |
| design above or equal | fill |
A section on a side hill comes out with one side in fill and the other in cut without touching anything.
| Option | What it does | What for |
|---|---|---|
| from the terrain (DEM) | the axis rests exactly on the DEM | seeing what comes out from the platform camber alone, without excavating or filling at the axis |
| from the alignment Z | the geometry carries the elevation | a grade line already designed, editable vertex by vertex |
| constant value | the same elevation at every station: a level platform | a yard, the basin of a pond, a stockpile platform, or trying out the earthworks of a grade line before designing it |
| offset from the terrain | grade line parallel to the terrain at the given distance: positive above, negative below | a constant earthwork thickness: a pavement box, a track excavated half a metre, a bund or a levee |
The last two are typed in the field next to the drop-down. The height difference of the axis defined in the section editor is added in all four cases: with an offset of −0.30 in the dialog and a height difference of −0.20 in the section, the grade line ends up 0.50 m below the terrain.
Next to the field there is a Write into the layer Z button, active with the two options that carry a value. With constant value it puts that elevation in the Z of every vertex; with offset from the terrain it drapes: each vertex takes the DEM elevation at its position plus the offset. In both cases it leaves the «from the alignment Z» option selected. From then on the grade line lives in the geometry and can be edited vertex by vertex with the normal QGIS tools: this is the way to start from a level platform and then give it grades.
There is one limit that cannot be worked around: the geometry type is fixed by the layer provider. Hence two behaviours:
| If the layer… | What happens |
|---|---|
already accepts Z (LineStringZ) | it is edited in place: whatever Z it had is replaced and the changes are saved |
is 2D (LineString) | it is not touched —its provider would discard the Z on saving— and a new layer is created with the same fields and attributes, with the Z set, which is left selected as the alignment |
With «selected features only» ticked, the elevation is written into those alone. The dialog warns before touching anything, and the log records how many features were modified.
When draping, if the DEM has no data at some vertex of an alignment, that alignment is left untouched and a warning is issued: better than giving it a made-up elevation.
The constant value is the shortcut for a level platform —a yard, the basin of a pond, a stockpile platform— and also for trying out how much earthwork a grade line would cost before having it designed: you set an elevation, compute and read the volume. The height difference of the axis is added in all cases, so with a constant elevation of 250 and a height difference of −0.5 the grade line ends up at 249.50.
Each element produces vertices with a code
(CARRIAGEWAY, SLOPE_DAY…), to which the prefix
L_ or R_ is added according to the side. The codes
serve three purposes:
SLOPE code in both conditional groups is what you want, and that is
why validation does not flag it as a duplicate.The vertex where the slope cuts the terrain always carries the
_DAY suffix, whichever element generated it, so that the
daylight line is continuous. With several chained slopes the code
is that of the last one even if it was the first that cut the terrain: this way
the line stays a single one and the stitching keeps its anchor when the number
of segments changes from one station to the next.
Vector → AQGIS-Sivil3D → Typical cross section editor…
On the left, the library of sections. In the centre, the two branches with their three groups and the ordering buttons. On the right, the parameters of the selected element. Below, the section resolved over a test hillside.
Elements are resolved in the order they are in, so Up and Down change the result. The buttons are:
| Button | What it does |
|---|---|
| Add | menu with the elements valid for each group; it is inserted after the selected one |
| Duplicate | copies the selected element right after it |
| Remove | deletes the selected one |
| Up / Down | moves the element one position in the chain |
| copy to the other side | overwrites the opposite branch with this one; for symmetric sections |
The test hillside is configurable and is the quick way to check that the slope daylights and that the bench fits, with no need for a DEM or an alignment:
| Control | What for |
|---|---|
| terrain (%) | cross slope of the test terrain; with a high value you see the side-hill case |
| terrain at axis (m) | terrain elevation relative to the grade line. Positive = terrain above = cut on both sides |
| Z exag. | vertical exaggeration of the drawing |
| max width | maximum half-width of the terrain search |
| codes | shows or hides the vertex labels |
Colours: brown the terrain, blue the design, ochre the cut, green the fill. A green circle is a daylight point; a red circle, a truncated slope that never reached the terrain.
The library is a JSON file that lives in the user profile
(<QGIS profile>/aqgis_sivil3d/secciones_en.json), so it is not
lost when the plugin is updated. It can be imported and exported
to share it.
There is one library per language: secciones_en.json and
secciones_es.json. They are not shared because the name of a
section is its key —it is what you write in the alignment field to choose
which section applies to each run— and the point codes travel to the output
layer attributes. Each language therefore starts with its own catalogue, in its
own language, and the first time you use a language the plugin writes it for
you.
| Section | What it is |
|---|---|
| Road 1+1 | carriageway, shoulder and verge, with a ditch in cut and a slope in fill |
| Track 5 m | a simple track with a ditch on both sides |
| Hillside platform (benched) | cut with berms, for a side hill |
| Platform from fields | widths and slopes read from the layer: a different section per feature |
| Trench 2 m | bottom at an imposed elevation and side walls, for a trench or a channel |
| Concrete gravity dam | the usual triangular profile: upstream face 0.15H:1V and downstream face 0.75H:1V |
| Rockfill dam with face slab | upstream slope 1.25H:1V —the face slab one— and downstream shoulder 1.4H:1V with 5 m berms every 15 m of height |
| Embankment dam | 2.5H:1V slopes on both sides, with berms on the downstream shoulder |
| Concrete wall | vertical faces; a starting point for a wall or a face slab |
All nine are already in the library the first time you open it. The complete
pack also travels inside the plugin, in
docs/secciones_pack.json, in case it needs reimporting.
The body of a dam is raised from the terrain up to the crest, so it goes entirely in the fill group: the slope starts at the crest edge and goes down until it meets the valley. The cut group only comes in where the terrain is above the crest, that is, at the abutments.
Two things are done differently from a road:
And for the basin, the reservoir capacity curve tool (see 13) gives the elevation-volume-area relationship from the DEM.
The strip at the bottom warns about the problems that can be detected without terrain:
| Warning | What it means |
|---|---|
| repeated codes | two elements that can coincide in the same resolved section have the same code; the meshing will not know how to pair them |
| empty group | one side has cut but not fill (or the other way round): at the stations where the empty group applies, the surface will be left open |
| does not seek the terrain | the conditional group does not end in a slope that daylights |
| is not the last one | an intermediate slope cannot hand over to the next (it has no segment limit, or they are benches, which do not take one); whatever comes behind will be ignored |
| below its limit | the maximum height of an intermediate slope cuts before its segment limit, so the hand-over would never happen |
| Element | What it does | Parameters | Groups |
|---|---|---|---|
offset_pendiente | advances a width at a given slope | width, slope (%) | all |
offset_cota | advances a width up to a height difference or an absolute elevation | width, mode, value | all |
vertical | goes up or down without advancing: kerb, wall, step | height difference | all |
cuneta | triangular or trapezoidal ditch (2 or 3 vertices) | depth, bottom width, inner and outer slopes, exit height difference | all |
talud_daylight | continuous slope until it cuts the terrain; goes up in cut and down in fill | slope, maximum height, segment limit and how it is measured | cut, fill |
talud_banqueta | repeats slope + bench until it cuts the terrain | slope, bench height and width, bench slope, maximum number of benches, slope of the 2nd and following | cut, fill |
offset_pendiente, talud_daylight…): they are what is
written in the section library JSON, so a library made in one language opens in
the other. What is translated is the name shown in the editor.The last two seek the terrain, and the last element of a conditional
group has to be one of them for the surface to close. With
talud_banqueta, the number of benches is decided by the terrain
at each station, not by you.
An intermediate talud_daylight is also valid, and that is
how a slope that changes gradient is defined: 1:1 up to 2 m of height and 1:1.5
from there on, for example. You put two slopes in a row and give the first a
segment limit of 2 m:
The break is a normal vertex: it anchors the TIN stitching and comes out as a feature line (the change-of-slope line), but it is not part of the daylight line. It appears only at the stations where that segment did not reach the terrain, just like the benches.
Its code is numbered automatically: SLOPE_Q1,
SLOPE_Q2… That is why the slopes of a chain can all be called the
same, and in fact that is what you want: they share the code of the
daylight vertex, so the toe or crest line comes out in one piece, and
their breaks do not get confused with each other because they are numbered.
Validation does not flag a repeated code between chained slopes of the same
group as a duplicate.
Behind a slope there can be any element, not just another slope: a
bench (Offset with slope with a slope of 0 or a slight adverse
slope), a catch ditch, a vertical segment… and then another slope. They alternate
as many times as needed, and the last one in the chain is the one with no
segment limit, so that it closes.
This is how a tall embankment is defined giving each segment its own
slope, its own height and its own bench, which is what a real case usually
asks for. The Benched slope element always repeats the same
[slope + bench] pair; by hand you get control segment by segment, at the price of
writing several elements.
In front of the first slope this does not happen: there the elements are a continuation of the platform and are always drawn. That is what lets the ditch of the road and track templates —which is the first element of the cut group— keep working the same way, and stops a shallow cut from closing at the ditch without reaching the slope.
The limit is measured by elevation (vertical height of the segment) or horizontally (distance covered), as you choose. More than two slopes can be chained; the last one is the one with no limit. Two details:
| Type | How it is written |
|---|---|
| lengths | metres: 3.5 or 3,5 (the comma works) |
| slopes | in % with a sign: -2, -2%. Positive = rises away from the axis, so a camber of -2% falls outwards on both sides |
| side slopes | V:H, the usual notation: 1:1.5 is gentle, 1:0.5 steep. 66% and a bare number (1.5 = 1:1.5) also work. Always positive: the slope is a magnitude and the direction is decided by the group (up in cut, down in fill) |
| optional slope | empty or 0 means «inherit the previous element's» |
Offset with
slope, whose slope does carry a sign. And inside a chain it also seeks the
terrain (see 5.2), so it daylights just like a slope. A negative side slope
raises an error on purpose, so as not to do the opposite of what is
written.Any parameter also accepts two forms that are resolved once per feature of the alignment:
| Form | Example | When |
|---|---|---|
| field reference | @width | one value per run, read from the alignment layer |
| QGIS expression | =if("rock" = 1, '1:0.5', '1:1.5') | logic per run |
In the editor, the test values box lets you preview a section with
dynamic parameters: width=7, cut_slope=1:1. Anything not given uses
the parameter's default.
Vector → AQGIS-Sivil3D → Extrude typical cross section (corridor)…
| Field | What it is |
|---|---|
| Alignments | line layer. Each feature is an independent corridor, and so is each part of a multiline |
| selected features only | processes only the selected features |
| Terrain (DEM) | any raster GDAL can read (GeoTIFF, ASC, IMG, DEM, HGT, VRT…) or a mesh layer. It can be in another reference system: it is transformed |
| Section library | JSON file; the button opens the editor |
| Field with the section | text column whose value is the exact name of the section that applies to that run. Empty = default section |
| Default section | the one used when there is no field or it comes empty |
| Axis elevation | four options, see 6.2.0 |
| Option | What it does |
|---|---|
| Station interval | spacing of the sections along the axis |
| Station at every vertex | leave it on. At a vertex the section is placed on the bisector of the angle; without a station there, the mesh opens up on the outside of the curve |
| Start station | station value at the beginning of each run |
| Labelled station interval | how often stations are labelled (100 m by default). It is independent of the computation step: you can compute every 5 m and label every 100. With 0 the stations layer is not created |
| Sampling step | how often the DEM is queried when seeking the cut. 0 = automatic from the resolution |
| Maximum half-width | maximum distance at which the terrain is sought. If a slope does not cut within it, it is marked as truncated |
DEM sampling. It is interpolated bilinearly between the centres of the four neighbouring pixels, the pixel value is not taken. With pixel values, the search for the cut jumps and the daylight line comes out saw-toothed. Reading goes through cached tiles, because each bisection is about 40 samples.
Tight curves. On the inside of a curve the normals converge and beyond a certain distance from the axis the adjacent sections cross. The plugin computes that distance per station and warns if the section width exceeds it: reduce the station interval or smooth the alignment. It does not fix it on its own.
NoData. If the axis falls on NoData, the section is discarded. If it is the slope that enters NoData, a warning is issued and it is truncated.
With the option on, the output TIN also includes the original terrain around the corridor, stitched to the daylight line, so that a single continuous surface is obtained.
How it works: the terrain is sampled on a grid, the nodes that fall inside the corridor footprint are discarded (and those too close to the edge, which would only give sliver triangles), the densified footprint with the corridor elevations is added —those are the seam nodes— and it is triangulated. Then the triangles whose centroid falls inside the footprint are thrown away.
| Option | What it does |
|---|---|
| Terrain cell | grid step. 0 = the DEM resolution |
| Margin around | how much terrain is included beyond the footprint |
If the requested cell would give an unmanageable grid, it is enlarged and a warning is issued, instead of exhausting the memory. The result has no gaps or overlaps: in the tests, the sum of the triangle areas matches the complete rectangle.
They all come out inside a group called
<alignment>-corridor, ordered for drawing and with symbology
applied. Each computation creates a new group (… (2),
(3)…) so that alternatives can be compared.
| Layer | Geometry | Content |
|---|---|---|
| stations | PointZ | round stations with P.K. (1+250), distance and angle; label rotated and offset from the axis |
| points | PointZ | every vertex, with code, offset and elevation |
| feature lines | LineStringZ | one per code, plus DAYLIGHT_IZQ and DAYLIGHT_DER |
| cross sections | LineStringZ | one per station: mode of each side, widths, cut/fill depth, cut and fill areas, warnings |
| axis | LineStringZ | the axis with a road symbol: the line on which the stations are labelled |
| footprint | PolygonZ | plan outline of each run |
| TIN | PolygonZ | one polygon per triangle, with zona, espesor, area, elevations and slope in % |
| earthworks | table | earthwork volumes between sections |
| 2DM mesh | .2dm file | loaded as a native mesh layer: contours, 3D view, mesh calculations |
The TIN comes out categorised by the zona field:
| zona | Colour | What it is |
|---|---|---|
desmonte | ochre | the design is below the terrain: excavation needed |
terraplen | green | the design is above: filling needed |
a cota | grey | strip next to the daylight, practically on the terrain |
terreno natural | tan | untouched terrain, in the merged mesh |
The classification is done triangle by triangle comparing its mean elevation
with the terrain one under its centroid. The espesor field stores
the signed height difference (negative excavates, positive fills), so the colour
can also be graduated by earthwork thickness. They are the same colours as the
editor preview.
The cut and fill areas of each section are obtained by integrating between the design line and the terrain one, splitting at the crossings: it is not an estimate, it is the exact area of the polygons you see shaded.
The volumes go by average end areas:
V = (A1 + A2) / 2 · L
which is the design convention and the one to use if the numbers have to match a measurement done by hand. There is also the prismoidal method, which damps the bias at the change from cut to fill, where one of the two areas is zero.
The mass haul diagram is the cumulative net volume, with a configurable bulking factor: 1.0 means that one cubic metre excavated fills one cubic metre; 0.9, that only 90 % is usable.
The measurement comes out in two places: the cross sections layer
carries cota_roja, area_des and area_ter
per station, and the earthworks table carries, for each run between
consecutive sections, the areas, the cut and fill volumes, the net and the
cumulative.
Vector → AQGIS-Sivil3D → Corridor profiles
Dockable panel with three views of the computed corridor:
| View | What it shows |
|---|---|
| Longitudinal profile | grade line against terrain along the axis, with cut and fill shaded and the stations marked |
| Section at the current station | the cross section with its codes, its cut/fill depth and its areas |
| Mass haul diagram | cumulative net volume |
| Control | What it does |
|---|---|
| Alignments | checklist of all the alignments of all the corridors in the session: you tick the ones you want to see at once, and they can be sorted by name or by date. See 10.2 |
| ◀ Sta ▶ | navigates the stations; the drop-down lets you jump to a specific one |
| vert. exag. | vertical exaggeration of the longitudinal. Without it the grade line would be a horizontal stroke |
| codes | vertex labels on the section |
| click on the map | enables a tool that jumps to the station nearest to the clicked point |
| Export… | saves the current view as a PNG |
Navigation is synchronised with the map both ways: as you move through the stations the section is highlighted on the map with a rubber band, and clicking on the map (with the box ticked) jumps to the nearest station. You can also click directly on the longitudinal profile to go to that station.
The Alignments box is the same widget in the viewer and in the drawings dialog. It lists one row per alignment (one per feature of the alignment layer) of each corridor in the session, with its own colour and a checkbox:
| Concept | What it means |
|---|---|
| checked (box) | enters the combined drawing: it appears on the longitudinal profile and on the mass haul diagram, overlaid and dashed in its colour, and it appears in the drawings |
| main (drop-down at the top) | rules where only one fits: the cross section, the station navigation and, in the drawings, the layout that defines the plan sheets. It is drawn with a solid line and gives the title |
The All / None / This one only buttons are shortcuts. Highlighting a row of the list also makes it the main one, and the other way round. The main one need not be checked: you can compare two alignments on the longitudinal while looking at the section of a third.
The Order drop-down sorts the list by creation date (oldest corridors first, which is the order in which they were computed) or by name, alphabetically. With many alternatives in the session, alphabetical is the one that lets you find one by its name. Reordering changes nothing else: the colour of each alignment does not depend on its position in the list, so one that is drawn does not change colour when reordering, nor is anything you had ticked lost.
In the drawings the criterion is the same, but each drawing does what makes sense with several alignments:
It is decided by the Drawing set drop-down, which is at the top of the dialog because it rules over the three drawings (see 11.3):
| Drawing | A single set, that of the main one (default) | A set per alignment |
|---|---|---|
| Cross sections | the cuts of the main one, and only those. The others appear as a trace on those very cutting planes | all the sections on the same sheet, with fid n in front of the station: two 0+000 stations of different axes would be indistinguishable |
| Longitudinal | that of the main one. The others appear where they cut its vertical plane | one strip per alignment, one after another, with its label in the title |
| Plan | one set of sheets framed and rotated on the main one | each alignment generates its own sheets |
| Earthworks | that of the main one | one table per alignment. Measuring different runs together would invent a volume between the end of one and the beginning of the next |
And in all three, with the Others box ticked, the other corridors are drawn where they cross or run close. See 11.4.
Vector → AQGIS-Sivil3D → Generate drawings…
QGIS has no layout item that draws profiles, and assembling hundreds of layout polylines is not viable. So «drawing on the sheet» is turned into «generating geometry»: a paper-space drawing layer where X and Y are millimetres of paper and each section is translated to its cell of a grid. The drawing is nothing more than a map framed on that layer.
Advantages: it is symbolised with QGIS like any other layer, the atlas paginates on its own, DXF export comes for free and the scales stay explicit.
| Drawing | How it is generated |
|---|---|
| Plan by sheets | layer of sheet rectangles along the axis, used as the atlas coverage: one page per sheet, with the map rotated according to the rotacion field so that the axis comes out horizontal, and a configurable overlap between sheets |
| Longitudinal profile | paper space at independent H and V scales (1:1000 and 1:100 by default) with the data band underneath: station, ground level, design level and cut/fill depth, with the values rotated. It is paginated into strips: the sheet is cut when the width runs out or when the height no longer fits |
| Cross sections | grid of n×m sections per sheet. The cells are sized to fill the paper you choose |
| Earthworks | closing sheet with the table of volumes per run and the summary of totals. It paginates on its own if it does not fit, and the totals always go on the last one |
A drawing is that of one axis: its cross sections are the cuts of its route and its longitudinal is its vertical plane. With several alignments checked you have to say whether you want a single project or several, and that is what the Drawing set drop-down is about, at the top of the dialog next to the list of alignments:
| Mode | What it does | When |
|---|---|---|
| A single set, that of the main alignment | cross sections, longitudinal and plan sheets of the main one. The others do not bring their own set: they appear where they cut those planes, dashed | alignments that run together: a road and its diversion, two carriageways, a slip road |
| A complete set per alignment | each one generates its cross sections, its longitudinal and its sheets, one after another and numbered continuously | alignments far apart or that are different works, each needing its own framing |
With the Others box ticked (it is on by default), the drawing of one alignment also shows the rest:
| Drawing | What you see |
|---|---|
| Cross sections | the trace of the other corridor on that very cutting plane. It is computed by cutting the section plane with the other's feature lines (carriageway edge, shoulder, toe of slope…), so it is literally its section there |
| Longitudinal | the intersection of the other corridors with the vertical plane that runs along the main one. An alignment that crosses it appears only in that stretch —what it measures across at the crossing—, with its elevation, so you can see where it goes: above or below. One that runs parallel and does not cut it does not appear |
| Plan | they always appear, because the map draws their layers |
The other traces are drawn dashed and in purple, so that they are not confused with the design being dimensioned, and they are clipped to the frame of the cell: a corridor passing 40 m away from a drawing that spans 15 cannot end up painted over the title block. If it does not fit, it is not shown.
The cut is made against the two families of lines of the other surface: the longitudinal ones (the feature lines) and its own sections. Both are needed: with nearly parallel alignments the plane crosses the longitudinal ones, and with two that meet at a right angle the opposite happens —the other's longitudinal lines are parallel to the plane and never touch it—.
The Drawing layers box lists the project layers that can go to the map and lets you tick the ones you really want. By default the corridor outputs (TIN, footprint, axis, feature lines, cross sections, stations) and the alignment are ticked; the rest of the project —cadastre, orthophotos, working layers— is left out.
The buttons are All, None and Corridor only. Paper-space drawing layers are not even listed: their coordinates are millimetres and on the map they would appear hundreds of kilometres away.
The longitudinal profile sheets are cut for two reasons: because the paper width runs out or because the height no longer fits. The second one is what throws you: you choose A3 and get strips that take up half of it.
With A3 landscape and margins, about 366 mm of usable width and 221 mm of drawing height are left. At a horizontal scale of 1:1000, 366 m of axis fit per sheet; at a vertical scale of 1:100, 22 m of height fit. If the terrain rises more than 22 m in less than 366 m —a grade above 6 %—, the height rules and the sheet is cut earlier:
| Vertical scale | Height that fits | Width used |
|---|---|---|
| 1:100 | 22.1 m | 71 % |
| 1:200 | 44.2 m | 98 % |
It is not a fault: if the drawing does not fit vertically, it has to be split. The fix is to lower the vertical scale (1:200, 1:250) or raise the horizontal one. The log says so when generating, with the scale that would fill the sheet.
| Group | Options |
|---|---|
| Which drawings | checked alignments and which is the main one, drawing set (11.3), Others box (11.4), title and .qpt template |
| Cross sections | paper, columns and rows of the grid, scale (or automatic) and earthwork sheet |
| Longitudinal | paper, H and V scales, and how often a data band column is placed |
| Plan | paper, scale, overlap between sheets and which project layers reach the map (11.5) |
| Export | output folder and formats: PDF and DXF |
.qpt template, it is loaded and the plugin only fills its variables
(@corredor_titulo, @corredor_subtitulo,
@corredor_papel), respecting its page size and using its map item.
That way you keep control of the corporate format.Vector → AQGIS-Sivil3D → Vertical alignment editor
It is the longitudinal profile in digitizing mode: the grade line is drawn with the mouse over the terrain, with the same tools used to digitize in plan view, and when finished it is written into the alignment Z. From there the corridor is computed with axis elevation = alignment Z (see 6.2.0), so this editor is the missing piece for designing in elevation without leaving QGIS.
| Control | What it does |
|---|---|
| Alignment | line layer the grade line is designed on. On opening, the active layer is chosen, or the first one that is not an output of the corridor itself (the axis, the cross sections and the feature lines are outputs, not alignments) |
| Run | the feature (and the part, if it is a multiline) being edited; each run has its own grade line |
| DEM | raster or mesh the terrain profile comes from. It is optional: without it you can draw blind, but you cannot see what against |
| exag. | vertical exaggeration of the view. Without it a 3 % grade line is a horizontal stroke |
| Fit | fits the view again after zooming or panning |
| From the terrain | starts a grade line hugging the terrain with the number of vertices you give: the usual starting point |
| From the layer Z | reads the grade line the geometry already has, vertex by vertex. Choosing a run with Z does it automatically |
The view is handled like any other drawing: wheel to zoom around the cursor and middle button dragging to pan.
The first thing to be clear about is that the bar has two different things:
| What they are | How they behave | |
|---|---|---|
| Move, Insert, Delete | modes | they stay active until you change tool |
| Straight, Arc, Bézier, NURBS, + control | one-shot actions | they change the segment and control returns to Move on its own, which is what you want to do next: drag the handles |
The actions can be used in two ways: with a segment already selected (they apply when you press the button, and that is it) or with nothing selected (they stay armed and apply to the segment clicked next). Below the bar there is a label that says at each moment what is expected of you.
| What | Appearance | What it does |
|---|---|---|
| Vertex | white square (orange when selected) | dragged in station and in elevation; it is a vertex of the grade line |
| Handle | purple circle, joined by a dashed line | shapes the curved segment. The arc has one and passes exactly through it; the Bézier has two and approaches without reaching them; the NURBS has as many as you add |
When you hover over it, what can be grabbed is surrounded by a circle, and the segment under the cursor is marked in bold. If nothing is highlighted, there is nothing to grab there.
The grade line is an elevation law independent of the plan: its vertices have nothing to do with those of the alignment polyline. Even so it can have quite a few, and raising or lowering a whole stretch one by one would distort the grades along the way. That is why several can be grabbed:
| Gesture | What it does |
|---|---|
| Ctrl (or Shift) + click | adds or removes that vertex from the group |
| dragging from empty space | opens a selection window; on release the vertices that fell inside are added |
| Ctrl+A / All button | selects them all |
| Esc / None button | clears the selection |
| dragging a vertex of the group | moves all of them by the same increment, so the stretch rises or falls without distorting |
| Del | deletes all the selected ones (never leaves fewer than two) |
The group's vertices are painted orange, and the top left shows how many are grabbed.
An alignment can have hundreds of vertices, and if the grade line is already written in its Z it will be densified as well. Reading that vertex by vertex would give a grade line of three hundred nodes that nobody can handle.
That is why, when reading the layer Z, it is simplified with the tol. field at the bottom (tolerance in metres of elevation): it keeps the vertices that really define the grade line, guaranteeing that at no point does it depart more than that tolerance from the original. With 0 they are all read. The Simplify… button does the same on the grade line being edited.
| Gesture | What it does |
|---|---|
| double click | inserts a vertex there, with any tool active |
| right button on a vertex | removes it, as in QGIS digitizing |
| Del | removes the selected vertex |
| Esc | goes back to Move mode |
| wheel | zoom around the cursor |
| middle button dragging | pans the view |
| Fit | frames everything again |
Besides the segment types —which are those of plan digitizing— each inner vertex accepts a vertical curve: the good old symmetric parabola that rounds the corner between two straights. It is the tool actually used when designing a road and it has no equivalent in plan.
It is defined in two ways: by writing its length L in the corresponding column of the table, or by writing its K and pressing Apply K. K is the metres of curve per 1 % of grade change, which is how it comes in the standards; the editor computes L = K · |Δ grade in %| and the other way round.
| Rule | Why |
|---|---|
| only at inner vertices | at the ends there are no two grades to join |
| only between two straights | on a Bézier or a NURBS what rules are the handles; the curve is ignored and a warning is issued |
| cannot exceed the available tangent | otherwise it would eat the neighbouring vertex. It is trimmed to what fits and a warning is issued |
| if there is no grade change, there is no curve | a parabola of zero curvature is the straight itself |
In the view, the vertical curve is labelled with its L and its K, and its two ends are marked with a small circle: that is where the curve begins and ends.
| Setting | Effect |
|---|---|
| snap to the terrain | the vertex being moved sticks to the terrain elevation. Handy to start and to force a level crossing |
| m Sta | the abscissa is rounded to multiples of that value (25 m, 50 m…) |
| m elevation | the elevation is rounded to multiples of that value (0.10 m, 0.50 m…) |
The table on the right is the other half of the editor: station, elevation and L are typed in, and the segment type, the K and the grade are read. It is how a grade line is finished off when it is nearly there: with the mouse you look for the shape and with the numbers you close it.
While editing, the map highlights the alignment of the run with a rubber band and marks with a cross the point the mouse is over and the selected vertex. The readout at the top of the view gives, for the cursor position, station, elevation, terrain elevation and cut/fill depth. Cut and fill are shaded in the same colours as the rest of the plugin (ochre and green), so you can see at a glance which part of the grade line excavates and which part fills.
Apply to the layer Z writes the grade line into the geometry of the run being edited:
LineStringZ), it is edited in
place, within a normal editing session;The step field is the densification: besides the vertices the alignment already has, points are inserted at that step and at the singular points of the grade line (vertices and ends of each vertical curve). It is needed because a parabola between two vertices 300 m apart would be lost if only its ends were written. With a step of 0 only the existing vertices are written.
Vector → AQGIS-Sivil3D → Reservoir capacity curve
Characteristic curve of a reservoir or pond from the DEM of its basin: for each elevation, the stored volume and the flooded area.
For a water level e:
It is the usual computation, but solved by histogram accumulation: each cell is classified once according to the elevation from which it starts counting, and two cumulative sums give the complete curve. The result is exact and the cost does not depend on the number of elevations, so a basin of several GB with a 10 cm step comes out in a single pass. The DEM is read in blocks, through GDAL when it is a file and through the QGIS provider when it is not.
| Input | What it is |
|---|---|
| Basin DEM | any raster QGIS can read |
| Clip with a polygon | optional: polygon layer to clip the DEM with, to evaluate only inside. It accepts «selected features only» and reprojects the polygon to the DEM system if needed |
| Elevations to compute | by default from the raster minimum to its maximum (or the clip's, if any). The range can also be set by hand: for example from the bottom to the crest elevation |
| Step | interval between elevations. 0.10 m is the usual one |
| Output | When |
|---|---|
| CSV of the curve | always. Five columns: elevation, volume in hm³, area in ha, volume in m³ and area in m². With «;» as separator and a decimal comma, so that Excel in Spanish opens it without touching anything |
| PNG of the chart | always. The two panels as they appear, drawn at 1800 × 1200 |
| Clipped DEM loaded and styled | only if it was clipped. That is what distinguishes the two ways of using the tool |
With the folder given and the box ticked, the CSV and the PNG are written when
the computation finishes; the Save CSV and Save PNG buttons let you
choose the place by hand. The clip files carry the -basin suffix so
as not to overwrite those of the complete DEM.
The clipped DEM is loaded with a blue ramp in interpolated pseudocolor: dark blue at the bottom and light blue, increasingly transparent, towards the shore, so that where the depth is small the terrain underneath shows through. The ramp is stretched between the minimum and maximum elevation of the clip, so the same style works for a pond 3 m deep and for a reservoir 60 m deep with nothing to change. The value 0 is left transparent, because in a clip it is a hole and not an elevation.
Moving the mouse over the chart reads the elevation, volume and area of each point; clicking pins an elevation, which stays drawn with its label. The two panels share the elevation axis and have their own scale: they are two different magnitudes, hm³ and ha, and a double Y axis would force you to look at two scales to read a single point.
Corridor file → Export corridor to file… and Import corridor from file… (a single toolbar button with a drop-down; in the menu they are two separate entries)
The output layers are in memory: they are lost when QGIS closes. Exporting writes two sibling files:
| File | What it carries |
|---|---|
<name>.gpkg | all the layers of the group, each with its style inside and its original name. Open format: it opens in any GIS and in CAD |
<name>.corredor.json | the resolved sections: terrain line, axis elevation and codes of each vertex |
The JSON is what the GeoPackage cannot store and without which nothing but the drawing is recovered. With it, on import the whole corridor comes back and you can use the profile viewer, measure and generate drawings without the DEM and without recomputing.
To export, select the corridor group in the layers panel. If you only want
the drawing, the .gpkg on its own will do; importing it without the
JSON warns that the viewer will have no data.
Each computation and each import is registered as an independent
alternative, with the group name (axis-corridor,
axis-corridor (2)…). They do not overwrite each other, so they can be
compared.
The Alignments box of the profile panel and of the drawings dialog lists the alignments of all the alternatives together, so there is no need to pick one and discard the rest: you tick the ones you want to compare, whether from the same corridor or from different ones. See 10.2.
A computed corridor is more than its layers: the resolved sections —what the profile viewer and the drawing generator need— live in memory and are not in any layer. So that they are not lost on closing, the plugin saves and recovers them on its own by two routes:
| Route | When |
|---|---|
<project>.sivil3d.json | on saving the project it is written alongside it, and on opening it is read. It is the normal route: just save the project as usual |
<package>.corredor.json | if the project has layers coming from a GeoPackage exported with the packaging tool (see 14), its sibling JSON is read too. It works even if the project has never been saved, and it is what recovers a colleague's corridor |
Recovery is attempted on opening the project and also on opening the viewer or the drawings dialog, in case you have just loaded a GeoPackage by hand. What was computed in the working session always takes precedence over what was recovered, which is older.
Only what is still loaded in the layers panel is recovered. Either of the two tests will do: that its layer group is there —which is the normal case when reopening a project, because the alignments may not have been saved in it— or that its alignment layer is there, which is the test when it has just been computed and no group has been loaded yet. If neither remains, nothing of the corridor is left in the project and it is not offered: deleting a corridor's layer group is the way to remove it from the viewer and the drawings. They used to be offered anyway, and old alignments appeared that led nowhere.
The aqgis:corredor algorithm is in the Toolbox, provider
AQGIS-Sivil3D, group Corridor. It is meant for models, batch
processes and the console:
import processing
processing.run("aqgis:corredor", {
"DIRECTRICES": line_layer,
"DEM": dem_layer,
"LIBRERIA": r"C:\...\secciones_en.json",
"CAMPO_SECCION": "section",
"SECCION_DEFECTO": "Road 1+1",
"PASO_PK": 10.0,
"INCLUIR_VERTICES": True,
"COTA_EJE": 0, # 0 = from the DEM, 1 = from the alignment Z
"INTERVALO_PK": 100.0,
"FUSIONAR": True,
"CELDA_FUSION": 0.0, # 0 = DEM resolution
"HOLGURA": 50.0,
"GRUPO": "", # empty = <alignment>-corridor
"OUT_TIN": "TEMPORARY_OUTPUT",
"OUT_SECCIONES": "TEMPORARY_OUTPUT",
"OUT_LINEAS": "TEMPORARY_OUTPUT",
"OUT_HUELLA": "TEMPORARY_OUTPUT",
"OUT_EJE": "TEMPORARY_OUTPUT",
"OUT_PK": "TEMPORARY_OUTPUT",
"OUT_2DM": r"C:\...\corridor.2dm",
})
DIRECTRICES,
PASO_PK…) do not change with the language: they are the algorithm's
API and changing them would break the models and scripts that already use it.
What is translated is the label shown in the dialog. The
SECCION_DEFECTO value does depend on the language, because it is the
name of a section in the library.The fine parameters (sampling step, maximum half-width, start station, DEM band, elevation factor and offset, merge extent, library as JSON and layer group) are under Advanced.
| Symptom | Cause and fix |
|---|---|
| «slope 1:X does not cut the terrain within N m» | the slope does not daylight within the maximum half-width. Typical case: a 1:2 fill on a 50 % hillside is parallel to the terrain and never cuts. Change the slope or widen the half-width |
| «N stations in a curve where adjacent sections cross» | curve too tight for the section width. Reduce the station interval or smooth the alignment |
| «the terrain has no data… (NoData)» | the DEM does not cover that area, or there is a hole. Extend the DEM or fill the hole |
| «unknown section '…'; skipped» | the value of the section field does not match any name in the library. The log lists the valid ones |
| «uses fields that do not exist in the layer» | a section with @field parameters is applied to a layer that does not have those fields |
| «empty group… the surface will be left open» | the cut or fill group is missing on one side. If you really want that side to always be of one type, put the same slope in both groups |
| no triangle is generated | the DEM does not cover the alignments, or they are in incompatible reference systems |
| the merge warns that scipy is missing | install scipy in the QGIS Python; the rest of the plugin works without it |
| the longitudinal profile does not fit | reduce the horizontal scale or let it paginate: it is split into strips automatically |
| the viewer opens without drawing anything | that is normal: no alignment comes out checked except those of the active layer. Tick in the Alignments box the ones you want to see, or press All |
| «'…' is dropped: neither its layer group nor its alignment layer are still loaded» | that corridor is in the session file but nothing of it is left in the project, so it is not offered. If you want it back, load its layers again (or its exported GeoPackage); if not, there is nothing to do |
| alignments that used to appear are missing | that corridor's layers have been removed from the project. Deleting its layer group is the way to withdraw it from the viewer and the drawings |
| «cannot be negative; the slope is a magnitude» | you have written a signed side slope, e.g. -66%. The direction is decided by the group. If you want to go the other way, use Offset with slope (see 5.3) |
| a bench is cut short of its width | it has reached the terrain. Inside the chain that seeks the terrain, every element stops at it (see 5.2). If you do not want it there, put it in front of the first slope |
| «repeated codes» | two elements that coincide in the same section have the same code and the meshing will not know how to pair them. Note: between chained slopes of the same group repeating the code is fine, and no warning is issued |
| after changing the language the menu is still in the old one | restart QGIS. The menu, the toolbar and the docked panels are built when the plugin loads; the dialogs are already translated the next time you open them |
| after changing the language the section library looks empty | each language has its own library (see 4.3). The first time you use a language its starting catalogue is written; if you had sections of your own in the other language, export them and import them here |
@field parameters or expressions).The separation is deliberate: core/ is pure Python and imports
nothing from QGIS, so all the delicate geometry (the TIN stitching, the daylight
search, the measurements, the paper space) can be tested without opening QGIS.
gis/ is what talks to QGIS; ui/, the interface;
processing/, the algorithm; i18n/, the language
catalogue.
python -m unittest aqgis_sivil3d.tests.test_core \
aqgis_sivil3d.tests.test_medicion \
aqgis_sivil3d.tests.test_paperspace \
aqgis_sivil3d.tests.test_i18n
Those of the QGIS layer need the QGIS Python:
aqgis_sivil3d.tests.test_gis. Among them there are two guards worth
not removing: one checks that no PyQt5 APIs removed in PyQt6 are left
(exec_, QRegExp…), and another that there are no
unscoped Qt enums outside ui/qtcompat.py. They are faults that do
not show on import, only when you press the button.
On texts: the literal in the code goes in English, which is the
original language, and it is translated with t(). What is stored
—internal keys, field names, the section library format— is never translated.
test_i18n checks that the format placeholders of the Spanish
catalogue match those of the original, because a mismatched one would leave the
string unformatted on screen.
Written by Rafael Vázquez with Claude (Anthropic). Version 1.4.1.
It is distributed as free software under the GNU General Public Licence, version 3 or later (GPL v3+). Anyone can use it, study it, modify it and redistribute it, but with one condition that makes it the most restrictive of the free licences: whoever distributes the plugin, or a modified version of it, has to do so under this same licence and hand over the source code. Nobody can close it or integrate it into a proprietary product. It is delivered without warranty of any kind.
The full text is in the LICENSE file that accompanies the plugin,
and also at gnu.org/licenses.