/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.constellation.numeric.table; // J2SE dependencies import java.nio.DoubleBuffer; import java.util.Arrays; import static java.lang.Double.isNaN; import static java.lang.System.arraycopy; import static org.apache.sis.util.ArraysExt.isSorted; // OpenGIS dependencies // Static imports /** * Un vecteur de valeurs <var>x</var> dont toutes les données sont en ordre croissant ou décroissant. * Les valeurs {@link Double#NaN NaN} ne sont pas ordonnées et peuvent apparaître n'importe où dans * ce vecteur. Notez que cette dernière règle est différente de celle de {@link Arrays#sort(double[])}, * qui place les valeurs {@link Double#NaN NaN} à la fin du vecteur. * * @version $Id$ * @author Martin Desruisseaux */ abstract class OrderedVector implements Cloneable { /** * Index des valeurs inférieure ({@code lower}) et supérieure ({@code upper}) à la valeur * spécifiée lors du dernier appel de {@link #locate(double)}. */ protected int lower, upper; /** * Valeur pour laquelle l'utilisateur a demandé une interpolation. Cette valeur est mémorisée * lors des appels à une méthode {@code locate}. Si le dernier appel d'une telle méthode a * retourné {@code true} et que {@code value} n'est pas {@link Double#NaN NaN}, alors la * condition suivante doit être respectée: * * <blockquote><pre> * {@linkplain #get get}({@linkplain #lower}) <= value <= {@linkplain #get get}({@linkplain #upper}) * </pre></blockquote> */ protected double value = Double.NaN; /** * Construit une nouvelle instance d'un vecteur ordonné. */ public OrderedVector() { } /** * Retourne l'ordre des données dans ce vecteur. Les valeurs {@link Double#NaN NaN} * ne sont pas prises en compte pour déterminer l'ordre. */ public abstract DataOrder getDataOrder(); /** * Retourne la longueur de ce vecteur. */ public abstract int length(); /** * Retourne la valeur à l'index spécifié. La valeur de cet index peut varier de 0 inclusivement * jusqu'à {@link #length} exclusivement. * <p> * Cette méthode peut être considérée comme l'inverse de {@link #locate} dans la mesure où * après un appel à <code>{@linkplain #locate locate}({@linkplain #get get}(index))</code>, * on aura {@link #upper} == {@link #lower} == {@code index}. * * @param index La valeur de l'index. * @return La valeur du vecteur à l'index spécifié. * @throws IndexOutOfBoundsException si l'index est en dehors des limites permises. */ public abstract double get(int index) throws IndexOutOfBoundsException; /** * Trouve les index {@link #lower} et {@link #upper} qui permettent d'encadrer la valeur * spécifiée. Après l'appel de cetet méthode, les conditions suivantes sont respectées: * <p> * <ul> * <li><b>Si</b> une correspondance exacte est trouvée entre la valeur <var>x</var> * demandée et une des valeurs de ce vecteur, <b>alors:</b> * <ul> * <li>{@link #lower} == {@link #upper}</li> * <li><code>{@linkplain #get get}({@linkplain #lower})</code> == <var>x</var> == * <code>{@linkplain #get get}({@linkplain #upper})</code></li> * </ul> * </li> * * <li><b>Sinon:</b> * <ul> * <li>{@link #lower} < {@link #upper}</li> * <li>Une des conditions suivantes: * <ul> * <li><code>{@linkplain #get get}({@linkplain #lower})</code> < <var>x</var> < * <code>{@linkplain #get get}({@linkplain #upper})</code> * si les données de ce vecteur sont en ordre croissant, ou</li> * <li><code>{@linkplain #get get}({@linkplain #lower})</code> > <var>x</var> > * <code>{@linkplain #get get}({@linkplain #upper})</code> * si les données de ce vecteur sont en ordre décroissant.</li> * </ul> * </li> * </ul> * </li> * </ul> * <p> * Mis à part les correspondances exactes, cette méthode produira dans la plupart des cas un * résultat tel que <code>{@linkplain #upper} == {@linkplain #lower}+1</code>. Si toutefois * ce vecteur contient des valeurs {@link Double#NaN NaN}, alors l'écart entre {@link #lower} * et {@link #upper} peut être plus grand, car cette méthode s'efforce de faire pointer les * index {@link #lower} et {@link #upper} vers des valeurs réelles. * <p> * <strong>Exemple:</strong> * Supposons que ce vecteur contient les données suivantes: * * <blockquote><pre> * Indices: [ 0 1 2 3 4 5 6 7 8] * Valeurs: { 4 9 12 NaN NaN 34 56 76 89} * </pre></blockquote> * * Alors, * <p> * <ul> * <li>{@code locate( 9)} donnera {@code lower=1} et {@code upper=1}.</li> * <li>{@code locate(60)} donnera {@code lower=6} et {@code upper=7}.</li> * <li>{@code locate(20)} donnera {@code lower=2} et {@code upper=5}.</li> * </ul> * <p> * Cette méthode peut être considérée comme l'inverse de {@link #get}. * * @param x valeur à rechercher dans ce vecteur. * @return {@code true} si la valeur spécifiée est comprise dans la plage de ce vecteur, ou * {@code false} si elle se trouve en dehors ou si le vecteur n'a pas suffisamment de * données autres que {@link Double#NaN NaN}. */ public abstract boolean locate(double x); /** * Positionne les index {@link #lower} et {@link #upper} autour de l'index spécifié. Si ce * vecteur ne contient pas de valeurs {@link Double#NaN NaN}, alors {@link #lower} sera égal * à {@code index-1} et {@link #upper} sera égal à {@code index+1}. Si ce vecteur contient * des valeurs {@link Double#NaN NaN}, alors {@link #lower} peut être un peu plus bas et/ou * {@link #upper} un peu plus haut de façon à pointer sur des valeurs réelles. * * @param index Index de la valeur autour de laquelle on veut se positionner. * @return {@code true} si les bornes {@link #lower} et {@link #upper} ont été définie, * ou {@code false} si l'index spécifié tombe en dehors des limites du vecteur. */ public abstract boolean locateAroundIndex(int index); /** * Copie dans le tableau spécifié en argument la valeur des champs {@link #lower} et {@link #upper}. * Ce tableau peut avoir diverses longueurs. Un cas typique est lorsque qu'il a une longueur de 2. * Alors le champs {@link #lower} sera simplement copié dans {@code index[0]} et {@link #upper} * dans {@code index[1]}. Si le tableau à une longueur de 1, alors seul {@link #lower} sera copié * dans {@code index[0]}. Si le tableau à une longueur de 0, rien ne sera fait. * <p> * Les cas le plus intéressants se produisent lorsque le tableau {@code index} à une longueur * de 3 et plus. Cette méthode copiera les valeurs de {@link #lower} et {@link #upper} au milieu * de ce tableau, puis complètera les autres cellules avec la suite des index qui pointent vers * des valeurs autres que {@link Double#NaN NaN}. Par exemple si ce vecteur contient: * * <blockquote><pre> * Indices: [ 0 1 2 3 4 5 6 7] * Valeurs: { 5 8 NaN 12 NaN 19 21 34} * </pre></blockquote> * * Alors l'appel de la méthode <code>{@link #locate locate}(15)</code> donnera aux champs * {@link #lower} et {@link #upper} les valeurs 3 et 5 respectivement, de sorte que * <code>{@linkplain #get get}(3) < 15 < {@linkplain #get get}(5)</code>. Si vous souhaitez * effectuer une interpolation polynomiale d'ordre 4 autour de ces données, vous pouvez écrire: * * <blockquote><pre> * if (locate(x)) { * int index[] = new int[4]; * if (copyIndexInto(index)) { * // Effectuer l'interpolation * } * } * </pre></blockquote> * * Le tableau {@code index} contiendra alors les valeurs <code>{1, 3, 5, 6}</code>. * * @param index tableau dans lequel copier les champs {@code lower} et {@code upper}. * @return {@code false} s'il n'y a pas suffisament de données valides. */ public abstract boolean copyIndexInto(int[] index); /** * Ajuste les index spécifiés de façon à ce qu'ils ne pointent vers aucune donnée {@link Double#NaN NaN}. * Ces index sont habituellement obtenus par la méthode {@link #copyIndexInto}. * <p> * Supposons que vous vous apprêtez à faire une interpolation polynomiale d'ordre 4 autour de la * donnée <var>x</var>=84. Supposons qu'avec les méthodes {@link #locate} et {@link #copyIndexInto}, * vous avez obtenu les index {@code [4 5 6 7]}. La valeur 84 se trouvera typiquement entre * {@code x[5]} et {@code x[6]}. Maintenant supposons que votre vecteur des <var>y</var> contienne * les données suivantes: * * <blockquote><pre> * y = [5 3 1 2 7 NaN 12 6 4 ...etc...] * </pre></blockquote> * * Vous voulez vous assurez que les index obtenus par {@code copyIndexInto} pointent * tous vers une donnée <var>y</var> valide. Après avoir appellé la méthode * * <blockquote><pre> * validateIndex(y, index) * </pre></blockquote> * * vos index {@code [4 5 6 7]} deviendront {@code [3 4 6 7]}, car {@code y[5]} avait pour * valeur NaN. Notez que vous n'avez pas à vous préocupper de savoir si les index pointent * vers des <var>x</var> valides. Ça avait déjà été assuré par {@code copyIndexInto} et * continuera à être assuré par {@code validateIndex}. * <p> * Voici un exemple d'utilisation. Supposons que trois vecteurs de données ({@code Y1}, * {@code Y2} et {@code Y3}) se partagent le même vecteur des <var>x</var> ({@code X}). * Supposons que vous souhaitez obtenir 4 index valides simultanément pour tous les vecteurs * autour de <var>x</var>=1045. Vous pourriez écrire: * * <blockquote><pre> * locate(1045); * final int index[] = new int[4]; * copyIndexIntoArray(index); * boolean hasChanged; * do { * hasChanged = validateIndex(Y1, index); * hasChanged |= validateIndex(Y2, index); * hasChanged |= validateIndex(Y3, index); * } while (hasChanged); * </pre></blockquote> * * S'il n'est pas nécessaire que les index soient valides pour tous les vecteurs simultanément, * vous pourriez copier les éléments de {@code index} dans un tableau temporaire après l'appel * de {@code copyIndexInto}. Il vous suffira alors de restituer cette copie avant chaque appel * de {@code validateIndex} pour chacun des vecteurs {@code Y}. En réutilisant cette copie, vous * évitez d'appeller trois fois {@code locate} et y gagnez ainsi un peu en vitesse d'éxecution. * * @param y Vecteur des données <var>y</var> servant à la vérification. * @param index A l'entrée, tableau d'index à vérifier. A la sortie, tableau d'index modifiés. * Cette méthode s'efforce autant que possible de ne pas modifier les index se * trouvant au centre de ce tableau. * @return {@code true} si des changements ont été fait, {@code false} sinon. * @throws ExtrapolationException s'il n'y a pas suffisament de données valides. * * @see #locate(double) * @see #copyIndexInto(int[]) */ public final boolean validateIndex(final DoubleBuffer y, final int[] index) throws ExtrapolationException { assert isSorted(index) : Arrays.toString(index); boolean hasChanged = false; final int xlength = length(); int center = index.length >> 1; loop: for (int i=center; --i>=0;) { if (isNaN(y.get(index[i]))) { /* * Ce bloc ne sera exécuté que si un NaN a été trouvé (sinon cette méthode sera * exécutée rapidement car elle n'aurait pratiquement rien à faire). La prochaine * boucle décale les index qui avaient déjà été trouvés (par 'copyIndexInto') de * façon à exclure les NaN. L'autre boucle va chercher d'autre index, de la même * façon que 'copyIndexInto' s'y prenait. */ hasChanged = true; for (int j=i; --j>=0;) { if (!isNaN(y.get(index[j]))) { index[i--] = index[j]; } } int lower = index[0]; do { do if (--lower < 0) { center -= ++i; arraycopy(index, i, index, 0, index.length-i); break loop; } while (isNaN(get(lower)) || isNaN(y.get(lower))); index[i--] = lower; } while (i >= 0); break loop; } } /* * Le code suivant fait la même opération que le code précédent, * mais pour la deuxième moitié des index. */ loop: for (int i=center; i<index.length; i++) { if (isNaN(y.get(index[i]))) { hasChanged = true; for (int j=i; ++j<index.length;) { if (!isNaN(y.get(index[j]))) { index[i++] = index[j]; } } int upper = index[index.length-1]; do { do if (++upper >= xlength || upper >= y.limit()) { int remainder = index.length-i; // center += remainder; // (not needed) arraycopy(index, 0, index, remainder, i); i = remainder; int lower = index[0]; do { do if (--lower < 0) { throw new ExtrapolationException(); } while (isNaN(get(lower)) || isNaN(y.get(lower))); index[--i] = lower; } while (i > 0); break loop; } while (isNaN(get(upper)) || isNaN(y.get(upper))); index[i++] = upper; } while (i < index.length); break loop; } } assert isSorted(index) : Arrays.toString(index); return hasChanged; } /** * Ajuste les index {@link #lower} et {@link #upper} de façon à ce qu'ils pointent vers des données * valides. Cette méthode est très similaire à la méthode {@link #validateIndex(DoubleBuffer,int[])}, * excepté qu'elle agit directement sur {@link #lower} et {@link #upper} plutôt que sur un tableau * passé en argument. On y gagne ainsi en rapidité d'exécution (on évite de faire un appel à * {@link #copyIndexInto)}, mais ça ne gère toujours que ces deux index. * <p> * Tout ce qui était entre {@code lower} et {@code upper} avant l'appel de cette méthode le resteront * après. Cette méthode ne fait que diminuer {@code lower} et augmenter {@code upper}, si nécessaire. * Si ce n'était pas possible, une exception {@link ExtrapolationException} sera lancée. * * @param y Vecteur des données <var>y</var> servant à la vérification. * @return {@code true} si des changements ont été fait, {@code false} sinon. * @throws ExtrapolationException s'il n'y a pas suffisament de données valides. * * @see #validateIndex(DoubleBuffer[],int[]) * @see #locateAroundIndex(int) * @see #locate(double) */ public final boolean validateIndex(final DoubleBuffer y) throws ExtrapolationException { int[] check = null; // For assertions only. Next line has intentional side effect. assert isSorted(check=new int[] {lower, upper}) : Arrays.toString(check); boolean hasChanged = false; if (isNaN(y.get(upper))) { hasChanged = true; do if (++upper >= y.limit()) { throw new ExtrapolationException(); } while (isNaN(get(upper)) || isNaN(y.get(upper))); } if (isNaN(y.get(lower))) { hasChanged = true; do if (--lower < 0) { throw new ExtrapolationException(); } while (isNaN(get(lower)) || isNaN(y.get(lower))); } // Compare avec la version générique. assert hasChanged == validateIndex(y, check) : Arrays.toString(check); assert check[0] == lower : lower; assert check[1] == upper : upper; return hasChanged; } /** * Retourne une estimation de la plage de valeurs encadrées par {@link #lower} et {@link #upper}. * Cette plage est mesurée à partir de l'espace qui se trouve entre deux données, comme dans le * dessin ci-dessous: * * <blockquote><pre> * lower upper * | | * Indices: [ 0 1 2 3 4 5 6 ] * Valeurs: {146 148 150 152 154 156 158} * | | * ^------plage------^ * </pre></blockquote> * * Dans l'exemple précédent, {@code getInterval()} retournerait la valeur 6. Notez que * {@code lower == upper} n'implique pas que cette méthode retourne 0. Dans l'exemple * précédent, {@code getInterval()} retournerait 2. * <p> * Il n'est pas obligatoire que l'intervalle entre les valeurs de ce vecteur soit constant. * Si ce vecteur contient des valeurs {@link Double#NaN NaN}, alors cette méthode utilisera * une interpolation linéaire. */ public abstract double getInterval(); /** * Retourne une copie de ce vecteur. La copie retournée devrait pouvoir être utilisée dans un * autre thread. Toutefois, les données sous-jacentes peuvent être partagées, de sorte que la * copie reste relativement économique. */ @Override public OrderedVector clone() { try { return (OrderedVector) super.clone(); } catch (CloneNotSupportedException e) { // Should never happen, since we are cloneable. throw new AssertionError(e); } } }