Project

General

Profile

Download (32.5 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.IOException;
15
import java.io.UnsupportedEncodingException;
16
import java.net.URLEncoder;
17
import java.util.ArrayList;
18
import java.util.Collection;
19
import java.util.Collections;
20
import java.util.HashMap;
21
import java.util.HashSet;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Set;
25
import java.util.UUID;
26

    
27
import javax.persistence.Transient;
28

    
29
import org.apache.commons.collections.CollectionUtils;
30
import org.apache.commons.collections.Transformer;
31
import org.apache.commons.lang.StringUtils;
32
import org.apache.log4j.Logger;
33

    
34
import com.fasterxml.jackson.core.JsonParseException;
35
import com.fasterxml.jackson.databind.JsonMappingException;
36
import com.fasterxml.jackson.databind.ObjectMapper;
37
import com.fasterxml.jackson.databind.type.MapType;
38
import com.fasterxml.jackson.databind.type.TypeFactory;
39

    
40
import eu.etaxonomy.cdm.api.service.ITermService;
41
import eu.etaxonomy.cdm.api.service.IVocabularyService;
42
import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;
43
import eu.etaxonomy.cdm.api.utility.DescriptionUtility;
44
import eu.etaxonomy.cdm.common.CdmUtils;
45
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
46
import eu.etaxonomy.cdm.model.common.Language;
47
import eu.etaxonomy.cdm.model.common.Representation;
48
import eu.etaxonomy.cdm.model.common.TermType;
49
import eu.etaxonomy.cdm.model.common.TermVocabulary;
50
import eu.etaxonomy.cdm.model.description.Distribution;
51
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
52
import eu.etaxonomy.cdm.model.location.Country;
53
import eu.etaxonomy.cdm.model.location.NamedArea;
54
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
55
import eu.etaxonomy.cdm.model.location.Point;
56
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
57
import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
58

    
59
/**
60
 * Class implementing the business logic for creating the map service string for
61
 * a given set of distributions. See {@link EditGeoService} as API for the given functionality.
62
 *
63
 * @see EditGeoService
64
 *
65
 * @author a.mueller
66
 * @created 17.11.2008
67
 */
68
public class EditGeoServiceUtilities {
69
    private static final Logger logger = Logger.getLogger(EditGeoServiceUtilities.class);
70

    
71
    private static final int INT_MAX_LENGTH = String.valueOf(Integer.MAX_VALUE).length();
72

    
73
    private static PresenceAbsenceTerm defaultStatus = PresenceAbsenceTerm.PRESENT();
74

    
75
    private static IDefinedTermDao termDao;
76

    
77

    
78

    
79
    /**
80
     * @param termDao
81
     */
82
    public static void setTermDao(IDefinedTermDao termDao) {
83
        EditGeoServiceUtilities.termDao= termDao;
84
    }
85

    
86

    
87
    private static HashMap<SpecimenOrObservationType, Color> defaultSpecimenOrObservationTypeColors = null;
88

    
89
    private static HashMap<SpecimenOrObservationType, Color> getDefaultSpecimenOrObservationTypeColors() {
90
        if(defaultSpecimenOrObservationTypeColors == null){
91
            defaultSpecimenOrObservationTypeColors = new HashMap<SpecimenOrObservationType, Color>();
92
            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.FieldUnit, Color.ORANGE);
93
            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.DerivedUnit, Color.RED);
94
            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.LivingSpecimen, Color.GREEN);
95
            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.Observation, Color.ORANGE);
96
            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.PreservedSpecimen, Color.GRAY);
97
            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.Media, Color.BLUE);
98
        }
99
        return defaultSpecimenOrObservationTypeColors;
100
    }
101

    
102

    
103
    private static HashMap<PresenceAbsenceTerm, Color> defaultPresenceAbsenceTermBaseColors = null;
104

    
105
    private static List<UUID>  presenceAbsenceTermVocabularyUuids = null;
