Wiki page
[Affine Transform] by
sandro
2015-05-02 23:40:35.
D 2015-05-02T23:40:35.957
L Affine\sTransform
P d811cbd7e2e0c54842fdefebdb7fe9853c8b6f74
U sandro
W 35904
<a href="https://www.gaia-gis.it/fossil/libspatialite/wiki?name=4.2.0-doc">back</a>
<h2>Affine Transformations</h2>
Starting since version <b>4.3.0</b> SpatiaLite supports several new SQL functions based on <a href="http://en.wikipedia.org/wiki/Affine_transformation">affine transformations</a>; understanding all the underlaying mathematics could easily be a rather difficult task, more notably if you have absolutely no familiarity with this kind of operations.<br>
So we'll start smoothly by introducing first a very simple practical example based on kind of a joke about the geography of Italy and Sicily.
<h2>Fancy Geograpy and amusing Math</h2>
<table border="1" cellspacing="4" cellpadding="8">
<tr><td>
<h3>Preface</h3>
Looking to a map of Italy it appears obvious at first glance that Sicily is located in a very inconvenient position:
<ol>
<li>Sicicly is placed too far South and too much close to North Africa; this implies an unpleasant torrid summer, and in turn this poses severe limitations to agricoltural activities.</li>
<li>The <a href="http://en.wikipedia.org/wiki/Strait_of_Messina">Strait of Messina</a> is exaggeratedly narrow, thus posing severe security risks to the navigation of capital ships and supertankers.<br>
Even worst, from time to time some crazy politician strongly advocates the very stupid idea to build an incredibly costly <a href="http://en.wikipedia.org/wiki/Strait_of_Messina_Bridge">bridge crossing the Strait</a>, completely overlooking the very high seismical risk of this district and purposely forgetting to remember that in 1908 both towns of <a href="http://en.wikipedia.org/wiki/1908_Messina_earthquake">Messina and Reggio Calabria</a> were completely destroyed by an earthquake followed by a tsunami.<br>
More than 100,000 peoples lost their lives, and it was one of the most frightening natural disasters registered in Europe during modern times.</li>
</ol>
So we'll immediately start a case study intended to identify a possible alternative location for Sicily.<br>
Incidentally we'll use the new SQL functions based on Affine Transformations for this task.<br>
Let's go on.
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/sicily_0.png" alt="wfs-1" border="1">
</td></tr>
<tr><td>
<h3>Step #1: translating Sicily into a more convenient position</h3>
There is plenty of free room in the lower Tyrrhenian Sea, so we'll start by applying a <a href="http://en.wikipedia.org/wiki/Translation_%28geometry%29">translation</a>: this practically means adding (or subctracting) a constant value to the coordinates on both <b>x</b> and <b>y</b> axes.<br>
After a very quick examination moving Sicily <b>150 km</b> (i.e. <b>150,000 m</b>) westwards and <b>150 km</b> northwards seems to be an absolutely reasonable choice.<br>
So using the Affine Transformation SQL functions we'll write the following SQL statement:
<verbatim>
CREATE TABLE sicilia_1 AS
SELECT cod_reg,
ATM_Transform(geometry,
ATM_CreateTranslate(-150000, 150000)) AS geom
FROM sicilia_0;
SELECT RecoverGeometryColumn('sicilia_1', 'geom', 32632, 'MULTIPOLYGON', 'XY');
</verbatim>
<b>Remarks</b>:
<ul>
<li>all affine transformation related SQL function names start with an <b>ATM_</b> prefix: this simply stands for <i><b><u>A</u></b>ffine <b><u>T</u></b>ransformation <b><u>M</u></b>atrix</i>.</li>
<li><b>ATM_Transform()</b> is very similar to <b>ST_Transform()</b>; the most obvious difference is in that all transformation arguments are now expected to be passed under the form of an appropriate <b>BLOB-serialized</b> Affine Transformation Matrix.</li>
<li><b>ATM_CreateTranslate()</b> simply is an SQL function returning a <b>BLOB-serialized</b> Affine Transformation Matrix.<br>
The BLOB-Matrix will be initialized so to represent a simple <b>2D Translate</b> accordingly to <b>(tx, ty)</b> arguments.<br>
You could eventually define a complete <b>3D Translate</b> by passing <b>(tx, ty, tz)</b> arguments, but this isn't strictly required in our current example.
</ul>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/sicily_1.png" alt="wfs-1" border="1">
</td></tr>
<tr><td>
<h3>Step #2: rotating Sicily so to get a nice horizontal alignement</h3>
Aligning the southern shores of Sicily to an almost horizontal line will surely lead to a more nicely ordered layout: so we have now to apply a <a href="http://en.wikipedia.org/wiki/Rotation_%28mathematics%29">counterclockwise rotation</a> of about <b>25.0 degrees</b>.<br>
Thanks to Affine Transformations, we can combine both the previous translation and the current rotation into a single movement (a so called <b>rototranslation</b>).<br>
We simply have to execute the following SQL statement:
<verbatim>CREATE TABLE sicilia_2 AS
SELECT cod_reg,
ATM_Transform(geometry,
ATM_Translate(-150000, 150000,
ATM_Translate(cx, cy,
ATM_Rotate(25,
ATM_CreateTranslate(-cx, -cy))))) AS geom
FROM (SELECT cod_reg, ST_X(centroid) AS cx, ST_Y(centroid) AS cy, geometry
FROM (SELECT cod_reg, ST_Centroid(geometry) AS centroid, geometry
FROM sicilia_0) AS g1
) AS g2;
SELECT RecoverGeometryColumn('sicilia_2', 'geom', 32632, 'MULTIPOLYGON', 'XY');
</verbatim>
<b>Remarks</b>:
<ul>
<li>correctly handling Rotation is rather complex.
Any rotation will always imply a <b>fixed center point</b>, and by default this is exactly placed at the coordinates origin: <b>(0, 0)</b> (in the <b>2D</b> case) or <b>(0, 0, 0)</b> (in the <b>3D</b> case).</li>
<li>so a very naive attempt to directly invoke <b>ATM_Rotate(25)</b> will simply relocate Sicily in Southern Spain, and this absolutely isn't our intention.</li>
<li>what we really need is a more complex sequence of chained transformations:
<ol>
<li>we'll start first by creating a new <b>BLOB-Matix</b> intended to relocate Sicily's <a href="http://en.wikipedia.org/wiki/Centroid">centroid</a> exactly on the coordinates origin: <b>ATM_CreateTranslate(-cx, -cy)</b></li>
<li>now we can safely chain the intended Rotation into the overall transformation stack: <b>ATM_Rotate(25, atm-blob)</b></li>
<li>after applying the Rotation we now have to relocate Sicily in its initial position: <b>ATM_Translate(cx, cy, atm-blob)</b></li>
<li>and finally we'll apply the Translation intended to reposition Sicily westwards and nothwards: <b>ATM_Translate(-150000, 150000, atm-blob)</b><br>
<i>we've used two separate translations simply for didactic clarity: we could easily merge both them into a single translation:<br> ATM_Translate(cx - 150000, cy + 150000, atm-blob)</i></li>
<li>this way <b>ATM_Transform()</b> will receive the final <b>BLOB-Matrix</b> resulting by chaining all the above transformation steps in the correct sequence.</li>
</ol></li>
<li><b><u>Very important notice</u></b>: Affine Transformations <u>are not commutative</u>: the relative order of subsequent operations in a complex transformation chain should always be very carefully considered.</li>
<li>all the <b>FROM (SELECT ... FROM (SELECT ...) AS g1) AS g2</b> stuff simply is a rather trivial SQL trick based on two nested sub-queries.<br>
it's indented scope simply is avoiding to call too many times ST_Centroid(), ST_X() and ST_Y() in order to get the centroid coordinates.</li>
</ul>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/sicily_2.png" alt="wfs-1" border="1">
</td></tr>
<tr><td>
<h3>Step #3: inflating and reshaping Sicily</h3>
An increased surface is surely welcome, because it automatically implies more agricultural lands: on the other hand shortening a little bit the length of the southern coastline will surely facilitate mobility and communications.<br>
So we'll now apply a <a href="http://en.wikipedia.org/wiki/Scaling_%28geometry%29">scaling</a> transformation using two different values: <b>sx=0.9</b> and <b>sy=1.3</b>.<br>
Once again, Affine Transformations enable us to combine altogether both translate, rotate and scale into a single transformation.<br>
This is the corresponding SQL statement:
<verbatim>
CREATE TABLE sicilia_3 AS
SELECT cod_reg,
ATM_Transform(geometry,
ATM_Translate(-150000, 150000,
ATM_Translate(cx, cy,
ATM_Scale(0.9, 1.3,
ATM_Rotate(25,
ATM_CreateTranslate(-cx, -cy)))))) AS geom
FROM (SELECT cod_reg, ST_X(centroid) AS cx, ST_Y(centroid) AS cy, geometry
FROM (SELECT cod_reg, ST_Centroid(geometry) AS centroid, geometry
FROM sicilia_0) AS g1
) AS g2;
SELECT RecoverGeometryColumn('sicilia_3', 'geom', 32632, 'MULTIPOLYGON', 'XY');
</verbatim>
<b>Remarks</b>:
<ul>
<li>there is nothing really new in this: we'll simply chain yet another transformation in the appropriate sequence order.</li>
<li><b>ATM_Scale(sx, sy)</b> is intended for the simpler <b>2D</b> case.</li>
<li>in the more general <b>3D</b> case you can invoke <b>ATM_Scale(sx, sy, sz)</b></li>
</ul>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/sicily_3.png" alt="wfs-1" border="1">
</td></tr>
<tr><td>
<h3>Step #4: final touch: reflecting Sicily</h3>
A reflected Sicily presents many interesting advantages: we'll examine all them in full detail in our study conclusions.<br>
So we'll now apply a final <a href=http://en.wikipedia.org/wiki/Reflection_%28mathematics%29">reflection</a> transformation; this simply corresponds to a <b>180 degrees</b> rotation around the <b>Y axis</b>.<br>
And the following is the final SQL statement applying all the above transformations in a single shot:
<verbatim>CREATE TABLE sicilia_2 AS
CREATE TABLE sicilia_4 AS
SELECT cod_reg,
ATM_Transform(geometry,
ATM_Translate(-150000, 150000,
ATM_Translate(cx, cy,
ATM_YRoll(180,
ATM_Scale(0.9, 1.3,
ATM_Rotate(25,
ATM_CreateTranslate(-cx, -cy))))))) AS geom
FROM (SELECT cod_reg, ST_X(centroid) AS cx, ST_Y(centroid) AS cy, geometry
FROM (SELECT cod_reg, ST_Centroid(geometry) AS centroid, geometry
FROM sicilia_0) AS g1
) AS g2;
SELECT RecoverGeometryColumn('sicilia_4', 'geom', 32632, 'MULTIPOLYGON', 'XY');
</verbatim>
<b>Remarks</b>:
<ul>
<li>there are several possible rotations supported by Affine Transformation:
<ol>
<li><b>ATM_Rotate()</b> always intends a <b>2D</b> rotation, i.e. a rotation centered around the <b>Z axis</b></li>
<li>In the more general <b>3D</b> case there are three possible rotations, one for each axis.</li>
<li>the corresponding SQL functions are: <b>ATM_XRoll(angle)</b>, <b>ATM_YRoll(angle)</b> and <b>ATM_ZRoll(angle)</b></li>
<li><i><u>Note</u>: ATM_ZRoll() and ATM_Rotate() simply are two different alias-names for the same identical SQL function</i>.</li>
</ol></li>
</ul>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/sicily_4.png" alt="wfs-1" border="1">
</td></tr>
<tr><td>
<h3>Final considerations</h3>
<ol>
<li><b>Agriculture</b>: after its relocation Sicily will gain much more agricultural land and will enjoy a more favourable climate: still sunny and warm but much less arid.<br>
This will certainly start a noticeable development of many economic activities, both direct and indirectly derived.</li>
<li><b>Transportation systems</b>: the suggested new layout for the Lower Tyrrhenian Sea strongly facilitates the development of maritime transports. Sicily could new become the central hub of a highly efficient network of high-speed and high-frequency ferry connections:
<ul>
<li>Palermo will now directly face Naples; Civitavecchia (Rome) seems to be a second obvious terminal for direct connections leading to Central Italy.</li>
<li>Messina will acquire a decisive strategic role, and will become the terminal for ferry connections leading to Civitavecchia, Leghorn and Genoa.</li>
<li>A less relevant (but anyway interesting) ferry link will join Trapani and Reggio Calabria.</li>
<li>Sardinia will strongly benefit from the new layout; Cagliari will be directly connected to Syracuse (or may be Augusta), and Olbia to Messina.<br>This means definitely breaking the secular insulation of Sardinia, that will now start enjoying a stronger integration with all others regions of Southern Italy.</li>
<li>Last but not least: at a more strategic level it's absolutely obvious that now supertankers and big container ships can freely circumnavigate Sicily in any direction under uncompromised safety conditions.<br>
All Tyrrhenian harbors will now be directly conneted both to Eastern and Western Mediterranean, and this will surely induce a growth in the volumes of international traffics they could potentially attract.</li>
</ul></li>
<li><b>Heavy industry</b>: the new transportation system strongly centered around maritime communications will surely induce an active rebirth of shipyards, a fluorishing traditional excellence of many southern regions in past times but nowadays a perishing and declining industrial branch.</li>
<li><b>Tourism</b>: the suggested new layout will certainly promote a strong development of international tourism.<br>
It's worth noting that the new layout will give birth to a wonderful island group extending between Sicily to Campania; several of these islands actually are active volcanoes, and this island chain will ideally join Mount Etna and Mount Vesuvius.<br>
This area could be considered the most impressive volcanic field of Europe and will certainly become a strong touristic attraction.
</li>
<li><b>Internal commerce</b>: we can easily forecast a strong growth in volume of internal exchanges thank to the better connectivity based on maritime transports..<br>
Just a single example: Sardinia should now be able to export its finest sheep-cheese on the Calabrian markets whilst Calabria could freely export its renown red hot chilly peppers to Sardinia, and both regions will widely befit from increased exchange volumes.</li>
<li><b>Practical realization</b>: the present study clearly demonstrates that there are no Mathematics, Geometry or Geography adverse factors potentially forbidding the practical realization of the suggested idea.<br>
Unhappily the current state of the art in Geology poses many puzzling questions not yet fully resolved in a completely satisfying way.<br>
Anway we are hopefully expecting that future advancements in Tectonics (and more specifically a deeper comprehension of the micro-plaques mechanics) will surely allow to overcome any remaining issue.
</ol>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/italy-2.0.png" alt="wfs-1" border="1">
</td></tr>
</table><br><hr>
<h2>Boring Math: a more formal presentation</h2>
Playtime's over: we'll now start a most serious exlanation.<br><br>
An Affine Transformation can be represented in the form of a square matrix; the simpler <b>2D</b> case requires a <b>3 x 3</b> matrix, and the followings are the possible arrangements corresponding to each elementary transformation:<br><br>
<table>
<tr><td rowspan="4" valign="middle">General layout</td>
<td>/</td><td align="center"><b>a</b></td><td align="center"><b>b</b></td><td align="center"><b>xoff</b></td><td>\</td></tr>
<tr><td>|</td><td align="center"><b>d</b></td><td align="center"><b>e</b></td><td align="center"><b>yoff</b></td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="4" valign="middle">Identity</td>
<td>/</td><td align="center">1</td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">1</td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="4" valign="middle">Translate(<b>tx</b>, <b>ty</b>) </td>
<td>/</td><td align="center">1</td><td align="center">0</td><td align="center"><b>tx</b></td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">1</td><td align="center"><b>ty</b></td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="4" valign="middle">Scale(<b>sx</b>, <b>sy</b>)</td>
<td>/</td><td align="center"><b>sx</b></td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center"><b>sy</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="4" valign="middle">Rotate(<b>θ</b>)</td>
<td>/</td><td align="center"><b>cos(θ)</b></td><td align="center"><b>-sin(θ)</b></td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center"><b>sin(θ)</b></td><td align="center"><b>cos(θ)</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td></td></tr>
</table><br>
A <b>3D</b> affine transformation requires a <b>4 x 4</b> matrix.<br>
As you can easily notice there is direct relation beetween a 3D matrix and a 2D matrix; notice the cells showing a gray backgroud.<br><br>
<table>
<tr><td rowspan="5" valign="middle">General layout </td>
<td>/</td><td align="center"><b>a</b></td><td align="center"><b>b</b></td><td align="center" bgcolor="#d0d0d0"><b>c</b></td><td align="center"><b>xoff</b></td><td>\</td></tr>
<tr><td>|</td><td align="center"><b>d</b></td><td align="center"><b>e</b></td><td align="center" bgcolor="#d0d0d0"><b>f</b></td><td align="center"><b>yoff</b></td><td>|</td></tr>
<tr><td>|</td><td align="center" bgcolor="#d0d0d0"><b>g</b></td><td align="center" bgcolor="#d0d0d0"><b>h</b></td><td align="center" bgcolor="#d0d0d0"><b>i</b></td><td align="center" bgcolor="#d0d0d0"><b>zoff</b></td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center" bgcolor="#d0d0d0">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="5" valign="middle">Identity</td>
<td>/</td><td align="center">1</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">1</td><td align="center">0</td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td align="center">0</td><td>/</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="5" valign="middle">Translate(<b>tx</b>, <b>ty</b>, <b>tz</b>) </td>
<td>/</td><td align="center">1</td><td align="center">0</td><td align="center">0</td><td align="center"><b>tx</b></td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">1</td><td align="center">0</td><td align="center"><b>ty</b></td><td>|</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td align="center"><b>tz</b></td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="5" valign="middle">Scale(<b>sx</b>, <b>sy</b>, <b>sz</b>)</td>
<td>/</td><td align="center"><b>sx</b></td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center"><b>sy</b></td><td align="center">0</td><td align="center">0</td><td>|</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">0</td><td align="center"><b>sz</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="5" valign="middle">X Roll(<b>θ</b>)</td>
<td>/</td><td align="center">1</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center"><b>cos(θ)</b></td><td align="center"><b>-sin(θ)</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center"><b>sin(θ)</b></td><td align="center"><b>cos(θ)</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="5" valign="middle">Y Roll(<b>θ</b>)</td>
<td>/</td><td align="center"><b>cos(θ)</b></td><td align="center">0</td><td align="center"><b>sin(θ)</b></td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">1</td><td align="center">0</td><td align="center">0</td><td>|</td></tr>
<tr><td>|</td><td align="center"><b>-sin(θ)</b></td><td align="center">0</td><td align="center"><b>cos(θ)</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td colspan="5"><hr></td></tr>
<tr><td rowspan="5" valign="middle">Z Roll(<b>θ</b>)</td>
<td>/</td><td align="center"><b>cos(θ)</b></td><td align="center"><b>-sin(θ)</b></td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center"><b>sin(θ)</b></td><td align="center"><b>cos(θ)</b></td><td align="center">0</td><td align="center">0</td><td>|</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td align="center">0</td><td>|</td></tr>
<tr><td>\</td><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">1</td><td>/</td></tr>
<tr><td></td></tr>
</table>
<br><hr>
<h3>applying an Affine Transformation</h3>
In order to materialize an affine transformation we simply have to compute <b>(x', y', z')</b> coordinates starting from <b>(x, y, z)</b> for every point or vertex found in the input Geometry accordingly to the following formulae:
<ul>
<li><b>x' = a*x + b*y + c*z + xoff</b></li>
<li><b>y' = d*x + e*y + f*z + yoff</b></li>
<li><b>z' = g*x + h*y + i*z + zoff</b></li>
</ul><br>
in the simpler <b>2D</b> case this will assume the reduced form:
<ul>
<li><b>x' = a*x + b*y + xoff</b></li>
<li><b>y' = d*x + e*y + yoff</b></li>
</ul><br>
As you can notice, applying an Affine Transformation does not requires computing any trigonometric function.<br>
Trigonometric functions are very costly in computational terms, so applying an Affine Transformation is an intrinsically efficient mechanism because simply depends on multiplications and additions.<br>
<br><hr>
<h3>chaining two (or even more) Affine Transformations in a sigle operation</h3>
Affine transformation matrices have another amazing property.<br>
We can <a href="http://en.wikipedia.org/wiki/Matrix_multiplication">multiply</a> two different affine transformation matrices thus obtaining a third affine transformation matrix, and this latest once applied will contain both transformations and in the right sequence.<br>
There is no limit; we can infinitively chain as many transformations as required, we'll simply have to continue multiplying all matrices one after the other carefully respecting the appropriate sequence.<br>
At the end of the process we'll always get just a single affine transformation matrix faithfully representing any individual transformation in the chain.
<table bgcolor="#ffc000" cellpadding="10"><tr><td>
The multiplication between two matrices <u>is not a commutative operation</u>: the relative order of operands is absolutely relevant.</td></tr></table>
<h3>multiplying two matrices</h3>
Multiplication is not really a simple operation when matrices are involved and requires a rather complex procedure; the following example shows how to multiply two <b>4 x 4</b> matrices.<br>
<table cellspacing="4" cellpadding="4">
<tr><td>
<table>
<tr><td>/</td><td align="center">a<sub>11</sub></td><td align="center">a<sub>12</sub></td><td align="center">a<sub>13</sub></td><td align="center">a<sub>14</sub></td><td>\</td></tr>
<tr><td>|</td><td align="center">a<sub>21</sub></td><td align="center">a<sub>22</sub></td><td align="center">a<sub>23</sub></td><td align="center">a<sub>24</sub></td><td>|</td></tr>
<tr><td>|</td><td align="center">a<sub>31</sub></td><td align="center">a<sub>32</sub></td><td align="center">a<sub>33</sub></td><td align="center">a<sub>34</sub></td><td>|</td></tr>
<tr><td>\</td><td align="center">a<sub>41</sub></td><td align="center">a<sub>42</sub></td><td align="center">a<sub>43</sub></td><td align="center">a<sub>44</sub></td><td>/</td></tr>
</table>
</td><td valign="middle">*</td>
<td>
<table>
<tr><td>/</td><td align="center">b<sub>11</sub></td><td align="center">b<sub>12</sub></td><td align="center">b<sub>13</sub></td><td align="center">b<sub>14</sub></td><td>\</td></tr>
<tr><td>|</td><td align="center">b<sub>21</sub></td><td align="center">b<sub>22</sub></td><td align="center">b<sub>23</sub></td><td align="center">b<sub>24</sub></td><td>|</td></tr>
<tr><td>|</td><td align="center">b<sub>31</sub></td><td align="center">b<sub>32</sub></td><td align="center">b<sub>33</sub></td><td align="center">b<sub>34</sub></td><td>|</td></tr>
<tr><td>\</td><td align="center">b<sub>41</sub></td><td align="center">b<sub>42</sub></td><td align="center">b<sub>43</sub></td><td align="center">b<sub>44</sub></td><td>/</td></tr>
</table>
</td>
</td><td valign="middle">=</td>
<td>
<table>
<tr><td>/</td><td align="center">
(a<sub>11</sub>*b<sub>11</sub> +
a<sub>12</sub>*b<sub>21</sub> +
a<sub>13</sub>*b<sub>31</sub> +
a<sub>14</sub>*b<sub>41</sub>)</td>
<td>(a<sub>11</sub>*b<sub>12</sub> +
a<sub>12</sub>*b<sub>22</sub> +
a<sub>13</sub>*b<sub>32</sub> +
a<sub>14</sub>*b<sub>42</sub>)</td>
<td>(a<sub>11</sub>*b<sub>13</sub> +
a<sub>12</sub>*b<sub>23</sub> +
a<sub>13</sub>*b<sub>33</sub> +
a<sub>14</sub>*b<sub>43</sub>)</td>
<td>(a<sub>11</sub>*b<sub>14</sub> +
a<sub>12</sub>*b<sub>24</sub> +
a<sub>13</sub>*b<sub>34</sub> +
a<sub>14</sub>*b<sub>44</sub>)</td><td>\</td></tr>
<tr><td>|</td><td align="center">
(a<sub>21</sub>*b<sub>11</sub> +
a<sub>22</sub>*b<sub>21</sub> +
a<sub>23</sub>*b<sub>31</sub> +
a<sub>24</sub>*b<sub>41</sub>)</td>
<td>(a<sub>21</sub>*b<sub>12</sub> +
a<sub>22</sub>*b<sub>22</sub> +
a<sub>23</sub>*b<sub>32</sub> +
a<sub>24</sub>*b<sub>42</sub>)</td>
<td>(a<sub>21</sub>*b<sub>13</sub> +
a<sub>22</sub>*b<sub>23</sub> +
a<sub>23</sub>*b<sub>33</sub> +
a<sub>24</sub>*b<sub>43</sub>)</td>
<td>(a<sub>21</sub>*b<sub>14</sub> +
a<sub>22</sub>*b<sub>24</sub> +
a<sub>23</sub>*b<sub>34</sub> +
a<sub>24</sub>*b<sub>44</sub>)</td><td>|</td></tr>
<tr><td>|</td><td align="center">
(a<sub>31</sub>*b<sub>11</sub> +
a<sub>32</sub>*b<sub>21</sub> +
a<sub>33</sub>*b<sub>31</sub> +
a<sub>34</sub>*b<sub>41</sub>)</td>
<td>(a<sub>31</sub>*b<sub>12</sub> +
a<sub>32</sub>*b<sub>22</sub> +
a<sub>33</sub>*b<sub>32</sub> +
a<sub>34</sub>*b<sub>42</sub>)</td>
<td>(a<sub>31</sub>*b<sub>13</sub> +
a<sub>32</sub>*b<sub>23</sub> +
a<sub>33</sub>*b<sub>33</sub> +
a<sub>34</sub>*b<sub>43</sub>)</td>
<td>(a<sub>31</sub>*b<sub>14</sub> +
a<sub>32</sub>*b<sub>24</sub> +
a<sub>33</sub>*b<sub>34</sub> +
a<sub>34</sub>*b<sub>44</sub>)</td><td>|</td></tr>
<tr><td>\</td><td align="center">
(a<sub>41</sub>*b<sub>11</sub> +
a<sub>42</sub>*b<sub>21</sub> +
a<sub>43</sub>*b<sub>31</sub> +
a<sub>44</sub>*b<sub>41</sub>)</td>
<td>(a<sub>41</sub>*b<sub>12</sub> +
a<sub>42</sub>*b<sub>22</sub> +
a<sub>43</sub>*b<sub>32</sub> +
a<sub>44</sub>*b<sub>42</sub>)</td>
<td>(a<sub>41</sub>*b<sub>13</sub> +
a<sub>42</sub>*b<sub>23</sub> +
a<sub>43</sub>*b<sub>33</sub> +
a<sub>44</sub>*b<sub>43</sub>)</td>
<td>(a<sub>41</sub>*b<sub>14</sub> +
a<sub>42</sub>*b<sub>24</sub> +
a<sub>43</sub>*b<sub>34</sub> +
a<sub>44</sub>*b<sub>44</sub>)</td><td>/</td></tr>
</table>
</td></tr></table>
<h3>identity matrix</h3>
An indentiy matrix simply corresponds to an affine transformation lacking any actual effect: at the end of the process the transformed geometry will be exactly the same as before.<br>
Moreover an identity matrix plays a special role in multiplication: the resulting matrix will always be exactly the same of the other matrix.<br>
(<i>it's more or less the equivalent of multiplying e.g. <b>8*1=8</b> in an ordinary scalar multiplication</i>).<br><br>
<hr>
<h3>Boring SQL functions: a formal explanation</h3>
<table border="1" cellspacing="4" cellpadding="8" bgcolor="#fff0d0">
<tr><th bgcolor="#C3B091">SQL Function</th><th bgcolor="#C3B091">Description</th></tr>
<tr><td><b>ATM_Transform ( BLOB Geometry , BLOB AT-matrix ) : BLOB Geometry</td><td>Will return a new Geometry by applying an Affine Transformation to the input Geometry.<br>
The output Geometry will preserve the original SRID, dimensions and type.<br>
NULL will be returned on invalid arguments.</td></tr>
<tr><td><b>ATM_IsValid ( BLOB AT-matrix ) : BOOLEAN</td><td>Will check if a BLOB do really correspond to an encoded Affine Transformation Matrix then returning TRUE or FALSE.<br>
-1 will be returned if the argument isn't of the BLOB type.</td></tr>
<tr><td><b>ATM_AsText ( BLOB AT-matrix ) : TEXT</td><td>Will return a text serialized representation of the Matrix.<br>
NULL will be returned on invalid arguments.</td></tr>
<tr><td><b>ATM_Multiply ( BLOB AT-matrix-A , BLOB AT-matrix-B ) : BLOB AT-matrix</td><td>Will multiply Matrix-B by Matrix-A then returnign the resulting Matrix.<br>
NULL will be returned on invalid arguments.<hr>
<u>Note</u>: the transformation defined by Matrix-A (left operand) will always happen <u>after</u> applying all transformations defined by Matrix-B (right operand).</td></tr>
<tr><th colspan="2" bgcolor="#F0E68C" align="center">SQL functions creating and initializing a new Affine Transformation Matrix</th></tr>
<tr><td><b>ATM_Create ( void ) : BLOB AT-matrix</b></td><td>Will return an Identity Affine Transformation Matrix.<br>
NULL if any error occurs.</td></tr>
<tr><td><b>ATM_Create ( double a , double b , double d , double e , double xoff , double yoff ) : BLOB AT-matrix</b></td><td>Will return a 2D Affine Transformation Matrix initialized with explict values.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_Create ( double a , double b , double c , double d , double e , double f , double g , double h , double i , double xoff , double yoff , double double zoff ) : BLOB AT-matrix</b></td><td>Will return a 3D Affine Transformation Matrix initialized with explict values.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_CreateTranslate ( double tx , double ty ) : BLOB AT-matrix</b><br>
<b>ATM_CreateTranslate ( double tx , double ty , double tz ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix initialized respectively as a 2D or 3D Translate.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_CreateScale ( double sx , double sy ) : BLOB AT-matrix</b><br>
<b>ATM_CreateScale ( double sx , double sy , double sz ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix initialized respectively as a 2D or 3D Scale.<br>
NULL if any error occurs or on invalid arguments..</td></tr>
<tr><td><b>ATM_CreateRotate ( double angle ) : BLOB AT-matrix</b><br>
<b>ATM_CreateZRoll ( double angle ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix initialized as Rotation around the Z axis.<br>
NULL if any error occurs or on invalid arguments.<hr>
The angle is always expected to be mesured in decimal degrees. The direction of rotation is defined such that positive angles rotate in the direction from the positive X axis toward the positive Y axis. With the default axis orientation positive angles rotate in a counterclockwise direction.<hr>
<u>Note</u>: this is the unique rotation allowed on a purely 2D plane.</td></tr>
<tr><td><b>ATM_CreateXRoll ( double angle ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix initialized as Rotation around the X axis.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_CreateYRoll ( double angle ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix initialized as Rotation around the Y axis.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><th colspan="2" bgcolor="#F0E68C" align="center">SQL functions supporting chaining two Affine Transformation Matrices</th></tr>
<tr><td><b>ATM_Translate ( double tx , double ty , BLOB AT-matrix ) : BLOB AT-matrix</b><br>
<b>ATM_Translate ( double tx , double ty , double tz , BLOB AT-matrix ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix by chaining respectively a 2D or 3D Translate and another Affine Transformation.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_Scale ( double sx , double sy , BLOB AT-matrix ) : BLOB AT-matrix</b><br>
<b>ATM_Scale ( double sx , double sy , double sz , BLOB AT-matrix ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix by chaining respectively a 2D or 3D Scale and another Affine Transformation.<br>
NULL if any error occurs or on invalid arguments..</td></tr>
<tr><td><b>ATM_Rotate ( double angle , BLOB AT-matrix ) : BLOB AT-matrix</b><br>
<b>ATM_ZRoll ( double angle , BLOB AT-matrix ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix by chaining a Rotation around the Z axis and another Affine Transformation.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_XRoll ( double angle ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix by chaining a Rotation around the X axis and another Affine Transformation.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td><b>ATM_YRoll ( double angle ) : BLOB AT-matrix</b></td><td>Will return an Affine Transformation Matrix by chaining a Rotation around the Y axis and another Affine Transformation.<br>
NULL if any error occurs or on invalid arguments.</td></tr>
<tr><td></td><td>
<u>Note</u>: all the above functions simply are convenience methods intended to avoid any need to repeatedly call <b>ATM_Multiply()</b>.
<verbatim>
SELECT ATM_Multiply(ATM_CreateRotate(15),
ATM_Multiply(ATM_CreateScale(1.1, 1.2, 1.3),
ATM_CreateTranslate(10, 20, 30)));
SELECT ATM_Rotate(15,
ATM_Scale(1.1, 1.2, 1.3,
ATM_CreateTranslate(10, 20, 30)));
SELECT ATM_Rotate(15,
ATM_Scale(1.1, 1.2, 1.3,
ATM_Translate(10, 20, 30,
ATM_Create())));
</verbatim>
All three statements will return exactly the same identical Affine Transformation Matrix; anyway the second notation is obviously most concise and more practical than the other two.
</td></tr>
<table><br>
<hr><br>
<a href="https://www.gaia-gis.it/fossil/libspatialite/wiki?name=4.2.0-doc">back</a>
Z 87c714d44a32f1963a8fc65e32b758a3