How it works
An image is a grid of pixels counted by column, left to right, and row, top to
bottom, from 0 at the top-left. Column and row here are positions, not just counts: pixel
(0, 0) covers columns 0 to 1 and rows 0 to 1, its corner is at (0, 0) and its
centre at (0.5, 0.5). Zoom in on the image and turn on the pixel grid to see them.
The geotransform is six numbers that put every one of those positions on the map:
x = gt0 + col * gt1 + row * gt2
y = gt3 + col * gt4 + row * gt5
(gt0, gt3) is the top-left corner of the image, gt1 is the pixel width and gt5 the pixel
height, negative because rows run down and y runs up. gt2 and gt4 are 0 for a north-up
image. An image with no geotransform gets (0, 1, 0, 0, 0, 1) from GDAL: x is the column and y
the row, so it lands upside down, a degree a pixel, north-east of 0, 0 (No coordinates).
North up, the geotransform and the extent say the same thing: xmin = gt0,
xmax = gt0 + ncol * gt1, ymax = gt3, ymin = gt3 + nrow * gt5. An extent and the number of
columns and rows are all a regular grid needs (see Regular in what?).
gt2 and gt4 tilt the rows and the columns, so the image becomes a parallelogram on the map:
rotation turns both, shear leans the columns. Any geotransform is some mix of an
offset, two pixel sizes, a rotation and a shear: six numbers, six ways to move. Whatever the mix,
straight lines stay straight and parallel ones stay parallel, so an affine cannot put a
Mercator or a polar map on longitude and latitude; that needs a warp, pixel by pixel through a
projection. The readout gives the inverse too, the six numbers that take x, y back to
column and row, which is how a reader finds the pixel under a point.
A world file (.pgw beside a .png, .jgw beside a .jpg, or .wld) holds the same numbers in
the order gt1, gt4, gt2, gt5, then C and F, which are the centre of the top-left pixel,
half a pixel in from gt0 and gt3. Mixing the two up moves the image half a pixel, one of the
commonest slips there is. Neither form says what x and y are: the CRS comes from somewhere else
(a .prj file, or -a_srs).
Registration works the geotransform out from ground control points: a place on
the image and the same place on the map. Each point gives two equations, one for x and one for
y. North up has four unknowns (gt0, gt1, gt3, gt5), so two points fix it as long as they
are in different rows and columns. Rotate and scale also has four (the offset, the size
of a square pixel and the angle), and also needs two. A full affine has all six and needs
three, not in a line. With more points than that, the fit is least squares and the
residuals say how far each point is from where the fit puts it: the size of your clicks'
error, or a sign the kind of fit is wrong. Fit the crooked scan north up and the residuals are
large; rotate and scale does better, and only the full affine fits it, because the scan is
sheared as well. For the two samples the readout also gives how far the fit puts the image's
corners from where they truly are.
To register your own world image, pick Your own PNG or JPEG (or drop a file on the
views), click a place you can find on it, then the same place on the map, and again for a
second point. Type a point's longitude and latitude in its row for an exact place. The map here
is longitude and latitude, so this suits a plate carree world image; the same fit works for any
CRS's x and y.
The GeoTIFF is the image's own pixels with the geotransform (a ModelTransformation tag
when it is not north up) in EPSG:4326, and the world file is the six lines above. The
GDAL command does the same from the command line: -a_ullr for north up,
or the points as -gcp and gdalwarp -order 1, GDAL's own least-squares
affine. The maths is in src/core/affine.js, checked in
test/check-affine.mjs.
Hash keys: pic (atlas, crooked), fit,
x0, y0, w, h, rot,
shear (by hand), gcp (col,row,x,y;...), op,
grat, pix. Your own image stays in your browser and is not in the link.