Project

General

Profile

Download (30.8 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 IDefinedTermDao termDao;
74

    
75
    /**
76
     * @param termDao
77
     */
78
    public static void setTermDao(IDefinedTermDao termDao) {
79
        EditGeoServiceUtilities.termDao= termDao;
80
    }
81

    
82

    
83
    private static HashMap<SpecimenOrObservationType, Color> defaultSpecimenOrObservationTypeColors = null;
84

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

    
98

    
99
    private static HashMap<PresenceAbsenceTerm, Color> defaultPresenceAbsenceTermBaseColors = null;
100

    
101
    private static List<UUID>  presenceAbsenceTermVocabularyUuids = null;
102

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

    
118

    
119

    
120
    private static final String SUBENTRY_DELIMITER = ",";
121
    private static final String ENTRY_DELIMITER = ";";
122
    static final String ID_FROM_VALUES_SEPARATOR = ":";
123
    static final String VALUE_LIST_ENTRY_SEPARATOR = "|";
124
    static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR = "||";
125

    
126

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

    
167

    
168
        /*
169
         * generateMultipleAreaDataParameters switches between the two possible styles:
170
         * 1. ad=layername1:area-data||layername2:area-data
171
         * 2. ad=layername1:area-data&ad=layername2:area-data
172
         */
173
        boolean generateMultipleAreaDataParameters = false;
174

    
175
        List<String>  perLayerAreaData = new ArrayList<String>();
176
        Map<Integer, String> areaStyles = new HashMap<Integer, String>();
177
        List<String> legendSortList = new ArrayList<String>();
178

    
179
        String borderWidth = "0.1";
180
        String borderColorRgb = "";
181
        String borderDashingPattern = "";
182

    
183

    
184
        //handle empty set
185
        if(filteredDistributions == null || filteredDistributions.size() == 0){
186
            return "";
187
        }
188

    
189
        presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);
190

    
191
        Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();
192
        List<PresenceAbsenceTerm> statusList = new ArrayList<PresenceAbsenceTerm>();
193

    
194
        groupStylesAndLayers(filteredDistributions, layerMap, statusList, mapping);
195

    
196
        Map<String, String> parameters = new HashMap<String, String>();
197

    
198
        //style
199
        int styleCounter = 0;
200
        for (PresenceAbsenceTerm status: statusList){
201

    
202
            char styleCode = getStyleAbbrev(styleCounter);
203

    
204
            //getting the area title
205
            if (languages == null){
206
                languages = new ArrayList<Language>();
207
            }
208
            if (languages.size() == 0){
209
                languages.add(Language.DEFAULT());
210
            }
211
            Representation statusRepresentation = status.getPreferredRepresentation(languages);
212

    
213
            //getting the area color
214
            Color statusColor = presenceAbsenceTermColors.get(status);
215
            String fillColorRgb;
216
            if (statusColor != null){
217
                fillColorRgb = Integer.toHexString(statusColor.getRGB()).substring(2);
218
            }else{
219
                fillColorRgb = status.getDefaultColor(); //TODO
220
            }
221
            String styleValues = StringUtils.join(new String[]{fillColorRgb, borderColorRgb, borderWidth, borderDashingPattern}, ',');
222

    
223
            areaStyles.put(styleCounter, styleValues);
224

    
225
            String legendEntry = styleCode + ID_FROM_VALUES_SEPARATOR + encode(statusRepresentation.getLabel());
226
            legendSortList.add(StringUtils.leftPad(String.valueOf(status.getOrderIndex()), INT_MAX_LENGTH, '0') + legendEntry );
227
            styleCounter++;
228
        }
229

    
230
        // area data
231
        List<String> styledAreasPerLayer;
232
        List<String> areasPerStyle;
233
        /**
234
         * Map<Integer, Integer> styleUsage
235
         *
236
         * Used to avoid reusing styles in multiple layers
237
         *
238
         * key: the style id
239
         * value: the count of how often the style has been used for different layers, starts with 0 for first time use
240
         */
