in endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/CoordinateFormat.java [1549:1831]
public DirectPosition parse(final CharSequence text, final ParsePosition pos) throws ParseException {
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 coordinate 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) {
createFormats(defaultCRS);
}
final double[] coordinates;
Format format;
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Format[] formats = this.formats;
if (formats != null) {
format = null;
coordinates = new double[formats.length];
} else {
format = getDefaultFormat();
coordinates = new double[DEFAULT_DIMENSION];
}
/*
* For each coordinate 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 < coordinates.length; i++) {
skipSep: if (i != 0) {
final int end = subPos.getIndex(); // End of previous coordinate.
int index = offset + end;
while (index < length) {
if (parseSeparator.isEmpty()) {
final int next = CharSequences.skipLeadingWhitespaces(text, index, length);
if (next > index) {
subPos.setIndex(next - offset);
break skipSep;
}
} else {
if (CharSequences.regionMatches(text, index, parseSeparator)) {
subPos.setIndex(index + parseSeparator.length() - offset);
break skipSep;
}
}
final int c = Character.codePointAt(text, index);
if (!Character.isSpaceChar(c)) break;
index += Character.charCount(c);
}
/*
* No separator found. If no CRS was specified (in which case we don't know how many coordinates
* were expected), then stop parsing and return whatever number of coordinates we got. Otherwise
* (another coordinate was expected) consider we have a too short string or unexpected characters.
*/
if (formats == null) {
pos.setIndex(index);
return new GeneralDirectPosition(Arrays.copyOf(coordinates, i));
}
pos.setIndex(start);
pos.setErrorIndex(index);
final CharSequence previous = text.subSequence(start, end);
final CharSequence found = CharSequences.token(text, index);
final short key;
final CharSequence[] args;
if (found.length() != 0) {
key = Errors.Keys.UnexpectedCharactersAfter_2;
args = new CharSequence[] {previous, found};
} else {
key = Errors.Keys.UnexpectedEndOfString_1;
args = new CharSequence[] {previous};
}
throw new LocalizedParseException(getLocale(), key, args, index);
}
/*
* At this point `subPos` is set to the beginning of the next coordinate 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") // `format` was initially null only if `formats` is non-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 coordinate.
* 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;
case INDEX: type = Long.class; break;
}
}
pos.setIndex(start);
if (subPos != pos) {
pos.setErrorIndex(offset + subPos.getErrorIndex());
}
throw new LocalizedParseException(getLocale(), type, text, pos);
}
/*
* The value part (number, angle or date) has been parsed successfully.
* Get the numerical value. The unit of measurement may not be the same
* than the one expected by the CRS (we will convert later).
*/
double value;
if (object instanceof Angle) {
value = ((Angle) object).degrees();
} else if (object instanceof Date) {
final Duration d = JDK23.until(epochs[i], ((Date) object).toInstant());
value = d.getSeconds() + (d.getNano() / (double) Constants.NANOS_PER_SECOND);
} else {
value = ((Number) object).doubleValue();
}
/*
* The value sign may need to be adjusted if the value is followed by a direction symbol
* such as "N", "E" or "SW". Get the symbols that are allowed for current coordinate.
* We will check for their presence after the unit symbol, or immediately after the value
* if there is no unit symbol.
*/
String direction = null;
String opposite = null;
if (directionSymbols != null) {
direction = directionSymbols[i*2 ];
opposite = directionSymbols[i*2 + 1];
}
/*
* The unit written after the coordinate value may not be the same as the unit declared
* in the CRS axis, so we have to parse the unit and convert the value before to apply the
* change of sign.
*/
final Unit<?> target;
UnitConverter toCRS = null;
parseUnit: if (units != null && (target = units[i]) != null) {
final int base = subPos.getIndex();
int index = base; // Will become start index of unit symbol.
/*
* Skip whitespaces using Character.isSpaceChar(…), not Character.isWhitespace(…),
* because we need to skip also the non-breaking space (Characters.NO_BREAK_SPACE).
* If we cannot parse the unit after those spaces, we will revert to the original
* position + spaces skipped (absence of unit will not be considered an error).
*/
int c;
for (;;) {
if (index >= asString.length()) {
break parseUnit; // Found only spaces until end of string.
}
c = asString.codePointAt(index);
if (!Character.isSpaceChar(c)) break;
index += Character.charCount(c);
}
/*
* Now the `index` should be positioned on the first character of the unit symbol.
* Before to parse the unit, verify if a direction symbol is found after the unit.
* We need to do this check because unit symbol and direction symbol are separated
* by a no-break space, which causes `UnitFormat` to try to parse them together as
* a unique unit symbol.
*/
int stopAt = index; // Will become stop index of unit symbol.
int nextAt = -1; // Will become start index of next coordinate.
checkDirection: if (direction != null) {
do {
stopAt += Character.charCount(c);
if (stopAt >= asString.length()) {
break checkDirection;
}
c = asString.codePointAt(stopAt);
} while (!Character.isSpaceChar(c));
/*
* Found the first space character, which may be a no-break space.
* Check for direction symbol here. This strategy is based on the
* fact that the direction symbol starts with a no-break space.
*/
if (asString.regionMatches(true, stopAt, direction, 0, direction.length())) {
nextAt = stopAt + direction.length();
} else if (asString.regionMatches(true, stopAt, opposite, 0, opposite.length())) {
nextAt = stopAt + opposite.length();
value = -value;
}
}
/*
* Parse the unit symbol now. The `nextAt` value determines whether a direction symbol
* has been found, in which case we need to exclude the direction from the text parsed
* by `UnitFormat`.
*/
final Format f = getFormat(Unit.class);
final Object unit;
try {
if (nextAt < 0) {
subPos.setIndex(index);
unit = f.parseObject(asString, subPos); // Let `UnitFormat` decide where to stop parsing.
} else {
unit = f.parseObject(asString.substring(index, stopAt));
subPos.setIndex(nextAt);
direction = opposite = null;
}
if (unit == null) {
subPos.setIndex(base);
subPos.setErrorIndex(-1);
} else {
toCRS = ((Unit<?>) unit).getConverterToAny(target);
}
} catch (ParseException | IncommensurableException e) {
index += offset;
pos.setIndex(start);
pos.setErrorIndex(index);
if (e instanceof ParseException) {
throw (ParseException) e;
}
throw (ParseException) new ParseException(e.getMessage(), index).initCause(e);
}
} else {
/*
* If we reach this point, the format at dimension `i` uses an implicit unit of measurement
* such as degrees for `AngleFormat` or milliseconds for `DateFormat`. Only for those cases
* (identified by `units[i] == null`), use the conversion declared in `toFormatUnit` array.
*/
if (toFormatUnit != null) {
toCRS = toFormatUnit[i];
if (toCRS != null) {
toCRS = toCRS.inverse();
}
}
}
/*
* At this point either the unit of measurement has been parsed, or there is no unit.
* If the direction symbol ("E", "N", "SW", etc.) has not been found before, check now.
*/
if (direction != null) {
int index = subPos.getIndex();
if (asString.regionMatches(true, index, direction, 0, direction.length())) {
index += direction.length();
} else if (asString.regionMatches(true, index, opposite, 0, opposite.length())) {
index += opposite.length();
value = -value;
}
subPos.setIndex(index);
}
/*
* The conversions and sign reversal applied below shall be in reverse order
* than the operations applied by the `format(…)` method.
*/
if (toCRS != null) {
value = toCRS.convert(value);
}
if (isNegative(i)) {
value = -value;
}
coordinates[i] = value;
}
/*
* If accuracy information is appended after the coordinates (e.g. " ± 3 km"), skip that text.
*/
if (accuracyText != null) {
final int index = subPos.getIndex();
final int lg = accuracyText.length();
if (asString.regionMatches(true, index, accuracyText, 0, lg)) {
subPos.setIndex(index + lg);
}
}
final GeneralDirectPosition position = new GeneralDirectPosition(coordinates);
position.setCoordinateReferenceSystem(defaultCRS);
return position;
}