Geologic AI georeferencing on a scanned geologic map: ground control points on the four corners and four edge midpoints of the neatline, with one slipped corner standing out in the per-point pixel errors
Four corners detect a bad point; the edge ticks name it

Key Takeaways

  • The task today: Georeferencing a scanned map often starts from the coordinates printed at its four neatline corners, then a residual check. Stratigraphic Amenity, our open-source MCP server (a tool server AI agents call), fits an affine to the GCPs an agent reads. Corners suit a person: they carry full coordinates, and each GCP costs a minute.
  • The problem: With four corners, one wrong digit splits into four equal residuals, and leaving out any corner misses by the full slip, so the fit detects the error but cannot name the point. Points bunched mid-sheet add a second blind spot: the corners, where the bounds are computed.
  • What the tool does: geomap_georeference returns per-point pixel errors, a residual in metres and the worst miss when each point is left out and predicted from the others (holdout_error), flags two- and three-point fits as not diagnostic, and sets no pass or fail threshold. An agent reads extra ticks for almost nothing; adding the edge ticks the map prints makes a slipped corner stand out.

TL;DR

Choosing GCPs for a scanned geologic map decides what the residuals can see: Stratigraphic Amenity, our open-source MCP server for AI agents, fits an affine to the points an agent reads and returns per-point pixel errors, a residual in metres and the worst leave-one-out miss. With only the four neatline corners, one wrong digit gives four equal residuals and the same miss whichever corner is held out, so the fit can say something is wrong but not which point, and points bunched mid-sheet hide error at the corners, where the bounds come from. Spreading points out to the edges and around them, with the corners plus the edge ticks the map already prints, lets a bad point stand out, and for an agent those extra ticks cost almost nothing.

Why Four Corners Leave a Geologic AI Agent Guessing

Many georeferencing tutorials for a scanned paper map go the same way. Zoom to the lower-left corner of the neatline and read the coordinates printed there. Type them in, then repeat for the other three corners. It is a sensible habit: the corners are where the map states exactly where it is. Then comes the advice everyone repeats, including the guide we wrote for geologic AI agents. Use at least four well-distributed GCPs, and check the residuals.

Suppose one of those four readings is wrong. A northing of 5,370,000 typed as 5,371,000: one digit, one kilometre. You open the residual table to find the culprit, and all four rows show the same number.

That is not a display bug. It is all that four corners can know.

The Arithmetic of One Spare

An affine transform has six unknowns. Four GCPs give eight numbers, which leaves two to spare, one per axis. One spare observation is enough to detect a contradiction but not to say where it came from. On a rectangle of four corners, least squares splits a single slip into four residuals of exactly the same size. Each is a quarter of the slip. Hold out any corner, fit the other three, and the prediction misses by the full kilometre, whichever corner you held out. As a result, every corner looks equally guilty.

Why Deleting the Worst Point Backfires

The desktop reflex makes it worse. Delete the point with the largest residual and refit: there is no largest, so you delete one of four. The remaining three fit exactly, the residual drops to zero, and the map looks perfect. Three times out of four, you have deleted a good point and kept the bad one.

Redundancy, a Surveyor’s Idea

Geodesists worked this out in the 1960s. In the reliability theory Willem Baarda set out in 1968, each observation has a redundancy number: the share of its own error that shows up in its own residual. For a corner of a four-corner rectangle, that share is a quarter. The corners are the points the fit leans on hardest and inspects least. Detecting a blunder takes one spare observation. Locating it takes spare observations arranged so that no point can stand in for another.

Where Ground Control Points Should Go

The intuitive repair is one more GCP in the middle of the sheet. It fails, and the reason is worth seeing. Take four corners and a centre point. A slip at one corner then leaves exactly the same residual pattern as the same slip at the opposite corner. The centre sits on the diagonal through the other two corners. The affine can add a correction that is zero along that diagonal and grows in opposite directions towards the two remaining corners, so a slip at one of them leaves the same residuals as the same slip at the other. A blunder at the centre point itself would stand out, because a point with little leverage shows nearly all of its own error. It is the corners that stay protected.

Edge Ticks Name the Bad Corner

Points along the edges, however, sit off that diagonal, so the correction would show at them. Add the four edge midpoints to the four corners. A slipped corner’s pixel error then comes out at about twice that of its nearest neighbours. Leave it out of the fit, and the residual falls to the level of reading noise.

In fact, the map already prints what this layout needs. A geologic map on a USGS 7.5-minute topographic base carries, among other marks:

  • latitude and longitude ticks along its edges;
  • small crosses inside, at 2.5-minute spacing;
  • 1,000-metre UTM ticks in the margin.

Each call takes one crs, so pair the corners with the latitude and longitude ticks, or use the UTM ticks on their own. The furniture of the sheet is a redundancy design that most of us skip.