106

    
107
    private static HashMap<PresenceAbsenceTerm, Color> getDefaultPresenceAbsenceTermBaseColors() {
108
        if(defaultPresenceAbsenceTermBaseColors == null){
109
            defaultPresenceAbsenceTermBaseColors = new HashMap<PresenceAbsenceTerm, Color>();
110
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.PRESENT(), Color.decode("0x4daf4a"));
111
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.NATIVE(), Color.decode("0x4daf4a"));
112
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.NATIVE_DOUBTFULLY_NATIVE(), Color.decode("0x377eb8"));
113
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.CULTIVATED(), Color.decode("0x984ea3"));
114
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED(), Color.decode("0xff7f00"));
115
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED_ADVENTITIOUS(), Color.decode("0xffff33"));
116
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED_CULTIVATED(), Color.decode("0xa65628"));
117
            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED_NATURALIZED(), Color.decode("0xf781bf"));
118
        }
119
        return defaultPresenceAbsenceTermBaseColors;
120
    }
121

    
122

    
123

    
124
    private static final String SUBENTRY_DELIMITER = ",";
125
    private static final String ENTRY_DELIMITER = ";";
126
    static final String ID_FROM_VALUES_SEPARATOR = ":";
127
    static final String VALUE_LIST_ENTRY_SEPARATOR = "|";
128
    static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR = "||";
129

    
130

    
131
    /**
132
     * Returns the parameter String for the EDIT geo webservice to create a
133
     * distribution map.
134
     *
135
     * @param distributions
136
     *            A set of distributions that should be shown on the map
137
     *            The {@link DescriptionUtility} class provides a method for
138
     *            filtering a set of Distributions :
139
     *
140
     *            {@code
141
     *            Collection<Distribution> filteredDistributions =
142
     *            DescriptionUtility.filterDistributions(distributions,
143
     *            subAreaPreference, statusOrderPreference, hideMarkedAreas);
144
     *            }
145
     * @param mapping
146
     *            Data regarding the mapping of NamedAreas to shape file
147
     *            attribute tables
148
     * @param presenceAbsenceTermColors
149
     *            A map that defines the colors of PresenceAbsenceTerms. The
150
     *            PresenceAbsenceTerms are defined by their uuid. If a
151
     *            PresenceAbsenceTerm is not included in this map, it's default
152
     *            color is taken instead. If the map == null all terms are
153
     *            colored by their default color.
154
     * @param projectToLayer
155
     *            name of a layer which is representing a specific
156
     *            {@link NamedAreaLevel} Supply this parameter if you to project
157
     *            all other distribution area levels to this layer.
158
     * @param languages
159
     *
160
     * @return the parameter string or an empty string if the
161
     *         <code>distributions</code> set was null or empty.
162
     */
163
    @Transient
164
    public static String getDistributionServiceRequestParameterString(
165
            Collection<Distribution> filteredDistributions,
166
            IGeoServiceAreaMapping mapping,
167
            Map<PresenceAbsenceTerm,Color> presenceAbsenceTermColors,
168
            String projectToLayer,
169
            List<Language> languages){
170

    
171

    
172
        /*
173
         * generateMultipleAreaDataParameters switches between the two possible styles:
174
         * 1. ad=layername1:area-data||layername2:area-data
175
         * 2. ad=layername1:area-data&ad=layername2:area-data
176
         */
177
        boolean generateMultipleAreaDataParameters = false;
178

    
179
        /*
180
         * doNotReuseStyles is a workaround for a problem in the EDIT MapService,
181
         * see https://dev.e-taxonomy.eu/trac/ticket/2707#comment:24
182
         *
183
         * a.kohlbecker 2014-07-02 :This bug in the map service has been
184
         * fixed now so reusing styles is now possible setting this flag to false.
185
         */
186
        boolean doNotReuseStyles = false;
187

    
188
        List<String>  perLayerAreaData = new ArrayList<String>();
189
        Map<Integer, String> areaStyles = new HashMap<Integer, String>();
190
        List<String> legendSortList = new ArrayList<String>();
191

    
192
        String borderWidth = "0.1";
193
        String borderColorRgb = "";
194
        String borderDashingPattern = "";
195

    
196

    
197
        //handle empty set
198
        if(filteredDistributions == null || filteredDistributions.size() == 0){
199
            return "";
200
        }
201

    
202
        presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);
203

    
204
        Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();
205
        List<PresenceAbsenceTerm> statusList = new ArrayList<PresenceAbsenceTerm>();
206

    
207
        groupStylesAndLayers(filteredDistributions, layerMap, statusList, mapping);
208

    
209
        Map<String, String> parameters = new HashMap<String, String>();
210

    
211
        //style
212
        int styleCounter = 0;
