in endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/AngleFormat.java [1469:1795]
private Angle parse(final String source, final ParsePosition pos, final boolean spaceAsSeparator) {
double degrees;
double minutes = NaN;
double seconds = NaN;
final int length = source.length();
@SuppressWarnings("LocalVariableHidesMemberVariable")
final NumberFormat numberFormat = numberFormat();
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ BLOCK A: Assign values to `degrees`, `minutes` and `seconds` variables. │
// │ This block does not take the hemisphere field in account, and │
// │ values will need adjustment if decimal separator is missing. │
// │ The { } block is for restricting the scope of local variables. │
// └─────────────────────────────────────────────────────────────────────────┘
{
/*
* Extract the prefix, if any. If we find a degrees, minutes or seconds suffix
* before to have meet any number, we will consider that as a parsing failure.
*/
final int indexStart = pos.getIndex();
int index = skipSuffix(source, pos, PREFIX_FIELD);
if (index >= DEGREES_FIELD && index <= SECONDS_FIELD) {
pos.setErrorIndex(indexStart);
pos.setIndex(indexStart);
return null;
}
index = skipSpaces(source, pos.getIndex(), length);
pos.setIndex(index);
/*
* Parse the degrees field. If there is no separator between degrees, minutes
* and seconds, then the parsed number may actually include many fields (e.g.
* "DDDMMmmm"). The separation will be done later.
*/
Number fieldObject = numberFormat.parse(source, pos);
if (fieldObject == null) {
pos.setIndex(indexStart);
if (pos.getErrorIndex() < indexStart) {
pos.setErrorIndex(index);
}
return null;
}
degrees = fieldObject.doubleValue();
int indexEndField = pos.getIndex();
boolean missingDegrees = true;
BigBoss: switch (skipSuffix(source, pos, DEGREES_FIELD)) {
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED DEGREES
* ------------------------------------------
* The degrees value is followed by the prefix for angles.
* Stop parsing, since the remaining characters are for another angle.
*/
case PREFIX_FIELD: {
pos.setIndex(indexEndField);
break BigBoss;
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED DEGREES
* ------------------------------------------
* Found the seconds suffix instead of the degrees suffix. Move 'degrees'
* value to 'seconds' and stop parsing, since seconds are the last field.
*/
case SECONDS_FIELD: {
seconds = degrees;
degrees = NaN;
break BigBoss;
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED DEGREES
* ------------------------------------------
* No recognized suffix after degrees. If "spaces as separator" are allowed and
* a minutes field is expected after the degrees field, we will pretent that we
* found the minutes suffix. Otherwise stop parsing.
*/
default: {
if (!spaceAsSeparator || !isFallbackAllowed || minutesFieldWidth == 0) {
break BigBoss;
}
// Fall through for parsing minutes.
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED DEGREES
* ------------------------------------------
* After the degrees field, check if there is a minute field.
* We proceed as for degrees (parse a number, skip the suffix).
*/
case DEGREES_FIELD: {
final int indexStartField = pos.getIndex();
index = skipSpaces(source, indexStartField, length);
if (!spaceAsSeparator && index != indexStartField) {
break BigBoss;
}
pos.setIndex(index);
fieldObject = numberFormat.parse(source, pos);
if (fieldObject == null) {
pos.setIndex(indexStartField);
break BigBoss;
}
indexEndField = pos.getIndex();
minutes = fieldObject.doubleValue();
switch (skipSuffix(source, pos, (minutesFieldWidth != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED MINUTES
* ------------------------------------------
* Found the expected suffix, nothing special to do.
* Continue the outer switch for parsing seconds.
*/
case MINUTES_FIELD: {
break; // Continue outer switch for parsing seconds.
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED MINUTES
* ------------------------------------------
* Found the seconds suffix instead of the minutes suffix. Move 'minutes'
* value to 'seconds' and stop parsing, since seconds are the last field.
*/
case SECONDS_FIELD: {
seconds = minutes;
minutes = NaN;
break BigBoss;
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED MINUTES
* ------------------------------------------
* No suffix has been found. This is normal if the pattern doesn't specify
* a minutes field, in which case we reject the number that we just parsed.
* However if minutes were expected and space separators are allowed, then
* check for seconds.
*/
default: {
if (spaceAsSeparator && isFallbackAllowed && minutesFieldWidth != 0) {
break; // Continue outer switch for parsing seconds.
}
// Fall through for rejecting the minutes.
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED MINUTES
* ------------------------------------------
* Found the degrees suffix instead of the minutes suffix.
* This means that the number we have just read belong to an
* other angle. Stop the parsing before that number.
*/
case DEGREES_FIELD: {
pos.setIndex(indexStartField);
minutes = NaN;
break BigBoss;
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED MINUTES
* ------------------------------------------
* Found the prefix of another angle. Accept the number that
* we have just parsed despite the missing minutes suffix, and
* stop parsing before the prefix.
*/
case PREFIX_FIELD: {
pos.setIndex(indexEndField);
break BigBoss;
}
}
missingDegrees = false;
// Fall through for parsing the seconds.
}
/* -----------------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED DEGREES OR MINUTES
* -----------------------------------------------------
* If a minutes field was found without degrees, move the 'degrees'
* value to 'minutes'. Then try to parse the next number as seconds.
*/
case MINUTES_FIELD: {
if (missingDegrees) {
minutes = degrees;
degrees = NaN;
}
final int indexStartField = pos.getIndex();
index = skipSpaces(source, indexStartField, length);
if (!spaceAsSeparator && index != indexStartField) {
break BigBoss;
}
pos.setIndex(index);
fieldObject = numberFormat.parse(source, pos);
if (fieldObject == null) {
pos.setIndex(indexStartField);
break;
}
indexEndField = pos.getIndex();
seconds = fieldObject.doubleValue();
switch (skipSuffix(source, pos, (secondsFieldWidth != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED SECONDS
* ------------------------------------------
* Found the expected second suffix. We are done.
*/
case SECONDS_FIELD: {
break;
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED SECONDS
* ------------------------------------------
* No suffix has been found. This is normal if the pattern doesn't specify
* a seconds field, in which case we reject the number that we just parsed.
* However if seconds were expected and space separators are allowed, then
* accept the value.
*/
default: {
if (isFallbackAllowed && secondsFieldWidth != 0) {
break;
}
// Fall through for rejecting the seconds.
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED SECONDS
* ------------------------------------------
* Found the degrees or minutes suffix instead of the seconds suffix.
* This means that the number we have just read belong to another angle.
* Stop the parsing before that number.
*/
case MINUTES_FIELD:
case DEGREES_FIELD: {
pos.setIndex(indexStartField);
seconds = NaN;
break;
}
/* ------------------------------------------
* STRING ANALYSIS FOLLOWING PRESUMED SECONDS
* ------------------------------------------
* Found the prefix of another angle. Accept the number that
* we have just parsed despite the missing seconds suffix, and
* stop parsing before the prefix.
*/
case PREFIX_FIELD: {
pos.setIndex(indexEndField);
break BigBoss;
}
}
}
}
}
// ┌────────────────────────────────────────────────────────────────┐
// │ BLOCK B: Handle the case when there is no decimal separator. │
// │ Then combine the fields into a decimal degrees value. │
// └────────────────────────────────────────────────────────────────┘
if (isNegative(minutes)) {
seconds = -seconds;
}
if (isNegative(degrees)) {
minutes = -minutes;
seconds = -seconds;
}
if (!useDecimalSeparator) {
final double facteur = pow10(fractionFieldWidth);
if (secondsFieldWidth != 0) {
if (minutesSuffix == null && isNaN(seconds)) {
if (degreesSuffix == null && isNaN(minutes)) {
degrees /= facteur;
} else {
minutes /= facteur;
}
} else {
seconds /= facteur;
}
} else if (isNaN(seconds)) {
if (minutesFieldWidth != 0) {
if (degreesSuffix == null && isNaN(minutes)) {
degrees /= facteur;
} else {
minutes /= facteur;
}
} else if (isNaN(minutes)) {
degrees /= facteur;
}
}
}
/*
* If there is no separation between degrees and minutes fields (e.g. if the pattern
* is "DDDMMmmm"), then the 'degrees' variable contains degrees, minutes and seconds
* in sexagesimal units. We need to convert to decimal units.
*/
if (minutesSuffix == null && secondsFieldWidth != 0 && isNaN(seconds)) {
double facteur = pow10(secondsFieldWidth);
if (degreesSuffix == null && minutesFieldWidth != 0 && isNaN(minutes)) {
// ┌───────────────┐
// │ DDDMMSS.s │
// └───────────────┘
seconds = degrees;
minutes = truncate(degrees / facteur);
seconds -= minutes * facteur;
facteur = pow10(minutesFieldWidth);
degrees = truncate(minutes / facteur);
minutes -= degrees * facteur;
} else {
// ┌───────────────┐
// │ DDD°MMSS.s │
// └───────────────┘
seconds = minutes;
minutes = truncate(minutes / facteur);
seconds -= minutes*facteur;
}
} else if (degreesSuffix == null && minutesFieldWidth != 0 && isNaN(minutes)) {
// ┌─────────────┐
// │ DDDMM.m │
// └─────────────┘
final double facteur = pow10(minutesFieldWidth);
minutes = degrees;
degrees = truncate(degrees / facteur);
minutes -= degrees * facteur;
}
pos.setErrorIndex(-1);
if ( isNaN(degrees)) degrees = 0;
if (!isNaN(minutes)) degrees += minutes / 60;
if (!isNaN(seconds)) degrees += seconds / 3600;
// ┌─────────────────────────────────────────────────────┐
// │ BLOCK C: Check for hemisphere suffix (N, S, E or W) │
// │ after the angle string representation. │
// └─────────────────────────────────────────────────────┘
for (int index = pos.getIndex(); index < length;) {
final int c = source.codePointAt(index);
index += Character.charCount(c);
switch (Character.toUpperCase(c)) {
case NORTH: pos.setIndex(index); return new Latitude ( degrees);
case SOUTH: pos.setIndex(index); return new Latitude (-degrees);
case EAST : pos.setIndex(index); return new Longitude( degrees);
case WEST : pos.setIndex(index); return new Longitude(-degrees);
}
if (!Character.isSpaceChar(c)) { // Method shall be consistent with skipSpaces(…)
break;
}
}
return new Angle(degrees);
}