Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

GIS & Geospatial Data (QGIS, GeoPandas, PostGIS)  Module 2 – Coordinate reference systems

Measuring distance and area correctly

Measure distances and areas on the ellipsoid, predict the error of degrees, Web Mercator, a conic or the wrong axis order, and convert hectares and acres.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Compute geodesic distances and areas on the WGS 84 ellipsoid, and say when a planar measurement is close enough
  • Predict the error of measuring in degrees, in Web Mercator, in a conformal conic or in the local UTM zone
  • Use QGIS $area and area(), PostGIS geography and DuckDB's spheroid functions with the right inputs
  • Convert areas into hectares, acres and local land units from their stated definitions

Before you start

What you’ll learn · Before you start

What you will learn

  • Compute geodesic distances and areas on the WGS 84 ellipsoid, and say when a planar measurement is close enough
  • Predict the error of measuring in degrees, in Web Mercator, in a conformal conic or in the local UTM zone
  • Use QGIS $area and area(), PostGIS geography and DuckDB's spheroid functions with the right inputs
  • Convert areas into hectares, acres and local land units from their stated definitions

Before you start

On this page

From Pune to Mumbai is 120,138.17 m on the WGS 84 ellipsoid. Measure the same line as a straight segment in Web Mercator and you get 127,065 m, 5.8 % too long; in EPSG:7755, 1.5 % too short; pass latitude and longitude the wrong way round to a geodesic function and it reports 110,690 m. Areas go the same way: a 400 ha ward near Pune measures 447 ha in Web Mercator.

This lesson measures distances and areas on the ellipsoid, shows when a flat grid or a sphere is close enough, puts numbers on the error of each wrong method, shows how QGIS, PostGIS and DuckDB decide what they measure, and converts areas into hectares, acres and local land units.

A distance on the Earth is a geodesic

The shortest line between two points on the ellipsoid is a geodesic: The shortest line between two points on the ellipsoid; its length is the distance between them on the Earth's model, 120,138.17 m from Pune to Mumbai on WGS 84., and its length is the distance a survey or a map needs. Karney’s algorithms, implemented in GeographicLib, compute it with an error “less than 15 nanometers”; the older formula of Vincenty is accurate to about 0.1 mm but “fails to converge for nearly antipodal points” (GeographicLib, Karney). The programs below use Vincenty’s formula, which is exact enough for any two places in India, and the first one measures Pune to Mumbai seven ways:

Pune to Mumbai, measured seven ways Python · measure/distances.py
"""Pune to Mumbai, measured seven ways."""
from measure import R_MEAN, geodesic, haversine, india_lcc, utm43, web_mercator

pune, mumbai = (18.5204, 73.8567), (19.0760, 72.8777)  # latitude, longitude on WGS 84
true = geodesic(*pune, *mumbai)[0]
print(f"On the WGS 84 ellipsoid (geodesic): {true:,.2f} m")


def show(label, metres):
    print(f"  {label:<38} {metres:12,.2f} m  {metres - true:+9,.2f} m  {(metres - true) / true:+8.3%}")


show(f"on a sphere, R = {R_MEAN:,.1f} m", haversine(*pune, *mumbai))
for label, project in [("straight line in UTM 43N", utm43), ("straight line in EPSG:7755", india_lcc),
                       ("straight line in EPSG:3857", web_mercator)]:
    (x1, y1), (x2, y2) = project(*pune), project(*mumbai)
    show(label, ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5)
show("geodesic with the axes swapped", geodesic(pune[1], pune[0], mumbai[1], mumbai[0])[0])
degrees = ((mumbai[0] - pune[0]) ** 2 + (mumbai[1] - pune[1]) ** 2) ** 0.5
print(f"  straight line in degrees: {degrees:.4f}, a number with no unit of length")

Output

On the WGS 84 ellipsoid (geodesic): 120,138.17 m
  on a sphere, R = 6,371,008.8 m           120,152.47 m     +14.30 m   +0.012%
  straight line in UTM 43N                 120,135.35 m      -2.82 m   -0.002%
  straight line in EPSG:7755               118,276.90 m  -1,861.27 m   -1.549%
  straight line in EPSG:3857               127,065.38 m  +6,927.21 m   +5.766%
  geodesic with the axes swapped           110,690.41 m  -9,447.76 m   -7.864%
  straight line in degrees: 1.1257, a number with no unit of length

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 distances.py

Its formulas come from a module shared by both programs of the lesson: Vincenty’s inverse and direct formulas, the haversine formula, the three projections of the comparison and an equal-area projection for areas:

measure.py: distances, projections and areas Python · measure/measure.py
"""Distances and areas on the WGS 84 ellipsoid, on a sphere and in four projections, for this lesson's programs."""
import math

A, F = 6_378_137.0, 1 / 298.257223563  # WGS 84
B = A * (1 - F)
E2 = F * (2 - F)
E = math.sqrt(E2)
R_MEAN = (2 * A + B) / 3  # the mean radius of the ellipsoid, 6,371,008.8 m


