source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/RenderingHelper.java@ 17318

Last change on this file since 17318 was 16100, checked in by simon04, 4 years ago

see #18468 - MapCSSRendererTest: generate style debugging output

File size: 8.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Dimension;
8import java.awt.Graphics2D;
9import java.awt.Point;
10import java.awt.RenderingHints;
11import java.awt.image.BufferedImage;
12import java.io.IOException;
13import java.io.PrintStream;
14import java.util.Collection;
15import java.util.HashMap;
16import java.util.Map;
17import java.util.Optional;
18
19import org.openstreetmap.josm.data.Bounds;
20import org.openstreetmap.josm.data.ProjectionBounds;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
24import org.openstreetmap.josm.data.projection.Projection;
25import org.openstreetmap.josm.data.projection.ProjectionRegistry;
26import org.openstreetmap.josm.gui.NavigatableComponent;
27import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
28import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
29import org.openstreetmap.josm.io.IllegalDataException;
30import org.openstreetmap.josm.tools.CheckParameterUtil;
31import org.openstreetmap.josm.tools.Logging;
32
33/**
34 * Class to render osm data to a file.
35 * @since 12963
36 */
37public class RenderingHelper {
38
39 private final DataSet ds;
40 private final Bounds bounds;
41 private final ProjectionBounds projBounds;
42 private final double scale;
43 private final Collection<StyleData> styles;
44 private Color backgroundColor;
45 private boolean fillBackground = true;
46 private PrintStream debugStream;
47
48 /**
49 * Data class to save style settings along with the corresponding style URL.
50 */
51 public static class StyleData {
52 public String styleUrl;
53 public Map<String, String> settings = new HashMap<>();
54 }
55
56 /**
57 * Construct a new {@code RenderingHelper}.
58 * @param ds the dataset to render
59 * @param bounds the bounds of the are to render
60 * @param scale the scale to render at (east/north units per pixel)
61 * @param styles the styles to use for rendering
62 */
63 public RenderingHelper(DataSet ds, Bounds bounds, double scale, Collection<StyleData> styles) {
64 CheckParameterUtil.ensureParameterNotNull(ds, "ds");
65 CheckParameterUtil.ensureParameterNotNull(bounds, "bounds");
66 CheckParameterUtil.ensureParameterNotNull(styles, "styles");
67 this.ds = ds;
68 this.bounds = bounds;
69 this.scale = scale;
70 this.styles = styles;
71 Projection proj = ProjectionRegistry.getProjection();
72 projBounds = new ProjectionBounds();
73 projBounds.extend(proj.latlon2eastNorth(bounds.getMin()));
74 projBounds.extend(proj.latlon2eastNorth(bounds.getMax()));
75 }
76
77 /**
78 * Set the background color to use for rendering.
79 *
80 * @param backgroundColor the background color to use, {@code} means
81 * to determine the background color automatically from the style
82 * @see #setFillBackground(boolean)
83 * @since 12966
84 */
85 public void setBackgroundColor(Color backgroundColor) {
86 this.backgroundColor = backgroundColor;
87 }
88
89 /**
90 * Decide if background should be filled or left transparent.
91 * @param fillBackground true, if background should be filled
92 * @see #setBackgroundColor(java.awt.Color)
93 * @since 12966
94 */
95 public void setFillBackground(boolean fillBackground) {
96 this.fillBackground = fillBackground;
97 }
98
99 Dimension getImageSize() {
100 double widthEn = projBounds.maxEast - projBounds.minEast;
101 double heightEn = projBounds.maxNorth - projBounds.minNorth;
102 int widthPx = (int) Math.round(widthEn / scale);
103 int heightPx = (int) Math.round(heightEn / scale);
104 return new Dimension(widthPx, heightPx);
105 }
106
107 /**
108 * Invoke the renderer.
109 *
110 * @return the rendered image
111 * @throws IOException in case of an IOException
112 * @throws IllegalDataException when illegal data is encountered (style has errors, etc.)
113 */
114 public BufferedImage render() throws IOException, IllegalDataException {
115 // load the styles
116 ElemStyles elemStyles = new ElemStyles();
117 MapCSSStyleSource.STYLE_SOURCE_LOCK.writeLock().lock();
118 try {
119 for (StyleData sd : styles) {
120 MapCSSStyleSource source = new MapCSSStyleSource(sd.styleUrl, "cliRenderingStyle", "cli rendering style '" + sd.styleUrl + "'");
121 source.loadStyleSource();
122 elemStyles.add(source);
123 if (!source.getErrors().isEmpty()) {
124 throw new IllegalDataException("Failed to load style file. Errors: " + source.getErrors());
125 }
126 for (String key : sd.settings.keySet()) {
127 StyleSetting.PropertyStyleSetting<?> match = source.settings.stream()
128 .filter(s -> s instanceof StyleSetting.PropertyStyleSetting)
129 .map(s -> (StyleSetting.PropertyStyleSetting<?>) s)
130 .filter(bs -> bs.getKey().endsWith(":" + key))
131 .findFirst().orElse(null);
132 if (match == null) {
133 Logging.warn(tr("Style setting not found: ''{0}''", key));
134 } else {
135 String value = sd.settings.get(key);
136 Logging.trace("setting applied: ''{0}:{1}''", key, value);
137 match.setStringValue(value);
138 }
139 }
140 if (!sd.settings.isEmpty()) {
141 source.loadStyleSource(); // reload to apply settings
142 }
143 }
144 } finally {
145 MapCSSStyleSource.STYLE_SOURCE_LOCK.writeLock().unlock();
146 }
147
148 Dimension imgDimPx = getImageSize();
149 NavigatableComponent nc = new NavigatableComponent() {
150 {
151 setBounds(0, 0, imgDimPx.width, imgDimPx.height);
152 updateLocationState();
153 }
154
155 @Override
156 protected boolean isVisibleOnScreen() {
157 return true;
158 }
159
160 @Override
161 public Point getLocationOnScreen() {
162 return new Point(0, 0);
163 }
164 };
165 nc.zoomTo(projBounds.getCenter(), scale);
166
167 // render the data
168 BufferedImage image = new BufferedImage(imgDimPx.width, imgDimPx.height, BufferedImage.TYPE_INT_ARGB);
169 Graphics2D g = image.createGraphics();
170
171 // Force all render hints to be defaults - do not use platform values
172 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
173 g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
174 g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
175 g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
176 g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
177 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
178 g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
179 g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
180 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
181
182 if (fillBackground) {
183 g.setColor(Optional.ofNullable(backgroundColor).orElse(elemStyles.getBackgroundColor()));
184 g.fillRect(0, 0, imgDimPx.width, imgDimPx.height);
185 }
186 StyledMapRenderer smr = new StyledMapRenderer(g, nc, false);
187 smr.setStyles(elemStyles);
188 smr.render(ds, false, bounds);
189
190 // For debugging, write computed StyleElement to debugStream for primitives marked with debug=yes
191 if (debugStream != null) {
192 for (OsmPrimitive primitive : ds.allPrimitives()) {
193 if (!primitive.isKeyTrue("debug")) {
194 continue;
195 }
196 debugStream.println(primitive);
197 for (StyleElement styleElement : elemStyles.get(primitive, scale, nc)) {
198 debugStream.append(" * ").println(styleElement);
199 }
200 }
201 }
202
203 return image;
204 }
205
206 void setDebugStream(PrintStream debugStream) {
207 this.debugStream = debugStream;
208 }
209}
Note: See TracBrowser for help on using the repository browser.