Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.mapcontext / org.gvsig.fmap.mapcontext.api / src / main / java / org / gvsig / fmap / mapcontext / rendering / symbols / impl / DefaultSymbolManager.java @ 41053

History | View | Annotate | Download (17.8 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
/*
25
 * AUTHORS (In addition to CIT):
26
 * 2009 {DiSiD Technologies}  {{Task}}
27
 * 
28
 **************************************************************
29
 * Colors from www.ColorBrewer.org by Cynthia A. Brewer,
30
 * Geography, Pennsylvania State University.
31
 * Using groups of 4 colors from the 9 Diverging Schemes
32
 * 4 * 9 = 36 colors
33
 **************************************************************
34
 * 
35
 */
36
package org.gvsig.fmap.mapcontext.rendering.symbols.impl;
37

    
38
import java.awt.Color;
39
import java.io.File;
40
import java.io.FileFilter;
41
import java.io.FileInputStream;
42
import java.io.FileOutputStream;
43
import java.io.IOException;
44
import java.util.Arrays;
45
import java.util.Collections;
46
import java.util.Comparator;
47
import java.util.HashMap;
48
import java.util.Map;
49
import java.util.Random;
50

    
51
import org.gvsig.fmap.mapcontext.MapContextRuntimeException;
52
import org.gvsig.fmap.mapcontext.impl.InvalidRegisteredClassException;
53
import org.gvsig.fmap.mapcontext.impl.RegisteredClassInstantiationException;
54
import org.gvsig.fmap.mapcontext.rendering.symbols.IMultiLayerSymbol;
55
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
56
import org.gvsig.fmap.mapcontext.rendering.symbols.IWarningSymbol;
57
import org.gvsig.fmap.mapcontext.rendering.symbols.SymbolException;
58
import org.gvsig.fmap.mapcontext.rendering.symbols.SymbolManager;
59
import org.gvsig.fmap.mapcontext.rendering.symbols.SymbolPreferences;
60
import org.gvsig.tools.ToolsLocator;
61
import org.gvsig.tools.exception.BaseException;
62
import org.gvsig.tools.persistence.PersistenceManager;
63
import org.gvsig.tools.persistence.PersistentState;
64
import org.gvsig.tools.persistence.exception.PersistenceException;
65
import org.gvsig.tools.task.AbstractMonitorableTask;
66
import org.gvsig.tools.task.CancellableTask;
67
import org.gvsig.tools.task.SimpleTaskStatus;
68
import org.gvsig.tools.task.TaskStatusManager;
69
import org.gvsig.tools.visitor.VisitCanceledException;
70
import org.gvsig.tools.visitor.Visitor;
71
import org.slf4j.Logger;
72
import org.slf4j.LoggerFactory;
73

    
74
/**
75
 * Default {@link SymbolManager} implementation.
76
 * 
77
 * @author gvSIG team
78
 */
79
public class DefaultSymbolManager implements SymbolManager {
80

    
81
        private static final Logger logger = LoggerFactory.getLogger(DefaultSymbolManager.class);
82
        
83
        private SymbolPreferences symbolPreferences =
84
                        new DefaultSymbolPreferences();
85

    
86
        private Map symbolsByName = Collections.synchronizedMap(new HashMap());
87

    
88
        private Map symbolsByShapeType = Collections.synchronizedMap(new HashMap());
89

    
90
        private Map multiLineSymbolsByName =
91
                        Collections.synchronizedMap(new HashMap());
92

    
93
        private Map multiLineSymbolsByShapeType =
94
                        Collections.synchronizedMap(new HashMap());
95

    
96
        private IWarningSymbol warningSymbol;
97

    
98
        private Object warningSymbolLock = new Object();
99

    
100
        public ISymbol[] loadSymbols(File folder) throws SymbolException {
101
                return loadSymbols(folder, null);
102
        }
103

    
104
        public ISymbol[] loadSymbols(File folder, FileFilter fileFilter)
105
                        throws SymbolException {
106
                // TODO: add symbol caching
107

    
108
                if (folder.exists()) {
109

    
110
                        File[] symbolFiles = null;
111
                        if (fileFilter == null) {
112
                                symbolFiles = folder.listFiles();
113
                        } else {
114
                                symbolFiles = folder.listFiles(fileFilter);
115
                        }
116

    
117
                        if (symbolFiles != null) {
118
                            TaskStatusManager tm = ToolsLocator.getTaskStatusManager();
119
                            SimpleTaskStatus status = tm.createDefaultSimpleTaskStatus("Load symbols");
120
                            status.setRangeOfValues(0, symbolFiles.length);
121
                            status.setAutoremove(true);
122
                            status.add();
123
                            
124
                            /*
125
                             * Sorting by file name before loading.
126
                             * The problem here is that some
127
                             * descriptions can be empty, so sorting using description
128
                             * can have strange behavior. Sorting by file name is not
129
                             * very elegant, though.
130
                             */
131
                status.message("sorting symbols");
132
                                Arrays.sort(symbolFiles, new Comparator() {
133
                                        public int compare( Object o1, Object o2) {
134
                                                File f1 = (File) o1;
135
                                                File f2 = (File) o2;
136
                                                return f1.getName().compareTo(f2.getName());
137
                                        }
138
                                });
139

    
140
                            ISymbol[] symbols = null;
141
                            try {
142
                        symbols = new ISymbol[symbolFiles.length];
143
                        for (int i = 0; i < symbolFiles.length; i++) {
144
                            status.setCurValue(i);
145
                            symbols[i] = loadSymbol(symbolFiles[i]);
146
                            
147
                        }
148

    
149
                            } finally {
150
                                status.terminate();
151
                            }
152
                                return symbols;
153
                        }
154
                }
155

    
156
                return null;
157
        }
158

    
159
        public class SymbolsLoaderTask extends AbstractMonitorableTask {
160
        
161
                private File folder;
162
                private FileFilter filter;
163
                private Visitor visitor;
164

    
165
                public SymbolsLoaderTask(File folder, FileFilter filter, Visitor visitor) {
166
                        super("Load symbols", true);
167
                        this.folder = folder; 
168
                        this.filter = filter;
169
                        this.visitor = visitor;
170
                        if( folder == null ) {
171
                                throw new IllegalArgumentException("folder is null");
172
                        }
173
                        if( visitor == null ) {
174
                                throw new IllegalArgumentException("visitor is null");
175
                        }
176
                }
177
                
178
                private File[] getFiles() {
179
                        if (filter == null) {
180
                                return folder.listFiles();
181
                        } else {
182
                                return folder.listFiles(filter);
183
                        }
184
                }
185

    
186
                private String getVisitorName() {
187
                        String s = visitor.toString() + "/" + visitor.getClass().getName();
188
                        return s;
189
                }
190
                
191
                private void visit(final File file, final ISymbol symbol) throws VisitCanceledException {
192
                        try {
193
                                visitor.visit(symbol);
194
                        } catch (BaseException e) {
195
                                logger.warn("Can't call visit '"+getVisitorName()+"' to offer the symbol '"+file.getAbsolutePath()+"'.",e);
196
                        }
197
                }
198
                
199
                public void run() {
200
                        // TODO: add symbol caching
201
                        try {
202
                                logger.info("[SymbolsLoaderTask"+this.getId()+"] process initited.");
203
                                if (folder.exists()) {
204
                                        taskStatus.setAutoremove(true);
205
                                        taskStatus.message("preparing");
206
                                        File[] symbolFiles = getFiles();
207

    
208
                                        if (symbolFiles != null) {
209
                                                taskStatus.setRangeOfValues(0, symbolFiles.length);
210

    
211
                                                /*
212
                                                 * Sorting by file name before loading. The problem here
213
                                                 * is that some descriptions can be empty, so sorting
214
                                                 * using description can have strange behavior. Sorting
215
                                                 * by file name is not very elegant, though.
216
                                                 */
217
                                                taskStatus.message("sorting");
218
                                                Arrays.sort(symbolFiles, new Comparator() {
219
                                                        public int compare(Object o1, Object o2) {
220
                                                                File f1 = (File) o1;
221
                                                                File f2 = (File) o2;
222
                                                                return f1.getName().compareTo(f2.getName());
223
                                                        }
224
                                                });
225

    
226
                                                taskStatus.message("loading");
227
                                                for (int i = 0; i < symbolFiles.length; i++) {
228
                                                        taskStatus.setCurValue(i);
229
                                                        if( taskStatus.isCancellationRequested() ) {
230
                                                                logger.info("[SymbolsLoaderTask"+this.getId()+"] process canceled.");
231
                                                                break;
232
                                                        }
233
                                                        ISymbol symbol = null;
234
                                                        try {
235
                                                                symbol = loadSymbol(symbolFiles[i]);
236
                                                                visit(symbolFiles[i], symbol);
237
                                                        } catch (VisitCanceledException e) {
238
                                                                break;
239
                                                        } catch (SymbolException e) {
240
                                                                logger.warn("Can't load symbol '"
241
                                                                                + symbolFiles[i].getAbsolutePath()
242
                                                                                + "'.", e);
243
                                                        }
244
                                                }
245
                                                taskStatus.message("");
246

    
247
                                        }
248
                                }
249

    
250
                        } finally {
251
                                taskStatus.terminate();
252
                        }
253
                        logger.info("[SymbolsLoaderTask"+this.getId()+"] process terminated.");
254

    
255
                }
256
        }
257
        
258
        public CancellableTask loadSymbols(File folder, FileFilter filter, Visitor visitor) {
259
                SymbolsLoaderTask task = new SymbolsLoaderTask(folder, filter, visitor);
260
                task.start();
261
                return task;
262
        }
263

    
264
        public void saveSymbol(ISymbol symbol, String fileName, File folder)
265
                        throws SymbolException {
266
                saveSymbol(symbol, fileName, folder, false);
267
        }
268

    
269
        public void saveSymbol(ISymbol symbol, String fileName, File folder,
270
                        boolean overwrite) throws SymbolException {
271
                // TODO: add symbol caching
272

    
273
                PersistenceManager persistenceManager = ToolsLocator
274
                                .getPersistenceManager();
275

    
276
                File symbolFile = new File(folder, fileName);
277
                if (!overwrite && symbolFile.exists()) {
278
                        throw new SymbolFileAlreadyExistsException(symbolFile);
279
                }
280

    
281
                try {
282
                        FileOutputStream fos = new FileOutputStream(symbolFile);
283

    
284
                        persistenceManager.saveState(persistenceManager.getState(symbol),
285
                                        fos);
286

    
287
                        fos.flush();
288
                        fos.close();
289
                } catch (PersistenceException e) {
290
                        throw new SaveSymbolException(e);
291
                } catch (IOException e) {
292
                        throw new SaveSymbolException(e);
293
                }
294
        }
295

    
296
        /**
297
         * Loads a persisted symbol from the given file.
298
         */
299
        private ISymbol loadSymbol(File file) throws SymbolException {
300
                if (file.exists()) {
301
                        try {
302
                                FileInputStream fis = new FileInputStream(file);
303

    
304
                                PersistenceManager persistenceManager = ToolsLocator
305
                                                .getPersistenceManager();
306

    
307
                                PersistentState state = persistenceManager.loadState(fis);
308
                                ISymbol symbol = (ISymbol) persistenceManager.create(state);
309

    
310
                                fis.close();
311
                                return symbol;
312
                        } catch (PersistenceException e) {
313
                                throw new LoadSymbolException(e);
314
                        } catch (IOException e) {
315
                                throw new LoadSymbolException(e);
316
                        }
317
                }
318

    
319
                return null;
320
        }
321

    
322
        public SymbolPreferences getSymbolPreferences() {
323
                return symbolPreferences;
324
        }
325

    
326
        public ISymbol createSymbol(String symbolName)
327
                        throws MapContextRuntimeException {
328
                return createSymbol(symbolName, (Class) symbolsByName.get(symbolName),
329
                                ISymbol.class);
330
        }
331

    
332
        public ISymbol createSymbol(int shapeType)
333
                        throws MapContextRuntimeException {
334
                String symbolName =
335
                                (String) symbolsByShapeType.get(new Integer(shapeType));
336

    
337
                return symbolName == null ? null : createSymbol(symbolName);
338
        }
339

    
340
        public ISymbol createSymbol(String symbolName, Color color)
341
                        throws MapContextRuntimeException {
342
                ISymbol symbol = createSymbol(symbolName);
343

    
344
                if (symbol != null) {
345
                        symbol.setColor(color);
346
                }
347

    
348
                return symbol;
349
        }
350

    
351
        public ISymbol createSymbol(int shapeType, Color color)
352
                        throws MapContextRuntimeException {
353
                String symbolName =
354
                                (String) symbolsByShapeType.get(new Integer(shapeType));
355

    
356
                return symbolName == null ? null : createSymbol(symbolName, color);
357
        }
358

    
359
        public IMultiLayerSymbol createMultiLayerSymbol(String symbolName)
360
                        throws MapContextRuntimeException {
361
                return (IMultiLayerSymbol) createSymbol(symbolName,
362
                                (Class) multiLineSymbolsByName.get(symbolName),
363
                                IMultiLayerSymbol.class);
364
        }
365

    
366
        public IMultiLayerSymbol createMultiLayerSymbol(int shapeType)
367
                        throws MapContextRuntimeException {
368
                String symbolName =
369
                                (String) multiLineSymbolsByShapeType.get(new Integer(shapeType));
370

    
371
                return symbolName == null ? null : createMultiLayerSymbol(symbolName);
372
        }
373

    
374
        public void registerSymbol(String symbolName, Class symbolClass)
375
                        throws MapContextRuntimeException {
376
                if (symbolClass == null || !ISymbol.class.isAssignableFrom(symbolClass)) {
377
                        throw new InvalidRegisteredClassException(ISymbol.class,
378
                                        symbolClass, symbolName);
379
                }
380
                symbolsByName.put(symbolName, symbolClass);
381
        }
382

    
383
        public void registerSymbol(String symbolName, int[] shapeTypes,
384
                        Class symbolClass) throws MapContextRuntimeException {
385
                registerSymbol(symbolName, symbolClass);
386
                if (shapeTypes != null) {
387
                        for (int i = 0; i < shapeTypes.length; i++) {
388
                                symbolsByShapeType.put(new Integer(shapeTypes[i]), symbolName);
389
                        }
390
                }
391
        }
392

    
393
        public void registerMultiLayerSymbol(String symbolName, Class symbolClass)
394
                        throws MapContextRuntimeException {
395
                if (symbolClass == null
396
                                || !IMultiLayerSymbol.class.isAssignableFrom(symbolClass)) {
397
                        throw new InvalidRegisteredClassException(IMultiLayerSymbol.class,
398
                                        symbolClass, symbolName);
399
                }
400

    
401
                multiLineSymbolsByName.put(symbolName, symbolClass);
402
        }
403

    
404
        public void registerMultiLayerSymbol(String symbolName, int[] shapeTypes,
405
                        Class symbolClass) throws MapContextRuntimeException {
406
                registerMultiLayerSymbol(symbolName, symbolClass);
407
                if (shapeTypes != null) {
408
                        for (int i = 0; i < shapeTypes.length; i++) {
409
                                multiLineSymbolsByShapeType.put(new Integer(shapeTypes[i]),
410
                                                symbolName);
411
                        }
412
                }
413
        }
414

    
415
        public IWarningSymbol getWarningSymbol(String message, String symbolDesc,
416
                        int symbolDrawExceptionType) throws MapContextRuntimeException {
417
                synchronized (warningSymbolLock) {
418
                        if (warningSymbol == null) {
419
                                warningSymbol = (IWarningSymbol) createSymbol("warning");
420
                        }
421
                }
422

    
423
                // TODO: set those values as parameter values in the draw method.
424
                warningSymbol.setDescription(symbolDesc);
425
                warningSymbol.setMessage(message);
426
                warningSymbol.setDrawExceptionType(symbolDrawExceptionType);
427

    
428
                return warningSymbol;
429
        }
430

    
431
        private ISymbol createSymbol(String symbolName, Class symbolClass,
432
                        Class expectedType) throws MapContextRuntimeException {
433
                ISymbol symbol;
434
                try {
435
                        symbol =
436
                                        (ISymbol) (symbolClass == null ? null
437
                                                        : symbolClass.newInstance());
438
                } catch (InstantiationException e) {
439
                        throw new RegisteredClassInstantiationException(expectedType,
440
                                        symbolClass, symbolName, e);
441
                } catch (IllegalAccessException e) {
442
                        throw new RegisteredClassInstantiationException(expectedType,
443
                                        symbolClass, symbolName, e);
444
                }
445

    
446
                Color the_color = null;
447
                
448
        if (getSymbolPreferences().isDefaultSymbolFillColorAleatory()) {
449
            
450
            the_color = getRandomBrewerBasedColor();
451
            
452
        } else {
453
            // not random
454
            the_color = getSymbolPreferences().getDefaultSymbolFillColor();
455
        }
456
        symbol.setColor(the_color);
457
                // Perform this initialization into the Symbol implementation
458
                // if (symbol instanceof CartographicSupport) {
459
                // CartographicSupport cs = (CartographicSupport) symbol;
460
                // cs.setUnit(getDefaultCartographicSupportMeasureUnit());
461
                // cs
462
                // .setReferenceSystem(getDefaultCartographicSupportReferenceSystem();
463
                // }
464

    
465
                return symbol;
466
        }
467
        
468
        public void setSymbolPreferences(SymbolPreferences symbolPreferences) {
469
                this.symbolPreferences = symbolPreferences;
470
        }
471
        
472
        /**
473
         * 
474
         * @param col
475
         * @return color 1/3 closer to black
476
         */
477
        public static Color darker(Color col) {
478
            return new Color(
479
                (2 * col.getRed()) / 3,
480
                (2 * col.getGreen()) / 3,
481
                (2 * col.getBlue()) / 3,
482
                col.getAlpha());
483
        }
484

    
485
        /**
486
         * 
487
         * @param col
488
         * @return color 1/3 closer to white
489
         */
490
        public static Color lighter(Color col) {
491
            Color resp = invert(col);
492
            resp = darker(resp);
493
            return invert(resp);
494
        }
495
        
496
        /**
497
         * 
498
         * @param col
499
         * @return inverted color (inverts each band, same alpha)
500
         */
501
        public static Color invert(Color col) {
502
        return new Color(
503
            255 - col.getRed(),
504
            255 - col.getGreen(),
505
            255 - col.getBlue(),
506
            col.getAlpha());
507
        }
508
        
509
        private static Color getRandomBrewerBasedColor() {
510
            
511
            int ind = rnd.nextInt(BREWER_COLOR.length);
512
            Color resp = BREWER_COLOR[ind]; 
513
            ind = rnd.nextInt(100);
514
            if (ind > 66) {
515
                resp = darker(resp);
516
            } else {
517
                if (ind > 33) {
518
                    resp = lighter(resp);
519
                }
520
                // ...else resp remains the same
521
            }
522
            
523
            // finally add some dark noise 
524
            resp = addDarkNoise(resp);
525
            return resp;
526
        }
527
        
528
    private static Color addDarkNoise(Color c) {
529
        int r = Math.max(0, c.getRed() - rnd.nextInt(30));
530
        int g = Math.max(0, c.getGreen() - rnd.nextInt(30));
531
        int b = Math.max(0, c.getBlue() - rnd.nextInt(30));
532
        return new Color(r, g, b, c.getAlpha());
533
    }
534

    
535
    private static Color[] BREWER_COLOR = new Color[36];
536
    private static final Random rnd = new Random();
537
        
538
        static {
539
            /**
540
             * Colors from www.ColorBrewer.org by Cynthia A. Brewer,
541
             * Geography, Pennsylvania State University.
542
             *
543
             * Using groups of 4 colors from the 9 Diverging Schemes
544
             * 4 * 9 = 36 colors
545
             */
546
            BREWER_COLOR[0] = new Color(230, 97, 1);
547
            BREWER_COLOR[1] = new Color(253, 184, 99);
548
            BREWER_COLOR[2] = new Color(178, 171, 210);
549
            BREWER_COLOR[3] = new Color(94, 60, 153);
550
            BREWER_COLOR[4] = new Color(166, 97, 26);
551
            BREWER_COLOR[5] = new Color(223, 194, 125);
552
            BREWER_COLOR[6] = new Color(128, 205, 193);
553
            BREWER_COLOR[7] = new Color(1, 133, 113);
554
            BREWER_COLOR[8] = new Color(123, 50, 148);
555
            BREWER_COLOR[9] = new Color(194, 165, 207);
556
            BREWER_COLOR[10] = new Color(166, 219, 160);
557
            BREWER_COLOR[11] = new Color(0, 136, 55);
558
            BREWER_COLOR[12] = new Color(208, 28, 139);
559
            BREWER_COLOR[13] = new Color(241, 182, 218);
560
            BREWER_COLOR[14] = new Color(184, 225, 134);
561
            BREWER_COLOR[15] = new Color(77, 172, 38);
562
            BREWER_COLOR[16] = new Color(202, 0, 32);
563
            BREWER_COLOR[17] = new Color(244, 165, 130);
564
            BREWER_COLOR[18] = new Color(146, 197, 222);
565
            BREWER_COLOR[19] = new Color(5, 113, 176);
566
            BREWER_COLOR[20] = new Color(202, 0, 32);
567
            BREWER_COLOR[21] = new Color(244, 165, 130);
568
            BREWER_COLOR[22] = new Color(186, 186, 186);
569
            BREWER_COLOR[23] = new Color(64, 64, 64);
570
            BREWER_COLOR[24] = new Color(215, 25, 28);
571
            BREWER_COLOR[25] = new Color(253, 174, 97);
572
            BREWER_COLOR[26] = new Color(171, 217, 233);
573
            BREWER_COLOR[27] = new Color(44, 123, 182);
574
            BREWER_COLOR[28] = new Color(215, 25, 28);
575
            BREWER_COLOR[29] = new Color(253, 174, 97);
576
            BREWER_COLOR[30] = new Color(171, 221, 164);
577
            BREWER_COLOR[31] = new Color(43, 131, 186);
578
            BREWER_COLOR[32] = new Color(215, 25, 28);
579
            BREWER_COLOR[33] = new Color(253, 174, 97);
580
            BREWER_COLOR[34] = new Color(166, 217, 106);
581
            BREWER_COLOR[35] = new Color(26, 150, 65);
582
        }
583

    
584
        
585
}