Project

General

Profile

Download (21.6 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
* Copyright (C) 2007 EDIT
4
* European Distributed Institute of Taxonomy 
5
* http://www.e-taxonomy.eu
6
* 
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.
9
*/
10

    
11
package eu.etaxonomy.cdm.ext.geo;
12

    
13
import java.awt.Color;
14
import java.io.UnsupportedEncodingException;
15
import java.net.URLEncoder;
16
import java.util.ArrayList;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.UUID;
23

    
24
import javax.persistence.Transient;
25

    
26
import org.apache.commons.lang.StringUtils;
27
import org.apache.log4j.Logger;
28
import org.springframework.stereotype.Component;
29

    
30
import eu.etaxonomy.cdm.common.CdmUtils;
31
import eu.etaxonomy.cdm.model.common.Language;
32
import eu.etaxonomy.cdm.model.common.Representation;
33
import eu.etaxonomy.cdm.model.common.TermVocabulary;
34
import eu.etaxonomy.cdm.model.description.Distribution;
35
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
36
import eu.etaxonomy.cdm.model.description.PresenceTerm;
37
import eu.etaxonomy.cdm.model.location.NamedArea;
38
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
39
import eu.etaxonomy.cdm.model.location.Point;
40
import eu.etaxonomy.cdm.model.location.TdwgArea;
41
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
42
import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
43
import eu.etaxonomy.cdm.model.occurrence.LivingBeing;
44
import eu.etaxonomy.cdm.model.occurrence.Observation;
45
import eu.etaxonomy.cdm.model.occurrence.Specimen;
46
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
47
import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
48

    
49
/**
50
 * @author a.mueller
51
 * @created 17.11.2008
52
 * @version 1.0
53
 */
54
@Component
55
public class EditGeoServiceUtilities {
56
	private static final Logger logger = Logger.getLogger(EditGeoServiceUtilities.class);
57

    
58
	private static PresenceAbsenceTermBase<?> defaultStatus = PresenceTerm.PRESENT();
59
	
60
	private static IDefinedTermDao termDao;
61
	
62
	/**
63
	 * @param termDao
64
	 */
65
	public static void setTermDao(IDefinedTermDao termDao) {
66
		EditGeoServiceUtilities.termDao= termDao;
67
	}
68
	
69
	private static HashMap<Class<? extends SpecimenOrObservationBase>, Color> defaultSpecimenOrObservationTypeColors = null;
70

    
71
	private static HashMap<Class<? extends SpecimenOrObservationBase>, Color> getDefaultSpecimenOrObservationTypeColors() {
72
		if(defaultSpecimenOrObservationTypeColors == null){
73
			defaultSpecimenOrObservationTypeColors = new HashMap<Class<? extends SpecimenOrObservationBase>, Color>();
74
			defaultSpecimenOrObservationTypeColors.put(FieldObservation.class, Color.ORANGE);
75
			defaultSpecimenOrObservationTypeColors.put(DerivedUnit.class, Color.RED);
76
			defaultSpecimenOrObservationTypeColors.put(LivingBeing.class, Color.GREEN);
77
			defaultSpecimenOrObservationTypeColors.put(Observation.class, Color.ORANGE);
78
			defaultSpecimenOrObservationTypeColors.put(Specimen.class, Color.GRAY);			
79
		}
80
		return defaultSpecimenOrObservationTypeColors;
81
	}
82

    
83

    
84
	private static HashMap<PresenceAbsenceTermBase<?>, Color> defaultPresenceAbsenceTermBaseColors = null;
85

    
86
	private static HashMap<PresenceAbsenceTermBase<?>, Color> getDefaultPresenceAbsenceTermBaseColors() {
87
		if(defaultPresenceAbsenceTermBaseColors == null){
88
			defaultPresenceAbsenceTermBaseColors = new HashMap<PresenceAbsenceTermBase<?>, Color>();
89
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.PRESENT(), Color.decode("0x4daf4a"));
90
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.NATIVE(), Color.decode("0x4daf4a"));
91
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.NATIVE_DOUBTFULLY_NATIVE(), Color.decode("0x377eb8"));
92
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.CULTIVATED(), Color.decode("0x984ea3"));
93
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED(), Color.decode("0xff7f00"));
94
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_ADVENTITIOUS(), Color.decode("0xffff33"));
95
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_CULTIVATED(), Color.decode("0xa65628"));
96
			defaultPresenceAbsenceTermBaseColors.put(PresenceTerm.INTRODUCED_NATURALIZED(), Color.decode("0xf781bf"));	
