public void format()

in endorsed/src/org.apache.sis.referencing.gazetteer/main/org/apache/sis/referencing/gazetteer/LocationFormat.java [199:420]


    public void format(final AbstractLocation location, final Appendable toAppendTo) throws IOException {
        ArgumentChecks.ensureNonNull("location", location);
        final Locale locale = getLocale(Locale.Category.DISPLAY);
        final Vocabulary vocabulary = Vocabulary.forLocale(locale);
        final var table = new TableAppender(toAppendTo, "│ ", columnSeparator, " │");
        table.setMultiLinesCells(true);
        /*
         * Location type.
         */
        table.appendHorizontalSeparator();
        final AbstractLocationType type = location.type();
        if (type != null) {
            append(table, vocabulary, Vocabulary.Keys.LocationType, toString(type.getName(), locale));
        }
        /*
         * Geographic identifier and alternative identifiers, if any.
         */
        append(table, vocabulary, Vocabulary.Keys.GeographicIdentifier, toString(location.getGeographicIdentifier(), locale));
        final Collection<? extends InternationalString> alt = location.getAlternativeGeographicIdentifiers();
        if (alt != null && !alt.isEmpty()) {
            boolean isFirst = true;
            vocabulary.appendLabel(Vocabulary.Keys.AlternativeIdentifiers, table);
            nextColumn(table);
            for (final InternationalString id : alt) {
                if (!isFirst) {
                    isFirst = false;
                    table.append(lineSeparator);
                }
                table.append(id);
            }
            table.nextLine();
        }
        /*
         * Extents (temporal and geographic). If an envelope exists and the CRS is not geographic,
         * then the envelope bounds will be appended on the same lines as the geographic bounds.
         * But before writing the bounding box and/or the envelope, check if they are redundant.
         * We may also need to change axis order (but not unit) of the envelope in order to match
         * the axis order of the geographic bounding box.
         */
        final var extent = new DefaultExtent(null, location.getGeographicExtent(), null, location.getTemporalExtent());
        final Range<Date> time = Extents.getTimeRange(extent);
        if (time != null) {
            append(table, vocabulary, Vocabulary.Keys.StartDate, toString(time.getMinValue()));
            append(table, vocabulary, Vocabulary.Keys.EndDate,   toString(time.getMaxValue()));
        }
        GeographicBoundingBox     bbox     = Extents.getGeographicBoundingBox(extent);
        Envelope                  envelope = location.getEnvelope();
        DirectPosition            position = location.getPosition();
        DirectPosition            geopos   = null;                      // Position in geographic CRS.
        CoordinateReferenceSystem crs      = null;                      // Envelope Coordinate Reference System.
        CoordinateReferenceSystem normCRS  = null;                      // CRS in conventional (x,y) axis order.
        Exception                 warning  = null;                      // If failed to transform envelope.
        try {
            if (envelope != null) {
                normCRS = normalize(crs = envelope.getCoordinateReferenceSystem());
                if (normCRS != crs) {
                    envelope = Envelopes.transform(envelope, normCRS);      // Should only change order and sign.
                }
            }
            if (position != null) {
                /*
                 * If only one of the envelope or the position objects specify a CRS, assume that the other object
                 * use the same CRS. If both the envelope and the position objects specify a CRS, the envelope CRS
                 * will have precedence and the "representative position" will be projected to that CRS.
                 */
                final CoordinateReferenceSystem posCRS = position.getCoordinateReferenceSystem();
                if (normCRS == null) {
                    normCRS = normalize(crs = posCRS);
                    if (normCRS != crs) {
                        envelope = Envelopes.transform(envelope, normCRS);  // Should only change order and sign.
                    }
                }
                if (bbox != null) {     // Compute geographic position only if there is a geographic bounding box.
                    GeographicCRS geogCRS = ReferencingUtilities.toNormalizedGeographicCRS(posCRS, false, false);
                    if (geogCRS != null) {
                        geopos = transform(position, posCRS, geogCRS);
                    }
                }
                position = transform(position, posCRS, normCRS);
            }
        } catch (FactoryException | TransformException e) {
            envelope = null;
            position = null;
            warning  = e;
        }
        /*
         * At this point we got the final geographic bounding box and/or envelope to write.
         * Since we will write the projected and geographic coordinates side-by-side in the same cells,
         * we need to format them in advance so we can compute their width for internal right-alignment.
         * We do the alignment ourselves instead of using TableAppender.setCellAlignment(ALIGN_RIGHT)
         * because we do not want (projected geographic) tuple to appear far on the right side if other
         * cells have long texts.
         */
        if (bbox != null || envelope != null) {
            final CoordinateSystem cs = (crs != null) ? crs.getCoordinateSystem() : null;
            String[]     geographic = null;
            String[]     projected  = null;
            String[]     unitSymbol = null;
            AngleFormat  geogFormat = null;
            NumberFormat projFormat = null;
            UnitFormat   unitFormat = null;
            int          maxGeogLength = 0;
            int          maxProjLength = 0;
            int          maxUnitLength = 0;
            boolean      showProj  = false;
            if (bbox != null || geopos != null) {
                geogFormat = (AngleFormat) getFormat(Angle.class);
                geographic = new String[BOUND_KEY.length];
                Arrays.fill(geographic, "");
            }
            if (envelope != null || position != null) {
                projFormat = (NumberFormat) getFormat(Number.class);
                unitFormat = (UnitFormat)   getFormat(Unit.class);
                projected  = new String[BOUND_KEY.length];
                unitSymbol = new String[BOUND_KEY.length];
                Arrays.fill(projected,  "");
                Arrays.fill(unitSymbol, "");
            }
            for (int i=0; i<BOUND_KEY.length; i++) {
                RoundingMode rounding = RoundingMode.FLOOR;
                double g = Double.NaN;
                double p = Double.NaN;
                int dimension = 0;
                switch (i) {
                    case 0: if (bbox     != null) g = bbox.getWestBoundLongitude();
                            if (envelope != null) p = envelope.getMinimum(0);
                            break;
                    case 2: if (bbox     != null) g = bbox.getEastBoundLongitude();
                            if (envelope != null) p = envelope.getMaximum(0);
                            rounding = RoundingMode.CEILING;
                            break;
                    case 3: if (bbox     != null) g = bbox.getSouthBoundLatitude();
                            if (envelope != null) p = envelope.getMinimum(1);
                            dimension = 1;
                            break;
                    case 5: if (bbox     != null) g = bbox.getNorthBoundLatitude();
                            if (envelope != null) p = envelope.getMaximum(1);
                            rounding = RoundingMode.CEILING;
                            dimension = 1;
                            break;
                    case 4: dimension = 1;                            // Fall through
                    case 1: if (geopos   != null) g = geopos  .getOrdinate(dimension);
                            if (position != null) p = position.getOrdinate(dimension);
                            rounding = RoundingMode.HALF_EVEN;
                            break;
                }
                if (!Double.isNaN(p)) {
                    showProj |= (g != p);
                    if (cs != null) {
                        final Unit<?> unit = cs.getAxis(dimension).getUnit();
                        if (unit != null) {
                            final int length = (unitSymbol[i] = unitFormat.format(unit)).length();
                            if (length > maxUnitLength) {
                                maxUnitLength = length;
                            }
                        }
                    }
                    try {
                        projFormat.setRoundingMode(rounding);
                    } catch (UnsupportedOperationException e) {
                        // Ignore.
                    }
                    final int length = (projected[i] = projFormat.format(p)).length();
                    if (length > maxProjLength) {
                        maxProjLength = length;
                    }
                }
                if (!Double.isNaN(g)) {
                    geogFormat.setRoundingMode(rounding);
                    final Angle angle = (dimension == 0) ? new Longitude(g) : new Latitude(g);
                    final int length = (geographic[i] = geogFormat.format(angle)).length();
                    if (length > maxGeogLength) {
                        maxGeogLength = length;
                    }
                }
            }
            if (!showProj) {
                projected  = null;          // All projected coordinates are identical to geographic ones.
                unitSymbol = null;
                maxProjLength = 0;
                maxUnitLength = 0;
            } else if (maxProjLength != 0) {
                if (maxUnitLength != 0) {
                    maxUnitLength++;
                }
                maxGeogLength += 4;         // Arbitrary space between projected and geographic coordinates.
            }
            /*
             * At this point all coordinates have been formatted in advance.
             */
            final String separator = (projected != null && geographic != null) ? "    —" : "";
            for (int i=0; i<BOUND_KEY.length; i++) {
                final String p = (projected  != null) ? projected [i] : "";
                final String u = (unitSymbol != null) ? unitSymbol[i] : "";
                final String g = (geographic != null) ? geographic[i] : "";
                if (!p.isEmpty() || !g.isEmpty()) {
                    vocabulary.appendLabel(BOUND_KEY[i], table);
                    nextColumn(table);
                    table.append(CharSequences.spaces(maxProjLength - p.length())).append(p);
                    table.append(CharSequences.spaces(maxUnitLength - u.length())).append(u).append(separator);
                    table.append(CharSequences.spaces(maxGeogLength - g.length())).append(g);
                    table.nextLine();
                }
            }
        }
        if (crs != null) {
            append(table, vocabulary, Vocabulary.Keys.CoordinateRefSys, IdentifiedObjects.getDisplayName(crs, locale));
        }
        /*
         * Organization responsible for defining the characteristics of the location instance.
         */
        final AbstractParty administrator = location.getAdministrator();
        if (administrator != null) {
            append(table, vocabulary, Vocabulary.Keys.Administrator, toString(administrator.getName(), locale));
        }
        table.appendHorizontalSeparator();
        table.flush();
        if (warning != null) {
            vocabulary.appendLabel(Vocabulary.Keys.Warnings, toAppendTo);
            toAppendTo.append(warning.toString()).append(lineSeparator);
        }
    }