use of org.apache.sis.math.Vector in project sis by apache.
the class ESRI method createPolyline.
/**
* Creates a polyline from the given ordinate values.
* Each {@link Double#NaN} ordinate value start a new path.
* The implementation returned by this method must be an instance of {@link #rootClass}.
*/
@Override
public Geometry createPolyline(final int dimension, final Vector... ordinates) {
if (dimension != 2) {
throw unsupported(dimension);
}
boolean lineTo = false;
final Polyline path = new Polyline();
for (final Vector v : ordinates) {
if (v != null) {
final int size = v.size();
for (int i = 0; i < size; ) {
final double x = v.doubleValue(i++);
final double y = v.doubleValue(i++);
if (Double.isNaN(x) || Double.isNaN(y)) {
lineTo = false;
} else if (lineTo) {
path.lineTo(x, y);
} else {
path.startPath(x, y);
lineTo = true;
}
}
}
}
return path;
}
use of org.apache.sis.math.Vector in project sis by apache.
the class MovingFeature method storeGeometry.
/**
* Sets the geometry of the given attribute to the values collected by this {@code MovingFeatures}.
* This method sets also the {@code "datetimes"} characteristic.
*
* @param <G> the type of the geometry value.
* @param featureName the name of the feature containing the attribute to update, for logging purpose.
* @param index index of the property for which geometry value is desired.
* @param dimension number of dimensions for all coordinates.
* @param factory the factory to use for creating the geometry object.
* @param dest attribute where to store the geometry value.
* @param warningListener where to report warnings. Implementation should set the source class name,
* source method name and logger name, then forward to a {@code WarningListener}.
*/
public final <G> void storeGeometry(final String featureName, final int index, final int dimension, final Geometries<G> factory, final AbstractAttribute<G> dest, final Consumer<LogRecord> warningListener) {
int n = count[index];
final Vector[] vectors = new Vector[n];
for (Period p = properties[index]; p != null; p = p.previous) {
vectors[--n] = Vector.create(p.value, false);
}
if (n != 0) {
// Should never happen unless this object has been modified concurrently in another thread.
throw new CorruptedObjectException();
}
// Maximal number of warnings, for avoiding to flood the logger.
int warnings = 10;
// Total number of points in all vectors, ignoring null vectors.
int numPts = 0;
// If non-null, shall be non-empty.
Vector previous = null;
for (int i = 0; i < vectors.length; i++) {
Vector v = vectors[i];
int length;
if (v == null || (length = v.size()) == 0) {
continue;
}
if ((length % dimension) != 0) {
if (--warnings >= 0) {
Period p = properties[index];
for (int j = i; --j >= 0; ) {
// This is inefficient but used only in case of warnings.
p = p.previous;
}
warningListener.accept(Resources.forLocale(null).getLogRecord(Level.WARNING, Resources.Keys.UnexpectedNumberOfOrdinates_4, featureName, new Date(p.startTime), dimension, length));
}
continue;
}
/*
* At this point we have a non-empty valid sequence of ordinate values. If the first point of current
* vector is equals to the last point of previous vector, assume that they form a continuous polyline.
*/
if (previous != null) {
if (equals(previous, v, dimension)) {
// Skip the first coordinate.
v = v.subList(dimension, length);
length -= dimension;
if (length == 0) {
vectors[i] = null;
continue;
}
vectors[i] = v;
}
}
numPts += length;
previous = v;
}
/*
* At this point we got the list of all coordinates to join together in a polyline.
* We will create the geometry at the end of this method. Before that, interpolate
* the dates and times.
*/
int i = vectors.length;
numPts /= dimension;
final long[] times = new long[numPts];
for (Period p = properties[index]; p != null; p = p.previous) {
final Vector v = vectors[--i];
if (v != null) {
int c = v.size() / dimension;
if (c == 1) {
times[--numPts] = p.endTime;
} else {
final long startTime = p.startTime;
final double scale = (p.endTime - startTime) / (double) (c - 1);
while (--c >= 0) {
times[--numPts] = startTime + Math.round(scale * c);
}
}
}
}
if (numPts != 0) {
// Should never happen unless this object has been modified concurrently in another thread.
throw new CorruptedObjectException();
}
/*
* Store the geometry and characteristics in the attribute.
*/
dest.setValue(factory.createPolyline(dimension, vectors));
final AbstractAttribute<Instant> c = TIME.newInstance();
c.setValues(new DateList(times));
dest.characteristics().values().add(c);
}
use of org.apache.sis.math.Vector in project sis by apache.
the class VariableTest method testRead1D.
/**
* Tests {@link Variable#read()} on a one-dimensional variable.
*
* @throws IOException if an error occurred while reading the netCDF file.
* @throws DataStoreException if a logical error occurred.
*/
@Test
public void testRead1D() throws IOException, DataStoreException {
final Variable variable = selectDataset(NCEP).getVariables()[25];
assertEquals("lon", variable.getName());
final Vector data = variable.read();
assertEquals("lon", Float.class, data.getElementType());
final int length = data.size();
assertEquals("length", 73, length);
for (int i = 0; i < length; i++) {
assertEquals("Longitude value", -180 + 5 * i, data.floatValue(i), 0f);
}
}
use of org.apache.sis.math.Vector in project sis by apache.
the class GridGeometry method localizationGrid.
/**
* Builds a localization grid from the given GeoTIFF tie points.
* This method may invoke itself recursively.
*
* @param modelTiePoints the model tie points read from GeoTIFF file.
* @param addTo if non-null, add the transform result to this map.
*/
private static MathTransform localizationGrid(final Vector modelTiePoints, final Map<Envelope, MathTransform> addTo) throws FactoryException, TransformException {
final int size = modelTiePoints.size();
final int n = size / RECORD_LENGTH;
if (n == 0)
return null;
final Vector x = modelTiePoints.subSampling(0, RECORD_LENGTH, n);
final Vector y = modelTiePoints.subSampling(1, RECORD_LENGTH, n);
try {
final LocalizationGridBuilder grid = new LocalizationGridBuilder(x, y);
final LinearTransform sourceToGrid = grid.getSourceToGrid();
final double[] ordinates = new double[2];
for (int i = 0; i < size; i += RECORD_LENGTH) {
ordinates[0] = modelTiePoints.doubleValue(i);
ordinates[1] = modelTiePoints.doubleValue(i + 1);
sourceToGrid.transform(ordinates, 0, ordinates, 0, 1);
grid.setControlPoint(Math.toIntExact(Math.round(ordinates[0])), Math.toIntExact(Math.round(ordinates[1])), modelTiePoints.doubleValue(i + 3), modelTiePoints.doubleValue(i + 4));
}
grid.setDesiredPrecision(PRECISION);
final MathTransform tr = grid.create(null);
if (addTo != null && addTo.put(grid.getSourceEnvelope(), tr) != null) {
// Should never happen. If it does, we have a bug in our algorithm.
throw new FactoryException();
}
return tr;
} catch (ArithmeticException | FactoryException e) {
/*
* May happen when the model tie points are not distributed on a regular grid.
* For example Sentinel 1 images may have tie points spaced by 1320 pixels on the X axis,
* except the very last point which is only 1302 pixels after the previous one. We try to
* handle such grids by splitting them in two parts: one grid for the columns where points
* are spaced by 1320 pixels and one grid for the last column. Such splitting needs to be
* done horizontally and vertically, which result in four grids:
*
* ┌──────────────────┬───┐
* │ │ │
* │ 0 │ 1 │
* │ │ │
* ├──────────────────┼───┤ splitY
* │ 2 │ 3 │
* └──────────────────┴───┘
* splitX
*/
final Set<Double> uniques = new HashSet<>(100);
final double splitX = threshold(x, uniques);
final double splitY = threshold(y, uniques);
if (Double.isNaN(splitX) && Double.isNaN(splitY)) {
// Can not do better. Report the failure.
throw e;
}
final int[][] indices = new int[4][size];
final int[] lengths = new int[4];
for (int i = 0; i < size; ) {
final double px = modelTiePoints.doubleValue(i);
final double py = modelTiePoints.doubleValue(i + 1);
// Number of the part where to add current point.
int part = 0;
// Point will be added to part #1 or #3.
if (px > splitX)
part = 1;
// Point will be added to part #2 or #3.
if (py > splitY)
part |= 2;
// Bitmask of the parts where to add the point.
int parts = 1 << part;
// Add also the point to part #1 or #3.
if (px == splitX)
parts |= 1 << (part | 1);
// Add also the point to part #2 or #3.
if (py == splitY)
parts |= 1 << (part | 2);
if (parts == 0b0111) {
// Add also the point to part #3.
parts = 0b1111;
assert px == splitX && py == splitY;
}
final int upper = i + RECORD_LENGTH;
do {
part = Integer.numberOfTrailingZeros(parts);
@SuppressWarnings("MismatchedReadAndWriteOfArray") final int[] tileIndices = indices[part];
int k = lengths[part];
for (int j = i; j < upper; j++) {
tileIndices[k++] = j;
}
lengths[part] = k;
} while (// Clear the bit of the part we processed.
(parts &= ~(1 << part)) != 0);
i = upper;
}
/*
* At this point, we finished to collect indices of the points to use for parts #0, 1, 2 and 3.
* Verify that each part has less points than the initial vector (otherwise it would be a bug),
* and identify which part is the biggest one. This is usually part #0.
*/
int maxLength = 0;
int largestPart = 0;
for (int i = 0; i < indices.length; i++) {
final int length = lengths[i];
// Safety against infinite recursivity.
if (length >= size)
throw e;
indices[i] = Arrays.copyOf(indices[i], length);
if (length > maxLength) {
maxLength = length;
largestPart = i;
}
}
/*
* The biggest part will define the global transform. All other parts will define a specialization
* valid only in a sub-area. Put those information in a map for MathTransforms.specialize(…).
*/
MathTransform global = null;
final Map<Envelope, MathTransform> specialization = new LinkedHashMap<>(4);
for (int i = 0; i < indices.length; i++) {
final Vector sub = modelTiePoints.pick(indices[i]);
if (i == largestPart) {
global = localizationGrid(sub, null);
} else {
localizationGrid(sub, specialization);
}
}
return MathTransforms.specialize(global, specialization);
}
}
use of org.apache.sis.math.Vector in project sis by apache.
the class Java2D method createPolyline.
/**
* Creates a path from the given ordinate values.
* Each {@link Double#NaN} ordinate value start a new path.
* The implementation returned by this method must be an instance of {@link #rootClass}.
*/
@Override
public Shape createPolyline(final int dimension, final Vector... ordinates) {
if (dimension != 2) {
throw unsupported(dimension);
}
/*
* Computes the total length of all vectors and verifies if all values
* can be casted to float without precision lost.
*/
int length = 0;
boolean isFloat = true;
for (final Vector v : ordinates) {
if (v != null) {
length = Math.addExact(length, v.size());
if (isFloat) {
for (int i = v.size(); --i >= 0; ) {
final double value = v.doubleValue(i);
if (Double.doubleToRawLongBits(value) != Double.doubleToRawLongBits((float) value)) {
isFloat = false;
break;
}
}
}
}
}
/*
* Note: Point2D is not an instance of Shape, so we can not make a special case for it.
*/
length /= 2;
if (length == 2 && ordinates.length == 1) {
final Vector v = ordinates[0];
final double x1, y1, x2, y2;
if (!Double.isNaN(x1 = v.doubleValue(0)) && !Double.isNaN(y1 = v.doubleValue(1)) && !Double.isNaN(x2 = v.doubleValue(2)) && !Double.isNaN(y2 = v.doubleValue(3))) {
final Line2D path = isFloat ? new Line2D.Float() : new Line2D.Double();
path.setLine(x1, y1, x2, y2);
return path;
}
}
final Path2D path = isFloat ? new Path2D.Float(Path2D.WIND_NON_ZERO, length) : new Path2D.Double(Path2D.WIND_NON_ZERO, length);
boolean lineTo = false;
for (final Vector v : ordinates) {
final int size = v.size();
for (int i = 0; i < size; ) {
final double x = v.doubleValue(i++);
final double y = v.doubleValue(i++);
if (Double.isNaN(x) || Double.isNaN(y)) {
lineTo = false;
} else if (lineTo) {
path.lineTo(x, y);
} else {
path.moveTo(x, y);
lineTo = true;
}
}
}
return ShapeUtilities.toPrimitive(path);
}
Aggregations