Reprojecting a Live Utility Network Without Downtime

Changing the coordinate frame of a production utility network is one of those tasks that looks like a single geoprocessing operation and behaves like a migration. The features are the least of it: the connectivity tolerance is expressed in the frame’s units, every published service and offline replica carries the old frame until it is rebuilt, and every hard-coded coordinate system in a decade of automation becomes a defect at cutover. Done as a big-bang reprojection under an edit freeze, the freeze lasts as long as the slowest step and the rollback plan is aspirational. Done as a parallel build with a rehearsal, the freeze covers only the final delta and the switch. This guide sets out the second approach, and it assumes the frame decision itself has already been made following CRS alignment and geodetic transformations.

Environment Prerequisites

  • ArcGIS Pro 3.2+ with a Standard or Advanced licence, arcpy importable from Python 3.11, and enough storage for a parallel copy of the network.
  • The target frame decided and published as configuration, including the vertical reference, not chosen during the migration.
  • The transformation path pinned per source frame, with the grid files verified present, as the CRS validation script checks.
  • Surveyed control in both frames, so the reprojected copy can be verified against something external rather than against itself.
  • An inventory of consumers: published services, offline replicas, scheduled jobs, downstream extracts, and any automation with an EPSG code in it.
  • A trace regression set — origins and expected result sets — captured on the live network before anything changes.

Schema-Aware Validation Protocol — Run Before the Parallel Build

  1. Confirm every feature class declares a frame. A class with an undefined spatial reference cannot be reprojected, only redefined, and redefining it during a migration buries the error permanently.
  2. Restate the tolerance in the target units. A tolerance of 0.01 in feet and 0.01 in metres are different networks. Compute the equivalent and record the decision.
  3. Capture the trace regression set first. Once the live network changes, there is nothing to compare against. Origins, parameters and result counts, stored.
  4. Enumerate the consumers exhaustively. Search the automation estate for EPSG codes and well-known text strings; each hit is either parameterised before cutover or a failure after it.
  5. Verify the grid files on the machine that will run the reprojection, not on a workstation. A missing grid on the server silently downgrades the transformation for the whole estate.

Minimal Reproducible Implementation

from __future__ import annotations

import logging
from dataclasses import dataclass, field

import arcpy

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
LOG = logging.getLogger("reproject")


@dataclass
class ReprojectReport:
    target_epsg: int
    transformation: str
    copied: list[str] = field(default_factory=list)
    control_residuals_m: dict[str, float] = field(default_factory=dict)
    problems: list[str] = field(default_factory=list)

    @property
    def ok(self) -> bool:
        return not self.problems and all(
            r <= 0.05 for r in self.control_residuals_m.values())


def reproject_parallel(
    source_gdb: str,
    target_gdb: str,
    classes: list[str],
    target_epsg: int,
    transformation: str,
    control: dict[str, tuple[float, float]],
) -> ReprojectReport:
    """Build a reprojected parallel copy and verify it against known control.

    ``transformation`` is named explicitly rather than left to the default, because a
    default path silently substitutes a parameter-based operation where a grid-based one
    was intended. ``control`` maps a control point id to its known coordinates in the
    TARGET frame; residuals above five centimetres fail the run.
    """
    report = ReprojectReport(target_epsg=target_epsg, transformation=transformation)
    sr = arcpy.SpatialReference(target_epsg)

    for name in classes:
        try:
            arcpy.management.Project(
                in_dataset=f"{source_gdb}/{name}",
                out_dataset=f"{target_gdb}/{name}",
                out_coor_system=sr,
                transform_method=transformation,
            )
            report.copied.append(name)
            LOG.info("projected %s", name)
        except arcpy.ExecuteError as exc:
            report.problems.append(f"{name}: {exc}")

    for point_id, (want_x, want_y) in control.items():
        try:
            with arcpy.da.SearchCursor(f"{target_gdb}/ControlPoints",
                                       ["SHAPE@X", "SHAPE@Y"],
                                       where_clause=f"POINT_ID = '{point_id}'") as cur:
                got_x, got_y = next(iter(cur))
            residual = ((got_x - want_x) ** 2 + (got_y - want_y) ** 2) ** 0.5
            report.control_residuals_m[point_id] = residual
            if residual > 0.05:
                LOG.warning("control %s residual %.3f m", point_id, residual)
        except (arcpy.ExecuteError, StopIteration) as exc:
            report.problems.append(f"control {point_id}: {exc}")

    return report
