/******************************************************************************* * Copyright 2013 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.common.color.io; import java.awt.Color; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.gov.ga.earthsci.common.color.ColorMap; import au.gov.ga.earthsci.common.color.ColorMap.InterpolationMode; import au.gov.ga.earthsci.common.color.MutableColorMap; import au.gov.ga.earthsci.worldwind.common.util.Util; /** * Reads {@link ColorMap} instances from colour map files of the format * supported by the <a * href="http://www.gdal.org/gdaldem.html#gdaldem_color_relief">gdaldem</a> * utility. * <p/> * This reader will also look for special comments in the file (lines beginning * with '#') that can be used to control how the map behaves. These comments * have the form: * * <pre> * #ATTRIBUTE=VALUE * </pre> * * For example, to set the name for the map: * * <pre> * #NAME=My colour map * </pre> * <p/> * Valid attribute names are: * <p/> * <dl> * <dt>NAME</dt> * <dd>The name for the colour map</dd> * <dt>DESCRIPTION</dt> * <dd>The description for the colour map</dd> * <dt>MODE</dt> * <dd>The interpolation mode to use for the colour map. Valid values are: * <ul> * <li>NEAREST_MATCH</li> * <li>EXACT_MATCH</li> * <li>INTERPOLATE_RGB</li> * <li>INTERPOLATE_HUE</li> * </ul> * If not specified, INTERPOLATE_RGB is used. * </dl> * * @author James Navin (james.navin@ga.gov.au) * */ public class GDALDEMColorMapReader implements IColorMapReader { private static final Logger logger = LoggerFactory.getLogger(GDALDEMColorMapReader.class); private static final Pattern ATTRIBUTE_LINE_PATTERN = Pattern.compile("#\\s*(\\w+)\\s*=\\s*(.*?)\\s*$"); //$NON-NLS-1$ private static final Pattern COLOR_LINE_PATTERN = Pattern .compile("(?:((?:\\d+(?:\\.\\d+)?%?)|(?:nv))[ \t:,]+)((?:(?:\\d+(?:\\.\\d+)?)[ \t:,]*){3,4})"); //$NON-NLS-1$ private static final Pattern NAMED_COLOR_LINE_PATTERN = Pattern .compile("(?:((?:\\d+(?:\\.\\d+)?%?)|(?:nv))[ \t:,]+)(\\w+)"); //$NON-NLS-1$ private static final Map<String, Color> NAMED_COLORS = new HashMap<String, Color>() { { put("WHITE", Color.WHITE); //$NON-NLS-1$ put("BLACK", Color.BLACK); //$NON-NLS-1$ put("RED", Color.RED); //$NON-NLS-1$ put("GREEN", Color.GREEN); //$NON-NLS-1$ put("BLUE", Color.BLUE); //$NON-NLS-1$ put("YELLOW", Color.YELLOW); //$NON-NLS-1$ put("MAGENTA", Color.MAGENTA); //$NON-NLS-1$ put("CYAN", Color.CYAN); //$NON-NLS-1$ put("AQUA", Color.CYAN); //$NON-NLS-1$ put("GRAY", Color.GRAY); //$NON-NLS-1$ put("GREY", Color.GRAY); //$NON-NLS-1$ put("ORANGE", Color.ORANGE); //$NON-NLS-1$ put("BROWN", new Color(150, 75, 0)); //$NON-NLS-1$ put("PURPLE", new Color(128, 0, 128)); //$NON-NLS-1$ put("VIOLET", new Color(128, 0, 128)); //$NON-NLS-1$ put("INDIGO", new Color(75, 0, 130)); //$NON-NLS-1$ } }; @Override public String getName() { return Messages.GDALDEMColorMapReader_ReaderName; } @Override public String getDescription() { return Messages.GDALDEMColorMapReader_ReaderDescription; } @Override public boolean supports(Object source) { if (source == null) { return false; } try { return open(source) != null; } catch (IOException e) { return false; } } @Override public ColorMap read(Object source) throws IOException { BufferedReader reader = open(source); if (reader == null) { return null; } MutableColorMap map = new MutableColorMap(); String line = null; while ((line = reader.readLine()) != null) { Matcher m = null; m = ATTRIBUTE_LINE_PATTERN.matcher(line); if (m.matches()) { String name = m.group(1); String value = m.group(2); setAttribute(map, name, value); continue; } m = NAMED_COLOR_LINE_PATTERN.matcher(line); if (m.matches()) { String value = m.group(1); String namedColor = m.group(2); addEntry(map, value, namedColor); continue; } m = COLOR_LINE_PATTERN.matcher(line); if (m.matches()) { String value = m.group(1); String[] colorComponents = m.group(2).split("[\\s,:]+"); //$NON-NLS-1$ addEntry(map, value, colorComponents); continue; } } reader.close(); return map.snapshot(); } private void addEntry(MutableColorMap map, String value, String namedColor) { if (map == null || value == null || namedColor == null) { return; } Color color = NAMED_COLORS.get(namedColor.toUpperCase()); if (color == null) { logger.debug("Unknown color '{}'", namedColor); //$NON-NLS-1$ return; } addEntry(map, value, color); } private void addEntry(MutableColorMap map, String value, String[] colorComponents) { if (map == null || value == null || colorComponents == null || colorComponents.length == 0) { return; } Color color = toColor(colorComponents); if (color == null) { logger.debug("Unknown color " + Arrays.asList(colorComponents)); //$NON-NLS-1$ return; } addEntry(map, value, color); } private void addEntry(MutableColorMap map, String value, Color color) { if (value.equalsIgnoreCase("nv")) //$NON-NLS-1$ { map.setNodataColour(color); return; } // We can't mix percentages and absolute values in the ColorMap, so first one wins. if (isPercentage(value)) { if (!map.isPercentageBased() && !map.isEmpty()) { return; } else if (map.isEmpty()) { map.setValuesArePercentages(true, 0.0, 1.0); } } else { if (map.isPercentageBased() && !map.isEmpty()) { return; } } double theValue = toValue(value); map.addEntry(theValue, color); } private void setAttribute(MutableColorMap map, String name, String value) { if (map == null || name == null || value == null) { return; } if ("NAME".equalsIgnoreCase(name)) //$NON-NLS-1$ { map.setName(value); return; } if ("DESCRIPTION".equalsIgnoreCase(name)) //$NON-NLS-1$ { map.setDescription(value); return; } if ("MODE".equalsIgnoreCase(name)) //$NON-NLS-1$ { try { InterpolationMode mode = InterpolationMode.valueOf(value.toUpperCase()); map.setMode(mode); } catch (Exception e) { // Must be an invalid mode name } return; } } private double toValue(String valueString) { boolean isPercentage = isPercentage(valueString); if (isPercentage) { valueString = valueString.substring(0, valueString.length() - 1); } Double value = Double.valueOf(valueString); if (isPercentage) { value = value / 100.0; } return value; } private boolean isPercentage(String valueString) { return valueString.endsWith("%"); //$NON-NLS-1$ } private Color toColor(String[] components) { if (components == null || components.length < 3 || components.length > 4) { return null; } Integer r, g, b, a; r = toColorChannel(components[0]); g = toColorChannel(components[1]); b = toColorChannel(components[2]); if (components.length > 3) { a = toColorChannel(components[3]); } else { a = 255; } if (r == null || g == null || b == null || a == null) { return null; } return new Color(r, g, b, a); } private Integer toColorChannel(String value) { try { return Util.clamp(Integer.parseInt(value), 0, 255); } catch (Exception e1) { try { float val = Float.parseFloat(value); if (val > 1.0) { return (int) val; } return (int) (val * 255); } catch (Exception e2) { return null; } } } private BufferedReader open(Object source) throws IOException { InputStream is = null; if (source instanceof String) { is = new ByteArrayInputStream(((String) source).getBytes()); } else if (source instanceof InputStream) { is = (InputStream) source; } else if (source instanceof File) { is = new FileInputStream((File) source); } else if (source instanceof URL) { is = ((URL) source).openStream(); } if (is == null) { return null; } return new BufferedReader(new InputStreamReader(is)); } }