213
        for (PresenceAbsenceTerm status: statusList){
214

    
215
            char styleCode = getStyleAbbrev(styleCounter);
216

    
217
            //getting the area title
218
            if (languages == null){
219
                languages = new ArrayList<Language>();
220
            }
221
            if (languages.size() == 0){
222
                languages.add(Language.DEFAULT());
223
            }
224
            Representation statusRepresentation = status.getPreferredRepresentation(languages);
225

    
226
            //getting the area color
227
            Color statusColor = presenceAbsenceTermColors.get(status);
228
            String fillColorRgb;
229
            if (statusColor != null){
230
                fillColorRgb = Integer.toHexString(statusColor.getRGB()).substring(2);
231
            }else{
232
                if(status != null){
233
                    fillColorRgb = status.getDefaultColor(); //TODO
234
                } else {
235
                    fillColorRgb = defaultStatus.getDefaultColor();
236
                }
237
            }
238
            String styleValues = StringUtils.join(new String[]{fillColorRgb, borderColorRgb, borderWidth, borderDashingPattern}, ',');
239

    
240
            areaStyles.put(styleCounter, styleValues);
241

    
242
            String legendEntry = styleCode + ID_FROM_VALUES_SEPARATOR + encode(statusRepresentation.getLabel());
243
            legendSortList.add(StringUtils.leftPad(String.valueOf(status.getOrderIndex()), INT_MAX_LENGTH, '0') + legendEntry );
244
            styleCounter++;
245
        }
246

    
247
        // area data
248
        List<String> styledAreasPerLayer;
249
        List<String> areasPerStyle;
250
        /**
251
         * Map<Integer, Integer> styleUsage
252
         *
253
         * Used to avoid reusing styles in multiple layers
254
         *
255
         * key: the style id
256
         * value: the count of how often the style has been used for different layers, starts with 0 for first time use
257
         */
258
        Map<Integer, Integer> styleUsage = new HashMap<Integer, Integer>();
259

    
260
        char styleChar;
