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 }