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
.utility
.DescriptionUtility
;
41 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
42 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
43 import eu
.etaxonomy
.cdm
.model
.common
.Marker
;
44 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
45 import eu
.etaxonomy
.cdm
.model
.common
.Representation
;
46 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
47 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
48 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
49 import eu
.etaxonomy
.cdm
.model
.location
.Country
;
50 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
51 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaLevel
;
52 import eu
.etaxonomy
.cdm
.model
.location
.Point
;
53 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationType
;
54 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IDefinedTermDao
;
57 * Class implementing the business logic for creating the map service string for
58 * a given set of distributions. See {@link EditGeoService} as API for the given functionality.
65 public class EditGeoServiceUtilities
{
66 private static final int INT_MAX_LENGTH
= String
.valueOf(Integer
.MAX_VALUE
).length();
68 private static final Logger logger
= Logger
.getLogger(EditGeoServiceUtilities
.class);
70 private static PresenceAbsenceTerm defaultStatus
= PresenceAbsenceTerm
.PRESENT();
72 private static IDefinedTermDao termDao
;
79 public static void setTermDao(IDefinedTermDao termDao
) {
80 EditGeoServiceUtilities
.termDao
= termDao
;
84 private static HashMap
<SpecimenOrObservationType
, Color
> defaultSpecimenOrObservationTypeColors
= null;
86 private static HashMap
<SpecimenOrObservationType
, Color
> getDefaultSpecimenOrObservationTypeColors() {
87 if(defaultSpecimenOrObservationTypeColors
== null){
88 defaultSpecimenOrObservationTypeColors
= new HashMap
<SpecimenOrObservationType
, Color
>();
89 defaultSpecimenOrObservationTypeColors
.put(SpecimenOrObservationType
.FieldUnit
, Color
.ORANGE
);
90 defaultSpecimenOrObservationTypeColors
.put(SpecimenOrObservationType
.DerivedUnit
, Color
.RED
);
91 defaultSpecimenOrObservationTypeColors
.put(SpecimenOrObservationType
.LivingSpecimen
, Color
.GREEN
);
92 defaultSpecimenOrObservationTypeColors
.put(SpecimenOrObservationType
.Observation
, Color
.ORANGE
);
93 defaultSpecimenOrObservationTypeColors
.put(SpecimenOrObservationType
.PreservedSpecimen
, Color
.GRAY
);
94 defaultSpecimenOrObservationTypeColors
.put(SpecimenOrObservationType
.Media
, Color
.BLUE
);
96 return defaultSpecimenOrObservationTypeColors
;
100 private static HashMap
<PresenceAbsenceTerm
, Color
> defaultPresenceAbsenceTermBaseColors
= null;
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
.INTRODUCED_ADVENTITIOUS(), Color
.decode("0xffff33"));
111 defaultPresenceAbsenceTermBaseColors
.put(PresenceAbsenceTerm
.INTRODUCED_CULTIVATED(), Color
.decode("0xa65628"));
112 defaultPresenceAbsenceTermBaseColors
.put(PresenceAbsenceTerm
.INTRODUCED_NATURALIZED(), Color
.decode("0xf781bf"));
115 * and now something very hacky ...
116 * ONLY-A-TEST is set by the Test class EditGeoServiceTest
118 * FIXME remove according line from
119 * EditGeoServiceTest.setUp() since the hardcoded colors for flora of
120 * cyprus should no longer be needed : #4268 (allow defining custom presence and absence term colors for EditGeoServiceUtilities)
122 String onlyTest
= System
.getProperty("ONLY-A-TEST"); //
123 if(onlyTest
!= null && onlyTest
.equals("TRUE")){
124 return defaultPresenceAbsenceTermBaseColors
;
126 //special colors for flora of cyprus !!! see HACK above !!!
127 UUID indigenousUuid
= UUID
.fromString("b325859b-504b-45e0-9ef0-d5c1602fcc0f");
128 UUID indigenousQUuid
= UUID
.fromString("17bc601f-53eb-4997-a4bc-c03ce5bfd1d3");
130 UUID cultivatedQUuid
= UUID
.fromString("4f31bfc8-3058-4d83-aea5-3a1fe9773f9f");
132 UUID casualUuid
= UUID
.fromString("5e81353c-38a3-4ca6-b979-0d9abc93b877");
133 UUID casualQUuid
= UUID
.fromString("73f75493-1185-4a3e-af1e-9a1f2e8dadb7");
135 UUID naturalizedNonInvasiveUuid
= UUID
.fromString("1b025e8b-901a-42e8-9739-119b410c6f03");
136 UUID naturalizedNonInvasiveQUuid
= UUID
.fromString("11f56e2f-c16c-4b3d-a870-bb5d3b20e624");
138 UUID naturalizedInvasiveUuid
= UUID
.fromString("faf2d271-868a-4bf7-b0b8-a1c5ab309de2");
139 UUID naturalizedInvasiveQUuid
= UUID
.fromString("ac429d5f-e8ad-49ae-a41c-e4779b58b96a");
141 UUID questionablelUuid
= UUID
.fromString("4b48f675-a6cf-49f3-a5ba-77e2c2979eb3");
142 UUID questionableQUuid
= UUID
.fromString("914e7393-1314-4632-bc45-5eff3dc1e424");
144 UUID reportedInErrorUuid
= UUID
.fromString("38604788-cf05-4607-b155-86db456f7680");
146 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(indigenousUuid
), Color
.decode("0x339966"));
147 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(indigenousQUuid
), Color
.decode("0x339966"));
149 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(cultivatedQUuid
), Color
.decode("0xbdb76b"));
151 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(casualUuid
), Color
.decode("0xffff00"));
152 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(casualQUuid
), Color
.decode("0xffff00"));
154 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedNonInvasiveUuid
), Color
.decode("0xff9900"));
155 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedNonInvasiveQUuid
), Color
.decode("0xff9900"));
157 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedInvasiveUuid
), Color
.decode("0xff0000"));
158 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedInvasiveQUuid
), Color
.decode("0xff0000"));
160 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(questionablelUuid
), Color
.decode("0x00ccff"));
161 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(questionableQUuid
), Color
.decode("0x00ccff"));
163 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(reportedInErrorUuid
), Color
.decode("0xcccccc"));
166 return defaultPresenceAbsenceTermBaseColors
;
171 private static final String SUBENTRY_DELIMITER
= ",";
172 private static final String ENTRY_DELIMITER
= ";";
173 static final String ID_FROM_VALUES_SEPARATOR
= ":";
174 static final String VALUE_LIST_ENTRY_SEPARATOR
= "|";
175 static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR
= "||";
179 //preliminary implementation for TDWG areas
181 * Returns the parameter String for the EDIT geo webservice to create a
184 * @param distributions
185 * A set of distributions that should be shown on the map
186 * @param subAreaPreference
187 * enables the <b>Sub area preference rule</b> if set to true,
188 * see {@link DescriptionUtility#filterDistributions(Collection,
191 * @param statusOrderPreference
192 * enables the <b>Status order preference rule</b> if set to
194 * DescriptionUtility#filterDistributions(Collection, boolean,
196 * @param hideMarkedAreas
197 * distributions where the area has a {@link Marker} with one of
198 * the specified {@link MarkerType}s will be skipped, see
199 * {@link DescriptionUtility#filterDistributions(Collection, boolean, boolean, Set)}
200 * @param presenceAbsenceTermColors
201 * A map that defines the colors of PresenceAbsenceTerms. The
202 * PresenceAbsenceTerms are defined by their uuid. If a
203 * PresenceAbsenceTerm is not included in this map, it's default
204 * color is taken instead. If the map == null all terms are
205 * colored by their default color.
211 * The maps bounding box (e.g. "-180,-90,180,90" for the whole
213 * @param projectToLayer
214 * name of a layer which is representing a specific
215 * {@link NamedAreaLevel} Supply this parameter if you to project
216 * all other distribution area levels to this layer.
218 * The layer that is responsible for background borders and
219 * colors. Use the name for the layer. If null 'earth' is taken
221 * @return the parameter string or an empty string if the
222 * <code>distributions</code> set was null or empty.
225 public static String
getDistributionServiceRequestParameterString(
226 Set
<Distribution
> distributions
,
227 boolean subAreaPreference
,
228 boolean statusOrderPreference
,
229 Set
<MarkerType
> hideMarkedAreas
,
230 IGeoServiceAreaMapping mapping
,
231 Map
<PresenceAbsenceTerm
,Color
> presenceAbsenceTermColors
,
232 String projectToLayer
, List
<Language
> languages
){
236 * generateMultipleAreaDataParameters switches between the two possible styles:
237 * 1. ad=layername1:area-data||layername2:area-data
238 * 2. ad=layername1:area-data&ad=layername2:area-data
240 boolean generateMultipleAreaDataParameters
= false;
243 * doNotReuseStyles is a workaround for a problem in the EDIT MapService,
244 * see https://dev.e-taxonomy.eu/trac/ticket/2707#comment:24
246 * a.kohlbecker 2014-07-02 :This bug in the map service has been
247 * fixed now so reusing styles is now possible setting this flag to false.
249 boolean doNotReuseStyles
= false;
251 List
<String
> perLayerAreaData
= new ArrayList
<String
>();
252 Map
<Integer
, String
> areaStyles
= new HashMap
<Integer
, String
>();
253 List
<String
> legendSortList
= new ArrayList
<String
>();
255 String borderWidth
= "0.1";
256 String borderColorRgb
= "";
257 String borderDashingPattern
= "";
261 if(distributions
== null || distributions
.size() == 0){
265 presenceAbsenceTermColors
= mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors
);
267 Collection
<Distribution
> filteredDistributions
= DescriptionUtility
.filterDistributions(distributions
, subAreaPreference
, statusOrderPreference
, hideMarkedAreas
);
269 Map
<String
, Map
<Integer
, Set
<Distribution
>>> layerMap
= new HashMap
<String
, Map
<Integer
, Set
<Distribution
>>>();
270 List
<PresenceAbsenceTerm
> statusList
= new ArrayList
<PresenceAbsenceTerm
>();
272 groupStylesAndLayers(filteredDistributions
, layerMap
, statusList
, mapping
);
274 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
277 int styleCounter
= 0;
278 for (PresenceAbsenceTerm status
: statusList
){
280 char styleCode
= getStyleAbbrev(styleCounter
);
282 //getting the area title
283 if (languages
== null){
284 languages
= new ArrayList
<Language
>();
286 if (languages
.size() == 0){
287 languages
.add(Language
.DEFAULT());
289 Representation statusRepresentation
= status
.getPreferredRepresentation(languages
);
290 String statusLabel
= statusRepresentation
.getLabel();
291 //statusLabel.replace('introduced: ', '');
292 statusLabel
= statusLabel
.replace("introduced: ", "introduced, ");
293 statusLabel
= statusLabel
.replace("native: ", "native, ");
295 //getting the area color
296 Color statusColor
= presenceAbsenceTermColors
.get(status
);
298 if (statusColor
!= null){
299 fillColorRgb
= Integer
.toHexString(statusColor
.getRGB()).substring(2);
302 fillColorRgb
= status
.getDefaultColor(); //TODO
304 fillColorRgb
= defaultStatus
.getDefaultColor();
307 String styleValues
= StringUtils
.join(new String
[]{fillColorRgb
, borderColorRgb
, borderWidth
, borderDashingPattern
}, ',');
309 areaStyles
.put(styleCounter
, styleValues
);
311 String legendEntry
= styleCode
+ ID_FROM_VALUES_SEPARATOR
+ encode(statusLabel
);
312 legendSortList
.add(StringUtils
.leftPad(String
.valueOf(status
.getOrderIndex()), INT_MAX_LENGTH
, '0') + legendEntry
);
317 List
<String
> styledAreasPerLayer
;
318 List
<String
> areasPerStyle
;
320 * Map<Integer, Integer> styleUsage
322 * Used to avoid reusing styles in multiple layers
325 * value: the count of how often the style has been used for different layers, starts with 0 for first time use
327 Map
<Integer
, Integer
> styleUsage
= new HashMap
<Integer
, Integer
>();
330 for (String layerString
: layerMap
.keySet()){
332 styledAreasPerLayer
= new ArrayList
<String
>();
333 Map
<Integer
, Set
<Distribution
>> styleMap
= layerMap
.get(layerString
);
334 for (int style
: styleMap
.keySet()){
336 if(doNotReuseStyles
) {
337 if(!styleUsage
.containsKey(style
)){
338 styleUsage
.put(style
, 0);
341 styleUsage
.put(style
, styleUsage
.get(style
) + 1);
343 Integer styleIncrement
= styleUsage
.get(style
);
344 if(styleIncrement
> 0){
345 // style code has been used before!
346 styleChar
= getStyleAbbrev(style
+ styleIncrement
+ styleCounter
);
347 //for debugging sometimes failing test #3831
348 logger
.warn("style: " + style
+ ", styleIncrement: " + styleIncrement
+ ", styleCounter: " + styleCounter
);
349 areaStyles
.put(style
+ styleIncrement
+ styleCounter
, areaStyles
.get(style
));
351 styleChar
= getStyleAbbrev(style
);
354 styleChar
= getStyleAbbrev(style
);
356 Set
<Distribution
> distributionSet
= styleMap
.get(style
);
357 areasPerStyle
= new ArrayList
<String
>();
358 for (Distribution distribution
: distributionSet
){
360 areasPerStyle
.add(encode(getAreaCode(distribution
, mapping
)));
362 styledAreasPerLayer
.add(styleChar
+ ID_FROM_VALUES_SEPARATOR
+ StringUtils
.join(areasPerStyle
.iterator(), SUBENTRY_DELIMITER
));
364 perLayerAreaData
.add(encode(layerString
) + ID_FROM_VALUES_SEPARATOR
+ StringUtils
.join(styledAreasPerLayer
.iterator(), VALUE_LIST_ENTRY_SEPARATOR
));
367 if(areaStyles
.size() > 0){
368 ArrayList
<Integer
> styleIds
= new ArrayList
<Integer
>(areaStyles
.size());
369 styleIds
.addAll(areaStyles
.keySet());
370 Collections
.sort(styleIds
); // why is it necessary to sort here?
371 StringBuilder db
= new StringBuilder();
372 for(Integer sid
: styleIds
){
374 db
.append(VALUE_LIST_ENTRY_SEPARATOR
);
376 db
.append( getStyleAbbrev(sid
)).append(ID_FROM_VALUES_SEPARATOR
).append(areaStyles
.get(sid
));
378 parameters
.put("as", db
.toString());
380 if(legendSortList
.size() > 0){
381 // sort the label entries after the status terms
382 Collections
.sort(legendSortList
);
383 // since the status terms are have an inverse natural order
384 // (as all other ordered term, see OrderedTermBase.performCompareTo(T orderedTerm, boolean skipVocabularyCheck)
385 // the sorted list must be reverted
386 // Collections.reverse(legendSortList);
387 // remove the prepended order index (like 000000000000001 ) from the legend entries
388 @SuppressWarnings("unchecked")
389 Collection
<String
> legendEntries
= CollectionUtils
.collect(legendSortList
, new Transformer()
392 public String
transform(Object o
)
394 String s
= ((String
) o
);
395 return s
.substring(INT_MAX_LENGTH
, s
.length());
399 parameters
.put("title", StringUtils
.join(legendEntries
.iterator(), VALUE_LIST_ENTRY_SEPARATOR
));
402 if(generateMultipleAreaDataParameters
){
403 // not generically possible since parameters can not contain duplicate keys with value "ad"
405 parameters
.put("ad", StringUtils
.join(perLayerAreaData
.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR
));
408 String queryString
= makeQueryString(parameters
);
409 logger
.debug("getDistributionServiceRequestParameterString(): " + queryString
);
416 * Fills the layerMap and the statusList
418 * @param distributions
419 * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
422 private static void groupStylesAndLayers(Collection
<Distribution
> distributions
,
423 Map
<String
, Map
<Integer
,Set
<Distribution
>>> layerMap
,
424 List
<PresenceAbsenceTerm
> statusList
,
425 IGeoServiceAreaMapping mapping
) {
428 //iterate through distributions and group styles and layers
429 //and collect necessary information
430 for (Distribution distribution
: distributions
){
432 PresenceAbsenceTerm status
= distribution
.getStatus();
434 status
= defaultStatus
;
436 if (! statusList
.contains(status
)){
437 statusList
.add(status
);
439 //group areas by layers and styles
440 NamedArea area
= distribution
.getArea();
442 addAreaToLayerMap(layerMap
, statusList
, distribution
, area
, mapping
);
447 * Adds the areas to the layer map. Areas which do not have layer information
448 * mapped to them are ignored.
450 * A layer map holds the following information:
453 * <li><b>String</b>: the WMSLayerName which matches the level of the
454 * contained distributions areas</li>
455 * <li><b>StyleMap</b>:</li>
457 * <li><b>Integer</b>: the index of the status in the
458 * <code>statusList</code></li>
459 * <li><b>Set{@code<Distribution>}</b>: the set of distributions having the
460 * same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>
466 * @param distribution
469 private static void addAreaToLayerMap(Map
<String
, Map
<Integer
,
470 Set
<Distribution
>>> layerMap
,
471 List
<PresenceAbsenceTerm
> statusList
,
472 Distribution distribution
,
474 IGeoServiceAreaMapping mapping
) {
477 String geoLayerName
= getWMSLayerName(area
, mapping
);
479 if(geoLayerName
== null){
480 /* IGNORE areas for which no layer is mapped */
482 Map
<Integer
, Set
<Distribution
>> styleMap
= layerMap
.get(geoLayerName
);
483 if (styleMap
== null) {
484 styleMap
= new HashMap
<Integer
, Set
<Distribution
>>();
485 layerMap
.put(geoLayerName
, styleMap
);
487 addDistributionToStyleMap(distribution
, styleMap
, statusList
);
495 * URI encode the given String
499 private static String
encode(String string
) {
500 String encoded
= string
;
502 encoded
= URLEncoder
.encode(string
, "UTF-8");
503 } catch (UnsupportedEncodingException e
) {
510 * combine parameter into a URI query string fragment. The values will be
514 * @return a URI query string fragment
516 private static String
makeQueryString(Map
<String
, String
> parameters
){
517 StringBuilder queryString
= new StringBuilder();
518 for (String key
: parameters
.keySet()) {
519 if(queryString
.length() > 0){
520 queryString
.append('&');
522 if(key
.equals("od") || key
.equals("os") || key
.equals("ms") || key
.equals("ad") || key
.equals("as") || key
.equals("title") || key
.equals("bbox")){
523 queryString
.append(key
).append('=').append(parameters
.get(key
));
525 queryString
.append(key
).append('=').append(encode(parameters
.get(key
)));
528 return queryString
.toString();
531 private static String
getAreaCode(Distribution distribution
, IGeoServiceAreaMapping mapping
){
532 NamedArea area
= distribution
.getArea();
533 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
534 String result
= null;
536 if (voc
!= null && voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
) || voc
.getUuid().equals(Country
.uuidCountryVocabulary
)) {
538 result
= area
.getIdInVocabulary();
539 if (area
.getLevel() != null && area
.getLevel().equals(NamedAreaLevel
.TDWG_LEVEL4())) {
540 result
= result
.replace("-", "");
543 // use generic GeoServiceArea data stored in technical annotations
546 GeoServiceArea areas
= mapping
.valueOf(area
);
547 if ((areas
!= null) && areas
.size() > 0) {
548 // FIXME multiple layers
549 List
<String
> values
= areas
.getAreasMap().values().iterator().next().values().iterator().next();
550 for (String value
: values
) {
551 result
= CdmUtils
.concat(SUBENTRY_DELIMITER
, result
, value
);
556 return CdmUtils
.Nz(result
, "-");
560 private static List
<String
> projectToWMSSubLayer(NamedArea area
){
562 List
<String
> layerNames
= new ArrayList
<String
>();
563 String matchedLayerName
= null;
564 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
566 if (voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
)){
567 NamedAreaLevel level
= area
.getLevel();
569 //TODO integrate into CDM
570 if (level
.equals(NamedAreaLevel
.TDWG_LEVEL1())) {
571 matchedLayerName
= "tdwg1" ;
572 } else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL2())) {
573 matchedLayerName
= "tdwg2";
574 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL3())) {
575 matchedLayerName
= "tdwg3";
576 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL4())) {
577 matchedLayerName
= "tdwg4";
580 //unrecognized tdwg area
585 // check if the matched layer equals the layer to project to
586 // if not: recurse into the sub-level in order to find the specified one.
587 String
[] matchedLayerNameTokens
= StringUtils
.split(matchedLayerName
, ':');
588 // if(matchedLayerNameTokens.length > 0 && matchedLayerNameTokens[0] != projectToLayer){
589 // for (NamedArea subArea : area.getIncludes()){
599 private static String
getWMSLayerName(NamedArea area
, IGeoServiceAreaMapping mapping
){
600 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
602 if (voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
)){
603 NamedAreaLevel level
= area
.getLevel();
605 //TODO integrate into CDM
606 if (level
.equals(NamedAreaLevel
.TDWG_LEVEL1())) {
608 } else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL2())) {
610 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL3())) {
612 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL4())) {
616 //unrecognized tdwg area
619 }else if (voc
.getUuid().equals(Country
.uuidCountryVocabulary
)){
620 return "country_earth:gmi_cntry";
623 GeoServiceArea areas
= mapping
.valueOf(area
);
624 if (areas
!= null && areas
.getAreasMap().size() > 0){
625 //FIXME multiple layers
626 String layer
= areas
.getAreasMap().keySet().iterator().next();
627 Map
<String
, List
<String
>> fields
= areas
.getAreasMap().get(layer
);
628 String field
= fields
.keySet().iterator().next();
629 String layerString
= layer
+ ":" + field
;
630 return layerString
.toLowerCase();
637 private static void addDistributionToStyleMap(Distribution distribution
, Map
<Integer
, Set
<Distribution
>> styleMap
,
638 List
<PresenceAbsenceTerm
> statusList
) {
639 PresenceAbsenceTerm status
= distribution
.getStatus();
640 if (status
== null) {
641 status
= defaultStatus
;
643 int style
= statusList
.indexOf(status
);
644 Set
<Distribution
> distributionSet
= styleMap
.get(style
);
645 if (distributionSet
== null) {
646 distributionSet
= new HashSet
<Distribution
>();
647 styleMap
.put(style
, distributionSet
);
649 distributionSet
.add(distribution
);
653 * @param fieldUnitPoints
654 * @param derivedUnitPoints
655 * @param specimenOrObservationTypeColors
668 * &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
669 * &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
671 public static String
getOccurrenceServiceRequestParameterString(
672 List
<Point
> fieldUnitPoints
,
673 List
<Point
> derivedUnitPoints
,
674 Map
<SpecimenOrObservationType
, Color
> specimenOrObservationTypeColors
) {
676 specimenOrObservationTypeColors
= mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors
);
678 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
679 parameters
.put("legend", "0");
681 Map
<String
, String
> styleAndData
= new HashMap
<String
, String
>();
683 addToStyleAndData(fieldUnitPoints
, SpecimenOrObservationType
.FieldUnit
, specimenOrObservationTypeColors
, styleAndData
);
684 addToStyleAndData(derivedUnitPoints
, SpecimenOrObservationType
.DerivedUnit
, specimenOrObservationTypeColors
, styleAndData
);
686 parameters
.put("os", StringUtils
.join(styleAndData
.keySet().iterator(), "||"));
687 parameters
.put("od", StringUtils
.join(styleAndData
.values().iterator(), "||"));
689 String queryString
= makeQueryString(parameters
);
691 logger
.info(queryString
);
703 private static <T
, S
> Map
<T
, S
> mergeMaps(Map
<T
, S
> defaultMap
, Map
<T
, S
> overrideMap
) {
704 Map
<T
, S
> tmpMap
= new HashMap
<T
, S
>();
705 tmpMap
.putAll(defaultMap
);
706 if(overrideMap
!= null){
707 tmpMap
.putAll(overrideMap
);
712 private static void addToStyleAndData(
714 SpecimenOrObservationType specimenOrObservationType
,
715 Map
<SpecimenOrObservationType
, Color
> specimenOrObservationTypeColors
, Map
<String
, String
> styleAndData
) {
717 //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>
719 if(points
!= null && points
.size()>0){
720 String style
= "c/" + Integer
.toHexString(specimenOrObservationTypeColors
.get(specimenOrObservationType
).getRGB()).substring(2) + "/10/noLabel";
721 StringBuilder data
= new StringBuilder();
722 for(Point point
: points
){
723 if(data
.length() > 0){
726 data
.append(point
.getLatitude() + "," + point
.getLongitude());
728 int index
= styleAndData
.size() + 1;
729 styleAndData
.put(index
+ ":" +style
, index
+ ":" +data
.toString());
735 * transform an integer (style counter) into a valid character representing a style.
738 * i not in {0,...,51} is undefined
742 private static char getStyleAbbrev(int i
){
752 * @param statusColorJson for example: {@code {"n":"#ff0000","p":"#ffff00"}}
754 * @throws IOException
755 * @throws JsonParseException
756 * @throws JsonMappingException
758 public static Map
<PresenceAbsenceTerm
, Color
> buildStatusColorMap(String statusColorJson
, ITermService termService
) throws IOException
, JsonParseException
,
759 JsonMappingException
{
761 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
= null;
762 if(StringUtils
.isNotEmpty(statusColorJson
)){
764 ObjectMapper mapper
= new ObjectMapper();
765 // TODO cache the color maps to speed this up?
767 TypeFactory typeFactory
= mapper
.getTypeFactory();
768 MapType mapType
= typeFactory
.constructMapType(HashMap
.class, String
.class, String
.class);
770 Map
<String
,String
> statusColorMap
= mapper
.readValue(statusColorJson
, mapType
);
771 UUID presenceTermVocabUuid
= PresenceAbsenceTerm
.NATIVE().getVocabulary().getUuid();
772 presenceAbsenceTermColors
= new HashMap
<PresenceAbsenceTerm
, Color
>();
773 PresenceAbsenceTerm paTerm
= null;
774 for(String statusId
: statusColorMap
.keySet()){
776 Color color
= Color
.decode(statusColorMap
.get(statusId
));
777 paTerm
= termService
.findByIdInVocabulary(statusId
, presenceTermVocabUuid
, PresenceAbsenceTerm
.class);
779 presenceAbsenceTermColors
.put(paTerm
, color
);
781 } catch (NumberFormatException e
){
782 logger
.error("Cannot decode color", e
);
786 return presenceAbsenceTermColors
;