241
        Map<Integer, Integer> styleUsage = new HashMap<Integer, Integer>();
242

    
243
        char styleChar;
244
        for (String layerString : layerMap.keySet()){
245
            // each layer
246
            styledAreasPerLayer = new ArrayList<String>();
247
            Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
248
            for (int style: styleMap.keySet()){
249
                // stylesPerLayer
250
                styleChar = getStyleAbbrev(style);
251
                Set<Distribution> distributionSet = styleMap.get(style);
252
                areasPerStyle = new ArrayList<String>();
253
                for (Distribution distribution: distributionSet){
254
                    // areasPerStyle
255
                    areasPerStyle.add(encode(getAreaCode(distribution, mapping)));
256
                }
257
                styledAreasPerLayer.add(styleChar + ID_FROM_VALUES_SEPARATOR + StringUtils.join(areasPerStyle.iterator(), SUBENTRY_DELIMITER));
258
            }
259
            perLayerAreaData.add(encode(layerString) + ID_FROM_VALUES_SEPARATOR + StringUtils.join(styledAreasPerLayer.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
260
        }
261

    
262
        if(areaStyles.size() > 0){
263
            ArrayList<Integer> styleIds = new ArrayList<Integer>(areaStyles.size());
264
            styleIds.addAll(areaStyles.keySet());
265
            Collections.sort(styleIds); // why is it necessary to sort here?
266
            StringBuilder db = new StringBuilder();
267
            for(Integer sid : styleIds){
268
                if(db.length() > 0){
269
                    db.append(VALUE_LIST_ENTRY_SEPARATOR);
270
                }
271
                db.append( getStyleAbbrev(sid)).append(ID_FROM_VALUES_SEPARATOR).append(areaStyles.get(sid));
272
            }
273
            parameters.put("as", db.toString());
274
        }
275
        if(legendSortList.size() > 0){
276
            // sort the label entries after the status terms
277
            Collections.sort(legendSortList);
278
            // since the status terms are have an inverse natural order
279
            // (as all other ordered term, see OrderedTermBase.performCompareTo(T orderedTerm, boolean skipVocabularyCheck)
280
            // the sorted list must be reverted
281
//            Collections.reverse(legendSortList);
282
            // remove the prepended order index (like 000000000000001 ) from the legend entries
283
            @SuppressWarnings("unchecked")
284
            Collection<String> legendEntries = CollectionUtils.collect(legendSortList, new Transformer()
285
            {
286
                @Override
287
                public String transform(Object o)
288
                {
289
                  String s = ((String) o);
290
                  return s.substring(INT_MAX_LENGTH, s.length());
291
                }
292
              });
293

    
294
            parameters.put("title", StringUtils.join(legendEntries.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
295
        }
296

    
297
        if(generateMultipleAreaDataParameters){
298
            // not generically possible since parameters can not contain duplicate keys with value "ad"
299
        } else {
300
            parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));
301
        }
302

    
303
        String queryString = makeQueryString(parameters);
304
        logger.debug("getDistributionServiceRequestParameterString(): " + queryString);
305

    
306
        return queryString;
307
    }
308

    
309

    
310
    /**
311
     * Fills the layerMap and the statusList
312
     *
313
     * @param distributions
314
     * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
315
     * @param statusList
316
     */
317
    private static void groupStylesAndLayers(Collection<Distribution> distributions,
318
            Map<String, Map<Integer,Set<Distribution>>> layerMap,
319
            List<PresenceAbsenceTerm> statusList,
320
            IGeoServiceAreaMapping mapping) {
321

    
322

    
323
        //iterate through distributions and group styles and layers
324
        //and collect necessary information
325
        for (Distribution distribution : distributions){
326
            //collect status
327
            PresenceAbsenceTerm status = distribution.getStatus();
328
            if(status == null){
329
                continue;
330
            }
331
            status = HibernateProxyHelper.deproxy(status, PresenceAbsenceTerm.class);
332
            if (! statusList.contains(status)){
333
                statusList.add(status);
334
            }
335
            //group areas by layers and styles
336
            NamedArea area = distribution.getArea();
337

    
338
            addAreaToLayerMap(layerMap, statusList, distribution, area, mapping);
339
        }
340
    }
341

    
342
    /**
343
     * Adds the areas to the layer map. Areas which do not have layer information
344
     * mapped to them are ignored.
345
     * <p>
346
     * A layer map holds the following information:
347
     *
348
     * <ul>
349
     *   <li><b>String</b>: the WMSLayerName which matches the level of the
350
     *   contained distributions areas</li>
351
     *   <li><b>StyleMap</b>:</li>
352
     *   <ul>
353
     *     <li><b>Integer</b>: the index of the status in the
354
     *     <code>statusList</code></li>
355
     *     <li><b>Set{@code<Distribution>}</b>: the set of distributions having the
356
     *     same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>
357
     *   </ul>
358
     * </ul>
359
     *
360
     * @param layerMap
361
     * @param statusList
362
     * @param distribution
363
     * @param area
364
     */
365
    private static void addAreaToLayerMap(Map<String, Map<Integer,
366
            Set<Distribution>>> layerMap,
367
            List<PresenceAbsenceTerm> statusList,
368
            Distribution distribution,
369
            NamedArea area,
370
            IGeoServiceAreaMapping mapping) {
371

    
372
        if (area != null){
373
            String geoLayerName = getWMSLayerName(area, mapping);
374

    
375
            if(geoLayerName == null){
376
               /* IGNORE areas for which no layer is mapped */
377
            } else {
378
                Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerName);
379
                if (styleMap == null) {
380
                    styleMap = new HashMap<Integer, Set<Distribution>>();
381
                    layerMap.put(geoLayerName, styleMap);
382
                }
383
                addDistributionToStyleMap(distribution, styleMap, statusList);
384
            }
385
        }
386
    }
