Project

General

Profile

Download (27.3 KB) Statistics
| Branch: | Tag: | Revision:
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.HashMap;
18
import java.util.HashSet;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.UUID;
23

    
24
import javax.persistence.Transient;
25

    
26
import org.apache.commons.lang.StringUtils;
27
import org.apache.log4j.Logger;
28
import org.springframework.stereotype.Component;
29

    
30
import eu.etaxonomy.cdm.common.CdmUtils;
31
import eu.etaxonomy.cdm.model.common.Language;
32
import eu.etaxonomy.cdm.model.common.Representation;
33
import eu.etaxonomy.cdm.model.common.TermVocabulary;
34
import eu.etaxonomy.cdm.model.description.Distribution;
35
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
36
import eu.etaxonomy.cdm.model.description.PresenceTerm;
37
import eu.etaxonomy.cdm.model.location.NamedArea;
38
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
39
import eu.etaxonomy.cdm.model.location.Point;
40
import eu.etaxonomy.cdm.model.location.TdwgArea;
41
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
42
import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
43
import eu.etaxonomy.cdm.model.occurrence.LivingBeing;
44
import eu.etaxonomy.cdm.model.occurrence.Observation;
45
import eu.etaxonomy.cdm.model.occurrence.Specimen;
46
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
47
import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
48

    
49
/**
50
 * @author a.mueller
51
 * @created 17.11.2008
52
 * @version 1.0
53
 */
54
@Component
55
public class EditGeoServiceUtilities {
56
    private static final Logger logger = Logger.getLogger(EditGeoServiceUtilities.class);
57

    
58
    private static PresenceAbsenceTermBase<?> defaultStatus = PresenceTerm.PRESENT();
59

    
60
    private static IDefinedTermDao termDao;
61

    
62
    /**
63
     * @param termDao
64
     */
65
    public static void setTermDao(IDefinedTermDao termDao) {
66
        EditGeoServiceUtilities.termDao= termDao;
67
    }
68

    
69
    private static HashMap<Class<? extends SpecimenOrObservationBase>, Color> defaultSpecimenOrObservationTypeColors = null;
70

    
71
    private static HashMap<Class<? extends SpecimenOrObservationBase>, Color> getDefaultSpecimenOrObservationTypeColors() {
72
        if(defaultSpecimenOrObservationTypeColors == null){
73
            defaultSpecimenOrObservationTypeColors = new HashMap<Class<? extends SpecimenOrObservationBase>, Color>();
74
            defaultSpecimenOrObservationTypeColors.put(FieldObservation.class, Color.ORANGE);
75
            defaultSpecimenOrObservationTypeColors.put(DerivedUnit.class, Color.RED);
76
            defaultSpecimenOrObservationTypeColors.put(LivingBeing.class, Color.GREEN);
77
            defaultSpecimenOrObservationTypeColors.put(Observation.class, Color.ORANGE);
78
            defaultSpecimenOrObservationTypeColors.put(Specimen.class, Color.GRAY);
79
        }
80
        return defaultSpecimenOrObservationTypeColors;
81
    }
82

    
83

    
84
    private static HashMap<PresenceAbsenceTermBase<?>, Color> defaultPresenceAbsenceTermBaseColors = null;
85

    
86
    private static HashMap<PresenceAbsenceTermBase<?>, Color> getDefaultPresenceAbsenceTermBaseColors() {
87
        if(defaultPresenceAbsenceTermBaseColors == null){
88
            defaultPresenceAbsenceTermBaseColors = new HashMap<PresenceAbsenceTermBase<?>, Color>();
89
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.PRESENT(), Color.decode("0x4daf4a"));
90
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.NATIVE(), Color.decode("0x4daf4a"));
91
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.NATIVE_DOUBTFULLY_NATIVE(), Color.decode("0x377eb8"));
92
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.CULTIVATED(), Color.decode("0x984ea3"));
93
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED(), Color.decode("0xff7f00"));
94
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_ADVENTITIOUS(), Color.decode("0xffff33"));
95
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_CULTIVATED(), Color.decode("0xa65628"));
96
            defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_NATURALIZED(), Color.decode("0xf781bf"));
