some bugfixes in CondensedDistribution composition code - #3907
[cdmlib.git] / cdmlib-ext / src / main / java / eu / etaxonomy / cdm / ext / geo / EditGeoServiceUtilities.java
index c65a46db9eba6585b5965cd80d71db1fbccc812c..47d01471c75dee9c97190a9b1c980f1a9e2b9d73 100644 (file)
@@ -1,9 +1,9 @@
 // $Id$\r
 /**\r
 * Copyright (C) 2007 EDIT\r
-* European Distributed Institute of Taxonomy \r
+* European Distributed Institute of Taxonomy\r
 * http://www.e-taxonomy.eu\r
-* \r
+*\r
 * The contents of this file are subject to the Mozilla Public License Version 1.1\r
 * See LICENSE.TXT at the top of this package for the full license terms.\r
 */\r
 package eu.etaxonomy.cdm.ext.geo;\r
 \r
 import java.awt.Color;\r
+import java.io.IOException;\r
 import java.io.UnsupportedEncodingException;\r
 import java.net.URLEncoder;\r
 import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
 import java.util.HashMap;\r
 import java.util.HashSet;\r
 import java.util.List;\r
 import java.util.Map;\r
 import java.util.Set;\r
+import java.util.UUID;\r
 \r
 import javax.persistence.Transient;\r
 \r
+import org.apache.commons.collections.CollectionUtils;\r
+import org.apache.commons.collections.Transformer;\r
 import org.apache.commons.lang.StringUtils;\r
 import org.apache.log4j.Logger;\r
+import org.codehaus.jackson.JsonParseException;\r
+import org.codehaus.jackson.map.JsonMappingException;\r
+import org.codehaus.jackson.map.ObjectMapper;\r
+import org.codehaus.jackson.map.type.MapType;\r
+import org.codehaus.jackson.map.type.TypeFactory;\r
 \r
+import eu.etaxonomy.cdm.api.service.ITermService;\r
+import eu.etaxonomy.cdm.api.service.dto.CondensedDistribution;\r
+import eu.etaxonomy.cdm.api.utility.DescriptionUtility;\r
 import eu.etaxonomy.cdm.common.CdmUtils;\r
 import eu.etaxonomy.cdm.model.common.Language;\r
 import eu.etaxonomy.cdm.model.common.Representation;\r
+import eu.etaxonomy.cdm.model.common.TermVocabulary;\r
 import eu.etaxonomy.cdm.model.description.Distribution;\r
-import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;\r
-import eu.etaxonomy.cdm.model.description.PresenceTerm;\r
+import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;\r
+import eu.etaxonomy.cdm.model.location.Country;\r
 import eu.etaxonomy.cdm.model.location.NamedArea;\r
 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;\r
 import eu.etaxonomy.cdm.model.location.Point;\r
