use of org.opengis.util.GenericName in project sis by apache.
the class IdentifiedObjects method getUnicodeIdentifier.
/**
* Returns the first name, alias or identifier which is a
* {@linkplain CharSequences#isUnicodeIdentifier(CharSequence) valid Unicode identifier}.
* This method performs the search in the following order:
*
* <ul>
* <li><code>object.{@linkplain AbstractIdentifiedObject#getName() getName()}</code></li>
* <li><code>object.{@linkplain AbstractIdentifiedObject#getAlias() getAlias()}</code> in iteration order</li>
* <li><code>object.{@linkplain AbstractIdentifiedObject#getIdentifiers() getIdentifiers()}</code> in iteration order</li>
* </ul>
*
* @param object the identified object, or {@code null}.
* @return the first name, alias or identifier which is a valid Unicode identifier, or {@code null} if none.
*
* @see org.apache.sis.metadata.iso.ImmutableIdentifier
* @see org.apache.sis.metadata.iso.citation.Citations#getUnicodeIdentifier(Citation)
* @see org.apache.sis.util.CharSequences#isUnicodeIdentifier(CharSequence)
*/
public static String getUnicodeIdentifier(final IdentifiedObject object) {
if (object != null) {
Identifier identifier = object.getName();
if (identifier != null) {
// Paranoiac check.
final String code = identifier.getCode();
if (CharSequences.isUnicodeIdentifier(code)) {
return code;
}
}
final Iterator<GenericName> it = iterator(object.getAlias());
if (it != null)
while (it.hasNext()) {
GenericName alias = it.next();
if (alias != null && (alias = alias.tip()) != null) {
final String code = alias.toString();
if (CharSequences.isUnicodeIdentifier(code)) {
return code;
}
}
}
final Iterator<? extends Identifier> id = iterator(object.getIdentifiers());
if (id != null)
while (id.hasNext()) {
identifier = id.next();
if (identifier != null) {
// Paranoiac check.
final String code = identifier.getCode();
if (CharSequences.isUnicodeIdentifier(code)) {
return code;
}
}
}
}
return null;
}
use of org.opengis.util.GenericName in project sis by apache.
the class ParameterFormat method formatSummary.
/**
* Implementation of public {@code format(…)} methods for {@code NAME_SUMMARY} content level.
*
* @param objects the collection of objects to format.
* @param out the stream or buffer where to write the summary.
* @throws IOException if an error occurred will writing to the given appendable.
*/
private void formatSummary(final IdentifiedObject[] objects, final Appendable out) throws IOException {
final Vocabulary resources = Vocabulary.getResources(displayLocale);
/*
* Prepares all rows before we write them to the output stream, because not all
* identified objects may have names with the same scopes in the same order. We
* also need to iterate over all rows in order to know the number of columns.
*
* The first column is reserved for the identifier. We put null as a sentinal key for
* that column name, to be replaced later by "Identifier" in user locale. We can not
* put the localized strings in the map right now because they could conflict with
* the scope of some alias to be processed below.
*/
boolean hasIdentifiers = false;
final List<String[]> rows = new ArrayList<>();
final Map<String, Integer> columnIndices = new LinkedHashMap<>();
// See above comment for the meaning of "null" here.
columnIndices.put(null, 0);
if (preferredCodespaces != null) {
for (final String codespace : preferredCodespaces) {
columnIndices.put(codespace, columnIndices.size());
}
}
for (final IdentifiedObject object : objects) {
// Will growth later if needed.
String[] row = new String[columnIndices.size()];
/*
* Put the first identifier in the first column. If no identifier has a codespace in the list
* supplied by the user, then we will use the first identifier (any codespace) as a fallback.
*/
final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
if (identifiers != null) {
// Paranoiac check.
Identifier identifier = null;
for (final ReferenceIdentifier candidate : identifiers) {
if (candidate != null) {
// Paranoiac check.
if (isPreferredCodespace(candidate.getCodeSpace())) {
identifier = candidate;
// Format now.
break;
}
if (identifier == null) {
// To be used as a fallback if we find nothing better.
identifier = candidate;
}
}
}
if (identifier != null) {
row[0] = IdentifiedObjects.toString(identifier);
hasIdentifiers = true;
}
}
/*
* If the name's codespace is in the list of codespaces asked by the user, add that name
* in the current row and clear the 'name' locale variable. Otherwise, keep the 'name'
* locale variable in case we found no alias to format.
*/
ReferenceIdentifier name = object.getName();
if (name != null) {
// Paranoiac check.
final String codespace = name.getCodeSpace();
if (isPreferredCodespace(codespace)) {
row = putIfAbsent(resources, row, columnIndices, codespace, name.getCode());
name = null;
}
}
/*
* Put all aliases having a codespace in the list asked by the user.
*/
final Collection<GenericName> aliases = object.getAlias();
if (aliases != null) {
// Paranoiac check.
for (final GenericName alias : aliases) {
if (alias != null) {
// Paranoiac check.
final String codespace = NameToIdentifier.getCodeSpace(alias, displayLocale);
if (isPreferredCodespace(codespace)) {
row = putIfAbsent(resources, row, columnIndices, codespace, alias.tip().toInternationalString().toString(displayLocale));
name = null;
}
}
}
}
/*
* If no name and no alias have a codespace in the list of codespaces asked by the user,
* force the addition of primary name regardless its codespace.
*/
if (name != null) {
row = putIfAbsent(resources, row, columnIndices, name.getCodeSpace(), name.getCode());
}
rows.add(row);
}
/*
* Writes the table. The header will contain one column for each codespace in the order declared
* by the user. If the user did not specified any codespace, or if we had to write codespace not
* on the user list, then those codespaces will be written in the order we found them.
*/
final boolean hasColors = (colors != null);
final TableAppender table = new TableAppender(out, columnSeparator);
table.setMultiLinesCells(true);
table.appendHorizontalSeparator();
for (String codespace : columnIndices.keySet()) {
if (codespace == null) {
// Skip empty column.
if (!hasIdentifiers)
continue;
codespace = resources.getString(Vocabulary.Keys.Identifier);
}
if (hasColors) {
codespace = X364.BOLD.sequence() + codespace + X364.NORMAL.sequence();
}
table.append(codespace);
nextColumn(table);
}
table.appendHorizontalSeparator();
/*
* Writes row content.
*/
final int numColumns = columnIndices.size();
for (final String[] row : rows) {
for (int i = hasIdentifiers ? 0 : 1; i < numColumns; i++) {
if (i < row.length) {
final String name = row[i];
if (name != null) {
table.append(name);
}
}
nextColumn(table);
}
table.nextLine();
}
table.appendHorizontalSeparator();
table.flush();
}
use of org.opengis.util.GenericName in project sis by apache.
the class MatrixParametersAlphaNum method createElementDescriptor.
/**
* Creates a new parameter descriptor for a matrix element at the given indices. This method creates both the
* OGC name (e.g. {@code "elt_1_2"}) and the EPSG name (e.g. {@code "B2"}), together with the EPSG identifier
* (e.g. {@code "EPSG:8641"}) if it exists. See {@link org.apache.sis.internal.referencing.provider.Affine}
* for a table summarizing the parameter names and identifiers.
*/
@Override
protected ParameterDescriptor<Double> createElementDescriptor(final int[] indices) throws IllegalArgumentException {
/*
* For the EPSG convention, we recycle the names created for the WKT1 convention but interchanging
* the name with the alias (since our WKT1 convention adds the EPSG names as aliases). We use WKT1
* as the primary source because it is still very widely used, and works for arbitrary dimensions
* while the EPSG parameters are (officially) restricted to 3×3 matrices.
*/
if (WKT1 == this) {
/*
* Should never happen, but still unconditionally tested
* (no 'assert' keyword) for preventing stack overflow.
*/
throw new AssertionError();
}
// Really 'WKT1', not 'super'.
final ParameterDescriptor<Double> wkt = WKT1.getElementDescriptor(indices);
GenericName name = first(wkt.getAlias());
if (name == null) {
/*
* Outside the range of names (e.g. more than 26 rows or more than 10 columns).
* Returns the OGC name as-is.
*/
return wkt;
}
final Map<String, Object> properties = new HashMap<>(6);
/*
* Declare the EPSG identifier only for A0, A1, A2, B0, B1 and B2.
*/
if (isEPSG(indices)) {
// Put the name in EPSG namespace.
name = EPSGName.create(name.tip().toString());
final int code = (indices[0] == 0 ? Constants.EPSG_A0 : Constants.EPSG_B0) + indices[1];
properties.put(ParameterDescriptor.IDENTIFIERS_KEY, EPSGName.identifier(code));
}
properties.put(ParameterDescriptor.NAME_KEY, name);
properties.put(ParameterDescriptor.ALIAS_KEY, wkt.getName());
return new DefaultParameterDescriptor<>(properties, 0, 1, Double.class, null, null, wkt.getDefaultValue());
}
use of org.opengis.util.GenericName in project sis by apache.
the class CoordinateOperationMethods method getDomainOfValidity.
/**
* Returns the domain of validity for the given operation method.
* If no domain of validity is found, returns {@code null}.
*/
private DefaultGeographicBoundingBox getDomainOfValidity(final OperationMethod method) {
DefaultGeographicBoundingBox validity = null;
for (final GenericName name : method.getAlias()) {
final String tip = name.tip().toString();
final DefaultGeographicBoundingBox candidate = domainOfValidity.get(tip);
if (candidate != null) {
if (validity == null) {
validity = new DefaultGeographicBoundingBox(candidate);
} else {
validity.add(candidate);
}
}
}
return validity;
}
use of org.opengis.util.GenericName in project sis by apache.
the class FeatureFormat method format.
/**
* Formats the given object to the given stream of buffer.
* The object may be an instance of any of the following types:
*
* <ul>
* <li>{@code Feature}</li>
* <li>{@code FeatureType}</li>
* </ul>
*
* @throws IOException if an error occurred while writing to the given appendable.
*/
@Override
public void format(final Object object, final Appendable toAppendTo) throws IOException {
ArgumentChecks.ensureNonNull("object", object);
ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
/*
* Separate the Feature (optional) and the FeatureType (mandatory) instances.
*/
final DefaultFeatureType featureType;
final AbstractFeature feature;
if (object instanceof AbstractFeature) {
feature = (AbstractFeature) object;
featureType = feature.getType();
} else if (object instanceof DefaultFeatureType) {
featureType = (DefaultFeatureType) object;
feature = null;
} else {
throw new IllegalArgumentException(Errors.getResources(displayLocale).getString(Errors.Keys.UnsupportedType_1, object.getClass()));
}
/*
* Computes the columns to show. We start with the set of columns specified by setAllowedColumns(Set),
* then we check if some of those columns are empty. For example in many cases there is no attribute
* with characteritic, in which case we will ommit the whole "characteristics" column. We perform such
* check only for optional information, not for mandatory information like property names.
*/
final EnumSet<Column> visibleColumns = columns.clone();
{
boolean hasDesignation = false;
boolean hasCharacteristics = false;
boolean hasDeprecatedTypes = false;
for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
if (!hasDesignation) {
hasDesignation = propertyType.getDesignation() != null;
}
if (!hasCharacteristics && propertyType instanceof DefaultAttributeType<?>) {
hasCharacteristics = !((DefaultAttributeType<?>) propertyType).characteristics().isEmpty();
}
if (!hasDeprecatedTypes && propertyType instanceof Deprecable) {
hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated();
}
}
if (!hasDesignation)
visibleColumns.remove(Column.DESIGNATION);
if (!hasCharacteristics)
visibleColumns.remove(Column.CHARACTERISTICS);
if (!hasDeprecatedTypes)
visibleColumns.remove(Column.REMARKS);
}
/*
* Format the feature type name. In the case of feature type, format also the names of super-type
* after the UML symbol for inheritance (an arrow with white head). We do not use the " : " ASCII
* character for avoiding confusion with the ":" separator in namespaces. After the feature (type)
* name, format the column header: property name, type, cardinality and (default) value.
*/
toAppendTo.append(toString(featureType.getName()));
if (feature == null) {
// UML symbol for inheritance.
String separator = " ⇾ ";
for (final FeatureType parent : featureType.getSuperTypes()) {
toAppendTo.append(separator).append(toString(parent.getName()));
separator = SEPARATOR;
}
}
toAppendTo.append(getLineSeparator());
/*
* Create a table and format the header. Columns will be shown in Column enumeration order.
*/
final Vocabulary resources = Vocabulary.getResources(displayLocale);
final TableAppender table = new TableAppender(toAppendTo, columnSeparator);
table.setMultiLinesCells(true);
table.nextLine('─');
boolean isFirstColumn = true;
for (final Column column : visibleColumns) {
short key = column.resourceKey;
if (key == Vocabulary.Keys.Value && feature == null) {
key = Vocabulary.Keys.DefaultValue;
}
if (!isFirstColumn)
nextColumn(table);
table.append(resources.getString(key));
isFirstColumn = false;
}
table.nextLine();
table.nextLine('─');
/*
* Done writing the header. Now write all property rows. For each row, the first part in the loop
* extracts all information needed without formatting anything yet. If we detect in that part that
* a row has no value, it will be skipped if and only if that row is optional (minimum occurrence
* of zero).
*/
final StringBuffer buffer = new StringBuffer();
final FieldPosition dummyFP = new FieldPosition(-1);
final List<String> remarks = new ArrayList<>();
for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
Object value = null;
int cardinality = -1;
if (feature != null) {
if (!(propertyType instanceof DefaultAttributeType<?>) && !(propertyType instanceof DefaultAssociationRole) && !DefaultFeatureType.isParameterlessOperation(propertyType)) {
continue;
}
value = feature.getPropertyValue(propertyType.getName().toString());
if (value == null) {
if (propertyType instanceof FieldType && ((FieldType) propertyType).getMinimumOccurs() == 0) {
// If optional and no value, skip the full row.
continue;
}
cardinality = 0;
} else if (value instanceof Collection<?>) {
cardinality = ((Collection<?>) value).size();
} else {
cardinality = 1;
}
} else if (propertyType instanceof DefaultAttributeType<?>) {
value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
} else if (propertyType instanceof AbstractOperation) {
buffer.append(" = ");
try {
((AbstractOperation) propertyType).formatResultFormula(buffer);
} catch (IOException e) {
// Should never happen since we write in a StringBuffer.
throw new UncheckedIOException(e);
}
value = CharSequences.trimWhitespaces(buffer).toString();
buffer.setLength(0);
}
// The value to write in the type column.
final String valueType;
// AttributeType.getValueClass() if applicable.
final Class<?> valueClass;
// Negative values mean no cardinality.
final int minimumOccurs, maximumOccurs;
// Result of operation if applicable.
final AbstractIdentifiedType resultType;
if (propertyType instanceof AbstractOperation) {
// May be null
resultType = ((AbstractOperation) propertyType).getResult();
} else {
resultType = propertyType;
}
if (resultType instanceof DefaultAttributeType<?>) {
final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) resultType;
minimumOccurs = pt.getMinimumOccurs();
maximumOccurs = pt.getMaximumOccurs();
valueClass = pt.getValueClass();
valueType = getFormat(Class.class).format(valueClass, buffer, dummyFP).toString();
buffer.setLength(0);
} else if (resultType instanceof DefaultAssociationRole) {
final DefaultAssociationRole pt = (DefaultAssociationRole) resultType;
minimumOccurs = pt.getMinimumOccurs();
maximumOccurs = pt.getMaximumOccurs();
valueType = toString(DefaultAssociationRole.getValueTypeName(pt));
valueClass = AbstractFeature.class;
} else {
valueType = (resultType != null) ? toString(resultType.getName()) : "";
valueClass = null;
minimumOccurs = -1;
maximumOccurs = -1;
}
/*
* At this point we determined that the row should not be skipped
* and we got all information to format.
*/
isFirstColumn = true;
for (final Column column : visibleColumns) {
if (!isFirstColumn)
nextColumn(table);
isFirstColumn = false;
switch(column) {
/*
* Human-readable name of the property. May contains any characters (spaces, ideographs, etc).
* In many cases, this information is not provided and the whole column is skipped.
*/
case DESIGNATION:
{
final InternationalString d = propertyType.getDesignation();
if (d != null)
table.append(d.toString(displayLocale));
break;
}
/*
* Machine-readable name of the property (identifier). This information is mandatory.
* This name is usually shorter than the designation and should contain only valid
* Unicode identifier characters (e.g. no spaces).
*/
case NAME:
{
table.append(toString(propertyType.getName()));
break;
}
/*
* The base class or interface for all values in properties of the same type.
* This is typically String, Number, Integer, Geometry or URL.
*/
case TYPE:
{
table.append(valueType);
break;
}
/*
* Minimum and maximum number of occurrences allowed for this property.
* If we are formatting a Feature instead than a FeatureType, then the
* actual number of values is also formatted. Example: 42 ∈ [0 … ∞]
*/
case CARDINALITY:
{
table.setCellAlignment(TableAppender.ALIGN_RIGHT);
if (cardinality >= 0) {
table.append(getFormat(Integer.class).format(cardinality, buffer, dummyFP));
buffer.setLength(0);
}
if (maximumOccurs >= 0) {
if (cardinality >= 0) {
table.append(' ').append((cardinality >= minimumOccurs && cardinality <= maximumOccurs) ? '∈' : '∉').append(' ');
}
final Format format = getFormat(Integer.class);
table.append('[').append(format.format(minimumOccurs, buffer, dummyFP)).append(" … ");
buffer.setLength(0);
if (maximumOccurs != Integer.MAX_VALUE) {
table.append(format.format(maximumOccurs, buffer, dummyFP));
} else {
table.append('∞');
}
buffer.setLength(0);
table.append(']');
}
break;
}
/*
* If formatting a FeatureType, the default value. If formatting a Feature, the actual value.
* A java.text.Format instance dedicated to the value class is used if possible. In addition
* to types for which a java.text.Format may be available, we also have to check for other
* special cases. If there is more than one value, they are formatted as a coma-separated list.
*/
case VALUE:
{
table.setCellAlignment(TableAppender.ALIGN_LEFT);
// Null if valueClass is null.
final Format format = getFormat(valueClass);
final Iterator<?> it = CollectionsExt.toCollection(value).iterator();
String separator = "";
int length = 0;
while (it.hasNext()) {
value = it.next();
if (value != null) {
if (propertyType instanceof DefaultAssociationRole) {
final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
if (p != null) {
value = ((AbstractFeature) value).getPropertyValue(p);
if (value == null)
continue;
}
} else if (format != null && valueClass.isInstance(value)) {
// Null safe because of getFormat(valueClass) contract.
/*
* Convert numbers, dates, angles, etc. to character sequences before to append them in the table.
* Note that DecimalFormat writes Not-a-Number as "NaN" in some locales and as "�" in other locales
* (U+FFFD - Unicode replacement character). The "�" seems to be used mostly for historical reasons;
* as of 2017 the Unicode Common Locale Data Repository (CLDR) seems to define "NaN" for all locales.
* We could configure DecimalFormatSymbols for using "NaN", but (for now) we rather substitute "�" by
* "NaN" here for avoiding to change the DecimalFormat configuration and for distinguishing the NaNs.
*/
final StringBuffer t = format.format(value, buffer, dummyFP);
if (value instanceof Number) {
final float f = ((Number) value).floatValue();
if (Float.isNaN(f)) {
if ("�".contentEquals(t)) {
t.setLength(0);
t.append("NaN");
}
try {
final int n = MathFunctions.toNanOrdinal(f);
if (n > 0)
t.append(" #").append(n);
} catch (IllegalArgumentException e) {
// May happen if the NaN is a signaling NaN instead than a quiet NaN.
final int bits = Float.floatToRawIntBits(f);
if (bits != illegalNaN) {
illegalNaN = bits;
Logging.recoverableException(Logging.getLogger(Modules.FEATURE), FeatureFormat.class, "format", e);
}
}
}
}
value = t;
}
/*
* All values: the numbers, dates, angles, etc. formatted above, any other character sequences
* (e.g. InternationalString), or other kind of values - some of them handled in a special way.
*/
length = formatValue(value, table.append(separator), length);
buffer.setLength(0);
// Value is too long, abandon remaining iterations.
if (length < 0)
break;
separator = SEPARATOR;
length += SEPARATOR.length();
}
}
break;
}
/*
* Characteristics are optional information attached to some values. For example if a property
* value is a temperature measurement, a characteritic of that value may be the unit of measure.
* Characteristics are handled as "attributes of attributes".
*/
case CHARACTERISTICS:
{
if (propertyType instanceof DefaultAttributeType<?>) {
int length = 0;
String separator = "";
format: for (final DefaultAttributeType<?> ct : ((DefaultAttributeType<?>) propertyType).characteristics().values()) {
/*
* Format the characteristic name. We will append the value(s) later.
* We keep trace of the text length in order to stop formatting if the
* text become too long.
*/
final GenericName cn = ct.getName();
final String cs = toString(cn);
table.append(separator).append(cs);
length += separator.length() + cs.length();
Collection<?> cv = CollectionsExt.singletonOrEmpty(ct.getDefaultValue());
if (feature != null) {
/*
* Usually, the property 'cp' below is null because all features use the same
* characteristic value (for example the same unit of measurement), which is
* given by the default value 'cv'. Nevertheless we have to check if current
* feature overrides this characteristic.
*/
final Object cp = feature.getProperty(propertyType.getName().toString());
if (cp instanceof AbstractAttribute<?>) {
// Should always be true, but we are paranoiac.
AbstractAttribute<?> ca = ((AbstractAttribute<?>) cp).characteristics().get(cn.toString());
if (ca != null)
cv = ca.getValues();
}
}
/*
* Now format the value, separated from the name with " = ". Example: unit = m/s
* If the value accepts multi-occurrences, we will format the value between {…}.
* We use {…} because we may have more than one characteristic in the same cell,
* so we need a way to distinguish multi-values from multi-characteristics.
*/
final boolean multi = ct.getMaximumOccurs() > 1;
String sep = multi ? " = {" : " = ";
for (Object c : cv) {
length = formatValue(c, table.append(sep), length += sep.length());
// Value is too long, abandon remaining iterations.
if (length < 0)
break format;
sep = SEPARATOR;
}
separator = SEPARATOR;
if (multi && sep == SEPARATOR) {
table.append('}');
}
}
}
break;
}
case REMARKS:
{
if (org.apache.sis.feature.Field.isDeprecated(propertyType)) {
table.append(resources.getString(Vocabulary.Keys.Deprecated));
final InternationalString r = ((Deprecable) propertyType).getRemarks();
if (r != null) {
remarks.add(r.toString(displayLocale));
appendSuperscript(remarks.size(), table);
}
}
break;
}
}
}
table.nextLine();
}
table.nextLine('─');
table.flush();
/*
* If there is any remarks, write them below the table.
*/
final int n = remarks.size();
for (int i = 0; i < n; i++) {
appendSuperscript(i + 1, toAppendTo);
toAppendTo.append(' ').append(remarks.get(i)).append(lineSeparator);
}
}
Aggregations