387

    
388

    
389

    
390
    /**
391
     * URI encode the given String
392
     * @param string
393
     * @return
394
     */
395
    private static String encode(String string) {
396
        String encoded = string;
397
        try {
398
            encoded = URLEncoder.encode(string, "UTF-8");
399
        } catch (UnsupportedEncodingException e) {
400
            logger.error(e);
401
        }
402
        return encoded;
403
    }
404

    
405
    /**
406
     * combine parameter into a URI query string fragment. The values will be
407
     * escaped correctly.
408
     *
409
     * @param parameters
410
     * @return a URI query string fragment
411
     */
412
    private static String makeQueryString(Map<String, String> parameters){
413
        StringBuilder queryString = new StringBuilder();
414
        for (String key : parameters.keySet()) {
415
            if(queryString.length() > 0){
416
                queryString.append('&');
417
            }
418
            if(key.equals("od") || key.equals("os") || key.equals("ms") || key.equals("ad") || key.equals("as") || key.equals("title") || key.equals("bbox")){
419
                queryString.append(key).append('=').append(parameters.get(key));
420
            } else {
421
                queryString.append(key).append('=').append(encode(parameters.get(key)));
422
            }
423
        }
424
        return queryString.toString();
425
    }
426

    
427
    private static String getAreaCode(Distribution distribution, IGeoServiceAreaMapping mapping){
428
        NamedArea area = distribution.getArea();
429
        TermVocabulary<NamedArea> voc = area.getVocabulary();
430
        String result = null;
431

    
432
        if (voc != null && voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary) ||  voc.getUuid().equals(Country.uuidCountryVocabulary)) {
433
            // TDWG or Country
434
            result = area.getIdInVocabulary();
435
            if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())) {
436
                result = result.replace("-", "");
437
            }