def geodesic(lat1, lon1, lat2, lon2):
    """Vincenty's inverse formula: metres between two points on WGS 84, and the azimuth at the first (degrees)."""
    u1, u2 = math.atan((1 - F) * math.tan(math.radians(lat1))), math.atan((1 - F) * math.tan(math.radians(lat2)))
    lam = big_l = math.radians(lon2 - lon1)
    for _ in range(200):
        sin_s = math.hypot(math.cos(u2) * math.sin(lam),
                           math.cos(u1) * math.sin(u2) - math.sin(u1) * math.cos(u2) * math.cos(lam))
        if sin_s == 0:
            return 0.0, 0.0
        cos_s = math.sin(u1) * math.sin(u2) + math.cos(u1) * math.cos(u2) * math.cos(lam)
        sigma = math.atan2(sin_s, cos_s)
        sin_al = math.cos(u1) * math.cos(u2) * math.sin(lam) / sin_s
        cos2 = 1 - sin_al ** 2
        c2m = cos_s - 2 * math.sin(u1) * math.sin(u2) / cos2 if cos2 else 0.0
        c = F / 16 * cos2 * (4 + F * (4 - 3 * cos2))
        last = lam
        lam = big_l + (1 - c) * F * sin_al * (sigma + c * sin_s * (c2m + c * cos_s * (-1 + 2 * c2m ** 2)))
        if abs(lam - last) < 1e-12:
            break
    u_sq = cos2 * (A * A - B * B) / (B * B)
    big_a = 1 + u_sq / 16384 * (4096 + u_sq * (-768 + u_sq * (320 - 175 * u_sq)))
    big_b = u_sq / 1024 * (256 + u_sq * (-128 + u_sq * (74 - 47 * u_sq)))
    d_s = big_b * sin_s * (c2m + big_b / 4 * (cos_s * (-1 + 2 * c2m ** 2)
                                            - big_b / 6 * c2m * (-3 + 4 * sin_s ** 2) * (-3 + 4 * c2m ** 2)))
    cross = math.cos(u1) * math.sin(u2) - math.sin(u1) * math.cos(u2) * math.cos(lam)
    az = math.atan2(math.cos(u2) * math.sin(lam), cross)
    return B * big_a * (sigma - d_s), math.degrees(az)


def destination(lat, lon, azimuth, s):
    """Vincenty's direct formula: the point s metres from (lat, lon) along the geodesic that leaves at azimuth."""
    al1 = math.radians(azimuth)
    tan_u1 = (1 - F) * math.tan(math.radians(lat))
    cos_u1 = 1 / math.sqrt(1 + tan_u1 ** 2)
    sin_u1 = tan_u1 * cos_u1
    sigma1 = math.atan2(tan_u1, math.cos(al1))
    sin_al = cos_u1 * math.sin(al1)
    cos2 = 1 - sin_al ** 2
    u_sq = cos2 * (A * A - B * B) / (B * B)
    big_a = 1 + u_sq / 16384 * (4096 + u_sq * (-768 + u_sq * (320 - 175 * u_sq)))
    big_b = u_sq / 1024 * (256 + u_sq * (-128 + u_sq * (74 - 47 * u_sq)))
    sigma = s / (B * big_a)
    for _ in range(200):
        c2m = math.cos(2 * sigma1 + sigma)
        d_s = big_b * math.sin(sigma) * (c2m + big_b / 4 * (math.cos(sigma) * (-1 + 2 * c2m ** 2) - big_b / 6 * c2m
                                                           * (-3 + 4 * math.sin(sigma) ** 2) * (-3 + 4 * c2m ** 2)))
        last, sigma = sigma, s / (B * big_a) + d_s
        if abs(sigma - last) < 1e-13:
            break
    c2m = math.cos(2 * sigma1 + sigma)
    x = sin_u1 * math.sin(sigma) - cos_u1 * math.cos(sigma) * math.cos(al1)
    up = sin_u1 * math.cos(sigma) + cos_u1 * math.sin(sigma) * math.cos(al1)
    lat2 = math.atan2(up, (1 - F) * math.hypot(sin_al, x))
    lam = math.atan2(math.sin(sigma) * math.sin(al1),
                     cos_u1 * math.cos(sigma) - sin_u1 * math.sin(sigma) * math.cos(al1))
    c = F / 16 * cos2 * (4 + F * (4 - 3 * cos2))
    big_l = lam - (1 - c) * F * sin_al * (sigma + c * math.sin(sigma)
                                          * (c2m + c * math.cos(sigma) * (-1 + 2 * c2m ** 2)))
    return math.degrees(lat2), lon + math.degrees(big_l)