261
        for (String layerString : layerMap.keySet()){
262
            // each layer
263
            styledAreasPerLayer = new ArrayList<String>();
264
            Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
265
            for (int style: styleMap.keySet()){
266
                // stylesPerLayer
267
                if(doNotReuseStyles) {
268
                    if(!styleUsage.containsKey(style)){
269
                        styleUsage.put(style, 0);
270
                    } else {
271
                        // increment by 1
272
                        styleUsage.put(style, styleUsage.get(style) + 1);
273
                    }
274
                    Integer styleIncrement = styleUsage.get(style);
275
                    if(styleIncrement > 0){
276
                        // style code has been used before!
277
                        styleChar = getStyleAbbrev(style + styleIncrement + styleCounter);
278
                        //for debugging sometimes failing test  #3831
279
                        logger.warn("style: " + style + ", styleIncrement: " +  styleIncrement + ", styleCounter: " + styleCounter);
280
                        areaStyles.put(style + styleIncrement + styleCounter, areaStyles.get(style));
281
                    } else {
282
                        styleChar = getStyleAbbrev(style);
283
                    }
284
                } else {
285
                    styleChar = getStyleAbbrev(style);
286
                }
287
                Set<Distribution> distributionSet = styleMap.get(style);
288
                areasPerStyle = new ArrayList<String>();
289
                for (Distribution distribution: distributionSet){
290
                    // areasPerStyle
291
                    areasPerStyle.add(encode(getAreaCode(distribution, mapping)));
292
                }
293
                styledAreasPerLayer.add(styleChar + ID_FROM_VALUES_SEPARATOR + StringUtils.join(areasPerStyle.iterator(), SUBENTRY_DELIMITER));
294
            }
295
            perLayerAreaData.add(encode(layerString) + ID_FROM_VALUES_SEPARATOR + StringUtils.join(styledAreasPerLayer.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
296
        }
297

    
298
        if(areaStyles.size() > 0){
299
            ArrayList<Integer> styleIds = new ArrayList<Integer>(areaStyles.size());
300
            styleIds.addAll(areaStyles.keySet());
301
            Collections.sort(styleIds); // why is it necessary to sort here?
302
            StringBuilder db = new StringBuilder();
303
            for(Integer sid : styleIds){
304
                if(db.length() > 0){
305
                    db.append(VALUE_LIST_ENTRY_SEPARATOR);
306
                }
307
                db.append( getStyleAbbrev(sid)).append(ID_FROM_VALUES_SEPARATOR).append(areaStyles.get(sid));
308
            }
309
            parameters.put("as", db.toString());
310
        }
311
        if(legendSortList.size() > 0){
312
            // sort the label entries after the status terms
313
            Collections.sort(legendSortList);
314
            // since the status terms are have an inverse natural order
315
            // (as all other ordered term, see OrderedTermBase.performCompareTo(T orderedTerm, boolean skipVocabularyCheck)
316
            // the sorted list must be reverted
317
//            Collections.reverse(legendSortList);
318
            // remove the prepended order index (like 000000000000001 ) from the legend entries
319
            @SuppressWarnings("unchecked")
320
            Collection<String> legendEntries = CollectionUtils.collect(legendSortList, new Transformer()
321
            {
322
                @Override
323
                public String transform(Object o)
324
                {
325
                  String s = ((String) o);
326
                  return s.substring(INT_MAX_LENGTH, s.length());
327
                }
328
              });
329

    
330
            parameters.put("title", StringUtils.join(legendEntries.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
331
        }
332

    
333
        if(generateMultipleAreaDataParameters){
334
            // not generically possible since parameters can not contain duplicate keys with value "ad"
335
        } else {
336
            parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));
337
        }
338

    
339
        String queryString = makeQueryString(parameters);
340
        logger.debug("getDistributionServiceRequestParameterString(): " + queryString);
341

    
342
        return queryString;
343
    }
344

    
345

    
346
    /**
347
     * Fills the layerMap and the statusList
348
     *
349
     * @param distributions
350
     * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
351
     * @param statusList
352
     */
353
    private static void groupStylesAndLayers(Collection<Distribution> distributions,
354
            Map<String, Map<Integer,Set<Distribution>>> layerMap,
355
            List<PresenceAbsenceTerm> statusList,
356
            IGeoServiceAreaMapping mapping) {
357

    
358

    
359
        //iterate through distributions and group styles and layers
360
        //and collect necessary information
361
        for (Distribution distribution : distributions){
362
            //collect status
363
            PresenceAbsenceTerm status = distribution.getStatus();
364
            if(status == null){
365
                status = defaultStatus;
366
            }
367
            status = HibernateProxyHelper.deproxy(status, PresenceAbsenceTerm.class);
368
            if (! statusList.contains(status)){
369
                statusList.add(status);
370
            }
371
            //group areas by layers and styles
372
            NamedArea area = distribution.getArea();
373

    
374
            addAreaToLayerMap(layerMap, statusList, distribution, area, mapping);
375
        }
376
    }
377

    
378
    /**
379
     * Adds the areas to the layer map. Areas which do not have layer information
380
     * mapped to them are ignored.
381
     * <p>
382
     * A layer map holds the following information:
383
     *
384
     * <ul>
385
     *   <li><b>String</b>: the WMSLayerName which matches the level of the
386
     *   contained distributions areas</li>
387
     *   <li><b>StyleMap</b>:</li>
388
     *   <ul>
389
     *     <li><b>Integer</b>: the index of the status in the
390
     *     <code>statusList</code></li>
391
     *     <li><b>Set{@code<Distribution>}</b>: the set of distributions having the
392
     *     same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>
393
     *   </ul>
394
     * </ul>
395
     *
396
     * @param layerMap
397
     * @param statusList
398
     * @param distribution
399
     * @param area
400
     */
401
    private static void addAreaToLayerMap(Map<String, Map<Integer,
402
            Set<Distribution>>> layerMap,
403
            List<PresenceAbsenceTerm> statusList,
404
            Distribution distribution,
405
            NamedArea area,
406
            IGeoServiceAreaMapping mapping) {
407

    
408
        if (area != null){
409
            String geoLayerName = getWMSLayerName(area, mapping);
410

    
411
            if(geoLayerName == null){
412
               /* IGNORE areas for which no layer is mapped */
413
            } else {
414
                Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerName);
415
                if (styleMap == null) {
416
                    styleMap = new HashMap<Integer, Set<Distribution>>();
417
                    layerMap.put(geoLayerName, styleMap);
418
                }
419
                addDistributionToStyleMap(distribution, styleMap, statusList);
420
            }
421
        }
422
    }
