/*******************************************************************************
* Gisgraphy Project
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
*
* Copyright 2008 Gisgraphy project
* David Masclet <davidmasclet@gisgraphy.com>
*
*
*******************************************************************************/
package com.gisgraphy.domain.geoloc.entity;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Transient;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gisgraphy.domain.valueobject.Constants;
import com.gisgraphy.domain.valueobject.FeatureCode;
import com.gisgraphy.domain.valueobject.GISSource;
import com.gisgraphy.domain.valueobject.SRID;
import com.gisgraphy.helper.FeatureClassCodeHelper;
import com.gisgraphy.helper.GeolocHelper;
import com.gisgraphy.helper.IntrospectionIgnoredField;
import com.gisgraphy.importer.ImporterConfig;
import com.gisgraphy.importer.LabelGenerator;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
/**
* GisFeature is the 'MotherClass of all Features. <b><u>IMPORTANT Note about
* admXCodes</u></b> : <br>
* The AdmCode can have the value from the Geonames CSV file or the value from
* the {@link #getAdm()}.getAdm1Code It depends on the option
* {@link ImporterConfig#isSyncAdmCodesWithLinkedAdmOnes()} in the
* env.properties file : Gisgraphy try to detect and correct errors in the CSV
* files. If an error is detected or wrong Adm code are set, the Adm for this
* GisFeature may not be the one that will be found from the Code in the CSV
* file. If syncAdmCodesWithLinkedAdmOnes is set to false, the Adm1Code will be
* set with the value of the CSV file (even if the no {@linkplain Adm} are
* found).<br>
* If syncAdmCodesWithLinkedAdmOnes is set to true then the Adm1Code will always
* be the same as the {@link #getAdm()}.getAdm1Code<br>
* It depends on what you expect for Adm1Code : ADM values
* (syncAdmCodesWithLinkedAdmOnes=true) or the CSV one
* (syncAdmCodesWithLinkedAdmOnes=false)
*
* @see ImporterConfig
* @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
*/
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@SequenceGenerator(name = "gisFeatureSequence", sequenceName = "gisfeature_sequence")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class GisFeature{
public static final int NAME_MAX_LENGTH= 200;
public static final String SHAPE_COLUMN_NAME = "shape";
public static final String LOCATION_COLUMN_NAME = "location";
protected static final Logger logger = LoggerFactory
.getLogger(GisFeature.class);
public static final int MAX_ALTERNATENAME_SIZE = 200;
/**
* Default Constructor, needed by cgLib
*/
public GisFeature() {
super();
}
/**
* Copy Constructor that populate the current {@link GisFeature} with the
* specified gisFeature fields<br>
*
* @param gisFeature
* The gisFeature from which we want to populate the
* {@linkplain GisFeature}
*/
public GisFeature(GisFeature gisFeature) {
super();
populate(gisFeature);
}
// @IntrospectionIgnoredField
private Long id;
private Long featureId;
private String name;
private String label;
private String labelPostal;
private Set<String> alternateLabels;
private String asciiName;
@IntrospectionIgnoredField
private Set<AlternateName> alternateNames;
private Point location;
private Point adminCentreLocation;
private String adm1Code;
private String adm2Code;
private String adm3Code;
private String adm4Code;
private String adm5Code;
private String adm1Name;
private String adm2Name;
private String adm3Name;
private String adm4Name;
private String adm5Name;
private String featureClass;
private String featureCode;
private String countryCode;
@IntrospectionIgnoredField
private Adm adm;
private Integer population;
private Integer elevation;
private Integer gtopo30;
private String timezone;
@IntrospectionIgnoredField
private Date modificationDate;
@IntrospectionIgnoredField
private GISSource source;
@IntrospectionIgnoredField
private Geometry shape;
private Set<ZipCode> zipCodes;
private String amenity;
private Long openstreetmapId;
private String isIn;
private String isInPlace;
private String isInAdm;
private String fullyQualifiedName;
@IntrospectionIgnoredField
private Long cityId;
/**
* if the associated city has been found by shape
* (only for sttistics and relevance purpose
*/
@IntrospectionIgnoredField
private boolean cityConfident = false;
@IntrospectionIgnoredField
@Transient
private Set<String> isInZip;
/**
* it is the best zipcode when there is several one (for instance when we set the zipcodes from the city and if there
* is several one we populate this field with the most accurate one
*/
private String zipCode;
/**
* @return the zipCode
*/
public String getZipCode() {
return zipCode;
}
/**
* @param zipCode the zipCode to set
*/
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
/**
* This field is only for relevance and allow to search for street<->cities in
* many alternateNames. It is not in stored
*/
@IntrospectionIgnoredField
private Set<String> isInCityAlternateNames;
/**
* The datastore id
*
* @return The datastoreId, it is not a domain value, just a technical One
*/
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gisFeatureSequence")
public Long getId() {
return this.id;
}
/**
* @return the country from the country code. Return null if the country Code
* is null or if no country is found
* @see #getCountryCode()
*/
@Transient
public String getCountry() {
return LabelGenerator.getInstance().getCountry(getCountryCode());
}
/**
* @return Returns the latitude (north-south) from the Location
* {@link #getLocation()}.
* @see #getLongitude()
* @see #getLocation()
*/
@Transient
public Double getLatitude() {
Double latitude = null;
if (this.location != null) {
latitude = this.location.getY();
}
return latitude;
}
/**
* @return Returns the latitude (north-south) for the admin centre of the GisFeature
* {@link #getLocation()}.
* @see #getLongitude()
* @see #getLocation()
*/
@Transient
public Double getAdminCentreLatitude() {
Double latitude = null;
if (this.adminCentreLocation != null) {
latitude = this.adminCentreLocation.getY();
}
return latitude;
}
/**
* Calculate the distance from the current GisFeature to the specified
* point.
*
* @param point
* the JTS point we want to calculate the distance from
* @return the calculated distance
* @see GeolocHelper#distance(Point, Point)
*/
@Transient
public double distanceTo(Point point) {
return GeolocHelper.distance(this.location, point);
}
/**
* Returns The JTS location point of the current GisFeature : The Geometry
* representation of the latitude, longitude. The Return type is a JTS
* point. The Location is calculate from the 4326 {@link SRID}
*
* @see SRID
* @see #getLongitude()
* @see #getLocation()
* @return The JTS Point
*/
@Column(nullable = false, name = LOCATION_COLUMN_NAME)
@Type(type = "org.hibernatespatial.GeometryUserType")
public Point getLocation() {
return location;
}
/**
* Returns The JTS location point of the admin centre of the GisFeature : The Geometry
* representation of the latitude, longitude. The Return type is a JTS
* point. The Location is calculate from the 4326 {@link SRID}
*
* @see SRID
* @see #getLongitude()
* @see #getLocation()
* @return The JTS Point
*/
@Column(nullable = true,name ="adminCentreLocation")
@Type(type = "org.hibernatespatial.GeometryUserType")
public Point getAdminCentreLocation() {
return adminCentreLocation;
}
/**
* Returns The JTS shape of the feature : The Return type is a JTS
* geometry.
*
* @see SRID
* @see #getLongitude()
* @see #getLocation()
* @return The JTS Point
*/
@Column(nullable = true, name = SHAPE_COLUMN_NAME)
@Type(type = "org.hibernatespatial.GeometryUserType")
public Geometry getShape() {
return shape;
}
/**
* @return Returns the longitude (east-west) from the Location
* {@link #getLocation()}.
* @see #getLongitude()
* @see #getLocation()
*/
@Transient
public Double getLongitude() {
Double longitude = null;
if (this.location != null) {
longitude = this.location.getX();
}
return longitude;
}
/**
* @return Returns the longitude (east-west) for the admin centre of the GisFeature
* {@link #getLocation()}.
* @see #getLongitude()
* @see #getLocation()
*/
@Transient
public Double getAdminCentreLongitude() {
Double longitude = null;
if (this.adminCentreLocation != null) {
longitude = this.adminCentreLocation.getX();
}
return longitude;
}
/**
* The modification date of the feature. The date must match the
* {@link Constants#GIS_DATE_PATTERN} This fields is not updated when saving
* or updating a GisFeature. This fields is to track changes in the
* gazetteers, not in the Datastore.
*
* @return The modification date of the feature
*/
public Date getModificationDate() {
return this.modificationDate;
}
/**
* Returns the Adm1Code for this feature. The only goal to have the Adm1Code
* directly in the GisFeature is for performance reasons :<br>
* It allows to have the adm1Code without loading all the Adm tree. See
* Important Notes for admXcode for {@link GisFeature}
*
* @return The Adm1Code for this Feature
*/
@Index(name = "adm1codeIndex")
@Column(length = 20)
public String getAdm1Code() {
return adm1Code;
}
/**
* @see #getAdm1Code()
* @param adm1Code
* The adm1code to set
*/
public void setAdm1Code(String adm1Code) {
this.adm1Code = adm1Code;
}
/**
* Returns the Adm2Code for this feature. The only goal to have the Adm2Code
* directly in the GisFeature is for performance reasons :<br>
* It allows to have the adm2Code without loading all the Adm tree. See
* Important Notes for admXcode for {@link GisFeature}
*
* @return The Adm2Code for this Feature
*/
// @Index(name = "adm2codeIndex")
@Column(length = 80)
public String getAdm2Code() {
return adm2Code;
}
/**
* @see #getAdm2Code()
* @param adm2Code
* the adm2code to set
*/
public void setAdm2Code(String adm2Code) {
this.adm2Code = adm2Code;
}
/**
* Returns the Adm3Code for this feature. The only goal to have the Adm3Code
* directly in the GisFeature is for performance reasons :<br>
* It allows to have the adm3Code without loading all the Adm tree. See
* Important Notes for admXcode for {@link GisFeature}
*
* @return The Adm3Code for this Feature
*/
//@Index(name = "adm3codeIndex")
@Column(length = 20)
public String getAdm3Code() {
return adm3Code;
}
/**
* @see #getAdm3Code()
* @param adm3Code
* the adm3code to set
*/
public void setAdm3Code(String adm3Code) {
this.adm3Code = adm3Code;
}
/**
* Returns the Adm4Code for this feature. The only goal to have the Adm4Code
* directly in the GisFeature is for performance reasons :<br>
* It allows to have the adm4Code without loading the all Adm tree. See
* Important Notes for admXcode for {@link GisFeature}
*
* @return The Adm4Code for this Feature
*/
//@Index(name = "adm4codeIndex")
@Column(length = 20)
public String getAdm4Code() {
return adm4Code;
}
/**
* @see #getAdm4Code()
* @param adm4Code
* the adm4code to set
*/
public void setAdm4Code(String adm4Code) {
this.adm4Code = adm4Code;
}
/**
* Returns the Adm4Code for this feature. The only goal to have the Adm4Code
* directly in the GisFeature is for performance reasons :<br>
* It allows to have the adm4Code without loading the all Adm tree. See
* Important Notes for admXcode for {@link GisFeature}
*
* @return The Adm4Code for this Feature
*/
//@Index(name = "adm4codeIndex")
@Column(length = 20)
public String getAdm5Code() {
return adm5Code;
}
/**
* @see #getAdm4Code()
* @param adm5Code
* the adm5Code to set
*/
public void setAdm5Code(String adm5Code) {
this.adm5Code = adm5Code;
}
/**
* Returns the name of the Adm of level 1 that this GisFeature is linked to.
* The only goal to have it directly in the gisFeature is for performance
* reasons :<br>
* It allows to have it without loading all the Adm tree
*
* @return The name of the Adm of level 1 that this GisFeature is linked to
*/
// @Index(name = "adm1NameIndex")
@Column(length = 200)
public String getAdm1Name() {
return adm1Name;
}
/**
* Set the name of the Adm of level 1 that this GisFeature is linked to
*
* @param adm1Name
* The name of the Adm of level 1 that this GisFeature is
* linked to
* @see #getAdm1Name()
*/
public void setAdm1Name(String adm1Name) {
this.adm1Name = adm1Name;
}
/**
* Returns the name of the Adm of level 2 that this GisFeature is linked to.
* The only goal to have it directly in the gisFeature is for performance
* reasons :<br>
* it allow to retrieve it without loading all the Adm tree
*
* @return The name of the Adm of level 2 that this GisFeature is linked to
*/
// @Index(name = "adm2NameIndex")
@Column(length = 200)
public String getAdm2Name() {
return adm2Name;
}
/**
* Set the name of the Adm of level 2 that this GisFeature is linked to
*
* @param adm2Name
* The name of the Adm of level 2 that this GisFeature is
* linked to
* @see #getAdm2Name()
*/
public void setAdm2Name(String adm2Name) {
this.adm2Name = adm2Name;
}
/**
* Returns the name of the Adm of level 3 that this GisFeature is linked to.
* The only goal to have it directly in the gisFeature is for performance
* reasons :<br>
* It allows to have it without loading all the Adm tree
*
* @return The name of the Adm of level 3 that this GisFeature is linked to
*/
// @Index(name = "adm3NameIndex")
@Column(length = 200)
public String getAdm3Name() {
return adm3Name;
}
/**
* Set the name of the Adm of level 3 that this GisFeature is linked to
*
* @param adm3Name
* The name of the Adm of level 3 that this GisFeature is
* linked to
* @see #getAdm3Name()
*/
public void setAdm3Name(String adm3Name) {
this.adm3Name = adm3Name;
}
/**
* Returns the name of the Adm of level 4 that this GisFeature is linked to.
* The only goal to have it directly in the gisFeature is for performance
* reasons :<br>
* It allows to have it without loading all the Adm tree
*
* @return The name of the Adm of level 4 that this GisFeature is linked to
*/
// @Index(name = "adm4NameIndex")
@Column(length = 200)
public String getAdm4Name() {
return adm4Name;
}
/**
* Set The name of the adm of level 4 that the GisFeature is linked to
*
* @param adm4Name
* The name of the adm of level 4 that the GisFeature is
* linked to
* @see #getAdm4Name()
*/
public void setAdm4Name(String adm4Name) {
this.adm4Name = adm4Name;
}
/**
* Returns the name of the Adm of level 5 that this GisFeature is linked to.
* The only goal to have it directly in the gisFeature is for performance
* reasons :<br>
* It allows to have it without loading all the Adm tree
*
* @return The name of the Adm of level 5 that this GisFeature is linked to
*/
// @Index(name = "adm4NameIndex")
@Column(length = 200)
public String getAdm5Name() {
return adm5Name;
}
/**
* Set The name of the adm of level 5 that the GisFeature is linked to
*
* @param adm5Name
* The name of the adm of level 5 that the GisFeature is
* linked to
* @see #getAdm5Name()
*/
public void setAdm5Name(String adm5Name) {
this.adm5Name = adm5Name;
}
/**
* @return The Adm with the higher Level that this GisFeature is linked to
* (the deeper in the Adm tree). See Important Notes for admXcode
* for {@link GisFeature}
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "adm", unique = false, referencedColumnName = "id", nullable = true)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Index(name = "gisfeatureadmindex")
public Adm getAdm() {
return adm;
}
/**
* @see #getAdm()
* @param adm
* The Adm with the higher Level that this GisFeature is
* linked to (the deeper in the Adm tree).
*/
public void setAdm(Adm adm) {
this.adm = adm;
}
/**
* @return A list of the {@link AlternateName}s for this GisFeature
*/
@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "gisFeature")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Fetch(FetchMode.SELECT)
public Set<AlternateName> getAlternateNames() {
return alternateNames;
}
/**
* @param alternateNames
* The {@link AlternateName}s for this GisFeature
*/
public void setAlternateNames(Set<AlternateName> alternateNames) {
this.alternateNames = alternateNames;
}
/**
* Do a double set : add the alternate name to the current GisFeature and set
* this GisFeature as the GisFeature of the specified AlternateName
*
* @param alternateName
* the alternateName to add
*/
public void addAlternateName(AlternateName alternateName) {
if (alternateName!=null){
if (alternateName.getName() != null && alternateName.getName().length() > MAX_ALTERNATENAME_SIZE){
logger.warn("alternate name "+ alternateName.getName()+" is too long");
} else {
alternateName.setGisFeature(this);
Set<AlternateName> currentAlternateNames = getAlternateNames();
if (currentAlternateNames == null) {
currentAlternateNames = new HashSet<AlternateName>();
}
currentAlternateNames.add(alternateName);
this.setAlternateNames(currentAlternateNames);
}
}
}
/**
* Do a double set : add (not replace !) the AlternateNames to the current
* GisFeature and for each alternatenames : set the current GisFeature as
* the GisFeature of the Alternate Names
*
* @param alternateNames
* The alternateNames list to add
*/
public void addAlternateNames(List<AlternateName> alternateNames) {
if (alternateNames != null) {
for (AlternateName alternateName : alternateNames) {
addAlternateName(alternateName);
}
}
}
/**
* @return The ASCII name of the current GisFeature
*/
@Column(nullable = true, length = 200)
//@Index(name = "gisFeatureAsciiNameIndex")
public String getAsciiName() {
return this.asciiName;
}
/**
* @see #getAsciiName()
* @param asciiname
* The ASCII name of the current GisFeature
*/
public void setAsciiName(String asciiname) {
this.asciiName = asciiname;
}
/**
* The country code is not mandatory because gisfeature like undersea does
* not belongs to a country
*
* @return The ISO 3166 alpha-2 letter code.
*/
@Index(name = "gisFeatureCountryindex")
@Column(length = 3)
public String getCountryCode() {
return countryCode;
}
/**
* @param countryCode
* The ISO 3166 alpha-2 letter code in upper Case (it will be
* automatically uppercased)
* @see #getCountryCode()
*/
public void setCountryCode(String countryCode) {
if (countryCode != null) {
this.countryCode = countryCode.toUpperCase();
} else {
this.countryCode = countryCode;
}
}
/**
* @return The elevation of this gisFeature (in meters)
*/
public Integer getElevation() {
return elevation;
}
/**
* @param elevation
* The elevation of this gisFeature (in meters)
* @see #getElevation()
*/
public void setElevation(Integer elevation) {
this.elevation = elevation;
}
/**
* @return the id of the city, we don't use the object because we don't want to link objects
* for performance reasons
*/
public Long getCityId() {
return cityId;
}
/**
* @param set the id of the city of this street
*/
public void setCityId(Long cityId) {
this.cityId = cityId;
}
public boolean isCityConfident() {
return cityConfident;
}
public void setCityConfident(boolean cityConfident) {
this.cityConfident = cityConfident;
}
/**
* @return The average elevation of 30'x30' (900mx900m) area in meters
*/
public Integer getGtopo30() {
return gtopo30;
}
/**
* @param gtopo30
* The average elevation of 30'x30' (900mx900m) area to
* set in meters
*/
public void setGtopo30(Integer gtopo30) {
this.gtopo30 = gtopo30;
}
/**
* @return The UTF-8 name for the current GisFeature
*/
@Column(nullable = false, length = 200)
//@Index(name = "gisFeatureNameIndex")
public String getName() {
return name;
}
/**
* @return the label that represent the street
*/
@Column(length = 500)
public String getLabel() {
return label;
}
/**
* @param label the label to set
*/
public void setLabel(String label) {
this.label = label;
}
/**
* @return the label that represent the Postal address
*/
@Column(length = 500)
public String getLabelPostal() {
return labelPostal;
}
/**
* @param labelPostal the labelPostal to set
*/
public void setLabelPostal(String labelPostal) {
this.labelPostal = labelPostal;
}
/**
* @return the alternate Labels of the streets
*/
@Transient
public Set<String> getAlternateLabels() {
return alternateLabels;
}
/**
* @param alternateLabels the alternate Labels to set
*/
public void setAlternateLabels(Set<String> alternateLabels) {
this.alternateLabels = alternateLabels;
}
public void addAlternateLabel(String alternateLabel) {
if (alternateLabel!=null){
Set<String> currentLabels = getAlternateLabels();
if (currentLabels == null) {
currentLabels = new HashSet<String>();
}
currentLabels.add(alternateLabel);
this.setAlternateLabels(currentLabels);
}
}
public void addAlternateLabels(Collection<String> alternateLabels) {
if (alternateLabels != null) {
for (String label : alternateLabels) {
addAlternateLabel(label);
}
}
}
/**
* @param name
* The UTF-8 name for the current GisFeature
*/
public void setName(String name) {
this.name = name;
}
/**
* @return The population (how many people are in) for this GisFeature
*/
public Integer getPopulation() {
return population;
}
/**
* @param population
* The population (how many people are in) of this GisFeature
*/
public void setPopulation(Integer population) {
this.population = population;
}
/**
* @return The source for the gisFeature. it tells from which files /
* gazetteers it has been imported
*/
@Enumerated(EnumType.STRING)
@Column(nullable = false)
public GISSource getSource() {
return source;
}
/**
* @param source
* The source for the gisFeature to be set
*/
public void setSource(GISSource source) {
this.source = source;
}
/**
* @return The timeZone for This GisFeature
* @see <a
* href="http://download.geonames.org/export/dump/timeZones.txt">Time
* zone</a>
*/
@Column(length = 40)
public String getTimezone() {
return timezone;
}
/**
* @param timezone
* The timeZone for This GisFeature
* @see #getTimezone()
*/
public void setTimezone(String timezone) {
this.timezone = timezone;
}
/**
* @param location
* The location of the GisFeature (JTS point)
* @see #getLocation()
* @see #getLatitude()
* @see #getLongitude()
*/
public void setLocation(Point location) {
this.location = location;
}
/**
* @param location
* The location of the GisFeature (JTS point)
* @see #getLocation()
* @see #getLatitude()
* @see #getLongitude()
*/
public void setAdminCentreLocation(Point adminCentreLocation) {
this.adminCentreLocation = adminCentreLocation;
}
/**
* @param shape
* The shape of the GisFeature (JTS)
* @see #getShape()
*/
public void setShape(Geometry shape) {
this.shape = shape;
}
/**
* @param modificationDate
* The Date of the Last Modification. This fields is not
* updated when saving or updating a GisFeature : This fields
* is to track changes in the gazetteers, not in the
* datastore. The date should match the
* {@link Constants#GIS_DATE_PATTERN}
* @see #getModificationDate()
*/
public void setModificationDate(Date modificationDate) {
this.modificationDate = modificationDate;
}
/**
* @param id
* The Id in the datastore. You should never call this
* method. It is the responsability of the dataStore
* @see #getId()
*/
public void setId(Long id) {
this.id = id;
}
/**
* @return The featureId for this GisFeature. it is a 'Domain value' not a
* datastore one. A featureId is unique and mandatory
*/
@Index(name = "gisFeatureIdIndex")
@Column(unique = true, nullable = false)
public Long getFeatureId() {
return featureId;
}
/**
* A featureId is unique and mandatory
*
* @param featureId
* The featureId for this GisFeature
*/
public void setFeatureId(Long featureId) {
this.featureId = featureId;
}
/**
* Whether the feature is a city, in the sense we define it in the
* {@link FeatureClassCodeHelper} class. It does not check the Class but the
* feature class and the feature code
*
* @return true if it is a city
*/
@Transient
public boolean isCity() {
return FeatureClassCodeHelper.isCity(this.featureClass,
this.featureCode);
}
/**
* Whether the feature is a coutry, in the sense we define it in the
* {@link FeatureClassCodeHelper} class. It does not check the Class but the
* feature class and the feature code
*
* @return true if it is a country
*/
@Transient
public boolean isCountry() {
return FeatureClassCodeHelper.isCountry(this.featureClass,
this.featureCode);
}
/**
* Whether the feature is an Adm, in the sense we define it in the
* {@link FeatureClassCodeHelper} class. It does not check the Class but the
* feature class and the feature code
*
* @return true if it is an ADM
*/
@Transient
public boolean isAdm() {
return FeatureClassCodeHelper.is_Adm(this.featureClass,
this.featureCode);
}
/**
* @return The feature class for this gisFeature (should always be in
* uppercase because setter automatically convert the feature class
* in upperCase). A feature class regroup some feature code
* @see <a href="http://www.geonames.org/export/codes.html">codes</a>
*/
// @Index(name = "gisFeatureFeatureClassindex")
@Column(length = 4)
public String getFeatureClass() {
return featureClass;
}
/**
* @param featureClass
* The feature class to set. <u>Note</u> The featureClass
* will automaticaly be uppercased.
* @see #getFeatureClass()
*/
public void setFeatureClass(String featureClass) {
if (featureClass != null) {
this.featureClass = featureClass.toUpperCase();
} else {
this.featureClass = featureClass;
}
}
/**
* @return The featureCode for the current GisFeature. A feature code
* represents a specific feature type. GisGraphy regroup some
* feature codes in an abstract Level called 'place type'. A place type
* regroup several featurecode.
* @see FeatureCode
*/
//@Index(name = "gisFeatureFeatureCodeindex")
@Column(length = 10)
public String getFeatureCode() {
return featureCode;
}
/**
* Populate all the field / association of the current gisFeature with The
* Value of The specified One.
*
* @param gisFeature
* the gisFeature to populate with
*/
public void populate(GisFeature gisFeature) {
if (gisFeature != null) {
this.setAdm(gisFeature.getAdm());
this.setAdm1Code(gisFeature.getAdm1Code());
this.setAdm2Code(gisFeature.getAdm2Code());
this.setAdm3Code(gisFeature.getAdm3Code());
this.setAdm4Code(gisFeature.getAdm4Code());
this.setAdm1Name(gisFeature.getAdm1Name());
this.setAdm2Name(gisFeature.getAdm2Name());
this.setAdm3Name(gisFeature.getAdm3Name());
this.setAdm4Name(gisFeature.getAdm4Name());
this.setAlternateNames(gisFeature.getAlternateNames());
if (getAlternateNames() != null) {
for (AlternateName alternateName : getAlternateNames()) {
alternateName.setGisFeature(this);
}
}
if (gisFeature.getAsciiName() != null) {
this.setAsciiName(gisFeature.getAsciiName().trim());
}
if (gisFeature.getCountryCode() != null) {
this.setCountryCode(gisFeature.getCountryCode().toUpperCase());
}
this.setElevation(gisFeature.getElevation());
this.setFeatureClass(gisFeature.getFeatureClass());
this.setFeatureCode(gisFeature.getFeatureCode());
this.setFeatureId(gisFeature.getFeatureId());
this.setGtopo30(gisFeature.getGtopo30());
this.setLocation(gisFeature.getLocation());
this.setModificationDate(gisFeature.getModificationDate());
this.setName(gisFeature.getName().trim());
this.setPopulation(gisFeature.getPopulation());
this.setSource(gisFeature.getSource());
this.setTimezone(gisFeature.getTimezone());
Set<ZipCode> zipCodes = gisFeature.getZipCodes();
if (zipCodes!= null){
for (ZipCode zipCode : zipCodes){
this.addZipCode(zipCode);
}
}
this.amenity=gisFeature.getAmenity();
this.openstreetmapId = gisFeature.getOpenstreetmapId();
}
}
/**
* @param featureCode
* The feature Code for this GisFeature
* <u>Note</u> The
* featureCode will automaticaly be uppercased
* @see #getFeatureCode()
*/
public void setFeatureCode(String featureCode) {
if (featureCode != null) {
this.featureCode = featureCode.toUpperCase();
} else {
this.featureCode = featureCode;
}
}
/**
* @return true If the gisFeature must be sync with the fullText search
* engine
*/
@Transient
public boolean isFullTextSearchable() {
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result
+ ((featureId == null) ? 0 : featureId.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final GisFeature other = (GisFeature) obj;
if (featureId == null) {
if (other.featureId != null) {
return false;
}
} else if (!featureId.equals(other.featureId)) {
return false;
}
return true;
}
/**
* @return a name with the Administrative division (but without Country)
* wrap {@link #getFullyQualifiedName(boolean)}
* @see #getFullyQualifiedName(boolean)
*/
@Column(length = 500)
public String getFullyQualifiedName() {
return fullyQualifiedName;
}
/**
* @param fullyQualifiedName the fullyQualifiedName to set
*/
public void setFullyQualifiedName(String fullyQualifiedName) {
this.fullyQualifiedName = fullyQualifiedName;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.getClass().getSimpleName() + "[" + getFeatureId() + "]["
+ getFeatureClass() + "." + getFeatureCode() + "][" + getName()
+ "]";
}
/**
* Do a double set : add the zip code to the current GisFeature and set
* this GisFeature as the GisFeature of the zipcode
* @param zipCode the zip code to add
*/
public void addZipCode(ZipCode zipCode) {
if (zipCode!=null){
Set<ZipCode> actualZipCodes = getZipCodes();
if (actualZipCodes == null) {
actualZipCodes = new HashSet<ZipCode>();
}
if (zipCode != null && zipCode.getCode()!=null && zipCode.getCode().length()>=3){
actualZipCodes.add(zipCode);
}
this.setZipCodes(actualZipCodes);
zipCode.setGisFeature(this);
}
}
/**
* Do a double set : add the zip codes to the current GisFeature and set
* this GisFeature as the GisFeature of the zipcodes
* * @param zipCodes the zip codes to add
*/
//TODO tests zip with null, and so on
public void addZipCodes(Collection<ZipCode> zipCodes) {
if (zipCodes != null) {
for (ZipCode zipCode : zipCodes) {
addZipCode(zipCode);
}
}
}
//we don't sync it, because we don't want join table, for the moment
@Transient
public Set<String> getIsInZip() {
return isInZip;
}
/**
* @param isInZip the zipcode where the street is located.
*/
public void setIsInZip(Set<String> isInZip) {
this.isInZip = isInZip;
}
/**
* add a zip
*/
public void addZip(String zip) {
Set<String> currentZips = getIsInZip();
if (currentZips == null) {
currentZips = new HashSet<String>();
}
if (zip != null && zip.length()>=3){
//there is some error in zip code and we avoid sip if it is less than 2 character
currentZips.add(zip);
}
this.setIsInZip(currentZips);
}
/**
* add zips
*/
public void addZips(Collection<String> zips) {
if (zips != null) {
for (String zip : zips) {
addZip(zip);
}
}
}
/**
* @return the zip codes for the city
*/
@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "gisFeature")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Fetch(FetchMode.SELECT)
//TODO tests zip
public Set<ZipCode> getZipCodes() {
return zipCodes;
}
/**
* Set The zipCodes for the city. IMPORTANT : if you set the zipCodes, you should do a double set
* : that means that you should set the gisfeature property for all the zip codes, if you don't
* you will get problems when saving entity in the datastore. Please use this method
* you should prefer the methods {@link #addZipCode(ZipCode)} and {@link #addZipCodes(Collection)}
* that do it automatically.
*
* @param zipCodes
* The zip codes for the City
*/
//TODO tests zip
public void setZipCodes(Set<ZipCode> zipCodes) {
this.zipCodes = zipCodes;
}
/**
* @return the amenity (typically the osm tag)
*/
public String getAmenity() {
return amenity;
}
/**
* @param amenity the amenity tag
* (typically the osm tag)
*/
public void setAmenity(String amenity) {
this.amenity = amenity;
}
/**
* @return the openstreetmap id.
* GisFeature has the openstreetmap field when it
* is a OSM POI
*/
public Long getOpenstreetmapId() {
return openstreetmapId;
}
/**
* @param openstreetmapId
* The openstreetmap id of the POI
*/
public void setOpenstreetmapId(Long openstreetmapId) {
this.openstreetmapId = openstreetmapId;
}
/**
* @return The city or state or any information where the street is located
*/
public String getIsIn() {
return isIn;
}
/**
* @param isIn
* The city or state or any information where the street is
* located
*/
public void setIsIn(String isIn) {
this.isIn = isIn;
}
/**
* @return the place where the street is located,
* this field is filled when {@link OpenStreetMap#isIn}
* is filled and we got more specific details (generally quarter, neighborhood)
*/
public String getIsInPlace() {
return isInPlace;
}
/**
* @param isInPlace the most precise information on where the street is located,
* generally quarter neighborhood
*/
public void setIsInPlace(String isInPlace) {
this.isInPlace = isInPlace;
}
@Transient
public void setAdmName(int level, String name){
switch (level) {
case 1:
setAdm1Name(name);
break;
case 2:
setAdm2Name(name);
break;
case 3:
setAdm3Name(name);
break;
case 4:
setAdm4Name(name);
break;
case 5:
setAdm5Name(name);
break;
default:
break;
}
}
@Transient
public String getAdmName(int level){
switch (level) {
case 1:
return getAdm1Name();
case 2:
return getAdm2Name();
case 3:
return getAdm3Name();
case 4:
return getAdm4Name();
case 5:
return getAdm5Name();
default:
return null;
}
}
/**
* @return the adm (aka administrative division) where the street is located.
*/
public String getIsInAdm() {
return isInAdm;
}
/**
* @param isInAdm the adm (aka administrative division) where the street is located
*/
public void setIsInAdm(String isInAdm) {
this.isInAdm = isInAdm;
}
/**
* This field is only for relevance and allow to search for street<->cities in
* many alternateNames. It is not in stored
*
*/
@Transient
public Set<String> getIsInCityAlternateNames() {
return isInCityAlternateNames;
}
public void setIsInCityAlternateNames(Set<String> isInCityAlternateNames) {
this.isInCityAlternateNames = isInCityAlternateNames;
}
public void addIsInCitiesAlternateName(String isInCityAlternateName) {
Set<String> currentCitiesAlternateNames = getIsInCityAlternateNames();
if (currentCitiesAlternateNames == null) {
currentCitiesAlternateNames = new HashSet<String>();
}
currentCitiesAlternateNames.add(isInCityAlternateName);
this.setIsInCityAlternateNames(currentCitiesAlternateNames);
}
public void addIsInCitiesAlternateNames(Collection<String> isInCityAlternateNames) {
if (isInCityAlternateNames != null) {
for (String isInCityAlternateName : isInCityAlternateNames) {
addIsInCitiesAlternateName(isInCityAlternateName);
}
}
}
}