distribution hack
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / Distribution.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.model.description;
11
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18
19 import eu.etaxonomy.cdm.common.CdmUtils;
20 import eu.etaxonomy.cdm.model.common.Language;
21 import eu.etaxonomy.cdm.model.common.Representation;
22 import eu.etaxonomy.cdm.model.location.NamedArea;
23 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
24 import eu.etaxonomy.cdm.model.taxon.Taxon;
25
26 import org.apache.log4j.Logger;
27 import org.hibernate.annotations.Cascade;
28 import org.hibernate.annotations.CascadeType;
29 import javax.persistence.*;
30 import javax.xml.bind.annotation.XmlAccessType;
31 import javax.xml.bind.annotation.XmlAccessorType;
32 import javax.xml.bind.annotation.XmlElement;
33 import javax.xml.bind.annotation.XmlIDREF;
34 import javax.xml.bind.annotation.XmlRootElement;
35 import javax.xml.bind.annotation.XmlSchemaType;
36 import javax.xml.bind.annotation.XmlType;
37
38 /**
39 * This class represents elementary distribution data for a {@link Taxon taxon}.
40 * Only {@link TaxonDescription taxon descriptions} may contain distributions.
41 * A distribution instance consist of a {@link NamedArea named area} and of a {@link PresenceAbsenceTermBase status}
42 * describing the absence or the presence of a taxon (like "extinct"
43 * or "introduced") in this named area.
44 * <P>
45 * This class corresponds partially to: <ul>
46 * <li> CodedDescriptionType according to the the SDD schema
47 * <li> Distribution according to the TDWG ontology
48 * </ul>
49 *
50 * @author m.doering
51 * @version 1.0
52 * @created 08-Nov-2007 13:06:21
53 */
54 @XmlAccessorType(XmlAccessType.FIELD)
55 @XmlType(name = "Distribution", propOrder = {
56 "area",
57 "status"
58 })
59 @XmlRootElement(name = "Distribution")
60 @Entity
61 public class Distribution extends DescriptionElementBase {
62 static Logger logger = Logger.getLogger(Distribution.class);
63
64 @XmlElement(name = "NamedArea")
65 @XmlIDREF
66 @XmlSchemaType(name = "IDREF")
67 private NamedArea area;
68
69 @XmlElement(name = "PresenceAbsenceStatus")
70 private PresenceAbsenceTermBase<?> status;
71
72
73 /**
74 * Class constructor: creates a new empty distribution instance.
75 * The corresponding {@link Feature feature} is set to {@link Feature#DISTRIBUTION() DISTRIBUTION}.
76 */
77 protected Distribution(){
78 super(Feature.DISTRIBUTION());
79 }
80
81
82 /**
83 * Creates an empty distribution instance. The corresponding {@link Feature feature}
84 * is set to {@link Feature#DISTRIBUTION() DISTRIBUTION}.
85 *
86 * @see #NewInstance(NamedArea, PresenceAbsenceTermBase)
87 */
88 public static Distribution NewInstance(){
89 Distribution result = new Distribution();
90 return result;
91 }
92
93 /**
94 * Creates a distribution instance with the given {@link NamedArea named area} and {@link PresenceAbsenceTermBase status}.
95 * The corresponding {@link Feature feature} is set to {@link Feature#DISTRIBUTION() DISTRIBUTION}.
96 *
97 * @param area the named area for the new distribution
98 * @param status the presence or absence term for the new distribution
99 * @see #NewInstance()
100 */
101 public static Distribution NewInstance(NamedArea area, PresenceAbsenceTermBase<?> status){
102 Distribution result = new Distribution();
103 result.setArea(area);
104 result.setStatus(status);
105 return result;
106 }
107
108
109
110
111 /**
112 * Deprecated because {@link Feature feature} should always be {@link Feature#DISTRIBUTION() DISTRIBUTION}
113 * for all distribution instances.
114 */
115 /* (non-Javadoc)
116 * @see eu.etaxonomy.cdm.model.description.DescriptionElementBase#setFeature(eu.etaxonomy.cdm.model.description.Feature)
117 */
118 @Override
119 @Deprecated
120 public void setFeature(Feature feature) {
121 super.setFeature(feature);
122 }
123
124 /**
125 * Returns the {@link NamedArea named area} <i>this</i> distribution applies to.
126 */
127 @ManyToOne
128 @Cascade({CascadeType.SAVE_UPDATE})
129 public NamedArea getArea(){
130 return this.area;
131 }
132 /**
133 * @see #getArea()
134 */
135 public void setArea(NamedArea area){
136 this.area = area;
137 }
138
139 /**
140 * Returns the {@link PresenceAbsenceTermBase presence or absence term} for <i>this</i> distribution.
141 */
142 @ManyToOne
143 public PresenceAbsenceTermBase<?> getStatus(){
144 return this.status;
145 }
146 /**
147 * @see #getStatus()
148 */
149 public void setStatus(PresenceAbsenceTermBase<?> status){
150 this.status = status;
151 }
152
153 //preliminary implementation for TDWG areas
154 @Transient
155 //TODO move to an other place -> e.g. service layer
156 public static String getWebServiceUrl(Set<Distribution> distributions, String webServiceUrl, int width, int height, String bbox){
157 String result = "";
158 String layer = "";
159 String areaData = "";
160 String areaStyle = "";
161 String widthStr = null;
162 String heightStr = null;
163 String adLayerSeperator = "/";
164
165 if (webServiceUrl == null){
166 logger.warn("No WebServiceURL defined");
167 return null;
168 }
169 //List<String> layerStrings = new ArrayList<String>();
170 Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();
171 List<PresenceAbsenceTermBase<?>> statusList = new ArrayList<PresenceAbsenceTermBase<?>>();
172
173 //bbox, width, hight
174 if (width > 0){
175 widthStr = "w=" + width;
176 }
177 if (height > 0){
178 heightStr = "h=" + height;
179 }
180
181 //iterate through distributions and group styles and layers
182 for (Distribution distribution:distributions){
183 //collect status
184 PresenceAbsenceTermBase<?> status = distribution.getStatus();
185 if (! statusList.contains(status)){
186 statusList.add(status);
187 }
188 //group by layers and styles
189 NamedArea area = distribution.getArea();
190 if (area != null){
191 NamedAreaLevel level = area.getLevel();
192 String geoLayerString = getGeoServiceLayer(level);
193 //Set<Distribution> layerDistributionSet;
194 //int index = layerStrings.indexOf(geoLayerString);
195 Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerString);
196 if (styleMap == null){
197 styleMap = new HashMap<Integer, Set<Distribution>>();
198 layerMap.put(geoLayerString, styleMap);
199 }
200 addDistributionToMap(distribution, styleMap, statusList);
201 }
202 }
203
204 //layer
205 layer = "";
206 for (String layerString : layerMap.keySet()){
207 layer += "|" + layerString;
208 }
209 layer = "l=" + layer.substring(1); //remove first |
210
211
212 //style
213 areaStyle = "";
214 Map<PresenceAbsenceTermBase<?>, Character> styleCharMap = new HashMap<PresenceAbsenceTermBase<?>, Character>();
215 int i = 0;
216 for (PresenceAbsenceTermBase<?> status: statusList){
217 char style = getStyleAbbrev(i);
218 String color = status.getDefaultColor();//"00FFAA"; //TODO
219 areaStyle += "|" + style + ":" + color;
220 styleCharMap.put(status, style);
221 i++;
222 }
223
224 String styleWorkaround = "|z:FFFFFF";
225 areaStyle = "as=" + areaStyle.substring(1) + styleWorkaround; //remove first |
226
227 //areaData
228 areaData = "";
229 boolean isFirstLayer = true;
230 for (String layerString : layerMap.keySet()){
231 //Set<Distribution> layerDistributions = layerData.get(layerIndex);
232 //int distributionListIndex = 1;
233 areaData += (isFirstLayer? "" : "||") + layerString + adLayerSeperator;
234 Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
235 boolean isFirstStyle = true;
236 for (int style: styleMap.keySet()){
237 char styleChar = getStyleAbbrev(style);
238 areaData += (isFirstStyle? "" : "|") + styleChar + ":";
239 Set<Distribution> distributionSet = styleMap.get(style);
240 boolean isFirstDistribution = true;
241 for (Distribution distribution: distributionSet){
242 String areaAbbrev = getAreaAbbrev(distribution);
243 areaData += (isFirstDistribution ? "" : ",") + areaAbbrev;
244 isFirstDistribution = false;
245 }
246 isFirstStyle = false;
247 }
248 isFirstLayer = false;
249 }
250
251 String workaround = "tdwg2/z:Subarctic%20America,Australia||";
252 areaData = "ad=" + workaround + areaData.substring(0); //remove first |
253
254 //result
255 result = webServiceUrl + "?";
256 result += CdmUtils.concat("&", new String[] {layer, areaData, areaStyle, bbox, widthStr, heightStr});
257 return result;
258 }
259
260 private static String getAreaAbbrev(Distribution distribution){
261 NamedArea area = distribution.getArea();
262 Representation representation = area.getRepresentation(Language.DEFAULT());
263 String areaAbbrev = representation.getAbbreviatedLabel();
264 return areaAbbrev;
265 }
266
267 private static void addDistributionToMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap, List<PresenceAbsenceTermBase<?>> statusList){
268 int style = statusList.indexOf(distribution.getStatus());
269 Set<Distribution> distributionSet = styleMap.get(style);
270 if (distributionSet == null){
271 distributionSet = new HashSet<Distribution>();
272 styleMap.put(style, distributionSet);
273 }
274 distributionSet.add(distribution);
275 }
276
277
278 /**
279 * transform an integer (style counter) into a valid character representing a style.
280 * 0-25 => a-z<br>
281 * 26-51 => A-Z<br>
282 * i not in {0,...,51} is undefined
283 * @param i
284 * @return
285 */
286 private static char getStyleAbbrev(int i){
287 i++;
288 int ascii = 96 + i;
289 if (i >26){
290 ascii = 64 + i;
291 }
292 return (char)ascii;
293 }
294
295 private static String getGeoServiceLayer(NamedAreaLevel level){
296 //TODO integrate into CDM
297 if (level.equals(NamedAreaLevel.TDWG_LEVEL1())){
298 return "tdwg1";
299 }else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())){
300 return "tdwg2";
301 }if (level.equals(NamedAreaLevel.TDWG_LEVEL3())){
302 return "tdwg3";
303 }if (level.equals(NamedAreaLevel.TDWG_LEVEL4())){
304 return "tdwg4";
305 }
306 return "unknown";
307 }
308
309 }