423

    
424

    
425

    
426
    /**
427
     * URI encode the given String
428
     * @param string
429
     * @return
430
     */
431
    private static String encode(String string) {
432
        String encoded = string;
433
        try {
434
            encoded = URLEncoder.encode(string, "UTF-8");
435
        } catch (UnsupportedEncodingException e) {
436
            logger.error(e);
437
        }
438
        return encoded;
439
    }
440

    
441
    /**
442
     * combine parameter into a URI query string fragment. The values will be
443
     * escaped correctly.
444
     *
445
     * @param parameters
446
     * @return a URI query string fragment
447
     */
448
    private static String makeQueryString(Map<String, String> parameters){
449
        StringBuilder queryString = new StringBuilder();
450
        for (String key : parameters.keySet()) {
451
            if(queryString.length() > 0){
452
                queryString.append('&');
453
            }
454
            if(key.equals("od") || key.equals("os") || key.equals("ms") || key.equals("ad") || key.equals("as") || key.equals("title") || key.equals("bbox")){
455
                queryString.append(key).append('=').append(parameters.get(key));
456
            } else {
457
                queryString.append(key).append('=').append(encode(parameters.get(key)));
458
            }
459
        }
460
        return queryString.toString();
461
    }
462

    
463
    private static String getAreaCode(Distribution distribution, IGeoServiceAreaMapping mapping){
464
        NamedArea area = distribution.getArea();
465
        TermVocabulary<NamedArea> voc = area.getVocabulary();
466
        String result = null;
467

    
468
        if (voc != null && voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary) ||  voc.getUuid().equals(Country.uuidCountryVocabulary)) {
469
            // TDWG or Country
470
            result = area.getIdInVocabulary();
471
            if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())) {
472
                result = result.replace("-", "");
473
            }
474
        } else {
475
            // use generic GeoServiceArea data stored in technical annotations
476
            // of the
477
            // named area
478
            GeoServiceArea areas = mapping.valueOf(area);
479
            if ((areas != null) && areas.size() > 0) {
480
                // FIXME multiple layers
481
                List<String> values = areas.getAreasMap().values().iterator().next().values().iterator().next();
482
                for (String value : values) {
483
                    result = CdmUtils.concat(SUBENTRY_DELIMITER, result, value);
484
                }
485
            }
486

    
487
        }
488
        return CdmUtils.Nz(result, "-");
489

    
490
    }
491

    
492
    private static List<String> projectToWMSSubLayer(NamedArea area){
493

    
494
        List<String> layerNames = new ArrayList<String>();
495
        String matchedLayerName = null;
496
        TermVocabulary<NamedArea> voc = area.getVocabulary();
497
        //TDWG areas
498
        if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){
499
            NamedAreaLevel level = area.getLevel();
500
            if (level != null) {
501
                //TODO integrate into CDM
502
                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
503
                    matchedLayerName = "tdwg1" ;
504
                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
505
                    matchedLayerName = "tdwg2";
506
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
507
                    matchedLayerName = "tdwg3";
508
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
509
                    matchedLayerName = "tdwg4";
510
                }
511
            }
512
            //unrecognized tdwg area
513

    
514
        }
515
        //TODO countries
516

    
517
        // check if the matched layer equals the layer to project to
518
        // if not: recurse into the sub-level in order to find the specified one.
519
        String[] matchedLayerNameTokens = StringUtils.split(matchedLayerName, ':');
520
//		if(matchedLayerNameTokens.length > 0 &&  matchedLayerNameTokens[0] != projectToLayer){
521
//			for (NamedArea subArea : area.getIncludes()){
522
//
523
//			}
524
            //
525
            // add all sub areas
526
//		}
527

    
528
        return null;
529
    }
530

    
531
    private static String getWMSLayerName(NamedArea area, IGeoServiceAreaMapping mapping){
532
        TermVocabulary<NamedArea> voc = area.getVocabulary();
533
        //TDWG areas
534
        if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){
535
            NamedAreaLevel level = area.getLevel();
536
            if (level != null) {
537
                //TODO integrate into CDM
538
                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
539
                    return "tdwg1";
540
                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
541
                    return "tdwg2";
542
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
543
                    return "tdwg3";
544
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
545
                    return "tdwg4";
546
                }
547
            }
548
            //unrecognized tdwg area
549
            return null;
