Project

General

Profile

Download (30.6 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9

    
10
package eu.etaxonomy.cdm.ext.geo;
11

    
12
import java.awt.Color;
13
import java.io.IOException;
14
import java.io.UnsupportedEncodingException;
15
import java.net.URLEncoder;
16
import java.util.ArrayList;
17
import java.util.Collection;
18
import java.util.Collections;
19
import java.util.HashMap;
20
import java.util.HashSet;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Set;
24
import java.util.UUID;
25

    
26
import javax.persistence.Transient;
27

    
28
import org.apache.commons.collections.CollectionUtils;
29
import org.apache.commons.lang.StringUtils;
30
import org.apache.log4j.Logger;
31

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

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

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

    
69
    private static final int INT_MAX_LENGTH = String.valueOf(Integer.MAX_VALUE).length();
70

    
71
    private static IDefinedTermDao termDao;
72

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

    
80

    
81
    private static HashMap<SpecimenOrObservationType, Color> defaultSpecimenOrObservationTypeColors = null;
82

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

    
96

    
97
    private static HashMap<PresenceAbsenceTerm, Color> defaultPresenceAbsenceTermBaseColors = null;
98

    
99
    private static List<UUID>  presenceAbsenceTermVocabularyUuids = null;
100

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

    
116

    
117

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

    
124

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

    
165

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

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

    
177
        String borderWidth = "0.1";
178
        String borderColorRgb = "";
179
        String borderDashingPattern = "";
180

    
181

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

    
187
        presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);
188

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

    
192
        groupStylesAndLayers(filteredDistributions, layerMap, statusList, mapping);
193

    
194
        Map<String, String> parameters = new HashMap<>();
195

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

    
200
            char styleCode = getStyleAbbrev(styleCounter);
201

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

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

    
221
            areaStyles.put(styleCounter, styleValues);
222

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

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

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

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

    
287
            parameters.put("title", StringUtils.join(legendEntries.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
288
        }
289

    
290
        if(generateMultipleAreaDataParameters){
291
            // not generically possible since parameters can not contain duplicate keys with value "ad"
292
        } else {
293
            parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));
294
        }
295

    
296
        String queryString = makeQueryString(parameters);
297
        logger.debug("getDistributionServiceRequestParameterString(): " + queryString);
298

    
299
        return queryString;
300
    }
301

    
302

    
303
    /**
304
     * Fills the layerMap and the statusList
305
     *
306
     * @param distributions
307
     * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
308
     * @param statusList
309
     */
310
    private static void groupStylesAndLayers(Collection<Distribution> distributions,
311
            Map<String, Map<Integer,Set<Distribution>>> layerMap,
312
            List<PresenceAbsenceTerm> statusList,
313
            IGeoServiceAreaMapping mapping) {
314

    
315

    
316
        //iterate through distributions and group styles and layers
317
        //and collect necessary information
318
        for (Distribution distribution : distributions){
319
            //collect status
320
            PresenceAbsenceTerm status = distribution.getStatus();
321
            if(status == null){
322
                continue;
323
            }
324
            status = HibernateProxyHelper.deproxy(status);
325
            if (! statusList.contains(status)){
326
                statusList.add(status);
327
            }
328
            //group areas by layers and styles
329
            NamedArea area = distribution.getArea();
330

    
331
            addAreaToLayerMap(layerMap, statusList, distribution, area, mapping);
332
        }
333
    }
334

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

    
365
        if (area != null){
366
            String geoLayerName = getWMSLayerName(area, mapping);
367

    
368
            if(geoLayerName == null){
369
               logger.warn("no wms layer mapping defined for " + area.getLabel() + " [" + area.getIdInVocabulary() + "]");
370
            } else {
371
                Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerName);
372
                if (styleMap == null) {
373
                    styleMap = new HashMap<Integer, Set<Distribution>>();
374
                    layerMap.put(geoLayerName, styleMap);
375
                }
376
                addDistributionToStyleMap(distribution, styleMap, statusList);
377
            }
378
        }
379
    }
380

    
381

    
382

    
383
    /**
384
     * URI encode the given String
385
     * @param string
386
     * @return
387
     */
388
    private static String encode(String string) {
389
        String encoded = string;
390
        try {
391
            encoded = URLEncoder.encode(string, "UTF-8");
392
        } catch (UnsupportedEncodingException e) {
393
            logger.error(e);
394
        }
395
        return encoded;
396
    }
397

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

    
420
    private static String getAreaCode(Distribution distribution, IGeoServiceAreaMapping mapping){
421
        NamedArea area = distribution.getArea();
422
        TermVocabulary<NamedArea> voc = area.getVocabulary();
423
        String result = null;
424

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

    
444
        }
445
        return CdmUtils.Nz(result, "-");
446

    
447
    }
448

    
449
    private static List<String> projectToWMSSubLayer(NamedArea area){
450

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

    
471
        }
472
        //TODO countries
473

    
474
        // check if the matched layer equals the layer to project to
475
        // if not: recurse into the sub-level in order to find the specified one.
476
        String[] matchedLayerNameTokens = StringUtils.split(matchedLayerName, ':');
477
//		if(matchedLayerNameTokens.length > 0 &&  matchedLayerNameTokens[0] != projectToLayer){
478
//			for (NamedArea subArea : area.getIncludes()){
479
//
480
//			}
481
            //
482
            // add all sub areas
483
//		}
484

    
485
        return null;
486
    }