97

    
98
            /*
99
             * and now something very hacky ...
100
             * ONLY-A-TEST is set by the Test class EditGeoServiceTest
101
             *
102
             * TODO remove according line from
103
             * EditGeoServiceTest.setUp() when the hardcoded colors for flora of
104
             * cyprus are no further needed !!
105
             */
106
            String onlyTest = System.getProperty("ONLY-A-TEST"); //
107
            if(onlyTest != null && onlyTest.equals("TRUE")){
108
                return defaultPresenceAbsenceTermBaseColors;
109
            }
110
            //special colors for flora of cyprus !!! see HACK above !!!
111
            UUID indigenousUuid = UUID.fromString("b325859b-504b-45e0-9ef0-d5c1602fcc0f");
112
            UUID indigenousQUuid = UUID.fromString("17bc601f-53eb-4997-a4bc-c03ce5bfd1d3");
113

    
114
            UUID cultivatedQUuid = UUID.fromString("4f31bfc8-3058-4d83-aea5-3a1fe9773f9f");
115

    
116
            UUID casualUuid = UUID.fromString("5e81353c-38a3-4ca6-b979-0d9abc93b877");
117
            UUID casualQUuid = UUID.fromString("73f75493-1185-4a3e-af1e-9a1f2e8dadb7");
118

    
119
            UUID naturalizedNonInvasiveUuid = UUID.fromString("1b025e8b-901a-42e8-9739-119b410c6f03");
120
            UUID naturalizedNonInvasiveQUuid = UUID.fromString("11f56e2f-c16c-4b3d-a870-bb5d3b20e624");
121

    
122
            UUID naturalizedInvasiveUuid = UUID.fromString("faf2d271-868a-4bf7-b0b8-a1c5ab309de2");
123
            UUID naturalizedInvasiveQUuid = UUID.fromString("ac429d5f-e8ad-49ae-a41c-e4779b58b96a");
124

    
125
            UUID questionablelUuid = UUID.fromString("4b48f675-a6cf-49f3-a5ba-77e2c2979eb3");
126
            UUID questionableQUuid = UUID.fromString("914e7393-1314-4632-bc45-5eff3dc1e424");
127

    
128
            UUID reportedInErrorUuid = UUID.fromString("38604788-cf05-4607-b155-86db456f7680");
129

    
130
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(indigenousUuid), Color.decode("0x339966"));
131
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(indigenousQUuid), Color.decode("0x339966"));
132

    
133
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(cultivatedQUuid), Color.decode("0xbdb76b"));
134

    
135
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(casualUuid), Color.decode("0xffff00"));
136
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(casualQUuid), Color.decode("0xffff00"));
137

    
138
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedNonInvasiveUuid), Color.decode("0xff9900"));
139
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedNonInvasiveQUuid), Color.decode("0xff9900"));
140

    
141
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedInvasiveUuid), Color.decode("0xff0000"));
142
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedInvasiveQUuid), Color.decode("0xff0000"));
143

    
144
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(questionablelUuid), Color.decode("0x00ccff"));
145
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(questionableQUuid), Color.decode("0x00ccff"));
146

    
147
            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(reportedInErrorUuid), Color.decode("0xcccccc"));
148

    
149
        }
150
        return defaultPresenceAbsenceTermBaseColors;
151
    }
152

    
153

    
154

    
155
    private static final String SUBENTRY_DELIMITER = ",";
156
    private static final String ENTRY_DELIMITER = ";";
157
    static final String ID_FROM_VALUES_SEPARATOR = ":";
158
    static final String VALUE_LIST_ENTRY_SEPARATOR = "|";
159
    static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR = "||";
160

    
161

    
162

    
163
    //preliminary implementation for TDWG areas
164
    /**
165
     * Returns the parameter String for the EDIT geo webservice to create a
166
     * dsitribution map.
167
     *
168
     * @param distributions
169
     *            A set of distributions that should be shown on the map
170
     * @param presenceAbsenceTermColors
171
     *            A map that defines the colors of PresenceAbsenceTerms. The
172
     *            PresenceAbsenceTerms are defined by their uuid. If a
173
     *            PresenceAbsenceTerm is not included in this map, it's default
174
     *            color is taken instead. If the map == null all terms are
175
     *            colored by their default color.
176
     * @param width
177
     *            The maps width
178
     * @param height
179
     *            The maps height
180
     * @param bbox
181
     *            The maps bounding box (e.g. "-180,-90,180,90" for the whole
182
     *            world)
183
     * @param projectToLayer
184
     *            name of a layer which is representing a specific
185
     *            {@link NamedAreaLevel} Supply this parameter if you to project
186
     *            all other distribution area levels to this layer.
187
     * @param layer
188
     *            The layer that is responsible for background borders and
189
     *            colors. Use the name for the layer. If null 'earth' is taken
190
     *            as default.
191
     * @return the parameter string or an empty string if the
192
     *         <code>distributions</code> set was null or empty.
193
     */
