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.Collection;
26  import java.util.Date;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  
31  import javax.persistence.CascadeType;
32  import javax.persistence.Column;
33  import javax.persistence.Entity;
34  import javax.persistence.EnumType;
35  import javax.persistence.Enumerated;
36  import javax.persistence.FetchType;
37  import javax.persistence.GeneratedValue;
38  import javax.persistence.GenerationType;
39  import javax.persistence.Id;
40  import javax.persistence.Inheritance;
41  import javax.persistence.InheritanceType;
42  import javax.persistence.JoinColumn;
43  import javax.persistence.ManyToOne;
44  import javax.persistence.OneToMany;
45  import javax.persistence.SequenceGenerator;
46  import javax.persistence.Transient;
47  
48  import org.hibernate.annotations.Cache;
49  import org.hibernate.annotations.CacheConcurrencyStrategy;
50  import org.hibernate.annotations.Fetch;
51  import org.hibernate.annotations.FetchMode;
52  import org.hibernate.annotations.Index;
53  import org.hibernate.annotations.Type;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  import com.gisgraphy.domain.valueobject.Constants;
58  import com.gisgraphy.domain.valueobject.FeatureCode;
59  import com.gisgraphy.domain.valueobject.GISSource;
60  import com.gisgraphy.domain.valueobject.SRID;
61  import com.gisgraphy.helper.FeatureClassCodeHelper;
62  import com.gisgraphy.helper.GeolocHelper;
63  import com.gisgraphy.helper.GisFeatureHelper;
64  import com.gisgraphy.helper.IntrospectionIgnoredField;
65  import com.gisgraphy.importer.ImporterConfig;
66  import com.vividsolutions.jts.geom.Geometry;
67  import com.vividsolutions.jts.geom.Point;
68  
69  /**
70   * GisFeature is the 'MotherClass of all Features. <b><u>IMPORTANT Note about
71   * admXCodes</u></b> : <br>
72   * The AdmCode can have the value from the Geonames CSV file or the value from
73   * the {@link #getAdm()}.getAdm1Code It depends on the option
74   * {@link ImporterConfig#isSyncAdmCodesWithLinkedAdmOnes()} in the
75   * env.properties file : Gisgraphy try to detect and correct errors in the CSV
76   * files. If an error is detected or wrong Adm code are set, the Adm for this
77   * GisFeature may not be the one that will be found from the Code in the CSV
78   * file. If syncAdmCodesWithLinkedAdmOnes is set to false, the Adm1Code will be
79   * set with the value of the CSV file (even if the no {@linkplain Adm} are
80   * found).<br>
81   * If syncAdmCodesWithLinkedAdmOnes is set to true then the Adm1Code will always
82   * be the same as the {@link #getAdm()}.getAdm1Code<br>
83   * It depends on what you expect for Adm1Code : ADM values
84   * (syncAdmCodesWithLinkedAdmOnes=true) or the CSV one
85   * (syncAdmCodesWithLinkedAdmOnes=false)
86   * 
87   * @see ImporterConfig
88   * @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
89   */
90  @Entity
91  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
92  @SequenceGenerator(name = "gisFeatureSequence", sequenceName = "gisfeature_sequence")
93  @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
94  public class GisFeature{
95  	
96  	public static final int NAME_MAX_LENGTH= 200;
97  	
98  	public static final String SHAPE_COLUMN_NAME = "shape";
99  
100     public static final String LOCATION_COLUMN_NAME = "location";
101 
102     protected static final Logger logger = LoggerFactory
103 	    .getLogger(GisFeature.class);
104 
105 	public static final int MAX_ALTERNATENAME_SIZE = 200;
106 
107     /**
108      * Default Constructor, needed by cgLib
109      */
110     public GisFeature() {
111 	super();
112     }
113 
114     /**
115      * Copy Constructor that populate the current {@link GisFeature} with the
116      * specified gisFeature fields<br>
117      * 
118      * @param gisFeature
119      *                The gisFeature from which we want to populate the
120      *                {@linkplain GisFeature}
121      */
122     public GisFeature(GisFeature gisFeature) {
123 	super();
124 	populate(gisFeature);
125     }
126 
127    // @IntrospectionIgnoredField
128     private Long id;
129 
130     private Long featureId;
131 
132     private String name;
133 
134     private String asciiName;
135 
136     private Set<AlternateName> alternateNames;
137 
138     private Point location;
139     
140     private String adm1Code;
141 
142     private String adm2Code;
143 
144     private String adm3Code;
145 
146     private String adm4Code;
147     
148     private String adm5Code;
149 
150     private String adm1Name;
151 
152     private String adm2Name;
153 
154     private String adm3Name;
155 
156     private String adm4Name;
157     
158     private String adm5Name;
159 
160     private String featureClass;
161 
162     private String featureCode;
163 
164     private String countryCode;
165 
166     @IntrospectionIgnoredField
167     private Adm adm;
168 
169     private Integer population;
170 
171     private Integer elevation;
172 
173     private Integer gtopo30;
174 
175     private String timezone;
176 
177     @IntrospectionIgnoredField
178     private Date modificationDate;
179 
180     @IntrospectionIgnoredField
181     private GISSource source;
182     
183     @IntrospectionIgnoredField
184     private Geometry shape;
185     
186     private Set<ZipCode> zipCodes;
187     
188     private String amenity;
189     
190     private Long openstreetmapId;
191         
192     private String isIn;
193     
194     private String isInPlace;
195     
196     private String isInAdm;
197     
198     @IntrospectionIgnoredField
199     @Transient
200     private Set<String> isInZip;
201     
202     /**
203      * This field is only for relevance and allow to search for street<->cities in 
204      * many alternateNames. It is not in stored
205      */
206     @IntrospectionIgnoredField
207     private Set<String> isInCityAlternateNames;
208    
209 
210 	
211 
212 
213 	/**
214      * The datastore id
215      * 
216      * @return The datastoreId, it is not a domain value, just a technical One
217      */
218     @Id
219     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gisFeatureSequence")
220     public Long getId() {
221 	return this.id;
222     }
223 
224     /**
225      * @return the country from the country code. Return null if the country Code
226      *         is null or if no country is found
227      * @see #getCountryCode()
228      */
229     @Transient
230     public Country getCountry() {
231 	return GisFeatureHelper.getInstance().getCountry(getCountryCode());
232     }
233 
234     /**
235      * @return Returns the latitude (north-south) from the Location
236      *         {@link #getLocation()}.
237      * @see #getLongitude()
238      * @see #getLocation()
239      */
240     @Transient
241     public Double getLatitude() {
242 	Double latitude = null;
243 	if (this.location != null) {
244 	    latitude = this.location.getY();
245 	}
246 	return latitude;
247     }
248 
249     /**
250      * Calculate the distance from the current GisFeature to the specified
251      * point.
252      * 
253      * @param point
254      *                the JTS point we want to calculate the distance from
255      * @return the calculated distance
256      * @see GeolocHelper#distance(Point, Point)
257      */
258     @Transient
259     public double distanceTo(Point point) {
260 	return GeolocHelper.distance(this.location, point);
261 
262     }
263 
264     /**
265      * Returns The JTS location point of the current GisFeature : The Geometry
266      * representation of the latitude, longitude. The Return type is a JTS
267      * point. The Location is calculate from the 4326 {@link SRID}
268      * 
269      * @see SRID
270      * @see #getLongitude()
271      * @see #getLocation()
272      * @return The JTS Point
273      */
274     @Column(nullable = false, name = LOCATION_COLUMN_NAME)
275     @Type(type = "org.hibernatespatial.GeometryUserType")
276     public Point getLocation() {
277 	return location;
278     }
279     
280     /**
281      * Returns The JTS shape of the feature : The Return type is a JTS
282      * geometry.
283      * 
284      * @see SRID
285      * @see #getLongitude()
286      * @see #getLocation()
287      * @return The JTS Point
288      */
289     @Column(nullable = true, name = SHAPE_COLUMN_NAME)
290     @Type(type = "org.hibernatespatial.GeometryUserType")
291     public Geometry getShape() {
292 	return shape;
293     }
294     
295     
296 
297     /**
298      * @return Returns the longitude (east-west) from the Location
299      *         {@link #getLocation()}.
300      * @see #getLongitude()
301      * @see #getLocation()
302      */
303     @Transient
304     public Double getLongitude() {
305 	Double longitude = null;
306 	if (this.location != null) {
307 	    longitude = this.location.getX();
308 	}
309 	return longitude;
310     }
311 
312     /**
313      * The modification date of the feature. The date must match the
314      * {@link Constants#GIS_DATE_PATTERN} This fields is not updated when saving
315      * or updating a GisFeature. This fields is to track changes in the
316      * gazetteers, not in the Datastore.
317      * 
318      * @return The modification date of the feature
319      */
320     public Date getModificationDate() {
321 	return this.modificationDate;
322     }
323 
324     /**
325      * Returns the Adm1Code for this feature. The only goal to have the Adm1Code
326      * directly in the GisFeature is for performance reasons :<br>
327      * It allows to have the adm1Code without loading all the Adm tree. See
328      * Important Notes for admXcode for {@link GisFeature}
329      * 
330      * @return The Adm1Code for this Feature
331      */
332     @Index(name = "adm1codeIndex")
333     @Column(length = 20)
334     public String getAdm1Code() {
335 	return adm1Code;
336     }
337 
338     /**
339      * @see #getAdm1Code()
340      * @param adm1Code
341      *                The adm1code to set
342      */
343     public void setAdm1Code(String adm1Code) {
344 	this.adm1Code = adm1Code;
345     }
346 
347     /**
348      * Returns the Adm2Code for this feature. The only goal to have the Adm2Code
349      * directly in the GisFeature is for performance reasons :<br>
350      * It allows to have the adm2Code without loading all the Adm tree. See
351      * Important Notes for admXcode for {@link GisFeature}
352      * 
353      * @return The Adm2Code for this Feature
354      */
355    // @Index(name = "adm2codeIndex")
356     @Column(length = 80)
357     public String getAdm2Code() {
358 	return adm2Code;
359     }
360 
361     /**
362      * @see #getAdm2Code()
363      * @param adm2Code
364      *                the adm2code to set
365      */
366     public void setAdm2Code(String adm2Code) {
367 	this.adm2Code = adm2Code;
368     }
369 
370     /**
371      * Returns the Adm3Code for this feature. The only goal to have the Adm3Code
372      * directly in the GisFeature is for performance reasons :<br>
373      * It allows to have the adm3Code without loading all the Adm tree. See
374      * Important Notes for admXcode for {@link GisFeature}
375      * 
376      * @return The Adm3Code for this Feature
377      */
378     //@Index(name = "adm3codeIndex")
379     @Column(length = 20)
380     public String getAdm3Code() {
381 	return adm3Code;
382     }
383 
384     /**
385      * @see #getAdm3Code()
386      * @param adm3Code
387      *                the adm3code to set
388      */
389     public void setAdm3Code(String adm3Code) {
390 	this.adm3Code = adm3Code;
391     }
392 
393     /**
394      * Returns the Adm4Code for this feature. The only goal to have the Adm4Code
395      * directly in the GisFeature is for performance reasons :<br>
396      * It allows to have the adm4Code without loading the all Adm tree. See
397      * Important Notes for admXcode for {@link GisFeature}
398      * 
399      * @return The Adm4Code for this Feature
400      */
401     //@Index(name = "adm4codeIndex")
402     @Column(length = 20)
403     public String getAdm4Code() {
404 	return adm4Code;
405     }
406 
407     /**
408      * @see #getAdm4Code()
409      * @param adm4Code
410      *                the adm4code to set
411      */
412     public void setAdm4Code(String adm4Code) {
413 	this.adm4Code = adm4Code;
414     }
415     
416     /**
417      * Returns the Adm4Code for this feature. The only goal to have the Adm4Code
418      * directly in the GisFeature is for performance reasons :<br>
419      * It allows to have the adm4Code without loading the all Adm tree. See
420      * Important Notes for admXcode for {@link GisFeature}
421      * 
422      * @return The Adm4Code for this Feature
423      */
424     //@Index(name = "adm4codeIndex")
425     @Column(length = 20)
426     public String getAdm5Code() {
427 	return adm5Code;
428     }
429 
430     /**
431      * @see #getAdm4Code()
432      * @param adm5Code
433      *                the adm5Code to set
434      */
435     public void setAdm5Code(String adm5Code) {
436 	this.adm5Code = adm5Code;
437     }
438 
439     /**
440      * Returns the name of the Adm of level 1 that this GisFeature is linked to.
441      * The only goal to have it directly in the gisFeature is for performance
442      * reasons :<br>
443      * It allows to have it without loading all the Adm tree
444      * 
445      * @return The name of the Adm of level 1 that this GisFeature is linked to
446      */
447    // @Index(name = "adm1NameIndex")
448     @Column(length = 200)
449     public String getAdm1Name() {
450 	return adm1Name;
451     }
452 
453     /**
454      * Set the name of the Adm of level 1 that this GisFeature is linked to
455      * 
456      * @param adm1Name
457      *                The name of the Adm of level 1 that this GisFeature is
458      *                linked to
459      * @see #getAdm1Name()
460      */
461     public void setAdm1Name(String adm1Name) {
462 	this.adm1Name = adm1Name;
463     }
464 
465     /**
466      * Returns the name of the Adm of level 2 that this GisFeature is linked to.
467      * The only goal to have it directly in the gisFeature is for performance
468      * reasons :<br>
469      * it allow to retrieve it without loading all the Adm tree
470      * 
471      * @return The name of the Adm of level 2 that this GisFeature is linked to
472      */
473    // @Index(name = "adm2NameIndex")
474     @Column(length = 200)
475     public String getAdm2Name() {
476 	return adm2Name;
477     }
478 
479     /**
480      * Set the name of the Adm of level 2 that this GisFeature is linked to
481      * 
482      * @param adm2Name
483      *                The name of the Adm of level 2 that this GisFeature is
484      *                linked to
485      * @see #getAdm2Name()
486      */
487     public void setAdm2Name(String adm2Name) {
488 	this.adm2Name = adm2Name;
489     }
490 
491     /**
492      * Returns the name of the Adm of level 3 that this GisFeature is linked to.
493      * The only goal to have it directly in the gisFeature is for performance
494      * reasons :<br>
495      * It allows to have it without loading all the Adm tree
496      * 
497      * @return The name of the Adm of level 3 that this GisFeature is linked to
498      */
499    // @Index(name = "adm3NameIndex")
500     @Column(length = 200)
501     public String getAdm3Name() {
502 	return adm3Name;
503     }
504 
505     /**
506      * Set the name of the Adm of level 3 that this GisFeature is linked to
507      * 
508      * @param adm3Name
509      *                The name of the Adm of level 3 that this GisFeature is
510      *                linked to
511      * @see #getAdm3Name()
512      */
513     public void setAdm3Name(String adm3Name) {
514 	this.adm3Name = adm3Name;
515     }
516 
517     /**
518      * Returns the name of the Adm of level 4 that this GisFeature is linked to.
519      * The only goal to have it directly in the gisFeature is for performance
520      * reasons :<br>
521      * It allows to have it without loading all the Adm tree
522      * 
523      * @return The name of the Adm of level 4 that this GisFeature is linked to
524      */
525   //  @Index(name = "adm4NameIndex")
526     @Column(length = 200)
527     public String getAdm4Name() {
528 	return adm4Name;
529     }
530 
531     /**
532      * Set The name of the adm of level 4 that the GisFeature is linked to
533      * 
534      * @param adm4Name
535      *                The name of the adm of level 4 that the GisFeature is
536      *                linked to
537      * @see #getAdm4Name()
538      */
539     public void setAdm4Name(String adm4Name) {
540 	this.adm4Name = adm4Name;
541     }
542     
543     /**
544      * Returns the name of the Adm of level 5 that this GisFeature is linked to.
545      * The only goal to have it directly in the gisFeature is for performance
546      * reasons :<br>
547      * It allows to have it without loading all the Adm tree
548      * 
549      * @return The name of the Adm of level 5 that this GisFeature is linked to
550      */
551   //  @Index(name = "adm4NameIndex")
552     @Column(length = 200)
553     public String getAdm5Name() {
554 	return adm5Name;
555     }
556 
557     /**
558      * Set The name of the adm of level 5 that the GisFeature is linked to
559      * 
560      * @param adm5Name
561      *                The name of the adm of level 5 that the GisFeature is
562      *                linked to
563      * @see #getAdm5Name()
564      */
565     public void setAdm5Name(String adm5Name) {
566 	this.adm5Name = adm5Name;
567     }
568 
569     /**
570      * @return The Adm with the higher Level that this GisFeature is linked to
571      *         (the deeper in the Adm tree). See Important Notes for admXcode
572      *         for {@link GisFeature}
573      */
574     @ManyToOne(fetch = FetchType.LAZY)
575     @JoinColumn(name = "adm", unique = false, referencedColumnName = "id", nullable = true)
576     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
577     @Index(name = "gisfeatureadmindex")
578     public Adm getAdm() {
579 	return adm;
580     }
581 
582     /**
583      * @see #getAdm()
584      * @param adm
585      *                The Adm with the higher Level that this GisFeature is
586      *                linked to (the deeper in the Adm tree).
587      */
588     public void setAdm(Adm adm) {
589 	this.adm = adm;
590     }
591 
592     /**
593      * @return A list of the {@link AlternateName}s for this GisFeature
594      */
595     @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "gisFeature")
596     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
597     @Fetch(FetchMode.SELECT)
598     public Set<AlternateName> getAlternateNames() {
599 	return alternateNames;
600     }
601 
602     /**
603      * @param alternateNames
604      *                The {@link AlternateName}s for this GisFeature
605      */
606     public void setAlternateNames(Set<AlternateName> alternateNames) {
607 	this.alternateNames = alternateNames;
608     }
609 
610     /**
611      * Do a double set : add the alternate name to the current GisFeature and set
612      * this GisFeature as the GisFeature of the specified AlternateName
613      * 
614      * @param alternateName
615      *                the alternateName to add
616      */
617     public void addAlternateName(AlternateName alternateName) {
618     	if (alternateName!=null){
619     		if (alternateName.getName() != null && alternateName.getName().length() > MAX_ALTERNATENAME_SIZE){
620     			logger.warn("alternate name "+ alternateName.getName()+" is too long");
621     		} else {
622 	    		alternateName.setGisFeature(this);
623 	    		Set<AlternateName> currentAlternateNames = getAlternateNames();
624 	    		if (currentAlternateNames == null) {
625 	    			currentAlternateNames = new HashSet<AlternateName>();
626 	    		}
627 	    		currentAlternateNames.add(alternateName);
628 	    		this.setAlternateNames(currentAlternateNames);
629     		}
630     	}
631     }
632 
633     /**
634      * Do a double set : add (not replace !) the AlternateNames to the current
635      * GisFeature and for each alternatenames : set the current GisFeature as
636      * the GisFeature of the Alternate Names
637      * 
638      * @param alternateNames
639      *                The alternateNames list to add
640      */
641     public void addAlternateNames(List<AlternateName> alternateNames) {
642 	if (alternateNames != null) {
643 	    for (AlternateName alternateName : alternateNames) {
644 		addAlternateName(alternateName);
645 	    }
646 	}
647     }
648 
649     /**
650      * @return The ASCII name of the current GisFeature
651      */
652     @Column(nullable = true, length = 200)
653     //@Index(name = "gisFeatureAsciiNameIndex")
654     public String getAsciiName() {
655 	return this.asciiName;
656     }
657 
658     /**
659      * @see #getAsciiName()
660      * @param asciiname
661      *                The ASCII name of the current GisFeature
662      */
663     public void setAsciiName(String asciiname) {
664 	this.asciiName = asciiname;
665     }
666 
667     /**
668      * The country code is not mandatory because gisfeature like undersea does
669      * not belongs to a country
670      * 
671      * @return The ISO 3166 alpha-2 letter code.
672      */
673     @Index(name = "gisFeatureCountryindex")
674     @Column(length = 3)
675     public String getCountryCode() {
676 	return countryCode;
677     }
678 
679     /**
680      * @param countryCode
681      *                The ISO 3166 alpha-2 letter code in upper Case (it will be
682      *                automatically uppercased)
683      * @see #getCountryCode()
684      */
685     public void setCountryCode(String countryCode) {
686 	if (countryCode != null) {
687 	    this.countryCode = countryCode.toUpperCase();
688 	} else {
689 	    this.countryCode = countryCode;
690 	}
691     }
692 
693     /**
694      * @return The elevation of this gisFeature (in meters)
695      */
696     public Integer getElevation() {
697 	return elevation;
698     }
699 
700     /**
701      * @param elevation
702      *                The elevation of this gisFeature (in meters)
703      * @see #getElevation()
704      */
705     public void setElevation(Integer elevation) {
706 	this.elevation = elevation;
707     }
708 
709     /**
710      * @return The average elevation of 30'x30' (900mx900m) area in meters
711      */
712     public Integer getGtopo30() {
713 	return gtopo30;
714     }
715 
716     /**
717      * @param gtopo30
718      *                The average elevation of 30'x30' (900mx900m) area to
719      *                set in meters
720      */
721     public void setGtopo30(Integer gtopo30) {
722 	this.gtopo30 = gtopo30;
723     }
724 
725     /**
726      * @return The UTF-8 name for the current GisFeature
727      */
728     @Column(nullable = false, length = 200)
729     //@Index(name = "gisFeatureNameIndex")
730     public String getName() {
731 	return name;
732     }
733 
734     /**
735      * @param name
736      *                The UTF-8 name for the current GisFeature
737      */
738     public void setName(String name) {
739 	this.name = name;
740     }
741 
742     /**
743      * @return The population (how many people are in) for this GisFeature
744      */
745     public Integer getPopulation() {
746 	return population;
747     }
748 
749     /**
750      * @param population
751      *                The population (how many people are in) of this GisFeature
752      */
753     public void setPopulation(Integer population) {
754 	this.population = population;
755     }
756 
757     /**
758      * @return The source for the gisFeature. it tells from which files /
759      *         gazetteers it has been imported
760      */
761     @Enumerated(EnumType.STRING)
762     @Column(nullable = false)
763     public GISSource getSource() {
764 	return source;
765     }
766 
767     /**
768      * @param source
769      *                The source for the gisFeature to be set
770      */
771     public void setSource(GISSource source) {
772 	this.source = source;
773     }
774 
775     /**
776      * @return The timeZone for This GisFeature
777      * @see <a
778      *      href="http://download.geonames.org/export/dump/timeZones.txt">Time
779      *      zone</a>
780      */
781     @Column(length = 40)
782     public String getTimezone() {
783 	return timezone;
784     }
785 
786     /**
787      * @param timezone
788      *                The timeZone for This GisFeature
789      * @see #getTimezone()
790      */
791     public void setTimezone(String timezone) {
792 	this.timezone = timezone;
793 
794     }
795 
796     /**
797      * @param location
798      *                The location of the GisFeature (JTS point)
799      * @see #getLocation()
800      * @see #getLatitude()
801      * @see #getLongitude()
802      */
803     public void setLocation(Point location) {
804 	this.location = location;
805     }
806     
807     /**
808      * @param shape
809      *                The shape of the GisFeature (JTS)
810      * @see #getShape()
811      */
812     public void setShape(Geometry shape) {
813 	this.shape = shape;
814     }
815 
816     /**
817      * @param modificationDate
818      *                The Date of the Last Modification. This fields is not
819      *                updated when saving or updating a GisFeature : This fields
820      *                is to track changes in the gazetteers, not in the
821      *                datastore. The date should match the
822      *                {@link Constants#GIS_DATE_PATTERN}
823      * @see #getModificationDate()
824      */
825     public void setModificationDate(Date modificationDate) {
826 	this.modificationDate = modificationDate;
827     }
828 
829     /**
830      * @param id
831      *                The Id in the datastore. You should never call this
832      *                method. It is the responsability of the dataStore
833      * @see #getId()
834      */
835     public void setId(Long id) {
836 	this.id = id;
837     }
838 
839     /**
840      * @return The featureId for this GisFeature. it is a 'Domain value' not a
841      *         datastore one. A featureId is unique and mandatory
842      */
843     @Index(name = "gisFeatureIdIndex")
844     @Column(unique = true, nullable = false)
845     public Long getFeatureId() {
846 	return featureId;
847     }
848 
849     /**
850      * A featureId is unique and mandatory
851      * 
852      * @param featureId
853      *                The featureId for this GisFeature
854      */
855     public void setFeatureId(Long featureId) {
856 	this.featureId = featureId;
857     }
858 
859     /**
860      * Whether the feature is a city, in the sense we define it in the
861      * {@link FeatureClassCodeHelper} class. It does not check the Class but the
862      * feature class and the feature code
863      * 
864      * @return true if it is a city
865      */
866     @Transient
867     public boolean isCity() {
868 	return FeatureClassCodeHelper.isCity(this.featureClass,
869 		this.featureCode);
870     }
871 
872     /**
873      * Whether the feature is a coutry, in the sense we define it in the
874      * {@link FeatureClassCodeHelper} class. It does not check the Class but the
875      * feature class and the feature code
876      * 
877      * @return true if it is a country
878      */
879     @Transient
880     public boolean isCountry() {
881 	return FeatureClassCodeHelper.isCountry(this.featureClass,
882 		this.featureCode);
883     }
884 
885     /**
886      * Whether the feature is an Adm, in the sense we define it in the
887      * {@link FeatureClassCodeHelper} class. It does not check the Class but the
888      * feature class and the feature code
889      * 
890      * @return true if it is an ADM
891      */
892     @Transient
893     public boolean isAdm() {
894 	return FeatureClassCodeHelper.is_Adm(this.featureClass,
895 		this.featureCode);
896     }
897 
898     /**
899      * @return The feature class for this gisFeature (should always be in
900      *         uppercase because setter automatically convert the feature class
901      *         in upperCase). A feature class regroup some feature code
902      * @see <a href="http://www.geonames.org/export/codes.html">codes</a>
903      */
904    // @Index(name = "gisFeatureFeatureClassindex")
905     @Column(length = 4)
906     public String getFeatureClass() {
907 	return featureClass;
908     }
909 
910     /**
911      * @param featureClass
912      *                The feature class to set. <u>Note</u> The featureClass
913      *                will automaticaly be uppercased.
914      * @see #getFeatureClass()
915      */
916     public void setFeatureClass(String featureClass) {
917 	if (featureClass != null) {
918 	    this.featureClass = featureClass.toUpperCase();
919 	} else {
920 	    this.featureClass = featureClass;
921 	}
922     }
923 
924     /**
925      * @return The featureCode for the current GisFeature. A feature code
926      *         represents a specific feature type. GisGraphy regroup some
927      *         feature codes in an abstract Level called 'place type'. A place type
928      *         regroup several featurecode.
929      * @see FeatureCode
930      */
931     //@Index(name = "gisFeatureFeatureCodeindex")
932     @Column(length = 10)
933     public String getFeatureCode() {
934 	return featureCode;
935     }
936 
937     /**
938      * Populate all the field / association of the current gisFeature with The
939      * Value of The specified One.
940      * 
941      * @param gisFeature
942      *                the gisFeature to populate with
943      */
944     public void populate(GisFeature gisFeature) {
945 	if (gisFeature != null) {
946 	    this.setAdm(gisFeature.getAdm());
947 
948 	    this.setAdm1Code(gisFeature.getAdm1Code());
949 	    this.setAdm2Code(gisFeature.getAdm2Code());
950 	    this.setAdm3Code(gisFeature.getAdm3Code());
951 	    this.setAdm4Code(gisFeature.getAdm4Code());
952 
953 	    this.setAdm1Name(gisFeature.getAdm1Name());
954 	    this.setAdm2Name(gisFeature.getAdm2Name());
955 	    this.setAdm3Name(gisFeature.getAdm3Name());
956 	    this.setAdm4Name(gisFeature.getAdm4Name());
957 
958 	    this.setAlternateNames(gisFeature.getAlternateNames());
959 	    if (getAlternateNames() != null) {
960 		for (AlternateName alternateName : getAlternateNames()) {
961 		    alternateName.setGisFeature(this);
962 		}
963 	    }
964 	    if (gisFeature.getAsciiName() != null) {
965 	    	this.setAsciiName(gisFeature.getAsciiName().trim());
966 	    }
967 	    if (gisFeature.getCountryCode() != null) {
968 	    	this.setCountryCode(gisFeature.getCountryCode().toUpperCase());
969 	    }
970 	    this.setElevation(gisFeature.getElevation());
971 	    this.setFeatureClass(gisFeature.getFeatureClass());
972 	    this.setFeatureCode(gisFeature.getFeatureCode());
973 	    this.setFeatureId(gisFeature.getFeatureId());
974 	    this.setGtopo30(gisFeature.getGtopo30());
975 	    this.setLocation(gisFeature.getLocation());
976 	    this.setModificationDate(gisFeature.getModificationDate());
977 	    this.setName(gisFeature.getName().trim());
978 	    this.setPopulation(gisFeature.getPopulation());
979 	    this.setSource(gisFeature.getSource());
980 	    this.setTimezone(gisFeature.getTimezone());
981 	    Set<ZipCode> zipCodes = gisFeature.getZipCodes();
982 		if (zipCodes!= null){
983 			for (ZipCode zipCode : zipCodes){
984 				this.addZipCode(zipCode);
985 			}
986 		}
987 		this.amenity=gisFeature.getAmenity();
988 		this.openstreetmapId = gisFeature.getOpenstreetmapId();
989 	}
990     }
991 
992     /**
993      * @param featureCode
994      *                The feature Code for this GisFeature 
995      * <u>Note</u> The
996      *                featureCode will automaticaly be uppercased
997      * @see #getFeatureCode()
998      */
999     public void setFeatureCode(String featureCode) {
1000 	if (featureCode != null) {
1001 	    this.featureCode = featureCode.toUpperCase();
1002 	} else {
1003 	    this.featureCode = featureCode;
1004 	}
1005     }
1006 
1007     /**
1008      * @return true If the gisFeature must be sync with the fullText search
1009      *         engine
1010      */
1011     @Transient
1012     public boolean isFullTextSearchable() {
1013 	return true;
1014     }
1015 
1016     /*
1017      * (non-Javadoc)
1018      * 
1019      * @see java.lang.Object#hashCode()
1020      */
1021     @Override
1022     public int hashCode() {
1023 	final int PRIME = 31;
1024 	int result = 1;
1025 	result = PRIME * result
1026 		+ ((featureId == null) ? 0 : featureId.hashCode());
1027 	return result;
1028     }
1029 
1030     /*
1031      * (non-Javadoc)
1032      * 
1033      * @see java.lang.Object#equals(java.lang.Object)
1034      */
1035     @Override
1036     public boolean equals(Object obj) {
1037 	if (this == obj) {
1038 	    return true;
1039 	}
1040 	if (obj == null) {
1041 	    return false;
1042 	}
1043 	if (getClass() != obj.getClass()) {
1044 	    return false;
1045 	}
1046 	final GisFeature other = (GisFeature) obj;
1047 	if (featureId == null) {
1048 	    if (other.featureId != null) {
1049 		return false;
1050 	    }
1051 	} else if (!featureId.equals(other.featureId)) {
1052 	    return false;
1053 	}
1054 	return true;
1055     }
1056 
1057     /**
1058      * Returns a name of the form : (adm1Name et adm2Name are printed) Paris,
1059      * D├ępartement de Ville-De-Paris, Ile-De-France, (FR)
1060      * 
1061      * @param withCountry
1062      *                Whether the country information should be added
1063      * @return a name with the Administrative division and Country
1064      */
1065     @Transient
1066     public String getFullyQualifiedName(boolean withCountry) {
1067 	return GisFeatureHelper.getInstance().getFullyQualifiedName(this, withCountry);
1068     }
1069     
1070     /**
1071      * @return a name with the Administrative division (but without Country)
1072      * wrap {@link #getFullyQualifiedName(boolean)}
1073      * @see #getFullyQualifiedName(boolean)
1074      */
1075     @Transient
1076     public String getFullyQualifiedName() {
1077 	return getFullyQualifiedName(false);
1078     }
1079 
1080     /*
1081      * (non-Javadoc)
1082      * 
1083      * @see java.lang.Object#toString()
1084      */
1085     @Override
1086     public String toString() {
1087 	return this.getClass().getSimpleName() + "[" + getFeatureId() + "]["
1088 		+ getFeatureClass() + "." + getFeatureCode() + "][" + getName()
1089 		+ "]";
1090     }
1091 
1092   //TODO tests zip
1093     /**
1094      * Do a double set : add the zip code to the current GisFeature and set
1095      * this GisFeature as the GisFeature of the zipcode
1096      * @param zipCode the zip code to add
1097      */
1098 	public void addZipCode(ZipCode zipCode) {
1099 		if (zipCode!=null){
1100 			Set<ZipCode> actualZipCodes = getZipCodes();
1101 			if (actualZipCodes == null) {
1102 				actualZipCodes = new HashSet<ZipCode>();
1103 			}
1104 			actualZipCodes.add(zipCode);
1105 			this.setZipCodes(actualZipCodes);
1106 			zipCode.setGisFeature(this);
1107 		}
1108 	}
1109 
1110 	/**
1111      * Do a double set : add the zip codes to the current GisFeature and set
1112      * this GisFeature as the GisFeature of the zipcodes
1113      *  * @param zipCodes the zip codes to add
1114      */
1115 	//TODO tests zip with null, and so on
1116 	public void addZipCodes(Collection<ZipCode> zipCodes) {
1117 		if (zipCodes != null) {
1118 		    for (ZipCode zipCode : zipCodes) {
1119 			addZipCode(zipCode);
1120 		    }
1121 		}
1122 	}
1123 
1124 	 /**
1125      * @return the zip codes for the city
1126      */
1127 	@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "gisFeature")
1128 	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
1129 	@Fetch(FetchMode.SELECT)
1130 	//TODO tests zip
1131 	public Set<ZipCode> getZipCodes() {
1132 		return zipCodes;
1133 	}
1134 
1135 	 /**
1136      * Set The zipCodes for the city. IMPORTANT : if you set the zipCodes, you should do a double set 
1137      * : that means that you should set the gisfeature property for all the zip codes, if you don't 
1138      * you will get problems when saving entity in the datastore. Please use this method
1139      * you should prefer the methods {@link #addZipCode(ZipCode)} and {@link #addZipCodes(Collection)}
1140      *  that do it automatically.
1141      * 
1142      * @param zipCodes
1143      *                The zip codes for the City
1144      */
1145 	//TODO tests zip
1146 	public void setZipCodes(Set<ZipCode> zipCodes) {
1147 		this.zipCodes = zipCodes;
1148 	}
1149 	
1150 
1151 	
1152 
1153     /**
1154      * @return the amenity (typically the osm tag)
1155      */
1156     public String getAmenity() {
1157 		return amenity;
1158 	}
1159 
1160     
1161 	/**
1162 	 * @param amenity the amenity tag 
1163 	 * (typically the osm tag)
1164 	 */
1165 	public void setAmenity(String amenity) {
1166 		this.amenity = amenity;
1167 	}
1168 
1169 	/**
1170 	 * @return the openstreetmap id. 
1171 	 * GisFeature has the openstreetmap field when it
1172 	 * is a OSM POI
1173 	 */
1174 	public Long getOpenstreetmapId() {
1175 		return openstreetmapId;
1176 	}
1177 
1178 	/**
1179 	 * @param openstreetmapId
1180 	 * The openstreetmap id of the POI
1181 	 */
1182 	public void setOpenstreetmapId(Long openstreetmapId) {
1183 		this.openstreetmapId = openstreetmapId;
1184 	}
1185 	
1186 	 /**
1187      * @return The city or state or any information where the street is located
1188      */
1189     public String getIsIn() {
1190 	return isIn;
1191     }
1192 
1193     /**
1194      * @param isIn
1195      *            The city or state or any information where the street is
1196      *            located
1197      */
1198     public void setIsIn(String isIn) {
1199 	this.isIn = isIn;
1200     }
1201     
1202     /**
1203 	 * @return the place where the street is located, 
1204 	 * this field is filled when {@link OpenStreetMap#isIn}
1205 	 *  is filled and we got more specific details (generally quarter, neighborhood)
1206 	 */
1207 	public String getIsInPlace() {
1208 		return isInPlace;
1209 	}
1210 
1211 	/**
1212 	 * @param isInPlace the most precise information on where the street is located,
1213 	 * generally quarter neighborhood
1214 	 */
1215 	public void setIsInPlace(String isInPlace) {
1216 		this.isInPlace = isInPlace;
1217 	}
1218 
1219 	/**
1220 	 * @return the adm (aka administrative division) where the street is located.
1221 	 */
1222 	public String getIsInAdm() {
1223 		return isInAdm;
1224 	}
1225 
1226 	/**
1227 	 * @param isInAdm  the adm (aka administrative division) where the street is located
1228 	 */
1229 	public void setIsInAdm(String isInAdm) {
1230 		this.isInAdm = isInAdm;
1231 	}
1232 
1233 	//we don't sync it, because we don't want join table, for the moment
1234 	@Transient
1235 	public Set<String> getIsInZip() {
1236 		return isInZip;
1237 	}
1238 
1239 	/**
1240 	 * @param isInZip the zipcode where the street is located.
1241 	 */
1242 	public void setIsInZip(Set<String> isInZip) {
1243 		this.isInZip = isInZip;
1244 	}
1245 
1246 	 /**
1247      * add a zip
1248      */
1249     public void addZip(String zip) {
1250 	Set<String> currentZips = getIsInZip();
1251 	if (currentZips == null) {
1252 		currentZips = new HashSet<String>();
1253 	}
1254 	currentZips.add(zip);
1255 	this.setIsInZip(currentZips);
1256     }
1257 
1258     /**
1259      * add zips
1260      */
1261     public void addZips(Collection<String> zips) {
1262 	if (zips != null) {
1263 	    for (String zip : zips) {
1264 	    	addZip(zip);
1265 	    }
1266 	}
1267     }
1268 	
1269 	/**
1270 	 * This field is only for relevance and allow to search for street<->cities in 
1271      * many alternateNames. It is not in stored
1272 	 * 
1273 	 */
1274 	@Transient
1275 	public Set<String> getIsInCityAlternateNames() {
1276 		return isInCityAlternateNames;
1277 	}
1278 
1279 	public void setIsInCityAlternateNames(Set<String> isInCityAlternateNames) {
1280 		this.isInCityAlternateNames = isInCityAlternateNames;
1281 	}
1282 	
1283 
1284     public void addIsInCitiesAlternateName(String isInCityAlternateName) {
1285 	Set<String> currentCitiesAlternateNames = getIsInCityAlternateNames();
1286 	if (currentCitiesAlternateNames == null) {
1287 		currentCitiesAlternateNames = new HashSet<String>();
1288 	}
1289 	currentCitiesAlternateNames.add(isInCityAlternateName);
1290 	this.setIsInCityAlternateNames(currentCitiesAlternateNames);
1291     }
1292 
1293     public void addIsInCitiesAlternateNames(Collection<String> isInCityAlternateNames) {
1294 	if (isInCityAlternateNames != null) {
1295 	    for (String isInCityAlternateName : isInCityAlternateNames) {
1296 	    	addIsInCitiesAlternateName(isInCityAlternateName);
1297 	    }
1298 	}
1299     }
1300 	
1301 }