438
        } else {
439
            // use generic GeoServiceArea data stored in technical annotations
440
            // of the
441
            // named area
442
            GeoServiceArea areas = mapping.valueOf(area);
443
            if ((areas != null) && areas.size() > 0) {
444
                // FIXME multiple layers
445
                List<String> values = areas.getAreasMap().values().iterator().next().values().iterator().next();
446
                for (String value : values) {
447
                    result = CdmUtils.concat(SUBENTRY_DELIMITER, result, value);
448
                }
449
            }
450

    
451
        }
452
        return CdmUtils.Nz(result, "-");
453

    
454
    }
455

    
456
    private static List<String> projectToWMSSubLayer(NamedArea area){
457

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

    
478
        }
479
        //TODO countries
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, IGeoServiceAreaMapping mapping){
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
                    return "tdwg1";
504
                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
505
                    return "tdwg2";
506
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
507
                    return "tdwg3";
508
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
509
                    return "tdwg4";
510
                }
511
            }
512
            //unrecognized tdwg area
513
            return null;
514

    
515
        }else if (voc.getUuid().equals(Country.uuidCountryVocabulary)){
516
            return "country_earth:gmi_cntry";
517
        }
518

    
519
        GeoServiceArea areas = mapping.valueOf(area);
520
        if (areas != null && areas.getAreasMap().size() > 0){
521
            //FIXME multiple layers
522
            String layer = areas.getAreasMap().keySet().iterator().next();
523
            Map<String, List<String>> fields = areas.getAreasMap().get(layer);
524
            String field = fields.keySet().iterator().next();
525
            String layerString = layer + ":" + field;
526
            return layerString.toLowerCase();
527
        }
528

    
529
        return null;
530
    }
531

    
532

    
533
    private static void addDistributionToStyleMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,
534
            List<PresenceAbsenceTerm> statusList) {
535
        PresenceAbsenceTerm status = distribution.getStatus();
536
        if (status != null) {
537
            int style = statusList.indexOf(status);
538
            Set<Distribution> distributionSet = styleMap.get(style);
539
            if (distributionSet == null) {
540
                distributionSet = new HashSet<Distribution>();
541
                styleMap.put(style, distributionSet);
542
            }
543
            distributionSet.add(distribution);
544
        }
545
    }
546

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

    
563
     *  &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
564
     *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
565
     */
566
    public static OccurrenceServiceRequestParameterDto getOccurrenceServiceRequestParameterString(
567
            List<Point> fieldUnitPoints,
568
            List<Point> derivedUnitPoints,
569
            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors) {
570
        OccurrenceServiceRequestParameterDto dto = new OccurrenceServiceRequestParameterDto();
571

    
572

    
573
        specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);
574

    
575
        Map<String, String> parameters = new HashMap<String, String>();
576
        parameters.put("legend", "0");
577

    
578
        Map<String, String> styleAndData = new HashMap<String, String>();
579

    
580
        addToStyleAndData(fieldUnitPoints, SpecimenOrObservationType.FieldUnit, specimenOrObservationTypeColors, styleAndData);
581
        addToStyleAndData(derivedUnitPoints, SpecimenOrObservationType.DerivedUnit, specimenOrObservationTypeColors, styleAndData);
582

    
583
        parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));
584
        parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));
585

    
586
        String queryString = makeQueryString(parameters);
587

    
588
        dto.setFieldUnitPoints(fieldUnitPoints);
589
        dto.setDerivedUnitPoints(derivedUnitPoints);
590
        dto.setOccurrenceQuery(queryString);
591

    
592
        logger.info(queryString);
593

    
594
        return dto;
595
    }
