public DirectPosition parse()

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;
    }