Artifact [e9901177b5]
Not logged in

Artifact e9901177b5ce8b06bc412598df3503cbe3328686:

Wiki page [Affine Transform] by bradh 2015-05-28 11:39:00.
D 2015-05-28T11:39:00.902
L Affine\sTransform
P aee58dee92697b8165794d9de1c391854dede7d3
U bradh
W 41158
<a href="https://www.gaia-gis.it/fossil/libspatialite/wiki?name=4.2.0-doc">back</a>
<h2>Affine Transformations</h2>
Starting with 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>,<br>
Understanding and mastering all the underlying mathematics could easily be a rather difficult task, especially if you have absolutely no familiarity with this kind of operation.<br>
So we'll start slowly and simply by first introducing a very simple practical example based on a joke about the geography of Italy and Sicily.<br>
<h2>Fancy Geography and amusing Math</h2>
<table border="1" cellspacing="4" cellpadding="8">
<tr><td>
<h3>Preparing to start</h3>
<ol>
<li>download the <a href="https://www.gaia-gis.it/gaia-sins/affine-pics/italy.zip">sample dataset</a><br>
(it simply corresponds to a slightly modified vector map of Italy's Regions as originally released by <a href?"http://www.istat.it/it/archivio/104317">ISTAT</a> under CC-BY license terms).</li>
<li><i>unzip</i> the downloaded dataset and import the Shapefile into a SpatiaLite DB file.
The appropriate import settings are shown in the side figure:<br>
<i>SRID</i>=<b>32632</b>, <i>charset</i>=<b>CP1252</b>.</li>
<li>Extract Sicily into a separate database table by executing the following SQL statements:
<verbatim>
CREATE TABLE sicilia AS
SELECT * FROM italy WHERE cod_reg = 19;

SELECT RecoverGeometryColumn('sicilia', 'geometry', 32632, 'MULTIPOLYGON', 'XY');

DELETE FROM italy WHERE cod_reg = 19;
</verbatim>
</li>
<li>that's all: you are now ready to start this funny (and certainly not serious) tutorial about Affine Transformations.</li>
</ol>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/load-shp.png" alt="load-shp" border="1">
</td></tr>
<tr><td>
<h3>Preface</h3>
Look at a map of Italy; it appears obvious at first glance that Sicily is located in a very inconvenient position:
<ol>
<li>Sicily is placed too far South and too much close to North Africa; this implies an unpleasant torrid summer, and this later poses in turn many severe limitations to agricultural activities.</li>
<li>The <a href="http://en.wikipedia.org/wiki/Strait_of_Messina">Strait of Messina</a> is exaggeratedly narrow, thus posing severe safety risks  to the navigation of capital ships, big container ships, cruise liners and supertankers.<br>
Even worse, from time to time some crazy politician starts strongly advocating 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 seismic risk of this district and deliberately forgetting 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 120,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 theoretical 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="sicily-0" 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> to Sicily: this practically means adding (or subtracting) 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>) westward and <b>150 km</b> northward seems to be an absolutely reasonable choice.<br>
So using the Affine Transformation SQL functions we'll duly execute the following SQL statement:
<verbatim>
CREATE TABLE sicilia_1 AS
SELECT cod_reg, 
   ATM_Transform(geometry,
      ATM_CreateTranslate(-150000, 150000)) AS geom
FROM sicilia;

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 in 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 initialized in such a way 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="sicilia-1" border="1">
</td></tr>
<tr><td>
<h3>Step #2: rotating Sicily so to get a nice horizontal alignment</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(
         ATM_Translate(
            ATM_Rotate(
               ATM_CreateTranslate(-cx, -cy),
            25),
         cx, cy),
      -150000, 150000)
   ) 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) AS g1
) AS g2;

SELECT RecoverGeometryColumn('sicilia_2', 'geom', 32632, 'MULTIPOLYGON', 'XY');
</verbatim>  
<b>Remarks</b>:
<ul>
<li>correctly handling Rotate is a little bit more difficult.
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>.</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-Matrix</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 Rotate: <b>ATM_Rotate(atm-blob, 25)</b></li>
<li>after applying Rotate we have now to restore the initial position: <b>ATM_Translate(atm-blob, cx, cy)</b></li>
<li>and finally we'll apply the Translation intended to reposition Sicily westward and northward: <b>ATM_Translate(atm-blob, -150000, 150000)</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(atm-blob, cx - 150000, cy + 150000)</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 intended scope is simply to avoid multiple calls to 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="sicilia-2" 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(
         ATM_Translate(
            ATM_Scale(
               ATM_Rotate(
                  ATM_CreateTranslate(-cx, -cy),
               25),
            0.9, 1.3),
         cx, cy),
      -150000, 150000)
   ) 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) 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(atm-blob, 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(atm-blob, sx, sy, sz)</b></li>
</ul>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/sicily_3.png" alt="sicilia-3" 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 the final conclusions of the present study.<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_4 AS
SELECT cod_reg, 
   ATM_Transform(geometry,
      ATM_Translate(
         ATM_Translate(
            ATM_YRoll(
               ATM_Scale(
                  ATM_Rotate(
                     ATM_CreateTranslate(-cx, -cy),
                  25),
               0.9, 1.3),
            180),
         cx, cy),
      -150000, 150000)
   ) 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) 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()</b>, <b>ATM_YRoll()</b> and <b>ATM_ZRoll()</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="sicilia-4" border="1">
