001 /*
002 * Copyright 2011 Christian Kumpe http://kumpe.de/christian/java
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package de.kumpe.hadooptimizer.examples.painting;
017
018 import static java.lang.Double.*;
019 import static java.lang.String.*;
020
021 import java.io.IOException;
022 import java.io.OutputStream;
023 import java.util.Locale;
024
025 import javax.xml.parsers.DocumentBuilder;
026 import javax.xml.parsers.DocumentBuilderFactory;
027 import javax.xml.parsers.ParserConfigurationException;
028 import javax.xml.transform.Transformer;
029 import javax.xml.transform.TransformerConfigurationException;
030 import javax.xml.transform.TransformerException;
031 import javax.xml.transform.TransformerFactory;
032 import javax.xml.transform.TransformerFactoryConfigurationError;
033 import javax.xml.transform.dom.DOMSource;
034 import javax.xml.transform.stream.StreamResult;
035 import javax.xml.xpath.XPath;
036 import javax.xml.xpath.XPathConstants;
037 import javax.xml.xpath.XPathExpressionException;
038 import javax.xml.xpath.XPathFactory;
039
040 import org.apache.commons.cli.Options;
041 import org.apache.hadoop.conf.Configuration;
042 import org.apache.hadoop.fs.FileSystem;
043 import org.apache.hadoop.fs.Path;
044 import org.apache.hadoop.io.LongWritable;
045 import org.apache.hadoop.io.ObjectWritable;
046 import org.apache.hadoop.io.SequenceFile;
047 import org.apache.hadoop.io.Text;
048 import org.apache.hadoop.mapreduce.Job;
049 import org.apache.hadoop.mapreduce.Mapper;
050 import org.apache.hadoop.mapreduce.Reducer;
051 import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
052 import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
053 import org.w3c.dom.DOMException;
054 import org.w3c.dom.Document;
055 import org.w3c.dom.Element;
056 import org.xml.sax.SAXException;
057
058 import de.kumpe.hadooptimizer.examples.Example;
059 import de.kumpe.hadooptimizer.hadoop.IndividualInputFormat;
060
061 /**
062 * The {@link SvgRenderer} uses a MapReduce-Job to convert the results.txt of a
063 * {@link Kit} run into a SVG-Image of every 1000 evolution-cycle's best result.
064 * <p>
065 * It also writes these results into a {@link SequenceFile} with the cycle as a
066 * {@link LongWritable} key and the values as an {@code double[]} wrapped into
067 * an {@link ObjectWritable}.
068 * <p>
069 * The results.txt has to be given as the first argument.
070 *
071 * @author <a href="http://kumpe.de/christian/java">Christian Kumpe</a>
072 */
073 public class SvgRenderer extends Example {
074 private static final String OUTPUT_PATH = "de.kumpe.hadooptimizer.examples.painting.SvgRenderer.output";
075
076 /**
077 * The Mapper to do the actual work. See {@link SvgRenderer} for details. *
078 *
079 * @author <a href="http://kumpe.de/christian/java">Christian Kumpe</a>
080 */
081 public static class SvgRendererMapper extends
082 Mapper<LongWritable, Text, LongWritable, ObjectWritable> {
083 private Element[] triangles;
084 private DOMSource source;
085 private Transformer transformer;
086 private StreamResult result;
087 private String outputPath;
088
089 @Override
090 protected void setup(final Context context) throws IOException,
091 InterruptedException {
092 try {
093 // parse the result-template
094 final DocumentBuilder documentBuilder = DocumentBuilderFactory
095 .newInstance().newDocumentBuilder();
096 final Document document = documentBuilder.parse(getClass()
097 .getResourceAsStream("/ResultTemplate.svg"));
098
099 // extract necessary elements
100 final XPath xPath = XPathFactory.newInstance().newXPath();
101 final Element triangle = (Element) xPath.evaluate(
102 "//path[@id='dreieck']", document, XPathConstants.NODE);
103 triangle.removeAttribute("id");
104 final Element parent = (Element) triangle.getParentNode();
105 parent.removeChild(triangle);
106
107 // create 50 triangles
108 triangles = new Element[50];
109 for (int i = 0; i < triangles.length; i++) {
110 triangles[i] = (Element) triangle.cloneNode(false);
111 parent.appendChild(triangles[i]);
112 }
113
114 // initialize the transformer for writing the SVG-output
115 source = new DOMSource(document);
116 result = new StreamResult();
117 transformer = TransformerFactory.newInstance().newTransformer();
118
119 outputPath = context.getConfiguration().get(OUTPUT_PATH);
120 } catch (final DOMException e) {
121 throw new RuntimeException(e);
122 } catch (final ParserConfigurationException e) {
123 throw new RuntimeException(e);
124 } catch (final SAXException e) {
125 throw new RuntimeException(e);
126 } catch (final XPathExpressionException e) {
127 throw new RuntimeException(e);
128 } catch (final TransformerConfigurationException e) {
129 throw new RuntimeException(e);
130 } catch (final TransformerFactoryConfigurationError e) {
131 throw new RuntimeException(e);
132 }
133 }
134
135 /**
136 * Parses the given line and if the best result of every 1000th cycle,
137 * create an SVG-image and write the parsed double-values into the
138 * output.
139 */
140 @Override
141 protected void map(final LongWritable key, final Text value,
142 final Context context) throws IOException, InterruptedException {
143 final String line = value.toString();
144 if (line.startsWith("#")) {
145 // it's a comment
146 return;
147 }
148
149 final String[] parts = line.split("[ \\t\\[\\],]+");
150 if (!"0".equals(parts[1])) {
151 // it's not the best result of the cycle
152 return;
153 }
154
155 final long cycle = Long.parseLong(parts[0]);
156 if (cycle % 1000 != 0) {
157 // it's not thousand cycle
158 return;
159 }
160
161 final double[] values = new double[parts.length];
162 for (int i = 0; i < parts.length; i++) {
163 if (4 == i) {
164 // remove "increment="-prefix
165 parts[i] = parts[i].split("=")[1];
166 }
167 values[i] = parseDouble(parts[i]);
168 }
169
170 configureTriangles(values);
171 writeSvgResultDocument(cycle);
172
173 context.write(new LongWritable(cycle), new ObjectWritable(values));
174 }
175
176 private void configureTriangles(final double[] values) {
177 for (int i = 0; i < triangles.length; i++) {
178 // extract and clip the values
179 final double opacity = clip(values[7 * i + 5 + 0] / 312, 0, 1);
180 final double x1 = clip(values[7 * i + 5 + 1], 0, 312);
181 final double y1 = clip(values[7 * i + 5 + 2], 0, 312);
182 final double x2 = clip(values[7 * i + 5 + 3], 0, 312);
183 final double y2 = clip(values[7 * i + 5 + 4], 0, 312);
184 final double x3 = clip(values[7 * i + 5 + 5], 0, 312);
185 final double y3 = clip(values[7 * i + 5 + 6], 0, 312);
186
187 // set the coordinates
188 triangles[i].setAttribute(
189 "d",
190 format(Locale.US, "M %f,%f %f,%f %f,%f %1$f,%2$f z",
191 x1, y1, x2, y2, x3, y3));
192
193 // set the opacity
194 triangles[i]
195 .setAttribute(
196 "style",
197 format(Locale.US,
198 "fill:#000000;stroke:#00ffff;stroke-width:0.1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.75;fill-opacity:%f",
199 opacity));
200 }
201 }
202
203 private void writeSvgResultDocument(final long cycle)
204 throws IOException {
205 // create the SVG-file
206 final Path outputPath = new Path(format("%s/result%06d.svg",
207 this.outputPath, cycle));
208 final FileSystem fileSystem = outputPath
209 .getFileSystem(new Configuration());
210 final OutputStream outputStream = fileSystem.create(outputPath);
211 result.setOutputStream(outputStream);
212
213 try {
214 // write the SVG-file
215 transformer.transform(source, result);
216 } catch (final TransformerException e) {
217 throw new RuntimeException(e);
218 }
219
220 outputStream.close();
221 }
222
223 private double clip(final double value, final double min,
224 final double max) {
225 final double diff = max - min;
226 return Math.abs(Math.abs(value % (diff * 2)) - diff) + min;
227 }
228 }
229
230 @Override
231 public Options createOptions() {
232 // no options needed
233 return new Options();
234 }
235
236 @Override
237 public void execute() throws Exception {
238 final String inputPath = (String) commandLine.getArgList().get(0);
239 final Configuration conf = getConf();
240 conf.set(OUTPUT_PATH, baseDir);
241
242 final Job job = new Job(conf, getClass().getSimpleName());
243 job.setJarByClass(IndividualInputFormat.class);
244 job.setMapperClass(SvgRendererMapper.class);
245 // use the identity-reducer
246 job.setReducerClass(Reducer.class);
247 job.setNumReduceTasks(1);
248
249 job.setInputFormatClass(TextInputFormat.class);
250 TextInputFormat.setMinInputSplitSize(job, 256 * 1024 * 1024);
251 TextInputFormat.setInputPaths(job, new Path(inputPath));
252
253 job.setOutputKeyClass(LongWritable.class);
254 job.setOutputValueClass(ObjectWritable.class);
255 job.setOutputFormatClass(SequenceFileOutputFormat.class);
256 SequenceFileOutputFormat.setOutputPath(job, new Path(baseDir
257 + "/output"));
258
259 if (!job.waitForCompletion(true)) {
260 throw new RuntimeException("Job failed.");
261 }
262 }
263
264 @Override
265 protected String getVersionInfo() {
266 return "$Id: SvgRenderer.java 3909 2011-04-25 10:01:48Z baumbart $";
267 }
268 }