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"));
114 * and now something very hacky ...
115 * ONLY-A-TEST is set by the Test class EditGeoServiceTest
117 * FIXME remove according line from
118 * EditGeoServiceTest.setUp() since the hardcoded colors for flora of
119 * cyprus should no longer be needed : #4268 (allow defining custom presence and absence term colors for EditGeoServiceUtilities)
121 String onlyTest
= System
.getProperty("ONLY-A-TEST"); //
122 if(onlyTest
!= null && onlyTest
.equals("TRUE")){
123 return defaultPresenceAbsenceTermBaseColors
;
125 //special colors for flora of cyprus !!! see HACK above !!!
126 UUID indigenousUuid
= UUID
.fromString("b325859b-504b-45e0-9ef0-d5c1602fcc0f");
127 UUID indigenousQUuid
= UUID
.fromString("17bc601f-53eb-4997-a4bc-c03ce5bfd1d3");
129 UUID cultivatedQUuid
= UUID
.fromString("4f31bfc8-3058-4d83-aea5-3a1fe9773f9f");
131 UUID casualUuid
= UUID
.fromString("5e81353c-38a3-4ca6-b979-0d9abc93b877");
132 UUID casualQUuid
= UUID
.fromString("73f75493-1185-4a3e-af1e-9a1f2e8dadb7");
134 UUID naturalizedNonInvasiveUuid
= UUID
.fromString("1b025e8b-901a-42e8-9739-119b410c6f03");
135 UUID naturalizedNonInvasiveQUuid
= UUID
.fromString("11f56e2f-c16c-4b3d-a870-bb5d3b20e624");
137 UUID naturalizedInvasiveUuid
= UUID
.fromString("faf2d271-868a-4bf7-b0b8-a1c5ab309de2");
138 UUID naturalizedInvasiveQUuid
= UUID
.fromString("ac429d5f-e8ad-49ae-a41c-e4779b58b96a");
140 UUID questionablelUuid
= UUID
.fromString("4b48f675-a6cf-49f3-a5ba-77e2c2979eb3");
141 UUID questionableQUuid
= UUID
.fromString("914e7393-1314-4632-bc45-5eff3dc1e424");
143 UUID reportedInErrorUuid
= UUID
.fromString("38604788-cf05-4607-b155-86db456f7680");
145 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(indigenousUuid
), Color
.decode("0x339966"));
146 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(indigenousQUuid
), Color
.decode("0x339966"));
148 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(cultivatedQUuid
), Color
.decode("0xbdb76b"));
150 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(casualUuid
), Color
.decode("0xffff00"));
151 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(casualQUuid
), Color
.decode("0xffff00"));
153 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedNonInvasiveUuid
), Color
.decode("0xff9900"));
154 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedNonInvasiveQUuid
), Color
.decode("0xff9900"));
156 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedInvasiveUuid
), Color
.decode("0xff0000"));
157 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(naturalizedInvasiveQUuid
), Color
.decode("0xff0000"));
159 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(questionablelUuid
), Color
.decode("0x00ccff"));
160 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(questionableQUuid
), Color
.decode("0x00ccff"));
162 defaultPresenceAbsenceTermBaseColors
.put((PresenceAbsenceTerm
) termDao
.load(reportedInErrorUuid
), Color
.decode("0xcccccc"));
165 return defaultPresenceAbsenceTermBaseColors
;
170 private static final String SUBENTRY_DELIMITER
= ",";
171 private static final String ENTRY_DELIMITER
= ";";
172 static final String ID_FROM_VALUES_SEPARATOR
= ":";
173 static final String VALUE_LIST_ENTRY_SEPARATOR
= "|";
174 static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR
= "||";
178 * Returns the parameter String for the EDIT geo webservice to create a
181 * @param distributions
182 * A set of distributions that should be shown on the map
183 * The {@link DescriptionUtility} class provides a method for
184 * filtering a set of Distributions :
187 * Collection<Distribution> filteredDistributions =
188 * DescriptionUtility.filterDistributions(distributions,
189 * subAreaPreference, statusOrderPreference, hideMarkedAreas);
192 * Data regarding the mapping of NamedAreas to shape file
194 * @param presenceAbsenceTermColors
195 * A map that defines the colors of PresenceAbsenceTerms. The
196 * PresenceAbsenceTerms are defined by their uuid. If a
197 * PresenceAbsenceTerm is not included in this map, it's default
198 * color is taken instead. If the map == null all terms are
199 * colored by their default color.
200 * @param projectToLayer
201 * name of a layer which is representing a specific
202 * {@link NamedAreaLevel} Supply this parameter if you to project
203 * all other distribution area levels to this layer.
206 * @return the parameter string or an empty string if the
207 * <code>distributions</code> set was null or empty.
210 public static String
getDistributionServiceRequestParameterString(
211 Collection
<Distribution
> filteredDistributions
,
212 IGeoServiceAreaMapping mapping
,
213 Map
<PresenceAbsenceTerm
,Color
> presenceAbsenceTermColors
,
214 String projectToLayer
,
215 List
<Language
> languages
){
219 * generateMultipleAreaDataParameters switches between the two possible styles:
220 * 1. ad=layername1:area-data||layername2:area-data
221 * 2. ad=layername1:area-data&ad=layername2:area-data
223 boolean generateMultipleAreaDataParameters
= false;
226 * doNotReuseStyles is a workaround for a problem in the EDIT MapService,
227 * see https://dev.e-taxonomy.eu/trac/ticket/2707#comment:24
229 * a.kohlbecker 2014-07-02 :This bug in the map service has been
230 * fixed now so reusing styles is now possible setting this flag to false.
232 boolean doNotReuseStyles
= false;
234 List
<String
> perLayerAreaData
= new ArrayList
<String
>();
235 Map
<Integer
, String
> areaStyles
= new HashMap
<Integer
, String
>();
236 List
<String
> legendSortList
= new ArrayList
<String
>();
238 String borderWidth
= "0.1";
239 String borderColorRgb
= "";
240 String borderDashingPattern
= "";
244 if(filteredDistributions
== null || filteredDistributions
.size() == 0){
248 presenceAbsenceTermColors
= mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors
);
250 Map
<String
, Map
<Integer
, Set
<Distribution
>>> layerMap
= new HashMap
<String
, Map
<Integer
, Set
<Distribution
>>>();
251 List
<PresenceAbsenceTerm
> statusList
= new ArrayList
<PresenceAbsenceTerm
>();
253 groupStylesAndLayers(filteredDistributions
, layerMap
, statusList
, mapping
);
255 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
258 int styleCounter
= 0;
259 for (PresenceAbsenceTerm status
: statusList
){
261 char styleCode
= getStyleAbbrev(styleCounter
);
263 //getting the area title
264 if (languages
== null){
265 languages
= new ArrayList
<Language
>();
267 if (languages
.size() == 0){
268 languages
.add(Language
.DEFAULT());
270 Representation statusRepresentation
= status
.getPreferredRepresentation(languages
);
272 //getting the area color
273 Color statusColor
= presenceAbsenceTermColors
.get(status
);
275 if (statusColor
!= null){
276 fillColorRgb
= Integer
.toHexString(statusColor
.getRGB()).substring(2);
279 fillColorRgb
= status
.getDefaultColor(); //TODO
281 fillColorRgb
= defaultStatus
.getDefaultColor();
284 String styleValues
= StringUtils
.join(new String
[]{fillColorRgb
, borderColorRgb
, borderWidth
, borderDashingPattern
}, ',');
286 areaStyles
.put(styleCounter
, styleValues
);
288 String legendEntry
= styleCode
+ ID_FROM_VALUES_SEPARATOR
+ encode(statusRepresentation
.getLabel());
289 legendSortList
.add(StringUtils
.leftPad(String
.valueOf(status
.getOrderIndex()), INT_MAX_LENGTH
, '0') + legendEntry
);
294 List
<String
> styledAreasPerLayer
;
295 List
<String
> areasPerStyle
;
297 * Map<Integer, Integer> styleUsage
299 * Used to avoid reusing styles in multiple layers
302 * value: the count of how often the style has been used for different layers, starts with 0 for first time use
304 Map
<Integer
, Integer
> styleUsage
= new HashMap
<Integer
, Integer
>();
307 for (String layerString
: layerMap
.keySet()){
309 styledAreasPerLayer
= new ArrayList
<String
>();
310 Map
<Integer
, Set
<Distribution
>> styleMap
= layerMap
.get(layerString
);
311 for (int style
: styleMap
.keySet()){
313 if(doNotReuseStyles
) {
314 if(!styleUsage
.containsKey(style
)){
315 styleUsage
.put(style
, 0);
318 styleUsage
.put(style
, styleUsage
.get(style
) + 1);
320 Integer styleIncrement
= styleUsage
.get(style
);
321 if(styleIncrement
> 0){
322 // style code has been used before!
323 styleChar
= getStyleAbbrev(style
+ styleIncrement
+ styleCounter
);
324 //for debugging sometimes failing test #3831
325 logger
.warn("style: " + style
+ ", styleIncrement: " + styleIncrement
+ ", styleCounter: " + styleCounter
);
326 areaStyles
.put(style
+ styleIncrement
+ styleCounter
, areaStyles
.get(style
));
328 styleChar
= getStyleAbbrev(style
);
331 styleChar
= getStyleAbbrev(style
);
333 Set
<Distribution
> distributionSet
= styleMap
.get(style
);
334 areasPerStyle
= new ArrayList
<String
>();
335 for (Distribution distribution
: distributionSet
){
337 areasPerStyle
.add(encode(getAreaCode(distribution
, mapping
)));
339 styledAreasPerLayer
.add(styleChar
+ ID_FROM_VALUES_SEPARATOR
+ StringUtils
.join(areasPerStyle
.iterator(), SUBENTRY_DELIMITER
));
341 perLayerAreaData
.add(encode(layerString
) + ID_FROM_VALUES_SEPARATOR
+ StringUtils
.join(styledAreasPerLayer
.iterator(), VALUE_LIST_ENTRY_SEPARATOR
));
344 if(areaStyles
.size() > 0){
345 ArrayList
<Integer
> styleIds
= new ArrayList
<Integer
>(areaStyles
.size());
346 styleIds
.addAll(areaStyles
.keySet());
347 Collections
.sort(styleIds
); // why is it necessary to sort here?
348 StringBuilder db
= new StringBuilder();
349 for(Integer sid
: styleIds
){
351 db
.append(VALUE_LIST_ENTRY_SEPARATOR
);
353 db
.append( getStyleAbbrev(sid
)).append(ID_FROM_VALUES_SEPARATOR
).append(areaStyles
.get(sid
));
355 parameters
.put("as", db
.toString());
357 if(legendSortList
.size() > 0){
358 // sort the label entries after the status terms
359 Collections
.sort(legendSortList
);
360 // since the status terms are have an inverse natural order
361 // (as all other ordered term, see OrderedTermBase.performCompareTo(T orderedTerm, boolean skipVocabularyCheck)
362 // the sorted list must be reverted
363 // Collections.reverse(legendSortList);
364 // remove the prepended order index (like 000000000000001 ) from the legend entries
365 @SuppressWarnings("unchecked")
366 Collection
<String
> legendEntries
= CollectionUtils
.collect(legendSortList
, new Transformer()
369 public String
transform(Object o
)
371 String s
= ((String
) o
);
372 return s
.substring(INT_MAX_LENGTH
, s
.length());
376 parameters
.put("title", StringUtils
.join(legendEntries
.iterator(), VALUE_LIST_ENTRY_SEPARATOR
));
379 if(generateMultipleAreaDataParameters
){
380 // not generically possible since parameters can not contain duplicate keys with value "ad"
382 parameters
.put("ad", StringUtils
.join(perLayerAreaData
.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR
));
385 String queryString
= makeQueryString(parameters
);
386 logger
.debug("getDistributionServiceRequestParameterString(): " + queryString
);
393 * Fills the layerMap and the statusList
395 * @param distributions
396 * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}
399 private static void groupStylesAndLayers(Collection
<Distribution
> distributions
,
400 Map
<String
, Map
<Integer
,Set
<Distribution
>>> layerMap
,
401 List
<PresenceAbsenceTerm
> statusList
,
402 IGeoServiceAreaMapping mapping
) {
405 //iterate through distributions and group styles and layers
406 //and collect necessary information
407 for (Distribution distribution
: distributions
){
409 PresenceAbsenceTerm status
= distribution
.getStatus();
411 status
= defaultStatus
;
413 if (! statusList
.contains(status
)){
414 statusList
.add(status
);
416 //group areas by layers and styles
417 NamedArea area
= distribution
.getArea();
419 addAreaToLayerMap(layerMap
, statusList
, distribution
, area
, mapping
);
424 * Adds the areas to the layer map. Areas which do not have layer information
425 * mapped to them are ignored.
427 * A layer map holds the following information:
430 * <li><b>String</b>: the WMSLayerName which matches the level of the
431 * contained distributions areas</li>
432 * <li><b>StyleMap</b>:</li>
434 * <li><b>Integer</b>: the index of the status in the
435 * <code>statusList</code></li>
436 * <li><b>Set{@code<Distribution>}</b>: the set of distributions having the
437 * same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>
443 * @param distribution
446 private static void addAreaToLayerMap(Map
<String
, Map
<Integer
,
447 Set
<Distribution
>>> layerMap
,
448 List
<PresenceAbsenceTerm
> statusList
,
449 Distribution distribution
,
451 IGeoServiceAreaMapping mapping
) {
454 String geoLayerName
= getWMSLayerName(area
, mapping
);
456 if(geoLayerName
== null){
457 /* IGNORE areas for which no layer is mapped */
459 Map
<Integer
, Set
<Distribution
>> styleMap
= layerMap
.get(geoLayerName
);
460 if (styleMap
== null) {
461 styleMap
= new HashMap
<Integer
, Set
<Distribution
>>();
462 layerMap
.put(geoLayerName
, styleMap
);
464 addDistributionToStyleMap(distribution
, styleMap
, statusList
);
472 * URI encode the given String
476 private static String
encode(String string
) {
477 String encoded
= string
;
479 encoded
= URLEncoder
.encode(string
, "UTF-8");
480 } catch (UnsupportedEncodingException e
) {
487 * combine parameter into a URI query string fragment. The values will be
491 * @return a URI query string fragment
493 private static String
makeQueryString(Map
<String
, String
> parameters
){
494 StringBuilder queryString
= new StringBuilder();
495 for (String key
: parameters
.keySet()) {
496 if(queryString
.length() > 0){
497 queryString
.append('&');
499 if(key
.equals("od") || key
.equals("os") || key
.equals("ms") || key
.equals("ad") || key
.equals("as") || key
.equals("title") || key
.equals("bbox")){
500 queryString
.append(key
).append('=').append(parameters
.get(key
));
502 queryString
.append(key
).append('=').append(encode(parameters
.get(key
)));
505 return queryString
.toString();
508 private static String
getAreaCode(Distribution distribution
, IGeoServiceAreaMapping mapping
){
509 NamedArea area
= distribution
.getArea();
510 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
511 String result
= null;
513 if (voc
!= null && voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
) || voc
.getUuid().equals(Country
.uuidCountryVocabulary
)) {
515 result
= area
.getIdInVocabulary();
516 if (area
.getLevel() != null && area
.getLevel().equals(NamedAreaLevel
.TDWG_LEVEL4())) {
517 result
= result
.replace("-", "");
520 // use generic GeoServiceArea data stored in technical annotations
523 GeoServiceArea areas
= mapping
.valueOf(area
);
524 if ((areas
!= null) && areas
.size() > 0) {
525 // FIXME multiple layers
526 List
<String
> values
= areas
.getAreasMap().values().iterator().next().values().iterator().next();
527 for (String value
: values
) {
528 result
= CdmUtils
.concat(SUBENTRY_DELIMITER
, result
, value
);
533 return CdmUtils
.Nz(result
, "-");
537 private static List
<String
> projectToWMSSubLayer(NamedArea area
){
539 List
<String
> layerNames
= new ArrayList
<String
>();
540 String matchedLayerName
= null;
541 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
543 if (voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
)){
544 NamedAreaLevel level
= area
.getLevel();
546 //TODO integrate into CDM
547 if (level
.equals(NamedAreaLevel
.TDWG_LEVEL1())) {
548 matchedLayerName
= "tdwg1" ;
549 } else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL2())) {
550 matchedLayerName
= "tdwg2";
551 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL3())) {
552 matchedLayerName
= "tdwg3";
553 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL4())) {
554 matchedLayerName
= "tdwg4";
557 //unrecognized tdwg area
562 // check if the matched layer equals the layer to project to
563 // if not: recurse into the sub-level in order to find the specified one.
564 String
[] matchedLayerNameTokens
= StringUtils
.split(matchedLayerName
, ':');
565 // if(matchedLayerNameTokens.length > 0 && matchedLayerNameTokens[0] != projectToLayer){
566 // for (NamedArea subArea : area.getIncludes()){
576 private static String
getWMSLayerName(NamedArea area
, IGeoServiceAreaMapping mapping
){
577 TermVocabulary
<NamedArea
> voc
= area
.getVocabulary();
579 if (voc
.getUuid().equals(NamedArea
.uuidTdwgAreaVocabulary
)){
580 NamedAreaLevel level
= area
.getLevel();
582 //TODO integrate into CDM
583 if (level
.equals(NamedAreaLevel
.TDWG_LEVEL1())) {
585 } else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL2())) {
587 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL3())) {
589 }else if (level
.equals(NamedAreaLevel
.TDWG_LEVEL4())) {
593 //unrecognized tdwg area
596 }else if (voc
.getUuid().equals(Country
.uuidCountryVocabulary
)){
597 return "country_earth:gmi_cntry";
600 GeoServiceArea areas
= mapping
.valueOf(area
);
601 if (areas
!= null && areas
.getAreasMap().size() > 0){
602 //FIXME multiple layers
603 String layer
= areas
.getAreasMap().keySet().iterator().next();
604 Map
<String
, List
<String
>> fields
= areas
.getAreasMap().get(layer
);
605 String field
= fields
.keySet().iterator().next();
606 String layerString
= layer
+ ":" + field
;
607 return layerString
.toLowerCase();
614 private static void addDistributionToStyleMap(Distribution distribution
, Map
<Integer
, Set
<Distribution
>> styleMap
,
615 List
<PresenceAbsenceTerm
> statusList
) {
616 PresenceAbsenceTerm status
= distribution
.getStatus();
617 if (status
== null) {
618 status
= defaultStatus
;
620 int style
= statusList
.indexOf(status
);
621 Set
<Distribution
> distributionSet
= styleMap
.get(style
);
622 if (distributionSet
== null) {
623 distributionSet
= new HashSet
<Distribution
>();
624 styleMap
.put(style
, distributionSet
);
626 distributionSet
.add(distribution
);
630 * @param fieldUnitPoints
631 * @param derivedUnitPoints
632 * @param specimenOrObservationTypeColors
645 * &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
646 * &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
648 public static OccurrenceServiceRequestParameterDto
getOccurrenceServiceRequestParameterString(
649 List
<Point
> fieldUnitPoints
,
650 List
<Point
> derivedUnitPoints
,
651 Map
<SpecimenOrObservationType
, Color
> specimenOrObservationTypeColors
) {
652 OccurrenceServiceRequestParameterDto dto
= new OccurrenceServiceRequestParameterDto();
655 specimenOrObservationTypeColors
= mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors
);
657 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
658 parameters
.put("legend", "0");
660 Map
<String
, String
> styleAndData
= new HashMap
<String
, String
>();
662 addToStyleAndData(fieldUnitPoints
, SpecimenOrObservationType
.FieldUnit
, specimenOrObservationTypeColors
, styleAndData
);
663 addToStyleAndData(derivedUnitPoints
, SpecimenOrObservationType
.DerivedUnit
, specimenOrObservationTypeColors
, styleAndData
);
665 parameters
.put("os", StringUtils
.join(styleAndData
.keySet().iterator(), "||"));
666 parameters
.put("od", StringUtils
.join(styleAndData
.values().iterator(), "||"));
668 String queryString
= makeQueryString(parameters
);
670 dto
.setFieldUnitPoints(fieldUnitPoints
);
671 dto
.setDerivedUnitPoints(derivedUnitPoints
);
672 dto
.setOccurrenceQuery(queryString
);
674 logger
.info(queryString
);
686 private static <T
, S
> Map
<T
, S
> mergeMaps(Map
<T
, S
> defaultMap
, Map
<T
, S
> overrideMap
) {
687 Map
<T
, S
> tmpMap
= new HashMap
<T
, S
>();
688 tmpMap
.putAll(defaultMap
);
689 if(overrideMap
!= null){
690 tmpMap
.putAll(overrideMap
);
695 private static void addToStyleAndData(
697 SpecimenOrObservationType specimenOrObservationType
,
698 Map
<SpecimenOrObservationType
, Color
> specimenOrObservationTypeColors
, Map
<String
, String
> styleAndData
) {
700 //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>
702 if(points
!= null && points
.size()>0){
703 String style
= "c/" + Integer
.toHexString(specimenOrObservationTypeColors
.get(specimenOrObservationType
).getRGB()).substring(2) + "/10/noLabel";
704 StringBuilder data
= new StringBuilder();
705 for(Point point
: points
){
706 if(data
.length() > 0){
709 data
.append(point
.getLatitude() + "," + point
.getLongitude());
711 int index
= styleAndData
.size() + 1;
712 styleAndData
.put(index
+ ":" +style
, index
+ ":" +data
.toString());
718 * transform an integer (style counter) into a valid character representing a style.
721 * i not in {0,...,51} is undefined
725 private static char getStyleAbbrev(int i
){
735 * @param statusColorJson for example: {@code {"n":"#ff0000","p":"#ffff00"}}
737 * @throws IOException
738 * @throws JsonParseException
739 * @throws JsonMappingException
741 public static Map
<PresenceAbsenceTerm
, Color
> buildStatusColorMap(String statusColorJson
, ITermService termService
) throws IOException
, JsonParseException
,
742 JsonMappingException
{
744 Map
<PresenceAbsenceTerm
, Color
> presenceAbsenceTermColors
= null;
745 if(StringUtils
.isNotEmpty(statusColorJson
)){
747 ObjectMapper mapper
= new ObjectMapper();
748 // TODO cache the color maps to speed this up?
750 TypeFactory typeFactory
= mapper
.getTypeFactory();
751 MapType mapType
= typeFactory
.constructMapType(HashMap
.class, String
.class, String
.class);
753 Map
<String
,String
> statusColorMap
= mapper
.readValue(statusColorJson
, mapType
);
754 UUID presenceTermVocabUuid
= PresenceAbsenceTerm
.NATIVE().getVocabulary().getUuid();
755 presenceAbsenceTermColors
= new HashMap
<PresenceAbsenceTerm
, Color
>();
756 PresenceAbsenceTerm paTerm
= null;
757 for(String statusId
: statusColorMap
.keySet()){
759 Color color
= Color
.decode(statusColorMap
.get(statusId
));
760 paTerm
= termService
.findByIdInVocabulary(statusId
, presenceTermVocabUuid
, PresenceAbsenceTerm
.class);
762 presenceAbsenceTermColors
.put(paTerm
, color
);
764 } catch (NumberFormatException e
){
765 logger
.error("Cannot decode color", e
);
769 return presenceAbsenceTermColors
;
774 * @param filteredDistributions
776 * @param hideMarkedAreas
780 public static CondensedDistribution
getCondensedDistribution(Collection
<Distribution
> filteredDistributions
,
781 CondensedDistributionRecipe recipe
, List
<Language
> langs
) {
782 ICondensedDistributionComposer composer
;
784 composer
= recipe
.newCondensedDistributionComposerInstance();
785 } catch (InstantiationException e
) {
786 throw new RuntimeException(e
);
787 } catch (IllegalAccessException e
) {
788 throw new RuntimeException(e
);
790 CondensedDistribution condensedDistribution
= composer
.createCondensedDistribution(
791 filteredDistributions
, langs
);
792 return condensedDistribution
;