package mil.nga.giat.geowave.analytic;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
/**
* Generate clusters of geometries.
*
*/
import org.geotools.referencing.CRS;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import mil.nga.giat.geowave.adapter.vector.FeatureDataAdapter;
import mil.nga.giat.geowave.analytic.distance.CoordinateCircleDistanceFn;
import mil.nga.giat.geowave.analytic.distance.DistanceFn;
import mil.nga.giat.geowave.core.geotime.ingest.SpatialDimensionalityTypeProvider;
import mil.nga.giat.geowave.core.store.DataStore;
import mil.nga.giat.geowave.core.store.IndexWriter;
import mil.nga.giat.geowave.core.store.index.PrimaryIndex;
/**
* Generate clusters of geometries.
*
*/
public class GeometryDataSetGenerator
{
final static Logger LOGGER = LoggerFactory.getLogger(GeometryDataSetGenerator.class);
private final Random rand = new Random();
private final GeometryFactory geoFactory = new GeometryFactory();
private final DistanceFn<SimpleFeature> distanceFunction;
private final SimpleFeatureBuilder builder;
// coordinate system boundaries
private SimpleFeature minFeature;
private double[] minAxis;
private double[] maxAxis;
private CoordinateSystem coordSystem;
private boolean includePolygons = true;
public GeometryDataSetGenerator(
final DistanceFn<SimpleFeature> distanceFunction,
final SimpleFeatureBuilder builder ) {
super();
this.distanceFunction = distanceFunction;
this.builder = builder;
init();
}
public boolean isIncludePolygons() {
return includePolygons;
}
public void setIncludePolygons(
final boolean includePolygons ) {
this.includePolygons = includePolygons;
}
public SimpleFeature getCorner() {
return minFeature;
}
public Geometry getBoundingRegion() {
final int[] adder = {
1,
2,
-1,
2
};
int num = 0;
int addCnt = 0;
final int dims = coordSystem.getDimension();
final int coords = (int) Math.pow(
dims,
2);
final Coordinate[] coordinates = new Coordinate[coords + 1];
for (int i = 0; i < coords; i++) {
coordinates[i] = new Coordinate();
for (int j = 0; j < dims; j++) {
final boolean isMin = ((num >> j) % 2) == 0;
coordinates[i].setOrdinate(
j,
isMin ? minAxis[j] : maxAxis[j]);
}
num += adder[addCnt];
addCnt = (addCnt + 1) % 4;
}
coordinates[coords] = coordinates[0];
return geoFactory.createPolygon(coordinates);
}
/**
* Calculate the range for the given bounds
*
* @param factor
* @param minAxis
* @param maxAxis
* @return
*/
private double[] createRange(
final double factor,
final double[] minAxis,
final double[] maxAxis ) {
final double[] range = new double[minAxis.length];
for (int i = 0; i < minAxis.length; i++) {
range[i] = (maxAxis[i] - minAxis[i]) * factor;
}
return range;
}
/**
* Pick a random grid cell and supply the boundary. The grid is determined
* by the parameter,which provides a percentage of distance over the total
* range for each cell.
*
* @param minCenterDistanceFactor
* @return
*/
private Pair<double[], double[]> gridCellBounds(
final double minCenterDistanceFactor,
final double[] minAxis,
final double[] maxAxis ) {
final double[] range = createRange(
1.0,
minAxis,
maxAxis);
final double[] min = new double[range.length];
final double[] max = new double[range.length];
for (int i = 0; i < range.length; i++) {
// HP Fortify "Insecure Randomness" false positive
// This random number is not used for any purpose
// related to security or cryptography
min[i] = Math
.max(
minAxis[i]
+ (minCenterDistanceFactor * (rand.nextInt(Integer.MAX_VALUE) % (range[i] / minCenterDistanceFactor))),
minAxis[i]);
max[i] = Math.min(
min[i] + (minCenterDistanceFactor * range[i]),
maxAxis[i]);
}
return Pair.of(
min,
max);
}
public void writeToGeoWave(
final DataStore dataStore,
final List<SimpleFeature> featureData )
throws IOException {
final PrimaryIndex index = new SpatialDimensionalityTypeProvider().createPrimaryIndex();
final FeatureDataAdapter adapter = new FeatureDataAdapter(
featureData.get(
0).getFeatureType());
final SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(
featureData.get(
0).getFeatureType());
LOGGER.info("Writing " + featureData.size() + " records to " + adapter.getFeatureType().getTypeName());
try (IndexWriter writer = dataStore.createWriter(
adapter,
index)) {
for (final SimpleFeature feature : featureData) {
writer.write(feature);
featureBuilder.reset();
}
}
}
public List<SimpleFeature> generatePointSet(
final double minCenterDistanceFactor,
final double outlierFactor,
final int numberOfCenters,
final int minSetSize ) {
return this.generatePointSet(
minCenterDistanceFactor,
outlierFactor,
numberOfCenters,
minSetSize,
minAxis,
maxAxis);
}
public List<SimpleFeature> generatePointSet(
final LineString line,
final double distanceFactor,
final int points ) {
final List<SimpleFeature> pointSet = new ArrayList<>();
for (final Point point : CurvedDensityDataGeneratorTool.generatePoints(
line,
distanceFactor,
points)) {
pointSet.add(createFeatureWithGeometry(point));
}
return pointSet;
}
public List<SimpleFeature> generatePointSet(
final double minCenterDistanceFactor,
final double outlierFactor,
final int numberOfCenters,
final int minSetSize,
final double[] minAxis,
final double[] maxAxis ) {
final List<SimpleFeature> pointSet = new ArrayList<>();
final List<double[]> minForCenter = new ArrayList<>();
final List<double[]> maxForCenter = new ArrayList<>();
final double[] range = createRange(
minCenterDistanceFactor,
minAxis,
maxAxis);
if (numberOfCenters >= minSetSize) {
LOGGER.error("The number of centers passed much be less than the minimum set size");
throw new IllegalArgumentException(
"The number of centers passed much be less than the minimum set size");
}
final double minDistance = computeMinDistance(
minCenterDistanceFactor,
minAxis,
maxAxis);
/**
* Pick the initial centers which have minimum distance from each other.
*
*/
while (pointSet.size() < numberOfCenters) {
final Pair<double[], double[]> axis = gridCellBounds(
minCenterDistanceFactor,
minAxis,
maxAxis);
final SimpleFeature nextFeature = createNewFeature(
axis.getLeft(),
axis.getRight());
if (isFarEnough(
nextFeature,
pointSet,
minDistance)) {
pointSet.add(nextFeature);
}
}
/**
* Calculate the boundaries around each center point to place additional
* points, thus creating clusters
*/
for (final SimpleFeature center : pointSet) {
final double[] centerMinAxis = new double[coordSystem.getDimension()];
final double[] centerMaxAxis = new double[coordSystem.getDimension()];
final Geometry geo = (Geometry) center.getDefaultGeometry();
final Coordinate centerCoord = geo.getCentroid().getCoordinate();
for (int i = 0; i < centerMinAxis.length; i++) {
centerMinAxis[i] = centerCoord.getOrdinate(i) - (range[i] / 2.0);
centerMaxAxis[i] = centerCoord.getOrdinate(i) + (range[i] / 2.0);
}
minForCenter.add(centerMinAxis);
maxForCenter.add(centerMaxAxis);
}
/*
* Pick a random center point and add a new geometry with the bounding
* range around that point.
*/
final int clusterdItemsCount = (int) Math.ceil((minSetSize) * (1.0 - outlierFactor));
while (pointSet.size() < clusterdItemsCount) {
// HP Fortify "Insecure Randomness" false positive
// This random number is not used for any purpose
// related to security or cryptography
final int centerPos = rand.nextInt(Integer.MAX_VALUE) % minForCenter.size();
pointSet.add(createNewFeature(
minForCenter.get(centerPos),
maxForCenter.get(centerPos)));
}
/**
* Add random points as potential outliers (no guarantees)
*/
while (pointSet.size() < minSetSize) {
pointSet.add(createNewFeature(
minAxis,
maxAxis));
}
return pointSet;
}
public List<SimpleFeature> addRandomNoisePoints(
final List<SimpleFeature> pointSet,
final int minSetSize,
final double[] minAxis,
final double[] maxAxis ) {
while (pointSet.size() < minSetSize) {
pointSet.add(createNewFeature(
minAxis,
maxAxis));
}
return pointSet;
}
private void init() {
coordSystem = builder.getFeatureType().getCoordinateReferenceSystem().getCoordinateSystem();
minAxis = new double[coordSystem.getDimension()];
maxAxis = new double[coordSystem.getDimension()];
for (int i = 0; i < coordSystem.getDimension(); i++) {
final CoordinateSystemAxis axis = coordSystem.getAxis(i);
minAxis[i] = axis.getMinimumValue();
maxAxis[i] = axis.getMaximumValue();
}
final int dims = coordSystem.getDimension();
final Coordinate coordinate = new Coordinate();
for (int i = 0; i < dims; i++) {
coordinate.setOrdinate(
i,
minAxis[i]);
}
minFeature = createFeatureWithGeometry(geoFactory.createPoint(coordinate));
}
private boolean isFarEnough(
final SimpleFeature feature,
final List<SimpleFeature> set,
final double minDistance ) {
for (final SimpleFeature setItem : set) {
if (distanceFunction.measure(
feature,
setItem) < minDistance) {
return false;
}
}
return true;
}
/**
* Find the distance maximum distance of the entire space and multiply that
* by the distance factor to determine a minimum distance each initial
* center point occurs from each other.
*
* @param minCenterDistanceFactor
* @return
*/
private double computeMinDistance(
final double minCenterDistanceFactor,
final double[] minAxis,
final double[] maxAxis ) {
assert minCenterDistanceFactor < 0.75;
final int dims = coordSystem.getDimension();
Coordinate coordinate = new Coordinate();
for (int i = 0; i < dims; i++) {
coordinate.setOrdinate(
i,
minAxis[i]);
}
final SimpleFeature minFeature = createFeatureWithGeometry(geoFactory.createPoint(coordinate));
coordinate = new Coordinate();
for (int i = 0; i < dims; i++) {
coordinate.setOrdinate(
i,
maxAxis[i]);
}
final SimpleFeature maxFeature = createFeatureWithGeometry(geoFactory.createPoint(coordinate));
return minCenterDistanceFactor * distanceFunction.measure(
minFeature,
maxFeature);
}
private SimpleFeature createNewFeature(
final double[] minAxis,
final double[] maxAxis ) {
final int dims = coordSystem.getDimension();
// HP Fortify "Insecure Randomness" false positive
// This random number is not used for any purpose
// related to security or cryptography
final int shapeSize = includePolygons ? (rand.nextInt(Integer.MAX_VALUE) % 5) + 1 : 1;
final Coordinate[] shape = new Coordinate[shapeSize > 2 ? shapeSize + 1 : shapeSize];
final double[] constrainedMaxAxis = Arrays.copyOf(
maxAxis,
maxAxis.length);
final double[] constrainedMinAxis = Arrays.copyOf(
minAxis,
minAxis.length);
for (int s = 0; s < shapeSize; s++) {
final Coordinate coordinate = new Coordinate();
for (int i = 0; i < dims; i++) {
// HP Fortify "Insecure Randomness" false positive
// This random number is not used for any purpose
// related to security or cryptography
coordinate.setOrdinate(
i,
constrainedMinAxis[i] + (rand.nextDouble() * (constrainedMaxAxis[i] - constrainedMinAxis[i])));
}
shape[s] = coordinate;
if (s == 0) {
constrain(
coordinate,
constrainedMaxAxis,
constrainedMinAxis);
}
}
if (shapeSize > 2) {
shape[shapeSize] = shape[0];
return createFeatureWithGeometry(geoFactory.createLinearRing(
shape).convexHull());
}
else if (shapeSize == 2) {
return createFeatureWithGeometry(geoFactory.createLineString(shape));
}
else {
return createFeatureWithGeometry(geoFactory.createPoint(shape[0]));
}
}
public GeometryFactory getFactory() {
return geoFactory;
}
/**
* Change the constrain min and max to center around the coordinate to keep
* the polygons tight.
*
* @param coordinate
* @param constrainedMaxAxis
* @param constrainedMinAxis
*/
private void constrain(
final Coordinate coordinate,
final double[] constrainedMaxAxis,
final double[] constrainedMinAxis ) {
for (int i = 0; i < constrainedMaxAxis.length; i++) {
final double range = (constrainedMaxAxis[i] - constrainedMinAxis[i]) * 0.001;
constrainedMaxAxis[i] = Math.min(
coordinate.getOrdinate(i) + range,
constrainedMaxAxis[i]);
constrainedMinAxis[i] = Math.max(
coordinate.getOrdinate(i) - range,
constrainedMinAxis[i]);
}
}
private SimpleFeature createFeatureWithGeometry(
final Geometry geometry ) {
final Object[] values = new Object[builder.getFeatureType().getAttributeCount()];
for (int i = 0; i < values.length; i++) {
final AttributeDescriptor desc = builder.getFeatureType().getDescriptor(
i);
if (desc.getType() instanceof GeometryType) {
values[i] = geometry;
}
else {
final Class<?> binding = desc.getType().getBinding();
if (String.class.isAssignableFrom(binding)) {
values[i] = UUID.randomUUID().toString();
}
}
}
return builder.buildFeature(
UUID.randomUUID().toString(),
values);
}
// public static void main(
// final String[] args )
// throws Exception {
// final Options allOptions = new Options();
// DataStoreCommandLineOptions.applyOptions(allOptions);
// final Option typeNameOption = new Option(
// "typename",
// true,
// "a name for the feature type (required)");
// typeNameOption.setRequired(true);
// allOptions.addOption(typeNameOption);
// CommandLine commandLine = new BasicParser().parse(
// allOptions,
// args);
//
// final CommandLineResult<DataStoreCommandLineOptions> dataStoreOption =
// DataStoreCommandLineOptions.parseOptions(
// allOptions,
// commandLine);
// if (dataStoreOption.isCommandLineChange()) {
// commandLine = dataStoreOption.getCommandLine();
// }
// else {
// throw new ParseException(
// "Unable to parse data store from command line");
// }
// final DataStore dataStore = dataStoreOption.getResult().createStore();
// final String typeName = commandLine.getOptionValue("typename");
// final GeometryDataSetGenerator dataGenerator = new
// GeometryDataSetGenerator(
// new FeatureCentroidDistanceFn(),
// getBuilder(typeName));
// dataGenerator.writeToGeoWave(
// dataStore,
// dataGenerator.generatePointSet(
// 0.2,
// 0.2,
// 5,
// 5000,
// new double[] {
// -100,
// -45
// },
// new double[] {
// -90,
// -35
// }));
// dataGenerator.writeToGeoWave(
// dataStore,
// dataGenerator.generatePointSet(
// 0.2,
// 0.2,
// 7,
// 5000,
// new double[] {
// 0,
// 0
// },
// new double[] {
// 10,
// 10
// }));
// dataGenerator.writeToGeoWave(
// dataStore,
// dataGenerator.addRandomNoisePoints(
// dataGenerator.generatePointSet(
// 0.2,
// 0.2,
// 6,
// 5000,
// new double[] {
// 65,
// 35
// },
// new double[] {
// 75,
// 45
// }),
// 6000,
// new double[] {
// -90,
// -90
// },
// new double[] {
// 90,
// 90
// }));
// }
private static SimpleFeatureBuilder getBuilder(
final String name )
throws FactoryException {
final SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
typeBuilder.setName(name);
typeBuilder.setCRS(CRS.decode(
"EPSG:4326",
true)); // <- Coordinate
// reference
// add attributes in order
typeBuilder.add(
"geom",
Geometry.class);
typeBuilder.add(
"name",
String.class);
typeBuilder.add(
"count",
Long.class);
// build the type
return new SimpleFeatureBuilder(
typeBuilder.buildFeatureType());
}
public static class CurvedDensityDataGeneratorTool
{
private static final CoordinateCircleDistanceFn DISTANCE_FN = new CoordinateCircleDistanceFn();
private CurvedDensityDataGeneratorTool() {}
public static final List<Point> generatePoints(
final LineString line,
final double distanceFactor,
final int points ) {
final List<Point> results = new ArrayList<>();
Coordinate lastCoor = null;
double distanceTotal = 0.0;
final double[] distancesBetweenCoords = new double[line.getCoordinates().length - 1];
int i = 0;
for (final Coordinate coor : line.getCoordinates()) {
if (lastCoor != null) {
distancesBetweenCoords[i] = Math.abs(DISTANCE_FN.measure(
lastCoor,
coor));
distanceTotal += distancesBetweenCoords[i++];
}
lastCoor = coor;
}
lastCoor = null;
i = 0;
for (final Coordinate coor : line.getCoordinates()) {
if (lastCoor != null) {
results.addAll(generatePoints(
line.getFactory(),
toVec(coor),
toVec(lastCoor),
distanceFactor,
(int) ((points) * (distancesBetweenCoords[i++] / distanceTotal))));
}
lastCoor = coor;
}
return results;
}
private static final List<Point> generatePoints(
final GeometryFactory factory,
final Vector2D coordinateOne,
final Vector2D coordinateTwo,
final double distanceFactor,
final int points ) {
final List<Point> results = new ArrayList<>();
final Random rand = new Random();
final Vector2D originVec = coordinateTwo.subtract(coordinateOne);
for (int i = 0; i < points; i++) {
// HP Fortify "Insecure Randomness" false positive
// This random number is not used for any purpose
// related to security or cryptography
final double factor = rand.nextDouble();
final Vector2D projectionPoint = originVec.scalarMultiply(factor);
final double direction = rand.nextGaussian() * distanceFactor;
final Vector2D orthogonal = new Vector2D(
originVec.getY(),
-originVec.getX());
results.add(factory.createPoint(toCoordinate(orthogonal.scalarMultiply(
direction).add(
projectionPoint).add(
coordinateOne))));
}
return results;
}
public static Coordinate toCoordinate(
final Vector2D vec ) {
return new Coordinate(
vec.getX(),
vec.getY());
}
public static Vector2D toVec(
final Coordinate coor ) {
return new Vector2D(
coor.x,
coor.y);
}
}
}