Releases: mf4633/gisc
Release list
gisc 0.3.1 — first PyPI release
First release on PyPI. pip install gisc
0.3.0 was tagged before the publish workflow existed, and a release event runs
the workflow as it exists at that tag — so this is the first tag the workflow
can act on. No library change from 0.3.0: packaging metadata and the release
workflow only. Everything below is what 0.3.0 brought.
ir.py has claimed since 0.1.0 that it is "one common plan that every task
compiles down to". With exactly one task that was untestable. This release is
the test, and the claim came out partly false.
alignment.crossings
A second task, deliberately a different shape of question: where does this
cross the centreline, not what is near it. No buffer at all, and an answer
that is a point existing in neither input until the intersection makes it.
gisc compile alignment.crossings \
--alignment alignment.xml --utils utilities.geojson \
--crs EPSG:2264 --out ./run
| name | station | geometry |
|---|---|---|
| WL-8IN | 12+00.00 | Point |
Written first with zero engine changes, it ran end to end and returned a
LineString — the whole 90 ft pipe — for a question asking where it crosses.
It did not error. It was simply, confidently wrong.
What the experiment found
The op set survived unchanged. No seventh op. That part of the claim is
real, and a test now pins it. Two things did not survive:
Plan.buffer_ftwas a required field of the IR, so a task with no
corridor had to name a corridor width. Optional now, and omitted from
plan.jsonand from provenance when a task doesn't buffer. The buffer op's
owndist_ftwas always its real home.intersectalways kept source geometry. Correct for which pipe is in my
corridor — the answer is the pipe, and how much of it is inside is an
attribute — and useless for where does it cross. It takes ageometrymode
now, defaulting tosourcesocorridor.conflictsis untouched, and records
which mode ran plus the geometry types produced.
The ir.py docstring says all of this rather than asserting generality it had
not earned, and ends: read "every task" as a claim with two data points behind
it, not a law.
The two tasks correctly disagree
Same fixtures, same centreline:
corridor.conflicts WL-8IN and SS-12IN — both within 15 ft
alignment.crossings WL-8IN only — SS-12IN runs parallel, 8 ft off,
and never touches the centreline
Both right; they answer different questions. Where both report a station they
agree exactly. A flood polygon the alignment runs through yields the line
through it rather than collapsing to a point.
Tests
363 passing, 100% line coverage, enforced in CI across Linux and Windows on
Python 3.11 and 3.12.
Known limits
postgis and fema remain stubs that print the exact SQL or HTTP request they
would issue. Non-clothoid spirals are refused by name rather than approximated.
A genuine LANDXMLOUT file containing a spiral or a station equation is still
not exercised — Civil 3D's export is a modal dialog with no COM entry point.
Full changelog: v0.2.0...v0.3.0
gisc 0.3.0 — a second task
ir.py has claimed since 0.1.0 that it is "one common plan that every task
compiles down to". With exactly one task that was untestable. This release is
the test, and the claim came out partly false.
alignment.crossings
A second task, deliberately a different shape of question: where does this
cross the centreline, not what is near it. No buffer at all, and an answer
that is a point existing in neither input until the intersection makes it.
gisc compile alignment.crossings \
--alignment alignment.xml --utils utilities.geojson \
--crs EPSG:2264 --out ./run
| name | station | geometry |
|---|---|---|
| WL-8IN | 12+00.00 | Point |
Written first with zero engine changes, it ran end to end and returned a
LineString — the whole 90 ft pipe — for a question asking where it crosses.
It did not error. It was simply, confidently wrong.
What the experiment found
The op set survived unchanged. No seventh op. That part of the claim is
real, and a test now pins it. Two things did not survive:
Plan.buffer_ftwas a required field of the IR, so a task with no
corridor had to name a corridor width. Optional now, and omitted from
plan.jsonand from provenance when a task doesn't buffer. The buffer op's
owndist_ftwas always its real home.intersectalways kept source geometry. Correct for which pipe is in my
corridor — the answer is the pipe, and how much of it is inside is an
attribute — and useless for where does it cross. It takes ageometrymode
now, defaulting tosourcesocorridor.conflictsis untouched, and records
which mode ran plus the geometry types produced.
The ir.py docstring says all of this rather than asserting generality it had
not earned, and ends: read "every task" as a claim with two data points behind
it, not a law.
The two tasks correctly disagree
Same fixtures, same centreline:
corridor.conflicts WL-8IN and SS-12IN — both within 15 ft
alignment.crossings WL-8IN only — SS-12IN runs parallel, 8 ft off,
and never touches the centreline
Both right; they answer different questions. Where both report a station they
agree exactly. A flood polygon the alignment runs through yields the line
through it rather than collapsing to a point.
Tests
363 passing, 100% line coverage, enforced in CI across Linux and Windows on
Python 3.11 and 3.12.
Known limits
postgis and fema remain stubs that print the exact SQL or HTTP request they
would issue. Non-clothoid spirals are refused by name rather than approximated.
A genuine LANDXMLOUT file containing a spiral or a station equation is still
not exercised — Civil 3D's export is a modal dialog with no COM entry point.
Full changelog: v0.2.0...v0.3.0
gisc 0.2.0 — spirals and station equations
Closes what the README used to confess: spirals, station equations and a curved
alignment from a real export were not exercised.
The one that mattered
StaEquation appeared nowhere in the codebase. A re-stationed alignment parsed
perfectly, offset perfectly, and reported every station past the equation off by
the size of the equation, with nothing on the record to say so:
gisc says the far end is station 2000.0
Civil 3D says it is 2600.0
Right geometry, right offset, right side, wrong station — the failure with no
symptom, in a tool whose whole thesis is never being quietly wrong. gisc now
applies the equations, numbers the regions and keeps the raw stations beside
them. Gaps and overlaps are each called out, and a feature straddling an
equation is labelled with both regions, because across one the displayed
stations stop being a length.
What else is new
- Clothoid spirals. A clothoid's heading is exact in closed form, so only
its sine and cosine are integrated. Each spiral is checked against the<End>
the file declares and refused if it does not land there — naming a reversed
rotas the likely cause when flipping it would close. - Chord count follows the tightest end, not the average. Curvature is not
constant along a spiral; sizing from mean curvature under-chorded by 2× and
quietly delivered 0.034 ft against an advertised 0.01 ft. Now 0.0096 ft. - The buffer stops giving back what the scale factor bought. A buffer is a
polygon inscribed in the true circle, so at 8 segments per quadrant a 15 ft
corridor is 14.93 ft at the ends. Segment count now comes from the tolerance,
and the scale factor's drift along a long alignment is measured and reported. - A run is all-or-nothing. The previous run is moved aside rather than
deleted, and restored on any failure — including one gisc has no error class
for. - Two plain bugs: the output folder was claimed before compiling, and the
sample op's loop variable shadowed the op's ownalong.
Checked against Civil 3D 2023, not against itself
Two alignments were built inside Civil 3D over COM, and the LandXML gisc reads
was written from the geometry read back out of it.
| quantity | Civil 3D vs gisc |
|---|---|
| tangent–arc–tangent, length | −0.0025 ft over 1035 ft |
| spiral–curve–spiral, length | −0.0020 ft over 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 the 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.
If you drive that COM API yourself: AddFreeSCSGroup1's sixth argument is
GreaterThan180, and passing 1 succeeds, returns a valid object, and fits the
315° reflex curve — 2900 ft where 985 ft was expected, with nothing
complaining. spiralDefinition must be 1. StationEquations.Add takes four
arguments. See validation/README.md and validation/c3d_api_dump.json.
Tests
339 passing, 100% line coverage, enforced in CI with --cov-fail-under=100.
The four guards that no input can reach carry pragma: no cover and the reason,
rather than being mocked into a number — one of them provably cannot execute at
all, because pyproj clamps an out-of-domain point instead of returning inf.
Known limits
A genuine LANDXMLOUT file containing a spiral or an equation is still not
exercised — Civil 3D's export is a modal dialog with no COM entry point.
Non-clothoid spirals (bloss, sinusoidal, the cubics) are refused by name rather
than approximated. postgis and fema remain stubs that print the exact SQL or
HTTP request they would issue. There is one task, corridor.conflicts.
Python ≥ 3.11 · tested on Linux and Windows against 3.11 and 3.12 · MIT