blob: e9f584e0fb10cb086f0594c82b5be00ad6167ef6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.sis.coverage;
import java.util.HashSet;
import java.util.function.DoubleToIntFunction;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.internal.feature.Resources;
/**
* Keep trace of allocated {@link Float#NaN} ordinal values for avoiding range collisions when building categories.
* This is a temporary object used only at {@link SampleDimension} construction time for producing values suitable
* to {@link MathFunctions#toNanFloat(int)}. Instances of this class are given for {@code toNaN} argument in the
* {@link Category} constructor.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
* @since 1.0
* @module
*/
@SuppressWarnings({"CloneableClassWithoutClone", "serial"}) // Not intended to be cloned or serialized.
final class ToNaN extends HashSet<Integer> implements DoubleToIntFunction {
/**
* The value which should be assigned ordinal 0 if that ordinal value is available.
* For performance reason, the background value should be assigned ordinal 0 when possible.
* This is {@code null} if unspecified.
*/
Number background;
/**
* To be constructed only from this package.
*/
ToNaN() {
}
/**
* Sets this function to the same state than after construction.
* This method is invoked when the same builder is reused for creating many sample dimensions.
*/
@Override
public void clear() {
super.clear();
background = null;
}
/**
* Returns {@code true} if the specified value is the background value.
*/
private boolean isBackground(final double value) {
return (background != null) && (value == background.doubleValue());
}
/**
* Mapping from sample values to ordinal values to be supplied to {@link MathFunctions#toNanFloat(int)}.
* That mapping shall ensure that there is no ordinal value collision between different categories in
* the same {@link SampleDimension}.
*
* @param value a real number in the {@link Category#range} sample value range.
* @return a value between {@value MathFunctions#MIN_NAN_ORDINAL} and {@value MathFunctions#MAX_NAN_ORDINAL} inclusive.
*/
@Override
public int applyAsInt(final double value) {
if (isBackground(value) && add(0)) {
return 0;
}
/*
* For qualitative category, we need an ordinal in the [MIN_NAN_ORDINAL … MAX_NAN_ORDINAL] range.
* This range is quite large (a few million of values) so using the sample directly usually work.
* If it does not work, we will use an arbitrary value in that range.
*/
int ordinal = Math.round((float) value);
if (ordinal > MathFunctions.MAX_NAN_ORDINAL) {
ordinal = (MathFunctions.MAX_NAN_ORDINAL + 1) / 2;
} else if (ordinal < MathFunctions.MIN_NAN_ORDINAL) {
ordinal = MathFunctions.MIN_NAN_ORDINAL / 2;
}
search: if (!add(ordinal)) {
/*
* Following algorithms are inefficient, but those loops should be rarely needed.
* They are executed only if many qualitative sample values are outside the range
* of ordinal NaN values. The range allows a few million of values.
*/
if (ordinal >= 0) {
do if (add(++ordinal)) break search;
while (ordinal < MathFunctions.MAX_NAN_ORDINAL);
} else {
do if (add(--ordinal)) break search;
while (ordinal > MathFunctions.MIN_NAN_ORDINAL);
}
throw new IllegalSampleDimensionException(Resources.format(Resources.Keys.TooManyQualitatives));
}
return ordinal;
}
/**
* Removes the NaN value which has been reserved for a qualitative category.
* This method does nothing if {@code converted} is not a NaN value, i.e. if
* the category is quantitative instead than qualitative.
*
* @param c the presumed qualitative category.
*/
void remove(final Category c) {
final float converted = (float) c.converse.range.getMinDouble();
if (Float.isNaN(converted) && super.remove(MathFunctions.toNanOrdinal(converted))) {
// Use of 'c.getMinDouble()' shall be consistent with 'SampleDimension.Builder.setBackground(…)'.
if (isBackground(c.range.getMinDouble())) {
background = null;
}
}
}
}