194
    @Transient
195
    public static String getDistributionServiceRequestParameterString(
196
            Set<Distribution> distributions,
197
            Map<PresenceAbsenceTermBase<?>,Color> presenceAbsenceTermColors,
198
            int width,
199
            int height,
200
            String bbox,
201
            String baseLayerName,
202
            String projectToLayer,
203
            List<Language> languages){
204

    
205

    
206
        /**
207
         * generateMultipleAreaDataParameters switches between the two possible styles:
208
         * 1. ad=layername1:area-data||layername2:area-data
209
         * 2. ad=layername1:area-data&ad=layername2:area-data
210
         */
211
        boolean generateMultipleAreaDataParameters = false;
212

    
213
        List<String>  perLayerAreaData = new ArrayList<String>();
214
        List<String> areaStyles = new ArrayList<String>();
215
        List<String> legendLabels = new ArrayList<String>();
216

    
217

    
218
        String borderWidth = "0.1";
219
        String borderColorRgb = "";
220
        String borderDashingPattern = "";
221

    
222

    
223
        if(distributions == null || distributions.size() == 0){
224
            return "";
225
        }
226
        Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();
227
        List<PresenceAbsenceTermBase<?>> statusList = new ArrayList<PresenceAbsenceTermBase<?>>();
228
        groupStylesAndLayers(distributions, layerMap, statusList);
229

    
230
        presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);
231

    
232
        Map<String, String> parameters = new HashMap<String, String>();
233

    
234
        //bbox
235
        if (bbox != null){
236
            parameters.put("bbox", bbox);
237
        }
238
        // map size
239
        String ms = compileMapSizeParameterValue(width, height);
240
        if(ms != null){
241
            parameters.put("ms", ms);
242
        }
243
        //layer
244
        if (StringUtils.isBlank(baseLayerName)){
245
            baseLayerName = "earth";
246
        }
247
        parameters.put("l", baseLayerName);
248

    
249
        //style
250
        int i = 0;
251
        for (PresenceAbsenceTermBase<?> status: statusList){
252

    
253
            char styleId = getStyleAbbrev(i);
254

    
255
            //getting the area title
256
            if (languages == null){
257
                languages = new ArrayList<Language>();
258
            }
259
            if (languages.size() == 0){
260
                languages.add(Language.DEFAULT());
261
            }
262
            Representation representation = status.getPreferredRepresentation(languages);
263
            String statusLabel = representation.getLabel();
264
            //statusLabel.replace('introduced: ', '');
265
            statusLabel = statusLabel.replace("introduced: ", "introduced, ");
266
            statusLabel = statusLabel.replace("native: ", "native,  ");
267

    
268
            //getting the area color
269
            Color statusColor = presenceAbsenceTermColors.get(status);
270
            String fillColorRgb;
271
            if (statusColor != null){
272
                fillColorRgb = Integer.toHexString(statusColor.getRGB()).substring(2);
273
            }else{
274
                if(status != null){
275
                    fillColorRgb = status.getDefaultColor(); //TODO
276
                } else {
277
                    fillColorRgb = defaultStatus.getDefaultColor();
278
                }
279
            }
280
            String styleValues = StringUtils.join(new String[]{fillColorRgb, borderColorRgb, borderWidth, borderDashingPattern}, ',');
281

    
282
            areaStyles.add(styleId + ID_FROM_VALUES_SEPARATOR + styleValues);
283
            legendLabels.add(styleId + ID_FROM_VALUES_SEPARATOR + encode(statusLabel));
284
            i++;
285
        }