97
			
98
			/*
99
			 * and now something very hacky ...
100
			 * ONLY-A-TEST is set by the Test class EditGeoServiceTest
101
			 * 
102
			 * TODO remove according line from
103
			 * EditGeoServiceTest.setUp() when the hardcoded colors for flora of
104
			 * cyprus are no further needed !!
105
			 */
106
			String onlyTest = System.getProperty("ONLY-A-TEST"); // 
107
			if(onlyTest != null && onlyTest.equals("TRUE")){
108
				return defaultPresenceAbsenceTermBaseColors;
109
			}
110
			//special colors for flora of cyprus !!! see HACK above !!!
111
			UUID indigenousUuid = UUID.fromString("b325859b-504b-45e0-9ef0-d5c1602fcc0f");
112
			UUID indigenousQUuid = UUID.fromString("17bc601f-53eb-4997-a4bc-c03ce5bfd1d3");
113
			
114
			UUID cultivatedQUuid = UUID.fromString("4f31bfc8-3058-4d83-aea5-3a1fe9773f9f");
115
			
116
			UUID casualUuid = UUID.fromString("5e81353c-38a3-4ca6-b979-0d9abc93b877");
117
			UUID casualQUuid = UUID.fromString("73f75493-1185-4a3e-af1e-9a1f2e8dadb7");
118
			
119
			UUID naturalizedNonInvasiveUuid = UUID.fromString("1b025e8b-901a-42e8-9739-119b410c6f03");
120
			UUID naturalizedNonInvasiveQUuid = UUID.fromString("11f56e2f-c16c-4b3d-a870-bb5d3b20e624");
121
			
122
			UUID naturalizedInvasiveUuid = UUID.fromString("faf2d271-868a-4bf7-b0b8-a1c5ab309de2");
123
			UUID naturalizedInvasiveQUuid = UUID.fromString("ac429d5f-e8ad-49ae-a41c-e4779b58b96a");
124
			
125
			UUID questionablelUuid = UUID.fromString("4b48f675-a6cf-49f3-a5ba-77e2c2979eb3");
126
			UUID questionableQUuid = UUID.fromString("914e7393-1314-4632-bc45-5eff3dc1e424");
127
			
128
			UUID reportedInErrorUuid = UUID.fromString("38604788-cf05-4607-b155-86db456f7680");
129
			
130
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(indigenousUuid), Color.decode("0x339966"));
131
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(indigenousQUuid), Color.decode("0x339966"));
132
			
133
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(cultivatedQUuid), Color.decode("0xbdb76b"));
134
			
135
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(casualUuid), Color.decode("0xffff00"));
136
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(casualQUuid), Color.decode("0xffff00"));
137
			
138
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedNonInvasiveUuid), Color.decode("0xff9900"));
139
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedNonInvasiveQUuid), Color.decode("0xff9900"));
140
			
141
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedInvasiveUuid), Color.decode("0xff0000"));
142
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(naturalizedInvasiveQUuid), Color.decode("0xff0000"));
143
			
144
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(questionablelUuid), Color.decode("0x00ccff"));
145
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(questionableQUuid), Color.decode("0x00ccff"));
146
			
147
			defaultPresenceAbsenceTermBaseColors.put((PresenceAbsenceTermBase<?>) termDao.load(reportedInErrorUuid), Color.decode("0xcccccc"));
148
			
149
		}
150
		return defaultPresenceAbsenceTermBaseColors;
151
	}
152

    
153

    
154

    
155
	private static final String SUBENTRY_DELIMITER = ",";
156
	private static final String ENTRY_DELIMITER = ";";
157
	static final String ID_FROM_VALUES_SEPARATOR = ":";
158
	static final String VALUE_LIST_ENTRY_SEPARATOR = "|";
159
	static final String VALUE_SUPER_LIST_ENTRY_SEPARATOR = "||";
160
	
161

    
162

    
163
	//preliminary implementation for TDWG areas
