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

Last change on this file was 19549, checked in by stoecker, 6 weeks ago

fix #24678 - Possibility to specify custom MapPaintSettings for MapCSS rendering - patch by zkir

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