PostGIS
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Raster affine georeferencing

Raster affine georeferencing

PostGIS raster stores the relationship between raster cell coordinates (i, j) and spatial coordinates (x, y) as the same six-coefficient geotransform used by GDAL world files:

x = upperleftx + scalex * i + skewx * j
y = upperlefty + skewy  * i + scaley * j

The same coefficients appear in ST_GeoReference output as scalex, skewy, skewx, scaley, upperleftx, and upperlefty. In C, the raster core exposes them as scaleX, skewX, skewY, scaleY, ipX, and ipY; see rt_raster_get_geotransform_matrix() and rt_raster_set_geotransform_matrix() in raster/rt_core/rt_raster.c.

The old DevWikiAffineParameters page is still useful because it explains how the four linear coefficients can be derived from more physical raster-grid descriptors. The reverse derivation is covered in Raster physical georeferencing parameters.

  • i_mag: size of one pixel step along the transformed i axis.
  • j_mag: size of one pixel step along the transformed j axis.
  • theta_i: clockwise rotation from the spatial x axis to the transformed i basis vector.
  • theta_ij: counterclockwise angle from the transformed i basis vector to the transformed j basis vector.

Raster basis vectors

The current code still uses these descriptors. rt_raster_calc_gt_coeff() computes xscale, xskew, yskew, and yscale from them. rt_raster_calc_phys_params() performs the reverse calculation. RASTER_setRotation() preserves i_mag, j_mag, and theta_ij while changing theta_i. RASTER_setGeotransform() accepts the physical descriptors and writes the resulting scale and skew coefficients.

Elementary transforms

The derivation uses ordinary two-dimensional linear transforms. These are not complete affine transforms until the upper-left offsets are added, but they make the scale, skew, and rotation terms easier to reason about.

Counterclockwise rotation:

Counterclockwise rotation matrix

x' = x cos(theta) - y sin(theta)
y' = x sin(theta) + y cos(theta)

Clockwise rotation:

Clockwise rotation matrix

x' = x cos(theta) + y sin(theta)
y' = -x sin(theta) + y cos(theta)

Scaling:

Scaling matrix

x' = x * s_x
y' = y * s_y

Shearing parallel to the x axis:

X shear matrix

x' = x + k_x * y
y' = y

Shearing parallel to the y axis:

Y shear matrix

x' = x
y' = k_y * x + y

Reflection across the x axis:

Reflection matrix

x' = x
y' = -y

Combining transforms

The derivation used by PostGIS combines:

  1. Reflection across the i axis.
  2. Scaling along the i and j axes.
  3. Shearing parallel to the i axis.
  4. Clockwise rotation by theta_i.

The aggregate matrix O is the product of those matrices:

Aggregate matrix step 1

The intermediate products are:

Aggregate matrix step 2

Aggregate matrix step 3

Aggregate matrix step 4

The result is:

o11 = s_i * cos(theta_i)
o12 = k_i * s_j * f * cos(theta_i) + s_j * f * sin(theta_i)
o21 = -s_i * sin(theta_i)
o22 = -k_i * s_j * f * sin(theta_i) + s_j * f * cos(theta_i)

The coefficients are mixed terms. None of them represents pure scale, rotation, or skew once the operations are combined.

The matrix is then applied to raster cell coordinates as:

Aggregate matrix usage

x' = o11 * i + o12 * j
y' = o21 * i + o22 * j

Calculating terms

The reflection flag f records whether the transformed j axis is flipped:

if theta_ij < 0 then f = -1
if theta_ij >= 0 then f = 1

The i basis vector is:

i basis vector

Its magnitude is:

i basis vector magnitude

So:

s_i = i_mag

The j basis vector is:

j basis vector

The shear angle is the complement of theta_ij, with the reflection flag included so one expression works for flipped and unflipped grids:

Shearing parameters

k_i = tan(f * pi / 2 - theta_ij)

This gives k_i = 0 when theta_ij is 90 degrees or -90 degrees. A theta_ij of 0 or pi is invalid because the transformed basis vectors would be parallel and the grid would collapse.

The j basis vector magnitude is:

j basis vector magnitude

Solving for s_j gives:

j scale factor

s_j = j_mag / sqrt(k_i * k_i + 1)

Affine matrix storage

The 2-by-2 matrix O becomes the upper-left part of the full affine matrix A. The upper-right column stores translation, and the bottom row is fixed:

Affine matrix

The cell-to-world conversion is:

Affine matrix usage

x = a11 * i + a12 * j + a13
y = a21 * i + a22 * j + a23

PostGIS raster stores those six changeable coefficients as:

Affine coefficient Raster field
a11 ScaleX / scaleX
a12 SkewX / skewX
a13 UpperLeftX / ipX
a21 SkewY / skewY
a22 ScaleY / scaleY
a23 UpperLeftY / ipY

For the general affine case, the names ScaleX, ScaleY, SkewX, and SkewY are conventional storage names rather than proof that a coefficient is pure scale or pure skew.

Current code anchors

The current implementation points to the same model:

  • rt_raster_calc_gt_coeff() in raster/rt_core/rt_raster.c implements the physical-parameter-to-coefficient equations above.
  • rt_raster_calc_phys_params() computes i_mag, j_mag, theta_i, and theta_ij from the four linear geotransform coefficients.
  • rt_raster_set_phys_params() applies the calculated scale and skew coefficients while leaving upper-left offsets unchanged.
  • RASTER_setRotation() in raster/rt_pg/rtpg_raster_properties.c preserves the current physical pixel sizes and basis-vector separation while changing only the rotation angle.
  • ST_SetScale, ST_SetSkew, ST_SetRotation, ST_ScaleX, ST_ScaleY, ST_SkewX, ST_SkewY, and ST_GeoReference remain the documented SQL entry points for inspecting or changing the stored georeference metadata.