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