The Other Half of Spreading Ground Control Points

There is a second blind spot, and it also sits at the corners. Residuals and holdout errors are measured where the points are. The result you care about is often measured somewhere else. Take Stratigraphic Amenity, our open-source MCP server for scanned geologic maps. Its bounds for a knowledge query come from the four corners of the map area, pushed through the fitted affine.

We ran the same least-squares fit in our own simulation on two sets of six GCPs, with the same two pixels of reading noise. One set is bunched tightly in the middle of a sheet; the other spreads to its edges. The residual and holdout error came back nearly the same. The bunched set still missed the sheet’s corners several times further, because every corner is an extrapolation, and the tighter the bunch, the larger the miss. No statistic computed at the points can see where there are no points.

So “well distributed” is two rules for two blind spots. Out to the edges, so the corners are interpolated rather than guessed. Around the edges, so a bad point has nowhere to hide. ArcGIS’s long-standing guidance (at least one link near each corner and a few throughout the interior) points at both. Four corners satisfy the first rule and fail the second.

What Changes When Geologic AI Reads the Ticks

Four corners are a sensible habit for a person. Every GCP costs a minute of zooming, reading and typing, and the corners carry full coordinates. A geologic AI agent reads a tick label for almost nothing.

Our server reads no text on the map: the agent or the user supplies every GCP. It fits however many it is given. Two give an axis-aligned fit, three an exact affine, and four or more a least-squares fit with a holdout check. Redundancy, the thing that makes a residual mean anything, has become cheap.

Here is a hypothesis we have not tested. A person clicking a tick errs by a pixel or two; an agent reading a label is more likely to err by a digit. Errors like that are large and rare, which is exactly what redundancy catches and an exact fit hides. If that is right, reading residuals is, for an agent, blunder hunting rather than averaging noise, and its tools should help it hunt.

What the Geologic AI Server Does Not Judge

What the server returns helps, but only partly. Each point gets an entry in gcp_pixel_errors, in pixels on the scan, alongside residual_m in metres. Then comes holdout_error, the worst leave-one-out miss: how bad, not which. Two- and three-point fits carry a residual_not_diagnostic warning. Nothing in the result says pass or fail, and no threshold on any of these numbers exists in the code.

The tolerance has to come from the map. The US National Map Accuracy Standards give one: within 1/50 inch at publication scale for 90 per cent of well-defined points. That applies to maps at 1:20,000 or smaller and is 40 feet at 1:24,000. On such a sheet, a residual of a few metres is drafting noise. A residual of hundreds of metres is almost certainly a misread label or a wrong coordinate, not a poor fit.

A small residual proves less than it seems. It only says the points agree with each other. Errors they all share, such as a missing datum or a wrong UTM zone, leave it clean. Two points hide one more trap. Their fit is axis-aligned, with no cross terms. A scan that sits off square still fits them exactly, and the error lands away from both points. The substantial_rotation_or_shear warning (raised when a cross term exceeds a tenth of the scale) cannot fire, because there is no cross term to measure. The other warning still does: residual_not_diagnostic: an exact fit has no independent error check; supply at least 4 well-distributed GCPs.

Where the Witness Stands

A residual is a witness with a narrow view. It sees only the points you gave it, at the places you put them. It can accuse a point only when the others disagree with it in a way no other point could explain. Choosing GCPs is choosing where that witness stands. Four corners put it where it can say “something is wrong” and nothing more. When reading another tick costs nothing, the open question is why any agent should stop at four.

Asking an agent to read more ticks is only half of the change. The other half is knowing what the returned numbers can and cannot say. That depends on how the server computes them, not on what the field names suggest. A field called holdout_error sounds like a verdict on each point. Whether it is one is a question the code answers in a few lines.

You don’t need to build a least-squares fitter or a cross-validation loop to see this for yourself. The open-source server fits whatever GCPs your agent sends. A fit without a map reference costs nothing to repeat, so the experiment above takes a handful of tool calls.

What the Leave-One-Out Loop Can Name

The holdout check in Stratigraphic Amenity is a short loop in the georeferencing module:

python
def _holdout_error_metres(
    gcps: list[GroundControlPoint], transformer: object, warnings: list[str]
) -> float | None:
    if len(gcps) < 4:
        return None
    errors: list[float] = []
    for index, held_out in enumerate(gcps):
        training = gcps[:index] + gcps[index + 1 :]
        try:
            heldout_affine = fit_affine(training)
        except AffineFitError:
            warnings.append(f"holdout_subset_degenerate:gcp_index={index}")
            continue
        error = _error_metres(
            transformer,
            heldout_affine.apply(held_out.pixel_x, held_out.pixel_y),
            (held_out.world_x, held_out.world_y),
        )
        if math.isfinite(error):
            errors.append(error)
        else:
            warnings.append(f"holdout_metric_unavailable:gcp_index={index}")
    return max(errors) if errors else None