487

    
488
    private static String getWMSLayerName(NamedArea area, IGeoServiceAreaMapping mapping){
489
        TermVocabulary<NamedArea> voc = area.getVocabulary();
490
        //TDWG areas
491
        if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){
492
            NamedAreaLevel level = area.getLevel();
493
            if (level != null) {
494
                //TODO integrate into CDM
495
                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
496
                    return "tdwg1";
497
                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
498
                    return "tdwg2";
499
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
500
                    return "tdwg3";
501
                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
502
                    return "tdwg4";
503
                }
504
            }
505
            //unrecognized tdwg area
506
            return null;
507

    
508
        }else if (voc.getUuid().equals(Country.uuidCountryVocabulary)){
509
            return "country_earth:gmi_cntry";
510
        }
511

    
512
        GeoServiceArea areas = mapping.valueOf(area);
513
        if (areas != null && areas.getAreasMap().size() > 0){
514
            //FIXME multiple layers
515
            String layer = areas.getAreasMap().keySet().iterator().next();
516
            Map<String, List<String>> fields = areas.getAreasMap().get(layer);
517
            String field = fields.keySet().iterator().next();
518
            String layerString = layer + ":" + field;
519
            return layerString.toLowerCase();
520
        }
521

    
522
        return null;
523
    }
524

    
525

    
526
    private static void addDistributionToStyleMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,
527
            List<PresenceAbsenceTerm> statusList) {
528
        PresenceAbsenceTerm status = distribution.getStatus();
529
        if (status != null) {
530
            int style = statusList.indexOf(status);
531
            Set<Distribution> distributionSet = styleMap.get(style);
532
            if (distributionSet == null) {
533
                distributionSet = new HashSet<Distribution>();
534
                styleMap.put(style, distributionSet);
535
            }
536
            distributionSet.add(distribution);
537
        }
538
    }
539

    
540
    /**
541
     * @param fieldUnitPoints
542
     * @param derivedUnitPoints
543
     * @param specimenOrObservationTypeColors
544
     * @param width
545
     * @param height
546
     * @param bbox
547
     * @param backLayer
548
     * @return
549
     * e.g.:
550
     * 	l=v%3Aatbi%2Ce_w_0
551
     *  &legend=0
552
     *  &image=false
553
     *  &recalculate=false
554
     *  &ms=400%2C350
555

    
556
     *  &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
557
     *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
558
     */
559
    public static OccurrenceServiceRequestParameterDto getOccurrenceServiceRequestParameterString(
560
            List<Point> fieldUnitPoints,
561
            List<Point> derivedUnitPoints,
562
            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors) {
563
        OccurrenceServiceRequestParameterDto dto = new OccurrenceServiceRequestParameterDto();
564

    
565

    
566
        specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);
567

    
568
        Map<String, String> parameters = new HashMap<>();
569
        parameters.put("legend", "0");
570

    
571
        Map<String, String> styleAndData = new HashMap<>();
572

    
573
        addToStyleAndData(fieldUnitPoints, SpecimenOrObservationType.FieldUnit, specimenOrObservationTypeColors, styleAndData);
574
        addToStyleAndData(derivedUnitPoints, SpecimenOrObservationType.DerivedUnit, specimenOrObservationTypeColors, styleAndData);
575

    
576
        parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));
577
        parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));
578

    
579
        String queryString = makeQueryString(parameters);
580

    
581
        dto.setFieldUnitPoints(fieldUnitPoints);
582
        dto.setDerivedUnitPoints(derivedUnitPoints);
583
        dto.setOccurrenceQuery(queryString);
584

    
585
        logger.info(queryString);
586

    
587
        return dto;
588
    }
589

    
590
    /**
591
     * @param <T>
592
     * @param <S>
593
     * @param defaultMap
594
     * @param overrideMap
595
     * @return
596
     */
597
    private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {
598
        Map<T, S> tmpMap = new HashMap<T, S>();
599
        tmpMap.putAll(defaultMap);
600
        if(overrideMap != null){
601
            tmpMap.putAll(overrideMap);
602
        }
603
        return tmpMap;
604
    }
605

    
606
    private static void addToStyleAndData(
607
            List<Point> points,
608
            SpecimenOrObservationType specimenOrObservationType,
609
            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {
610

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

    
613
        if(points != null && points.size()>0){
614
            String style =  "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";
615
            StringBuilder data = new StringBuilder();
616
            for(Point point : points){
617
                if (point != null){  //should not be null, but just in case
618
                    if(data.length() > 0){
619
                        data.append('|');
620
                    }
621
                    data.append(point.getLatitude() + "," + point.getLongitude());
622
                }
623
            }
624
            int index = styleAndData.size() + 1;
625
            styleAndData.put(index + ":" +style, index + ":" +data.toString());
626
        }
627
    }
628

    
629

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

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

    
658
        Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors = null;
659
        if(StringUtils.isNotEmpty(statusColorJson)){
660

    
661
            ObjectMapper mapper = new ObjectMapper();
662
            // TODO cache the color maps to speed this up?
663

    
664
            TypeFactory typeFactory = mapper.getTypeFactory();
665
            MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, String.class);
666

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

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

    
701
        if(EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids == null) {
702

    
703
            List<UUID> uuids = new ArrayList<UUID>();
704
            // the default as first entry
705
            UUID presenceTermVocabUuid = PresenceAbsenceTerm.NATIVE().getVocabulary().getUuid();
706
            uuids.add(presenceTermVocabUuid);
707

    
708

    
709
            for(TermVocabulary vocab : vocabularyService.findByTermType(TermType.PresenceAbsenceTerm, null)) {
710
                if(!uuids.contains(vocab.getUuid())) {
711
                    uuids.add(vocab.getUuid());
712
                }
713
            }
714

    
715
            EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids = uuids;
716
        }
717

    
718
        return EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids;
719
    }
720

    
721

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

    
747
}
(4-4/14)