use of org.apache.sis.measure.Angle in project sis by apache.
the class CoordinateFormat method format.
/**
* Formats the given coordinate and appends the resulting text to the given stream or buffer.
*
* @param position the coordinate to format.
* @param toAppendTo where the text is to be appended.
* @throws IOException if an error occurred while writing to the given appendable.
*/
@Override
@SuppressWarnings("UnnecessaryBoxing")
public void format(final DirectPosition position, final Appendable toAppendTo) throws IOException {
ArgumentChecks.ensureNonNull("position", position);
ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
CoordinateReferenceSystem crs = position.getCoordinateReferenceSystem();
if (crs == null) {
// May still be null.
crs = defaultCRS;
}
if (crs != lastCRS) {
initialize(crs);
}
/*
* Standard java.text.Format API can only write into a StringBuffer. If the given Appendable is not a
* StringBuffer, then we will need to format in a temporary buffer before to copy to the Appendable.
*/
final StringBuffer destination;
if (toAppendTo instanceof StringBuffer) {
destination = (StringBuffer) toAppendTo;
} else {
if (buffer == null) {
buffer = new StringBuffer();
}
destination = buffer;
destination.setLength(0);
}
if (dummy == null) {
dummy = new FieldPosition(0);
}
/*
* The format to use for each ordinate has been computed by 'initialize'. The format array length
* should match the number of dimensions in the given position if the DirectPosition is consistent
* with its CRS, but we will nevertheless verify has a paranoiac check. If there is no CRS, or if
* the DirectPosition dimension is (illegally) greater than the CRS dimension, then we will format
* the ordinate as a number.
*/
final int dimension = position.getDimension();
for (int i = 0; i < dimension; i++) {
double value = position.getOrdinate(i);
final Object object;
final Format f;
if (formats != null && i < formats.length) {
f = formats[i];
if (isNegative(i)) {
value = -value;
}
if (toFormatUnit != null) {
final UnitConverter c = toFormatUnit[i];
if (c != null) {
value = c.convert(value);
}
}
switch(types[i]) {
default:
object = Double.valueOf(value);
break;
case LONGITUDE:
object = new Longitude(value);
break;
case LATITUDE:
object = new Latitude(value);
break;
case ANGLE:
object = new Angle(value);
break;
case DATE:
object = new Date(Math.round(value) + epochs[i]);
break;
}
} else {
object = value;
f = getFormat(Number.class);
}
/*
* At this point we got the value to format together with the Format instance to use.
*/
if (i != 0) {
toAppendTo.append(separator);
}
if (f.format(object, destination, dummy) != toAppendTo) {
toAppendTo.append(destination);
destination.setLength(0);
}
if (unitSymbols != null && i < unitSymbols.length) {
final String symbol = unitSymbols[i];
if (symbol != null) {
toAppendTo.append(Characters.NO_BREAK_SPACE).append(symbol);
}
}
}
}
use of org.apache.sis.measure.Angle in project sis by apache.
the class AnglePattern method format.
/**
* Formats the given angle. This function is typically invoked for formatting only one value.
* But it is nevertheless defined as a matrix function for more efficient conversions of a bulk of angles.
*
* @param value the value to format.
* @param locale the target locale.
*/
String[][] format(final double[][] value, final Locale locale) {
final AngleFormat format = getAngleFormat(locale);
final String[][] text = new String[value.length][];
synchronized (format) {
for (int j = 0; j < value.length; j++) {
final double[] input = value[j];
if (input != null) {
// Paranoiac check.
final String[] result = new String[input.length];
for (int i = 0; i < input.length; i++) {
final double v = input[i];
final Angle angle;
switch(type) {
default:
angle = new Angle(v);
break;
case LATITUDE:
angle = new Latitude(v);
break;
case LONGITUDE:
angle = new Longitude(v);
break;
}
result[i] = format.format(angle);
}
text[j] = result;
}
}
}
return text;
}
use of org.apache.sis.measure.Angle in project sis by apache.
the class DefaultCartesianCS method ensurePerpendicularAxis.
/**
* Ensures that all axes are perpendicular.
*/
private void ensurePerpendicularAxis(final Map<String, ?> properties) throws IllegalArgumentException {
final int dimension = getDimension();
for (int i = 0; i < dimension; i++) {
final AxisDirection axis0 = getAxis(i).getDirection();
for (int j = i; ++j < dimension; ) {
final AxisDirection axis1 = getAxis(j).getDirection();
final Angle angle = CoordinateSystems.angle(axis0, axis1);
/*
* The angle may be null for grid directions (COLUMN_POSITIVE, COLUMN_NEGATIVE,
* ROW_POSITIVE, ROW_NEGATIVE). We conservatively accept those directions even if
* they are not really for Cartesian CS because we do not know the grid geometry.
*/
if (angle != null && Math.abs(angle.degrees()) != 90) {
throw new IllegalArgumentException(Resources.forProperties(properties).getString(Resources.Keys.NonPerpendicularDirections_2, axis0, axis1));
}
}
}
}
use of org.apache.sis.measure.Angle in project sis by apache.
the class CoordinateFormat method initialize.
/**
* Computes the value of transient fields from the given CRS.
*/
private void initialize(final CoordinateReferenceSystem crs) {
types = null;
formats = null;
units = null;
toFormatUnit = null;
unitSymbols = null;
epochs = null;
negate = 0;
lastCRS = crs;
if (crs == null) {
return;
}
/*
* If no CRS were specified, we will format everything as numbers. Working with null CRS
* is sometime useful because null CRS are allowed in DirectPosition according ISO 19107.
* Otherwise (if a CRS is given), infer the format subclasses from the axes.
*/
final CoordinateSystem cs = crs.getCoordinateSystem();
final int dimension = cs.getDimension();
final byte[] types = new byte[dimension];
final Format[] formats = new Format[dimension];
for (int i = 0; i < dimension; i++) {
final CoordinateSystemAxis axis = cs.getAxis(i);
final Unit<?> unit = axis.getUnit();
/*
* Formatter for angular units. Target unit is DEGREE_ANGLE.
* Type is LONGITUDE, LATITUDE or ANGLE depending on axis direction.
*/
if (Units.isAngular(unit)) {
byte type = ANGLE;
final AxisDirection dir = axis.getDirection();
if (AxisDirection.NORTH.equals(dir)) {
type = LATITUDE;
} else if (AxisDirection.EAST.equals(dir)) {
type = LONGITUDE;
} else if (AxisDirection.SOUTH.equals(dir)) {
type = LATITUDE;
negate(i);
} else if (AxisDirection.WEST.equals(dir)) {
type = LONGITUDE;
negate(i);
}
types[i] = type;
formats[i] = getFormat(Angle.class);
setConverter(dimension, i, unit.asType(javax.measure.quantity.Angle.class).getConverterTo(Units.DEGREE));
continue;
}
/*
* Formatter for temporal units. Target unit is MILLISECONDS.
* Type is DATE.
*/
if (Units.isTemporal(unit)) {
final CoordinateReferenceSystem t = CRS.getComponentAt(crs, i, i + 1);
if (t instanceof TemporalCRS) {
if (epochs == null) {
epochs = new long[dimension];
}
types[i] = DATE;
formats[i] = getFormat(Date.class);
epochs[i] = ((TemporalCRS) t).getDatum().getOrigin().getTime();
setConverter(dimension, i, unit.asType(Time.class).getConverterTo(Units.MILLISECOND));
if (AxisDirection.PAST.equals(axis.getDirection())) {
negate(i);
}
continue;
}
types[i] = TIME;
// Fallthrough: formatted as number.
}
/*
* Formatter for all other units. Do NOT set types[i] since it may have been set
* to a non-zero value by previous case. If not, the default value (zero) is the
* one we want.
*/
formats[i] = getFormat(Number.class);
if (unit != null) {
if (units == null) {
units = new Unit<?>[dimension];
}
units[i] = unit;
final String symbol = getFormat(Unit.class).format(unit);
if (!symbol.isEmpty()) {
if (unitSymbols == null) {
unitSymbols = new String[dimension];
}
unitSymbols[i] = symbol;
}
}
}
// Assign only on success.
this.types = types;
this.formats = formats;
}
use of org.apache.sis.measure.Angle in project sis by apache.
the class CoordinateFormat method parse.
/**
* Parses a coordinate from the given character sequence.
* This method presumes that the coordinate reference system is the {@linkplain #getDefaultCRS() default CRS}.
* The parsing begins at the {@linkplain ParsePosition#getIndex() index} given by the {@code pos} argument.
* If parsing succeeds, then the {@code pos} index is updated to the index after the last ordinate value and
* the parsed coordinate is returned. Otherwise (if parsing fails), the {@code pos} index is left unchanged,
* the {@code pos} {@linkplain ParsePosition#getErrorIndex() error index} is set to the index of the first
* unparsable character and an exception is thrown with a similar {@linkplain ParseException#getErrorOffset()
* error index}.
*
* @param text the character sequence for the coordinate to parse.
* @param pos the index where to start the parsing.
* @return the parsed coordinate (never {@code null}).
* @throws ParseException if an error occurred while parsing the coordinate.
*/
@Override
public DirectPosition parse(final CharSequence text, final ParsePosition pos) throws ParseException {
ArgumentChecks.ensureNonNull("text", text);
ArgumentChecks.ensureNonNull("pos", pos);
final int start = pos.getIndex();
final int length = text.length();
/*
* The NumberFormat, DateFormat and AngleFormat work only on String values, not on CharSequence.
* If the given text is not a String, we will convert an arbitrarily small section of the given
* text. Note that this will require to adjust the ParsePosition indices.
*/
final int offset;
final String asString;
final ParsePosition subPos;
if (text instanceof String) {
offset = 0;
subPos = pos;
asString = (String) text;
} else {
offset = start;
subPos = new ParsePosition(0);
asString = text.subSequence(start, Math.min(start + READ_AHEAD_LIMIT, length)).toString();
}
/*
* The Format instances to be used for each ordinate values is determined by the default CRS.
* If no such CRS has been specified, then we will parse everything as plain numbers.
*/
if (lastCRS != defaultCRS) {
initialize(defaultCRS);
}
final double[] ordinates;
Format format;
final Format[] formats = this.formats;
if (formats != null) {
format = null;
ordinates = new double[formats.length];
} else {
format = getFormat(Number.class);
ordinates = new double[DEFAULT_DIMENSION];
}
/*
* For each ordinate value except the first one, we need to skip the separator.
* If we do not find the separator, we may consider that we reached the coordinate
* end ahead of time. We currently allow that only for coordinate without CRS.
*/
for (int i = 0; i < ordinates.length; i++) {
if (i != 0) {
final int end = subPos.getIndex();
int index = offset + end;
while (!CharSequences.regionMatches(text, index, parseSeparator)) {
if (index < length) {
final int c = Character.codePointAt(text, index);
if (Character.isSpaceChar(c)) {
index += Character.charCount(c);
continue;
}
}
if (formats == null) {
pos.setIndex(index);
return new GeneralDirectPosition(Arrays.copyOf(ordinates, i));
}
pos.setIndex(start);
pos.setErrorIndex(index);
throw new LocalizedParseException(getLocale(), Errors.Keys.UnexpectedCharactersAfter_2, new CharSequence[] { text.subSequence(start, end), CharSequences.token(text, index) }, index);
}
subPos.setIndex(index + parseSeparator.length() - offset);
}
/*
* At this point 'subPos' is set to the beginning of the next ordinate to parse in 'asString'.
* Parse the value as a number, angle or date, as determined from the coordinate system axis.
*/
if (formats != null) {
format = formats[i];
}
@SuppressWarnings("null") final Object object = format.parseObject(asString, subPos);
if (object == null) {
/*
* If we failed to parse, build an error message with the type that was expected for that ordinate.
* If the given CharSequence was not a String, we may need to update the error index since we tried
* to parse only a substring.
*/
Class<?> type = Number.class;
if (types != null) {
switch(types[i]) {
case LONGITUDE:
type = Longitude.class;
break;
case LATITUDE:
type = Latitude.class;
break;
case ANGLE:
type = Angle.class;
break;
case DATE:
type = Date.class;
break;
}
}
pos.setIndex(start);
if (subPos != pos) {
pos.setErrorIndex(offset + subPos.getErrorIndex());
}
throw new LocalizedParseException(getLocale(), type, text, pos);
}
double value;
if (object instanceof Angle) {
value = ((Angle) object).degrees();
} else if (object instanceof Date) {
value = ((Date) object).getTime() - epochs[i];
} else {
value = ((Number) object).doubleValue();
}
/*
* The conversions and sign reversal applied below shall be in exact reverse order than
* in the 'format(…)' method. However we have one additional step compared to format(…):
* the unit written after the ordinate value may not be the same than the unit declared
* in the CRS axis, so we have to parse the unit and convert the value before to apply
* the reverse of 'format(…)' steps.
*/
if (units != null) {
final Unit<?> target = units[i];
if (target != null) {
final int base = subPos.getIndex();
int index = base;
/*
* Skip whitespaces using Character.isSpaceChar(…), not Character.isWhitespace(…),
* because we need to skip also the non-breaking space (Characters.NO_BREAK_SPACE).
* If we can not parse the unit after those spaces, we will revert to the original
* position (absence of unit will not be considered an error).
*/
while (index < asString.length()) {
final int c = asString.codePointAt(index);
if (Character.isSpaceChar(c)) {
index += Character.charCount(c);
continue;
}
subPos.setIndex(index);
final Object unit = getFormat(Unit.class).parseObject(asString, subPos);
if (unit == null) {
subPos.setIndex(base);
subPos.setErrorIndex(-1);
} else
try {
value = ((Unit<?>) unit).getConverterToAny(target).convert(value);
} catch (IncommensurableException e) {
index += offset;
pos.setIndex(start);
pos.setErrorIndex(index);
throw (ParseException) new ParseException(e.getMessage(), index).initCause(e);
}
break;
}
}
}
if (toFormatUnit != null) {
final UnitConverter c = toFormatUnit[i];
if (c != null) {
value = c.inverse().convert(value);
}
}
if (isNegative(i)) {
value = -value;
}
ordinates[i] = value;
}
final GeneralDirectPosition position = new GeneralDirectPosition(ordinates);
position.setCoordinateReferenceSystem(defaultCRS);
return position;
}
Aggregations