Skip to content

Locations and Position Location Information

Location represents a timestamped WGS-84 position with optional altitude, bearing, accuracy, source, velocity, angular velocity, and confidence. Use it to publish or observe the position and motion of an entity.

A Location is compliant with EPSG:4326, or with EPSG:4979 where altitude is supplied as height above the WGS-84 ellipsoid. Latitude and longitude are decimal degrees and altitude is metres; publishers convert mean-sea-level or geoid-referenced height before publication. Bearing is degrees clockwise from true north in [0, 360); publishers normalize a wraparound value such as 360 before publication. An absent altitude means the height is unknown. It does not mean zero, mean sea level, or a height the client may infer, and altitude-dependent operations are unavailable without one.

Velocity is ordered north/east/down and measured in metres per second. Angular velocity is exposed as roll/pitch/yaw radians per second. Review the externally unverified axis interpretation on the limitations page.

Location.to_ecef converts a position to EPSG:4978 Cartesian metres, and Location.to_ecef_velocity rotates the velocity onto the same axes. ECEFPosition.to_geodetic inverts the position conversion, returning a GeodeticPosition rather than a Location, because a coordinate triple has no timestamp or source to reconstruct.

from picogrid_ecn_client import Location
def to_cartesian(location: Location) -> None:
position = location.to_ecef()
print(position.x, position.y, position.z)
print(position.to_geodetic().altitude)

Altitude is read as height above the ellipsoid. A location without one raises ValidationError with code missing_altitude; pass assume_zero_ellipsoidal_height=True to place the position on the ellipsoid instead. Velocity conversion needs no altitude, because the rotation depends on latitude and longitude alone. GeodeticPosition.to_ned_velocity rotates an ECEFVelocity back onto north/east/down at that position, making the velocity rotation available in both directions.

The public model always exposes geodetic coordinates. Canonical protobuf input can instead mark a point as EPSG:4978 and carry ECEF x/y/z metres in the latitude/longitude/altitude wire fields; the decoder converts that point to WGS-84 latitude, longitude, and ellipsoidal height before constructing Location. Public publication remains geodetic. Explicit conversions are local to the client, return new objects, and leave the Location unchanged. Axis order, tolerances, the supported domain, and the polar and antimeridian conventions are in the coordinate reference matrix.

accuracy is horizontal position uncertainty in metres, expressed as drms (distance root mean square):

drms = sqrt(sigma_north^2 + sigma_east^2)

where the sigmas are the one-sigma per-axis standard deviations of the horizontal position error. drms is exact for any horizontal covariance, but its approximately 63% containment holds only for a circular error, an isotropic two-dimensional Gaussian with equal north and east components, and does not apply to an anisotropic or non-Gaussian source.

Convert to drms before publishing. The wire carries no field naming the convention a value used.

The covariance and 2drms rows hold for any horizontal error, because 2drms is twice drms by definition. The CEP50 and R95 rows assume a circular error, meaning an isotropic two-dimensional Gaussian with equal north and east components. No scalar factor converts CEP50 or R95 to drms for an anisotropic or non-Gaussian source; publish sqrt(sigma_north^2 + sigma_east^2) from the covariance instead.

If your source reportsThen accuracy is
a horizontal covariance or per-axis sigmassqrt(sigma_north^2 + sigma_east^2)
2drmsvalue / 2
CEP50value * 1.200
R95value * 0.578
one-sigma per-axis, isotropicvalue * 1.414

The 2drms row is exact by definition. The CEP50 and R95 factors invert the circular-error rows above and carry the same assumption. They must not be applied to an anisotropic or non-Gaussian source. For such a source, drms remains a valid magnitude but does not map to a containment percentage.

To obtainCompute
CEP500.833 * accuracy
R951.731 * accuracy
2drms2 * accuracy

accuracy is a circular approximation. It cannot express an oriented error ellipse, so a sensor whose along-range and cross-range errors differ substantially loses that structure. It never covers vertical uncertainty.

confidence is a separate dimensionless value between 0 and 1. It carries no unit and no positional-uncertainty meaning.

Position Location Information (PLI) associates a location with one canonical entity UUID. The expansion follows the Position Location Information Concept of Operations.

Publish PLI by pairing one canonical entity UUID with one validated Location. It uses the same supported location topic and payload family as any other location update; PLI is not a separate Python model, MQTT topic, entity category, discovery mechanism, or tracking protocol.

Use this location publication when an integration reports the position of its own platform or another entity it is authorized to update. Keep the UUID stable, use timezone-aware timestamps, and follow the north/east/down velocity convention above.

receipt = await client.locations.publish(
entity_id=entity_id, location=location
)

Publishing is a broker-authorized mutation. Its receipt reports local MQTT completion, not persistence or consumer processing. See the runnable publish_location.py example.

Location reads are strictly process-local MQTT observations:

  • last_observed(entity_id, integration=...) returns a value already decoded by this client, or None.
  • wait_for_update(...) lazily subscribes to the narrow matching family and waits for the next message.

The cache updates only from successfully decoded dedicated location messages or entity events carrying an embedded location. With no integration argument, last_observed returns the most recently received matching location across integrations; this is arrival order, not authoritative event-time history.

Neither method queries authoritative server state. The cache may miss messages sent before subscription, disappears when the client closes, and must not be described as ECN-wide history.

The ECN location broadcast uses the terminal-geolocation integration segment. The SDK observes it through the dedicated location families. The specialized observation method uses one fixed-depth UUID segment and returns the UUID from the received event; it never performs a query.

from picogrid_ecn_client import ECNClient
async def wait_for_one(client: ECNClient) -> None:
event = await client.locations.wait_for_terminal_geolocation(
timeout=10,
)
assert (
client.locations.last_observed(
event.entity_id,
integration="terminal-geolocation",
)
== event.location
)

See observe a location and publish entities, locations, and PLI.

Version 0.2.0 · branch main