550

    
551
        }else if (voc.getUuid().equals(Country.uuidCountryVocabulary)){
552
            return "country_earth:gmi_cntry";
553
        }
554

    
555
        GeoServiceArea areas = mapping.valueOf(area);
556
        if (areas != null && areas.getAreasMap().size() > 0){
557
            //FIXME multiple layers
558
            String layer = areas.getAreasMap().keySet().iterator().next();
559
            Map<String, List<String>> fields = areas.getAreasMap().get(layer);
560
            String field = fields.keySet().iterator().next();
561
            String layerString = layer + ":" + field;
562
            return layerString.toLowerCase();
563
        }
564

    
565
        return null;
566
    }
567

    
568

    
569
    private static void addDistributionToStyleMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,
570
            List<PresenceAbsenceTerm> statusList) {
571
        PresenceAbsenceTerm status = distribution.getStatus();
572
        if (status == null) {
573
            status = defaultStatus;
574
        }
575
        int style = statusList.indexOf(status);
576
        Set<Distribution> distributionSet = styleMap.get(style);
577
        if (distributionSet == null) {
578
            distributionSet = new HashSet<Distribution>();
579
            styleMap.put(style, distributionSet);
580
        }
581
        distributionSet.add(distribution);
582
    }
583

    
584
    /**
585
     * @param fieldUnitPoints
586
     * @param derivedUnitPoints
587
     * @param specimenOrObservationTypeColors
588
     * @param width
589
     * @param height
590
     * @param bbox
591
     * @param backLayer
592
     * @return
593
     * e.g.:
594
     * 	l=v%3Aatbi%2Ce_w_0
595
     *  &legend=0
596
     *  &image=false
597
     *  &recalculate=false
598
     *  &ms=400%2C350
599

    
600
     *  &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
601
     *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
602
     */
603
    public static OccurrenceServiceRequestParameterDto getOccurrenceServiceRequestParameterString(
604
            List<Point> fieldUnitPoints,
605
            List<Point> derivedUnitPoints,
606
            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors) {
607
        OccurrenceServiceRequestParameterDto dto = new OccurrenceServiceRequestParameterDto();
608

    
609

    
610
        specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);
611

    
612
        Map<String, String> parameters = new HashMap<String, String>();
613
        parameters.put("legend", "0");
614

    
615
        Map<String, String> styleAndData = new HashMap<String, String>();
616

    
617
        addToStyleAndData(fieldUnitPoints, SpecimenOrObservationType.FieldUnit, specimenOrObservationTypeColors, styleAndData);
618
        addToStyleAndData(derivedUnitPoints, SpecimenOrObservationType.DerivedUnit, specimenOrObservationTypeColors, styleAndData);
619

    
620
        parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));
621
        parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));
622

    
623
        String queryString = makeQueryString(parameters);
624

    
625
        dto.setFieldUnitPoints(fieldUnitPoints);
626
        dto.setDerivedUnitPoints(derivedUnitPoints);
627
        dto.setOccurrenceQuery(queryString);
628

    
629
        logger.info(queryString);
630

    
631
        return dto;
632
    }
633

    
634
    /**
635
     * @param <T>
636
     * @param <S>
637
     * @param defaultMap
638
     * @param overrideMap
639
     * @return
640
     */
641
    private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {
642
        Map<T, S> tmpMap = new HashMap<T, S>();
643
        tmpMap.putAll(defaultMap);
644
        if(overrideMap != null){
645
            tmpMap.putAll(overrideMap);
646
        }
647
        return tmpMap;
648
    }