164
	/**
165
	 * Returns the parameter String for the EDIT geo webservice to create a
166
	 * dsitribution map.
167
	 * 
168
	 * @param distributions
169
	 *            A set of distributions that should be shown on the map
170
	 * @param presenceAbsenceTermColors
171
	 *            A map that defines the colors of PresenceAbsenceTerms. The
172
	 *            PresenceAbsenceTerms are defined by their uuid. If a
173
	 *            PresenceAbsenceTerm is not included in this map, it's default
174
	 *            color is taken instead. If the map == null all terms are
175
	 *            colored by their default color.
176
	 * @param width
177
	 *            The maps width
178
	 * @param height
179
	 *            The maps height
180
	 * @param bbox
181
	 *            The maps bounding box (e.g. "-180,-90,180,90" for the whole
182
	 *            world)
183
	 * @param layer
184
	 *            The layer that is responsible for background borders and
185
	 *            colors. Use the name for the layer. If null 'earth' is taken
186
	 *            as default.
187
	 * @return the parameter string or an empty string if the
188
	 *         <code>distributions</code> set was null or empty.
189
	 */
190
	@Transient
191
	public static String getDistributionServiceRequestParameterString(
192
			Set<Distribution> distributions, 
193
			Map<PresenceAbsenceTermBase<?>,Color> presenceAbsenceTermColors, 
194
			int width, 
195
			int height, 
196
			String bbox, 
197
			String backLayer,
198
			List<Language> languages){
199
		
200

    
201
		/**
202
		 * generateMultipleAreaDataParameters switches between the two possible styles:
203
		 * 1. ad=layername1:area-data||layername2:area-data
204
		 * 2. ad=layername1:area-data&ad=layername2:area-data
205
		 */
206
		boolean generateMultipleAreaDataParameters = false;
207

    
208
		List<String>  perLayerAreaData = new ArrayList<String>();
209
		List<String> areaStyles = new ArrayList<String>();
210
		List<String> legendLabels = new ArrayList<String>();
211

    
212
		
213
		String borderWidth = "0.1";
214
		String borderColorRgb = "";
215
		String borderDashingPattern = "";
216
		
217
		
218
		if(distributions == null || distributions.size() == 0){
219
			return "";
220
		}
221
		Map<String, Map<Integer, Set<Distribution>>> layerMap = new HashMap<String, Map<Integer, Set<Distribution>>>(); 
222
		List<PresenceAbsenceTermBase<?>> statusList = new ArrayList<PresenceAbsenceTermBase<?>>();
223
		groupStylesAndLayers(distributions, layerMap, statusList);
224
				
225
		presenceAbsenceTermColors = mergeMaps(getDefaultPresenceAbsenceTermBaseColors(), presenceAbsenceTermColors);
226

    
227
		Map<String, String> parameters = new HashMap<String, String>();
228
		
229
		//bbox
230
		if (bbox != null){
231
			parameters.put("bbox", bbox);
232
		}
233
		// map size
234
		String ms = compileMapSizeParameterValue(width, height);
235
		if(ms != null){
236
			parameters.put("ms", ms);
237
		}
238
		//layer
239
		if (StringUtils.isBlank(backLayer)){
240
			backLayer = "earth"; 
241
		}
242
		parameters.put("l", backLayer);
243
		
244
		//style
245
		int i = 0;
246
		for (PresenceAbsenceTermBase<?> status: statusList){
247
			
248
			char styleId = getStyleAbbrev(i);
249
			
250
			//getting the area title
251
			if (languages == null){
252
				languages = new ArrayList<Language>();
253
			}
254
			if (languages.size() == 0){
255
				languages.add(Language.DEFAULT());
256
			}
257
			Representation representation = status.getPreferredRepresentation(languages);
258
			String statusLabel = representation.getLabel();
259
			//statusLabel.replace('introduced: ', '');
260
			statusLabel = statusLabel.replace("introduced: ", "introduced, ");
261
			statusLabel = statusLabel.replace("native: ", "native,  ");
262
			
263
			//getting the area color
264
			Color statusColor = presenceAbsenceTermColors.get(status);
265
			String fillColorRgb;
266
			if (statusColor != null){
267
				fillColorRgb = Integer.toHexString(statusColor.getRGB()).substring(2);
268
			}else{
269
				if(status != null){
270
					fillColorRgb = status.getDefaultColor(); //TODO
271
				} else {
272
					fillColorRgb = defaultStatus.getDefaultColor();
273
				}
274
			}
275
			String styleValues = StringUtils.join(new String[]{fillColorRgb, borderColorRgb, borderWidth, borderDashingPattern}, ',');			
276

    
277
			areaStyles.add(styleId + ID_FROM_VALUES_SEPARATOR + styleValues);
278
			legendLabels.add(styleId + ID_FROM_VALUES_SEPARATOR + encode(statusLabel));
279
			i++;			
280
		}
281
		
282
		if(areaStyles.size() > 0){
283
			parameters.put("as", StringUtils.join(areaStyles.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
284
		}
285
		if(legendLabels.size() > 0){
286
			parameters.put("title", StringUtils.join(legendLabels.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
287
		}
288
		
289
		// area data
290
		List<String> stylesPerLayer;
291
		List<String> areasPerStyle;
292
		for (String layerString : layerMap.keySet()){
293
			// each layer
294
			stylesPerLayer = new ArrayList<String>();
295
			Map<Integer, Set<Distribution>> styleMap = layerMap.get(layerString);
296
			for (int style: styleMap.keySet()){
297
				// stylesPerLayer
298
				char styleChar = getStyleAbbrev(style);
299
				Set<Distribution> distributionSet = styleMap.get(style);
300
				areasPerStyle = new ArrayList<String>();
301
				for (Distribution distribution: distributionSet){
302
					// areasPerStyle
303
					areasPerStyle.add(getAreaAbbrev(distribution));
304
				}
305
				stylesPerLayer.add(styleChar + ID_FROM_VALUES_SEPARATOR + StringUtils.join(areasPerStyle.iterator(), SUBENTRY_DELIMITER));
306
			}
307
			perLayerAreaData.add(encode(layerString) + ID_FROM_VALUES_SEPARATOR + StringUtils.join(stylesPerLayer.iterator(), VALUE_LIST_ENTRY_SEPARATOR));
308
		}
309
		
310
		if(generateMultipleAreaDataParameters){
311
			// not generically possible since parameters can not contain duplicate keys with value "ad"
312
		} else {
313
			parameters.put("ad", StringUtils.join(perLayerAreaData.iterator(), VALUE_SUPER_LIST_ENTRY_SEPARATOR));
314
		}
315

    
316
		String queryString = makeQueryString(parameters);
317
		logger.debug("getDistributionServiceRequestParameterString(): " + queryString);
318
		
319
		return queryString;
320
	}
321

    
322
	private static void groupStylesAndLayers(Set<Distribution> distributions,
323
			Map<String, Map<Integer, Set<Distribution>>> layerMap,
324
			List<PresenceAbsenceTermBase<?>> statusList) {
325
		//iterate through distributions and group styles and layers
326
		//and collect necessary information
327
		for (Distribution distribution:distributions){
328
			//collect status
329
			PresenceAbsenceTermBase<?> status = distribution.getStatus();
330
			if(status == null){
331
				status = defaultStatus;
332
			}
333
			if (! statusList.contains(status)){
334
				statusList.add(status);
335
			}
336
			//group by layers and styles
337
			NamedArea area = distribution.getArea();
338
			if (area != null){
339
				String geoLayerString = getWMSLayerName(area);
340
				if(geoLayerString != null){
341
					Map<Integer, Set<Distribution>> styleMap = layerMap.get(geoLayerString);
342
					if (styleMap == null){
343
						styleMap = new HashMap<Integer, Set<Distribution>>();
344
						layerMap.put(geoLayerString, styleMap);
345
					}
346
					addDistributionToMap(distribution, styleMap, statusList);
347
				}
348
			}
349
		}
350
	}
351

    
352
	private static String compileMapSizeParameterValue(int width, int height) {
353

    
354
		String widthStr = "";
355
		String heightStr = "";
356

    
357
		if (width > 0) {
358
			widthStr = "" + width;
359
		}
360
		if (height > 0) {
361
			heightStr = SUBENTRY_DELIMITER + height;
362
		}
363
		String ms = widthStr + heightStr;
364
		if(ms.length() == 0){
365
			ms = null;
366
		}
367
		return ms;
368
	}
369

    
370
	/**
371
	 * URI encode the given String
372
	 * @param string
373
	 * @return
374
	 */
375
	private static String encode(String string) {
376
		String encoded = string;
377
		try {
378
			encoded = URLEncoder.encode(string, "UTF-8");
379
		} catch (UnsupportedEncodingException e) {
380
			logger.error(e);
381
		}
382
		return encoded;
383
	}
384
	
385
	/**
386
	 * combine parameter into a URI query string fragment. The values will be
387
	 * escaped correctly.
388
	 * 
389
	 * @param parameters
390
	 * @return a URI query string fragment
391
	 */
392
	private static String makeQueryString(Map<String, String> parameters){
393
		StringBuilder queryString = new StringBuilder();
394
		for (String key : parameters.keySet()) {
395
			if(queryString.length() > 0){
396
				queryString.append('&');
397
			}
398
			if(key.equals("od") || key.equals("os") || key.equals("ms") || key.equals("ad") || key.equals("as") || key.equals("title") || key.equals("bbox")){
399
				queryString.append(key).append('=').append(parameters.get(key));				
400
			} else {
401
				queryString.append(key).append('=').append(encode(parameters.get(key)));
402
			}
403
		}
404
		return queryString.toString();
405
	}
406
	
407
	private static String getAreaAbbrev(Distribution distribution){
408
		NamedArea area = distribution.getArea();
409
		Representation representation = area.getRepresentation(Language.DEFAULT());
410
		String areaAbbrev = representation.getAbbreviatedLabel();
411
		if (area.getLevel() != null && area.getLevel().equals(NamedAreaLevel.TDWG_LEVEL4())){
412
			areaAbbrev = areaAbbrev.replace("-", "");
413
		}
414
		return CdmUtils.Nz(areaAbbrev, "-");
415
	}
416
	
417

    
418
	//Preliminary as long as user defined areas are not fully implemented  
419
	public static final UUID uuidCyprusDivisionsVocabulary = UUID.fromString("2119f610-1f93-4d87-af28-40aeefaca100");
420
	
421
	private static String getWMSLayerName(NamedArea area){
422
		TermVocabulary<NamedArea> voc = area.getVocabulary();
423
		//TDWG areas
424
		if (voc.getUuid().equals(TdwgArea.uuidTdwgAreaVocabulary)){
425
			NamedAreaLevel level = area.getLevel();
426
			if (level != null) {
427
				//TODO integrate into CDM 
428
				if (level.equals(NamedAreaLevel.TDWG_LEVEL1())) {
429
					return "tdwg1";
430
				} else if (level.equals(NamedAreaLevel.TDWG_LEVEL2())) {
431
					return "tdwg2";
432
				}else if (level.equals(NamedAreaLevel.TDWG_LEVEL3())) {
433
					return "tdwg3";
434
				}else if (level.equals(NamedAreaLevel.TDWG_LEVEL4())) {
435
					return "tdwg4";
436
				}
437
			}
438
			//unrecognized tdwg area
439
			return null;
440
		
441
		}
442
		//hardcoded for cyprus (as long as user defined areas are not fully implemented). Remove afterwards.
443
		if (voc.getUuid().equals(uuidCyprusDivisionsVocabulary)){
444
			return "cyprusdivs:bdcode";
445
		}
446
		return null;
447
	}
448
	
449
	
450
	private static void addDistributionToMap(Distribution distribution, Map<Integer, Set<Distribution>> styleMap,
451
			List<PresenceAbsenceTermBase<?>> statusList) {
452
		PresenceAbsenceTermBase<?> status = distribution.getStatus();
453
		if (status == null) {
454
			status = defaultStatus;
455
		}
456
		int style = statusList.indexOf(status);
457
		Set<Distribution> distributionSet = styleMap.get(style);
458
		if (distributionSet == null) {
459
			distributionSet = new HashSet<Distribution>();
460
			styleMap.put(style, distributionSet);
461
		}
462
		distributionSet.add(distribution);
463
	}
464
	
465
	/**
466
	 * @param fieldObservationPoints
467
	 * @param derivedUnitPoints
468
	 * @param specimenOrObservationTypeColors
469
	 * @param doReturnImage TODO
470
	 * @param width
471
	 * @param height
472
	 * @param bbox
473
	 * @param backLayer
474
	 * @return
475
	 * e.g.:
476
	 * 	l=v%3Aatbi%2Ce_w_0
477
	 *  &legend=0
478
	 *  &image=false
479
	 *  &recalculate=false
480
	 *  &ms=400%2C350
481

    
482
	 *  &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
483
	 *  &os=1%3Ac%2FFFD700%2F10%2FAporrectodea caliginosa
484
	 */
485
	public static String getOccurrenceServiceRequestParameterString(
486
			List<Point> fieldObservationPoints,
487
			List<Point> derivedUnitPoints,
488
			Map<Class<? extends SpecimenOrObservationBase>, Color> specimenOrObservationTypeColors,
489
			Boolean doReturnImage, Integer width, Integer height, String bbox, String backLayer) {
490
		
491
			specimenOrObservationTypeColors = mergeMaps(getDefaultSpecimenOrObservationTypeColors(), specimenOrObservationTypeColors);
492
			
493
			Map<String, String> parameters = new HashMap<String, String>();
494
			parameters.put("legend", "0");
495
			parameters.put("image", doReturnImage != null && doReturnImage ? "true" : "false");
496
			parameters.put("recalculate", "false"); // TODO add parameter to method
497
			if(bbox != null){
498
				parameters.put("bbox", bbox);
499
			}
500
			if(width != null || height != null){
501
				parameters.put("ms", compileMapSizeParameterValue(width, height));
502
			}
503
			
504
			Map<String, String> styleAndData = new HashMap<String, String>();
505
			
506
			addToStyleAndData(fieldObservationPoints, FieldObservation.class, specimenOrObservationTypeColors, styleAndData);
507
			addToStyleAndData(derivedUnitPoints, DerivedUnit.class, specimenOrObservationTypeColors, styleAndData);
508
			
509
			parameters.put("os", StringUtils.join(styleAndData.keySet().iterator(), "||"));
510
			parameters.put("od", StringUtils.join(styleAndData.values().iterator(), "||"));
511
			
512
			String queryString = makeQueryString(parameters);
513
			
514
			logger.info(queryString);
515
			
516
		return queryString;
517
	}
518

    
519
	/**
520
	 * @param <T>
521
	 * @param <S>
522
	 * @param defaultMap
523
	 * @param overrideMap
524
	 * @return
525
	 */
526
	private static <T, S> Map<T, S> mergeMaps(Map<T, S> defaultMap, Map<T, S> overrideMap) {
527
		Map<T, S> tmpMap = new HashMap<T, S>();
528
		tmpMap.putAll(defaultMap);
529
		if(overrideMap != null){				
530
			tmpMap.putAll(overrideMap);
531
		}
532
		return tmpMap;
533
	}
534

    
535
	private static void addToStyleAndData(
536
			List<Point> points,
537
			Class<? extends SpecimenOrObservationBase> specimenOrObservationType,
538
			Map<Class<? extends SpecimenOrObservationBase>, Color> specimenOrObservationTypeColors, Map<String, String> styleAndData) {
539

    
540
		//TODO add markerShape and size and Label to specimenOrObservationTypeColors -> Map<Class<SpecimenOrObservationBase<?>>, MapStyle>
541
		
542
		if(points != null && points.size()>0){
543
			String style =  "c/" + Integer.toHexString(specimenOrObservationTypeColors.get(specimenOrObservationType).getRGB()).substring(2) + "/10/noLabel";
544
			StringBuilder data = new StringBuilder();
545
			for(Point point : points){
546
				if(data.length() > 0){
547
					data.append('|');
548
				}
549
				data.append(point.getLatitude() + "," + point.getLongitude());
550
			}
551
			int index = styleAndData.size() + 1;
552
			styleAndData.put(index + ":" +style, index + ":" +data.toString());
553
		}
554
	}
555
	
556
	
557
	/**
558
	 * transform an integer (style counter) into a valid character representing a style.
559
	 * 0-25 => a-z<br>
560
	 * 26-51 => A-Z<br>
561
	 * i not in {0,...,51} is undefined
562
	 * @param i
563
	 * @return
564
	 */
565
	private static char getStyleAbbrev(int i){
566
		i++;
567
		int ascii = 96 + i;
568
		if (i >26){
569
			ascii = 64 + i;
570
		}
571
		return (char)ascii;
572
	}
573

    
574
}
(2-2/3)