</td></tr>
<tr><td>
<h3>Final considerations and conclusions</h3>
<ol>
<li><b>Agriculture</b>: after applying the suggested relocation Sicily will gain an increased agricultural surface and will enjoy a more favourable climate: still sunny and warm but much less torrid and arid.
This will certainly sustain a noticeable development of many economic activities based on agri-food industries.</li>
<li><b>Transportation systems</b>: the suggested new layout for the Lower Tyrrhenian Sea strongly facilitates the development of maritime transports. Sicily could now become the central hub of an 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 Central and Northern Italy: Civitavecchia, Leghorn and Genoa are the obvious destinations.</li>
<li>A less relevant (but anyway interesting) ferry link will join Trapani and Reggio Calabria.</li>
<li>Sardinia as well 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 and more effective integration with Southern Italy.</li> 
<li>Last but not least: at a more strategic level it's absolutely obvious that now supertankers, big container ships and cruise liners can freely circumnavigate Sicily in any direction under uncompromised safety conditions.<br>
And consequently all Tyrrhenian harbors will now be directly connected both to Eastern and Western Mediterranean: this will surely induce a remarkable 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 flourishing traditional excellence of many southern regions in past times but nowadays a sadly declining activity.</li>
<li><b>Tourism</b>: a rearranged Lower Tyrrhenian Sea will certainly become a very attractive destination for international tourists.<br>
It's worth noting that the proposed layout will give birth to a wonderful island group extending between Sicily and southern Lazio - Campania; the Pontine, Aeolian and Gulf of Naples islands will be practically merged into a single archipelago.<br>
Several of these islands actually are active volcanoes, and the new island chain will directly join Mount Etna and Mount Vesuvius.<br>
So this area will represent the most impressive volcanic field of Europe and will certainly become a major tourist attraction thanks to its nicely sunny weather and pleasant climate.
</li>
<li><b>Internal commerce</b>: we can easily forecast a strong growth in volume of internal exchanges thanks 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 Calabrian markets whilst Calabria could freely export its renown red hot chilly peppers to Sardinia; not only both regions will widely benefit from increased exchange volumes, but Sicily as well will take profit from flourishing logistic and by other activities based on commercial intermediation services.</li>
<li><b>Practical realization</b>: the present study clearly demonstrates that there is nothing in Mathematics, Geometry or Geography forbidding the practical realization of the suggested idea.<br>
Unhappily the current state of the art in Geology still poses many puzzling problems not yet fully resolved; anyway we are hopefully expecting that future advancements in Tectonics will possibly allow to overcome any remaining issue.<br>
More specifically a better knowledge of deep interactions between the crust and the upper mantle along the Moho and a more detailed comprehension of the micro-plaques mechanics will certainly help; we are rather confident in future research achievements on these fields.
</ol>
</td><td>
<img src="https://www.gaia-gis.it/gaia-sins/affine-pics/italy-2.0.png" alt="italy-2.0" border="1">
</td></tr>
</table><br><hr>
<h2>Boring Math: a more formal presentation</h2>
Playtime is over: we'll now start a more serious explanation.<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>)&nbsp;&nbsp;&nbsp;</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>&theta;</b>)</td>
<td>/</td><td align="center"><b>cos(&theta;)</b></td><td align="center"><b>-sin(&theta;)</b></td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center"><b>sin(&theta;)</b></td><td align="center"><b>cos(&theta;)</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 an obvious direct relation between a 3D matrix and a 2D matrix; notice the cells showing a gray background.<br><br>
<table>
<tr><td rowspan="5" valign="middle">General layout&nbsp;&nbsp;&nbsp;</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>)&nbsp;&nbsp;&nbsp;</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>&theta;</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(&theta;)</b></td><td align="center"><b>-sin(&theta;)</b></td><td align="center">0</td><td>|</td></tr>
<tr><td>|</td><td align="center">0</td><td align="center"><b>sin(&theta;)</b></td><td align="center"><b>cos(&theta;)</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>&theta;</b>)</td>
<td>/</td><td align="center"><b>cos(&theta;)</b></td><td align="center">0</td><td align="center"><b>sin(&theta;)</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(&theta;)</b></td><td align="center">0</td><td align="center"><b>cos(&theta;)</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>&theta;</b>)</td>
<td>/</td><td align="center"><b>cos(&theta;)</b></td><td align="center"><b>-sin(&theta;)</b></td><td align="center">0</td><td align="center">0</td><td>\</td></tr>
<tr><td>|</td><td align="center"><b>sin(&theta;)</b></td><td align="center"><b>cos(&theta;)</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 see, 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 it only requires multiplications and additions.<br>
<br><hr>
<h3>chaining two (or even more) Affine Transformations in a single operation</h3>
Affine transformation matrices have another astonishing property.<br>
We can <a href="http://en.wikipedia.org/wiki/Matrix_multiplication">multiply</a> two different affine transformation matrices thus obtaining a third matrix, and this latest once applied will contain both transformations and in the right sequence.<br>
There is no limit; we can infinitely 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 identity 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>).
<h3>inverse matrix</h3>
If two (non-identity) matrices are such that their multiplication produces an identity matrix they are said to be one the inverse of the other.<br>
In simpler words both them cause the same identical trasformations but in exactly opposite ways, so that the final combination simply is a no-op.<br>
<u>Please note</u>: not all Affine Trasformation matrices necessarily have a corresponding inverse matrix.<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_Transform ( BLOB Geometry , BLOB AT-matrix , int srid ) : BLOB Geometry</td><td>Same as above, but the output Geometry will assume the explicitly set SRID.</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><th colspan="2" bgcolor="#F0E68C" align="center">basic SQL functions on Affine Transformation Matrix</th></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 returning the resulting Matrix.<br>
NULL will be returned on invalid arguments.</td></tr>
<tr><td><b>ATM_Determinant ( BLOB AT-matrix ) : double</td><td>Will return the <a href="http://en.wikipedia.org/wiki/Determinant">determinant</a> of the Matrix.<br>
NULL will be returned on invalid argument.</td></tr>
<tr><td><b>ATM_IsInvertible ( BLOB AT-matrix ) : boolean</td><td>Will return 1 <b>TRUE</b> if the Affine Transformation matrix can be inverted, 0 <b>FALSE</b> if not (<i>only matrices presenting a determinant different from zero can be inverted</i>).<br>
NULL will be returned on invalid argument.</td></tr>
<tr><td><b>ATM_Invert ( BLOB AT-matrix ) : BLOB AT-matrix</td><td>Will return the <a href="http://en.wikipedia.org/wiki/Invertible_matrix">inverse</a> matrix.<br>
NULL will be returned on invalid argument.</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 explicit 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 explicit 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 angleInDegrees ) : BLOB AT-matrix</b><br>
<b>ATM_CreateZRoll ( double angleInDegrees ) : 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 measured 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 angleInDegrees ) : 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 angleInDegrees ) : 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 ( BLOB AT-matrix , double tx , double ty ) : BLOB AT-matrix</b><br>
<b>ATM_Translate ( BLOB AT-matrix , double tx , double ty , double tz ) : 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 ( BLOB AT-matrix , double sx , double sy ) : BLOB AT-matrix</b><br>
<b>ATM_Scale ( BLOB AT-matrix , double sx , double sy , double sz ) : 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 ( BLOB AT-matrix , double angleInDegrees ) : BLOB AT-matrix</b><br>
<b>ATM_ZRoll ( BLOB AT-matrix , double angleInDegrees ) : 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 ( BLOB AT-matrix , double angleInDegrees ) : 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 ( BLOB AT-matrix , double angleInDegrees ) : 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(
          ATM_Scale(
             ATM_CreateTranslate(10, 20, 30), 
          1.1, 1.2, 1.3), 
       15);

