sascha@424: package de.intevation.gnv.raster; sascha@424: sascha@779: import de.intevation.gnv.raster.Raster.ValueToIndex; sascha@779: sascha@779: import java.awt.Color; sascha@779: sascha@779: import java.util.Arrays; sascha@779: sascha@779: import org.apache.log4j.Logger; sascha@779: sascha@424: import org.w3c.dom.Document; sascha@424: import org.w3c.dom.Element; sascha@424: import org.w3c.dom.NodeList; sascha@424: sascha@424: /** sascha@801: * Lookup mechanism to map double values from a list of sascha@801: * ranges onto a set of colors and vice versa. sascha@780: * @author Sascha L. Teichmann sascha@424: */ sascha@424: public class Palette sascha@424: implements ValueToIndex sascha@778: { sascha@801: private static Logger log = Logger.getLogger(Palette.class); sascha@447: sascha@801: /** sascha@801: * An entry in the lookup table. sascha@801: */ sascha@778: public static final class Entry sascha@424: implements Comparable sascha@424: { sascha@424: private Entry left; sascha@424: private Entry right; sascha@424: sascha@424: private int index; sascha@424: sascha@483: private int externalIndex; sascha@483: sascha@424: private double from; sascha@424: private double to; sascha@424: sascha@424: private String description; sascha@424: sascha@424: private Color color; sascha@424: sascha@801: /** sascha@801: * Default constructor sascha@801: */ sascha@424: public Entry() { sascha@424: } sascha@424: sascha@801: /** sascha@801: * Copy constructor sascha@801: * @param other The entry to copy. sascha@801: */ sascha@435: public Entry(Entry other) { sascha@483: index = other.index; sascha@483: externalIndex = other.externalIndex; sascha@483: from = other.from; sascha@483: to = other.to; sascha@483: description = other.description; sascha@483: color = other.color; sascha@435: } sascha@435: sascha@801: /** sascha@801: * Constructor to set the palette index, the exteral index, sascha@801: * the value range, the color and the description. sascha@801: * @param index The pallette index sascha@801: * @param externalIndex The external index sascha@801: * @param from Start of the interval sascha@801: * @param to End of the interval sascha@801: * @param color The color belonging to this interval. sascha@801: * @param description The description of this range. sascha@801: */ sascha@424: public Entry( sascha@778: int index, sascha@483: int externalIndex, sascha@778: double from, sascha@778: double to, sascha@424: Color color, sascha@424: String description sascha@424: ) { sascha@483: this.index = index; sascha@483: this.externalIndex = externalIndex; sascha@483: this.from = from; sascha@483: this.to = to; sascha@483: this.color = color; sascha@483: this.description = description; sascha@424: } sascha@424: sascha@801: /** sascha@801: * Compares two entries by the start point of the interval. sascha@801: * @param other sascha@803: * @return -1 if this start point is before other. +1 if this sascha@801: * start point is after, else 0. sascha@801: */ sascha@424: public int compareTo(Object other) { sascha@424: Entry e = (Entry)other; sascha@424: if (from < e.from) return -1; sascha@424: if (from > e.from) return +1; sascha@424: return 0; sascha@424: } sascha@424: sascha@801: /** sascha@801: * The starting point of the interval. sascha@801: * @return The starting point. sascha@801: */ sascha@448: public double getFrom() { sascha@448: return from; sascha@448: } sascha@448: sascha@801: /** sascha@801: * The end point of the interval. sascha@801: * @return The end point. sascha@801: */ sascha@448: public double getTo() { sascha@448: return to; sascha@448: } sascha@448: sascha@801: /** sascha@801: * The color of the entry. sascha@801: * @return The color. sascha@801: */ sascha@449: public Color getColor() { sascha@449: return color; sascha@449: } sascha@449: sascha@801: /** sascha@801: * The external index of this entry. Useful to model sascha@801: * external joins. sascha@801: * @return The external index. sascha@801: */ sascha@483: public int getExternalIndex() { sascha@483: return externalIndex; sascha@483: } sascha@483: sascha@801: /** sascha@801: * Searches for an entry which has a range that contains sascha@801: * the given value. sascha@801: * @param value The value to be searched. sascha@801: * @return The entry containing the value or null if no such sascha@801: * entry exists. sascha@801: */ sascha@424: public Entry locateEntry(double value) { sascha@424: Entry current = this; sascha@424: while (current != null) { sascha@424: boolean b = value >= current.from; sascha@424: if (b && value <= current.to) { sascha@424: return current; sascha@424: } sascha@424: current = b sascha@424: ? current.right sascha@424: : current.left; sascha@424: } sascha@424: return null; sascha@424: } sascha@424: sascha@801: /** sascha@801: * Returns the palette index for a given value. sascha@801: * @param value The value to search. sascha@801: * @return The index of the palette entry or -1 if sascha@801: * no such entry exists. sascha@801: */ sascha@424: public int locate(double value) { sascha@424: Entry entry = locateEntry(value); sascha@424: return entry != null sascha@424: ? entry.index sascha@424: : -1; sascha@424: } sascha@424: sascha@801: /** sascha@801: * Searches for an interval with a given index. sascha@801: * @param index The index to be searched. sascha@801: * @return The entry with the given index or null if no sascha@801: * such entry exists. sascha@801: */ sascha@424: public Entry findByIndex(int index) { sascha@424: Entry current = this; sascha@424: while (current != null) { sascha@424: if (current.index == index) { sascha@424: break; sascha@424: } sascha@424: current = index < current.index sascha@424: ? current.left sascha@424: : current.right; sascha@424: } sascha@424: return current; sascha@424: } sascha@424: sascha@801: /** sascha@801: * The description of this entry. sascha@801: * @return The description. sascha@801: */ sascha@424: public String getDescription() { sascha@424: return description; sascha@424: } sascha@435: sascha@801: /** sascha@801: * Determines if the range of this entry has sascha@801: * infinitive length. sascha@801: * @return true if the range of this entry has infinitive sascha@801: * length else false. sascha@801: */ sascha@435: public boolean isInfinity() { sascha@435: return from == -Double.MAX_VALUE sascha@435: || to == Double.MAX_VALUE; sascha@435: } sascha@424: } // class Entry sascha@424: sascha@801: /** sascha@801: * Internal table of the entrys. sascha@801: */ sascha@424: protected Entry [] entries; sascha@803: sascha@801: /** sascha@801: * Root of the entry tree used to map values to entries. sascha@801: */ sascha@424: protected Entry lookup; sascha@801: sascha@801: /** sascha@801: * The list of colors used by this palette. sascha@801: */ sascha@424: protected Color [] rgbs; sascha@424: sascha@424: private Entry buildLookup(Entry [] entries, int lo, int hi) { sascha@424: if (lo > hi) { sascha@424: return null; sascha@424: } sascha@424: int mid = (lo + hi)/2; sascha@424: Entry entry = entries[mid]; sascha@424: entry.left = buildLookup(entries, lo, mid-1); sascha@424: entry.right = buildLookup(entries, mid+1, hi); sascha@424: return entry; sascha@424: } sascha@424: sascha@801: /** sascha@801: * Default constructor. sascha@801: */ sascha@424: public Palette() { sascha@424: } sascha@424: sascha@801: /** sascha@801: * Constructor to create the palette from an XML document. sascha@801: * @param document The document with palette information. sascha@801: */ sascha@424: public Palette(Document document) { sascha@424: sascha@424: NodeList rangeNodes = document.getElementsByTagName("range"); sascha@424: sascha@424: entries = new Entry[rangeNodes.getLength()]; sascha@424: rgbs = new Color[entries.length]; sascha@424: sascha@424: for (int i = 0; i < entries.length; ++i) { sascha@424: Element rangeElement = (Element)rangeNodes.item(i); sascha@483: double from = doubleValue(rangeElement.getAttribute("from")); sascha@483: double to = doubleValue(rangeElement.getAttribute("to")); sascha@483: int extIndex = intValue(rangeElement.getAttribute("index"), i); sascha@483: Color color = color(rangeElement.getAttribute("rgb")); sascha@483: String desc = rangeElement.getAttribute("description"); sascha@424: if (from > to) { double t = from; from = to; to = t; } sascha@483: entries[i] = new Entry(i, extIndex, from, to, color, desc); sascha@424: rgbs[i] = color; sascha@424: } sascha@424: sascha@424: buildLookup(); sascha@424: } sascha@424: sascha@801: /** sascha@801: * Constructor to split a given palette into a given number of sascha@801: * equal sized sub entries. Ranges of infinitive length are not sascha@801: * splitted. sascha@801: * @param original The source palette. sascha@801: * @param N The number of chunks to split single ranges into. sascha@801: */ sascha@435: public Palette(Palette original, int N) { sascha@435: if (N < 2) { sascha@435: throw new IllegalArgumentException("N < 2"); sascha@435: } sascha@435: sascha@435: Entry [] origEntries = original.entries; sascha@435: sascha@435: int newSize = 0; sascha@435: for (int i = 0; i < origEntries.length; ++i) { sascha@435: // cannot split infinity intervals sascha@435: newSize += origEntries[i].isInfinity() ? 1 : N; sascha@435: } sascha@435: sascha@435: entries = new Entry[newSize]; sascha@435: rgbs = new Color[newSize]; sascha@435: sascha@435: for (int i = 0, j = 0; i < origEntries.length; ++i) { sascha@435: Entry origEntry = origEntries[i]; sascha@435: if (origEntry.isInfinity()) { sascha@435: // infinity intervals are just copied sascha@435: Entry nEntry = new Entry(origEntry); sascha@435: entries[j] = nEntry; sascha@435: rgbs[j] = nEntry.color; sascha@435: nEntry.index = j++; sascha@435: } sascha@435: else { sascha@435: // split interval into N parts sascha@435: double from = origEntry.from; sascha@435: double to = origEntry.to; sascha@435: double delta = (to - from)/N; sascha@447: for (int k = 0; k < N; ++k) { sascha@435: Entry nEntry = new Entry(origEntry); sascha@435: nEntry.from = from; sascha@435: nEntry.to = from + delta; sascha@435: from += delta; sascha@435: entries[j] = nEntry; sascha@435: rgbs[j] = nEntry.color; sascha@435: nEntry.index = j++; sascha@435: } sascha@435: } // limited interval sascha@435: } // for all original entries sascha@435: sascha@435: buildLookup(); sascha@435: } sascha@435: sascha@483: private static final int intValue(String s, int def) { sascha@483: if (s == null || (s = s.trim()).length() == 0) { sascha@483: return def; sascha@483: } sascha@483: try { sascha@483: return Integer.parseInt(s); sascha@483: } sascha@483: catch (NumberFormatException nfe) { sascha@483: log.error(nfe.getLocalizedMessage(), nfe); sascha@483: } sascha@483: return def; sascha@483: } sascha@483: sascha@424: private static final double doubleValue(String s) { sascha@424: if (s == null || (s = s.trim()).length() == 0) { sascha@424: return 0d; sascha@424: } sascha@424: if ((s = s.toLowerCase()).startsWith("-inf")) { sascha@424: return -Double.MAX_VALUE; // XXX: Not using Double.NEGATIVE_INFINITY! sascha@424: } sascha@424: sascha@424: if (s.startsWith("inf")) { sascha@424: return Double.MAX_VALUE; // XXX: Not using Double.NEGATIVE_INFINITY! sascha@424: } sascha@424: sascha@424: return Double.parseDouble(s); sascha@424: } sascha@424: sascha@424: private static final Color color(String s) { sascha@424: if (s == null || (s = s.trim()).length() == 0) { sascha@424: return null; sascha@424: } sascha@424: return Color.decode(s); sascha@424: } sascha@424: sascha@424: sascha@801: /** sascha@801: * Internal method to build the lookup tree. sascha@801: */ sascha@424: protected void buildLookup() { sascha@424: Arrays.sort(entries); sascha@424: lookup = buildLookup(entries, 0, entries.length-1); sascha@424: } sascha@424: sascha@801: /** sascha@801: * Creates a new palette which has ranges that are subdivided sascha@801: * into a given number of sub ranges each. sascha@801: * @param N The number od sub divisions of each range. sascha@801: * @return The new created palette. sascha@801: */ sascha@435: public Palette subdivide(int N) { sascha@435: return new Palette(this, N); sascha@435: } sascha@435: sascha@801: /** sascha@801: * Return the number of entries in this palette. sascha@801: * @return The number of entries. sascha@801: */ sascha@424: public int getSize() { sascha@424: return rgbs.length; sascha@424: } sascha@424: sascha@801: /** sascha@801: * Return the color of an entry for a given index. sascha@801: * @param index The index. sascha@801: * @return The color. sascha@801: */ sascha@424: public Color getColor(int index) { sascha@424: return rgbs[index]; sascha@424: } sascha@424: sascha@801: /** sascha@801: * Return the RGB value sascha@801: * @param index of an entry for a given index. sascha@801: * @return rgb value sascha@801: */ sascha@424: public int indexToRGB(int index) { sascha@424: return rgbs[index].getRGB(); sascha@424: } sascha@424: sascha@801: /** sascha@801: * Searches for the index of an entry which contains the sascha@801: * given value. sascha@801: * @param value The value to be searched. sascha@801: * @return The index of the entry or -1 if no entry is found. sascha@801: */ sascha@424: public int toIndex(double value) { sascha@424: return lookup.locate(value); sascha@424: } sascha@424: sascha@801: /** sascha@801: * Searches for the an entry which contains the given value. sascha@801: * @param value The value to be searched. sascha@801: * @return The entry which contains the value or null if sascha@801: * no such entry is found. sascha@801: */ sascha@424: public Entry getEntry(double value) { sascha@424: return lookup.locateEntry(value); sascha@424: } sascha@424: sascha@801: /** sascha@801: * Searches the entry for a given index. sascha@801: * @param index The index of the entry. sascha@801: * @return The entry or null if no such entry is found. sascha@801: */ sascha@424: public Entry getEntryByIndex(int index) { sascha@424: return lookup.findByIndex(index); sascha@424: } sascha@424: } sascha@798: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :