merging delete functionality into trunk
[cdmlib.git] / cdmlib-ext / src / main / java / eu / etaxonomy / cdm / ext / geo / EditGeoServiceUtilities.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.ext.geo;
12
13 import java.awt.Color;
14 import java.io.UnsupportedEncodingException;
15 import java.net.URLEncoder;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.UUID;
24
25 import javax.persistence.Transient;
26
27 import org.apache.commons.lang.StringUtils;
28 import org.apache.log4j.Logger;
29
30 import eu.etaxonomy.cdm.api.utility.DescriptionUtility;
31 import eu.etaxonomy.cdm.common.CdmUtils;
32 import eu.etaxonomy.cdm.model.common.Language;
33 import eu.etaxonomy.cdm.model.common.Representation;
34 import eu.etaxonomy.cdm.model.common.TermVocabulary;
35 import eu.etaxonomy.cdm.model.description.Distribution;
36 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
37 import eu.etaxonomy.cdm.model.description.PresenceTerm;
38 import eu.etaxonomy.cdm.model.location.NamedArea;
39 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
40 import eu.etaxonomy.cdm.model.location.Point;
41 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
42 import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
43
44 /**
45 * Class implementing the business logic for creating the map service string for
46 * a given set of distributions. See {@link EditGeoService} as API for the given functionality.
47 *
48 * @see EditGeoService
49 *
50 * @author a.mueller
51 * @created 17.11.2008
52 */
53 public class EditGeoServiceUtilities {
54 private static final Logger logger = Logger.getLogger(EditGeoServiceUtilities.class);
55
56 private static PresenceAbsenceTermBase<?> defaultStatus = PresenceTerm.PRESENT();
57
58 private static IDefinedTermDao termDao;
59
60
61
62 /**
63 * @param termDao
64 */
65 public static void setTermDao(IDefinedTermDao termDao) {
66 EditGeoServiceUtilities.termDao= termDao;
67 }
68
69 private static HashMap<SpecimenOrObservationType, Color> defaultSpecimenOrObservationTypeColors = null;
70
71 private static HashMap<SpecimenOrObservationType, Color> getDefaultSpecimenOrObservationTypeColors() {
72 if(defaultSpecimenOrObservationTypeColors == null){
73 defaultSpecimenOrObservationTypeColors = new HashMap<SpecimenOrObservationType, Color>();
74 defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.FieldUnit, Color.ORANGE);
75 defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.DerivedUnit, Color.RED);
76 defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.LivingSpecimen, Color.GREEN);
77 defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.Observation, Color.ORANGE);
78 defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.PreservedSpecimen, Color.GRAY);
79 defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.Media, Color.BLUE);
80 }
81 return defaultSpecimenOrObservationTypeColors;
82 }
83
84
85 private static HashMap<PresenceAbsenceTermBase<?>, Color> defaultPresenceAbsenceTermBaseColors = null;
86
87 private static HashMap<PresenceAbsenceTermBase<?>, Color> getDefaultPresenceAbsenceTermBaseColors() {
88 if(defaultPresenceAbsenceTermBaseColors == null){
89 defaultPresenceAbsenceTermBaseColors = new HashMap<PresenceAbsenceTermBase<?>, Color>();
90 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.PRESENT(), Color.decode("0x4daf4a"));
91 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.NATIVE(), Color.decode("0x4daf4a"));
92 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.NATIVE_DOUBTFULLY_NATIVE(), Color.decode("0x377eb8"));
93 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.CULTIVATED(), Color.decode("0x984ea3"));
94 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED(), Color.decode("0xff7f00"));
95 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_ADVENTITIOUS(), Color.decode("0xffff33"));
96 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_CULTIVATED(), Color.decode("0xa65628"));
97 defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_NATURALIZED(), Color.decode("0xf781bf"));
98
99 /*
100 * and now something very hacky ...
101 * ONLY-A-TEST is set by the Test class EditGeoServiceTest
102 *
103 * TODO remove according line from
104 * EditGeoServiceTest.setUp() when the hardcoded colors for flora of
105 * cyprus are no further needed !!
106 */
107 String onlyTest = System.getProperty("ONLY-A-TEST"); //
108 if(onlyTest != null && onlyTest.equals("TRUE")){
109 return defaultPresenceAbsenceTermBaseColors;
110 }
111 //special colors for flora of cyprus !!! see HACK above !!!
112 UUID indigenousUuid = UUID.fromString("b325859b-504b-45e0-9ef0-d5c1602fcc0f");
113 UUID indigenousQUuid = UUID.fromString("17bc601f-53eb-4997-a4bc-c03ce5bfd1d3");
114
115 UUID cultivatedQUuid = UUID.fromString("4f31bfc8-3058-4d83-aea5-3a1fe9773f9f");
116
117 UUID casualUuid = UUID.fromString("5e81353c-38a3-4ca6-b979-0d9abc93b877");
118 UUID casualQUuid = UUID.fromString("73f75493-1185-4a3e-af1e-9a1f2e8dadb7");
119
120 UUID naturalizedNonInvasiveUuid = UUID.fromString("1b025e8b-901a-42e8-9739-119b410c6f03");
121 UUID naturalizedNonInvasiveQUuid = UUID.fromString("11f56e2f-c16c-4b3d-a870-bb5d3b20e624");
122
123 UUID naturalizedInvasiveUuid = UUID.fromString("faf2d271-868a-4bf7-b0b8-a1c5ab309de2");
124 UUID naturalizedInvasiveQUuid = UUID.fromString("ac429d5f-e8ad-49ae-a41c-e4779b58b96a");
125
126 UUID questionablelUuid = UUID.fromString("4b48f675-a6cf-49f3-a5ba-77e2c2979eb3");
127 UUID questionableQUuid = UUID.fromString("914e7393-1314-4632-bc45-5eff3dc1e424");
128
129 UUID reportedInErrorUuid = UUID.fromString("38604788-cf05-4607-b155-86db456f7680");
130
131 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(indigenousUuid), Color.decode("0x339966"));
132 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(indigenousQUuid), Color.decode("0x339966"));
133
134 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(cultivatedQUuid), Color.decode("0xbdb76b"));
135
136 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(casualUuid), Color.decode("0xffff00"));
137 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(casualQUuid), Color.decode("0xffff00"));
138
139 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedNonInvasiveUuid), Color.decode("0xff9900"));
140 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedNonInvasiveQUuid), Color.decode("0xff9900"));
141
142 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedInvasiveUuid), Color.decode("0xff0000"));
143 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedInvasiveQUuid), Color.decode("0xff0000"));
144
145 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(questionablelUuid), Color.decode("0x00ccff"));
146 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(questionableQUuid), Color.decode("0x00ccff"));
147
148 defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(reportedInErrorUuid), Color.decode("0xcccccc"));
149
150 }
151 return defaultPresenceAbsenceTermBaseColors;
152 }
153
154
155
156 private static final String SUBENTRY_DELIMITER = ",";
157 private static final String ENTRY_DELIMITER = ";";
158 static final String ID_FROM_VALUES_SEPARATOR = ":";
159 static final String VALUE_LIST_ENTRY_SEPARATOR = "|";
160 static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR = "||";
161
162
163
164 //preliminary implementation for TDWG areas
165 /**
166 * Returns the parameter String for the EDIT geo webservice to create a
167 * dsitribution map.
168 *
169 * @param distributions
170 * A set of distributions that should be shown on the map
171 * @param presenceAbsenceTermColors
172 * A map that defines the colors of PresenceAbsenceTerms. The
173 * PresenceAbsenceTerms are defined by their uuid. If a
174 * PresenceAbsenceTerm is not included in this map, it's default
175 * color is taken instead. If the map == null all terms are
176 * colored by their default color.
177 * @param width
178 * The maps width
179 * @param height
180 * The maps height
181 * @param bbox
182 * The maps bounding box (e.g. "-180,-90,180,90" for the whole
183 * world)
184 * @param projectToLayer
185 * name of a layer which is representing a specific
186 * {@link NamedAreaLevel} Supply this parameter if you to project
187 * all other distribution area levels to this layer.
188 * @param layer
189 * The layer that is responsible for background borders and
190 * colors. Use the name for the layer. If null 'earth' is taken
191 * as default.
192 * @return the parameter string or an empty string if the
193 * <code>distributions</code> set was null or empty.
194 */
195 @Transient
196 public static String getDistributionServiceRequestParameterString(
197 Set<Distribution> distributions,
198 IGeoServiceAreaMapping mapping,
199 Map<PresenceAbsenceTermBase<?>,Color> presenceAbsenceTermColors,
200 int width,
201 int height,
202 String bbox,
203 String baseLayerName,
204 String projectToLayer,
205 List<Language> languages){
206
207
208 /**
209 * generateMultipleAreaDataParameters switches between the two possible styles:
210 * 1. ad=layername1:area-data||layername2:area-data
211 * 2. ad=layername1:area-data&ad=layername2:area-data
212 */
213 boolean generateMultipleAreaDataParameters = false;
214
215 List<String> perLayerAreaData = new ArrayList<String>();
216 List<String> areaStyles = new ArrayList<String>();
217 List<String> legendLabels = new ArrayList<String>();
218
219
220 String borderWidth = "0.1";
221 String borderColorRgb = "";
222 String borderDashingPattern = "";
223
224
225 //handle empty set
226 if(distributions == null || distributions.size() == 0){
227 return "";
228 }
229
230 Collection<Distribution> filteredDistributions = DescriptionUtility.filterDistributions(distributions);
231
232 Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();
233 List<PresenceAbsenceTermBase<?>> statusList = new ArrayList<PresenceAbsenceTermBase<?>>();
234
235 groupStylesAndLayers(filteredDistributions, layerMap, statusList, mapping);
236
237 presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);
238
239 Map<String, String> parameters = new HashMap<String, String>();
240
241 //bbox
242 if (bbox != null){
243 parameters.put("bbox", bbox);
244 }
245 // map size
246 String ms = compileMapSizeParameterValue(width, height);
247 if(ms != null){
248 parameters.put("ms", ms);
249 }
250 //layer
251 if (StringUtils.isBlank(baseLayerName)){
252 baseLayerName = "earth";
253 }
254 parameters.put("l", baseLayerName);
255
256 //style
257 int i = 0;
258 for (PresenceAbsenceTermBase<?> status: statusList){
259
260 char styleId = getStyleAbbrev(i);
261
262 //getting the area title
263 if (languages == null){
264 languages = new ArrayList<Language>();
265 }
266 if (languages.size() == 0){
267 languages.add(Language.DEFAULT());
268 }
269 Representation representation = status.getPreferredRepresentation(languages);
270 String statusLabel = representation.getLabel();
271 //statusLabel.replace('introduced: ', '');
272 statusLabel = statusLabel.replace("introduced: ", "introduced, ");
273 statusLabel = statusLabel.replace("native: ", "native, ");
274
275 //getting the area color
276 Color statusColor = presenceAbsenceTermColors.get(status);
277 String fillColorRgb;
278 if (statusColor != null){
279 fillColorRgb = Integer.toHexString(statusColor.getRGB()).substring(2);
280 }else{
281 if(status != null){
282 fillColorRgb = status.getDefaultColor(); //TODO
283 } else {
284 fillColorRgb = defaultStatus.getDefaultColor();
285 }
286 }
287 String styleValues = StringUtils.join(new String[]{fillColorRgb, borderColorRgb, borderWidth, borderDashingPattern}, ',');
288
289 areaStyles.add(styleId + ID_FROM_VALUES_SEPARATOR + styleValues);
290 legendLabels.add(styleId + ID_FROM_VALUES_SEPARATOR + encode(statusLabel));
291 i++;
292 }
293
294 if(areaStyles.size() > 0){
295 parameters.put("as", StringUtils.join(areaStyles.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
296 }
297 if(legendLabels.size() > 0){
298 parameters.put("title", StringUtils.join(legendLabels.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
299 }
300
301 // area data
302 List<String> stylesPerLayer;
303 List<String> areasPerStyle;
304 for (String layerString : layerMap.keySet()){
305 // each layer
306 stylesPerLayer = new ArrayList<String>();
307 Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
308 for (int style: styleMap.keySet()){
309 // stylesPerLayer
310 char styleChar = getStyleAbbrev(style);
311 Set<Distribution> distributionSet = styleMap.get(style);
312 areasPerStyle = new ArrayList<String>();
313 for (Distribution distribution: distributionSet){
314 // areasPerStyle
315 areasPerStyle.add(encode(getAreaAbbrev(distribution, mapping)));
316 }
317 stylesPerLayer.add(styleChar + ID_FROM_VALUES_SEPARATOR + StringUtils.join(areasPerStyle.iterator(), SUBENTRY_DELIMITER));
318 }
319 perLayerAreaData.add(encode(layerString) + ID_FROM_VALUES_SEPARATOR + StringUtils.join(stylesPerLayer.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
320 }
321
322
323 if(generateMultipleAreaDataParameters){
324 // not generically possible since parameters can not contain duplicate keys with value "ad"
325 } else {
326 parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));
327 }
328
329 String queryString = makeQueryString(parameters);
330 logger.debug("getDistributionServiceRequestParameterString(): " + queryString);
331
332 return queryString;
333 }
334
335
336 /**
337 * Fills the layerMap and the statusList
338 *
339 * @param distributions
340 * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
341 * @param statusList
342 */
343 private static void groupStylesAndLayers(Collection<Distribution> distributions,
344 Map<String, Map<Integer,Set<Distribution>>> layerMap,
345 List<PresenceAbsenceTermBase<?>> statusList,
346 IGeoServiceAreaMapping mapping) {
347
348
349 //iterate through distributions and group styles and layers
350 //and collect necessary information
351 for (Distribution distribution : distributions){
352 //collect status
353 PresenceAbsenceTermBase<?> status = distribution.getStatus();
354 if(status == null){
355 status = defaultStatus;
356 }
357 if (! statusList.contains(status)){
358 statusList.add(status);
359 }
360 //group areas by layers and styles
361 NamedArea area = distribution.getArea();
362
363 addAreaToLayerMap(layerMap, statusList, distribution, area, mapping);
364 }
365 }
366
367 /**
368 * A layer map holds the following information:
369 *
370 * <ul>
371 * <li><b>String</b>: the WMSLayerName which matches the level of the
372 * contained distributions areas</li>
373 * <li><b>StyleMap</b>:</li>
374 * <ul>
375 * <li><b>Integer</b>: the index of the status in the
376 * <code>statusList</code></li>
377 * <li><b>Set{@code<Distribution>}</b>: the set of distributions having the
378 * same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>
379 * </ul>
380 * </ul>
381 *
382 * @param layerMap
383 * @param statusList
384 * @param distribution
385 * @param area
386 */
387 private static void addAreaToLayerMap(Map<String, Map<Integer,
388 Set<Distribution>>> layerMap,
389 List<PresenceAbsenceTermBase<?>> statusList,
390 Distribution distribution,
391 NamedArea area,
392 IGeoServiceAreaMapping mapping) {
393
394 if (area != null){
395 String geoLayerString = getWMSLayerName(area, mapping);
396
397 if(geoLayerString == null){
398
399 // if no layer is mapped this area descend into sub areas in order to project
400 // the distribution to those
401 for(NamedArea subArea : area.getIncludes()){
402 addAreaToLayerMap(layerMap, statusList, distribution, subArea, mapping);
403 }
404
405 } else {
406
407 Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerString);
408 if (styleMap == null) {
409 styleMap = new HashMap<Integer, Set<Distribution>>();
410 layerMap.put(geoLayerString, styleMap);
411 }
412 addDistributionToStyleMap(distribution, styleMap, statusList);
413
414 }
415 }
416 }
417
418
419 private static String compileMapSizeParameterValue(int width, int height) {
420
421 String widthStr = "";
422 String heightStr = "";
423
424 if (width > 0) {
425 widthStr = "" + width;
426 }
427 if (height > 0) {
428 heightStr = SUBENTRY_DELIMITER + height;
429 }
430 String ms = widthStr + heightStr;
431 if(ms.length() == 0){
432 ms = null;
433 }
434 return ms;
435 }
436
437 /**
438 * URI encode the given String
439 * @param string
440 * @return
441 */
442 private static String encode(String string) {
443 String encoded = string;
444 try {
445 encoded = URLEncoder.encode(string, "UTF-8");
446 } catch (UnsupportedEncodingException e) {
447 logger.error(e);
448 }
449 return encoded;
450 }
451
452 /**
453 * combine parameter into a URI query string fragment. The values will be
454 * escaped correctly.
455 *
456 * @param parameters
457 * @return a URI query string fragment
458 */
459 private static String makeQueryString(Map<String, String> parameters){
460 StringBuilder queryString = new StringBuilder();
461 for (String key : parameters.keySet()) {
462 if(queryString.length() > 0){
463 queryString.append('&');
464 }
465 if(key.equals("od") || key.equals("os") || key.equals("ms") || key.equals("ad") || key.equals("as") || key.equals("title") || key.equals("bbox")){
466 queryString.append(key).append('=').append(parameters.get(key));
467 } else {
468 queryString.append(key).append('=').append(encode(parameters.get(key)));
469 }
470 }
471 return queryString.toString();
472 }
473
474 private static String getAreaAbbrev(Distribution distribution, IGeoServiceAreaMapping mapping){
475 NamedArea area = distribution.getArea();
476 TermVocabulary<NamedArea> voc = area.getVocabulary();
477 String result = null;
478 if (voc != null && voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary) || voc.getUuid().equals(uuidCyprusDivisionsVocabulary) ){
479 Representation representation = area.getRepresentation(Language.DEFAULT());
480 result = representation.getAbbreviatedLabel();
481 if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())){
482 result = result.replace("-", "");
483 }
484
485 }else{
486 GeoServiceArea areas =mapping.valueOf(area);
487 if ((areas != null) && areas.size()>0){
488 //FIXME multiple layers
489 List<String> values= areas.getAreasMap().values().iterator().next().values().iterator().next();
490 for (String value : values){
491 result = CdmUtils.concat(SUBENTRY_DELIMITER, result, value);
492 }
493 }
494
495 }
496 return CdmUtils.Nz(result, "-");
497
498 }
499
500
501
502 //Preliminary as long as user defined areas are not fully implemented
503 public static final UUID uuidCyprusDivisionsVocabulary = UUID.fromString("2119f610-1f93-4d87-af28-40aeefaca100");
504
505 private static List<String> projectToWMSSubLayer(NamedArea area){
506
507 List<String> layerNames = new ArrayList<String>();
508 String matchedLayerName = null;
509 TermVocabulary<NamedArea> voc = area.getVocabulary();
510 //TDWG areas
511 if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){
512 NamedAreaLevel level = area.getLevel();
513 if (level != null) {
514 //TODO integrate into CDM
515 if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
516 matchedLayerName = "tdwg1" ;
517 } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
518 matchedLayerName = "tdwg2";
519 }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
520 matchedLayerName = "tdwg3";
521 }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
522 matchedLayerName = "tdwg4";
523 }
524 }
525 //unrecognized tdwg area
526
527 }
528 //TODO hardcoded for cyprus (as long as user defined areas are not fully implemented). Remove afterwards.
529 if (voc.getUuid().equals(uuidCyprusDivisionsVocabulary)){
530 matchedLayerName = "cyprusdivs:bdcode";
531 }
532
533 // check if the matched layer equals the layer to project to
534 // if not: recurse into the sub-level in order to find the specified one.
535 String[] matchedLayerNameTokens = StringUtils.split(matchedLayerName, ':');
536 // if(matchedLayerNameTokens.length > 0 && matchedLayerNameTokens[0] != projectToLayer){
537 // for (NamedArea subArea : area.getIncludes()){
538 //
539 // }
540 //
541 // add all sub areas
542 // }
543
544 return null;
545 }
546
547 private static String getWMSLayerName(NamedArea area, IGeoServiceAreaMapping mapping){
548 TermVocabulary<NamedArea> voc = area.getVocabulary();
549 //TDWG areas
550 if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){
551 NamedAreaLevel level = area.getLevel();
552 if (level != null) {
553 //TODO integrate into CDM
554 if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
555 return "tdwg1";
556 } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
557 return "tdwg2";
558 }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
559 return "tdwg3";
560 }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
561 return "tdwg4";
562 }
563 }
564 //unrecognized tdwg area
565 return null;
566
567 }
568 //hardcoded for cyprus (as long as user defined areas are not fully implemented). Remove afterwards.
569 if (voc.getUuid().equals(uuidCyprusDivisionsVocabulary)){
570 return "cyprusdivs:bdcode";
571 }
572
573 GeoServiceArea areas = mapping.valueOf(area);
574 if (areas != null && areas.getAreasMap().size() > 0){
575 //FIXME multiple layers
576 String layer = areas.getAreasMap().keySet().iterator().next();
577 Map<String, List<String>> fields = areas.getAreasMap().get(layer);
578 String field = fields.keySet().iterator().next();
579 return layer + ":" + field;
580 }
581
582 return null;
583 }
584
585
586 private static void addDistributionToStyleMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,
587 List<PresenceAbsenceTermBase<?>> statusList) {
588 PresenceAbsenceTermBase<?> status = distribution.getStatus();
589 if (status == null) {
590 status = defaultStatus;
591 }
592 int style = statusList.indexOf(status);
593 Set<Distribution> distributionSet = styleMap.get(style);
594 if (distributionSet == null) {
595 distributionSet = new HashSet<Distribution>();
596 styleMap.put(style, distributionSet);
597 }
598 distributionSet.add(distribution);
599 }
600
601 /**
602 * @param fieldUnitPoints
603 * @param derivedUnitPoints
604 * @param specimenOrObservationTypeColors
605 * @param doReturnImage TODO
606 * @param width
607 * @param height
608 * @param bbox
609 * @param backLayer
610 * @return
611 * e.g.:
612 * l=v%3Aatbi%2Ce_w_0
613 * &legend=0
614 * &image=false
615 * &recalculate=false
616 * &ms=400%2C350
617
618 * &od=1%3A44.29481%2C6.82161|44.29252%2C6.822873|44.29247%2C6.82346|44.29279%2C6.823678|44.29269%2C6.82394|44.28482%2C6.887252|44.11469%2C7.287144|44.11468%2C7.289168
619 * &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
620 */
621 public static String getOccurrenceServiceRequestParameterString(
622 List<Point> fieldUnitPoints,
623 List<Point> derivedUnitPoints,
624 Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors,
625 Boolean doReturnImage, Integer width, Integer height, String bbox, String backLayer) {
626
627 specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);
628
629 Map<String, String> parameters = new HashMap<String, String>();
630 parameters.put("legend", "0");
631 parameters.put("image", doReturnImage != null && doReturnImage ? "true" : "false");
632 parameters.put("recalculate", "false"); // TODO add parameter to method
633 if(bbox != null){
634 parameters.put("bbox", bbox);
635 }
636 if(width != null || height != null){
637 parameters.put("ms", compileMapSizeParameterValue(width, height));
638 }
639
640 Map<String, String> styleAndData = new HashMap<String, String>();
641
642 addToStyleAndData(fieldUnitPoints, SpecimenOrObservationType.FieldUnit, specimenOrObservationTypeColors, styleAndData);
643 addToStyleAndData(derivedUnitPoints, SpecimenOrObservationType.DerivedUnit, specimenOrObservationTypeColors, styleAndData);
644
645 parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));
646 parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));
647
648 String queryString = makeQueryString(parameters);
649
650 logger.info(queryString);
651
652 return queryString;
653 }
654
655 /**
656 * @param <T>
657 * @param <S>
658 * @param defaultMap
659 * @param overrideMap
660 * @return
661 */
662 private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {
663 Map<T, S> tmpMap = new HashMap<T, S>();
664 tmpMap.putAll(defaultMap);
665 if(overrideMap != null){
666 tmpMap.putAll(overrideMap);
667 }
668 return tmpMap;
669 }
670
671 private static void addToStyleAndData(
672 List<Point> points,
673 SpecimenOrObservationType specimenOrObservationType,
674 Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {
675
676 //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>
677
678 if(points != null && points.size()>0){
679 String style = "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";
680 StringBuilder data = new StringBuilder();
681 for(Point point : points){
682 if(data.length() > 0){
683 data.append('|');
684 }
685 data.append(point.getLatitude() + "," + point.getLongitude());
686 }
687 int index = styleAndData.size() + 1;
688 styleAndData.put(index + ":" +style, index + ":" +data.toString());
689 }
690 }
691
692
693 /**
694 * transform an integer (style counter) into a valid character representing a style.
695 * 0-25 => a-z<br>
696 * 26-51 => A-Z<br>
697 * i not in {0,...,51} is undefined
698 * @param i
699 * @return
700 */
701 private static char getStyleAbbrev(int i){
702 i++;
703 int ascii = 96 + i;
704 if (i >26){
705 ascii = 64 + i;
706 }
707 return (char)ascii;
708 }
709
710 }