SELECT ATM_Rotate(
          ATM_Scale(
             ATM_Translate(
                ATM_Create(), 
             10, 20, 30),
          1.1, 1.2, 1.3),
       15);
</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.<br>
<u>Note</u>: in any complex chain of transformations the <b>innermost</b> operation will be applied <b>first</b>, and the <b>outermost</b> operation will be applied <b>last</b>.
</td></tr>
<table><br><br>
<b>Note</b>: the SQL interface supporting Affine Trasformations is designed in such a way that any complexity and difficulty required in order to directly handle matricial operations is completely hidden.<br>
You are simply required to chain several elementary transformations, each one of them basically simple, in the correct sequence and that's absolutely all.<br>
<b>ATM</b> SQL functions will <i><u>magically and silently</u></i> keep care of any mathematical complexity.<br><br>  
<hr>
<h3>Conclusion</h3>
<ul>
<li>Affine Transformations are a very powerful and very flexible mathematical instrumentation, and their practical utility is noticeably relevant in many applied branches of computing, for example.:</li>
<ul>
<li>advanced 2D / 3D graphics</li>
<li>videogames</li>
<li>CAD</li>
<li>artificial vision / enhanced reality</li>
<li>robotics</li>
</ul></li>
<li>in the specific GeoSpatial field the most interesting application for affine transformations is that they can effectively transform the coordinates from one Reference System to another preserving a very high precision.<br>
The unique prerequisite to be fulfilled simply is determining in advance the appropriates values for the appropriate transformation matrix.
This absolutely isn't a trivial task, and a very high accuracy is absolutely required so to get high quality results.<br>
Happily enough we can easily deploy few further mathematical and statistical methods allowing to resolve this kind of problem in a rather painless way: please read <a href="https://www.gaia-gis.it/fossil/libspatialite/wiki?name=Ground+Control+Points">this wiki page</a> in order to learn more about all these topics.
</ul>
<br><br>
<hr><br>
<a href="https://www.gaia-gis.it/fossil/libspatialite/wiki?name=4.2.0-doc">back</a>
Z 3ae196a231ca60f6cdedd243b386cfe1