286

    
287
        if(areaStyles.size() > 0){
288
            parameters.put("as", StringUtils.join(areaStyles.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
289
        }
290
        if(legendLabels.size() > 0){
291
            parameters.put("title", StringUtils.join(legendLabels.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
292
        }
293

    
294
        // area data
295
        List<String> stylesPerLayer;
296
        List<String> areasPerStyle;
297
        for (String layerString : layerMap.keySet()){
298
            // each layer
299
            stylesPerLayer = new ArrayList<String>();
300
            Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
301
            for (int style: styleMap.keySet()){
302
                // stylesPerLayer
303
                char styleChar = getStyleAbbrev(style);
304
                Set<Distribution> distributionSet = styleMap.get(style);
305
                areasPerStyle = new ArrayList<String>();
306
                for (Distribution distribution: distributionSet){
307
                    // areasPerStyle
308
                    areasPerStyle.add(getAreaAbbrev(distribution));
309
                }
310
                stylesPerLayer.add(styleChar + ID_FROM_VALUES_SEPARATOR + StringUtils.join(areasPerStyle.iterator(), SUBENTRY_DELIMITER));
311
            }
312
            perLayerAreaData.add(encode(layerString) + ID_FROM_VALUES_SEPARATOR + StringUtils.join(stylesPerLayer.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
313
        }
314

    
315
        if(generateMultipleAreaDataParameters){
316
            // not generically possible since parameters can not contain duplicate keys with value "ad"
317
        } else {
318
            parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));
319
        }
320

    
321
        String queryString = makeQueryString(parameters);
322
        logger.debug("getDistributionServiceRequestParameterString(): " + queryString);
323

    
324
        return queryString;
325
    }
326

    
327
    private static void groupStylesAndLayers(Set<Distribution> distributions,
328
            Map<String, Map<Integer, Set<Distribution>>> layerMap,
329
            List<PresenceAbsenceTermBase<?>> statusList) {
330

    
331
        //iterate through distributions and group styles and layers
332
        //and collect necessary information
333
        for (Distribution distribution:distributions){
334
            //collect status
335
            PresenceAbsenceTermBase<?> status = distribution.getStatus();
336
            if(status == null){
337
                status = defaultStatus;
338
            }
339
            if (! statusList.contains(status)){
340
                statusList.add(status);
341
            }
342
            //group areas by layers and styles
343
            NamedArea area = distribution.getArea();
344

    
345
            addArea(layerMap, statusList, distribution, area);
346

    
347
        }
348
    }
349

    
350
    /**
351
     * @param layerMap
352
     * @param statusList
353
     * @param distribution
354
     * @param area
355
     */
356
    private static void addArea(Map<String, Map<Integer, Set<Distribution>>> layerMap, List<PresenceAbsenceTermBase<?>> statusList,
357
            Distribution distribution, NamedArea area) {
358

    
359
        if (area != null){
360
            String geoLayerString = getWMSLayerName(area);
361

    
362
            if(geoLayerString == null){
363

    
364
                // if no layer is mapped this area descend into sub areas in order to project
365
                // the distribution to those
366
                for(NamedArea subArea : area.getIncludes()){
367
                    addArea(layerMap, statusList, distribution, subArea);
368
                }
369

    
370
            } else {
371

    
372
                Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerString);
373
                if (styleMap == null) {
374
                    styleMap = new HashMap<Integer, Set<Distribution>>();
375
                    layerMap.put(geoLayerString, styleMap);
376
                }
377
                addDistributionToMap(distribution, styleMap, statusList);
378

    
379
            }
380
        }
381
    }
382

    
383

    
384
    private static String compileMapSizeParameterValue(int width, int height) {
385

    
386
        String widthStr = "";
387
        String heightStr = "";
388

    
389
        if (width > 0) {
390
            widthStr = "" + width;
391
        }
392
        if (height > 0) {
393
            heightStr = SUBENTRY_DELIMITER + height;
394
        }
395
        String ms = widthStr + heightStr;
396
        if(ms.length() == 0){
397
            ms = null;
398
        }
399
        return ms;
400
    }
401

    
402
    /**
403
     * URI encode the given String
404
     * @param string
405
     * @return
406
     */
407
    private static String encode(String string) {
408
        String encoded = string;
409
        try {
410
            encoded = URLEncoder.encode(string, "UTF-8");
411
        } catch (UnsupportedEncodingException e) {
412
            logger.error(e);
413
        }
414
        return encoded;
415
    }
416

    
417
    /**
418
     * combine parameter into a URI query string fragment. The values will be
419
     * escaped correctly.
420
     *
421
     * @param parameters
422
     * @return a URI query string fragment
423
     */
424
    private static String makeQueryString(Map<String, String> parameters){
425
        StringBuilder queryString = new StringBuilder();
426
        for (String key : parameters.keySet()) {
427
            if(queryString.length() > 0){
428
                queryString.append('&');
429
            }
430
            if(key.equals("od") || key.equals("os") || key.equals("ms") || key.equals("ad") || key.equals("as") || key.equals("title") || key.equals("bbox")){
431
                queryString.append(key).append('=').append(parameters.get(key));
432
            } else {
433
                queryString.append(key).append('=').append(encode(parameters.get(key)));
434
            }
435
        }
436
        return queryString.toString();
437
    }
438

    
439
    private static String getAreaAbbrev(Distribution distribution){
440
        NamedArea area = distribution.getArea();
441
        Representation representation = area.getRepresentation(Language.DEFAULT());
442
        String areaAbbrev = representation.getAbbreviatedLabel();
443
        if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())){
444
            areaAbbrev = areaAbbrev.replace("-", "");
445
        }
446
        return CdmUtils.Nz(areaAbbrev, "-");
447
    }
