3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.ext
.geo
;
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
;
25 import java
.util
.UUID
;
27 import javax
.persistence
.Transient
;
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 import org
.codehaus
.jackson
.JsonParseException
;
34 import org
.codehaus
.jackson
.map
.JsonMappingException
;
35 import org
.codehaus
.jackson
.map
.ObjectMapper
;
36 import org
.codehaus
.jackson
.map
.type
.MapType
;
37 import org
.codehaus
.jackson
.map
.type
.TypeFactory
;
39 import eu
.etaxonomy
.cdm
.api
.service
.ITermService
;
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
.model
.common
.Language
;
44 import eu
.etaxonomy
.cdm
.model
.common
.Representation
;
45 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
46 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
47 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
48 import eu
.etaxonomy
.cdm
.model
.location
.Country
;
49 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
50 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaLevel
;
51 import eu
.etaxonomy
.cdm
.model
.location
.Point
;
52 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationType
;
53 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IDefinedTermDao
;
56 * Class implementing the business logic for creating the map service string for
57 * a given set of distributions. See {@link EditGeoService} as API for the given functionality.
64 public class EditGeoServiceUtilities
{
65 private static final Logger logger
= Logger
.getLogger(EditGeoServiceUtilities
.class);
67 private static final int INT_MAX_LENGTH
= String
.valueOf(Integer
.MAX_VALUE
).length();
69 private static PresenceAbsenceTerm defaultStatus
= PresenceAbsenceTerm
.PRESENT();
71 private static IDefinedTermDao termDao
;
78 public static void setTermDao(IDefinedTermDao termDao
) {
79 EditGeoServiceUtilities
.termDao
= termDao
;
83 private static HashMap
<SpecimenOrObservationType
, Color
> defaultSpecimenOrObservationTypeColors
= null;
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
);
95 return defaultSpecimenOrObservationTypeColors
;
99 private static HashMap
<PresenceAbsenceTerm
, Color
> defaultPresenceAbsenceTermBaseColors
= null;
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
.INTRODUCED_ADVENTITIOUS(), Color
.decode("0xffff33"));
110 defaultPresenceAbsenceTermBaseColors
.put(PresenceAbsenceTerm
.INTRODUCED_CULTIVATED(), Color
.decode("0xa65628"));
111 defaultPresenceAbsenceTermBaseColors
.put(PresenceAbsenceTerm
.INTRODUCED_NATURALIZED(), Color
.decode("0xf781bf"));
113 return defaultPresenceAbsenceTermBaseColors
;
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
= "||";
126 * Returns the parameter String for the EDIT geo webservice to create a
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 :
135 * Collection<Distribution> filteredDistributions =
136 * DescriptionUtility.filterDistributions(distributions,
137 * subAreaPreference, statusOrderPreference, hideMarkedAreas);
140 * Data regarding the mapping of NamedAreas to shape file
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.
154 * @return the parameter string or an empty string if the
155 * <code>distributions</code> set was null or empty.
158 public static String
getDistributionServiceRequestParameterString(
159 Collection
<Distribution
> filteredDistributions
,
160 IGeoServiceAreaMapping mapping
,
161 Map
<PresenceAbsenceTerm
,Color
> presenceAbsenceTermColors
,
162 String projectToLayer
,
163 List
<Language
> languages
){
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
171 boolean generateMultipleAreaDataParameters
= false;
174 * doNotReuseStyles is a workaround for a problem in the EDIT MapService,
175 * see https://dev.e-taxonomy.eu/trac/ticket/2707#comment:24
177 * a.kohlbecker 2014-07-02 :This bug in the map service has been
178 * fixed now so reusing styles is now possible setting this flag to false.
180 boolean doNotReuseStyles
= false;
182 List
<String
> perLayerAreaData
= new ArrayList
<String
>();
183 Map
<Integer
, String
> areaStyles
= new HashMap
<Integer
, String
>();
184 List
<String
> legendSortList
= new ArrayList
<String
>();
186 String borderWidth
= "0.1";
187 String borderColorRgb
= "";
188 String borderDashingPattern
= "";
192 if(filteredDistributions
== null || filteredDistributions
.size() == 0){
196 presenceAbsenceTermColors
= mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors
);
198 Map
<String
, Map
<Integer
, Set
<Distribution
>>> layerMap
= new HashMap
<String
, Map
<Integer
, Set
<Distribution
>>>();
199 List
<PresenceAbsenceTerm
> statusList
= new ArrayList
<PresenceAbsenceTerm
>();
201 groupStylesAndLayers(filteredDistributions
, layerMap
, statusList
, mapping
);
203 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
206 int styleCounter
= 0;
207 for (PresenceAbsenceTerm status
: statusList
){
209 char styleCode
= getStyleAbbrev(styleCounter
);
211 //getting the area title
212 if (languages
== null){
213 languages
= new ArrayList
<Language
>();
215 if (languages
.size() == 0){
216 languages
.add(Language
.DEFAULT());
218 Representation statusRepresentation
= status
.getPreferredRepresentation(languages
);
220 //getting the area color
221 Color statusColor
= presenceAbsenceTermColors
.get(status
);
223 if (statusColor
!= null){
224 fillColorRgb
= Integer
.toHexString(statusColor
.getRGB()).substring(2);
227 fillColorRgb
= status
.getDefaultColor(); //TODO
229 fillColorRgb
= defaultStatus
.getDefaultColor();
232 String styleValues
= StringUtils
.join(new String
[]{fillColorRgb
, borderColorRgb
, borderWidth
, borderDashingPattern
}, ',');
234 areaStyles
.put(styleCounter
, styleValues
);
236 String legendEntry
= styleCode
+ ID_FROM_VALUES_SEPARATOR
+ encode(statusRepresentation
.getLabel());
237 legendSortList
.add(StringUtils
.leftPad(String
.valueOf(status
.getOrderIndex()), INT_MAX_LENGTH
, '0') + legendEntry
);
242 List
<String
> styledAreasPerLayer
;
243 List
<String
> areasPerStyle
;
245 * Map<Integer, Integer> styleUsage
247 * Used to avoid reusing styles in multiple layers
250 * value: the count of how often the style has been used for different layers, starts with 0 for first time use
252 Map
<Integer
, Integer
> styleUsage
= new HashMap
<Integer
, Integer
>();
255 for (String layerString
: layerMap
.keySet()){
257 styledAreasPerLayer
= new ArrayList
<String
>();
258 Map
<Integer
, Set
<Distribution
>> styleMap
= layerMap
.get(layerString
);
259 for (int style
: styleMap
.keySet()){
261 if(doNotReuseStyles
) {
262 if(!styleUsage
.containsKey(style
)){
263 styleUsage
.put(style
, 0);
266 styleUsage
.put(style
, styleUsage
.get(style
) + 1);
268 Integer styleIncrement
= styleUsage
.get(style
);
269 if(styleIncrement
> 0){
270 // style code has been used before!
271 styleChar
= getStyleAbbrev(style
+ styleIncrement
+ styleCounter
);
272 //for debugging sometimes failing test #3831
273 logger
.warn("style: " + style
+ ", styleIncrement: " + styleIncrement
+ ", styleCounter: " + styleCounter
);
274 areaStyles
.put(style
+ styleIncrement
+ styleCounter
, areaStyles
.get(style
));
276 styleChar
= getStyleAbbrev(style
);
279 styleChar
= getStyleAbbrev(style
);
281 Set
<Distribution
> distributionSet
= styleMap
.get(style
);
282 areasPerStyle
= new ArrayList
<String
>();
283 for (Distribution distribution
: distributionSet
){
285 areasPerStyle
.add(encode(getAreaCode(distribution
, mapping
)));
287 styledAreasPerLayer
.add(styleChar
+ ID_FROM_VALUES_SEPARATOR
+ StringUtils
.join(areasPerStyle
.iterator(), SUBENTRY_DELIMITER
));
289 perLayerAreaData
.add(encode(layerString
) + ID_FROM_VALUES_SEPARATOR
+ StringUtils
.join(styledAreasPerLayer
.iterator(), VALUE_LIST_ENTRY_SEPARATOR
));
292 if(areaStyles
.size() > 0){
293 ArrayList
<Integer
> styleIds
= new ArrayList
<Integer
>(areaStyles
.size());
294 styleIds
.addAll(areaStyles
.keySet());
295 Collections
.sort(styleIds
); // why is it necessary to sort here?
296 StringBuilder db
= new StringBuilder();
297 for(Integer sid
: styleIds
){
299 db
.append(VALUE_LIST_ENTRY_SEPARATOR
);
301 db
.append( getStyleAbbrev(sid
)).append(ID_FROM_VALUES_SEPARATOR
).append(areaStyles
.get(sid
));
303 parameters
.put("as", db
.toString());
305 if(legendSortList
.size() > 0){
306 // sort the label entries after the status terms
307 Collections
.sort(legendSortList
);
308 // since the status terms are have an inverse natural order
309 // (as all other ordered term, see OrderedTermBase.performCompareTo(T orderedTerm, boolean skipVocabularyCheck)
310 // the sorted list must be reverted
311 // Collections.reverse(legendSortList);
312 // remove the prepended order index (like 000000000000001 ) from the legend entries
313 @SuppressWarnings("unchecked")
314 Collection
<String
> legendEntries
= CollectionUtils
.collect(legendSortList
, new Transformer()
317 public String
transform(Object o
)
319 String s
= ((String
) o
);
320 return s
.substring(INT_MAX_LENGTH
, s
.length());
324 parameters
.put("title", StringUtils
.join(legendEntries
.iterator(), VALUE_LIST_ENTRY_SEPARATOR
));
327 if(generateMultipleAreaDataParameters
){
328 // not generically possible since parameters can not contain duplicate keys with value "ad"
330 parameters
.put("ad", StringUtils
.join(perLayerAreaData
.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR
));
333 String queryString
= makeQueryString(parameters
);
334 logger
.debug("getDistributionServiceRequestParameterString(): " + queryString
);
341 * Fills the layerMap and the statusList
343 * @param distributions
344 * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
347 private static void groupStylesAndLayers(Collection
<Distribution
> distributions
,
348 Map
<String
, Map
<Integer
,Set
<Distribution
>>> layerMap
,
349 List
<PresenceAbsenceTerm
> statusList
,
350 IGeoServiceAreaMapping mapping
) {
353 //iterate through distributions and group styles and layers
354 //and collect necessary information
355 for (Distribution distribution
: distributions
){
357 PresenceAbsenceTerm status
= distribution
.getStatus();
359 status
= defaultStatus
;
361 if (! statusList
.contains(status
)){
362 statusList
.add(status
);
364 //group areas by layers and styles
365 NamedArea area
= distribution
.getArea();
367 addAreaToLayerMap(layerMap
, statusList
, distribution
, area
, mapping
);
372 * Adds the areas to the layer map. Areas which do not have layer information
373 * mapped to them are ignored.
375 * A layer map holds the following information:
378 * <li><b>String</b>: the WMSLayerName which matches the level of the
379 * contained distributions areas</li>
380 * <li><b>StyleMap</b>:</li>
382 * <li><b>Integer</b>: the index of the status in the
383 * <code>statusList</code></li>
384 * <li><b>Set{@code<Distribution>}</b>: the set of distributions having the
385 * same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>
391 * @param distribution
394 private static void addAreaToLayerMap(Map
<String
, Map
<Integer
,
395 Set
<Distribution
>>> layerMap
,
396 List
<PresenceAbsenceTerm
> statusList
,
397 Distribution distribution
,
399 IGeoServiceAreaMapping mapping
) {
402 String geoLayerName
= getWMSLayerName(area
, mapping
);
404 if(geoLayerName
== null){
405 /* IGNORE areas for which no layer is mapped */
407 Map
<Integer
, Set
<Distribution
>> styleMap
= layerMap
.get(geoLayerName
);
408 if (styleMap
== null) {
409 styleMap
= new HashMap
<Integer
, Set
<Distribution
>>();
410 layerMap
.put(geoLayerName
, styleMap
);
412 addDistributionToStyleMap(distribution
, styleMap
, statusList
);
420 * URI encode the given String
424 private static String
encode(String string
) {
425 String encoded
= string
;
427 encoded
= URLEncoder
.encode(string
, "UTF-8");
428 } catch (UnsupportedEncodingException e
) {
435 * combine parameter into a URI query string fragment. The values will be
439 * @return a URI query string fragment
441 private static String
makeQueryString(Map
<String
, String
> parameters
){
442 StringBuilder queryString
= new StringBuilder();
443 for (String key
: parameters
.keySet()) {
444 if(queryString
.length() > 0){
445 queryString
.append('&');
447 if(key
.equals("od") || key
.equals("os") || key
.equals("ms") || key
.equals("ad") || key
.equals("as") || key
.equals("title") || key
.equals("bbox")){
448 queryString
.append(key
).append('=').append(parameters
.get(key
));
450 queryString
.append(key
).append('=').append(encode(parameters
.get(key
)));
453 return queryString
.toString();
456 private static String
getAreaCode(Distribution distribution
, IGeoServiceAreaMapping mapping
){
457 NamedArea area
= distribution
.getArea();
458 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
459 String result
= null;
461 if (voc
!= null && voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
) || voc
.getUuid().equals(Country
.uuidCountryVocabulary
)) {
463 result
= area
.getIdInVocabulary();
464 if (area
.getLevel() != null && area
.getLevel().equals(NamedAreaLevel
.TDWG_LEVEL4())) {
465 result
= result
.replace("-", "");
468 // use generic GeoServiceArea data stored in technical annotations
471 GeoServiceArea areas
= mapping
.valueOf(area
);
472 if ((areas
!= null) && areas
.size() > 0) {
473 // FIXME multiple layers
474 List
<String
> values
= areas
.getAreasMap().values().iterator().next().values().iterator().next();
475 for (String value
: values
) {
476 result
= CdmUtils
.concat(SUBENTRY_DELIMITER
, result
, value
);
481 return CdmUtils
.Nz(result
, "-");
485 private static List
<String
> projectToWMSSubLayer(NamedArea area
){
487 List
<String
> layerNames
= new ArrayList
<String
>();
488 String matchedLayerName
= null;
489 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
491 if (voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
)){
492 NamedAreaLevel level
= area
.getLevel();
494 //TODO integrate into CDM
495 if (level
.equals(NamedAreaLevel
.TDWG_LEVEL1())) {
496 matchedLayerName
= "tdwg1" ;
497 } else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL2())) {
498 matchedLayerName
= "tdwg2";
499 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL3())) {
500 matchedLayerName
= "tdwg3";
501 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL4())) {
502 matchedLayerName
= "tdwg4";
505 //unrecognized tdwg area
510 // check if the matched layer equals the layer to project to
511 // if not: recurse into the sub-level in order to find the specified one.
512 String
[] matchedLayerNameTokens
= StringUtils
.split(matchedLayerName
, ':');
513 // if(matchedLayerNameTokens.length > 0 && matchedLayerNameTokens[0] != projectToLayer){
514 // for (NamedArea subArea : area.getIncludes()){
524 private static String
getWMSLayerName(NamedArea area
, IGeoServiceAreaMapping mapping
){
525 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
527 if (voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
)){
528 NamedAreaLevel level
= area
.getLevel();
530 //TODO integrate into CDM
531 if (level
.equals(NamedAreaLevel
.TDWG_LEVEL1())) {
533 } else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL2())) {
535 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL3())) {
537 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL4())) {
541 //unrecognized tdwg area
544 }else if (voc
.getUuid().equals(Country
.uuidCountryVocabulary
)){
545 return "country_earth:gmi_cntry";
548 GeoServiceArea areas
= mapping
.valueOf(area
);
549 if (areas
!= null && areas
.getAreasMap().size() > 0){
550 //FIXME multiple layers
551 String layer
= areas
.getAreasMap().keySet().iterator().next();
552 Map
<String
, List
<String
>> fields
= areas
.getAreasMap().get(layer
);
553 String field
= fields
.keySet().iterator().next();
554 String layerString
= layer
+ ":" + field
;
555 return layerString
.toLowerCase();
562 private static void addDistributionToStyleMap(Distribution distribution
, Map
<Integer
, Set
<Distribution
>> styleMap
,
563 List
<PresenceAbsenceTerm
> statusList
) {
564 PresenceAbsenceTerm status
= distribution
.getStatus();
565 if (status
== null) {
566 status
= defaultStatus
;
568 int style
= statusList
.indexOf(status
);
569 Set
<Distribution
> distributionSet
= styleMap
.get(style
);
570 if (distributionSet
== null) {
571 distributionSet
= new HashSet
<Distribution
>();
572 styleMap
.put(style
, distributionSet
);
574 distributionSet
.add(distribution
);
578 * @param fieldUnitPoints
579 * @param derivedUnitPoints
580 * @param specimenOrObservationTypeColors
593 * &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
594 * &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
596 public static OccurrenceServiceRequestParameterDto
getOccurrenceServiceRequestParameterString(
597 List
<Point
> fieldUnitPoints
,
598 List
<Point
> derivedUnitPoints
,
599 Map
<SpecimenOrObservationType
, Color
> specimenOrObservationTypeColors
) {
600 OccurrenceServiceRequestParameterDto dto
= new OccurrenceServiceRequestParameterDto();
603 specimenOrObservationTypeColors
= mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors
);
605 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
606 parameters
.put("legend", "0");
608 Map
<String
, String
> styleAndData
= new HashMap
<String
, String
>();
610 addToStyleAndData(fieldUnitPoints
, SpecimenOrObservationType
.FieldUnit
, specimenOrObservationTypeColors
, styleAndData
);
611 addToStyleAndData(derivedUnitPoints
, SpecimenOrObservationType
.DerivedUnit
, specimenOrObservationTypeColors
, styleAndData
);
613 parameters
.put("os", StringUtils
.join(styleAndData
.keySet().iterator(), "||"));
614 parameters
.put("od", StringUtils
.join(styleAndData
.values().iterator(), "||"));
616 String queryString
= makeQueryString(parameters
);
618 dto
.setFieldUnitPoints(fieldUnitPoints
);
619 dto
.setDerivedUnitPoints(derivedUnitPoints
);
620 dto
.setOccurrenceQuery(queryString
);
622 logger
.info(queryString
);
634 private static <T
, S
> Map
<T
, S
> mergeMaps(Map
<T
, S
> defaultMap
, Map
<T
, S
> overrideMap
) {
635 Map
<T
, S
> tmpMap
= new HashMap
<T
, S
>();
636 tmpMap
.putAll(defaultMap
);
637 if(overrideMap
!= null){
638 tmpMap
.putAll(overrideMap
);
643 private static void addToStyleAndData(
645 SpecimenOrObservationType specimenOrObservationType
,
646 Map
<SpecimenOrObservationType
, Color
> specimenOrObservationTypeColors
, Map
<String
, String
> styleAndData
) {
648 //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>
650 if(points
!= null && points
.size()>0){
651 String style
= "c/" + Integer
.toHexString(specimenOrObservationTypeColors
.get(specimenOrObservationType
).getRGB()).substring(2) + "/10/noLabel";
652 StringBuilder data
= new StringBuilder();
653 for(Point point
: points
){
654 if(data
.length() > 0){
657 data
.append(point
.getLatitude() + "," + point
.getLongitude());
659 int index
= styleAndData
.size() + 1;
660 styleAndData
.put(index
+ ":" +style
, index
+ ":" +data
.toString());
666 * transform an integer (style counter) into a valid character representing a style.
669 * i not in {0,...,51} is undefined
673 private static char getStyleAbbrev(int i
){
683 * @param statusColorJson for example: {@code {"n":"#ff0000","p":"#ffff00"}}
685 * @throws IOException
686 * @throws JsonParseException
687 * @throws JsonMappingException
689 public static Map
<PresenceAbsenceTerm
, Color
> buildStatusColorMap(String statusColorJson
, ITermService termService
) throws IOException
, JsonParseException
,
690 JsonMappingException
{
692 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
= null;
693 if(StringUtils
.isNotEmpty(statusColorJson
)){
695 ObjectMapper mapper
= new ObjectMapper();
696 // TODO cache the color maps to speed this up?
698 TypeFactory typeFactory
= mapper
.getTypeFactory();
699 MapType mapType
= typeFactory
.constructMapType(HashMap
.class, String
.class, String
.class);
701 Map
<String
,String
> statusColorMap
= mapper
.readValue(statusColorJson
, mapType
);
702 UUID presenceTermVocabUuid
= PresenceAbsenceTerm
.NATIVE().getVocabulary().getUuid();
703 presenceAbsenceTermColors
= new HashMap
<PresenceAbsenceTerm
, Color
>();
704 PresenceAbsenceTerm paTerm
= null;
705 for(String statusId
: statusColorMap
.keySet()){
707 Color color
= Color
.decode(statusColorMap
.get(statusId
));
708 paTerm
= termService
.findByIdInVocabulary(statusId
, presenceTermVocabUuid
, PresenceAbsenceTerm
.class);
710 presenceAbsenceTermColors
.put(paTerm
, color
);
712 } catch (NumberFormatException e
){
713 logger
.error("Cannot decode color", e
);
717 return presenceAbsenceTermColors
;
722 * @param filteredDistributions
724 * @param hideMarkedAreas
728 public static CondensedDistribution
getCondensedDistribution(Collection
<Distribution
> filteredDistributions
,
729 CondensedDistributionRecipe recipe
, List
<Language
> langs
) {
730 ICondensedDistributionComposer composer
;
732 throw new NullPointerException("parameter recipe must not be null");
735 composer
= recipe
.newCondensedDistributionComposerInstance();
736 } catch (InstantiationException e
) {
737 throw new RuntimeException(e
);
738 } catch (IllegalAccessException e
) {
739 throw new RuntimeException(e
);
741 CondensedDistribution condensedDistribution
= composer
.createCondensedDistribution(
742 filteredDistributions
, langs
);
743 return condensedDistribution
;