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.Collection;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.SortedSet;
31  import java.util.TreeSet;
32  
33  import javax.persistence.CascadeType;
34  import javax.persistence.Column;
35  import javax.persistence.Entity;
36  import javax.persistence.EnumType;
37  import javax.persistence.Enumerated;
38  import javax.persistence.GeneratedValue;
39  import javax.persistence.GenerationType;
40  import javax.persistence.Id;
41  import javax.persistence.OneToMany;
42  import javax.persistence.SequenceGenerator;
43  import javax.persistence.Transient;
44  
45  import org.hibernate.annotations.Cache;
46  import org.hibernate.annotations.CacheConcurrencyStrategy;
47  import org.hibernate.annotations.Fetch;
48  import org.hibernate.annotations.FetchMode;
49  import org.hibernate.annotations.Index;
50  import org.hibernate.annotations.Sort;
51  import org.hibernate.annotations.SortType;
52  import org.hibernate.annotations.Type;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import com.gisgraphy.domain.valueobject.SRID;
57  import com.gisgraphy.helper.IntrospectionIgnoredField;
58  import com.gisgraphy.street.HouseNumberComparator;
59  import com.gisgraphy.street.StreetSearchMode;
60  import com.gisgraphy.street.StreetType;
61  import com.vividsolutions.jts.geom.LineString;
62  import com.vividsolutions.jts.geom.Point;
63  
64  /**
65   * Represents a street in OpenStreetMap. it is different from {@link Street} that represent a street in Geonames.
66   * 
67   * @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
68   */
69  @Entity
70  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
71  @SequenceGenerator(name = "streetosmsequence", sequenceName = "street_osm_sequence")
72  public class OpenStreetMap {
73  	
74  	 protected static final Logger logger = LoggerFactory
75  			    .getLogger(OpenStreetMap.class);
76  	
77  	@Transient
78  	private static final HouseNumberComparator houseNumberComparator = new HouseNumberComparator();
79  	
80  
81      public static final String SHAPE_COLUMN_NAME = "shape";
82      
83      public static final int MAX_ALTERNATENAME_SIZE = 200;
84  
85      /**
86       * Name of the column that is equals to to_tsvector(
87       * {@link #FULLTEXTSEARCH_COLUMN_NAME} It is used to do Fulltext search with
88       * the postgres text search module (to use the index). This value should be
89       * change if the getter and the setter of the {@link #textSearchName} change
90       */
91      public static final String FULLTEXTSEARCH_VECTOR_PROPERTY_NAME = "textsearchVector";
92  
93      
94      /**
95       * Name of the field property in hibernate. This is a string that is used
96       * for fulltext and contains search without postgres fulltext engine. this
97       * fields will have the name without accent and special char This value
98       * should be changed if the getter and the setter of the
99       * {@link #getTextsearchVector()} change
100      * 
101      * @see StreetSearchMode#FULLTEXT
102      */
103     public static final String FULLTEXTSEARCH_PROPERTY_NAME = "textSearchName";
104 
105     /**
106      * Name of the column that is equals to store a string that is used for
107      * fulltext search. it deffer form the @{@link #FULLTEXTSEARCH_COLUMN_NAME}
108      * because Hibernate, by default, lowercase the property to get the column
109      * name This value should be change if the getter and the setter of the
110      * {@link #getTextsearchVector()} change
111      * 
112      * @see StreetSearchMode#FULLTEXT
113      */
114     public static final String FULLTEXTSEARCH_COLUMN_NAME = FULLTEXTSEARCH_PROPERTY_NAME.toLowerCase();
115 
116 
117     public static final String LOCATION_COLUMN_NAME = "location";
118 
119     /**
120      * Needed by CGLib
121      */
122     public OpenStreetMap() {
123     }
124 
125     @IntrospectionIgnoredField
126     private Long id;
127 
128     private Long gid;
129     
130     private Long openstreetmapId;
131 
132     private String name;
133 
134     private StreetType streetType;
135 
136     private boolean oneWay = false;
137 
138     private Point location;
139 
140     @IntrospectionIgnoredField
141     private LineString shape;
142     
143     @IntrospectionIgnoredField
144     private Integer population;
145     
146     private String isIn;
147     
148     private String isInPlace;
149     
150     private String isInAdm;
151     
152     /**
153      * This field is only for relevance and allow to search for street<->cities in 
154      * many alternateNames. It is not in stored
155      */
156     @IntrospectionIgnoredField
157     private Set<String> isInCityAlternateNames;
158     
159     private Set<String> isInZip;
160     
161     private String fullyQualifiedAddress;
162 
163     private String countryCode;
164 
165     private Double length;
166     
167     //@Sort(comparator=HouseNumberComparator.class,type=SortType.COMPARATOR)
168     private SortedSet<HouseNumber> houseNumbers;
169 
170     @IntrospectionIgnoredField
171     private String partialSearchName;
172 
173     @IntrospectionIgnoredField
174     private String textSearchName;
175     
176     private List<AlternateOsmName> alternateNames;
177     
178     /**
179      * if the associated city has been found by shape 
180      * (only for sttistics and relevance purpose
181      */
182     @IntrospectionIgnoredField
183     private boolean cityConfident = false;
184     
185     /**
186      * @return A list of the {@link AlternateName}s for this GisFeature
187      */
188     @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "street")
189     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
190     @Fetch(FetchMode.SELECT)
191     public List<AlternateOsmName> getAlternateNames() {
192 	return alternateNames;
193     }
194     
195     /**
196      * @param alternateNames
197      *                The {@link AlternateName}s for this GisFeature
198      */
199     public void setAlternateNames(List<AlternateOsmName> alternateNames) {
200 	this.alternateNames = alternateNames;
201     }
202 
203     /**
204      * Do a double set : add the alternate name to the current GisFeature and set
205      * this GisFeature as the GisFeature of the specified AlternateName
206      * 
207      * @param alternateName
208      *                the alternateName to add
209      */
210     public void addAlternateName(AlternateOsmName alternateName) {
211     	if (alternateName!=null){
212     		if (alternateName.getName() != null && alternateName.getName().length() > MAX_ALTERNATENAME_SIZE){
213     			logger.warn("alternate name "+ alternateName.getName()+" is too long");
214     		} else {
215     		List<AlternateOsmName> currentAlternateNames = getAlternateNames();
216     		if (currentAlternateNames == null) {
217     			currentAlternateNames = new ArrayList<AlternateOsmName>();
218     		}
219     		currentAlternateNames.add(alternateName);
220     		this.setAlternateNames(currentAlternateNames);
221     		alternateName.setStreet(this);
222     	}
223     	}
224     }
225 
226     /**
227      * Do a double set : add (not replace !) the AlternateNames to the current
228      * GisFeature and for each alternatenames : set the current GisFeature as
229      * the GisFeature of the Alternate Names
230      * 
231      * @param alternateNames
232      *                The alternateNames list to add
233      */
234     public void addAlternateNames(List<AlternateOsmName> alternateNames) {
235 	if (alternateNames != null) {
236 	    for (AlternateOsmName alternateName : alternateNames) {
237 		addAlternateName(alternateName);
238 	    }
239 	}
240     }
241 
242     /**
243      * (Experimental) This String is used to search for a part of a street name
244      * 
245      * @see StreetSearchMode#CONTAINS
246      * @return the partialSearchName
247      */
248     @Column(unique = false, nullable = true, columnDefinition = "text")
249     public String getPartialSearchName() {
250 	return partialSearchName;
251     }
252 
253     /**
254      * @param partialSearchName
255      *            the partialSearchName to set
256      */
257     public void setPartialSearchName(String partialSearchName) {
258 	this.partialSearchName = partialSearchName;
259     }
260 
261     /**
262      * This value is use to do a Fulltext search for a street name with index
263      * 
264      * @return the textSearchName
265      */
266     @Column(unique = false, nullable = true, columnDefinition = "text")
267     public String getTextSearchName() {
268 	return textSearchName;
269     }
270 
271     /**
272      * @param textSearchName
273      *            the textSearchName to set
274      */
275     public void setTextSearchName(String textSearchName) {
276 	this.textSearchName = textSearchName;
277     }
278 
279     /**
280      * IT DOES NOTHING. ONLY USE BY HIBERNATE This field is only use for the
281      * text search to improve performance, you should not set / get a value, it
282      * is declared here, to create the column
283      * 
284      * @return null ALWAYS
285      */
286     @Column(unique = false, nullable = true, insertable = false, updatable = true, columnDefinition = "tsvector")
287     @Type(type = "com.gisgraphy.hibernate.type.TsVectorStringType")
288     public String getTextsearchVector() {
289 	return null;
290     }
291 
292     /**
293      * IT DOES NOTHING. ONLY USE BY HIBERNATE
294      * 
295      * @param textsearchVector
296      *            the textsearchVector to set
297      * 
298      */
299     public void setTextsearchVector(String textsearchVector) {
300     }
301 
302     /**
303      * IT DOES NOTHING. ONLY USE BY HIBERNATE This field is only use for the
304      * autocomplete search to improve performance, you should not set / get a
305      * value, it is declared here, to create the column
306      * 
307      * @return null ALWAYS
308      */
309     @Column(unique = false, nullable = true, insertable = false, updatable = true, columnDefinition = "tsvector")
310     @Type(type = "com.gisgraphy.hibernate.type.TsVectorStringType")
311     public String getPartialsearchVector() {
312 	return null;
313     }
314 
315     /**
316      * IT DOES NOTHING. ONLY USE BY HIBERNATE
317      * 
318      * @param partialsearchVector
319      *            the ilikesearch to set
320      */
321     public void setPartialsearchVector(String partialsearchVector) {
322 
323     }
324 
325     /**
326      * @return the id
327      */
328     @Id
329     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "streetosmsequence")
330     public Long getId() {
331 	return id;
332     }
333 
334     /**
335      * @param id
336      *            the id to set
337      */
338     public void setId(Long id) {
339 	this.id = id;
340     }
341 
342     /**
343      * @return an uniqueid that identify the street, it differs from {@link OpenStreetMap#openstreetmapId}
344      *  because the value can not be in conflict between geonames and openstreetmap
345      */
346     @Index(name = "streetosmgidindex")
347     @Column(unique = true, nullable = false)
348     public Long getGid() {
349 	return gid;
350     }
351 
352     /**
353      * @param gid
354      *            the gid to set
355      */
356     public void setGid(Long gid) {
357 	this.gid = gid;
358     }
359     
360     /**
361      * @return the openstreetmap internal id
362      */
363     @Index(name = "streetosmopenstreetmapidindex")
364     @Column(unique = false, nullable = true)
365     public Long getOpenstreetmapId() {
366         return openstreetmapId;
367     }
368 
369     /**
370      * @param openstreetmapId the openstreetmap internal id
371      */
372     public void setOpenstreetmapId(Long openstreetmapId) {
373         this.openstreetmapId = openstreetmapId;
374     }
375 
376     /**
377      * @return the name
378      */
379     @Column(length = 255)
380     public String getName() {
381 	return name;
382     }
383 
384     /**
385      * @param name
386      *            the name to set
387      */
388     public void setName(String name) {
389 	this.name = name;
390     }
391 
392     /**
393      * @return the type of the street
394      */
395     @Index(name = "streetosmtypeIndex")
396     @Enumerated(EnumType.STRING)
397     public StreetType getStreetType() {
398 	return streetType;
399     }
400 
401     /**
402      * @param streetType
403      *            the streetType to set
404      */
405     public void setStreetType(StreetType streetType) {
406 	this.streetType = streetType;
407     }
408 
409     /**
410      * @return the oneway
411      */
412     @Index(name = "streetosmonewayIndex")
413     @Column(length = 9)
414     public boolean isOneWay() {
415 	return oneWay;
416     }
417 
418     /**
419      * @param oneWay
420      *            the oneWay to set
421      */
422     public void setOneWay(boolean oneWay) {
423 	this.oneWay = oneWay;
424     }
425 
426     /**
427      * Returns The JTS location point of the current street : The Geometry
428      * representation for the latitude, longitude. The Return type is a JTS
429      * point. The Location is calculate from the 4326 {@link SRID}
430      * 
431      * @see SRID
432      * @return The JTS Point
433      */
434     @Type(type = "org.hibernatespatial.GeometryUserType")
435     @Column(name = OpenStreetMap.LOCATION_COLUMN_NAME)
436     public Point getLocation() {
437 	return location;
438     }
439     
440     /**
441      * @return Returns the latitude (north-south) from the Location
442      *         {@link #getLocation()}.
443      * @see #getLongitude()
444      * @see #getLocation()
445      */
446     @Transient
447     public Double getLatitude() {
448 	Double latitude = null;
449 	if (this.location != null) {
450 	    latitude = this.location.getY();
451 	}
452 	return latitude;
453     }
454     
455     /**
456      * @return Returns the longitude (east-west) from the Location
457      *         {@link #getLocation()}.
458      * @see #getLongitude()
459      * @see #getLocation()
460      */
461     @Transient
462     public Double getLongitude() {
463 	Double longitude = null;
464 	if (this.location != null) {
465 	    longitude = this.location.getX();
466 	}
467 	return longitude;
468     }
469 
470     /**
471      * @param location
472      *            the location to set
473      */
474     public void setLocation(Point location) {
475 	this.location = location;
476     }
477 
478     /**
479      * @return the shape
480      */
481     @Type(type = "org.hibernatespatial.GeometryUserType")
482     @Column(nullable = false)
483     public LineString getShape() {
484 	return shape;
485     }
486 
487     /**
488      * @param shape
489      *            the shape to set
490      */
491     public void setShape(LineString shape) {
492 	this.shape = shape;
493     }
494 
495     /**
496      * @return The ISO 3166 alpha-2 letter code.
497      */
498     @Index(name = "openstreetmapcountryindex")
499     @Column(length = 3)
500     public String getCountryCode() {
501 	return countryCode;
502     }
503 
504     /**
505      * @param countryCode
506      *            the countryCode to set
507      */
508     public void setCountryCode(String countryCode) {
509 	this.countryCode = countryCode;
510     }
511 
512     /**
513      * @return the length of the street in meters
514      */
515     public Double getLength() {
516 	return length;
517     }
518 
519     /**
520      * @param length
521      *            the length to set
522      */
523     public void setLength(Double length) {
524 	this.length = length;
525     }
526 
527     @Override
528     public int hashCode() {
529 	final int prime = 31;
530 	int result = 1;
531 	result = prime * result + ((id == null) ? 0 : id.hashCode());
532 	return result;
533     }
534 
535     @Override
536     //TODO use business key
537     public boolean equals(Object obj) {
538 	if (this == obj)
539 	    return true;
540 	if (obj == null)
541 	    return false;
542 	if (getClass() != obj.getClass())
543 	    return false;
544 	OpenStreetMap other = (OpenStreetMap) obj;
545 	if (id == null) {
546 	    if (other.id != null)
547 		return false;
548 	} else if (!id.equals(other.id))
549 	    return false;
550 	return true;
551     }
552 
553 
554     /**
555      * @return The city or state or any information where the street is located
556      */
557     public String getIsIn() {
558     	return isIn;
559     }
560 
561     /**
562      * @param isIn
563      *            The city or state or any information where the street is
564      *            located
565      */
566     public void setIsIn(String isIn) {
567 	this.isIn = isIn;
568     }
569     
570 
571 	/**
572 	 * @return the place where the street is located, 
573 	 * this field is filled when {@link OpenStreetMap#isIn}
574 	 *  is filled and we got more specific details (generally quarter, neighborhood)
575 	 */
576 	public String getIsInPlace() {
577 		return isInPlace;
578 	}
579 
580 	/**
581 	 * @param isInPlace the most precise information on where the street is located,
582 	 * generally quarter neighborhood
583 	 */
584 	public void setIsInPlace(String isInPlace) {
585 		this.isInPlace = isInPlace;
586 	}
587 
588 	/**
589 	 * @return the adm (aka administrative division) where the street is located.
590 	 */
591 	public String getIsInAdm() {
592 		return isInAdm;
593 	}
594 
595 	/**
596 	 * @param isInAdm  the adm (aka administrative division) where the street is located
597 	 */
598 	public void setIsInAdm(String isInAdm) {
599 		this.isInAdm = isInAdm;
600 	}
601 
602 	/**
603 	 * @return the zipcode where the street is located
604 	 */
605 	//don't want to store it, just for fulltext purpose
606 	@Transient
607 	public Set<String> getIsInZip() {
608 		return isInZip;
609 	}
610 
611 	/**
612 	 * @param isInZip the zipcode where the street is located.
613 	 */
614 	public void setIsInZip(Set<String> isInZip) {
615 		this.isInZip = isInZip;
616 	}
617 	
618     
619 
620     /**
621      * add a zip
622      */
623     public void addZip(String zip) {
624 	Set<String> currentZips = getIsInZip();
625 	if (currentZips == null) {
626 		currentZips = new HashSet<String>();
627 	}
628 	currentZips.add(zip);
629 	this.setIsInZip(currentZips);
630     }
631 
632     /**
633      * add zips
634      */
635     public void addZips(Collection<String> zips) {
636 	if (zips != null) {
637 	    for (String zip : zips) {
638 	    	addZip(zip);
639 	    }
640 	}
641     }
642 
643 
644 	public String getFullyQualifiedAddress() {
645 		return fullyQualifiedAddress;
646 	}
647 
648 	public void setFullyQualifiedAddress(String fullyQualifiedAddress) {
649 		this.fullyQualifiedAddress = fullyQualifiedAddress;
650 	}
651 
652 	public Integer getPopulation() {
653 		return population;
654 	}
655 
656 	public void setPopulation(Integer population) {
657 		this.population = population;
658 	}
659 
660 	/**
661 	 * @return the houseNumbers associated to that street
662 	 */
663 	@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "street")
664     @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
665     @Fetch(FetchMode.SELECT)
666 	@Sort(comparator=HouseNumberComparator.class,type=SortType.COMPARATOR)
667 	public SortedSet<HouseNumber> getHouseNumbers() {
668 		return houseNumbers;
669 	}
670 
671 	/**
672 	 * @param houseNumbers the houseNumbers to set
673 	 */
674 	public void setHouseNumbers(SortedSet<HouseNumber> houseNumbers) {
675 		this.houseNumbers = houseNumbers;
676 	}
677 	
678 	/**
679      * Do a double set : add the house number to the current street and set
680      * this street as the street of the specified AlternateName
681      * 
682      * @param houseNumber
683      *                the houseNumber to add
684      */
685 	public void addHouseNumber(HouseNumber houseNumber) {
686 		if (houseNumber!=null){
687 			SortedSet<HouseNumber> currentHouseNumbers = getHouseNumbers();
688 			if (currentHouseNumbers == null) {
689 				currentHouseNumbers = new TreeSet<HouseNumber>(houseNumberComparator);
690 			}
691 			currentHouseNumbers.add(houseNumber);
692 			this.setHouseNumbers(currentHouseNumbers);
693 			houseNumber.setStreet(this);
694 		}
695 	}
696 
697     /**
698      * Do a double set : add (not replace !) the House Numbers to the current
699      * street and for each House numbers : set the current street as
700      * the street of the House Numbers
701      * 
702      * @param HouseNumbers
703      *                The House Numbers list to add
704      */
705     public void addHouseNumbers(List<HouseNumber> HouseNumbers) {
706 	if (HouseNumbers != null) {
707 	    for (HouseNumber houseNumber : HouseNumbers) {
708 	    	addHouseNumber(houseNumber);
709 	    }
710 	}
711     }
712 
713 	public boolean isCityConfident() {
714 		return cityConfident;
715 	}
716 
717 	public void setCityConfident(boolean cityConfident) {
718 		this.cityConfident = cityConfident;
719 	}
720 
721 	/**
722 	 * This field is only for relevance and allow to search for street<->cities in 
723      * many alternateNames. It is not in stored
724 	 * 
725 	 */
726 	@Transient
727 	public Set<String> getIsInCityAlternateNames() {
728 		return isInCityAlternateNames;
729 	}
730 
731 	public void setIsInCityAlternateNames(Set<String> isInCityAlternateNames) {
732 		this.isInCityAlternateNames = isInCityAlternateNames;
733 	}
734 	
735 
736 	public void addIsInCitiesAlternateName(String isInCityAlternateName) {
737 		if (isInCityAlternateName!=null){
738 			Set<String> currentCitiesAlternateNames = getIsInCityAlternateNames();
739 			if (currentCitiesAlternateNames == null) {
740 				currentCitiesAlternateNames = new HashSet<String>();
741 			}
742 			currentCitiesAlternateNames.add(isInCityAlternateName);
743 			this.setIsInCityAlternateNames(currentCitiesAlternateNames);
744 		}
745 	}
746 
747     public void addIsInCitiesAlternateNames(Collection<String> isInCityAlternateNames) {
748 	if (isInCityAlternateNames != null) {
749 	    for (String isInCityAlternateName : isInCityAlternateNames) {
750 	    	addIsInCitiesAlternateName(isInCityAlternateName);
751 	    }
752 	}
753     }
754 
755 	@Override
756 	public String toString() {
757 		StringBuilder builder = new StringBuilder();
758 		builder.append("OpenStreetMap [");
759 		if (openstreetmapId != null)
760 			builder.append("openstreetmapId=").append(openstreetmapId)
761 					.append(", ");
762 		if (name != null)
763 			builder.append("name=").append(name).append(", ");
764 		if (location != null)
765 			builder.append("location=").append(location);
766 		builder.append("]");
767 		return builder.toString();
768 	}
769 
770 }