448

    
449

    
450
    //Preliminary as long as user defined areas are not fully implemented
451
    public static final UUID uuidCyprusDivisionsVocabulary = UUID.fromString("2119f610-1f93-4d87-af28-40aeefaca100");
452

    
453
    private static List<String> projectToWMSSubLayer(NamedArea area){
454

    
455
        List<String> layerNames = new ArrayList<String>();
456
        String matchedLayerName = null;
457
        TermVocabulary<NamedArea> voc = area.getVocabulary();
458
        //TDWG areas
459
        if (voc.getUuid().equals(TdwgArea.uuidTdwgAreaVocabulary)){
460
            NamedAreaLevel level = area.getLevel();
461
            if (level != null) {
462
                //TODO integrate into CDM
463
                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
464
                    matchedLayerName = "tdwg1" ;
465
                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
466
                    matchedLayerName = "tdwg2";
467
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
468
                    matchedLayerName = "tdwg3";
469
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
470
                    matchedLayerName = "tdwg4";
471
                }
472
            }
473
            //unrecognized tdwg area
474

    
475
        }
476
        //TODO hardcoded for cyprus (as long as user defined areas are not fully implemented). Remove afterwards.
477
        if (voc.getUuid().equals(uuidCyprusDivisionsVocabulary)){
478
            matchedLayerName = "cyprusdivs:bdcode";
479
        }
480

    
481
        // check if the matched layer equals the layer to project to
482
        // if not: recurse into the sub-level in order to find the specified one.
483
        String[] matchedLayerNameTokens = StringUtils.split(matchedLayerName, ':');
484
//		if(matchedLayerNameTokens.length > 0 &&  matchedLayerNameTokens[0] != projectToLayer){
485
//			for (NamedArea subArea : area.getIncludes()){
486
//
487
//			}
488
            //
489
            // add all sub areas
490
//		}
491

    
492
        return null;
493
    }
494

    
495
    private static String getWMSLayerName(NamedArea area){
496
        TermVocabulary<NamedArea> voc = area.getVocabulary();
497

    
498
        if(voc == null){
499
            logger.warn("NamedArea " + area + " not in Vocabulary" );
500
            return null;
501
        }
502
        //TDWG areas
503
        if (voc.getUuid().equals(TdwgArea.uuidTdwgAreaVocabulary)){
504
            NamedAreaLevel level = area.getLevel();
505
            if (level != null) {
506
                //TODO integrate into CDM
507
                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
508
                    return "tdwg1";
509
                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
510
                    return "tdwg2";
511
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
512
                    return "tdwg3";
513
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
514
                    return "tdwg4";
515
                }
516
            }
517
            //unrecognized tdwg area
518
            return null;
519

    
520
        }
521
        //hardcoded for cyprus (as long as user defined areas are not fully implemented). Remove afterwards.
522
        if (voc.getUuid().equals(uuidCyprusDivisionsVocabulary)){
523
            return "cyprusdivs:bdcode";
524
        }
525
        return null;
526
    }
527

    
528

    
529
    private static void addDistributionToMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,
