MinMaxTreeTile.java

  1. /* Copyright 2013-2025 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>
  56.  *  <caption>"min/max kd-tree levels for a 107 x19 raw tile "</caption>
  57.  *   <thead>
  58.  *       <tr>
  59.  *           <th>Level</th>
  60.  *           <th>Number of sub-tiles</th>
  61.  *           <th>Regular sub-tiles dimension</th>
  62.  *       </tr>
  63.  *   </thead>
  64.  *   <tbody>
  65.  *       <tr>
  66.  *           <td>8</td>
  67.  *           <td>107 ⨉ 10</td>
  68.  *           <td>1 ⨉ 2</td>
  69.  *       </tr>
  70.  *       <tr>
  71.  *           <td>7</td>
  72.  *           <td>54 ⨉ 10</td>
  73.  *           <td>2 ⨉ 2</td>
  74.  *       </tr>
  75.  *       <tr>
  76.  *           <td>6</td>
  77.  *           <td>54 ⨉ 5</td>
  78.  *           <td>2 ⨉ 4</td>
  79.  *       </tr>
  80.  *       <tr>
  81.  *           <td>5</td>
  82.  *           <td>27 ⨉ 5</td>
  83.  *           <td>4 ⨉ 4</td>
  84.  *       </tr>
  85.  *       <tr>
  86.  *           <td>4</td>
  87.  *           <td>27 ⨉ 3</td>
  88.  *           <td>4 ⨉ 8</td>
  89.  *       </tr>
  90.  *       <tr>
  91.  *           <td>3</td>
  92.  *           <td>14 ⨉ 3</td>
  93.  *           <td>8 ⨉ 8</td>
  94.  *       </tr>
  95.  *       <tr>
  96.  *           <td>2</td>
  97.  *           <td>14 ⨉ 2</td>
  98.  *           <td>8 ⨉ 16</td>
  99.  *       </tr>
  100.  *       <tr>
  101.  *           <td>1</td>
  102.  *           <td>7 ⨉ 2</td>
  103.  *           <td>16 ⨉ 16</td>
  104.  *       </tr>
  105.  *       <tr>
  106.  *           <td>0</td>
  107.  *           <td>7 ⨉ 1</td>
  108.  *           <td>16 ⨉ 32</td>
  109.  *       </tr>
  110.  *   </tbody>
  111.  *</table>
  112.  *
  113.  * @see MinMaxTreeTileFactory
  114.  * @author Luc Maisonobe
  115.  */
  116. public class MinMaxTreeTile extends SimpleTile {

  117.     /** Raw elevations. */
  118.     private double[] raw;

  119.     /** Min kd-tree. */
  120.     private double[] minTree;

  121.     /** Max kd-tree. */
  122.     private double[] maxTree;

  123.     /** Start indices of tree levels. */
  124.     private int[] start;

  125.     /** Simple constructor.
  126.      * <p>
  127.      * Creates an empty tile.
  128.      * </p>
  129.      */
  130.     protected MinMaxTreeTile() {
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     protected void processUpdatedElevation(final double[] elevations) {

  135.         raw = elevations;

  136.         final int nbRows = getLatitudeRows();
  137.         final int nbCols = getLongitudeColumns();

  138.         // set up the levels
  139.         final int size = setLevels(0, nbRows, nbCols);
  140.         minTree = new double[size];
  141.         maxTree = new double[size];

  142.         // compute min/max trees
  143.         if (start.length > 0) {

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

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

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

  149.         }

  150.     }

  151.     /** Get the number of kd-tree levels (not counting raw elevations).
  152.      * @return number of kd-tree levels
  153.      * @see #getMinElevation(int, int, int)
  154.      * @see #getMaxElevation(int, int, int)
  155.      * @see #getMergeLevel(int, int, int, int)
  156.      */
  157.     public int getLevels() {
  158.         return start.length;
  159.     }

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

  198.         // compute indices in level merged array
  199.         final int k        = start.length - level;
  200.         final int rowShift = k / 2;
  201.         final int colShift = (k + 1) / 2;
  202.         final int levelI   = i >> rowShift;
  203.         final int levelJ   = j >> colShift;
  204.         final int levelC   = 1 + ((getLongitudeColumns() - 1) >> colShift);

  205.         if (DumpManager.isActive()) {
  206.             final int[] min = locateMin(i, j, level);
  207.             final int index = min[0] * getLongitudeColumns() + min[1];
  208.             DumpManager.dumpTileCell(this, min[0],     min[1],     raw[index]);
  209.             if (index + getLongitudeColumns() < raw.length) {
  210.                 DumpManager.dumpTileCell(this, min[0] + 1, min[1],     raw[index + getLongitudeColumns()]);
  211.             }
  212.             if (index + 1 < raw.length) {
  213.                 DumpManager.dumpTileCell(this, min[0],     min[1] + 1, raw[index + 1]);
  214.             }
  215.             if (index + getLongitudeColumns() + 1 < raw.length) {
  216.                 DumpManager.dumpTileCell(this, min[0] + 1, min[1] + 1, raw[index + getLongitudeColumns() + 1]);
  217.             }
  218.         }

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

  220.     }

  221.     /** Get the maximum elevation at some level tree.
  222.      * <p>
  223.      * Note that the max elevation is <em>not</em> computed
  224.      * only at cell center, but considering that it is interpolated
  225.      * considering also Eastwards and Northwards neighbors, and extends
  226.      * up to the center of these neighbors. As an example, lets consider
  227.      * four neighboring cells in some Digital Elevation Model:
  228.      * <table>
  229.      * <caption>"four neighboring cells"</caption>
  230.      *<thead>
  231.      *   <tr><th>j/i</th> <th>i</th> <th>i+1</th></tr>
  232.      *</thead>
  233.      *<tbody>
  234.      *   <tr> <th>j+1</th><td>11</td><td>10</td> </tr>
  235.      *   <tr> <th>j</th> <td>12</td> <td>11</td> </tr>
  236.      * </tbody>
  237.      *</table>
  238.      * When we interpolate elevation at a point located slightly South-West
  239.      * to the center of the (i+1, j+1) cell, we use all four cells in the
  240.      * interpolation, and we will get a result very close to 12 if we start
  241.      * close to (i+1, j+1) cell center. As the max value for this interpolation
  242.      * is stored at (i, j) indices, this implies that {@code getMaxElevation(i,
  243.      * j, l)} must return 12 if l is chosen such that the sub-tile at
  244.      * tree level l includes cell (i,j) but not cell (i+1, j+1). In other words,
  245.      * interpolation implies sub-tile boundaries are overshoot by one column to
  246.      * the East and one row to the North when computing max.
  247.      *
  248.      * @param i row index of the cell
  249.      * @param j column index of the cell
  250.      * @param level tree level
  251.      * @return maximum value that can be reached when interpolating elevation
  252.      * in the sub-tile
  253.      * @see #getLevels()
  254.      * @see #getMinElevation(int, int, int)
  255.      * @see #getMergeLevel(int, int, int, int)
  256.      */
  257.     public double getMaxElevation(final int i, final int j, final int level) {

  258.         // compute indices in level merged array
  259.         final int k        = start.length - level;
  260.         final int rowShift = k / 2;
  261.         final int colShift = (k + 1) / 2;
  262.         final int levelI   = i >> rowShift;
  263.         final int levelJ   = j >> colShift;
  264.         final int levelC   = 1 + ((getLongitudeColumns() - 1) >> colShift);

  265.         if (DumpManager.isActive()) {
  266.             final int[] max = locateMax(i, j, level);
  267.             final int index = max[0] * getLongitudeColumns() + max[1];
  268.             DumpManager.dumpTileCell(this, max[0],     max[1],     raw[index]);
  269.             if (index + getLongitudeColumns() < raw.length) {
  270.                 DumpManager.dumpTileCell(this, max[0] + 1, max[1],     raw[index + getLongitudeColumns()]);
  271.             }
  272.             if (index + 1 < raw.length) {
  273.                 DumpManager.dumpTileCell(this, max[0],     max[1] + 1, raw[index + 1]);
  274.             }
  275.             if (index + getLongitudeColumns() + 1 < raw.length) {
  276.                 DumpManager.dumpTileCell(this, max[0] + 1, max[1] + 1, raw[index + getLongitudeColumns() + 1]);
  277.             }
  278.         }

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

  280.     }

  281.     /** Locate the cell at which min elevation is reached for a specified level.
  282.      * <p>
  283.      * Min is computed with respect to the continuous interpolated elevation, which
  284.      * takes four neighboring cells into account. This implies that the cell at which
  285.      * min value is reached for some level is either within the sub-tile for this level,
  286.      * or in some case it may be one column outside to the East or one row outside to
  287.      * the North. See {@link #getMinElevation()} for a more complete explanation.
  288.      * </p>
  289.      * @param i row index of the cell
  290.      * @param j column index of the cell
  291.      * @param level tree level of the sub-tile considered
  292.      * @return row/column indices of the cell at which min elevation is reached
  293.      */
  294.     public int[] locateMin(final int i, final int j, final int level) {
  295.         return locateMinMax(i, j, level, MinSelector.getInstance(), minTree);
  296.     }

  297.     /** Locate the cell at which max elevation is reached for a specified level.
  298.      * <p>
  299.      * Max is computed with respect to the continuous interpolated elevation, which
  300.      * takes four neighboring cells into account. This implies that the cell at which
  301.      * max value is reached for some level is either within the sub-tile for this level,
  302.      * or in some case it may be one column outside to the East or one row outside to
  303.      * the North. See {@link #getMaxElevation()} for a more complete explanation.
  304.      * </p>
  305.      * @param i row index of the cell
  306.      * @param j column index of the cell
  307.      * @param level tree level of the sub-tile considered
  308.      * @return row/column indices of the cell at which min elevation is reached
  309.      */
  310.     public int[] locateMax(final int i, final int j, final int level) {
  311.         return locateMinMax(i, j, level, MaxSelector.getInstance(), maxTree);
  312.     }

  313.     /** Locate the cell at which min/max elevation is reached for a specified level.
  314.      * @param i row index of the cell
  315.      * @param j column index of the cell
  316.      * @param level tree level of the sub-tile considered
  317.      * @param selector min/max selector to use
  318.      * @param tree min/max tree to use
  319.      * @return row/column indices of the cell at which min/max elevation is reached
  320.      */
  321.     private int[] locateMinMax(final int i, final int j, final int level,
  322.                                final Selector selector, final double[] tree) {

  323.         final int k  = start.length - level;
  324.         int rowShift = k / 2;
  325.         int colShift = (k + 1) / 2;
  326.         int levelI   = i >> rowShift;
  327.         int levelJ   = j >> colShift;
  328.         int levelR   = 1 + ((getLatitudeRows()     - 1) >> rowShift);
  329.         int levelC   = 1 + ((getLongitudeColumns() - 1) >> colShift);

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

  332.             if (isColumnMerging(l)) {

  333.                 --colShift;
  334.                 levelC = 1 + ((getLongitudeColumns() - 1) >> colShift);
  335.                 levelJ = levelJ << 1;

  336.                 if (levelJ + 1 < levelC) {
  337.                     // the cell results from a regular merging of two columns
  338.                     if (selector.selectFirst(tree[start[l] + levelI * levelC + levelJ + 1],
  339.                                              tree[start[l] + levelI * levelC + levelJ])) {
  340.                         levelJ++;
  341.                     }
  342.                 }

  343.             } else {

  344.                 --rowShift;
  345.                 levelR = 1 + ((getLatitudeRows() - 1) >> rowShift);
  346.                 levelI = levelI << 1;

  347.                 if (levelI + 1 < levelR) {
  348.                     // the cell results from a regular merging of two rows
  349.                     if (selector.selectFirst(tree[start[l] + (levelI + 1) * levelC + levelJ],
  350.                                              tree[start[l] + levelI       * levelC + levelJ])) {
  351.                         levelI++;
  352.                     }
  353.                 }

  354.             }

  355.         }

  356.         // we are now at first merge level, which always results from a column merge
  357.         // or pre-processed data, which themselves result from merging four cells
  358.         // used in interpolation
  359.         // this imply the ancestor of min/max at (n, m) is one of
  360.         // (2n, m), (2n+1, m), (2n+2, m), (2n, m+1), (2n+1, m+1), (2n+2, m+1)
  361.         int selectedI = levelI;
  362.         int selectedJ = 2 * levelJ;
  363.         double selectedElevation = Double.NaN;
  364.         for (int n = 2 * levelJ; n < 2 * levelJ + 3; ++n) {
  365.             if (n < getLongitudeColumns()) {
  366.                 for (int m = levelI; m < levelI + 2; ++m) {
  367.                     if (m < getLatitudeRows()) {
  368.                         final double elevation = raw[m * getLongitudeColumns() + n];
  369.                         if (selector.selectFirst(elevation, selectedElevation)) {
  370.                             selectedI         = m;
  371.                             selectedJ         = n;
  372.                             selectedElevation = elevation;
  373.                         }
  374.                     }
  375.                 }
  376.             }
  377.         }

  378.         return new int[] {
  379.             selectedI, selectedJ
  380.         };

  381.     }

  382.     /** Get the deepest level at which two cells are merged in the same min/max sub-tile.
  383.      * @param i1 row index of first cell
  384.      * @param j1 column index of first cell
  385.      * @param i2 row index of second cell
  386.      * @param j2 column index of second cell
  387.      * @return deepest level at which two cells are merged in the same min/max sub-tile,
  388.      * or -1 if they are never merged in the same sub-tile
  389.      * @see #getLevels()
  390.      * @see #getMinElevation(int, int, int)
  391.      * @see #getMaxElevation(int, int, int)
  392.      */
  393.     public int getMergeLevel(final int i1, final int j1, final int i2, final int j2) {

  394.         int largest = -1;

  395.         for (int level = 0; level < start.length; ++level) {
  396.             // compute indices in level merged array
  397.             final int k        = start.length - level;
  398.             final int rowShift = k / 2;
  399.             final int colShift = (k + 1) / 2;
  400.             final int levelI1  = i1 >> rowShift;
  401.             final int levelJ1  = j1 >> colShift;
  402.             final int levelI2  = i2 >> rowShift;
  403.             final int levelJ2  = j2 >> colShift;
  404.             if (levelI1 != levelI2 || levelJ1 != levelJ2) {
  405.                 return largest;
  406.             }
  407.             largest = level;
  408.         }

  409.         return largest;

  410.     }

  411.     /** Get the index of sub-tiles start rows crossed.
  412.      * <p>
  413.      * When going from one row to another row at some tree level,
  414.      * we cross sub-tiles boundaries. This method returns the index
  415.      * of these boundaries.
  416.      * </p>
  417.      * @param row1 starting row
  418.      * @param row2 ending row
  419.      * @param level tree level
  420.      * @return indices of rows crossed at sub-tiles boundaries, in crossing order,
  421.      * the endpoints <em>are</em> included (i.e. if {@code row1} or {@code row2} are
  422.      * boundary rows, they will be in returned array)
  423.      */
  424.     public int[] getCrossedBoundaryRows(final int row1, final int row2, final int level) {

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

  427.         // build the crossings in ascending order
  428.         final int min = FastMath.min(row1, row2);
  429.         final int max = FastMath.max(row1, row2) + 1;
  430.         return buildCrossings((min + rows - 1) - ((min + rows - 1) % rows), max, rows,
  431.                               row1 <= row2);

  432.     }

  433.     /** Get the index of sub-tiles start columns crossed.
  434.      * <p>
  435.      * When going from one column to another column at some tree level,
  436.      * we cross sub-tiles boundaries. This method returns the index
  437.      * of these boundaries.
  438.      * </p>
  439.      * @param column1 starting column
  440.      * @param column2 ending column (excluded)
  441.      * @param level tree level
  442.      * @return indices of columns crossed at sub-tiles boundaries, in crossing order,
  443.      * the endpoints <em>are</em> included (i.e. if {@code column1} or {@code column2} are
  444.      * boundary columns, they will be in returned array)
  445.      */
  446.     public int[] getCrossedBoundaryColumns(final int column1, final int column2, final int level) {

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

  449.         // build the crossings in ascending order
  450.         final int min = FastMath.min(column1, column2);
  451.         final int max = FastMath.max(column1, column2) + 1;
  452.         return buildCrossings((min + columns - 1) - ((min + columns - 1) % columns), max, columns,
  453.                               column1 <= column2);

  454.     }

  455.     /** Build crossings arrays.
  456.      * @param begin begin crossing index
  457.      * @param end end crossing index (excluded, if equal to begin, the array is empty)
  458.      * @param step crossing step
  459.      * @param ascending if true, the crossings must be in ascending order
  460.      * @return indices of rows or columns crossed at sub-tiles boundaries, in crossing order
  461.      */
  462.     private int[] buildCrossings(final int begin, final int end, final int step, final boolean ascending) {

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

  466.         // fill it up
  467.         int crossing = begin;
  468.         if (ascending) {
  469.             for (int i = 0; i < crossings.length; ++i) {
  470.                 crossings[i] = crossing;
  471.                 crossing += step;
  472.             }
  473.         } else {
  474.             for (int i = 0; i < crossings.length; ++i) {
  475.                 crossings[crossings.length - 1 - i] = crossing;
  476.                 crossing += step;
  477.             }
  478.         }

  479.         return crossings;

  480.     }

  481.     /** Check if the merging operation between level and level-1 is a column merging.
  482.      * @param level level to check
  483.      * @return true if the merging operation between level and level-1 is a column
  484.      * merging, false if is a row merging
  485.      */
  486.     public boolean isColumnMerging(final int level) {
  487.         return (level & 0x1) == (start.length & 0x1);
  488.     }

  489.     /** Recursive setting of tree levels.
  490.      * <p>
  491.      * The following algorithms works for any array shape, even with
  492.      * rows or columns which are not powers of 2 or with one
  493.      * dimension much larger than the other. As an example, starting
  494.      * from a 107 ⨉ 19 array, we get the following 9 levels, for a
  495.      * total of 2187 elements in each tree:
  496.      * </p>
  497.      * <p>
  498.      * <table border="0">
  499.      * <tr BGCOLOR="#EEEEFF">
  500.      *     <td>Level</td>   <td>Dimension</td>  <td>Start index</td>  <td>End index (inclusive)</td></tr>
  501.      * <tr>   <td>0</td>     <td>  7 ⨉  1</td>       <td>   0</td>        <td>  6</td> </tr>
  502.      * <tr>   <td>1</td>     <td>  7 ⨉  2</td>       <td>   7</td>        <td> 20</td> </tr>
  503.      * <tr>   <td>2</td>     <td> 14 ⨉  2</td>       <td>  21</td>        <td> 48</td> </tr>
  504.      * <tr>   <td>3</td>     <td> 14 ⨉  3</td>       <td>  49</td>        <td> 90</td> </tr>
  505.      * <tr>   <td>4</td>     <td> 27 ⨉  3</td>       <td>  91</td>        <td>171</td> </tr>
  506.      * <tr>   <td>5</td>     <td> 27 ⨉  5</td>       <td> 172</td>        <td>306</td> </tr>
  507.      * <tr>   <td>6</td>     <td> 54 ⨉  5</td>       <td> 307</td>        <td>576</td> </tr>
  508.      * <tr>   <td>7</td>     <td> 54 ⨉ 10</td>      <td> 577</td>        <td>1116</td> </tr>
  509.      * <tr>   <td>8</td>     <td>107 ⨉ 10</td>      <td>1117</td>        <td>2186</td> </tr>
  510.      * </table>
  511.      * </p>
  512.      * @param stage number of merging stages
  513.      * @param stageRows number of rows at current stage
  514.      * @param stageColumns number of columns at current stage
  515.      * @return size cumulative size from root to current level
  516.      */
  517.     private int setLevels(final int stage, final int stageRows, final int stageColumns) {

  518.         if (stageRows == 1 || stageColumns == 1) {
  519.             // we have found root, stop recursion
  520.             start   = new int[stage];
  521.             if (stage > 0) {
  522.                 start[0]   = 0;
  523.             }
  524.             return stageRows * stageColumns;
  525.         }

  526.         final int size;
  527.         if ((stage & 0x1) == 0) {
  528.             // columns merging
  529.             size = setLevels(stage + 1, stageRows, (stageColumns + 1) / 2);
  530.         } else {
  531.             // rows merging
  532.             size = setLevels(stage + 1, (stageRows + 1) / 2, stageColumns);
  533.         }

  534.         if (stage > 0) {
  535.             // store current level characteristics
  536.             start[start.length     - stage] = size;
  537.             return size + stageRows * stageColumns;
  538.         } else {
  539.             // we don't count the elements at stage 0 as they are not stored in the
  540.             // min/max trees (they correspond to the raw elevation, without merging)
  541.             return size;
  542.         }

  543.     }

  544.     /** Preprocess recursive application of a function.
  545.      * <p>
  546.      * At start, the min/max should be computed for each cell using the four corners values.
  547.      * </p>
  548.      * @param preprocessed preprocessed array to fill up
  549.      * @param elevations raw elevations te preprocess
  550.      * @param nbRows number of rows
  551.      * @param nbCols number of columns
  552.      * @param selector selector to use
  553.      */
  554.     private void preprocess(final double[] preprocessed, final double[] elevations,
  555.                             final int nbRows, final int nbCols,
  556.                             final Selector selector) {

  557.         int k = 0;

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

  559.             // regular elements with both a column at right and a row below
  560.             for (int j = 0; j < nbCols - 1; ++j) {
  561.                 preprocessed[k] = selector.select(selector.select(elevations[k],          elevations[k + 1]),
  562.                                                   selector.select(elevations[k + nbCols], elevations[k + nbCols + 1]));
  563.                 k++;
  564.             }

  565.             // last column elements, lacking a right column
  566.             preprocessed[k] = selector.select(elevations[k], elevations[k + nbCols]);
  567.             k++;

  568.         }

  569.         // last row elements, lacking a below row
  570.         for (int j = 0; j < nbCols - 1; ++j) {
  571.             preprocessed[k] = selector.select(elevations[k], elevations[k + 1]);
  572.             k++;
  573.         }

  574.         // last element
  575.         preprocessed[k] = elevations[k];

  576.     }

  577.     /** Recursive application of a function.
  578.      * @param tree tree to fill-up with the recursive applications
  579.      * @param level current level
  580.      * @param levelRows number of rows at current level
  581.      * @param levelColumns number of columns at current level
  582.      * @param selector to apply
  583.      * @param base base array from which function arguments are drawn
  584.      * @param first index of the first element to consider in base array
  585.      */
  586.     private void applyRecursively(final double[] tree,
  587.                                   final int level, final int levelRows, final int levelColumns,
  588.                                   final Selector selector,
  589.                                   final double[] base, final int first) {

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

  591.             // merge columns pairs
  592.             int           iTree       = start[level];
  593.             int           iBase       = first;
  594.             final int     nextColumns = (levelColumns + 1) / 2;
  595.             final boolean odd         = (levelColumns & 0x1) != 0;
  596.             final int     jEnd        = odd ? nextColumns - 1 : nextColumns;
  597.             for (int i = 0; i < levelRows; ++i) {

  598.                 // regular pairs
  599.                 for (int j = 0; j < jEnd; ++j) {
  600.                     tree[iTree++] = selector.select(base[iBase], base[iBase + 1]);
  601.                     iBase += 2;
  602.                 }

  603.                 if (odd) {
  604.                     // last column
  605.                     tree[iTree++] = base[iBase++];
  606.                 }


  607.             }

  608.             if (level > 0) {
  609.                 applyRecursively(tree, level - 1, levelRows, nextColumns, selector, tree, start[level]);
  610.             }

  611.         } else {

  612.             // merge rows pairs
  613.             int           iTree    = start[level];
  614.             int           iBase    = first;
  615.             final int     nextRows = (levelRows + 1) / 2;
  616.             final boolean odd      = (levelRows & 0x1) != 0;
  617.             final int     iEnd     = odd ? nextRows - 1 : nextRows;

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

  620.                 for (int j = 0; j < levelColumns; ++j) {
  621.                     tree[iTree++] = selector.select(base[iBase], base[iBase + levelColumns]);
  622.                     iBase++;
  623.                 }
  624.                 iBase += levelColumns;

  625.             }

  626.             if (odd) {
  627.                 // last row
  628.                 System.arraycopy(base, iBase, tree, iTree, levelColumns);
  629.             }

  630.             if (level > 0) {
  631.                 applyRecursively(tree, level - 1, nextRows, levelColumns, selector, tree, start[level]);
  632.             }

  633.         }
  634.     }

  635. }