-import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;\r
-import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;\r
-import eu.etaxonomy.cdm.model.occurrence.FieldObservation;\r
-import eu.etaxonomy.cdm.model.occurrence.LivingBeing;\r
-import eu.etaxonomy.cdm.model.occurrence.Observation;\r
-import eu.etaxonomy.cdm.model.occurrence.Specimen;\r
-import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;\r
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;\r
+import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;\r
 \r
 /**\r
+ * Class implementing the business logic for creating the map service string for\r
+ * a given set of distributions. See {@link EditGeoService} as API for the given functionality.\r
+ *\r
+ * @see EditGeoService\r
+ *\r
  * @author a.mueller\r
  * @created 17.11.2008\r
- * @version 1.0\r
  */\r
 public class EditGeoServiceUtilities {\r
-       private static final Logger logger = Logger.getLogger(EditGeoServiceUtilities.class);\r
-\r
-       private static PresenceAbsenceTermBase<?> defaultStatus = PresenceTerm.PRESENT();\r
-       \r
-       private static HashMap<Class<? extends SpecimenOrObservationBase<?>>, Color> defaultSpecimenOrObservationTypeColors;\r
-\r
-       private static final String MS_SEPARATOR = ",";\r
-       \r
-       static {\r
-               defaultSpecimenOrObservationTypeColors = new HashMap<Class<? extends SpecimenOrObservationBase<?>>, Color>();\r
-               defaultSpecimenOrObservationTypeColors.put(FieldObservation.class, Color.ORANGE);\r
-               defaultSpecimenOrObservationTypeColors.put(DerivedUnit.class, Color.YELLOW);\r
-               defaultSpecimenOrObservationTypeColors.put(LivingBeing.class, Color.GREEN);\r
-               defaultSpecimenOrObservationTypeColors.put(Observation.class, Color.ORANGE);\r
-               defaultSpecimenOrObservationTypeColors.put(Specimen.class, Color.GRAY);\r
-               \r
-       }\r
-\r
-       \r
-//     @Transient\r
-//     public static String getOccurrenceServiceRequestParameterString()\r
-//                     \r
-\r
-       //preliminary implementation for TDWG areas\r
-       /**\r
-        * Returns the parameter String for the EDIT geo webservice to create a dsitribution map.\r
-        * @param distributions A set of distributions that should be shown on the map\r
-        * @param presenceAbsenceTermColors A map that defines the colors of PresenceAbsenceTerms. \r
-        * The PresenceAbsenceTerms are defined by their uuid. If a PresenceAbsenceTerm is not included in\r
-        * this map, it's default color is taken instead. If the map == null all terms are colored by their default color. \r
-        * @param width The maps width\r
-        * @param height The maps height\r
-        * @param bbox The maps bounding box (e.g. "-180,-90,180,90" for the whole world)\r
-        * @param layer The layer that is responsible for background borders and colors. Use the name for the layer.\r
-        * If null 'earth' is taken as default. \r
-        * @return the parameter string or an empty string if the <code>distributions</code> set was null or empty.\r
-        */\r
-       @Transient\r
-       public static String getDistributionServiceRequestParameterString(\r
-                       Set<Distribution> distributions, \r
-                       Map<PresenceAbsenceTermBase<?>,Color> presenceAbsenceTermColors, \r
-                       int width, \r
-                       int height, \r
-                       String bbox, \r
-                       String backLayer,\r
-                       List<Language> languages){\r
-               \r
-               String result = "";\r
-               String layer = ""; \r
-               String areaData = "";\r
-               String areaStyle = "";\r
-               String areaTitle = "";\r
-               String adLayerSeparator = ":";\r
-               String styleInAreaDataSeparator = "|";\r
-               //double borderWidth = 0.1;\r
-               double borderWidth = 0.1;\r
-               \r
-               \r
-               if(distributions == null || distributions.size() == 0){\r
-                       return "";\r
-               }\r
-               if (presenceAbsenceTermColors == null) {\r
-                       //presenceAbsenceTermColors = new HashMap<PresenceAbsenceTermBase<?>, Color>();\r
-                       presenceAbsenceTermColors = makeDefaultColorMap();\r
-               }\r
-\r
-               //List<String> layerStrings = new ArrayList<String>(); \r
-               Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>(); \r
-               List<PresenceAbsenceTermBase<?>> statusList = new ArrayList<PresenceAbsenceTermBase<?>>();\r
-               \r
-               //bbox, width, hight\r
-               if (bbox == null){\r
-                       // FIXME uncommented this as it can not be desirable to have default values in this method\r
-                       // we need a parameterString that consists of essential parameters only \r
-                       // defaults should be implemented in the geoservice itself.\r
-                       //bbox ="bbox=-180,-90,180,90";  //earth is default\r
-               }else{\r
-                       bbox = "bbox=" + bbox;\r
-               }\r
-               String ms = mapSizeParameter(width, height);\r
-               \r
-               //iterate through distributions and group styles and layers\r
-               //and collect necessary information\r
-               for (Distribution distribution:distributions){\r
-                       //collect status\r
-                       PresenceAbsenceTermBase<?> status = distribution.getStatus();\r
-                       if(status == null){\r
-                               status = defaultStatus;\r
-                       }\r
-                       if (! statusList.contains(status)){\r
-                               statusList.add(status);\r
-                       }\r
-                       //group by layers and styles\r
-                       NamedArea area = distribution.getArea();\r
-                       if (area != null){\r
-                               NamedAreaLevel level = area.getLevel();\r
-                               String geoLayerString = getGeoServiceLayer(level);\r
-                               //Set<Distribution> layerDistributionSet;\r
-                               //int index = layerStrings.indexOf(geoLayerString);\r
-                               Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerString);\r
-                               if (styleMap == null){\r
-                                       styleMap = new HashMap<Integer, Set<Distribution>>();\r
-                                       layerMap.put(geoLayerString, styleMap);\r
-                               }\r
-                               addDistributionToMap(distribution, styleMap, statusList);\r
-                       }\r
-               }\r
-               \r
-               //layer\r
-               if (backLayer == null || "".equals(layer.trim())){\r
-                       backLayer = "earth"; \r
-               }\r
-               layer = "l="+backLayer;\r
-//             for (String layerString : layerMap.keySet()){\r
-//                     layer += "," + layerString;\r
+    private static final Logger logger = Logger.getLogger(EditGeoServiceUtilities.class);\r
+\r
+    private static final int INT_MAX_LENGTH = String.valueOf(Integer.MAX_VALUE).length();\r
+\r
+    private static PresenceAbsenceTerm defaultStatus = PresenceAbsenceTerm.PRESENT();\r
+\r
+    private static IDefinedTermDao termDao;\r
+\r
+\r
+\r
+    /**\r
+     * @param termDao\r
+     */\r
+    public static void setTermDao(IDefinedTermDao termDao) {\r
+        EditGeoServiceUtilities.termDao= termDao;\r
+    }\r
+\r
+\r
+    private static HashMap<SpecimenOrObservationType, Color> defaultSpecimenOrObservationTypeColors = null;\r
+\r
+    private static HashMap<SpecimenOrObservationType, Color> getDefaultSpecimenOrObservationTypeColors() {\r
+        if(defaultSpecimenOrObservationTypeColors == null){\r
+            defaultSpecimenOrObservationTypeColors = new HashMap<SpecimenOrObservationType, Color>();\r
+            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.FieldUnit, Color.ORANGE);\r
+            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.DerivedUnit, Color.RED);\r
+            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.LivingSpecimen, Color.GREEN);\r
+            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.Observation, Color.ORANGE);\r
+            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.PreservedSpecimen, Color.GRAY);\r
+            defaultSpecimenOrObservationTypeColors.put(SpecimenOrObservationType.Media, Color.BLUE);\r
+        }\r
+        return defaultSpecimenOrObservationTypeColors;\r
+    }\r
+\r
+\r
+    private static HashMap<PresenceAbsenceTerm, Color> defaultPresenceAbsenceTermBaseColors = null;\r
+\r
+    private static HashMap<PresenceAbsenceTerm, Color> getDefaultPresenceAbsenceTermBaseColors() {\r
+        if(defaultPresenceAbsenceTermBaseColors == null){\r
+            defaultPresenceAbsenceTermBaseColors = new HashMap<PresenceAbsenceTerm, Color>();\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.PRESENT(), Color.decode("0x4daf4a"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.NATIVE(), Color.decode("0x4daf4a"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.NATIVE_DOUBTFULLY_NATIVE(), Color.decode("0x377eb8"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.CULTIVATED(), Color.decode("0x984ea3"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED(), Color.decode("0xff7f00"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED_ADVENTITIOUS(), Color.decode("0xffff33"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED_CULTIVATED(), Color.decode("0xa65628"));\r
+            defaultPresenceAbsenceTermBaseColors.put(PresenceAbsenceTerm.INTRODUCED_NATURALIZED(), Color.decode("0xf781bf"));\r
+\r
+            /*\r
+             * and now something very hacky ...\r
+             * ONLY-A-TEST is set by the Test class EditGeoServiceTest\r
+             *\r
+             * FIXME remove according line from\r
+             * EditGeoServiceTest.setUp() since the hardcoded colors for flora of\r
+             * cyprus should no longer be needed : #4268 (allow defining custom presence and absence term colors for EditGeoServiceUtilities)\r
+             */\r
+            String onlyTest = System.getProperty("ONLY-A-TEST"); //\r
+            if(onlyTest != null && onlyTest.equals("TRUE")){\r
+                return defaultPresenceAbsenceTermBaseColors;\r
+            }\r
+            //special colors for flora of cyprus !!! see HACK above !!!\r
+            UUID indigenousUuid = UUID.fromString("b325859b-504b-45e0-9ef0-d5c1602fcc0f");\r
+            UUID indigenousQUuid = UUID.fromString("17bc601f-53eb-4997-a4bc-c03ce5bfd1d3");\r
+\r
+            UUID cultivatedQUuid = UUID.fromString("4f31bfc8-3058-4d83-aea5-3a1fe9773f9f");\r
+\r
+            UUID casualUuid = UUID.fromString("5e81353c-38a3-4ca6-b979-0d9abc93b877");\r
+            UUID casualQUuid = UUID.fromString("73f75493-1185-4a3e-af1e-9a1f2e8dadb7");\r
+\r
+            UUID naturalizedNonInvasiveUuid = UUID.fromString("1b025e8b-901a-42e8-9739-119b410c6f03");\r
+            UUID naturalizedNonInvasiveQUuid = UUID.fromString("11f56e2f-c16c-4b3d-a870-bb5d3b20e624");\r
+\r
+            UUID naturalizedInvasiveUuid = UUID.fromString("faf2d271-868a-4bf7-b0b8-a1c5ab309de2");\r
+            UUID naturalizedInvasiveQUuid = UUID.fromString("ac429d5f-e8ad-49ae-a41c-e4779b58b96a");\r
+\r
+            UUID questionablelUuid = UUID.fromString("4b48f675-a6cf-49f3-a5ba-77e2c2979eb3");\r
+            UUID questionableQUuid = UUID.fromString("914e7393-1314-4632-bc45-5eff3dc1e424");\r
+\r
+            UUID reportedInErrorUuid = UUID.fromString("38604788-cf05-4607-b155-86db456f7680");\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(indigenousUuid), Color.decode("0x339966"));\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(indigenousQUuid), Color.decode("0x339966"));\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(cultivatedQUuid), Color.decode("0xbdb76b"));\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(casualUuid), Color.decode("0xffff00"));\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(casualQUuid), Color.decode("0xffff00"));\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(naturalizedNonInvasiveUuid), Color.decode("0xff9900"));\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(naturalizedNonInvasiveQUuid), Color.decode("0xff9900"));\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(naturalizedInvasiveUuid), Color.decode("0xff0000"));\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(naturalizedInvasiveQUuid), Color.decode("0xff0000"));\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(questionablelUuid), Color.decode("0x00ccff"));\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(questionableQUuid), Color.decode("0x00ccff"));\r
+\r
+            defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTerm) termDao.load(reportedInErrorUuid), Color.decode("0xcccccc"));\r
+\r
+        }\r
+        return defaultPresenceAbsenceTermBaseColors;\r
+    }\r
+\r
+\r
+\r
+    private static final String SUBENTRY_DELIMITER = ",";\r
+    private static final String ENTRY_DELIMITER = ";";\r
+    static final String ID_FROM_VALUES_SEPARATOR = ":";\r
+    static final String VALUE_LIST_ENTRY_SEPARATOR = "|";\r
+    static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR = "||";\r
+\r
+\r
+    /**\r
+     * Returns the parameter String for the EDIT geo webservice to create a\r
+     * distribution map.\r
+     *\r
+     * @param distributions\r
+     *            A set of distributions that should be shown on the map\r
+     *            The {@link DescriptionUtility} class provides a method for\r
+     *            filtering a set of Distributions :\r
+     *\r
+     *            {@code\r
+     *            Collection<Distribution> filteredDistributions =\r
+     *            DescriptionUtility.filterDistributions(distributions,\r
+     *            subAreaPreference, statusOrderPreference, hideMarkedAreas);\r
+     *            }\r
+     * @param mapping\r
+     *            Data regarding the mapping of NamedAreas to shape file\r
+     *            attribute tables\r
+     * @param presenceAbsenceTermColors\r
+     *            A map that defines the colors of PresenceAbsenceTerms. The\r
+     *            PresenceAbsenceTerms are defined by their uuid. If a\r
+     *            PresenceAbsenceTerm is not included in this map, it's default\r
+     *            color is taken instead. If the map == null all terms are\r
+     *            colored by their default color.\r
+     * @param projectToLayer\r
+     *            name of a layer which is representing a specific\r
+     *            {@link NamedAreaLevel} Supply this parameter if you to project\r
+     *            all other distribution area levels to this layer.\r
+     * @param languages\r
+     *\r
+     * @return the parameter string or an empty string if the\r
+     *         <code>distributions</code> set was null or empty.\r
+     */\r
+    @Transient\r
+    public static String getDistributionServiceRequestParameterString(\r
+            Collection<Distribution> filteredDistributions,\r
+            IGeoServiceAreaMapping mapping,\r
+            Map<PresenceAbsenceTerm,Color> presenceAbsenceTermColors,\r
+            String projectToLayer,\r
+            List<Language> languages){\r
+\r
+\r
+        /*\r
+         * generateMultipleAreaDataParameters switches between the two possible styles:\r
+         * 1. ad=layername1:area-data||layername2:area-data\r
+         * 2. ad=layername1:area-data&ad=layername2:area-data\r
+         */\r
+        boolean generateMultipleAreaDataParameters = false;\r
+\r
+        /*\r
+         * doNotReuseStyles is a workaround for a problem in the EDIT MapService,\r
+         * see https://dev.e-taxonomy.eu/trac/ticket/2707#comment:24\r
+         *\r
+         * a.kohlbecker 2014-07-02 :This bug in the map service has been\r
+         * fixed now so reusing styles is now possible setting this flag to false.\r
+         */\r
+        boolean doNotReuseStyles = false;\r
+\r
+        List<String>  perLayerAreaData = new ArrayList<String>();\r
+        Map<Integer, String> areaStyles = new HashMap<Integer, String>();\r
+        List<String> legendSortList = new ArrayList<String>();\r
+\r
+        String borderWidth = "0.1";\r
+        String borderColorRgb = "";\r
+        String borderDashingPattern = "";\r
+\r
+\r
+        //handle empty set\r
+        if(filteredDistributions == null || filteredDistributions.size() == 0){\r
+            return "";\r
+        }\r
+\r
+        presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);\r
+\r
+        Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>();\r
+        List<PresenceAbsenceTerm> statusList = new ArrayList<PresenceAbsenceTerm>();\r
+\r
+        groupStylesAndLayers(filteredDistributions, layerMap, statusList, mapping);\r
+\r
+        Map<String, String> parameters = new HashMap<String, String>();\r
+\r
+        //style\r
+        int styleCounter = 0;\r
+        for (PresenceAbsenceTerm status: statusList){\r
+\r
+            char styleCode = getStyleAbbrev(styleCounter);\r
+\r
+            //getting the area title\r
+            if (languages == null){\r
+                languages = new ArrayList<Language>();\r
+            }\r
+            if (languages.size() == 0){\r
+                languages.add(Language.DEFAULT());\r
+            }\r
+            Representation statusRepresentation = status.getPreferredRepresentation(languages);\r
+\r
+            //getting the area color\r
+            Color statusColor = presenceAbsenceTermColors.get(status);\r
+            String fillColorRgb;\r
+            if (statusColor != null){\r
+                fillColorRgb = Integer.toHexString(statusColor.getRGB()).substring(2);\r
+            }else{\r
+                if(status != null){\r
+                    fillColorRgb = status.getDefaultColor(); //TODO\r
+                } else {\r
+                    fillColorRgb = defaultStatus.getDefaultColor();\r
+                }\r
+            }\r
+            String styleValues = StringUtils.join(new String[]{fillColorRgb, borderColorRgb, borderWidth, borderDashingPattern}, ',');\r
+\r
+            areaStyles.put(styleCounter, styleValues);\r
+\r
+            String legendEntry = styleCode + ID_FROM_VALUES_SEPARATOR + encode(statusRepresentation.getLabel());\r
+            legendSortList.add(StringUtils.leftPad(String.valueOf(status.getOrderIndex()), INT_MAX_LENGTH, '0') + legendEntry );\r
+            styleCounter++;\r
+        }\r
+\r
+        // area data\r
+        List<String> styledAreasPerLayer;\r
+        List<String> areasPerStyle;\r
+        /**\r
+         * Map<Integer, Integer> styleUsage\r
+         *\r
+         * Used to avoid reusing styles in multiple layers\r
+         *\r
+         * key: the style id\r
+         * value: the count of how often the style has been used for different layers, starts with 0 for first time use\r
+         */\r
+        Map<Integer, Integer> styleUsage = new HashMap<Integer, Integer>();\r
+\r
+        char styleChar;\r
+        for (String layerString : layerMap.keySet()){\r
+            // each layer\r
+            styledAreasPerLayer = new ArrayList<String>();\r
+            Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);\r
+            for (int style: styleMap.keySet()){\r
+                // stylesPerLayer\r
+                if(doNotReuseStyles) {\r
+                    if(!styleUsage.containsKey(style)){\r
+                        styleUsage.put(style, 0);\r
+                    } else {\r
+                        // increment by 1\r
+                        styleUsage.put(style, styleUsage.get(style) + 1);\r
+                    }\r
+                    Integer styleIncrement = styleUsage.get(style);\r
+                    if(styleIncrement > 0){\r
+                        // style code has been used before!\r
+                        styleChar = getStyleAbbrev(style + styleIncrement + styleCounter);\r
+                        //for debugging sometimes failing test  #3831\r
+                        logger.warn("style: " + style + ", styleIncrement: " +  styleIncrement + ", styleCounter: " + styleCounter);\r
+                        areaStyles.put(style + styleIncrement + styleCounter, areaStyles.get(style));\r
+                    } else {\r
+                        styleChar = getStyleAbbrev(style);\r
+                    }\r
+                } else {\r
+                    styleChar = getStyleAbbrev(style);\r
+                }\r
+                Set<Distribution> distributionSet = styleMap.get(style);\r
+                areasPerStyle = new ArrayList<String>();\r
+                for (Distribution distribution: distributionSet){\r
+                    // areasPerStyle\r
+                    areasPerStyle.add(encode(getAreaCode(distribution, mapping)));\r
+                }\r
+                styledAreasPerLayer.add(styleChar + ID_FROM_VALUES_SEPARATOR + StringUtils.join(areasPerStyle.iterator(), SUBENTRY_DELIMITER));\r
+            }\r
+            perLayerAreaData.add(encode(layerString) + ID_FROM_VALUES_SEPARATOR + StringUtils.join(styledAreasPerLayer.iterator(), VALUE_LIST_ENTRY_SEPARATOR));\r
+        }\r
+\r
+        if(areaStyles.size() > 0){\r
+            ArrayList<Integer> styleIds = new ArrayList<Integer>(areaStyles.size());\r
+            styleIds.addAll(areaStyles.keySet());\r
+            Collections.sort(styleIds); // why is it necessary to sort here?\r
+            StringBuilder db = new StringBuilder();\r
+            for(Integer sid : styleIds){\r
+                if(db.length() > 0){\r
+                    db.append(VALUE_LIST_ENTRY_SEPARATOR);\r
+                }\r
+                db.append( getStyleAbbrev(sid)).append(ID_FROM_VALUES_SEPARATOR).append(areaStyles.get(sid));\r
+            }\r
+            parameters.put("as", db.toString());\r
+        }\r
+        if(legendSortList.size() > 0){\r
+            // sort the label entries after the status terms\r
+            Collections.sort(legendSortList);\r
+            // since the status terms are have an inverse natural order\r
+            // (as all other ordered term, see OrderedTermBase.performCompareTo(T orderedTerm, boolean skipVocabularyCheck)\r
+            // the sorted list must be reverted\r
+//            Collections.reverse(legendSortList);\r
+            // remove the prepended order index (like 000000000000001 ) from the legend entries\r
+            @SuppressWarnings("unchecked")\r
+            Collection<String> legendEntries = CollectionUtils.collect(legendSortList, new Transformer()\r
+            {\r
+                @Override\r
+                public String transform(Object o)\r
+                {\r
+                  String s = ((String) o);\r
+                  return s.substring(INT_MAX_LENGTH, s.length());\r
+                }\r
+              });\r
+\r
+            parameters.put("title", StringUtils.join(legendEntries.iterator(), VALUE_LIST_ENTRY_SEPARATOR));\r
+        }\r
+\r
+        if(generateMultipleAreaDataParameters){\r
+            // not generically possible since parameters can not contain duplicate keys with value "ad"\r
+        } else {\r
+            parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));\r
+        }\r
+\r
+        String queryString = makeQueryString(parameters);\r
+        logger.debug("getDistributionServiceRequestParameterString(): " + queryString);\r
+\r
+        return queryString;\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills the layerMap and the statusList\r
+     *\r
+     * @param distributions\r
+     * @param layerMap see {@link #addAreaToLayerMap(Map, List, Distribution, NamedArea, IGeoServiceAreaMapping)}\r
+     * @param statusList\r
+     */\r
+    private static void groupStylesAndLayers(Collection<Distribution> distributions,\r
+            Map<String, Map<Integer,Set<Distribution>>> layerMap,\r
+            List<PresenceAbsenceTerm> statusList,\r
+            IGeoServiceAreaMapping mapping) {\r
+\r
+\r
+        //iterate through distributions and group styles and layers\r
+        //and collect necessary information\r
+        for (Distribution distribution : distributions){\r
+            //collect status\r
+            PresenceAbsenceTerm status = distribution.getStatus();\r
+            if(status == null){\r
+                status = defaultStatus;\r
+            }\r
+            if (! statusList.contains(status)){\r
+                statusList.add(status);\r
+            }\r
+            //group areas by layers and styles\r
+            NamedArea area = distribution.getArea();\r
+\r
+            addAreaToLayerMap(layerMap, statusList, distribution, area, mapping);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Adds the areas to the layer map. Areas which do not have layer information\r
+     * mapped to them are ignored.\r
+     * <p>\r
+     * A layer map holds the following information:\r
+     *\r
+     * <ul>\r
+     *   <li><b>String</b>: the WMSLayerName which matches the level of the\r
+     *   contained distributions areas</li>\r
+     *   <li><b>StyleMap</b>:</li>\r
+     *   <ul>\r
+     *     <li><b>Integer</b>: the index of the status in the\r
+     *     <code>statusList</code></li>\r
+     *     <li><b>Set{@code<Distribution>}</b>: the set of distributions having the\r
+     *     same Status, the status list is populated in {@link #groupStylesAndLayers(Set, Map, List, IGeoServiceAreaMapping)}</li>\r
+     *   </ul>\r
+     * </ul>\r
+     *\r
+     * @param layerMap\r
+     * @param statusList\r
+     * @param distribution\r
+     * @param area\r
+     */\r
+    private static void addAreaToLayerMap(Map<String, Map<Integer,\r
+            Set<Distribution>>> layerMap,\r
+            List<PresenceAbsenceTerm> statusList,\r
+            Distribution distribution,\r
+            NamedArea area,\r
+            IGeoServiceAreaMapping mapping) {\r
+\r
+        if (area != null){\r
+            String geoLayerName = getWMSLayerName(area, mapping);\r
+\r
+            if(geoLayerName == null){\r
+               /* IGNORE areas for which no layer is mapped */\r
+            } else {\r
+                Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerName);\r
+                if (styleMap == null) {\r
+                    styleMap = new HashMap<Integer, Set<Distribution>>();\r
+                    layerMap.put(geoLayerName, styleMap);\r
+                }\r
+                addDistributionToStyleMap(distribution, styleMap, statusList);\r
+            }\r
+        }\r
+    }\r
+\r
+\r
+\r
+    /**\r
+     * URI encode the given String\r
+     * @param string\r
+     * @return\r
+     */\r
+    private static String encode(String string) {\r
+        String encoded = string;\r
+        try {\r
+            encoded = URLEncoder.encode(string, "UTF-8");\r
+        } catch (UnsupportedEncodingException e) {\r
+            logger.error(e);\r
+        }\r
+        return encoded;\r
+    }\r
+\r
+    /**\r
+     * combine parameter into a URI query string fragment. The values will be\r
+     * escaped correctly.\r
+     *\r
+     * @param parameters\r
+     * @return a URI query string fragment\r
+     */\r
+    private static String makeQueryString(Map<String, String> parameters){\r
+        StringBuilder queryString = new StringBuilder();\r
+        for (String key : parameters.keySet()) {\r
+            if(queryString.length() > 0){\r
+                queryString.append('&');\r
+            }\r
+            if(key.equals("od") || key.equals("os") || key.equals("ms") || key.equals("ad") || key.equals("as") || key.equals("title") || key.equals("bbox")){\r
+                queryString.append(key).append('=').append(parameters.get(key));\r
+            } else {\r
+                queryString.append(key).append('=').append(encode(parameters.get(key)));\r
+            }\r
+        }\r
+        return queryString.toString();\r
+    }\r
+\r
+    private static String getAreaCode(Distribution distribution, IGeoServiceAreaMapping mapping){\r
+        NamedArea area = distribution.getArea();\r
+        TermVocabulary<NamedArea> voc = area.getVocabulary();\r
+        String result = null;\r
+\r
+        if (voc != null && voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary) ||  voc.getUuid().equals(Country.uuidCountryVocabulary)) {\r
+            // TDWG or Country\r
+            result = area.getIdInVocabulary();\r
+            if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())) {\r
+                result = result.replace("-", "");\r
+            }\r
+        } else {\r
+            // use generic GeoServiceArea data stored in technical annotations\r
+            // of the\r
+            // named area\r
+            GeoServiceArea areas = mapping.valueOf(area);\r
+            if ((areas != null) && areas.size() > 0) {\r
+                // FIXME multiple layers\r
+                List<String> values = areas.getAreasMap().values().iterator().next().values().iterator().next();\r
+                for (String value : values) {\r
+                    result = CdmUtils.concat(SUBENTRY_DELIMITER, result, value);\r
+                }\r
+            }\r
+\r
+        }\r
+        return CdmUtils.Nz(result, "-");\r
+\r
+    }\r
+\r
+    private static List<String> projectToWMSSubLayer(NamedArea area){\r
+\r
+        List<String> layerNames = new ArrayList<String>();\r
+        String matchedLayerName = null;\r
+        TermVocabulary<NamedArea> voc = area.getVocabulary();\r
+        //TDWG areas\r
+        if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){\r
+            NamedAreaLevel level = area.getLevel();\r
+            if (level != null) {\r
+                //TODO integrate into CDM\r
+                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {\r
+                    matchedLayerName = "tdwg1" ;\r
+                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {\r
+                    matchedLayerName = "tdwg2";\r
+                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {\r
+                    matchedLayerName = "tdwg3";\r
+                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {\r
+                    matchedLayerName = "tdwg4";\r
+                }\r
+            }\r
+            //unrecognized tdwg area\r
+\r
+        }\r
+        //TODO countries\r
+\r
+        // check if the matched layer equals the layer to project to\r
+        // if not: recurse into the sub-level in order to find the specified one.\r
+        String[] matchedLayerNameTokens = StringUtils.split(matchedLayerName, ':');\r
+//             if(matchedLayerNameTokens.length > 0 &&  matchedLayerNameTokens[0] != projectToLayer){\r
+//                     for (NamedArea subArea : area.getIncludes()){\r
+//\r
+//                     }\r
+            //\r
+            // add all sub areas\r
 //             }\r
-               //layer = "l=" + layer.substring(1); //remove first |\r
-               \r
-               \r
-               //style\r
-               areaStyle = "";\r
-               int i = 0;\r
-               for (PresenceAbsenceTermBase<?> status: statusList){\r
-                       \r
-                       char style = getStyleAbbrev(i);\r
-                       \r
-                       //getting the area title\r
-                       if (languages == null){\r
-                               languages = new ArrayList<Language>();\r
-                       }\r
-                       if (languages.size() == 0){\r
-                               languages.add(Language.DEFAULT());\r
-                       }\r
-                       Representation representation = status.getPreferredRepresentation(languages);\r
-                       String statusLabel = representation.getLabel();\r
-                       //statusLabel.replace('introduced: ', '');\r
-                       statusLabel = statusLabel.replace("introduced: ", "introduced, ");\r
-                       statusLabel = statusLabel.replace("native: ", "native,  ");\r
-                       areaTitle += "|" + style + ":" + statusLabel;\r
-                       \r
-                       //getting the area color\r
-                       Color statusColor = presenceAbsenceTermColors.get(status);\r
-                       String rgb;\r
-                       if (statusColor != null){\r
-                               rgb = Integer.toHexString(statusColor.getRGB()).substring(2);\r
-                       }else{\r
-                               if(status != null){\r
-                                       rgb = status.getDefaultColor(); //TODO\r
-                               } else {\r
-                                       rgb = defaultStatus.getDefaultColor();\r
-                               }\r
-                       }\r
-                       areaStyle += "|" + style + ":" + rgb;\r
-\r
-                       if (borderWidth >0){\r
-                               areaStyle += ",," + borderWidth;\r
-                       }\r
-\r
-                       i++;                    \r
-               }\r
-               \r
-               if(areaStyle.length() > 0){\r
-                       areaStyle = "as=" + encode(areaStyle.substring(1)); //remove first |\r
-               }\r
-               if(areaTitle.length() > 0){\r
-                       areaTitle = "title=" + encode(areaTitle.substring(1)); //remove first |\r
-               }\r
-               \r
-               boolean separateLevels = false; //FIXME as parameter\r
-               //areaData\r
-               areaData = "";\r
-               boolean isFirstLayer = true;\r
-               Map<String, String> resultMap = new HashMap<String, String>();\r
-               \r
-               for (String layerString : layerMap.keySet()){\r
-                       //Set<Distribution> layerDistributions = layerData.get(layerIndex);\r
-                       //int distributionListIndex = 1;\r
-                       areaData += (isFirstLayer? "" : "||") + layerString + adLayerSeparator;\r
-                       Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);\r
-                       boolean isFirstStyle = true;\r
-                       for (int style: styleMap.keySet()){\r
-                               char styleChar = getStyleAbbrev(style);\r
-                               areaData += (isFirstStyle? "" : styleInAreaDataSeparator) + styleChar + ":";\r
-                               Set<Distribution> distributionSet = styleMap.get(style);\r
-                               boolean isFirstDistribution = true;\r
-                               for (Distribution distribution: distributionSet){\r
-                                       String areaAbbrev = getAreaAbbrev(distribution);\r
-                                       areaData += (isFirstDistribution ? "" : ",") + areaAbbrev;\r
-                                       isFirstDistribution = false;\r
-                               }\r
-                               isFirstStyle = false;\r
-                       }\r
-                       isFirstLayer = separateLevels;\r
-                       if(separateLevels){\r
-                               //result per level\r
-                               result = CdmUtils.concat("&", new String[] {layer, "ad=" + areaData.substring(0), areaStyle, areaTitle, bbox, ms});\r
-                               //result = CdmUtils.concat("&", new String[] {layer, "ad=" + areaData.substring(0), areaStyle, bbox, ms});\r
-                               resultMap.put(layerString, result);\r
-                       }\r
-               }\r
-               \r
-               areaData = "ad=" + areaData.substring(0); //remove first |\r
-               \r
-               //result\r
-               result = CdmUtils.concat("&", new String[] {layer, areaData, areaStyle, areaTitle, bbox, ms});\r
-               //result = CdmUtils.concat("&", new String[] {layer, areaData, areaStyle, bbox, ms});\r
-               if (logger.isDebugEnabled()){logger.debug("getEditGeoServiceUrlParameterString end");}\r
-               \r
-               return result;\r
-       }\r
-\r
-       private static String mapSizeParameter(int width, int height) {\r
-\r
-               String widthStr = "";\r
-               String heightStr = "";\r
-\r
-               if (width > 0) {\r
-                       widthStr = "" + width;\r
-               }\r
-               if (height > 0) {\r
-                       heightStr = MS_SEPARATOR + height;\r
-               }\r
-               String ms = "ms=" + widthStr + heightStr;\r
-               if (ms.equals("ms=")) {\r
-                       ms = null;\r
-               }\r
-               return ms;\r
-       }\r
-\r
-       /**\r
-        * URI encode the given String\r
-        * @param string\r
-        * @return\r
-        */\r
-       private static String encode(String string) {\r
-               String encoded = string;\r
-               try {\r
-                       encoded = URLEncoder.encode(string, "UTF-8");\r
-               } catch (UnsupportedEncodingException e) {\r
-                       logger.error(e);\r
-               }\r
-               return encoded;\r
-       }\r
-       \r
-       /**\r
-        * combine parameter into a URI query string fragment. The values will be\r
-        * escaped correctly.\r
-        * \r
-        * @param parameters\r
-        * @return a URI query string fragment\r
-        */\r
-       private static String makeQueryString(Map<String, String> parameters){\r
-               StringBuilder queryString = new StringBuilder();\r
-               for (String key : parameters.keySet()) {\r
-                       if(queryString.length() > 0){\r
-                               queryString.append('&');\r
-                       }\r
-                       if(key.equals("od")){\r
-                               queryString.append(key).append('=').append(parameters.get(key));                                \r
-                       } else {\r
-                               queryString.append(key).append('=').append(encode(parameters.get(key)));\r
-                       }\r
-               }\r
-               return queryString.toString();\r
-       }\r
-       \r
-       private static Map<PresenceAbsenceTermBase<?>,Color> makeDefaultColorMap(){\r
-               Map<PresenceAbsenceTermBase<?>,Color> result = new HashMap<PresenceAbsenceTermBase<?>, Color>();\r
-               try {\r
-                       result.put(PresenceTerm.PRESENT(), Color.decode("0x4daf4a"));\r
-                       result.put(PresenceTerm.NATIVE(), Color.decode("0x4daf4a"));\r
-                       result.put(PresenceTerm.NATIVE_DOUBTFULLY_NATIVE(), Color.decode("0x377eb8"));\r
-                       result.put(PresenceTerm.CULTIVATED(), Color.decode("0x984ea3"));\r
-                       result.put(PresenceTerm.INTRODUCED(), Color.decode("0xff7f00"));\r
-                       result.put(PresenceTerm.INTRODUCED_ADVENTITIOUS(), Color.decode("0xffff33"));\r
-                       result.put(PresenceTerm.INTRODUCED_CULTIVATED(), Color.decode("0xa65628"));\r
-                       result.put(PresenceTerm.INTRODUCED_NATURALIZED(), Color.decode("0xf781bf"));\r
-               } catch (NumberFormatException nfe) {\r
-                       logger.error(nfe);\r
-               }\r
-               return result;\r
-       }\r
-       \r
-       \r
-       private static String getAreaAbbrev(Distribution distribution){\r
-               NamedArea area = distribution.getArea();\r
-               Representation representation = area.getRepresentation(Language.DEFAULT());\r
-               String areaAbbrev = representation.getAbbreviatedLabel();\r
-               if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())){\r
-                       areaAbbrev = areaAbbrev.replace("-", "");\r
-               }\r
-               return areaAbbrev;\r
-       }\r
-       \r
-       \r
-       /**\r
-        * transform an integer (style counter) into a valid character representing a style.\r
-        * 0-25 => a-z<br>\r
-        * 26-51 => A-Z<br>\r
-        * i not in {0,...,51} is undefined\r
-        * @param i\r
-        * @return\r
-        */\r
-       private static char getStyleAbbrev(int i){\r
-               i++;\r
-               int ascii = 96 + i;\r
-               if (i >26){\r
-                       ascii = 64 + i;\r
-               }\r
-               return (char)ascii;\r
-       }\r
-       \r
-       private static String getGeoServiceLayer(NamedAreaLevel level){\r
-               //TODO integrate into CDM \r
-               if (level.equals(NamedAreaLevel.TDWG_LEVEL1())){\r
-                       return "tdwg1";\r
-               }else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())){\r
-                       return "tdwg2";\r
-               }if (level.equals(NamedAreaLevel.TDWG_LEVEL3())){\r
-                       return "tdwg3";\r
-               }if (level.equals(NamedAreaLevel.TDWG_LEVEL4())){\r
-                       return "tdwg4";\r
-               }\r
-               return "unknown";\r
-       }\r
-       \r
-       \r
-       private static void addDistributionToMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,\r
-                       List<PresenceAbsenceTermBase<?>> statusList) {\r
-               PresenceAbsenceTermBase<?> status = distribution.getStatus();\r
-               if (status == null) {\r
-                       status = defaultStatus;\r
-               }\r
-               int style = statusList.indexOf(status);\r
-               Set<Distribution> distributionSet = styleMap.get(style);\r
-               if (distributionSet == null) {\r
-                       distributionSet = new HashSet<Distribution>();\r
-                       styleMap.put(style, distributionSet);\r
-               }\r
-               distributionSet.add(distribution);\r
-       }\r
-       \r
-\r
-       \r
-       /**\r
-        * @param args\r
-        */\r
-       public static void main(String[] args) {\r
-               // TODO Auto-generated method stub\r
-\r
-       }\r
-\r
-       /**\r
-        * @param fieldObservationPoints\r
-        * @param derivedUnitPoints\r
-        * @param specimenOrObservationTypeColors\r
-        * @param doReturnImage TODO\r
-        * @param width\r
-        * @param height\r
-        * @param bbox\r
-        * @param backLayer\r
-        * @return\r
-        * e.g.:\r
-        *      l=v%3Aatbi%2Ce_w_0\r
-        *  &legend=0\r
-        *  &image=false\r
-        *  &recalculate=false\r
-        *  &ms=400%2C350\r
-\r
-        *  &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\r
-        *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa\r
-        */\r
-       public static String getOccurrenceServiceRequestParameterString(\r
-                       List<Point> fieldObservationPoints,\r
-                       List<Point> derivedUnitPoints,\r
-                       Map<Class<? extends SpecimenOrObservationBase<?>>, Color> specimenOrObservationTypeColors,\r
-                       Boolean doReturnImage, Integer width, Integer height, String bbox, String backLayer) {\r
-               \r
-                       specimenOrObservationTypeColors = mergeMaps(defaultSpecimenOrObservationTypeColors, specimenOrObservationTypeColors);\r
-                       \r
-                       Map<String, String> parameters = new HashMap<String, String>();\r
-                       parameters.put("legend", "0");\r
-                       parameters.put("image", doReturnImage != null && doReturnImage ? "true" : "false");\r
-                       parameters.put("recalculate", "false"); // TODO add parameter to method\r
-                       if(bbox != null){\r
-                               parameters.put("bbox", bbox);\r
-                       }\r
-                       if(width != null || height != null){\r
-                               parameters.put("ms", mapSizeParameter(width, height));\r
-                       }\r
-                       \r
-                       Map<String, String> styleAndData = new HashMap<String, String>();\r
-                       \r
-                       addToStyleAndData(fieldObservationPoints, FieldObservation.class, specimenOrObservationTypeColors, styleAndData);\r
-                       addToStyleAndData(derivedUnitPoints, DerivedUnit.class, specimenOrObservationTypeColors, styleAndData);\r
-                       \r
-                       parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));\r
-                       parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));\r
-                       \r
-                       String queryString = makeQueryString(parameters);\r
-                       \r
-                       logger.info(queryString);\r
-                       \r
-               return queryString;\r
-       }\r
-\r
-       /**\r
-        * @param <T>\r
-        * @param <S>\r
-        * @param defaultMap\r
-        * @param overrideMap\r
-        * @return\r
-        */\r
-       private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {\r
-               Map<T, S> tmpMap = new HashMap<T, S>();\r
-               tmpMap.putAll(defaultMap);\r
-               if(overrideMap != null){                                \r
-                       tmpMap.putAll(overrideMap);\r
-               }\r
-               return tmpMap;\r
-       }\r
-\r
-       private static void addToStyleAndData(\r
-                       List<Point> points,\r
-                       Class<? extends SpecimenOrObservationBase<?>> specimenOrObservationType,\r
-                       Map<Class<? extends SpecimenOrObservationBase<?>>, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {\r
-\r
-               //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>\r
-               \r
-               if(points != null && points.size()>0){\r
-                       String style =  "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";\r
-                       StringBuilder data = new StringBuilder();\r
-                       for(Point point : points){\r
-                               if(data.length() > 0){\r
-                                       data.append('|');\r
-                               }\r
-                               data.append(point.getLatitude() + "," + point.getLongitude());\r
-                       }\r
-                       int index = styleAndData.size() + 1;\r
-                       styleAndData.put(index + ":" +style, index + ":" +data.toString());\r
-               }\r
-       }\r
+\r
+        return null;\r
+    }\r
+\r
+    private static String getWMSLayerName(NamedArea area, IGeoServiceAreaMapping mapping){\r
+        TermVocabulary<NamedArea> voc = area.getVocabulary();\r
+        //TDWG areas\r
+        if (voc.getUuid().equals(NamedArea.uuidTdwgAreaVocabulary)){\r
+            NamedAreaLevel level = area.getLevel();\r
+            if (level != null) {\r
+                //TODO integrate into CDM\r
+                if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {\r
+                    return "tdwg1";\r
+                } else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {\r
+                    return "tdwg2";\r
+                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {\r
+                    return "tdwg3";\r
+                }else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {\r
+                    return "tdwg4";\r
+                }\r
+            }\r
+            //unrecognized tdwg area\r
+            return null;\r
+\r
+        }else if (voc.getUuid().equals(Country.uuidCountryVocabulary)){\r
+            return "country_earth:gmi_cntry";\r
+        }\r
+\r
+        GeoServiceArea areas = mapping.valueOf(area);\r
+        if (areas != null && areas.getAreasMap().size() > 0){\r
+            //FIXME multiple layers\r
+            String layer = areas.getAreasMap().keySet().iterator().next();\r
+            Map<String, List<String>> fields = areas.getAreasMap().get(layer);\r
+            String field = fields.keySet().iterator().next();\r
+            String layerString = layer + ":" + field;\r
+            return layerString.toLowerCase();\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+\r
+    private static void addDistributionToStyleMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,\r
+            List<PresenceAbsenceTerm> statusList) {\r
+        PresenceAbsenceTerm status = distribution.getStatus();\r
+        if (status == null) {\r
+            status = defaultStatus;\r
+        }\r
+        int style = statusList.indexOf(status);\r
+        Set<Distribution> distributionSet = styleMap.get(style);\r
+        if (distributionSet == null) {\r
+            distributionSet = new HashSet<Distribution>();\r
+            styleMap.put(style, distributionSet);\r
+        }\r
+        distributionSet.add(distribution);\r
+    }\r
+\r
+    /**\r
+     * @param fieldUnitPoints\r
+     * @param derivedUnitPoints\r
+     * @param specimenOrObservationTypeColors\r
+     * @param width\r
+     * @param height\r
+     * @param bbox\r
+     * @param backLayer\r
+     * @return\r
+     * e.g.:\r
+     *         l=v%3Aatbi%2Ce_w_0\r
+     *  &legend=0\r
+     *  &image=false\r
+     *  &recalculate=false\r
+     *  &ms=400%2C350\r
+\r
+     *  &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\r
+     *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa\r
+     */\r
+    public static OccurrenceServiceRequestParameterDto getOccurrenceServiceRequestParameterString(\r
+            List<Point> fieldUnitPoints,\r
+            List<Point> derivedUnitPoints,\r
+            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors) {\r
+        OccurrenceServiceRequestParameterDto dto = new OccurrenceServiceRequestParameterDto();\r
+\r
+\r
+        specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);\r
+\r
+        Map<String, String> parameters = new HashMap<String, String>();\r
+        parameters.put("legend", "0");\r
+\r
+        Map<String, String> styleAndData = new HashMap<String, String>();\r
+\r
+        addToStyleAndData(fieldUnitPoints, SpecimenOrObservationType.FieldUnit, specimenOrObservationTypeColors, styleAndData);\r
+        addToStyleAndData(derivedUnitPoints, SpecimenOrObservationType.DerivedUnit, specimenOrObservationTypeColors, styleAndData);\r
+\r
+        parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));\r
+        parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));\r
+\r
+        String queryString = makeQueryString(parameters);\r
+\r
+        dto.setFieldUnitPoints(fieldUnitPoints);\r
+        dto.setDerivedUnitPoints(derivedUnitPoints);\r
+        dto.setOccurrenceQuery(queryString);\r
+\r
+        logger.info(queryString);\r
+\r
+        return dto;\r
+    }\r
+\r
+    /**\r
+     * @param <T>\r
+     * @param <S>\r
+     * @param defaultMap\r
+     * @param overrideMap\r
+     * @return\r
+     */\r
+    private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {\r
+        Map<T, S> tmpMap = new HashMap<T, S>();\r
+        tmpMap.putAll(defaultMap);\r
+        if(overrideMap != null){\r
+            tmpMap.putAll(overrideMap);\r
+        }\r
+        return tmpMap;\r
+    }\r
+\r
+    private static void addToStyleAndData(\r
+            List<Point> points,\r
+            SpecimenOrObservationType specimenOrObservationType,\r
+            Map<SpecimenOrObservationType, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {\r
+\r
+        //TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>\r
+\r
+        if(points != null && points.size()>0){\r
+            String style =  "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";\r
+            StringBuilder data = new StringBuilder();\r
+            for(Point point : points){\r
+                if(data.length() > 0){\r
+                    data.append('|');\r
+                }\r
+                data.append(point.getLatitude() + "," + point.getLongitude());\r
+            }\r
+            int index = styleAndData.size() + 1;\r
+            styleAndData.put(index + ":" +style, index + ":" +data.toString());\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * transform an integer (style counter) into a valid character representing a style.\r
+     * 0-25 => a-z<br>\r
+     * 26-51 => A-Z<br>\r
+     * i not in {0,...,51} is undefined\r
+     * @param i\r
+     * @return\r
+     */\r
+    private static char getStyleAbbrev(int i){\r
+        i++;\r
+        int ascii = 96 + i;\r
+        if (i >26){\r
+            ascii = 64 + i;\r
+        }\r
+        return (char)ascii;\r
+    }\r
+\r
+    /**\r
+     * @param statusColorJson for example: {@code {"n":"#ff0000","p":"#ffff00"}}\r
+     * @return\r
+     * @throws IOException\r
+     * @throws JsonParseException\r
+     * @throws JsonMappingException\r
+     */\r
+    public static Map<PresenceAbsenceTerm, Color> buildStatusColorMap(String statusColorJson, ITermService termService) throws IOException, JsonParseException,\r
+            JsonMappingException {\r
+\r
+        Map<PresenceAbsenceTerm, Color> presenceAbsenceTermColors = null;\r
+        if(StringUtils.isNotEmpty(statusColorJson)){\r
+\r
+            ObjectMapper mapper = new ObjectMapper();\r
+            // TODO cache the color maps to speed this up?\r
+\r
+            TypeFactory typeFactory = mapper.getTypeFactory();\r
+            MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, String.class);\r
+\r
+            Map<String,String> statusColorMap = mapper.readValue(statusColorJson, mapType);\r
+            UUID presenceTermVocabUuid = PresenceAbsenceTerm.NATIVE().getVocabulary().getUuid();\r
+            presenceAbsenceTermColors = new HashMap<PresenceAbsenceTerm, Color>();\r
+            PresenceAbsenceTerm paTerm = null;\r
+            for(String statusId : statusColorMap.keySet()){\r
+                try {\r
+                    Color color = Color.decode(statusColorMap.get(statusId));\r
+                    paTerm = termService.findByIdInVocabulary(statusId, presenceTermVocabUuid, PresenceAbsenceTerm.class);\r
+                    if(paTerm != null){\r
+                        presenceAbsenceTermColors.put(paTerm, color);\r
+                    }\r
+                } catch (NumberFormatException e){\r
+                    logger.error("Cannot decode color", e);\r
+                }\r
+            }\r
+        }\r
+        return presenceAbsenceTermColors;\r
+    }\r
+\r
+\r
+    /**\r
+     * @param filteredDistributions\r
+     * @param recipe\r
+     * @param hideMarkedAreas\r
+     * @param langs\r
+     * @return\r
+     */\r
+    public static CondensedDistribution getCondensedDistribution(Collection<Distribution> filteredDistributions,\r
+            CondensedDistributionRecipe recipe, List<Language> langs) {\r
+        ICondensedDistributionComposer composer;\r
+        if(recipe == null) {\r
+            throw new NullPointerException("parameter recipe must not be null");\r
+        }\r
+        try {\r
+            composer = recipe.newCondensedDistributionComposerInstance();\r
+        } catch (InstantiationException e) {\r
+            throw new RuntimeException(e);\r
+        } catch (IllegalAccessException e) {\r
+            throw new RuntimeException(e);\r
+        }\r
+        CondensedDistribution condensedDistribution = composer.createCondensedDistribution(\r
+                filteredDistributions,  langs);\r
+        return condensedDistribution;\r
+    }\r
+\r
 }\r