def haversine(lat1, lon1, lat2, lon2, r=R_MEAN):
    """Great-circle metres on a sphere of radius r."""
    p1, p2 = math.radians(lat1), math.radians(lat2)
    h = math.sin((p2 - p1) / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(math.radians(lon2 - lon1) / 2) ** 2
    return 2 * r * math.asin(math.sqrt(h))


def utm43(lat, lon):
    """WGS 84 / UTM zone 43N (EPSG:32643), with the 4th-order series of IOGP report 373-07-2."""
    n = F / (2 - F)
    b = A / (1 + n) * (1 + n ** 2 / 4 + n ** 4 / 64)
    h = (n / 2 - 2 * n ** 2 / 3 + 5 * n ** 3 / 16 + 41 * n ** 4 / 180,
         13 * n ** 2 / 48 - 3 * n ** 3 / 5 + 557 * n ** 4 / 1440,
         61 * n ** 3 / 240 - 103 * n ** 4 / 140, 49561 * n ** 4 / 161280)
    p = math.radians(lat)
    beta = math.atan(math.sinh(math.asinh(math.tan(p)) - E * math.atanh(E * math.sin(p))))
    eta0 = math.atanh(math.cos(beta) * math.sin(math.radians(lon - 75)))
    xi0 = math.asin(math.sin(beta) * math.cosh(eta0))
    xi = xi0 + sum(c * math.sin(2 * j * xi0) * math.cosh(2 * j * eta0) for j, c in enumerate(h, 1))
    eta = eta0 + sum(c * math.cos(2 * j * xi0) * math.sinh(2 * j * eta0) for j, c in enumerate(h, 1))
    return 500_000 + 0.9996 * b * eta, 0.9996 * b * xi


def utm43_inverse(x, y):
    """Easting and northing in UTM zone 43N back to latitude and longitude on WGS 84."""
    n = F / (2 - F)
    b = A / (1 + n) * (1 + n ** 2 / 4 + n ** 4 / 64)
    h = (n / 2 - 2 * n ** 2 / 3 + 37 * n ** 3 / 96 - n ** 4 / 360, n ** 2 / 48 + n ** 3 / 15 - 437 * n ** 4 / 1440,
         17 * n ** 3 / 480 - 37 * n ** 4 / 840, 4397 * n ** 4 / 161280)
    eta_, xi_ = (x - 500_000) / (0.9996 * b), y / (0.9996 * b)
    xi0 = xi_ - sum(c * math.sin(2 * j * xi_) * math.cosh(2 * j * eta_) for j, c in enumerate(h, 1))
    eta0 = eta_ - sum(c * math.cos(2 * j * xi_) * math.sinh(2 * j * eta_) for j, c in enumerate(h, 1))
    beta = math.asin(math.sin(xi0) / math.cosh(eta0))
    q = q0 = math.asinh(math.tan(beta))
    for _ in range(15):
        q = q0 + E * math.atanh(E * math.tanh(q))
    return math.degrees(math.atan(math.sinh(q))), 75 + math.degrees(math.asin(math.tanh(eta0) / math.cos(beta)))


def _t(p):
    return math.tan(math.pi / 4 - p / 2) / ((1 - E * math.sin(p)) / (1 + E * math.sin(p))) ** (E / 2)


def _m(p):
    return math.cos(p) / math.sqrt(1 - E2 * math.sin(p) ** 2)


def india_lcc(lat, lon):
    """WGS 84 / India NSF LCC (EPSG:7755), Lambert Conic Conformal (2SP)."""
    p0, p1, p2 = math.radians(24.0), math.radians(12.472955), math.radians(35.1728044444444)
    n = (math.log(_m(p1)) - math.log(_m(p2))) / (math.log(_t(p1)) - math.log(_t(p2)))
    f = _m(p1) / (n * _t(p1) ** n)
    r, r0 = A * f * _t(math.radians(lat)) ** n, A * f * _t(p0) ** n
    theta = n * math.radians(lon - 80.0)
    return 4_000_000 + r * math.sin(theta), 4_000_000 + r0 - r * math.cos(theta)


def web_mercator(lat, lon):
    """WGS 84 / Pseudo-Mercator (EPSG:3857)."""
    return A * math.radians(lon), A * math.log(math.tan(math.pi / 4 + math.radians(lat) / 2))


def _q(p):
    s = math.sin(p)
    return (1 - E2) * (s / (1 - E2 * s * s) - math.log((1 - E * s) / (1 + E * s)) / (2 * E))


def equal_area(lat, lon, lat0, lon0):
    """Lambert cylindrical equal-area, ellipsoidal, true scale at lat0: keeps every area of the ellipsoid."""
    k0 = _m(math.radians(lat0))
    return A * k0 * math.radians(lon - lon0), A * _q(math.radians(lat)) / (2 * k0)


def planar_area(points):
    """The shoelace formula: the area of a polygon from its vertices in a plane."""
    return abs(sum(x1 * y2 - x2 * y1 for (x1, y1), (x2, y2) in zip(points, points[1:] + points[:1]))) / 2


def densify(vertices, step=1_000.0):
    """The polygon's edges as geodesics, with a vertex at least every `step` metres: a list of (lat, lon)."""
    out = []
    for (lat1, lon1), (lat2, lon2) in zip(vertices, vertices[1:] + vertices[:1]):
        s, az = geodesic(lat1, lon1, lat2, lon2)
        pieces = max(1, math.ceil(s / step))
        out += [(lat1, lon1)] + [destination(lat1, lon1, az, s * i / pieces) for i in range(1, pieces)]
    return out


def geodesic_area(vertices, step=2.0):
    """The area on WGS 84 of a polygon with geodesic edges, in m², measured in an equal-area projection."""
    ring = densify(vertices, step)
    lat0 = sum(lat for lat, _ in vertices) / len(vertices)
    lon0 = sum(lon for _, lon in vertices) / len(vertices)
    return planar_area([equal_area(lat, lon, lat0, lon0) for lat, lon in ring])

The geodesic is 120,138.17 m; GeographicLib’s own online solver gives 120,138.170 m for the same two points (GeodSolve). A sphere of the ellipsoid’s mean radius, 6,371,008.8 m, with the haversine formula: A formula for the great-circle distance between two points on a sphere from their latitudes and longitudes. With the Earth's mean radius it is fast and about 0.01 % off over distances such as Pune to Mumbai., gives 14.30 m more, 0.012 %. That is the trade PostGIS offers too: ST_DistanceSphere uses a sphere of radius 6,371,008 m by default and is “faster than ST_DistanceSpheroid, but less accurate” (PostGIS). For most analysis within India a sphere is close enough; for surveys, legal distances or any check of others’ numbers, use the geodesic.

Measure your own pair of places

Press Edit and change pune and mumbai to two places you know, latitude first. Compare the sphere’s error with 0.012 % for lines in other directions and at other latitudes.

A planar distance is only as good as the grid’s scale

The same program measures the line as a straight segment in three projected CRSs, the way a GIS measures geometry in the layer’s own grid. The chart puts every method against the geodesic:

Bars of error against the 120,138 m geodesic: sphere +14.3 m, UTM 43N −2.8 m, EPSG:7755 −1.55 %, EPSG:3857 +5.77 %, axes swapped −7.86 %.Pune to Mumbai: error by methodThe geodesic on WGS 84 is the line at 0 %.Sphere (haversine)+14.3 mUTM 43N, planar-2.8 mEPSG:7755, planar-1.55 %EPSG:3857, planar+5.77 %Axes swapped-7.86 %0 %: the geodesicRight of the line: too long. Left: too short.

Pune to Mumbai measured five ways, against the geodesic on WGS 84

Text description of the diagram

A chart of five horizontal bars around a vertical line that stands for the geodesic distance on the WGS 84 ellipsoid, 120,138.17 m, so an error of 0 %. A bar to the right means the method gives too long a distance; to the left, too short.

A sphere with the mean radius of 6,371,008.8 m (the haversine formula): 14.3 m too long, a bar too short to draw. A straight line in UTM zone 43N: 2.8 m too short, also too short to draw. A straight line in EPSG:7755: 1.55 % too short. A straight line in EPSG:3857, Web Mercator: 5.77 % too long, the longest bar to the right. The geodesic formula with latitude and longitude swapped: 7.86 % too short, the longest bar to the left.

In UTM zone 43N the straight line is 2.82 m short, 0.002 %; in EPSG:7755 it is 1.549 % short; in Web Mercator 5.766 % too long. Those are the scale factors of the previous lessons at work: near Pune, UTM 43N’s scale is 0.9998, EPSG:7755’s 0.9849 and Web Mercator’s about 1.054 to 1.061, and a planar length is wrong by the scale along the line, minus one. A straight line in degrees is 1.1257, which is no distance at all, because at India’s latitudes a degree of longitude is shorter than a degree of latitude, and it shrinks further towards the poles.

So a planar distance is close enough when the grid’s scale is close to 1 along the whole line: inside a city in its own UTM zone, the error here was under 3 m in 120 km. Across states, through several zones, or in any CRS made for display, measure the geodesic.

Key idea

A planar length carries the grid’s scale error: a few parts in a hundred thousand in the local UTM zone, 1.5 % in EPSG:7755 and 5.8 % in Web Mercator near Pune. Measure on the ellipsoid when the scale is not close to 1.

Checkpoint

Check: Geodesic distances · Planar distances and the grid's scale

  1. Question 1 of 3 What is the geodesic distance from Pune to Mumbai on WGS 84, according to the program?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: 120,138.17 m

    The geodesic is the shortest line on the ellipsoid. Every other number in the program's output is a method's error against it.

  2. Question 2 of 3 The haversine formula on a sphere of radius 6,371,008.8 m gives 120,152.47 m from Pune to Mumbai. How many metres longer than the geodesic is that? Give one decimal.

    Type a number.

    Show the answer: Question 2 of 3

    Answer: 14.3 m (anything from 14.25 to 14.35 counts)

    120,152.47 − 120,138.17 = 14.30 m, 0.012 % of the distance: close enough for most analysis, not for surveys or legal distances.

  3. Question 3 of 3 A city's GIS stores roads in its UTM zone. When are the lengths it computes in that grid close enough?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: For roads inside the city, where the zone's scale stays within about 0.1 % of 1

    A planar length is wrong by the grid's scale minus one. In the local UTM zone that is tiny; for long lines or lines across zones, measure geodesics.

Areas belong on the ellipsoid or in an equal-area grid

Areas follow the areal scale, which errs twice as much as the length scale in a conformal grid. The next program takes the ward of the projections lesson, a 2 km square in UTM zone 43N near Pune, and measures it on the ellipsoid and in three grids; then it does the same for a block of central India from 15° N to 25° N and 75° E to 85° E:

A ward and a block of India, measured several ways Python · measure/areas.py
"""A 2 km square ward near Pune, and a 10° block of India, measured in several ways."""
from measure import densify, geodesic_area, india_lcc, planar_area, utm43, utm43_inverse, web_mercator

ward_utm = [(378_000, 2_046_000), (380_000, 2_046_000), (380_000, 2_048_000), (378_000, 2_048_000)]
ward = [utm43_inverse(x, y) for x, y in ward_utm]  # the corners as latitude and longitude on WGS 84
true = geodesic_area(ward)
print(f"The ward, a 2 km x 2 km square in UTM zone 43N near Pune:")
print(f"  on the ellipsoid (geodesic edges)   {true:14,.2f} m²")
for label, area in [("planar, in UTM 43N", planar_area(ward_utm)),
                    ("planar, in EPSG:7755", planar_area([india_lcc(*p) for p in ward])),
                    ("planar, in EPSG:3857", planar_area([web_mercator(*p) for p in ward]))]:
    print(f"  {label:<35} {area:14,.2f} m²  {(area - true) / true:+8.3%}")
print(f"  that is {true / 10_000:.4f} ha or {true / 4_046.8564224:.4f} acres")

block = [(15.0, 75.0), (15.0, 85.0), (25.0, 85.0), (25.0, 75.0)]  # latitude, longitude
true = geodesic_area(block, 500.0)
ring = densify(block, 500.0)
print(f"A block from 15° N to 25° N and 75° E to 85° E: {true / 1e6:,.0f} km² on the ellipsoid")
for label, project in [("EPSG:7755", india_lcc), ("EPSG:3857", web_mercator)]:
    area = planar_area([project(*p) for p in ring])
    print(f"  planar in {label}: {area / 1e6:,.0f} km²  {(area - true) / true:+.2%}")

Output

The ward, a 2 km x 2 km square in UTM zone 43N near Pune:
  on the ellipsoid (geodesic edges)     4,001,752.60 m²
  planar, in UTM 43N                    4,000,000.00 m²   -0.044%
  planar, in EPSG:7755                  3,882,247.90 m²   -2.986%
  planar, in EPSG:3857                  4,474,235.76 m²  +11.807%
  that is 400.1753 ha or 988.8546 acres
A block from 15° N to 25° N and 75° E to 85° E: 1,158,951 km² on the ellipsoid
  planar in EPSG:7755: 1,122,429 km²  -3.15%
  planar in EPSG:3857: 1,323,828 km²  +14.23%

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 areas.py

On the ellipsoid the ward has 4,001,752.60 m²; GeographicLib’s online planimeter gives 4,001,752.5 m² for its four corners (Planimeter). In its own UTM zone the planar area, exactly 4,000,000 m², is 0.044 % small; in EPSG:7755 it is 2.986 % small and in Web Mercator 11.807 % large. For the block, 1,158,951 km² on the ellipsoid, EPSG:7755 gives 3.15 % less and Web Mercator 14.23 % more. The program measures the ellipsoidal area by projecting the polygon, its edges divided into short geodesic pieces, onto an equal-area projection: A projection that keeps the areas of all features in proportion to their areas on the Earth, such as the Albers and Lambert azimuthal equal-area projections. It distorts angles and shapes. More in “Map projections and their distortions”, which keeps every area of the ellipsoid.

Libraries warn about the worst case. GeoPandas computes areas “expressed in the units of the CRS”, notes that “area may be invalid for a geographic CRS using degrees as units”, and advises projecting first (GeoPandas). A projected CRS is not enough on its own: it must be equal-area, or local enough that its areal scale is close to 1.

Every engine measures what you ask it to

Each engine measures in two ways, and the name of the function or the type of the data decides which:

Engine On the ellipsoid Planar, in the data’s own CRS
QGIS $area and $length, with the project’s ellipsoid set area() and length(), “always planimetric” in the geometry’s SRS
PostGIS geography columns: ST_Distance on the spheroid by default, a sphere with use_spheroid false geometry columns: ST_Distance in the units of the SRS
DuckDB spatial ST_Distance_Spheroid, ST_Area_Spheroid: latitude, longitude order ST_Distance, the planar distance

The table follows each engine’s documentation: QGIS for its expressions (QGIS), PostGIS for ST_Distance, which since version 2.2 uses GeographicLib on the spheroid (PostGIS), and DuckDB, whose ST_Distance_Spheroid uses GeographicLib and whose spheroid functions expect “a [latitude, longitude] axis order” (DuckDB).

That last rule is a trap for data stored longitude first. Points built as ST_Point(longitude, latitude) and passed to ST_Distance_Spheroid are read with their axes swapped, and the program shows what that does: 110,690.41 m instead of 120,138.17 m, 7.864 % short, for the swapped points GeographicLib’s solver also measures at 110,690.413 m. Answer the three questions of the table below for your own measurement; it rules out the methods that would distort it:

Interactive · answer and compare Which way should you measure? Answer three questions about your measurement, and the table rules out the methods that would distort it, with the reason.
What do you measure?
Where does it lie?
How many measurements at a time?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Six ways to measure distances and areas against four needs
Method Lengths in a city Lengths across India Areas Millions of pairs
Geodesic on WGS 84 Works on: latitude, longitude Good exact: 120,138.17 m from Pune to Mumbai Good exact at any distance Good exact: the ward is 4,001,752.6 m² Mixed an iterative formula, slower than a sphere
Sphere (haversine) Works on: latitude, longitude Good 14.3 m too long over 120 km, 0.012 % Mixed small errors that depend on direction and latitude Mixed spherical areas differ slightly from the ellipsoid Good one closed formula per pair
Planar in the local UTM zone Works on: UTM metres Good 2.8 m short over 120 km near Pune Poor one zone spans 6°; outside it the scale grows Good the ward 0.044 % small in zone 43N Good plain arithmetic once projected
Planar in EPSG:7755 Works on: national LCC metres Poor 1.55 % short from Pune to Mumbai Mixed conformal, but 0.2 to 1.9 % short at the lesson's cities Poor the ward 2.99 % small, a 10° block 3.15 % small Good plain arithmetic once projected
Planar in EPSG:3857 Works on: web map metres Poor 5.77 % too long from Pune to Mumbai Poor errors grow with latitude Poor the ward 11.81 % large, a 10° block 14.23 % large Good plain arithmetic once projected
Planar in degrees Works on: raw latitude, longitude Poor 1.1257, a number with no unit of length Poor a degree of longitude shrinks with latitude Poor square degrees, not square metres Mixed fast and meaningless
How the answers decide
  • “What do you measure?” Areas: rules out Planar in EPSG:3857 and Planar in degrees and adds a caveat to Planar in EPSG:7755 and Sphere (haversine). Areas need an areal scale of 1: Web Mercator made the ward 11.81 % large, EPSG:7755 2.99 % small, degrees give square degrees, and a sphere's areas differ slightly from the ellipsoid's.
  • “What do you measure?” Areas and “Where does it lie?” Across several zones or all India: rules out Planar in the local UTM zone and Planar in EPSG:7755. Across India no single conformal grid keeps areas; measure geodesic areas on the ellipsoid.
  • “What do you measure?” Distances or lengths and “Where does it lie?” Across several zones or all India: rules out Planar in the local UTM zone, Planar in EPSG:3857 and Planar in degrees and adds a caveat to Planar in EPSG:7755 and Sphere (haversine). Long lines cross the scale changes of any flat grid; geodesics on the ellipsoid are exact at every length.
  • “What do you measure?” Distances or lengths and “Where does it lie?” Inside one city or one UTM zone: rules out Planar in EPSG:3857 and Planar in degrees and adds a caveat to Planar in EPSG:7755. In one city the local UTM zone was 2.8 m short over 120 km, while EPSG:3857 was 5.77 % too long.
  • “How many measurements at a time?” Millions of pairs, at speed: adds a caveat to Geodesic on WGS 84. For millions of pairs, a sphere or a local UTM grid is faster; check that their 0.012 % or zone-edge errors are acceptable.

Checkpoint

Check: Measuring areas · What each engine measures

  1. Question 1 of 3 The ward measures 4,001,752.60 m² on the ellipsoid and 3,882,247.90 m² planar in EPSG:7755. By how many per cent is the EPSG:7755 area too small? Give two decimals.

    Type a number.

    Show the answer: Question 1 of 3

    Answer: 2.99 % (anything from 2.985 to 2.995 counts)

    (4,001,752.60 − 3,882,247.90) ÷ 4,001,752.60 = 2.986 %, about 2.99 %: the areal scale of EPSG:7755 near Pune is about 0.970.

  2. Question 2 of 3 In a QGIS layer in UTM zone 43N, with the project's ellipsoid set to WGS 84, how do $area and area($geometry) differ for the ward?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: $area measures on the ellipsoid, like the program's 4,001,752.60 m²; area($geometry) is planar in UTM, 4,000,000 m²

    QGIS gives both: $area on the ellipsoid when one is set, area() in the layer's own grid. For the ward the two should differ by about 0.044 %, the areal scale of UTM 43N near Pune.

  3. Question 3 of 3 A DuckDB query passes points built as ST_Point(longitude, latitude) to ST_Distance_Spheroid. Pune to Mumbai comes out as 110,690 m. What is wrong?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: The function expects latitude first, so it measured between two points with their axes swapped

    Build the points latitude first for DuckDB's spheroid functions, or flip them before calling. Test with one pair whose distance you know.

Hectares and acres are exact; local units need their definition

Once an area is right in square metres, its units are a matter of exact definitions. NIST’s Handbook 44 gives 1 hectare as 10,000 square metres exactly, and 1 acre as 43,560 square feet exactly, which is 0.404 685 642 24 hectare or 4,046.856 422 4 m² (NIST). The ward of the previous program, in each unit:

Unit Its definition The ward
Square metre The SI unit of area 4,001,752.60 m²
Hectare 10,000 m², exactly 400.1753 ha
Acre 43,560 square feet, exactly 4,046.856 422 4 m² 988.8546 acres
Bigha, katha, biswa Set by the land records that use them Only with the record’s definition

Local units are another matter. A bigha, a katha or a biswa is a unit of land records, and its size is whatever the records that use it define. Haryana’s land records, for example, make a bigha 20 biswa of 20 biswansi each, a biswansi being one square karam of 57.157 inches, so that bigha is about 9,075 square feet and 4 bigha 16 biswa make an acre; for their units after settlement, the marla and the kanal, the same records use a karam of 66 inches (Haryana land records). An area in such a unit therefore means nothing without the definition it uses: convert only with the size the record itself gives, and say which definition you used.

Land Area Converter — Bigha, Guntha, Cent, Marla, Acre Convert an area between square metres, acres, hectares and the land units of a state's records; it uses a state's size only where an official source states it, and asks for your local size elsewhere.

Checkpoint

Check: Land units and their definitions

  1. Question 1 of 2 How many square metres is one acre, by NIST's definition?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 4,046.8564224 m², exactly

    NIST gives 1 acre = 43,560 square feet = 0.404 685 642 24 hectare = 4,046.856 422 4 m², all exact.

  2. Question 2 of 2 A sale deed gives a plot as 3 bigha. How do you turn that into square metres?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Use the size of a bigha that the land record or deed itself uses, and state it with the result

    Hectares and acres are exact; local units need their definition. Convert with the size the record states, as the land-area converter asks you to.

Practise: measure a ward and report it in land units

Build the last two steps of a measurement report: the planar area of a ring of vertices, then the same area in hectares, acres and a local unit whose size you are given:

Code lab · 2 steps

Measure a ward and report it in land units

Measure a polygon from its vertices with the shoelace formula, then report the area in hectares, acres and a stated local unit.

  1. Measure a polygon in a plane To do
  2. Report an area in hectares, acres and a local unit To do

Exercise · Easy · Python

Measure a polygon in a plane

Write ring_area(points) in rings.py: the area of a polygon from the (x, y) coordinates of its vertices in a plane, with the shoelace formula of the lesson's planar_area. Add x1 * y2 - x2 * y1 for every edge from one vertex to the next, the last vertex joining the first, and return half the absolute value of the sum.

Rings come both ways: some repeat the first vertex at the end and some do not, and some run clockwise, some anticlockwise. All of them must give the same area. With fewer than three distinct vertices, return 0.0.

The sample tests measure the lesson's ward, 4,000,000 m² in UTM zone 43N, a triangle and an L-shaped ring.

Starter code · rings.py

def ring_area(points):
    """The area of a polygon ring from its (x, y) vertices in a plane, whether or not the first vertex is repeated."""
    # Replace this line: drop a repeated last vertex, then add x1 * y2 - x2 * y1 around the ring.
    return 0.0
The sample tests · test_rings.py
from rings import ring_area

WARD = [(378000, 2046000), (380000, 2046000), (380000, 2048000), (378000, 2048000)]


def test_the_ward():
    """measures the lesson's 2 km square ward in UTM metres"""
    assert ring_area(WARD) == 4_000_000.0


def test_direction_and_closing():
    """gives the same area clockwise, anticlockwise and with the first vertex repeated"""
    assert ring_area(WARD[::-1]) == 4_000_000.0
    assert ring_area(WARD + [WARD[0]]) == 4_000_000.0


def test_other_shapes():
    """measures a right triangle and an L-shaped, concave ring"""
    assert ring_area([(0, 0), (4, 0), (0, 3)]) == 6.0
    assert ring_area([(0, 0), (4, 0), (4, 1), (1, 1), (1, 3), (0, 3)]) == 6.0


def test_too_few_points():
    """returns 0.0 for fewer than three distinct vertices"""
    assert ring_area([]) == 0.0
    assert ring_area([(1, 1), (5, 5)]) == 0.0
    assert ring_area([(1, 1), (5, 5), (1, 1)]) == 0.0
A hint

First drop a repeated last vertex: if len(points) > 1 and points[0] == points[-1]: points = points[:-1]. Then loop over range(len(points)) and pair each vertex with points[(i + 1) % len(points)], so the last one joins the first.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Easy · Python

Report an area in hectares, acres and a local unit

Write land_units(m2, local=None) in units.py. It takes an area in square metres and returns a dict with the area in hectares, "ha", and in acres, "acre", each rounded to 4 decimals. Use the exact definitions in the file: a hectare is 10,000 m² and an acre 4,046.8564224 m².

Local units such as the bigha have no single size, so the function only converts to one when the caller says how big it is: local is a pair (name, size in m²), and the result then has a third entry under that name. Raise a ValueError when the local unit's size is not more than 0.

The sample tests use the lesson's ward, 4,001,752.6 m², the exact acre, and a local bigha defined as 1,600 square yards.

Starter code · units.py

HECTARE = 10_000.0  # square metres, exactly
ACRE = 4_046.8564224  # square metres, exactly: 43,560 international square feet


def land_units(m2, local=None):
    """An area in m² in hectares and acres, and in a local unit given as (name, size in m²), to 4 decimals."""
    # Replace this line: divide by HECTARE and ACRE, and by the local unit's size when one is given.
    return {"ha": 0.0, "acre": 0.0}
The sample tests · test_units.py
from units import land_units

BIGHA = ("bigha", 1_600 * 0.83612736)  # a local bigha defined as 1,600 square yards


def test_the_ward():
    """reports the lesson's ward, 4,001,752.6 m² on the ellipsoid, in hectares and acres"""
    assert land_units(4_001_752.6) == {"ha": 400.1753, "acre": 988.8546}


def test_exact_definitions():
    """uses the exact sizes of the acre and the hectare"""
    assert land_units(4_046.8564224) == {"ha": 0.4047, "acre": 1.0}
    assert land_units(10_000.0)["acre"] == 2.4711


def test_a_local_unit():
    """converts to a local unit only when its size is given, as the land-area converter asks"""
    assert land_units(10_000.0, BIGHA) == {"ha": 1.0, "acre": 2.4711, "bigha": 7.4749}
    assert "bigha" not in land_units(10_000.0)


def test_bad_local_unit():
    """refuses a local unit without a positive size"""
    try:
        land_units(10_000.0, ("bigha", 0.0))
    except ValueError:
        return
    assert False, "a size of 0 m² should raise ValueError"
A hint

Build the dict with round(m2 / HECTARE, 4) and round(m2 / ACRE, 4). When local is not None, unpack it with name, size = local, check that size is positive, and add round(m2 / size, 4) under name.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Area Converter Check your conversions between square metres, hectares, acres and other units of area.

Finally, practise the five ideas, from geodesics to land units; the session begins with the idea your answers show to be weakest:

Practice that adapts to you

Practise this lesson

12 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 What does GeographicLib say about Vincenty's formula?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: It is accurate to about 0.1 mm and fails to converge for nearly antipodal points

    Karney's algorithms give distances with errors under 15 nanometres and always converge; Vincenty's are good enough for points that are not nearly antipodal, such as any two places in India.

  2. Question 2 of 12 Which job can use the haversine formula on a sphere instead of the geodesic?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: Ranking millions of shops by distance from a customer

    A sphere is a speed trade: 14.30 m in 120 km here. Use it where small errors do not change the decision.

  3. Question 3 of 12 The straight line from Pune to Mumbai in degrees is 1.1257. What is that?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: A number with no unit of length, because degrees of latitude and longitude differ in size

    Never measure in degrees. Transform to a suitable grid, or compute the geodesic from latitude and longitude.

  4. Question 4 of 12 Near Pune the scale factor of EPSG:7755 is 0.9849. A road is 10.00 km long on the ground. About how long is it measured in EPSG:7755, in km? Give two decimals.

    Type a number.

    Show the answer: Question 4 of 12

    Answer: 9.85 km (anything from 9.845 to 9.855 counts)

    10.00 km × 0.9849 = 9.849 km, about 9.85 km: 1.5 % short, the same share as the program's Pune to Mumbai line in EPSG:7755.

  5. Question 5 of 12 In EPSG:7755 near Pune, lengths are about 1.5 % short. About how far off are areas?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: About 3 % small, because the areal scale is the square of the length scale

    In a conformal grid the areal scale is the square of the length scale, so area errors are about twice length errors.

  6. Question 6 of 12 For the block from 15° N to 25° N and 75° E to 85° E, what did the program measure?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: 1,158,951 km² on the ellipsoid; 3.15 % less in EPSG:7755 and 14.23 % more in EPSG:3857

    At national scale the errors of a conformal grid and of Web Mercator stay: about 3 % small and 14 % large for this block.

  7. Question 7 of 12 A GeoDataFrame of districts is in EPSG:4326 and gdf.area returns values such as 0.85. What do you do before reporting areas?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: Compute geodesic areas, or project to an equal-area CRS and measure there

    GeoPandas warns that areas may be invalid in a geographic CRS. Measure on the ellipsoid or in an equal-area grid.

  8. Question 8 of 12 What does PostGIS's ST_Distance return for two geography values?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: Metres on the spheroid, unless use_spheroid is false

    The type decides: geography measures on the ellipsoid, geometry in its SRS. Store latitude and longitude as geography when distances must be in metres on the Earth.

  9. Question 9 of 12 ST_DistanceSphere uses a sphere of radius 6,371,008 m by default. How far from the program's sphere is that?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: Under a metre, so its Pune to Mumbai distance is close to the program's 120,152.47 m

    PostGIS's sphere and the program's sphere use nearly the same radius, so both are about 14 m long over 120 km, 0.012 %.

  10. Question 10 of 12 A QGIS layer of roads is in EPSG:4326 and the project has no ellipsoid set. What does $length return?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: A planimetric length worked out from the layer's degrees, not a length on the ground

    Set the project's ellipsoid for $length and $area to be ellipsoidal, or reproject the layer and use length() in a suitable grid.

  11. Question 11 of 12 The ward is 4,001,752.6 m². With 1 acre = 4,046.8564224 m², how many acres is that? Give one decimal.

    Type a number.

    Show the answer: Question 11 of 12

    Answer: 988.9 acres (anything from 988.85 to 988.95 counts)

    4,001,752.6 ÷ 4,046.8564224 = 988.85 acres, as the program prints, or 400.18 ha.

  12. Question 12 of 12 Two plots, one in each of two states, are both described as 2 bigha. Which statement is safe?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Nothing about their relative size follows until each bigha is converted with its own record's definition

    Compare land areas in square metres or hectares after converting each with the definition its record uses.

Interview questions

Warm-up (fresher): When is a planar distance in a projected CRS good enough?

When the grid’s scale factor stays close to 1 along the whole line, because a planar length is wrong by that scale minus one. In the local UTM zone the scale is within about 0.1 % of 1, so the straight line from Pune to Mumbai in zone 43N was only 2.82 m short of the 120,138.17 m geodesic. In a national conformal grid such as EPSG:7755 the same line was 1.55 % short, and in Web Mercator 5.77 % too long, which is not good enough for any measurement. So use the local UTM zone for lengths inside a city or a district, and measure geodesics on the ellipsoid for long lines, lines across zones, or anything that must match another party’s numbers.

Two questions in the module’s bank build on this: delivery distances that come out 8 % short in DuckDB, and two departments whose district areas differ by 3 %. Each lists what it tests and a first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A delivery company computes trip distances in DuckDB with ST_Distance_Spheroid on points built as ST_Point(longitude, latitude). Most trips in Maharashtra come out several per cent short. Explain what happened and how you would fix and test it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Two departments report the area of the same district: one computed it in EPSG:7755, the other on the ellipsoid, and their figures differ by about 3 %. Which is right, how do you explain the difference, and how do you make all district statistics consistent?

    Model answer, Pro. First step and follow-ups

Flashcards · 7 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What is a geodesic, and how accurate are Karney's and Vincenty's methods?

    The shortest line between two points on the ellipsoid. Karney's algorithms in GeographicLib err by less than 15 nanometres; Vincenty's older formula is good to about 0.1 mm but may not converge for points almost opposite each other on the Earth.

  2. How far off is a sphere, instead of the ellipsoid, for the distance from Pune to Mumbai?

    The haversine formula on a sphere of radius 6,371,008.8 m gave 14.30 m more than the 120,138.17 m geodesic, 0.012 %. That is close enough for most analysis in India, but not for surveys, legal distances or checking another party's numbers.

  3. How wrong is a straight-line length measured in a projected grid?

    By the grid's scale along the line, minus one. Pune to Mumbai came out 2.82 m short in UTM 43N, 1.549 % short in EPSG:7755 and 5.766 % long in Web Mercator. A straight line in degrees is no distance at all.

  4. Why measure areas on the ellipsoid or in an equal-area grid rather than in any projected CRS?

    A conformal grid's area error is about twice its length error. Measured on the ellipsoid, a 2 km ward near Pune has 4,001,752.60 m²; its UTM zone makes it 0.044 % smaller, EPSG:7755 2.986 % smaller and Web Mercator 11.807 % larger.

  5. How do QGIS, PostGIS and DuckDB decide between measuring on the ellipsoid and in the plane?

    QGIS by function: $area and $length use the project's ellipsoid, area() and length() the layer's own CRS. PostGIS by type: geography on the spheroid, geometry in the units of its SRS. DuckDB by function: ST_Distance_Spheroid and ST_Area_Spheroid, which read latitude first.

  6. Why did DuckDB's ST_Distance_Spheroid report 110,690 m from Pune to Mumbai instead of about 120,138 m?

    The points were built as ST_Point(longitude, latitude), but the spheroid functions expect latitude first, so the axes were swapped and the answer came out 7.864 % short.

  7. Which units of land area are exact, and which depend on a record's definition?

    A hectare is exactly 10,000 m² and an acre exactly 43,560 square feet, or 4,046.856 422 4 m². A bigha, katha or biswa is whatever the land records using it define, so convert only with the size the record states, and say which one you used.

Key takeaways

  • A distance on the Earth is a geodesic on the ellipsoid, 120,138.17 m from Pune to Mumbai; a sphere of the mean radius is 0.012 % off there, close enough for most analysis and not for surveys.
  • A planar length is wrong by the grid’s scale minus one: 0.002 % in UTM 43N, −1.55 % in EPSG:7755 and +5.77 % in Web Mercator for the same line, and a length in degrees means nothing.
  • Areas follow the areal scale, so measure them on the ellipsoid or in an equal-area grid: the 4,001,752.60 m² ward is 2.99 % small in EPSG:7755 and 11.81 % large in Web Mercator.
  • Engines measure what the function or the type asks for: $area against area() in QGIS, geography against geometry in PostGIS, and DuckDB’s spheroid functions, which expect latitude first.
  • Hectares and acres have exact definitions, 10,000 m² and 4,046.856 422 4 m²; local units such as the bigha need the definition their record uses.

Terms in this lesson

geodesic
The shortest line between two points on the ellipsoid; its length is the distance between them on the Earth's model, 120,138.17 m from Pune to Mumbai on WGS 84.
haversine formula
A formula for the great-circle distance between two points on a sphere from their latitudes and longitudes. With the Earth's mean radius it is fast and about 0.01 % off over distances such as Pune to Mumbai.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.