View Javadoc

1   /*******************************************************************************
2    *   Gisgraphy Project 
3    * 
4    *   This library is free software; you can redistribute it and/or
5    *   modify it under the terms of the GNU Lesser General Public
6    *   License as published by the Free Software Foundation; either
7    *   version 2.1 of the License, or (at your option) any later version.
8    * 
9    *   This library is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   *   Lesser General Public License for more details.
13   * 
14   *   You should have received a copy of the GNU Lesser General Public
15   *   License along with this library; if not, write to the Free Software
16   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
17   * 
18   *  Copyright 2008  Gisgraphy project 
19   *  David Masclet <davidmasclet@gisgraphy.com>
20   *  
21   *  
22   *******************************************************************************/
23  package com.gisgraphy.domain.geoloc.entity;
24  
25  import java.util.ArrayList;
26  import java.util.Date;
27  import java.util.List;
28  
29  import javax.persistence.CascadeType;
30  import javax.persistence.Column;
31  import javax.persistence.Entity;
32  import javax.persistence.EnumType;
33  import javax.persistence.Enumerated;
34  import javax.persistence.FetchType;
35  import javax.persistence.GeneratedValue;
36  import javax.persistence.GenerationType;
37  import javax.persistence.Id;
38  import javax.persistence.Inheritance;
39  import javax.persistence.InheritanceType;
40  import javax.persistence.JoinColumn;
41  import javax.persistence.ManyToOne;
42  import javax.persistence.OneToMany;
43  import javax.persistence.SequenceGenerator;
44  import javax.persistence.Transient;
45  
46  import org.hibernate.annotations.Cache;
47  import org.hibernate.annotations.CacheConcurrencyStrategy;
48  import org.hibernate.annotations.Fetch;
49  import org.hibernate.annotations.FetchMode;
50  import org.hibernate.annotations.Index;
51  import org.hibernate.annotations.Type;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  import org.springframework.beans.factory.annotation.Autowired;
55  import org.springframework.beans.factory.annotation.Configurable;
56  
57  import com.gisgraphy.domain.geoloc.importer.ImporterConfig;
58  import com.gisgraphy.domain.repository.ICountryDao;
59  import com.gisgraphy.domain.valueobject.Constants;
60  import com.gisgraphy.domain.valueobject.FeatureCode;
61  import com.gisgraphy.domain.valueobject.GISSource;
62  import com.gisgraphy.domain.valueobject.SRID;
63  import com.gisgraphy.helper.FeatureClassCodeHelper;
64  import com.gisgraphy.helper.GeolocHelper;
65  import com.gisgraphy.helper.IntrospectionIgnoredField;
66  import com.vividsolutions.jts.geom.Point;
67  
68  /**
69   * GisFeature is the 'MotherClass of all Features. <b><u>IMPORTANT Note about
70   * admXCodes</u></b> : <br>
71   * The AdmCode can have the value from the Geonames CSV file or the value from
72   * the {@link #getAdm()}.getAdm1Code It depends on the option
73   * {@link ImporterConfig#isSyncAdmCodesWithLinkedAdmOnes()} in the
74   * env.properties file : Gisgraphy try to detect and correct errors in the CSV
75   * files. If an error is detected or wrong Adm code are set, the Adm for this
76   * GisFeature may not be the one that will be found from the Code in The CSV
77   * file : if syncAdmCodesWithLinkedAdmOnes is set to false, the Adm1Code will be
78   * set with the value of the CSV file (even if the no {@linkplain Adm} are
79   * found).<br>
80   * If syncAdmCodesWithLinkedAdmOnes is set to true then the Adm1Code will always
81   * be the same as the {@link #getAdm()}.getAdm1Code<br>
82   * It depends on what you expect for Adm1Code : redondance
83   * (syncAdmCodesWithLinkedAdmOnes=true) or the CSV data
84   * (syncAdmCodesWithLinkedAdmOnes=false)
85   * 
86   * @see ImporterConfig
87   * @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
88   */
89  @Entity
90  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
91  @SequenceGenerator(name = "gisFeatureSequence", sequenceName = "gisfeature_sequence")
92  @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
93  @Configurable
94  public class GisFeature {
95  
96      @Autowired
97      public ICountryDao countryDao;
98  
99      public static final String LOCATION_COLUMN_NAME = "location";
100 
101     protected static final Logger logger = LoggerFactory
102 	    .getLogger(GisFeature.class);
103 
104     /**
105      * Default Constructor
106      */
107     public GisFeature() {
108 	super();
109     }
110 
111     /**
112      * Copy Constructor that populate the current {@link GisFeature} with the
113      * specified gisFeature fields<br>
114      * 
115      * @param gisFeature
116      *                The gisFeature from which we want to populate the
117      *                {@linkplain GisFeature}
118      */
119     public GisFeature(GisFeature gisFeature) {
120 	super();
121 	populate(gisFeature);
122     }
123 
124     @IntrospectionIgnoredField
125     private Long id;
126 
127     private Long featureId;
128 
129     private String name;
130 
131     private String asciiName;
132 
133     private List<AlternateName> alternateNames;
134 
135     private Point location;
136 
137     private String adm1Code;
138 
139     private String adm2Code;
140 
141     private String adm3Code;
142 
143     private String adm4Code;
144 
145     private String adm1Name;
146 
147     private String adm2Name;
148 
149     private String adm3Name;
150 
151     private String adm4Name;
152 
153     private String featureClass;
154 
155     private String featureCode;
156 
157     private String countryCode;
158 
159     @IntrospectionIgnoredField
160     private Adm adm;
161 
162     private Integer population;
163 
164     private Integer elevation;
165 
166     private Integer gtopo30;
167 
168     private String timezone;
169 
170     @IntrospectionIgnoredField
171     private Date modificationDate;
172 
173     @IntrospectionIgnoredField
174     private GISSource source;
175 
176     /**
177      * The datastore id
178      * 
179      * @return The DatastoreId, it is not a domain value, just a technical One
180      */
181     @Id
182     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gisFeatureSequence")
183     public Long getId() {
184 	return this.id;
185     }
186 
187     /**
188      * @return the country from the CountryCode. Return null if the countryCode
189      *         is null or if no country is found
190      */
191     @Transient
192     public Country getCountry() {
193 	Country country = null;
194 	if (countryCode != null) {
195 	    country = countryDao.getByIso3166Alpha2Code(countryCode);
196 	}
197 	return country;
198     }
199 
200     /**
201      * @return Returns the latitude (north-south) from the Location
202      *         {@link #getLocation()}.
203      * @see #getLongitude()
204      * @see #getLocation()
205      */
206     @Transient
207     public Double getLatitude() {
208 	Double latitude = null;
209 	if (this.location != null) {
210 	    latitude = this.location.getY();
211 	}
212 	return latitude;
213     }
214 
215     /**
216      * Calculate the distance from the current GisFeature to the specified
217      * point.
218      * 
219      * @param point
220      *                the JTS point from which we want to calculate the distance
221      * @return the calculated distance
222      * @see GeolocHelper#distance(Point, Point)
223      */
224     @Transient
225     public double distance(Point point) {
226 	return GeolocHelper.distance(this.location, point);
227 
228     }
229 
230     /**
231      * Returns The JTS location point of the current GisFeature : The Geometry
232      * representation for the latitude, longitude. The Return type is a JTS
233      * point. The Location is calculate from the 4326 {@link SRID}
234      * 
235      * @see SRID
236      * @see #getLongitude()
237      * @see #getLocation()
238      * @return The JTS Point
239      */
240     @Column(nullable = false, name = LOCATION_COLUMN_NAME)
241     @Type(type = "org.hibernatespatial.GeometryUserType")
242     // @Index(name = "locationIndex")
243     public Point getLocation() {
244 	return location;
245     }
246 
247     /**
248      * @return Returns the longitude (east-West) from the Location
249      *         {@link #getLocation()}.
250      * @see #getLongitude()
251      * @see #getLocation()
252      */
253     @Transient
254     public Double getLongitude() {
255 	Double longitude = null;
256 	if (this.location != null) {
257 	    longitude = this.location.getX();
258 	}
259 	return longitude;
260     }
261 
262     /**
263      * The modification date of the feature. The date must match the
264      * {@link Constants#GIS_DATE_PATTERN} This fields is not updated when saving
265      * or updating a GisFeature : This fields is to track change in the
266      * gazetteers, not in the Datastore.
267      * 
268      * @return The modification date of the feature
269      */
270     public Date getModificationDate() {
271 	return this.modificationDate;
272     }
273 
274     /**
275      * Returns the Adm1Code for this feature. The only goal to have the Adm1Code
276      * directly in the GisFeature is for performance reason :<br>
277      * It allows to have the adm1Code without loading all the Adm tree. See
278      * Important Notes for admXcode for {@link GisFeature}
279      * 
280      * @return The Adm1Code for this Feature
281      */
282     @Index(name = "adm1codeIndex")
283     @Column(length = 20)
284     public String getAdm1Code() {
285 	return adm1Code;
286     }
287 
288     /**
289      * @see #getAdm1Code()
290      * @param adm1Code
291      *                the adm1code to set
292      */
293     public void setAdm1Code(String adm1Code) {
294 	this.adm1Code = adm1Code;
295     }
296 
297     /**
298      * Returns the Adm2Code for this feature. The only goal to have the Adm2Code
299      * directly in the GisFeature is for performance reason :<br>
300      * It allows to have the adm2Code without loading all the Adm tree. See
301      * Important Notes for admXcode for {@link GisFeature}
302      * 
303      * @return The Adm2Code for this Feature
304      */
305     @Index(name = "adm2codeIndex")
306     @Column(length = 80)
307     public String getAdm2Code() {
308 	return adm2Code;
309     }
310 
311     /**
312      * @see #getAdm2Code()
313      * @param adm2Code
314      *                the adm2code to set
315      */
316     public void setAdm2Code(String adm2Code) {
317 	this.adm2Code = adm2Code;
318     }
319 
320     /**
321      * Returns the Adm3Code for this feature. The only goal to have the Adm3Code
322      * directly in the GisFeature is for performance reason :<br>
323      * It allows to have the adm3Code without loading all the Adm tree. See
324      * Important Notes for admXcode for {@link GisFeature}
325      * 
326      * @return The Adm3Code for this Feature
327      */
328     @Index(name = "adm3codeIndex")
329     @Column(length = 20)
330     public String getAdm3Code() {
331 	return adm3Code;
332     }
333 
334     /**
335      * @see #getAdm3Code()
336      * @param adm3Code
337      *                the adm3code to set
338      */
339     public void setAdm3Code(String adm3Code) {
340 	this.adm3Code = adm3Code;
341     }
342 
343     /**
344      * Returns the Adm4Code for this feature. The only goal to have the Adm4Code
345      * directly in the GisFeature is for performance reason :<br>
346      * It allows to have the adm4Code without loading the all Adm tree. See
347      * Important Notes for admXcode for {@link GisFeature}
348      * 
349      * @return The Adm4Code for this Feature
350      */
351     @Index(name = "adm4codeIndex")
352     @Column(length = 20)
353     public String getAdm4Code() {
354 	return adm4Code;
355     }
356 
357     /**
358      * @see #getAdm4Code()
359      * @param adm4Code
360      *                the adm4code to set
361      */
362     public void setAdm4Code(String adm4Code) {
363 	this.adm4Code = adm4Code;
364     }
365 
366     /**
367      * Returns the name of the Adm of level 1 that this GisFeature is linked to.
368      * The only goal to have it directly in the gisFeature is for performance
369      * reason :<br>
370      * It allows to have it without loading all the Adm tree
371      * 
372      * @return The name of the Adm of level 1 that this GisFeature is linked to
373      */
374     @Index(name = "adm1NameIndex")
375     @Column(length = 200)
376     public String getAdm1Name() {
377 	return adm1Name;
378     }
379 
380     /**
381      * Set the name of the Adm of level 1 that this GisFeature is linked to
382      * 
383      * @param adm1Name
384      *                The name of the Adm of level 1 that this GisFeature is
385      *                linked to
386      * @see #getAdm1Name()
387      */
388     public void setAdm1Name(String adm1Name) {
389 	this.adm1Name = adm1Name;
390     }
391 
392     /**
393      * Returns the name of the Adm of level 2 that this GisFeature is linked to.
394      * The only goal to have it directly in the gisFeature is for performance
395      * reason :<br>
396      * it allow to retrieve it without loading all the Adm tree
397      * 
398      * @return The name of the Adm of level 2 that this GisFeature is linked to
399      */
400     @Index(name = "adm2NameIndex")
401     @Column(length = 200)
402     public String getAdm2Name() {
403 	return adm2Name;
404     }
405 
406     /**
407      * Set the name of the Adm of level 2 that this GisFeature is linked to
408      * 
409      * @param adm2Name
410      *                The name of the Adm of level 2 that this GisFeature is
411      *                linked to
412      * @see #getAdm2Name()
413      */
414     public void setAdm2Name(String adm2Name) {
415 	this.adm2Name = adm2Name;
416     }
417 
418     /**
419      * Returns the name of the Adm of level 3 that this GisFeature is linked to.
420      * The only goal to have it directly in the gisFeature is for performance
421      * reason :<br>
422      * It allows to have it without loading all the Adm tree
423      * 
424      * @return The name of the Adm of level 3 that this GisFeature is linked to
425      */
426     @Index(name = "adm3NameIndex")
427     @Column(length = 200)
428     public String getAdm3Name() {
429 	return adm3Name;
430     }
431 
432     /**
433      * Set the name of the Adm of level 3 that this GisFeature is linked to
434      * 
435      * @param adm3Name
436      *                The name of the Adm of level 3 that this GisFeature is
437      *                linked to
438      * @see #getAdm3Name()
439      */
440     public void setAdm3Name(String adm3Name) {
441 	this.adm3Name = adm3Name;
442     }
443 
444     /**
445      * Returns the name of the Adm of level 4 that this GisFeature is linked to.
446      * The only goal to have it directly in the gisFeature is for performance
447      * reason :<br>
448      * It allows to have it without loading all the Adm tree
449      * 
450      * @return The name of the Adm of level 4 that this GisFeature is linked to
451      */
452     @Index(name = "adm4NameIndex")
453     @Column(length = 200)
454     public String getAdm4Name() {
455 	return adm4Name;
456     }
457 
458     /**
459      * Set The name of the adm of level 4 that the GisFeature is linked to
460      * 
461      * @param adm4Name
462      *                The name of the adm of level 4 that the GisFeature is
463      *                linked to
464      * @see #getAdm4Name()
465      */
466     public void setAdm4Name(String adm4Name) {
467 	this.adm4Name = adm4Name;
468     }
469 
470     /**
471      * @return The Adm with the higher Level that this GisFeature is linked to
472      *         (the deeper in the Adm tree). See Important Notes for admXcode
473      *         for {@link GisFeature}
474      */
475     @ManyToOne(fetch = FetchType.LAZY)
476     @JoinColumn(name = "adm", unique = false, referencedColumnName = "id", nullable = true)
477     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
478     public Adm getAdm() {
479 	return adm;
480     }
481 
482     /**
483      * @see #getAdm()
484      * @param adm
485      *                The Adm with the higher Level that this GisFeature is
486      *                linked to (the deeper in the Adm tree).
487      */
488     public void setAdm(Adm adm) {
489 	this.adm = adm;
490     }
491 
492     /**
493      * @return A list of the {@link AlternateName}s for this GisFeature
494      */
495     @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "gisFeature")
496     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
497     @Fetch(FetchMode.SELECT)
498     public List<AlternateName> getAlternateNames() {
499 	return alternateNames;
500     }
501 
502     /**
503      * @param alternateNames
504      *                The {@link AlternateName}s for this GisFeature
505      */
506     public void setAlternateNames(List<AlternateName> alternateNames) {
507 	this.alternateNames = alternateNames;
508     }
509 
510     /**
511      * Do a double set : add the AlternateName to the current GisFeature and set
512      * this GisFeature as the GisFeature of the specified AlternateName
513      * 
514      * @param alternateName
515      *                the alternateName to add
516      */
517     public void addAlternateName(AlternateName alternateName) {
518 	List<AlternateName> currentAlternateNames = getAlternateNames();
519 	if (currentAlternateNames == null) {
520 	    currentAlternateNames = new ArrayList<AlternateName>();
521 	}
522 	currentAlternateNames.add(alternateName);
523 	this.setAlternateNames(currentAlternateNames);
524 	alternateName.setGisFeature(this);
525     }
526 
527     /**
528      * Do a double set : add (not replace !)the AlternateNames to the current
529      * GisFeature and for each alternatenames : set the current GisFeature as
530      * the GisFeature of the AlternateNames
531      * 
532      * @param alternateNames
533      *                The alternateNames list to add
534      */
535     public void addAlternateNames(List<AlternateName> alternateNames) {
536 	if (alternateNames != null) {
537 	    for (AlternateName alternateName : alternateNames) {
538 		addAlternateName(alternateName);
539 	    }
540 	}
541 	;
542     }
543 
544     /**
545      * @return The ASCII name of the current GisFeature
546      */
547     @Column(nullable = true, length = 200)
548     @Index(name = "gisFeatureAsciiNameIndex")
549     public String getAsciiName() {
550 	return this.asciiName;
551     }
552 
553     /**
554      * @see #getAsciiName()
555      * @param asciiname
556      *                The ASCII name of the current GisFeature
557      */
558     public void setAsciiName(String asciiname) {
559 	this.asciiName = asciiname;
560     }
561 
562     /**
563      * The country code is not mandatory because gisfeature like undersea does
564      * not belongs to a country
565      * 
566      * @return The ISO 3166 alpha-2 letter code.
567      */
568     @Index(name = "gisFeatureCountryindex")
569     @Column(length = 3)
570     public String getCountryCode() {
571 	return countryCode;
572     }
573 
574     /**
575      * @param countryCode
576      *                The ISO 3166 alpha-2 letter code in upper Case (it will be
577      *                automatically uppercased)
578      * @see #getCountryCode()
579      */
580     public void setCountryCode(String countryCode) {
581 	if (countryCode != null) {
582 	    this.countryCode = countryCode.toUpperCase();
583 	} else {
584 	    this.countryCode = countryCode;
585 	}
586     }
587 
588     /**
589      * @return The elevation of this gisFeature (in meters)
590      */
591     public Integer getElevation() {
592 	return elevation;
593     }
594 
595     /**
596      * @param elevation
597      *                The elevation of this gisFeature (in meters)
598      * @see #getElevation()
599      */
600     public void setElevation(Integer elevation) {
601 	this.elevation = elevation;
602     }
603 
604     /**
605      * @return The average elevation of 30'x30' (ca 900mx900m) area in meters
606      */
607     public Integer getGtopo30() {
608 	return gtopo30;
609     }
610 
611     /**
612      * @param gtopo30
613      *                The average elevation of 30'x30' (ca 900mx900m) area to
614      *                set in meters
615      */
616     public void setGtopo30(Integer gtopo30) {
617 	this.gtopo30 = gtopo30;
618     }
619 
620     /**
621      * @return The UTF-8 name for the current GisFeature
622      */
623     @Column(nullable = false, length = 200)
624     @Index(name = "gisFeatureNameIndex")
625     public String getName() {
626 	return name;
627     }
628 
629     /**
630      * @param name
631      *                The UTF-8 name for the current GisFeature
632      */
633     public void setName(String name) {
634 	this.name = name;
635     }
636 
637     /**
638      * @return The population (how many people are in) for this GisFeature
639      */
640     public Integer getPopulation() {
641 	return population;
642     }
643 
644     /**
645      * @param population
646      *                The population (how many people are in) of this GisFeature
647      */
648     public void setPopulation(Integer population) {
649 	this.population = population;
650     }
651 
652     /**
653      * @return The source for the gisFeature. it tells from which files /
654      *         gazetteers it has been imported
655      */
656     @Enumerated(EnumType.STRING)
657     @Column(nullable = false)
658     public GISSource getSource() {
659 	return source;
660     }
661 
662     /**
663      * @param source
664      *                The source for the gisFeature to be set
665      */
666     public void setSource(GISSource source) {
667 	this.source = source;
668     }
669 
670     /**
671      * @return The timeZone for This GisFeature
672      * @see <a
673      *      href="http://download.geonames.org/export/dump/timeZones.txt">Time
674      *      zone</a>
675      */
676     @Column(length = 40)
677     public String getTimezone() {
678 	return timezone;
679     }
680 
681     /**
682      * @param timezone
683      *                The timeZone for This GisFeature
684      * @see #getTimezone()
685      */
686     public void setTimezone(String timezone) {
687 	this.timezone = timezone;
688 
689     }
690 
691     /**
692      * @param location
693      *                The location of the GisFeature (JTS point)
694      * @see #getLocation()
695      * @see #getLatitude()
696      * @see #getLongitude()
697      */
698     public void setLocation(Point location) {
699 	this.location = location;
700     }
701 
702     /**
703      * @param modificationDate
704      *                The Date of the Last Modification. This fields is not
705      *                updated when saving or updating a GisFeature : This fields
706      *                is to track change in the gazetteers, not in the
707      *                Datastore. The date should match the
708      *                {@link Constants#GIS_DATE_PATTERN}
709      * @see #getModificationDate()
710      */
711     public void setModificationDate(Date modificationDate) {
712 	this.modificationDate = modificationDate;
713     }
714 
715     /**
716      * @param id
717      *                The Id in the datastore. You should never call this
718      *                method. It is the responsability of the dataStore
719      * @see #getId()
720      */
721     public void setId(Long id) {
722 	this.id = id;
723     }
724 
725     /**
726      * @return The featureId for this GisFeature. it is a 'Domain value' not a
727      *         datastore one A featureId is unique and mandatory
728      */
729     @Index(name = "gisFeatureIdIndex")
730     @Column(unique = true, nullable = false)
731     public Long getFeatureId() {
732 	return featureId;
733     }
734 
735     /**
736      * A featureId is unique and mandatory
737      * 
738      * @param featureId
739      *                The featureId for this GisFeature
740      */
741     public void setFeatureId(Long featureId) {
742 	this.featureId = featureId;
743     }
744 
745     /**
746      * Whether the feature is a city, in the sense we define it in the
747      * {@link FeatureClassCodeHelper} class. It does not check the Class but the
748      * feature class and the feature code
749      * 
750      * @return true if it is a city
751      */
752     @Transient
753     public boolean isCity() {
754 	return FeatureClassCodeHelper.isCity(this.featureClass,
755 		this.featureCode);
756     }
757 
758     /**
759      * Whether the feature is a coutry, in the sense we define it in the
760      * {@link FeatureClassCodeHelper} class. It does not check the Class but the
761      * feature class and the feature code
762      * 
763      * @return true if it is a country
764      */
765     @Transient
766     public boolean isCountry() {
767 	return FeatureClassCodeHelper.isCountry(this.featureClass,
768 		this.featureCode);
769     }
770 
771     /**
772      * Whether the feature is an Adm, in the sense we define it in the
773      * {@link FeatureClassCodeHelper} class. It does not check the Class but the
774      * feature class and the feature code
775      * 
776      * @return true if it is an ADM
777      */
778     @Transient
779     public boolean isAdm() {
780 	return FeatureClassCodeHelper.is_Adm(this.featureClass,
781 		this.featureCode);
782     }
783 
784     /**
785      * @return The feature class for this gisFeature (should always be in
786      *         uppercase because setter automatically convert the feature class
787      *         in upperCase). A feature class regroup some feature code
788      * @see <a href="http://www.geonames.org/export/codes.html">codes</a>
789      */
790     @Index(name = "gisFeatureFeatureClassindex")
791     @Column(length = 4)
792     public String getFeatureClass() {
793 	return featureClass;
794     }
795 
796     /**
797      * @param featureClass
798      *                The feature class to set. <u>Note</u> The featureClass
799      *                will automaticaly be uppercased.
800      * @see #getFeatureClass()
801      */
802     public void setFeatureClass(String featureClass) {
803 	if (featureClass != null) {
804 	    this.featureClass = featureClass.toUpperCase();
805 	} else {
806 	    this.featureClass = featureClass;
807 	}
808     }
809 
810     /**
811      * @return the featureCode for the current GisFeature. A feature Code
812      *         represents a specific Feature type. GisGraphy regroup some
813      *         featureCode in an abstract Level called 'GisType'. A GisType
814      *         regroup several featurecode.
815      * @see FeatureCode
816      */
817     @Index(name = "gisFeatureFeatureCodeindex")
818     @Column(length = 10)
819     public String getFeatureCode() {
820 	return featureCode;
821     }
822 
823     /**
824      * Populate all the field / association of the current gisFeature with The
825      * Value of The specified One.
826      * 
827      * @param gisFeature
828      *                the gisFeature to populate with
829      */
830     public void populate(GisFeature gisFeature) {
831 	if (gisFeature != null) {
832 	    this.setAdm(gisFeature.getAdm());
833 
834 	    this.setAdm1Code(gisFeature.getAdm1Code());
835 	    this.setAdm2Code(gisFeature.getAdm2Code());
836 	    this.setAdm3Code(gisFeature.getAdm3Code());
837 	    this.setAdm4Code(gisFeature.getAdm4Code());
838 
839 	    this.setAdm1Name(gisFeature.getAdm1Name());
840 	    this.setAdm2Name(gisFeature.getAdm2Name());
841 	    this.setAdm3Name(gisFeature.getAdm3Name());
842 	    this.setAdm4Name(gisFeature.getAdm4Name());
843 
844 	    this.setAlternateNames(gisFeature.getAlternateNames());
845 	    if (getAlternateNames() != null) {
846 		for (AlternateName alternateName : getAlternateNames()) {
847 		    alternateName.setGisFeature(this);
848 		}
849 	    }
850 	    if (gisFeature.getAsciiName() != null) {
851 		this.setAsciiName(gisFeature.getAsciiName().trim());
852 	    }
853 	    if (gisFeature.getCountryCode() != null) {
854 		this.setCountryCode(gisFeature.getCountryCode().toUpperCase());
855 	    }
856 	    this.setElevation(gisFeature.getElevation());
857 	    this.setFeatureClass(gisFeature.getFeatureClass());
858 	    this.setFeatureCode(gisFeature.getFeatureCode());
859 	    this.setFeatureId(gisFeature.getFeatureId());
860 	    this.setGtopo30(gisFeature.getGtopo30());
861 	    this.setLocation(gisFeature.getLocation());
862 	    this.setModificationDate(gisFeature.getModificationDate());
863 	    this.setName(gisFeature.getName().trim());
864 	    this.setPopulation(gisFeature.getPopulation());
865 	    this.setSource(gisFeature.getSource());
866 	    this.setTimezone(gisFeature.getTimezone());
867 	}
868     }
869 
870     /**
871      * @param featureCode
872      *                The feature Code for this GisFeature * <u>Note</u> The
873      *                featureCode will automaticaly be uppercased
874      * @see #getFeatureCode()
875      */
876     public void setFeatureCode(String featureCode) {
877 	if (featureCode != null) {
878 	    this.featureCode = featureCode.toUpperCase();
879 	} else {
880 	    this.featureCode = featureCode;
881 	}
882     }
883 
884     /**
885      * @return true If the gisFeature must be sync with the fullText search
886      *         engine
887      */
888     @Transient
889     public boolean isFullTextSearchable() {
890 	return true;
891     }
892 
893     /*
894      * (non-Javadoc)
895      * 
896      * @see java.lang.Object#hashCode()
897      */
898     @Override
899     public int hashCode() {
900 	final int PRIME = 31;
901 	int result = 1;
902 	result = PRIME * result
903 		+ ((featureId == null) ? 0 : featureId.hashCode());
904 	return result;
905     }
906 
907     /*
908      * (non-Javadoc)
909      * 
910      * @see java.lang.Object#equals(java.lang.Object)
911      */
912     @Override
913     public boolean equals(Object obj) {
914 	if (this == obj) {
915 	    return true;
916 	}
917 	if (obj == null) {
918 	    return false;
919 	}
920 	if (getClass() != obj.getClass()) {
921 	    return false;
922 	}
923 	final GisFeature other = (GisFeature) obj;
924 	if (featureId == null) {
925 	    if (other.featureId != null) {
926 		return false;
927 	    }
928 	} else if (!featureId.equals(other.featureId)) {
929 	    return false;
930 	}
931 	return true;
932     }
933 
934     /**
935      * Returns a name of the form : (adm1Name et adm2Name are printed) Paris,
936      * Département de Ville-De-Paris, Ile-De-France, (FR)
937      * 
938      * @param withCountry
939      *                Whether the country information should be added
940      * @return a name with the Administrative division and Country
941      */
942     @Transient
943     public String getFullyQualifiedName(boolean withCountry) {
944 	StringBuilder completeCityName = new StringBuilder();
945 	completeCityName.append(getName());
946 	if (adm2Name != null && !adm2Name.trim().equals("")) {
947 	    completeCityName.append(", " + adm2Name);
948 	}
949 	if (adm1Name != null && !adm1Name.trim().equals("")) {
950 	    completeCityName.append(", " + adm1Name);
951 	}
952 
953 	if (withCountry) {
954 	    Country countryObj = getCountry();
955 	    if (countryObj != null && countryObj.getName() != null
956 		    && !countryObj.getName().trim().equals("")) {
957 		completeCityName.append(" , " + countryObj.getName() + "");
958 	    }
959 	}
960 
961 	return completeCityName.toString();
962     }
963     
964     /**
965      * @return a name with the Administrative division (but without Country)
966      * wrap {@link #getFullyQualifiedName(boolean)}
967      * @see #getFullyQualifiedName(boolean)
968      */
969     @Transient
970     public String getFullyQualifiedName() {
971 	return getFullyQualifiedName(false);
972     }
973 
974     /*
975      * (non-Javadoc)
976      * 
977      * @see java.lang.Object#toString()
978      */
979     @Override
980     public String toString() {
981 	return this.getClass().getSimpleName() + "[" + getFeatureId() + "]["
982 		+ getFeatureClass() + "." + getFeatureCode() + "][" + getName()
983 		+ "]";
984     }
985 
986 }