Each GCP is left out in turn, the affine is refitted on the rest, and the miss at the held-out point is measured in metres along the WGS84 ellipsoid after both positions are reprojected to longitude and latitude (_error_metres, lines 176-185). The loop keeps every miss but returns only the largest. That is the distinction in code: holdout_error says how bad the worst case is, not which point caused it. With four corners, every training set is three points, an exact fit, so every fold misses by the full slip and the maximum names nobody. A layout that leaves three exactly collinear points in a fold (three ticks along one edge plus one elsewhere) skips that fold with holdout_subset_degenerate:gcp_index=N. Ticks clicked on a real scan are rarely exactly in line, so the fold usually runs instead, and its nearly degenerate fit can miss by as much as the whole sheet, which holdout_error then reports as the worst case.

Where the Per-Point Errors Come From

The per-point view is gcp_pixel_errors (lines 292-300): the fitted affine is inverted at each GCP’s world coordinate and the distance to its pixel is measured. It is an in-sample number, so it only points at a culprit when the layout leaves the culprit nowhere to hide. Below four points the result carries residual_diagnostic: false and the residual_not_diagnostic warning (lines 271-276).

Try the Geologic AI Fit Yourself

To try it, clone the Stratigraphic Amenity repository and install the georeference-only extras (uv sync --group dev --extra mcp --extra geo). Then register the server with your MCP host. Next, call geomap_georeference without a map reference. Send the four corners and four edge midpoints of a sheet, with one northing read a digit wrong:

json
{
  "crs": "EPSG:26915",
  "gcps": [
    {"pixel_x": 0, "pixel_y": 0, "world_x": 660000, "world_y": 5400000},
    {"pixel_x": 500, "pixel_y": 0, "world_x": 675000, "world_y": 5400000},
    {"pixel_x": 1000, "pixel_y": 0, "world_x": 690000, "world_y": 5400000},
    {"pixel_x": 0, "pixel_y": 500, "world_x": 660000, "world_y": 5385000},
    {"pixel_x": 1000, "pixel_y": 500, "world_x": 690000, "world_y": 5385000},
    {"pixel_x": 0, "pixel_y": 1000, "world_x": 660000, "world_y": 5370000},
    {"pixel_x": 500, "pixel_y": 1000, "world_x": 675000, "world_y": 5370000},
    {"pixel_x": 1000, "pixel_y": 1000, "world_x": 690000, "world_y": 5371000}
  ],
  "pixel_extent": [0, 0, 1000, 1000]
}

The result reports fit_method: "affine-least-squares", residual_diagnostic: true, a holdout_error close to the 1,000 m slip, and eight gcp_pixel_errors in which the last corner stands at about twice its two neighbours. Call again without that point, and the residual and holdout error drop to zero because the other seven agree exactly. On a real scan, they drop to the level of reading noise. Without a map reference, nothing is stored. With a map_id, each call writes a new georeference revision. It also moves the current pointer that geomap_query_map reads its bounds from. So keep the experiments unstored, and send the map reference only with the final set.

FAQs

How many ground control points does geologic AI georeferencing need?

Four is the least that can detect a bad point: with four, the server fits a least-squares affine and reports a holdout error. Four corners still cannot say which point is wrong. Adding the four edge midpoints lets a slipped corner stand out. Two or three points fit exactly and are flagged as not diagnostic.

Where should ground control points go on a scanned map?

Out to the edges, so the map’s corners are interpolated rather than extrapolated, and around the edges, so a bad point has nowhere to hide. The corners plus the edge midpoints work. One extra point in the middle does not, because a slip at one corner then looks the same as a slip at the opposite corner.

Why does a geologic AI fit show four equal residuals?

With one GCP on each corner of a rectangle, least squares splits a single slip into four residuals of the same size. Each is a quarter of the slip. Holding out any corner misses by the full slip. Deleting one point leaves an exact fit with a residual of zero, and three times out of four the bad point stays.

What residual is good enough for a geologic AI fit?

The server sets no threshold, so the tolerance comes from the map. For maps at 1:20,000 or smaller, the US National Map Accuracy Standards allow 1/50 inch for 90 per cent of well-defined points. At 1:24,000, that is 40 feet. A residual of hundreds of metres there almost certainly means a misread label or a wrong coordinate.

Try It With Geocluster

Stratigraphic Amenity is one of the Geocluster tools, MCP servers that let AI agents work with geology data and maps.

The same fit, with the same residuals and warnings, runs without an agent when you batch georeference maps in Python.