596

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

    
613
    private static void addToStyleAndData(
614
            List<Point> points,
615
            SpecimenOrObservationType specimenOrObservationType,
616
            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {
617

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

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

    
634

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

    
652
    /**
653
     * @param statusColorJson for example: {@code {"n":"#ff0000","p":"#ffff00"}}
654
     * @param vocabularyService TODO
655
     * @return
656
     * @throws IOException
657
     * @throws JsonParseException
658
     * @throws JsonMappingException
659
     */
660
    public static Map<PresenceAbsenceTerm, Color> buildStatusColorMap(String statusColorJson, ITermService termService, IVocabularyService vocabularyService) throws IOException, JsonParseException,
661
            JsonMappingException {
662

    
663
        Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors = null;
664
        if(StringUtils.isNotEmpty(statusColorJson)){
665

    
666
            ObjectMapper mapper = new ObjectMapper();
667
            // TODO cache the color maps to speed this up?
668

    
669
            TypeFactory typeFactory = mapper.getTypeFactory();
670
            MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, String.class);
671

    
672
            Map<String,String> statusColorMap = mapper.readValue(statusColorJson, mapType);
673
            presenceAbsenceTermColors = new HashMap<PresenceAbsenceTerm, Color>();
674
            PresenceAbsenceTerm paTerm = null;
675
            for(String statusId : statusColorMap.keySet()){
676
                try {
677
                    Color color = Color.decode(statusColorMap.get(statusId));
678
                    // the below loop is  a hack for #4522 (custom status colors not working in cyprus portal)
679
                    // remove it once the ticket is solved
680
                    for(UUID vocabUuid : presenceAbsenceTermVocabularyUuids(vocabularyService)) {
681
                        paTerm = termService.findByIdInVocabulary(statusId, vocabUuid, PresenceAbsenceTerm.class);
682
                        if(paTerm != null) {
683
                            break;
684
                        }
685
                    }
686
                    if(paTerm != null){
687
                        presenceAbsenceTermColors.put(paTerm, color);
688
                    }
689
                } catch (NumberFormatException e){
690
                    logger.error("Cannot decode color", e);
691
                }
692
            }
693
        }
694
        return presenceAbsenceTermColors;
695
    }
696

    
697
    /**
698
     * this is a hack for #4522 (custom status colors not working in cyprus portal)
699
     * remove this method once the ticket is solved
700
     *
701
     * @param vocabularyService
702
     * @return
703
     */
704
    private static List<UUID> presenceAbsenceTermVocabularyUuids(IVocabularyService vocabularyService) {
705

    
706
        if(EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids == null) {
707

    
708
            List<UUID> uuids = new ArrayList<UUID>();
709
            // the default as first entry
710
            UUID presenceTermVocabUuid = PresenceAbsenceTerm.NATIVE().getVocabulary().getUuid();
711
            uuids.add(presenceTermVocabUuid);
712

    
713

    
714
            for(TermVocabulary vocab : vocabularyService.findByTermType(TermType.PresenceAbsenceTerm)) {
715
                if(!uuids.contains(vocab.getUuid())) {
716
                    uuids.add(vocab.getUuid());
717
                }
718
            }
719

    
720
            EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids = uuids;
721
        }
722

    
723
        return EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids;
724
    }
725

    
726

    
727
    /**
728
     * @param filteredDistributions
729
     * @param recipe
730
     * @param hideMarkedAreas
731
     * @param langs
732
     * @return
733
     */
734
    public static CondensedDistribution getCondensedDistribution(Collection<Distribution> filteredDistributions,
735
            CondensedDistributionRecipe recipe, List<Language> langs) {
736
        ICondensedDistributionComposer composer;
737
        if(recipe == null) {
738
            throw new NullPointerException("parameter recipe must not be null");
739
        }
740
        try {
741
            composer = recipe.newCondensedDistributionComposerInstance();
742
        } catch (InstantiationException e) {
743
            throw new RuntimeException(e);
744
        } catch (IllegalAccessException e) {
745
            throw new RuntimeException(e);
746
        }
747
        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(
748
                filteredDistributions,  langs);
749
        return condensedDistribution;
750
    }
751

    
752
}
(4-4/14)