ECEF to local ENU converter
This ECEF to ENU converter transforms an Earth-centered, Earth-fixed XYZ position into east, north, and up distances relative to a chosen local origin.
Run — free
Enter the target ECEF coordinates and the reference point's geodetic latitude, longitude, and optional ellipsoidal height. The calculator derives the origin's ECEF position on WGS 84, subtracts it from the target, and rotates the resulting vector into the local tangent frame. You can also supply another ellipsoid and choose the output precision.
Define the ECEF point and local origin consistently
An ECEF coordinate describes a position in a Cartesian frame fixed to the rotating Earth. The X axis passes through latitude zero and longitude zero, the Y axis passes through latitude zero and longitude 90 degrees east, and the Z axis points through the North Pole. Supply all three target coordinates in meters. The local frame also needs an origin, given here as geodetic latitude, longitude, and ellipsoidal height. Latitude and longitude are expressed in decimal degrees, while height is measured along the ellipsoid normal rather than above mean sea level. That distinction matters: an orthometric elevation from a map is not automatically an ellipsoidal height, because the geoid and reference ellipsoid differ. Keep the target ECEF coordinate and the origin tied to the same datum. The defaults use the WGS 84 semi-major axis and inverse flattening, which is appropriate for ordinary GNSS coordinates. If your source uses a regional datum or another reference ellipsoid, enter its matching parameters before interpreting the local displacement.
Understand the east, north, and up rotation
The conversion has two deterministic stages. First, the calculator converts the reference geodetic position to ECEF using the selected ellipsoid and subtracts that origin from the target XYZ position. This produces a displacement vector, not another absolute position. Second, it rotates the displacement using the reference latitude and longitude. East is tangent to the latitude circle and positive toward increasing longitude. North is tangent to the meridian and positive toward increasing geodetic latitude. Up is normal to the reference ellipsoid and positive away from it. The three returned values are therefore signed distances in meters. A negative east value means west of the local origin, a negative north value means south, and a negative up value means below the origin's tangent plane. ENU is a local linear frame, so its components are especially intuitive over limited areas. Over long distances, up can differ substantially from a simple height difference because Earth curvature changes the orientation of local vertical.
Use the result in positioning and engineering workflows
Local ENU coordinates are convenient when an application needs relative motion or geometry instead of planet-scale XYZ numbers. Surveying software can express observations around a station, robotics systems can place detections in a map centered on a base, and GNSS analysis can compare a measured antenna position with a known reference. Choose the origin deliberately and retain its latitude, longitude, height, ellipsoid, and datum with the result; without that metadata, an ENU tuple cannot be reconstructed as a global position. The decimals option controls presentation rounding from zero through twelve places, while all calculations use JavaScript floating-point arithmetic before rounding. Increasing decimals does not create accuracy that was absent from the inputs. For quality control, convert the exact ECEF coordinates of the reference origin and confirm that east, north, and up are all zero within the requested precision. Also test a known offset near the origin and verify the expected signs. The calculation is pure, performs no network requests, and returns the same output for the same input.
What you can do with it
Center a GNSS survey
Express receiver positions as intuitive local offsets from a surveyed reference monument.
Build a local robotics frame
Transform Earth-fixed positions into east, north, and up coordinates around a vehicle base or field station.
Analyze antenna displacement
Compare an antenna's ECEF position with a known origin and inspect horizontal and vertical components separately.
FAQ
What does the conversion cost?
Each API request costs $0.002. The browser version can run locally without a network calculation.
Which Earth model is used by default?
The defaults are the WGS 84 semi-major axis of 6378137 meters and inverse flattening of 298.257223563.
Is reference height optional?
Yes. It defaults to zero meters above the selected reference ellipsoid.
Can I enter elevation above mean sea level?
Not directly unless it equals ellipsoidal height for your workflow. Convert orthometric elevation with an appropriate geoid model first.
Why is up not simply the difference in height?
Up is the component along the ellipsoid normal at the origin. For separated points, Earth curvature makes it different from a simple height subtraction.
What units does the output use?
East, north, and up are returned in meters, matching the ECEF coordinates and ellipsoid dimensions.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/geo/ecef-to-enu \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"x":6378137,"y":100,"z":50,"ref_lat_deg":0,"ref_lon_deg":0}'const res = await fetch("https://api.kit.forhosting.com/geo/ecef-to-enu", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"x": 6378137,
"y": 100,
"z": 50,
"ref_lat_deg": 0,
"ref_lon_deg": 0
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/geo/ecef-to-enu",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"x": 6378137,
"y": 100,
"z": 50,
"ref_lat_deg": 0,
"ref_lon_deg": 0
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/geo/ecef-to-enu", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"x":6378137,"y":100,"z":50,"ref_lat_deg":0,"ref_lon_deg":0}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"x":6378137,"y":100,"z":50,"ref_lat_deg":0,"ref_lon_deg":0}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/geo/ecef-to-enu", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"x": 6378137,
"y": 100,
"z": 50,
"ref_lat_deg": 0,
"ref_lon_deg": 0
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "geo.ecef_to_enu",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |