package mil.nga.giat.geowave.core.geotime.ingest;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import mil.nga.giat.geowave.core.geotime.index.dimension.LatitudeDefinition;
import mil.nga.giat.geowave.core.geotime.index.dimension.LongitudeDefinition;
import mil.nga.giat.geowave.core.geotime.index.dimension.TemporalBinningStrategy.Unit;
import mil.nga.giat.geowave.core.geotime.index.dimension.TimeDefinition;
import mil.nga.giat.geowave.core.geotime.store.dimension.GeometryWrapper;
import mil.nga.giat.geowave.core.geotime.store.dimension.LatitudeField;
import mil.nga.giat.geowave.core.geotime.store.dimension.LongitudeField;
import mil.nga.giat.geowave.core.geotime.store.dimension.Time;
import mil.nga.giat.geowave.core.geotime.store.dimension.TimeField;
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.SFCFactory.SFCType;
import mil.nga.giat.geowave.core.index.sfc.xz.XZHierarchicalIndexFactory;
import mil.nga.giat.geowave.core.store.dimension.NumericDimensionField;
import mil.nga.giat.geowave.core.store.index.BasicIndexModel;
import mil.nga.giat.geowave.core.store.index.CommonIndexValue;
import mil.nga.giat.geowave.core.store.index.CustomIdIndex;
import mil.nga.giat.geowave.core.store.index.PrimaryIndex;
import mil.nga.giat.geowave.core.store.operations.remote.options.IndexPluginOptions.BaseIndexBuilder;
import mil.nga.giat.geowave.core.store.spi.DimensionalityTypeOptions;
import mil.nga.giat.geowave.core.store.spi.DimensionalityTypeProviderSpi;
public class SpatialTemporalDimensionalityTypeProvider implements
DimensionalityTypeProviderSpi
{
private final SpatialTemporalOptions options = new SpatialTemporalOptions();
private static final String DEFAULT_SPATIAL_TEMPORAL_ID_STR = "SPATIAL_TEMPORAL_IDX";
public SpatialTemporalDimensionalityTypeProvider() {}
@Override
public String getDimensionalityTypeName() {
return "spatial_temporal";
}
@Override
public String getDimensionalityTypeDescription() {
return "This dimensionality type matches all indices that only require Geometry and Time.";
}
@Override
public int getPriority() {
// arbitrary - just lower than spatial so that the default
// will be spatial over spatial-temporal
return 5;
}
@Override
public DimensionalityTypeOptions getOptions() {
return options;
}
@Override
public PrimaryIndex createPrimaryIndex() {
return internalCreatePrimaryIndex(options);
}
private static PrimaryIndex internalCreatePrimaryIndex(
final SpatialTemporalOptions options ) {
// TODO should we use different default IDs for all the different
// options, for now lets just use one
final NumericDimensionField[] fields = new NumericDimensionField[] {
new LongitudeField(),
new LatitudeField(
true),
new TimeField(
options.periodicity)
};
final NumericDimensionDefinition[] dimensions = new NumericDimensionDefinition[] {
new LongitudeDefinition(),
new LatitudeDefinition(
true),
new TimeDefinition(
options.periodicity)
};
final String combinedId = DEFAULT_SPATIAL_TEMPORAL_ID_STR + "_" + options.bias + "_" + options.periodicity;
return new CustomIdIndex(
XZHierarchicalIndexFactory.createFullIncrementalTieredStrategy(
dimensions,
new int[] {
options.bias.getSpatialPrecision(),
options.bias.getSpatialPrecision(),
options.bias.getTemporalPrecision()
},
SFCType.HILBERT,
options.maxDuplicates),
new BasicIndexModel(
fields),
new ByteArrayId(
combinedId));
}
@Override
public Class<? extends CommonIndexValue>[] getRequiredIndexTypes() {
return new Class[] {
GeometryWrapper.class,
Time.class
};
}
private static class SpatialTemporalOptions implements
DimensionalityTypeOptions
{
@Parameter(names = {
"--period"
}, required = false, description = "The periodicity of the temporal dimension. Because time is continuous, it is binned at this interval.", converter = UnitConverter.class)
protected Unit periodicity = Unit.YEAR;
@Parameter(names = {
"--bias"
}, required = false, description = "The bias of the spatial-temporal index. There can be more precision given to time or space if necessary.", converter = BiasConverter.class)
protected Bias bias = Bias.BALANCED;
@Parameter(names = {
"--maxDuplicates"
}, required = false, description = "The max number of duplicates per dimension range. The default is 2 per range (for example lines and polygon timestamp data would be up to 4 because its 2 dimensions, and line/poly time range data would be 8).")
protected long maxDuplicates = -1;
}
public static enum Bias {
TEMPORAL,
BALANCED,
SPATIAL;
// converter that will be used later
public static Bias fromString(
final String code ) {
for (final Bias output : Bias.values()) {
if (output.toString().equalsIgnoreCase(
code)) {
return output;
}
}
return null;
}
protected int getSpatialPrecision() {
switch (this) {
case SPATIAL:
return 25;
case TEMPORAL:
return 10;
case BALANCED:
default:
return 20;
}
}
protected int getTemporalPrecision() {
switch (this) {
case SPATIAL:
return 10;
case TEMPORAL:
return 40;
case BALANCED:
default:
return 20;
}
}
}
public static class BiasConverter implements
IStringConverter<Bias>
{
@Override
public Bias convert(
final String value ) {
final Bias convertedValue = Bias.fromString(value);
if (convertedValue == null) {
throw new ParameterException(
"Value " + value + "can not be converted to an index bias. " + "Available values are: "
+ StringUtils.join(
Bias.values(),
", ").toLowerCase(
Locale.ENGLISH));
}
return convertedValue;
}
}
public static class UnitConverter implements
IStringConverter<Unit>
{
@Override
public Unit convert(
final String value ) {
final Unit convertedValue = Unit.fromString(value);
if (convertedValue == null) {
throw new ParameterException(
"Value " + value + "can not be converted to Unit. " + "Available values are: "
+ StringUtils.join(
Unit.values(),
", ").toLowerCase(
Locale.ENGLISH));
}
return convertedValue;
}
}
public static class SpatialTemporalIndexBuilder extends
BaseIndexBuilder<SpatialTemporalIndexBuilder>
{
private final SpatialTemporalOptions options;
public SpatialTemporalIndexBuilder() {
options = new SpatialTemporalOptions();
}
public SpatialTemporalIndexBuilder setBias(
final Bias bias ) {
options.bias = bias;
return this;
}
public SpatialTemporalIndexBuilder setPeriodicity(
final Unit periodicity ) {
options.periodicity = periodicity;
return this;
}
public SpatialTemporalIndexBuilder setMaxDuplicates(
final long maxDuplicates ) {
options.maxDuplicates = maxDuplicates;
return this;
}
@Override
public PrimaryIndex createIndex() {
return createIndex(internalCreatePrimaryIndex(options));
}
}
public static boolean isSpatialTemporal(
final PrimaryIndex index ) {
if ((index == null) || (index.getIndexStrategy() == null)
|| (index.getIndexStrategy().getOrderedDimensionDefinitions() == null)) {
return false;
}
final NumericDimensionDefinition[] dimensions = index.getIndexStrategy().getOrderedDimensionDefinitions();
if (dimensions.length < 3) {
return false;
}
boolean hasLat = false, hasLon = false, hasTime = false;
for (final NumericDimensionDefinition definition : dimensions) {
if (definition instanceof TimeDefinition) {
hasTime = true;
}
else if (definition instanceof LatitudeDefinition) {
hasLat = true;
}
else if (definition instanceof LongitudeDefinition) {
hasLon = true;
}
}
return hasTime && hasLat && hasLon;
}
}