001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.brewer.color;
017:
018: import org.w3c.dom.Document;
019: import org.w3c.dom.Node;
020: import org.w3c.dom.NodeList;
021: import org.xml.sax.SAXException;
022: import java.awt.Color;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.net.URL;
026: import java.util.ArrayList;
027: import java.util.HashSet;
028: import java.util.Hashtable;
029: import java.util.List;
030: import java.util.Set;
031: import java.util.StringTokenizer;
032: import java.util.logging.Level;
033: import javax.xml.parsers.DocumentBuilder;
034: import javax.xml.parsers.DocumentBuilderFactory;
035: import javax.xml.parsers.ParserConfigurationException;
036:
037: /**
038: * Contains ColorBrewer palettes and suitability data.
039: *
040: * @author James Macgill
041: * @author Cory Horner, Refractions Research Inc.
042: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/brewer/src/main/java/org/geotools/brewer/color/ColorBrewer.java $
043: */
044: public class ColorBrewer {
045: private static final java.util.logging.Logger LOGGER = org.geotools.util.logging.Logging
046: .getLogger("org.geotools.brewer.color");
047: public static final PaletteType ALL = new PaletteType(true, true,
048: "ALL");
049: public static final PaletteType SUITABLE_RANGED = new PaletteType(
050: true, false);
051: public static final PaletteType SUITABLE_UNIQUE = new PaletteType(
052: false, true);
053: public static final PaletteType SEQUENTIAL = new PaletteType(true,
054: false, "SEQUENTIAL");
055: public static final PaletteType DIVERGING = new PaletteType(true,
056: false, "DIVERGING");
057: public static final PaletteType QUALITATIVE = new PaletteType(
058: false, true, "QUALITATIVE");
059: String name = null;
060: String description = null;
061: Hashtable palettes = new Hashtable();
062:
063: /**
064: * Creates a new instance of ColorBrewer
065: */
066: public ColorBrewer() {
067: }
068:
069: /**
070: * Creates a static instance of ColorBrewer containing all default palettes
071: *
072: * @return The ColorBrewer instance with all the default palettes.
073: * @throws IOException
074: */
075: public static ColorBrewer instance() {
076: ColorBrewer me = new ColorBrewer();
077: me.loadPalettes();
078:
079: return me;
080: }
081:
082: /**
083: * Creates a static instance of ColorBrewer containing a subset of the
084: * default palettes.
085: *
086: * @param type A PaletteType object which will be used to configure the
087: * returned ColorBrewer.
088: * @return The ColorBrewer instance with the palette from the parameter.
089: * @throws IOException
090: */
091: public static ColorBrewer instance(PaletteType type)
092: throws IOException {
093: ColorBrewer me = new ColorBrewer();
094: me.loadPalettes(type);
095:
096: return me;
097: }
098:
099: public void registerPalette(BrewerPalette pal) {
100: palettes.put(pal.getName(), pal);
101: }
102:
103: /**
104: * Returns true if the palette exists in this ColorBrewer
105: *
106: * @param paletteName A String with the name of the palette
107: * @return A boolean, true if the ColorBrewer has a palette of the name
108: * given.
109: */
110: public boolean hasPalette(String paletteName) {
111: return palettes.containsKey(paletteName);
112: }
113:
114: public BrewerPalette[] getPalettes() {
115: Object[] entry = this .palettes.keySet().toArray();
116: BrewerPalette[] palettes = new BrewerPalette[entry.length];
117:
118: for (int i = 0; i < entry.length; i++) {
119: palettes[i] = (BrewerPalette) getPalette(entry[i]
120: .toString());
121: }
122:
123: return palettes;
124: }
125:
126: public BrewerPalette[] getPalettes(PaletteType type) {
127: return getPalettes(type, -1);
128: }
129:
130: public BrewerPalette[] getPalettes(PaletteType type, int numClasses) {
131: List palettes = new ArrayList();
132: Object[] entry = this .palettes.keySet().toArray();
133:
134: for (int i = 0; i < entry.length; i++) {
135: BrewerPalette pal = (BrewerPalette) getPalette(entry[i]
136: .toString());
137: boolean match = true;
138:
139: //filter by number of classes
140: if (numClasses > -1) {
141: if (pal.getMaxColors() < numClasses) {
142: match = false;
143: }
144: }
145:
146: if (!pal.getType().isMatch(type)) {
147: match = false;
148: }
149:
150: if (match) {
151: palettes.add(pal);
152: }
153: }
154:
155: return (BrewerPalette[]) palettes
156: .toArray(new BrewerPalette[palettes.size()]);
157: }
158:
159: public BrewerPalette[] getPalettes(PaletteType type,
160: int numClasses, int requiredViewers) {
161: List palettes = new ArrayList();
162: Object[] entry = this .palettes.keySet().toArray();
163:
164: for (int i = 0; i < entry.length; i++) {
165: BrewerPalette pal = (BrewerPalette) getPalette(entry[i]
166: .toString());
167: boolean match = true;
168:
169: //filter by number of classes
170: if (numClasses > -1) {
171: if (pal.getMaxColors() < numClasses) {
172: match = false;
173: }
174: }
175:
176: if (!pal.getType().isMatch(type)) {
177: match = false;
178: }
179:
180: int[] suitability = pal.getPaletteSuitability()
181: .getSuitability(numClasses);
182:
183: if (isSet(PaletteSuitability.VIEWER_COLORBLIND,
184: requiredViewers)
185: && (suitability[PaletteSuitability.VIEWER_COLORBLIND] != PaletteSuitability.QUALITY_GOOD)) {
186: match = false;
187: } else if (isSet(PaletteSuitability.VIEWER_CRT,
188: requiredViewers)
189: && (suitability[PaletteSuitability.VIEWER_CRT] != PaletteSuitability.QUALITY_GOOD)) {
190: match = false;
191: } else if (isSet(PaletteSuitability.VIEWER_LCD,
192: requiredViewers)
193: && (suitability[PaletteSuitability.VIEWER_LCD] != PaletteSuitability.QUALITY_GOOD)) {
194: match = false;
195: } else if (isSet(PaletteSuitability.VIEWER_PHOTOCOPY,
196: requiredViewers)
197: && (suitability[PaletteSuitability.VIEWER_PHOTOCOPY] != PaletteSuitability.QUALITY_GOOD)) {
198: match = false;
199: } else if (isSet(PaletteSuitability.VIEWER_PRINT,
200: requiredViewers)
201: && (suitability[PaletteSuitability.VIEWER_PRINT] != PaletteSuitability.QUALITY_GOOD)) {
202: match = false;
203: } else if (isSet(PaletteSuitability.VIEWER_PROJECTOR,
204: requiredViewers)
205: && (suitability[PaletteSuitability.VIEWER_PROJECTOR] != PaletteSuitability.QUALITY_GOOD)) {
206: match = false;
207: }
208:
209: if (match) {
210: palettes.add(pal);
211: }
212: }
213:
214: return (BrewerPalette[]) palettes
215: .toArray(new BrewerPalette[palettes.size()]);
216: }
217:
218: /**
219: * Generates a String array with the names of the palettes in the
220: * ColorBrewer instance.
221: *
222: * @return A String array with the names of the palettes in the ColorBrewer
223: * instance.
224: */
225: public String[] getPaletteNames() {
226: Object[] keys = palettes.keySet().toArray();
227: String[] paletteList = new String[keys.length];
228:
229: for (int i = 0; i < keys.length; i++) {
230: paletteList[i] = keys[i].toString();
231: }
232:
233: return paletteList;
234: }
235:
236: /**
237: * Generates an array of palette names for palettes which have at least x
238: * classes and at most y classes.
239: *
240: * @param minClasses x
241: * @param maxClasses y
242: *
243: * @return A string array of palette names filtered by number of classes.
244: */
245: public String[] getPaletteNames(int minClasses, int maxClasses) {
246: Object[] keys = palettes.keySet().toArray();
247: Set paletteSet = new HashSet();
248:
249: //generate the set of palette names
250: for (int i = 0; i < keys.length; i++) {
251: BrewerPalette this Palette = (BrewerPalette) palettes
252: .get(keys[i]);
253: int numColors = this Palette.getMaxColors();
254:
255: if ((numColors >= minClasses) && (numColors <= maxClasses)) {
256: paletteSet.add(this Palette.getName());
257: }
258: }
259:
260: //convert set to string array
261: String[] paletteList = new String[paletteSet.size()];
262: Object[] paletteObjList = paletteSet.toArray();
263:
264: for (int i = 0; i < paletteSet.size(); i++) {
265: paletteList[i] = (String) paletteObjList[i];
266: }
267:
268: return paletteList;
269: }
270:
271: public BrewerPalette getPalette(String name) {
272: return (BrewerPalette) palettes.get(name);
273: }
274:
275: /**
276: * Loads the default ColorBrewer palettes.
277: *
278: * @throws IOException
279: */
280: public void loadPalettes() {
281: loadPalettes(SEQUENTIAL);
282: loadPalettes(DIVERGING);
283: loadPalettes(QUALITATIVE);
284: }
285:
286: /**
287: * Loads into the ColorBrewer instance the set of palettes which have the
288: * PaletteType matching that of the parameter.
289: *
290: * @param type The PaletteType for the palettes to load.
291: * @throws IOException
292: */
293: public void loadPalettes(PaletteType type) {
294: if (type.equals(ALL)) {
295: loadPalettes();
296:
297: return;
298: } else if (type.equals(SUITABLE_RANGED)) {
299: loadPalettes(SEQUENTIAL);
300: loadPalettes(DIVERGING);
301:
302: return;
303: } else if (type.equals(SUITABLE_UNIQUE)) {
304: loadPalettes(QUALITATIVE);
305:
306: return;
307: }
308:
309: if (type.getName() == null) {
310: return;
311: }
312:
313: String paletteSet = type.getName().toLowerCase();
314: URL url = getClass().getResource(
315: "resources/" + paletteSet + ".xml");
316: InputStream stream;
317:
318: try {
319: stream = url.openStream();
320: } catch (IOException e) {
321: LOGGER.log(Level.SEVERE,
322: "couldn't open input stream to load palette", e);
323:
324: return;
325: }
326:
327: load(stream, type);
328: }
329:
330: /**
331: * Loads into the ColorBrewer instance the set of palettes matching the
332: * given parameters.
333: *
334: * @param XMLinput
335: * @param type identifier for palettes. use "new PaletteType();"
336: */
337: public void loadPalettes(InputStream XMLinput, PaletteType type) {
338: load(XMLinput, type);
339: }
340:
341: private void load(InputStream stream, PaletteType type) {
342: try {
343: DocumentBuilderFactory factory = DocumentBuilderFactory
344: .newInstance();
345: DocumentBuilder builder = factory.newDocumentBuilder();
346: Document document = builder.parse(stream);
347: this .name = fixToString(document.getElementsByTagName(
348: "name").item(0).getFirstChild().toString());
349: this .description = fixToString(document
350: .getElementsByTagName("description").item(0)
351: .getFirstChild().toString());
352:
353: SampleScheme scheme = new SampleScheme();
354:
355: NodeList samples = document.getElementsByTagName("sample");
356:
357: for (int i = 0; i < samples.getLength(); i++) {
358: Node sample = samples.item(i);
359: int size = Integer.parseInt(sample.getAttributes()
360: .getNamedItem("size").getNodeValue());
361: String values = fixToString(sample.getFirstChild()
362: .toString());
363: int[] list = new int[size];
364: StringTokenizer tok = new StringTokenizer(values);
365:
366: for (int j = 0; j < size; j++) {
367: list[j] = Integer.parseInt(tok.nextToken(","));
368: }
369:
370: scheme.setSampleScheme(size, list);
371: }
372:
373: NodeList palettes = document
374: .getElementsByTagName("palette");
375:
376: for (int i = 0; i < palettes.getLength(); i++) {
377: BrewerPalette pal = new BrewerPalette();
378: PaletteSuitability suitability = new PaletteSuitability();
379: NodeList paletteInfo = palettes.item(i).getChildNodes();
380:
381: for (int j = 0; j < paletteInfo.getLength(); j++) {
382: Node item = paletteInfo.item(j);
383:
384: if (item.getNodeName().equals("name")) {
385: pal.setName(fixToString(item.getFirstChild()
386: .toString()));
387: }
388:
389: if (item.getNodeName().equals("description")) {
390: pal.setDescription(fixToString(item
391: .getFirstChild().toString()));
392: }
393:
394: if (item.getNodeName().equals("colors")) {
395: StringTokenizer oTok = new StringTokenizer(
396: fixToString(item.getFirstChild()
397: .toString()));
398: int numColors = 0;
399: Color[] colors = new Color[15];
400:
401: for (int k = 0; k < 15; k++) {
402: if (!oTok.hasMoreTokens()) {
403: break;
404: }
405:
406: String entry = oTok.nextToken(":");
407: StringTokenizer iTok = new StringTokenizer(
408: entry);
409: int r = Integer.parseInt(iTok
410: .nextToken(",").trim());
411: int g = Integer.parseInt(iTok
412: .nextToken(",").trim());
413: int b = Integer.parseInt(iTok
414: .nextToken(",").trim());
415: colors[numColors] = new Color(r, g, b);
416: numColors++;
417: }
418:
419: pal.setColors(colors);
420: }
421:
422: if (item.getNodeName().equals("suitability")) {
423: NodeList schemeSuitability = item
424: .getChildNodes();
425:
426: for (int k = 0; k < schemeSuitability
427: .getLength(); k++) {
428: Node palScheme = schemeSuitability.item(k);
429:
430: if (palScheme.getNodeName()
431: .equals("scheme")) {
432: int paletteSize = Integer
433: .parseInt(palScheme
434: .getAttributes()
435: .getNamedItem("size")
436: .getNodeValue());
437:
438: String values = fixToString(palScheme
439: .getFirstChild().toString());
440: String[] list = new String[6];
441: StringTokenizer tok = new StringTokenizer(
442: values);
443:
444: // obtain all 6 values, which should each be
445: // G=GOOD, D=DOUBTFUL, B=BAD, or ?=UNKNOWN.
446: for (int m = 0; m < 6; m++) {
447: list[m] = tok.nextToken(",");
448: }
449:
450: suitability.setSuitability(paletteSize,
451: list);
452: }
453: }
454: }
455: }
456:
457: pal.setType(type);
458: pal.setColorScheme(scheme);
459: pal.setPaletteSuitability(suitability);
460: registerPalette(pal); // add the palette
461: }
462: } catch (SAXException sxe) {
463: LOGGER.log(Level.SEVERE, "Error during palette parsing",
464: sxe);
465: } catch (ParserConfigurationException pce) {
466: LOGGER
467: .log(
468: Level.SEVERE,
469: "Parser with specified options can't be built",
470: pce);
471: } catch (IOException ioe) {
472: LOGGER.log(Level.SEVERE,
473: "i/o error during palette parsing", ioe);
474: }
475: }
476:
477: /**
478: * Converts "[#text: 1,2,3]" to "1,2,3".
479: *
480: * <p>
481: * This is a brutal hack for fixing the org.w3c.dom API. Under j1.4
482: * Node.toString() returns "1,2,3", under j1.5 Node.toString() returns
483: * "[#text: 1,2,3]".
484: * </p>
485: *
486: * @param input A String with the input.
487: *
488: * @return A String with the modified input.
489: */
490: private String fixToString(String input) {
491: if (input.startsWith("[") && input.endsWith("]")) {
492: input = input.substring(1, input.length() - 1); //remove []
493: input = input.replaceAll("#text: ", ""); //remove "#text: "
494: }
495:
496: return input;
497: }
498:
499: public String getName() {
500: return name;
501: }
502:
503: public String getDescription() {
504: return description;
505: }
506:
507: public void reset() {
508: name = null;
509: description = null;
510: palettes = new Hashtable();
511: }
512:
513: public boolean isSet(int singleValue, int multipleValue) {
514: return ((singleValue & multipleValue) != 0);
515: }
516: }
|