changed default behaviour of getEditGeoServiceUrlParameterString()
[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.awt.Color;
13 import java.awt.color.ColorSpace;
14 import java.text.DecimalFormat;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.UUID;
22
23 import eu.etaxonomy.cdm.common.CdmUtils;
24 import eu.etaxonomy.cdm.model.common.Language;
25 import eu.etaxonomy.cdm.model.common.Representation;
26 import eu.etaxonomy.cdm.model.location.NamedArea;
27 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
28 import eu.etaxonomy.cdm.model.taxon.Taxon;
29
30 import org.apache.log4j.Logger;
31 import org.hibernate.annotations.Cascade;
32 import org.hibernate.annotations.CascadeType;
33 import javax.persistence.*;
34 import javax.xml.bind.annotation.XmlAccessType;
35 import javax.xml.bind.annotation.XmlAccessorType;
36 import javax.xml.bind.annotation.XmlElement;
37 import javax.xml.bind.annotation.XmlIDREF;
38 import javax.xml.bind.annotation.XmlRootElement;
39 import javax.xml.bind.annotation.XmlSchemaType;
40 import javax.xml.bind.annotation.XmlType;
41
42 /**
43 * This class represents elementary distribution data for a {@link Taxon taxon}.
44 * Only {@link TaxonDescription taxon descriptions} may contain distributions.
45 * A distribution instance consist of a {@link NamedArea named area} and of a {@link PresenceAbsenceTermBase status}
46 * describing the absence or the presence of a taxon (like "extinct"
47 * or "introduced") in this named area.
48 * <P>
49 * This class corresponds partially to: <ul>
50 * <li> CodedDescriptionType according to the the SDD schema
51 * <li> Distribution according to the TDWG ontology
52 * </ul>
53 *
54 * @author m.doering
55 * @version 1.0
56 * @created 08-Nov-2007 13:06:21
57 */
58 @XmlAccessorType(XmlAccessType.FIELD)
59 @XmlType(name = "Distribution", propOrder = {
60 "area",
61 "status"
62 })
63 @XmlRootElement(name = "Distribution")
64 @Entity
65 public class Distribution extends DescriptionElementBase {
66 static Logger logger = Logger.getLogger(Distribution.class);
67
68 @XmlElement(name = "NamedArea")
69 @XmlIDREF
70 @XmlSchemaType(name = "IDREF")
71 private NamedArea area;
72
73 @XmlElement(name = "PresenceAbsenceStatus")
74 private PresenceAbsenceTermBase<?> status;
75
76
77 /**
78 * Class constructor: creates a new empty distribution instance.
79 * The corresponding {@link Feature feature} is set to {@link Feature#DISTRIBUTION() DISTRIBUTION}.
80 */
81 protected Distribution(){
82 super(Feature.DISTRIBUTION());
83 }
84
85
86 /**
87 * Creates an empty distribution instance. The corresponding {@link Feature feature}
88 * is set to {@link Feature#DISTRIBUTION() DISTRIBUTION}.
89 *
90 * @see #NewInstance(NamedArea, PresenceAbsenceTermBase)
91 */
92 public static Distribution NewInstance(){
93 Distribution result = new Distribution();
94 return result;
95 }
96
97 /**
98 * Creates a distribution instance with the given {@link NamedArea named area} and {@link PresenceAbsenceTermBase status}.
99 * The corresponding {@link Feature feature} is set to {@link Feature#DISTRIBUTION() DISTRIBUTION}.
100 *
101 * @param area the named area for the new distribution
102 * @param status the presence or absence term for the new distribution
103 * @see #NewInstance()
104 */
105 public static Distribution NewInstance(NamedArea area, PresenceAbsenceTermBase<?> status){
106 Distribution result = new Distribution();
107 result.setArea(area);
108 result.setStatus(status);
109 return result;
110 }
111
112
113
114
115 /**
116 * Deprecated because {@link Feature feature} should always be {@link Feature#DISTRIBUTION() DISTRIBUTION}
117 * for all distribution instances.
118 */
119 /* (non-Javadoc)
120 * @see eu.etaxonomy.cdm.model.description.DescriptionElementBase#setFeature(eu.etaxonomy.cdm.model.description.Feature)
121 */
122 @Override
123 @Deprecated
124 public void setFeature(Feature feature) {
125 super.setFeature(feature);
126 }
127
128 /**
129 * Returns the {@link NamedArea named area} <i>this</i> distribution applies to.
130 */
131 @ManyToOne
132 @Cascade({CascadeType.SAVE_UPDATE})
133 public NamedArea getArea(){
134 return this.area;
135 }
136 /**
137 * @see #getArea()
138 */
139 public void setArea(NamedArea area){
140 this.area = area;
141 }
142
143 /**
144 * Returns the {@link PresenceAbsenceTermBase presence or absence term} for <i>this</i> distribution.
145 */
146 @ManyToOne
147 public PresenceAbsenceTermBase<?> getStatus(){
148 return this.status;
149 }
150 /**
151 * @see #getStatus()
152 */
153 public void setStatus(PresenceAbsenceTermBase<?> status){
154 this.status = status;
155 }
156
157 //preliminary implementation for TDWG areas
158 /**
159 * Returns the parameter String for the EDIT geo webservice to create a map.
160 * @param distributions A set of distributions that should be shown on the map
161 * @param presenceAbsenceTermColors A map that defines the colors of PresenceAbsenceTerms.
162 * The PresenceAbsenceTerms are defined by their uuid. If a PresenceAbsenceTerm is not included in
163 * this map, it's default color is taken instead. If the map == null all terms are colored by their default color.
164 * @param width The maps width
165 * @param height The maps height
166 * @param bbox The maps bounding box (e.g. "-180,-90,180,90" for the whole world)
167 * @param layer The layer that is responsible for background borders and colors. Use the name for the layer.
168 * If null 'earth' is taken as default.
169 * @return
170 */
171 //TODO move to an other place -> e.g. service layer
172 @Transient
173 public static String getEditGeoServiceUrlParameterString(Set<Distribution> distributions, Map<PresenceAbsenceTermBase<?>, Color> presenceAbsenceTermColors, int width, int height, String bbox, String backLayer){
174 String result = "";
175 String layer = "";
176 String areaData = "";
177 String areaStyle = "";
178 String widthStr = "";
179 String heightStr = "";
180 String adLayerSeparator = "/";
181 String msSeparator = "x";
182 int borderWidth = 1;
183
184
185 if (presenceAbsenceTermColors == null) {
186 presenceAbsenceTermColors = new HashMap<PresenceAbsenceTermBase<?>, Color>();
187 }
188
189 //List<String> layerStrings = new ArrayList<String>();
190 Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();
191 List<PresenceAbsenceTermBase<?>> statusList = new ArrayList<PresenceAbsenceTermBase<?>>();
192
193 //bbox, width, hight
194 if (bbox == null){
195 // FIXME uncommented this as it can not be desirable to have default values in this method
196 // we need a parameterString that consists of essential parameters only
197 // defaults should be implemented in the geoservice itself.
198 //bbox ="bbox=-180,-90,180,90"; //earth is default
199 }else{
200 bbox = "bbox=" + bbox;
201 }
202 if (width > 0){
203 widthStr = "" + width;
204 }
205 if (height > 0){
206 heightStr = msSeparator + height;
207 }
208 String ms = "ms=" + widthStr + heightStr;
209 if (ms.equals("ms=")){
210 ms = null;
211 }
212
213 //iterate through distributions and group styles and layers
214 //and collect necessary information
215 for (Distribution distribution:distributions){
216 //collect status
217 PresenceAbsenceTermBase<?> status = distribution.getStatus();
218 if (! statusList.contains(status)){
219 statusList.add(status);
220 }
221 //group by layers and styles
222 NamedArea area = distribution.getArea();
223 if (area != null){
224 NamedAreaLevel level = area.getLevel();
225 String geoLayerString = getGeoServiceLayer(level);
226 //Set<Distribution> layerDistributionSet;
227 //int index = layerStrings.indexOf(geoLayerString);
228 Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerString);
229 if (styleMap == null){
230 styleMap = new HashMap<Integer, Set<Distribution>>();
231 layerMap.put(geoLayerString, styleMap);
232 }
233 addDistributionToMap(distribution, styleMap, statusList);
234 }
235 }
236
237 //layer
238 if (backLayer == null || "".equals(layer.trim())){
239 backLayer = "earth";
240 }
241 layer = "l="+backLayer;
242 // for (String layerString : layerMap.keySet()){
243 // layer += "," + layerString;
244 // }
245 //layer = "l=" + layer.substring(1); //remove first |
246
247
248 //style
249 areaStyle = "";
250 Map<PresenceAbsenceTermBase<?>, Character> styleCharMap = new HashMap<PresenceAbsenceTermBase<?>, Character>();
251 int i = 0;
252 for (PresenceAbsenceTermBase<?> status: statusList){
253 char style = getStyleAbbrev(i);
254 Color statusColor = presenceAbsenceTermColors.get(status);
255 String rgb;
256 if (statusColor != null){
257 rgb = Integer.toHexString(statusColor.getRGB()).substring(2);
258 }else{
259 rgb = status.getDefaultColor(); //TODO
260 }
261 areaStyle += "|" + style + ":" + rgb;
262 if (borderWidth >0){
263 areaStyle += ",," + borderWidth;
264 }
265 styleCharMap.put(status, style);
266 i++;
267 }
268
269 areaStyle = "as=" + areaStyle.substring(1); //remove first |
270
271 //areaData
272 areaData = "";
273 boolean isFirstLayer = true;
274 for (String layerString : layerMap.keySet()){
275 //Set<Distribution> layerDistributions = layerData.get(layerIndex);
276 //int distributionListIndex = 1;
277 areaData += (isFirstLayer? "" : "||") + layerString + adLayerSeparator;
278 Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
279 boolean isFirstStyle = true;
280 for (int style: styleMap.keySet()){
281 char styleChar = getStyleAbbrev(style);
282 areaData += (isFirstStyle? "" : "|") + styleChar + ":";
283 Set<Distribution> distributionSet = styleMap.get(style);
284 boolean isFirstDistribution = true;
285 for (Distribution distribution: distributionSet){
286 String areaAbbrev = getAreaAbbrev(distribution);
287 areaData += (isFirstDistribution ? "" : ",") + areaAbbrev;
288 isFirstDistribution = false;
289 }
290 isFirstStyle = false;
291 }
292 isFirstLayer = false;
293 }
294
295 areaData = "ad=" + areaData.substring(0); //remove first |
296
297 //result
298 result += CdmUtils.concat("&", new String[] {layer, areaData, areaStyle, bbox, ms});
299 return result;
300 }
301
302 private static String getAreaAbbrev(Distribution distribution){
303 NamedArea area = distribution.getArea();
304 Representation representation = area.getRepresentation(Language.DEFAULT());
305 String areaAbbrev = representation.getAbbreviatedLabel();
306 if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())){
307 areaAbbrev = areaAbbrev.replace("-", "");
308 }
309 return areaAbbrev;
310 }
311
312 private static void addDistributionToMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap, List<PresenceAbsenceTermBase<?>> statusList){
313 int style = statusList.indexOf(distribution.getStatus());
314 Set<Distribution> distributionSet = styleMap.get(style);
315 if (distributionSet == null){
316 distributionSet = new HashSet<Distribution>();
317 styleMap.put(style, distributionSet);
318 }
319 distributionSet.add(distribution);
320 }
321
322
323 /**
324 * transform an integer (style counter) into a valid character representing a style.
325 * 0-25 => a-z<br>
326 * 26-51 => A-Z<br>
327 * i not in {0,...,51} is undefined
328 * @param i
329 * @return
330 */
331 private static char getStyleAbbrev(int i){
332 i++;
333 int ascii = 96 + i;
334 if (i >26){
335 ascii = 64 + i;
336 }
337 return (char)ascii;
338 }
339
340 private static String getGeoServiceLayer(NamedAreaLevel level){
341 //TODO integrate into CDM
342 if (level.equals(NamedAreaLevel.TDWG_LEVEL1())){
343 return "tdwg1";
344 }else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())){
345 return "tdwg2";
346 }if (level.equals(NamedAreaLevel.TDWG_LEVEL3())){
347 return "tdwg3";
348 }if (level.equals(NamedAreaLevel.TDWG_LEVEL4())){
349 return "tdwg4";
350 }
351 return "unknown";
352 }
353
354 }