package mil.nga.giat.geowave.analytic.partitioner;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapreduce.JobContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import mil.nga.giat.geowave.analytic.PropertyManagement;
import mil.nga.giat.geowave.analytic.SerializableAdapterStore;
import mil.nga.giat.geowave.analytic.param.ParameterEnum;
import mil.nga.giat.geowave.analytic.param.StoreParameters;
import mil.nga.giat.geowave.analytic.partitioner.AdapterBasedPartitioner.AdapterDataEntry;
import mil.nga.giat.geowave.analytic.store.PersistableStore;
import mil.nga.giat.geowave.core.geotime.index.dimension.LongitudeDefinition;
import mil.nga.giat.geowave.core.index.ByteArrayId;
import mil.nga.giat.geowave.core.index.dimension.NumericDimensionDefinition;
import mil.nga.giat.geowave.core.index.sfc.data.BasicNumericDataset;
import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData;
import mil.nga.giat.geowave.core.index.sfc.data.NumericData;
import mil.nga.giat.geowave.core.index.sfc.data.NumericRange;
import mil.nga.giat.geowave.core.store.adapter.AdapterPersistenceEncoding;
import mil.nga.giat.geowave.core.store.adapter.AdapterStore;
import mil.nga.giat.geowave.core.store.adapter.DataAdapter;
import mil.nga.giat.geowave.core.store.index.CommonIndexModel;
/**
* This class uses the {@link DataAdapter} to decode the dimension fields to be
* indexed. Although seemingly more flexible than the
* {@link OrthodromicDistancePartitioner}, handling different types of data
* entries, the assumption is that each object decode by the adapter provides
* the fields required according to the supplied model.
*
* The user provides the distances per dimension. It us up to the user to
* convert geographic distance into distance in degrees per longitude and
* latitude.
*
* This class depends on an AdapterStore. Since an AdapterStore is not
* Serializable, the dependency is transient requiring initialization after
* serialization
* {@link AdapterBasedPartitioner#initialize(ConfigurationWrapper)
*
*
*/
public class AdapterBasedPartitioner extends
AbstractPartitioner<AdapterDataEntry> implements
Partitioner<AdapterDataEntry>,
Serializable
{
final static Logger LOGGER = LoggerFactory.getLogger(AdapterBasedPartitioner.class);
private static final long serialVersionUID = 5951564193108204266L;
private NumericData[] fullRangesPerDimension;
private boolean[] wrapsAroundBoundary;
private SerializableAdapterStore adapterStore;
public AdapterBasedPartitioner() {
}
public AdapterBasedPartitioner(
final CommonIndexModel indexModel,
final double[] distancesPerDimension,
final AdapterStore adapterStore ) {
super(
indexModel,
distancesPerDimension);
this.adapterStore = new SerializableAdapterStore(
adapterStore);
init();
}
public static class AdapterDataEntry
{
ByteArrayId adapterId;
Object data;
public AdapterDataEntry(
final ByteArrayId adapterId,
final Object data ) {
super();
this.adapterId = adapterId;
this.data = data;
}
}
@Override
protected NumericDataHolder getNumericData(
final AdapterDataEntry entry ) {
final NumericDataHolder numericDataHolder = new NumericDataHolder();
@SuppressWarnings("unchecked")
final DataAdapter<Object> adapter = (DataAdapter<Object>) adapterStore.getAdapter(entry.adapterId);
if (adapter == null) {
LOGGER.error(
"Unable to find an adapter for id {}",
entry.adapterId.toString());
return null;
}
final AdapterPersistenceEncoding encoding = adapter.encode(
entry.data,
getIndex().getIndexModel());
final double[] thetas = getDistancePerDimension();
final MultiDimensionalNumericData primaryData = encoding.getNumericData(getIndex()
.getIndexModel()
.getDimensions());
numericDataHolder.primary = primaryData;
numericDataHolder.expansion = querySet(
primaryData,
thetas);
return numericDataHolder;
}
protected void init() {
final NumericDimensionDefinition[] definitions = getIndex().getIndexStrategy().getOrderedDimensionDefinitions();
fullRangesPerDimension = new NumericData[definitions.length];
wrapsAroundBoundary = new boolean[definitions.length];
for (int i = 0; i < definitions.length; i++) {
fullRangesPerDimension[i] = definitions[i].getFullRange();
wrapsAroundBoundary[i] = getIndex().getIndexModel().getDimensions()[i].getBaseDefinition() instanceof LongitudeDefinition;
}
}
@Override
public void initialize(
final JobContext context,
final Class<?> scope )
throws IOException {
super.initialize(
context,
scope);
adapterStore = new SerializableAdapterStore(
((PersistableStore) StoreParameters.StoreParam.INPUT_STORE.getHelper().getValue(
context,
scope,
null)).getDataStoreOptions().createAdapterStore());
init();
}
@Override
public void setup(
final PropertyManagement runTimeProperties,
final Class<?> scope,
final Configuration configuration ) {
super.setup(
runTimeProperties,
scope,
configuration);
final ParameterEnum[] params = new ParameterEnum[] {
StoreParameters.StoreParam.INPUT_STORE
};
runTimeProperties.setConfig(
params,
configuration,
scope);
}
protected MultiDimensionalNumericData[] querySet(
final MultiDimensionalNumericData dimensionsData,
final double[] distances ) {
final List<NumericRange[]> resultList = new ArrayList<NumericRange[]>();
final NumericRange[] currentData = new NumericRange[dimensionsData.getDimensionCount()];
addToList(
resultList,
currentData,
distances,
dimensionsData,
0);
final MultiDimensionalNumericData[] finalSet = new MultiDimensionalNumericData[resultList.size()];
int i = 0;
for (final NumericRange[] rangeData : resultList) {
finalSet[i++] = new BasicNumericDataset(
rangeData);
}
return finalSet;
}
private void addToList(
final List<NumericRange[]> resultList,
final NumericRange[] currentData,
final double[] distances,
final MultiDimensionalNumericData dimensionsData,
final int d ) {
if (d == currentData.length) {
resultList.add(Arrays.copyOf(
currentData,
currentData.length));
return;
}
final NumericData dimensionData = dimensionsData.getDataPerDimension()[d];
final double lowerBound = dimensionData.getMin() - distances[d];
final double upperBound = dimensionData.getMax() + distances[d];
final double mindiff = lowerBound - fullRangesPerDimension[d].getMin();
final double maxdiff = upperBound - fullRangesPerDimension[d].getMax();
if (wrapsAroundBoundary[d] && (mindiff < 0)) {
currentData[d] = new NumericRange(
fullRangesPerDimension[d].getMax() + mindiff,
fullRangesPerDimension[d].getMax());
addToList(
resultList,
currentData,
distances,
dimensionsData,
d + 1);
currentData[d] = new NumericRange(
fullRangesPerDimension[d].getMin(),
upperBound);
addToList(
resultList,
currentData,
distances,
dimensionsData,
d + 1);
}
else if (wrapsAroundBoundary[d] && (maxdiff > 0)) {
currentData[d] = new NumericRange(
lowerBound,
fullRangesPerDimension[d].getMax());
addToList(
resultList,
currentData,
distances,
dimensionsData,
d + 1);
currentData[d] = new NumericRange(
fullRangesPerDimension[d].getMin(),
fullRangesPerDimension[d].getMin() + maxdiff);
addToList(
resultList,
currentData,
distances,
dimensionsData,
d + 1);
}
else {
currentData[d] = new NumericRange(
lowerBound,
upperBound);
addToList(
resultList,
currentData,
distances,
dimensionsData,
d + 1);
}
}
}