Jonathan Capone Technical Portfolio
OMEGA sections How It Works Research Context Data
How It Works chaptersOverviewSensingValidationMapping

How It Works / Validation

OMEGA

Validation, stability, and point acceptance.

A closer look at the checks that determine whether a measurement becomes part of the mapping dataset: GPS quality, fix age, depth stability, speed, tilt, and spatial spacing.

Back to How OMEGA WorksPrevious: SensingNext: MappingResearch ContextData
Validation layer

OMEGA decides whether a point is acceptable before it joins the map dataset.

Bathymetry depends on where a depth was measured, how stable the platform was at that moment, and whether the sensor values were still trustworthy. The validation logic makes those conditions explicit.

From getMapStatus()
const char* getMapStatus(unsigned long currentMillis) {
  if (millis() - startupMillis < gpsWarmupTime) return "WARMUP";
  if (!mapReady || !sdReady || !mapFile) return "NOFILE";
  if (!gpsDataPresent(currentMillis)) return "NOGPS";
  if (!gps.location.isValid()) return "NOFIX";
  if (!gps.date.isValid() || !gps.time.isValid()) return "NOTIME";
  if (isnan(depthCm)) return "NODEP";
  if (depthSamplesCollected < 3) return "WARM";

  if (!gps.satellites.isValid()) return "SAT";
  if (gps.satellites.value() < minMapSats) return "SAT";

  if (!gps.hdop.isValid()) return "HDOP";
  if (gps.hdop.hdop() > maxMapHdop) return "HDOP";

  if (gps.location.age() > maxMapFixAgeMs) return "STALE";
  if (!isnan(smoothedSpeed) && smoothedSpeed > maxMapSpeedKmph) return "FAST";

  if (requireLevelForMapping) {
    if (!imuReady) return "NOIMU";
    if (!imuOrientationValid) return "IMU?";
    if (imuAccuracy < minImuAccuracyForMapping) return "CAL";
    if (fabs(imuPitchDeg) > maxMapPitchDeg || fabs(imuRollDeg) > maxMapRollDeg) return "UNLVL";
  }

  return "ARMED";
}
What this function controls

The returned status tells the rest of the system whether the logger is still warming up, missing required data, or fully ready to write a mapping-quality point. The accepted state is ARMED.

Why each check exists

The validation rules are really rules about evidence.

Each check asks a different question about the point. Does the system know where it is? Is the fix current? Is the depth stable? Is the vehicle moving too fast or tilted too far? Those questions shape the survey.

StatusWhat it meansWhy it affects mapping
NOFIXNo valid GPS positionA depth without a reliable location cannot be placed correctly in the bathymetric surface.
HDOPHorizontal precision is missing or too poorEven a good depth can distort the map if the position uncertainty is too large.
STALEThe GPS fix is too oldThe platform may have moved since the last valid position was reported.
NODEP / WARMDepth is unavailable or not yet stabilizedThe logger waits until multiple readings have built a stable working depth.
FASTThe platform is moving faster than the mapping limitRapid motion reduces how well each depth represents the bottom directly beneath the sensor.
UNLVLPitch or roll exceeds the allowed limitExcess tilt changes the geometry of the measurement and weakens the assumption of a near-vertical depth reading.
ARMEDAll required conditions are currently metThe point is eligible to enter the mapping dataset.
Depth stability

OMEGA smooths depth and rejects sharp spikes.

The logger uses a moving average buffer to keep the working depth from jumping around. It also rejects isolated spikes when a new average would jump too far from the previous accepted depth.

Depth buffer logic
void addDepthSample(float sample) {
  depthReadingsTotal -= depthReadingsBuffer[depthBufferIndex];
  depthReadingsBuffer[depthBufferIndex] = sample;
  depthReadingsTotal += sample;
  depthBufferIndex = (depthBufferIndex + 1) % DEPTH_BUFFER_SIZE;

  if (depthSamplesCollected < DEPTH_BUFFER_SIZE) {
    depthSamplesCollected++;
  }

  float newDepth = depthReadingsTotal / depthSamplesCollected;

  // Reject a jump that is too large to trust as the next point.
  if (!isnan(lastDepthCm) && fabs(newDepth - lastDepthCm) > maxDepthJumpCm) {
    return;
  }

  depthCm = newDepth;
  lastDepthCm = depthCm;
}
How this changes the dataset

Instead of letting every raw pulse become a survey point, the logger forms a short memory of recent readings and compares the new value to the previous accepted depth. That reduces noise without erasing genuine depth variation.

Spacing

The logger also avoids collecting too many nearly identical points.

OMEGA checks the distance from the last accepted map point and can hold the next one if the platform has not moved far enough. The minimum spacing is adaptive: it grows with speed inside a constrained range.

Adaptive spacing from the mapping logic
if (hasLastMapPoint) {
  double distanceMeters = TinyGPSPlus::distanceBetween(
    gps.location.lat(), gps.location.lng(),
    lastMapLat, lastMapLng
  );

  float minSpacing = minMapSpacingMetersBase;
  if (!isnan(smoothedSpeed)) {
    minSpacing = constrain(smoothedSpeed * 0.5f, 2.0f, 10.0f);
  }

  if (distanceMeters < minSpacing) return "HOLD";
}
Why spacing matters

A map built from a tight cluster of nearly duplicate points is less useful than a map built from points that are spread across the survey path. Spacing helps distribute control points through the area instead of oversampling one small stretch.