530
            List<PresenceAbsenceTermBase<?>> statusList) {
531
        PresenceAbsenceTermBase<?> status = distribution.getStatus();
532
        if (status == null) {
533
            status = defaultStatus;
534
        }
535
        int style = statusList.indexOf(status);
536
        Set<Distribution> distributionSet = styleMap.get(style);
537
        if (distributionSet == null) {
538
            distributionSet = new HashSet<Distribution>();
539
            styleMap.put(style, distributionSet);
540
        }
541
        distributionSet.add(distribution);
542
    }
543

    
544
    /**
545
     * @param fieldObservationPoints
546
     * @param derivedUnitPoints
547
     * @param specimenOrObservationTypeColors
548
     * @param doReturnImage TODO
549
     * @param width
550
     * @param height
551
     * @param bbox
552
     * @param backLayer
553
     * @return
554
     * e.g.:
555
     * 	l=v%3Aatbi%2Ce_w_0
556
     *  &legend=0
557
     *  &image=false
558
     *  &recalculate=false
559
     *  &ms=400%2C350
560

    
561
     *  &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
562
     *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
563
     */
564
    public static String getOccurrenceServiceRequestParameterString(
565
            List<Point> fieldObservationPoints,
566
            List<Point> derivedUnitPoints,
567
            Map<Class<? extends SpecimenOrObservationBase>, Color> specimenOrObservationTypeColors,
568
            Boolean doReturnImage, Integer width, Integer height, String bbox, String backLayer) {
569

    
570
            specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);
571

    
572
            Map<String, String> parameters = new HashMap<String, String>();
573
            parameters.put("legend", "0");
574
            parameters.put("image", doReturnImage != null && doReturnImage ? "true" : "false");
575
            parameters.put("recalculate", "false"); // TODO add parameter to method
576
            if(bbox != null){
577
                parameters.put("bbox", bbox);
578
            }
579
            if(width != null || height != null){
580
                parameters.put("ms", compileMapSizeParameterValue(width, height));
581
            }
582

    
583
            Map<String, String> styleAndData = new HashMap<String, String>();
584

    
585
            addToStyleAndData(fieldObservationPoints, FieldObservation.class, specimenOrObservationTypeColors, styleAndData);
586
            addToStyleAndData(derivedUnitPoints, DerivedUnit.class, specimenOrObservationTypeColors, styleAndData);
587

    
588
            parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));
589
            parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));
590

    
591
            String queryString = makeQueryString(parameters);
592

    
593
            logger.info(queryString);
594

    
595
        return queryString;
596
    }
597

    
598
    /**
599
     * @param <T>
600
     * @param <S>
601
     * @param defaultMap
602
     * @param overrideMap
603
     * @return
604
     */
605
    private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {
606
        Map<T, S> tmpMap = new HashMap<T, S>();
607
        tmpMap.putAll(defaultMap);
608
        if(overrideMap != null){
609
            tmpMap.putAll(overrideMap);
610
        }
611
        return tmpMap;
612
    }
613

    
614
    private static void addToStyleAndData(
615
            List<Point> points,
616
            Class<? extends SpecimenOrObservationBase> specimenOrObservationType,
617
            Map<Class<? extends SpecimenOrObservationBase>, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {
618

    
619
        //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>
620

    
621
        if(points != null && points.size()>0){
622
            String style =  "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";
623
            StringBuilder data = new StringBuilder();
624
            for(Point point : points){
625
                if(data.length() > 0){
626
                    data.append('|');
627
                }
628
                data.append(point.getLatitude() + "," + point.getLongitude());
629
            }
630
            int index = styleAndData.size() + 1;
631
            styleAndData.put(index + ":" +style, index + ":" +data.toString());
632
        }
633
    }
634

    
635

    
636
    /**
637
     * transform an integer (style counter) into a valid character representing a style.
638
     * 0-25 => a-z<br>
639
     * 26-51 => A-Z<br>
640
     * i not in {0,...,51} is undefined
641
     * @param i
642
     * @return
643
     */
644
    private static char getStyleAbbrev(int i){
645
        i++;
646
        int ascii = 96 + i;
647
        if (i >26){
648
            ascii = 64 + i;
649
        }
650
        return (char)ascii;
651
    }
652

    
653
}
(2-2/3)