MinMaxTreeTile.java

  1. /* Copyright 2013-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.rugged.intersection.duvenhage;

  18. import org.hipparchus.util.FastMath;
  19. import org.orekit.rugged.errors.DumpManager;
  20. import org.orekit.rugged.raster.SimpleTile;
  21. import org.orekit.rugged.utils.MaxSelector;
  22. import org.orekit.rugged.utils.MinSelector;
  23. import org.orekit.rugged.utils.Selector;

  24. /** Implementation of a {@link org.orekit.rugged.raster.Tile} with a min/max kd tree.
  25.  * <p>
  26.  * A n level min/max kd-tree contains sub-tiles merging individual cells
  27.  * together from coarse-grained (at level 0) to fine-grained (at level n-1).
  28.  * Level n-1, which is the deepest one, is computed from the raw cells by
  29.  * merging adjacent cells pairs columns (i.e. cells at indices (i, 2j)
  30.  * and (i, 2j+1) are merged together by computing and storing the minimum
  31.  * and maximum in a sub-tile. Level n-1 therefore has the same number of rows
  32.  * but half the number of columns of the raw tile, and its sub-tiles are
  33.  * 1 cell high and 2 cells wide. Level n-2 is computed from level n-1 by
  34.  * merging sub-tiles rows. Level n-2 therefore has half the number of rows
  35.  * and half the number of columns of the raw tile, and its sub-tiles are
  36.  * 2 cells high and 2 cells wide. Level n-3 is again computed by merging
  37.  * columns, level n-4 merging rows and so on. As depth decreases, the number
  38.  * of sub-tiles decreases and their size increase. Level 0 is reached when
  39.  * there is only either one row or one column of large sub-tiles.
  40.  * </p>
  41.  * <p>
  42.  * During the merging process, if at some stage there is an odd number of
  43.  * rows or columns, then the last sub-tile at next level will not be computed
  44.  * by merging two rows/columns from the current level, but instead computed by
  45.  * simply copying the last single row/column. The process is therefore well
  46.  * defined for any raw tile initial dimensions. A direct consequence is that
  47.  * the dimension of the sub-tiles in the last row or column may be smaller than
  48.  * the dimension of regular sub-tiles.
  49.  * </p>
  50.  * <p>
  51.  * If we consider for example a tall 107 ⨉ 19 raw tile, the min/max kd-tree will
  52.  * have 9 levels:
  53.  * </p>
  54.  *
  55.  * <table summary="" border="0">
  56.  * <tr style="background-color:#EEEEFF;">
  57.  *             <td>Level</td>         <td>Number of sub-tiles</td>    <td>Regular sub-tiles dimension</td></tr>
  58.  * <tr>  <td align="center">8</td>  <td align="center">107 ⨉ 10</td>       <td align="center"> 1 ⨉   2</td>
  59.  * <tr>  <td align="center">7</td>  <td align="center"> 54 ⨉ 10</td>       <td align="center"> 2 ⨉   2</td>
  60.  * <tr>  <td align="center">6</td>  <td align="center"> 54 ⨉  5</td>        <td align="center"> 2 ⨉  4</td>
  61.  * <tr>  <td align="center">5</td>  <td align="center"> 27 ⨉  5</td>        <td align="center"> 4 ⨉  4</td>
  62.  * <tr>  <td align="center">4</td>  <td align="center"> 27 ⨉  3</td>        <td align="center"> 4 ⨉  8</td>
  63.  * <tr>  <td align="center">3</td>  <td align="center"> 14 ⨉  3</td>        <td align="center"> 8 ⨉  8</td>
  64.  * <tr>  <td align="center">2</td>  <td align="center"> 14 ⨉  2</td>        <td align="center"> 8 ⨉ 16</td>
  65.  * <tr>  <td align="center">1</td>  <td align="center">  7 ⨉  2</td>        <td align="center">16 ⨉ 16</td>
  66.  * <tr>  <td align="center">0</td>  <td align="center">  7 ⨉  1</td>        <td align="center">16 ⨉ 32</td>
  67.  * </table>
  68.  * <p>
  69.  * @see MinMaxTreeTileFactory
  70.  * @author Luc Maisonobe
  71.  */
  72. public class MinMaxTreeTile extends SimpleTile {

  73.     /** Raw elevations. */
  74.     private double[] raw;

  75.     /** Min kd-tree. */
  76.     private double[] minTree;

  77.     /** Max kd-tree. */
  78.     private double[] maxTree;

  79.     /** Start indices of tree levels. */
  80.     private int[] start;

  81.     /** Simple constructor.
  82.      * <p>
  83.      * Creates an empty tile.
  84.      * </p>
  85.      */
  86.     MinMaxTreeTile() {
  87.     }

  88.     /** {@inheritDoc} */
  89.     @Override
  90.     protected void processUpdatedElevation(final double[] elevations) {

  91.         raw = elevations;

  92.         final int nbRows = getLatitudeRows();
  93.         final int nbCols = getLongitudeColumns();

  94.         // set up the levels
  95.         final int size = setLevels(0, nbRows, nbCols);
  96.         minTree = new double[size];
  97.         maxTree = new double[size];

  98.         // compute min/max trees
  99.         if (start.length > 0) {

  100.             final double[] preprocessed = new double[raw.length];

  101.             preprocess(preprocessed, raw, nbRows, nbCols, MinSelector.getInstance());
  102.             applyRecursively(minTree, start.length - 1, nbRows, nbCols, MinSelector.getInstance(), preprocessed, 0);

  103.             preprocess(preprocessed, raw, nbRows, nbCols, MaxSelector.getInstance());
  104.             applyRecursively(maxTree, start.length - 1, nbRows, nbCols, MaxSelector.getInstance(), preprocessed, 0);

  105.         }

  106.     }

  107.     /** Get the number of kd-tree levels (not counting raw elevations).
  108.      * @return number of kd-tree levels
  109.      * @see #getMinElevation(int, int, int)
  110.      * @see #getMaxElevation(int, int, int)
  111.      * @see #getMergeLevel(int, int, int, int)
  112.      */
  113.     public int getLevels() {
  114.         return start.length;
  115.     }

  116.     /** Get the minimum elevation at some level tree.
  117.      * <p>
  118.      * Note that the min elevation is <em>not</em> computed
  119.      * only at cell center, but considering that it is interpolated
  120.      * considering also Eastwards and Northwards neighbors, and extends
  121.      * up to the center of these neighbors. As an example, lets consider
  122.      * four neighboring cells in some Digital Elevation Model:
  123.      * <table summary="" border="0" cellpadding="5" style="background-color:#f5f5dc;">
  124.      * <tr><th style="background-color:#c9d5c9;">j+1</th><td>11</td><td>10</td></tr>
  125.      * <tr><th style="background-color:#c9d5c9;">j</th><td>12</td><td>11</td></tr>
  126.      * <tr  style="background-color:#c9d5c9;"><th>j/i</th><th>i</th><th>i+1</th></tr>
  127.      * </table>
  128.      * When we interpolate elevation at a point located slightly South-West
  129.      * to the center of the (i+1, j+1) cell, we use all four cells in the
  130.      * interpolation, and we will get a result very close to 10 if we start
  131.      * close to (i+1, j+1) cell center. As the min value for this interpolation
  132.      * is stored at (i, j) indices, this implies that {@code getMinElevation(i,
  133.      * j, l)} must return 10 if l is chosen such that the sub-tile at
  134.      * tree level l includes cell (i,j) but not cell (i+1, j+1). In other words,
  135.      * interpolation implies sub-tile boundaries are overshoot by one column to
  136.      * the East and one row to the North when computing min.
  137.      *
  138.      * @param i row index of the cell
  139.      * @param j column index of the cell
  140.      * @param level tree level
  141.      * @return minimum value that can be reached when interpolating elevation
  142.      * in the sub-tile
  143.      * @see #getLevels()
  144.      * @see #getMaxElevation(int, int, int)
  145.      * @see #getMergeLevel(int, int, int, int)
  146.      */
  147.     public double getMinElevation(final int i, final int j, final int level) {

  148.         // compute indices in level merged array
  149.         final int k        = start.length - level;
  150.         final int rowShift = k / 2;
  151.         final int colShift = (k + 1) / 2;
  152.         final int levelI   = i >> rowShift;
  153.         final int levelJ   = j >> colShift;
  154.         final int levelC   = 1 + ((getLongitudeColumns() - 1) >> colShift);

  155.         if (DumpManager.isActive()) {
  156.             final int[] min = locateMin(i, j, level);
  157.             final int index = min[0] * getLongitudeColumns() + min[1];
  158.             DumpManager.dumpTileCell(this, min[0],     min[1],     raw[index]);
  159.             if (index + getLongitudeColumns() < raw.length) {
  160.                 DumpManager.dumpTileCell(this, min[0] + 1, min[1],     raw[index + getLongitudeColumns()]);
  161.             }
  162.             if (index + 1 < raw.length) {
  163.                 DumpManager.dumpTileCell(this, min[0],     min[1] + 1, raw[index + 1]);
  164.             }
  165.             if (index + getLongitudeColumns() + 1 < raw.length) {
  166.                 DumpManager.dumpTileCell(this, min[0] + 1, min[1] + 1, raw[index + getLongitudeColumns() + 1]);
  167.             }
  168.         }

  169.         return minTree[start[level] + levelI * levelC + levelJ];

  170.     }

  171.     /** Get the maximum elevation at some level tree.
  172.      * <p>
  173.      * Note that the max elevation is <em>not</em> computed
  174.      * only at cell center, but considering that it is interpolated
  175.      * considering also Eastwards and Northwards neighbors, and extends
  176.      * up to the center of these neighbors. As an example, lets consider
  177.      * four neighboring cells in some Digital Elevation Model:
  178.      * <table summary="" border="0" cellpadding="5" style="background-color:#f5f5dc;">
  179.      * <tr><th style="background-color:#c9d5c9;">j+1</th><td>11</td><td>12</td></tr>
  180.      * <tr><th style="background-color:#c9d5c9;">j</th><td>10</td><td>11</td></tr>
  181.      * <tr  style="background-color:#c9d5c9;"><th>j/i</th><th>i</th><th>i+1</th></tr>
  182.      * </table>
  183.      * When we interpolate elevation at a point located slightly South-West
  184.      * to the center of the (i+1, j+1) cell, we use all four cells in the
  185.      * interpolation, and we will get a result very close to 12 if we start
  186.      * close to (i+1, j+1) cell center. As the max value for this interpolation
  187.      * is stored at (i, j) indices, this implies that {@code getMaxElevation(i,
  188.      * j, l)} must return 12 if l is chosen such that the sub-tile at
  189.      * tree level l includes cell (i,j) but not cell (i+1, j+1). In other words,
  190.      * interpolation implies sub-tile boundaries are overshoot by one column to
  191.      * the East and one row to the North when computing max.
  192.      *
  193.      * @param i row index of the cell
  194.      * @param j column index of the cell
  195.      * @param level tree level
  196.      * @return maximum value that can be reached when interpolating elevation
  197.      * in the sub-tile
  198.      * @see #getLevels()
  199.      * @see #getMinElevation(int, int, int)
  200.      * @see #getMergeLevel(int, int, int, int)
  201.      */
  202.     public double getMaxElevation(final int i, final int j, final int level) {

  203.         // compute indices in level merged array
  204.         final int k        = start.length - level;
  205.         final int rowShift = k / 2;
  206.         final int colShift = (k + 1) / 2;
  207.         final int levelI   = i >> rowShift;
  208.         final int levelJ   = j >> colShift;
  209.         final int levelC   = 1 + ((getLongitudeColumns() - 1) >> colShift);

  210.         if (DumpManager.isActive()) {
  211.             final int[] max = locateMax(i, j, level);
  212.             final int index = max[0] * getLongitudeColumns() + max[1];
  213.             DumpManager.dumpTileCell(this, max[0],     max[1],     raw[index]);
  214.             if (index + getLongitudeColumns() < raw.length) {
  215.                 DumpManager.dumpTileCell(this, max[0] + 1, max[1],     raw[index + getLongitudeColumns()]);
  216.             }
  217.             if (index + 1 < raw.length) {
  218.                 DumpManager.dumpTileCell(this, max[0],     max[1] + 1, raw[index + 1]);
  219.             }
  220.             if (index + getLongitudeColumns() + 1 < raw.length) {
  221.                 DumpManager.dumpTileCell(this, max[0] + 1, max[1] + 1, raw[index + getLongitudeColumns() + 1]);
  222.             }
  223.         }

  224.         return maxTree[start[level] + levelI * levelC + levelJ];

  225.     }

  226.     /** Locate the cell at which min elevation is reached for a specified level.
  227.      * <p>
  228.      * Min is computed with respect to the continuous interpolated elevation, which
  229.      * takes four neighboring cells into account. This implies that the cell at which
  230.      * min value is reached for some level is either within the sub-tile for this level,
  231.      * or in some case it may be one column outside to the East or one row outside to
  232.      * the North. See {@link #getMinElevation()} for a more complete explanation.
  233.      * </p>
  234.      * @param i row index of the cell
  235.      * @param j column index of the cell
  236.      * @param level tree level of the sub-tile considered
  237.      * @return row/column indices of the cell at which min elevation is reached
  238.      */
  239.     public int[] locateMin(final int i, final int j, final int level) {
  240.         return locateMinMax(i, j, level, MinSelector.getInstance(), minTree);
  241.     }

  242.     /** Locate the cell at which max elevation is reached for a specified level.
  243.      * <p>
  244.      * Max is computed with respect to the continuous interpolated elevation, which
  245.      * takes four neighboring cells into account. This implies that the cell at which
  246.      * max value is reached for some level is either within the sub-tile for this level,
  247.      * or in some case it may be one column outside to the East or one row outside to
  248.      * the North. See {@link #getMaxElevation()} for a more complete explanation.
  249.      * </p>
  250.      * @param i row index of the cell
  251.      * @param j column index of the cell
  252.      * @param level tree level of the sub-tile considered
  253.      * @return row/column indices of the cell at which min elevation is reached
  254.      */
  255.     public int[] locateMax(final int i, final int j, final int level) {
  256.         return locateMinMax(i, j, level, MaxSelector.getInstance(), maxTree);
  257.     }

  258.     /** Locate the cell at which min/max elevation is reached for a specified level.
  259.      * @param i row index of the cell
  260.      * @param j column index of the cell
  261.      * @param level tree level of the sub-tile considered
  262.      * @param selector min/max selector to use
  263.      * @param tree min/max tree to use
  264.      * @return row/column indices of the cell at which min/max elevation is reached
  265.      */
  266.     private int[] locateMinMax(final int i, final int j, final int level,
  267.                                final Selector selector, final double[] tree) {

  268.         final int k  = start.length - level;
  269.         int rowShift = k / 2;
  270.         int colShift = (k + 1) / 2;
  271.         int levelI   = i >> rowShift;
  272.         int levelJ   = j >> colShift;
  273.         int levelR   = 1 + ((getLatitudeRows()     - 1) >> rowShift);
  274.         int levelC   = 1 + ((getLongitudeColumns() - 1) >> colShift);

  275.         // track the cell ancestors from merged tree at specified level up to tree at level 1
  276.         for (int l = level + 1; l < start.length; ++l) {

  277.             if (isColumnMerging(l)) {

  278.                 --colShift;
  279.                 levelC = 1 + ((getLongitudeColumns() - 1) >> colShift);
  280.                 levelJ = levelJ << 1;

  281.                 if (levelJ + 1 < levelC) {
  282.                     // the cell results from a regular merging of two columns
  283.                     if (selector.selectFirst(tree[start[l] + levelI * levelC + levelJ + 1],
  284.                                              tree[start[l] + levelI * levelC + levelJ])) {
  285.                         levelJ++;
  286.                     }
  287.                 }

  288.             } else {

  289.                 --rowShift;
  290.                 levelR = 1 + ((getLatitudeRows() - 1) >> rowShift);
  291.                 levelI = levelI << 1;

  292.                 if (levelI + 1 < levelR) {
  293.                     // the cell results from a regular merging of two rows
  294.                     if (selector.selectFirst(tree[start[l] + (levelI + 1) * levelC + levelJ],
  295.                                              tree[start[l] + levelI       * levelC + levelJ])) {
  296.                         levelI++;
  297.                     }
  298.                 }

  299.             }

  300.         }

  301.         // we are now at first merge level, which always results from a column merge
  302.         // or pre-processed data, which themselves result from merging four cells
  303.         // used in interpolation
  304.         // this imply the ancestor of min/max at (n, m) is one of
  305.         // (2n, m), (2n+1, m), (2n+2, m), (2n, m+1), (2n+1, m+1), (2n+2, m+1)
  306.         int selectedI = levelI;
  307.         int selectedJ = 2 * levelJ;
  308.         double selectedElevation = Double.NaN;
  309.         for (int n = 2 * levelJ; n < 2 * levelJ + 3; ++n) {
  310.             if (n < getLongitudeColumns()) {
  311.                 for (int m = levelI; m < levelI + 2; ++m) {
  312.                     if (m < getLatitudeRows()) {
  313.                         final double elevation = raw[m * getLongitudeColumns() + n];
  314.                         if (selector.selectFirst(elevation, selectedElevation)) {
  315.                             selectedI         = m;
  316.                             selectedJ         = n;
  317.                             selectedElevation = elevation;
  318.                         }
  319.                     }
  320.                 }
  321.             }
  322.         }

  323.         return new int[] {
  324.             selectedI, selectedJ
  325.         };

  326.     }

  327.     /** Get the deepest level at which two cells are merged in the same min/max sub-tile.
  328.      * @param i1 row index of first cell
  329.      * @param j1 column index of first cell
  330.      * @param i2 row index of second cell
  331.      * @param j2 column index of second cell
  332.      * @return deepest level at which two cells are merged in the same min/max sub-tile,
  333.      * or -1 if they are never merged in the same sub-tile
  334.      * @see #getLevels()
  335.      * @see #getMinElevation(int, int, int)
  336.      * @see #getMaxElevation(int, int, int)
  337.      */
  338.     public int getMergeLevel(final int i1, final int j1, final int i2, final int j2) {

  339.         int largest = -1;

  340.         for (int level = 0; level < start.length; ++level) {
  341.             // compute indices in level merged array
  342.             final int k        = start.length - level;
  343.             final int rowShift = k / 2;
  344.             final int colShift = (k + 1) / 2;
  345.             final int levelI1  = i1 >> rowShift;
  346.             final int levelJ1  = j1 >> colShift;
  347.             final int levelI2  = i2 >> rowShift;
  348.             final int levelJ2  = j2 >> colShift;
  349.             if (levelI1 != levelI2 || levelJ1 != levelJ2) {
  350.                 return largest;
  351.             }
  352.             largest = level;
  353.         }

  354.         return largest;

  355.     }

  356.     /** Get the index of sub-tiles start rows crossed.
  357.      * <p>
  358.      * When going from one row to another row at some tree level,
  359.      * we cross sub-tiles boundaries. This method returns the index
  360.      * of these boundaries.
  361.      * </p>
  362.      * @param row1 starting row
  363.      * @param row2 ending row
  364.      * @param level tree level
  365.      * @return indices of rows crossed at sub-tiles boundaries, in crossing order,
  366.      * the endpoints <em>are</em> included (i.e. if {@code row1} or {@code row2} are
  367.      * boundary rows, they will be in returned array)
  368.      */
  369.     public int[] getCrossedBoundaryRows(final int row1, final int row2, final int level) {

  370.         // number of rows in each sub-tile
  371.         final int rows = 1 << ((start.length - level) / 2);

  372.         // build the crossings in ascending order
  373.         final int min = FastMath.min(row1, row2);
  374.         final int max = FastMath.max(row1, row2) + 1;
  375.         return buildCrossings((min + rows - 1) - ((min + rows - 1) % rows), max, rows,
  376.                               row1 <= row2);

  377.     }

  378.     /** Get the index of sub-tiles start columns crossed.
  379.      * <p>
  380.      * When going from one column to another column at some tree level,
  381.      * we cross sub-tiles boundaries. This method returns the index
  382.      * of these boundaries.
  383.      * </p>
  384.      * @param column1 starting column
  385.      * @param column2 ending column (excluded)
  386.      * @param level tree level
  387.      * @return indices of columns crossed at sub-tiles boundaries, in crossing order,
  388.      * the endpoints <em>are</em> included (i.e. if {@code column1} or {@code column2} are
  389.      * boundary columns, they will be in returned array)
  390.      */
  391.     public int[] getCrossedBoundaryColumns(final int column1, final int column2, final int level) {

  392.         // number of columns in each sub-tile
  393.         final int columns  = 1 << ((start.length + 1 - level) / 2);

  394.         // build the crossings in ascending order
  395.         final int min = FastMath.min(column1, column2);
  396.         final int max = FastMath.max(column1, column2) + 1;
  397.         return buildCrossings((min + columns - 1) - ((min + columns - 1) % columns), max, columns,
  398.                               column1 <= column2);

  399.     }

  400.     /** Build crossings arrays.
  401.      * @param begin begin crossing index
  402.      * @param end end crossing index (excluded, if equal to begin, the array is empty)
  403.      * @param step crossing step
  404.      * @param ascending if true, the crossings must be in ascending order
  405.      * @return indices of rows or columns crossed at sub-tiles boundaries, in crossing order
  406.      */
  407.     private int[] buildCrossings(final int begin, final int end, final int step, final boolean ascending) {

  408.         // allocate array
  409.         final int n = FastMath.max(0, (end - begin + step + ((step > 0) ? -1 : +1)) / step);
  410.         final int[] crossings = new int[n];

  411.         // fill it up
  412.         int crossing = begin;
  413.         if (ascending) {
  414.             for (int i = 0; i < crossings.length; ++i) {
  415.                 crossings[i] = crossing;
  416.                 crossing += step;
  417.             }
  418.         } else {
  419.             for (int i = 0; i < crossings.length; ++i) {
  420.                 crossings[crossings.length - 1 - i] = crossing;
  421.                 crossing += step;
  422.             }
  423.         }

  424.         return crossings;

  425.     }

  426.     /** Check if the merging operation between level and level-1 is a column merging.
  427.      * @param level level to check
  428.      * @return true if the merging operation between level and level-1 is a column
  429.      * merging, false if is a row merging
  430.      */
  431.     public boolean isColumnMerging(final int level) {
  432.         return (level & 0x1) == (start.length & 0x1);
  433.     }

  434.     /** Recursive setting of tree levels.
  435.      * <p>
  436.      * The following algorithms works for any array shape, even with
  437.      * rows or columns which are not powers of 2 or with one
  438.      * dimension much larger than the other. As an example, starting
  439.      * from a 107 ⨉ 19 array, we get the following 9 levels, for a
  440.      * total of 2187 elements in each tree:
  441.      * </p>
  442.      * <p>
  443.      * <table border="0">
  444.      * <tr BGCOLOR="#EEEEFF">
  445.      *     <td>Level</td>   <td>Dimension</td>  <td>Start index</td>  <td>End index (inclusive)</td></tr>
  446.      * <tr>   <td>0</td>     <td>  7 ⨉  1</td>       <td>   0</td>        <td>  6</td> </tr>
  447.      * <tr>   <td>1</td>     <td>  7 ⨉  2</td>       <td>   7</td>        <td> 20</td> </tr>
  448.      * <tr>   <td>2</td>     <td> 14 ⨉  2</td>       <td>  21</td>        <td> 48</td> </tr>
  449.      * <tr>   <td>3</td>     <td> 14 ⨉  3</td>       <td>  49</td>        <td> 90</td> </tr>
  450.      * <tr>   <td>4</td>     <td> 27 ⨉  3</td>       <td>  91</td>        <td>171</td> </tr>
  451.      * <tr>   <td>5</td>     <td> 27 ⨉  5</td>       <td> 172</td>        <td>306</td> </tr>
  452.      * <tr>   <td>6</td>     <td> 54 ⨉  5</td>       <td> 307</td>        <td>576</td> </tr>
  453.      * <tr>   <td>7</td>     <td> 54 ⨉ 10</td>      <td> 577</td>        <td>1116</td> </tr>
  454.      * <tr>   <td>8</td>     <td>107 ⨉ 10</td>      <td>1117</td>        <td>2186</td> </tr>
  455.      * </table>
  456.      * </p>
  457.      * @param stage number of merging stages
  458.      * @param stageRows number of rows at current stage
  459.      * @param stageColumns number of columns at current stage
  460.      * @return size cumulative size from root to current level
  461.      */
  462.     private int setLevels(final int stage, final int stageRows, final int stageColumns) {

  463.         if (stageRows == 1 || stageColumns == 1) {
  464.             // we have found root, stop recursion
  465.             start   = new int[stage];
  466.             if (stage > 0) {
  467.                 start[0]   = 0;
  468.             }
  469.             return stageRows * stageColumns;
  470.         }

  471.         final int size;
  472.         if ((stage & 0x1) == 0) {
  473.             // columns merging
  474.             size = setLevels(stage + 1, stageRows, (stageColumns + 1) / 2);
  475.         } else {
  476.             // rows merging
  477.             size = setLevels(stage + 1, (stageRows + 1) / 2, stageColumns);
  478.         }

  479.         if (stage > 0) {
  480.             // store current level characteristics
  481.             start[start.length     - stage] = size;
  482.             return size + stageRows * stageColumns;
  483.         } else {
  484.             // we don't count the elements at stage 0 as they are not stored in the
  485.             // min/max trees (they correspond to the raw elevation, without merging)
  486.             return size;
  487.         }

  488.     }

  489.     /** Preprocess recursive application of a function.
  490.      * <p>
  491.      * At start, the min/max should be computed for each cell using the four corners values.
  492.      * </p>
  493.      * @param preprocessed preprocessed array to fill up
  494.      * @param elevations raw elevations te preprocess
  495.      * @param nbRows number of rows
  496.      * @param nbCols number of columns
  497.      * @param selector selector to use
  498.      */
  499.     private void preprocess(final double[] preprocessed, final double[] elevations,
  500.                             final int nbRows, final int nbCols,
  501.                             final Selector selector) {

  502.         int k = 0;

  503.         for (int i = 0; i < nbRows - 1; ++i) {

  504.             // regular elements with both a column at right and a row below
  505.             for (int j = 0; j < nbCols - 1; ++j) {
  506.                 preprocessed[k] = selector.select(selector.select(elevations[k],          elevations[k + 1]),
  507.                                                   selector.select(elevations[k + nbCols], elevations[k + nbCols + 1]));
  508.                 k++;
  509.             }

  510.             // last column elements, lacking a right column
  511.             preprocessed[k] = selector.select(elevations[k], elevations[k + nbCols]);
  512.             k++;

  513.         }

  514.         // last row elements, lacking a below row
  515.         for (int j = 0; j < nbCols - 1; ++j) {
  516.             preprocessed[k] = selector.select(elevations[k], elevations[k + 1]);
  517.             k++;
  518.         }

  519.         // last element
  520.         preprocessed[k] = elevations[k];

  521.     }

  522.     /** Recursive application of a function.
  523.      * @param tree tree to fill-up with the recursive applications
  524.      * @param level current level
  525.      * @param levelRows number of rows at current level
  526.      * @param levelColumns number of columns at current level
  527.      * @param selector to apply
  528.      * @param base base array from which function arguments are drawn
  529.      * @param first index of the first element to consider in base array
  530.      */
  531.     private void applyRecursively(final double[] tree,
  532.                                   final int level, final int levelRows, final int levelColumns,
  533.                                   final Selector selector,
  534.                                   final double[] base, final int first) {

  535.         if (isColumnMerging(level + 1)) {

  536.             // merge columns pairs
  537.             int           iTree       = start[level];
  538.             int           iBase       = first;
  539.             final int     nextColumns = (levelColumns + 1) / 2;
  540.             final boolean odd         = (levelColumns & 0x1) != 0;
  541.             final int     jEnd        = odd ? nextColumns - 1 : nextColumns;
  542.             for (int i = 0; i < levelRows; ++i) {

  543.                 // regular pairs
  544.                 for (int j = 0; j < jEnd; ++j) {
  545.                     tree[iTree++] = selector.select(base[iBase], base[iBase + 1]);
  546.                     iBase += 2;
  547.                 }

  548.                 if (odd) {
  549.                     // last column
  550.                     tree[iTree++] = base[iBase++];
  551.                 }


  552.             }

  553.             if (level > 0) {
  554.                 applyRecursively(tree, level - 1, levelRows, nextColumns, selector, tree, start[level]);
  555.             }

  556.         } else {

  557.             // merge rows pairs
  558.             int           iTree    = start[level];
  559.             int           iBase    = first;
  560.             final int     nextRows = (levelRows + 1) / 2;
  561.             final boolean odd      = (levelRows & 0x1) != 0;
  562.             final int     iEnd     = odd ? nextRows - 1 : nextRows;

  563.             // regular pairs
  564.             for (int i = 0; i < iEnd; ++i) {

  565.                 for (int j = 0; j < levelColumns; ++j) {
  566.                     tree[iTree++] = selector.select(base[iBase], base[iBase + levelColumns]);
  567.                     iBase++;
  568.                 }
  569.                 iBase += levelColumns;

  570.             }

  571.             if (odd) {
  572.                 // last row
  573.                 System.arraycopy(base, iBase, tree, iTree, levelColumns);
  574.             }

  575.             if (level > 0) {
  576.                 applyRecursively(tree, level - 1, nextRows, levelColumns, selector, tree, start[level]);
  577.             }

  578.         }
  579.     }

  580. }