649

    
650
    private static void addToStyleAndData(
651
            List<Point> points,
652
            SpecimenOrObservationType specimenOrObservationType,
653
            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {
654

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

    
657
        if(points != null && points.size()>0){
658
            String style =  "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";
659
            StringBuilder data = new StringBuilder();
660
            for(Point point : points){
661
                if(data.length() > 0){
662
                    data.append('|');
663
                }
664
                data.append(point.getLatitude() + "," + point.getLongitude());
665
            }
666
            int index = styleAndData.size() + 1;
667
            styleAndData.put(index + ":" +style, index + ":" +data.toString());
668
        }
669
    }
670

    
671

    
672
    /**
673
     * transform an integer (style counter) into a valid character representing a style.
674
     * 0-25 => a-z<br>
675
     * 26-51 => A-Z<br>
676
     * i not in {0,...,51} is undefined
677
     * @param i
678
     * @return
679
     */
680
    private static char getStyleAbbrev(int i){
681
        i++;
682
        int ascii = 96 + i;
683
        if (i >26){
684
            ascii = 64 + i;
685
        }
686
        return (char)ascii;
687
    }
688

    
689
    /**
690
     * @param statusColorJson for example: {@code {"n":"#ff0000","p":"#ffff00"}}
691
     * @param vocabularyService TODO
692
     * @return
693
     * @throws IOException
694
     * @throws JsonParseException
695
     * @throws JsonMappingException
696
     */
697
    public static Map<PresenceAbsenceTerm, Color> buildStatusColorMap(String statusColorJson, ITermService termService, IVocabularyService vocabularyService) throws IOException, JsonParseException,
698
            JsonMappingException {
699

    
700
        Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors = null;
701
        if(StringUtils.isNotEmpty(statusColorJson)){
702

    
703
            ObjectMapper mapper = new ObjectMapper();
704
            // TODO cache the color maps to speed this up?
705

    
706
            TypeFactory typeFactory = mapper.getTypeFactory();
707
            MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, String.class);
708

    
709
            Map<String,String> statusColorMap = mapper.readValue(statusColorJson, mapType);
710
            presenceAbsenceTermColors = new HashMap<PresenceAbsenceTerm, Color>();
711
            PresenceAbsenceTerm paTerm = null;
712
            for(String statusId : statusColorMap.keySet()){
713
                try {
714
                    Color color = Color.decode(statusColorMap.get(statusId));
715
                    // the below loop is  a hack for #4522 (custom status colors not working in cyprus portal)
716
                    // remove it once the ticket is solved
717
                    for(UUID vocabUuid : presenceAbsenceTermVocabularyUuids(vocabularyService)) {
718
                        paTerm = termService.findByIdInVocabulary(statusId, vocabUuid, PresenceAbsenceTerm.class);
719
                        if(paTerm != null) {
720
                            break;
721
                        }
722
                    }
723
                    if(paTerm != null){
724
                        presenceAbsenceTermColors.put(paTerm, color);
725
                    }
726
                } catch (NumberFormatException e){
727
                    logger.error("Cannot decode color", e);
728
                }
729
            }
730
        }
731
        return presenceAbsenceTermColors;
732
    }
733

    
734
    /**
735
     * this is a hack for #4522 (custom status colors not working in cyprus portal)
736
     * remove this method once the ticket is solved
737
     *
738
     * @param vocabularyService
739
     * @return
740
     */
741
    private static List<UUID> presenceAbsenceTermVocabularyUuids(IVocabularyService vocabularyService) {
742

    
743
        if(EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids == null) {
744

    
745
            List<UUID> uuids = new ArrayList<UUID>();
746
            // the default as first entry
747
            UUID presenceTermVocabUuid = PresenceAbsenceTerm.NATIVE().getVocabulary().getUuid();
748
            uuids.add(presenceTermVocabUuid);
749

    
750

    
751
            for(TermVocabulary vocab : vocabularyService.findByTermType(TermType.PresenceAbsenceTerm)) {
752
                if(!uuids.contains(vocab.getUuid())) {
753
                    uuids.add(vocab.getUuid());
754
                }
755
            }
756

    
757
            EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids = uuids;
758
        }
759

    
760
        return EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids;
761
    }
762

    
763

    
764
    /**
765
     * @param filteredDistributions
766
     * @param recipe
767
     * @param hideMarkedAreas
768
     * @param langs
769
     * @return
770
     */
771
    public static CondensedDistribution getCondensedDistribution(Collection<Distribution> filteredDistributions,
772
            CondensedDistributionRecipe recipe, List<Language> langs) {
773
        ICondensedDistributionComposer composer;
774
        if(recipe == null) {
775
            throw new NullPointerException("parameter recipe must not be null");
776
        }
777
        try {
778
            composer = recipe.newCondensedDistributionComposerInstance();
779
        } catch (InstantiationException e) {
780
            throw new RuntimeException(e);
781
        } catch (IllegalAccessException e) {
782
            throw new RuntimeException(e);
783
        }
784
        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(
785
                filteredDistributions,  langs);
786
        return condensedDistribution;
787
    }
788

    
789
}
(3-3/12)