A live reprojection run as a sequence of reversible steps The cutover is planned backwards from the point of no return. A parallel copy is built and reprojected while the live network keeps running; consumers are pointed at the copy in a read-only rehearsal; the edit freeze is short and covers only the final delta and the switch. Rollback is available until services are republished against the new frame. Parallel copy reprojected, validated offline week −2 Rehearsal consumers read the copy week −1 Edit freeze final delta reprojected hour 0 Switch services republished hour 1 Verify traces and control checks hour 2 the freeze covers the delta and the switch, not the reprojection Reprojecting under a freeze is what makes a cutover an outage instead of a task.

The control check is what separates a reprojection from a hope. Reprojected data always looks right against itself; only an external observation reveals that the transformation ran without its grid file.

What actually has to change when the frame changes, beyond the coordinates Reprojecting the features is the easy part and the part most plans account for. The connectivity tolerance is expressed in the frame’s units and has to be restated. Cached services, offline replicas and any downstream extract carry the old frame until they are rebuilt. And every hard-coded EPSG code in automation is a defect waiting for the first run after cutover. Item Changes If missed Feature coordinates reprojected obvious immediately XY tolerance and resolution restated in new units silent connectivity change Published services republished consumers see the old frame Offline replicas recreated field edits land wrong Automation EPSG codes parameterised first run after cutover fails Only the first row is visible; the rest fail quietly after the celebration.

What Makes the Freeze Short

The instinct is to plan the freeze around the reprojection, because that is the visible step. In practice the reprojection is the one part that can happen entirely outside it. What must happen inside the freeze is small: reproject the delta accumulated since the last parallel build, switch the connection strings and service definitions, and verify.

Two things decide how small that delta is. The first is how recently the parallel build ran — a build refreshed nightly leaves a day of edits, one refreshed a month ago leaves a month. The second is whether the estate can hold edits briefly rather than needing them applied continuously; most can, for an hour, if it is scheduled.

Everything else that people put in the freeze belongs outside it. Republishing services can be staged and swapped. Replicas can be recreated in advance against the parallel copy and activated at the switch. Automation can be parameterised weeks earlier and tested against the copy. A freeze that contains only the delta and the switch is measured in tens of minutes; one that contains the whole migration is measured in whatever the slowest step turns out to be, which is never known in advance.

Production Deployment Pattern

  1. Build the parallel copy while the network is live, and repeat it as a delta until the final run. The last delta is small enough to complete inside a short freeze.
  2. Rehearse with real consumers. Point a service, a replica and a scheduled job at the copy and let them run for a week. Every defect found here is one not found during the freeze.
  3. Parameterise every EPSG code before cutover. A configuration value that can be changed once is the difference between a switch and a week of individual fixes.
  4. Keep the old frame readable until confidence is earned. A read-only copy of the pre-cutover network costs storage and buys the ability to answer “was it always like this?”
  5. Re-run the trace regression set after the switch. Identical result sets are the evidence that connectivity survived the frame change.
  6. Record the transformation and grid versions in the audit trail, because every coordinate in the estate now depends on them.
The go / no-go decision at the end of the rehearsal Three questions decide whether the cutover proceeds. Do control points land within tolerance in the new frame? Do traces on the reprojected copy return the same result sets as the live network? And has every consumer been rebuilt or repointed? A no to any of them means the rehearsal did its job. Rehearsal complete — proceed to cutover? geometry Control within tolerance and traces identical? yes Yes — geometric risk is retired no No — the transformation path is wrong, stop consumers Every service, replica and job repointed? yes Yes — schedule the freeze no No — cut over anyway and the freeze becomes an outage The rehearsal exists to convert an unknown into one of these four answers.

Conclusion

A frame change is a migration with a short cutover, not a geoprocessing task with a long one. Building in parallel, verifying against external control, rehearsing with real consumers and parameterising the frame everywhere reduces the freeze to the final delta and the switch. The trace regression set is what proves the network is the same network afterwards — and keeping the old frame readable for a while is what lets you answer the questions that arrive in the weeks after.

For authoritative reference, consult the ArcGIS Pro Project tool documentation and the PROJ transformation documentation.