Open source · MIT · Python ≥ 3.11
A stateless civil/GIS compiler. It reads the LandXML and GIS files you already own — including the two things most readers get quietly wrong, clothoid spirals and station equations — answers one civil question about them, and writes exactly one folder with the provenance to prove it.
gisc compile corridor.conflicts \
--alignment alignment.xml --utils utilities.geojson \
--buffer-ft 15 --crs EPSG:2264 --out ./run
Two tasks today. corridor.conflicts asks what is inside N feet of an
alignment; alignment.crossings asks where something crosses the
centreline, and returns the crossing point with its station. gisc ir
prints the whole compiled plan and executes nothing.
gisc has no database. It reads your files in place, read-only, and writes one
folder: the plan it compiled, the results, a summary, and
provenance.json — source path, mtime, sha256, CRS in and out,
filter and feature counts for every input.
Every emitted feature carries the source it came from. If gisc cannot say where a geometry came from, it does not emit it. A missing CRS is a non-zero exit, never a guess. And a run either replaces the previous one completely or leaves it exactly as it was — the old run is moved aside, not deleted, and restored on any failure.
This is the one worth knowing about even if you never use gisc. An alignment
can be re-stationed, and when it is, the station of a point stops being the
distance to it. A <StaEquation> declares that at one point the
stationing jumps, so the drawing reads 14+00 Back = 20+00 Ahead.
Ignore it and nothing looks wrong. The geometry parses, the CRS resolves, the offsets are right — and every station past the equation is off by the size of the equation:
a reader that ignores StaEquation says 2000.0
Civil 3D says 2600.0
The mapping itself is simple once stated. The raw station is
staStart plus distance along. At each equation, with
staInternal = S and staAhead = A, everything past S is
displayed at raw + (A − S). Two details bite:
Some exporters omit staInternal and give only staBack;
the raw station then follows from the equations already applied, which means
file order matters. gisc reads both forms and says which it used.
A <Spiral spiType="clothoid"> is a transition whose curvature ramps
linearly, so its heading is exact in closed form and only the sine and
cosine of it need integrating:
θ(s) = θ₀ + sign · ( k₁·s + (k₂ − k₁)·s² / 2L ) k = 1/R, 0 for a tangent end
One expression covers an entry spiral, an exit spiral, and a compound spiral between two finite radii. Two things are easy to get wrong:
<PI> is the intersection of the
tangents at the spiral's two ends, so Start → PI is the entry tangent
whichever way round the spiral runs.
gisc integrates it, then checks the result against the <End> the
file declares and refuses a spiral that does not land there — naming a reversed
rot as the likely cause when flipping it would close.
A projected CRS measures grid distance; a design clearance is a ground distance. In EPSG:3857 near 35.6°N the factor is 1.230, so an uncorrected 15 ft buffer covers 12.2 ft of real ground. State plane runs the other way, slightly under 1. gisc measures the local scale factor empirically — a geodesic walk on WGS 84 — rather than trusting a projection's analytic factor, which is wrong for Web Mercator on an ellipsoid.
Two things quietly give that correction back, so gisc handles both. A buffer is a polygon inscribed in the true circle, and at the usual 8 segments per quadrant a 15 ft corridor is 14.93 ft at its ends. And the scale factor is a property of a point, so over a long alignment one buffer distance cannot be right everywhere; gisc measures the drift and reports what it costs in feet.
Stationing is measured in the alignment's native design CRS, because a station is a grid distance in the system the drawing was made in.
Undocumented behaviour found while validating gisc against Civil 3D 2023 over COM. Recorded here because none of it is written down anywhere obvious, and the first one produces a valid-looking result that is completely wrong.
AddFreeSCSGroup1's sixth argument is GreaterThan180.
The signature is (PreviousElementId, NextElementId, Spiral1Length,
Radius, Spiral2Length, GreaterThan180, spiralDefinition). Passing
1 succeeds, returns a valid object, and fits the 315°
reflex curve instead of the 45° one — 2900 ft where 985 ft was
expected, with nothing complaining. Check the fitted length against a hand
computation before trusting the result. spiralDefinition must be
1 for the clothoid; 0 raises.
acad.Documents returns an object with no methods at all —
Documents.Add, .Count and .Item all
raise AttributeError. It looks like a permissions failure and
isn't. Open a document first.ti.GetNames(fd.memid) returns the method name and its
parameter names. That is how GreaterThan180 was found.StationEquations.Add(RawStationBack,
StationBack, StationAhead, StationEquationType) — four arguments, type
1 for increasing.al.Entities
after adding geometry or Count reads zero.SpiralIn, Arc, SpiralOut.
They come back in id order, not station order — sort them, or your CoordGeom
is not a centreline.RadialEasting/RadialNorthing.Two alignments were built inside Civil 3D over COM and Civil 3D was asked for its own answers. The LandXML gisc reads was written from the geometry read back out of Civil 3D, so gisc parses Civil 3D's coordinates rather than the design that was fed in.
| Quantity | Civil 3D vs gisc |
|---|---|
| tangent–arc–tangent, length | −0.0025 ft / 1035 ft |
| spiral–curve–spiral, length | −0.0020 ft / 985 ft |
| centre-line XY, 40 stations | worst 0.0091 ft |
clothoid TotalX / TotalY | ~7 × 10⁻¹¹ ft |
| offset, 8 probes | worst 0.0073 ft |
| station across a station equation | worst 0.0300 ft |
The clothoid row is the one that says something. Civil 3D carries the spiral
analytically and publishes its own TotalX/TotalY; gisc
integrates it from nothing but the length and the two radii. Agreement to a
ten-billionth of a foot means whatever error remains is chording, not the
mathematics — and chording lands at 0.0091 ft against an advertised 0.01.
363 tests, 100% line coverage enforced in CI, on Linux and Windows across Python 3.11 and 3.12. The validation artifacts are committed, so the Civil 3D checks run anywhere without Civil 3D.
postgis and fema (NFHL) adapters are stubs.
They parse their input and fail with the exact SQL or HTTP request they would
have issued, rather than inventing an answer.LANDXMLOUT file containing a spiral or a station
equation is not exercised: Civil 3D's LandXML export is a modal dialog with
no COM entry point.