blob: ac8490e7f792eff458413c26097a45724a39b86e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing.gazetteer;
import java.util.Collection;
import java.util.Collections;
import org.opengis.metadata.extent.TemporalExtent;
import org.opengis.metadata.extent.GeographicExtent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.coordinate.Position;
import org.opengis.util.InternationalString;
import org.apache.sis.util.iso.Types;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.GeneralDirectPosition;
// Specific to the main branch:
import org.apache.sis.metadata.iso.citation.AbstractParty;
/**
* Identifiable geographic place. A geographic place may be identified by a name (for example “Eiffel Tower”),
* by a postcode, or any other method specified by the {@linkplain ModifiableLocationType#getIdentifications()
* location type identifications}.
*
* <p>ISO 19112 describes the following properties as mandatory, but Apache SIS relaxes this restriction by
* providing default values (possibly {@code null}) in most cases:</p>
* <ul>
* <li><b>geographic identifier</b> (the value, for example a name or code)</li>
* <li><b>geographic extent</b> (the position of the identified thing)</li>
* <li><b>administrator</b> (who is responsible for this identifier)</li>
* <li><b>location type</b> (which specifies the nature of the identifier and its associated geographic location)</li>
* </ul>
*
* The following properties are optional:
* <ul>
* <li><b>temporal extent</b></li>
* <li><b>alternative geographic identifier</b></li>
* <li><b>envelope</b> (an Apache SIS extension not in ISO 19112 standard)</li>
* <li><b>position</b> (mandatory if the geographic identifier contains insufficient information to identify location)</li>
* <li><b>parent location instance</b></li>
* <li><b>child location instance</b></li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
*
* @see ModifiableLocationType
* @see ReferencingByIdentifiers
* @see LocationFormat
*
* @since 0.8
*/
public abstract class AbstractLocation {
/**
* The description of the nature of this geographic identifier, or {@code null} if unspecified.
*
* <p>This field is non-final for sub-class constructors convenience,
* but its value should not be changed after {@code Location} construction.</p>
*
* @see #getLocationType()
*/
private AbstractLocationType type;
/**
* The geographic identifier, or {@code null} if unspecified.
*
* @see #getGeographicIdentifier()
*/
private final CharSequence identifier;
/**
* Creates a new location for the given geographic identifier.
* This constructor accepts {@code null} arguments, but this is not recommended.
*
* <div class="warning"><b>Upcoming API change — generalization</b><br>
* in a future SIS version, the type of {@code type} argument may be generalized to the
* {@code org.opengis.referencing.gazetteer.Location} interface.
* This change is pending GeoAPI revision.</div>
*
* @param type the description of the nature of this geographic identifier.
* @param identifier the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
*/
protected AbstractLocation(final ModifiableLocationType type, final CharSequence identifier) {
this.type = type;
this.identifier = identifier;
}
/**
* Temporary workaround for the lack of {@code LocationType} interface and {@code AbstractLocationType}
* being package-private. We do not want to expose {@code AbstractLocationType} in public API for now.
*/
AbstractLocation(final AbstractLocationType type, final CharSequence identifier) {
this.type = type;
this.identifier = identifier;
}
/**
* Sets the location type to the unique child of current type. This method should be invoked
* only when the caller know that there is at least one children and that the children class
* is {@link FinalLocationType} (for performance reason).
*/
final void setTypeToChild() {
type = ((FinalLocationType) type).children.get(0);
}
/**
* Returns a unique identifier for the location instance. The methods of identifying locations is specified
* by the {@linkplain ModifiableLocationType#getIdentifications() location type identifications}.
*
* In order to ensure that a geographic identifier is unique within a wider geographic domain,
* the geographic identifier may need to include an identifier of an instance of a parent location type,
* for example “Paris, Texas”.
*
* <h4>Examples</h4>
* If {@code LocationType.getIdentifications()} contain “name”, then geographic identifiers may be country
* names like “Japan” or “France”, or places like “Eiffel Tower”. If location type identifications contain
* “code”, then geographic identifiers may be “SW1P 3AD” postcode.
*
* @return unique identifier for the location instance.
*
* @see ModifiableLocationType#getIdentifications()
*/
public InternationalString getGeographicIdentifier() {
return Types.toInternationalString(identifier);
}
/**
* Returns other identifier(s) for the location instance.
* The default implementation returns an empty set.
*
* @return other identifier(s) for the location instance, or an empty collection if none.
*/
public Collection<? extends InternationalString> getAlternativeGeographicIdentifiers() {
return Collections.emptySet();
}
/**
* Returns the date of creation of this version of the location instance.
* The default implementation returns {@code null}.
*
* @return date of creation of this version of the location instance, or {@code null} if none.
*/
public TemporalExtent getTemporalExtent() {
return null;
}
/**
* Returns a description of the location instance. This properties is mandatory according ISO 19112,
* but Apache SIS nevertheless allows {@code null} value. If non-null, SIS implementations typically
* provide instances of {@linkplain org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
* geographic bounding boxes}.
*
* @return description of the location instance, or {@code null} if none.
*
* @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
* @see org.apache.sis.metadata.iso.extent.DefaultBoundingPolygon
*/
public GeographicExtent getGeographicExtent() {
return null;
}
/**
* Returns an envelope that encompass the location. This property is partially redundant with
* {@link #getGeographicExtent()}, except that this method allows envelopes in non-geographic CRS.
*
* <p>The default implementation copies the {@link #getGeographicExtent()} in a new envelope associated
* to the {@linkplain org.apache.sis.referencing.CommonCRS#defaultGeographic() default geographic CRS}.</p>
*
* @return envelope that encompass the location, or {@code null} if none.
*/
public Envelope getEnvelope() {
final GeographicExtent extent = getGeographicExtent();
return (extent instanceof GeographicBoundingBox) ? new Envelope2D((GeographicBoundingBox) extent) : null;
}
/**
* Returns coordinates of a representative point for the location instance.
* This is typically (but not necessarily) the centroid of the location instance.
*
* <p>The default implementation returns the {@linkplain #getEnvelope()} median position.</p>
*
* @return coordinates of a representative point for the location instance, or {@code null} if none.
*/
public Position getPosition() {
final Envelope envelope = getEnvelope();
if (envelope == null) {
return null;
}
final int dimension = envelope.getDimension();
final GeneralDirectPosition pos = new GeneralDirectPosition(dimension);
pos.setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem());
for (int i=0; i<dimension; i++) {
pos.setCoordinate(i, envelope.getMedian(i));
}
return pos;
}
/**
* Returns a description of the nature of this geographic identifier.
*
* <div class="warning"><b>Upcoming API change — generalization</b><br>
* in a future SIS version, the type of returned element may be generalized to the
* {@code org.opengis.referencing.gazetteer.Location} interface.
* This change is pending GeoAPI revision.
* If applied, this method will be made non-final.</div>
*
* @return the nature of the identifier and its associated geographic location.
*/
public final ModifiableLocationType getLocationType() {
return ModifiableLocationTypeAdapter.copy(type);
}
/**
* Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
* This workaround will be removed in a future SIS version if the location
* type interface is introduced in a future GeoAPI version.
*/
final AbstractLocationType type() {
return type;
}
/**
* Returns the organization responsible for defining the characteristics of the location instance.
* The default implementation returns the {@linkplain ModifiableLocationType#getOwner() owner}.
*
* <div class="warning"><b>Upcoming API change — generalization</b><br>
* in a future SIS version, the type of returned element may be generalized to the
* {@code org.opengis.metadata.citation.Party} interface. This change is pending
* GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
*
* @return organization responsible for defining the characteristics of the location instance, or {@code null}.
*
* @see ModifiableLocationType#getOwner()
* @see ReferencingByIdentifiers#getOverallOwner()
*/
public AbstractParty getAdministrator() {
return (type != null) ? type.getOwner() : null;
}
/**
* Returns location instances of a different location type, for which this location instance is a sub-division.
* The default implementation returns an empty list.
*
* <div class="warning"><b>Upcoming API change — generalization</b><br>
* in a future SIS version, the type of collection elements may be generalized
* to the {@code org.opengis.referencing.gazetteer.Location} interface.
* This change is pending GeoAPI revision.</div>
*
* @return parent locations, or an empty collection if none.
*
* @see ModifiableLocationType#getParents()
*/
public Collection<? extends AbstractLocation> getParents() {
return Collections.emptyList();
}
/**
* Returns location instances of a different location type which subdivides this location instance.
* The default implementation returns an empty list.
*
* <div class="warning"><b>Upcoming API change — generalization</b><br>
* in a future SIS version, the type of collection elements may be generalized
* to the {@code org.opengis.referencing.gazetteer.Location} interface.
* This change is pending GeoAPI revision.</div>
*
* @return child locations, or an empty collection if none.
*
* @see ModifiableLocationType#getChildren()
*/
public Collection<? extends AbstractLocation> getChildren() {
return Collections.emptyList();
}
/**
* Returns a string representation of this location.
* This representation is mostly for debugging purpose and may change in any future Apache SIS version.
*
* @return a string representation of this location for debugging purpose.
*/
@Override
public String toString() {
synchronized (LocationFormat.INSTANCE) {
return LocationFormat.INSTANCE.format(this);
}
}
}