Project

General

Profile

Download (30.8 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.collections.Transformer;
30
import org.apache.commons.lang.StringUtils;
31
import org.apache.log4j.Logger;
32

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

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

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

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

    
72
    private static IDefinedTermDao termDao;
73

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

    
81

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

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

    
97

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

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

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

    
117

    
118

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

    
125

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

    
166

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

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

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

    
182

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

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

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

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

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

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

    
201
            char styleCode = getStyleAbbrev(styleCounter);
202

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

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

    
222
            areaStyles.put(styleCounter, styleValues);
223

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

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

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

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

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

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

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

    
305
        return queryString;
306
    }
307

    
308

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

    
321

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

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

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

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

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

    
387

    
388

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

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

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

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

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

    
453
    }
454

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

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

    
477
        }
478
        //TODO countries
479

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

    
491
        return null;
492
    }
493

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

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

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

    
528
        return null;
529
    }
530

    
531

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

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

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

    
571

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

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

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

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

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

    
585
        String queryString = makeQueryString(parameters);
586

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

    
591
        logger.info(queryString);
592

    
593
        return dto;
594
    }
595

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

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

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

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

    
633

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

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

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

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

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

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

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

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

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

    
712

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

    
719
            EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids = uuids;
720
        }
721

    
722
        return EditGeoServiceUtilities.presenceAbsenceTermVocabularyUuids;
723
    }
724

    
725

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

    
751
}
(4-4/14)