Skip to content

Releases: mf4633/gisc

gisc 0.3.1 — first PyPI release

Choose a tag to compare

@mf4633 mf4633 released this 12 Sep 20:32
d12897a

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_ft was a required field of the IR, so a task with no
    corridor had to name a corridor width. Optional now, and omitted from
    plan.json and from provenance when a task doesn't buffer. The buffer op's
    own dist_ft was always its real home.
  • intersect always 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 a geometry mode
    now, defaulting to source so corridor.conflicts is 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

Choose a tag to compare

@mf4633 mf4633 released this 12 Sep 18:19
606f850

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_ft was a required field of the IR, so a task with no
    corridor had to name a corridor width. Optional now, and omitted from
    plan.json and from provenance when a task doesn't buffer. The buffer op's
    own dist_ft was always its real home.
  • intersect always 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 a geometry mode
    now, defaulting to source so corridor.conflicts is 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

Choose a tag to compare

@mf4633 mf4633 released this 12 Sep 16:53
dd4708e

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
    rot as 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 own along.

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