001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Dimension; 007import java.awt.image.BufferedImage; 008import java.io.File; 009import java.io.FileNotFoundException; 010import java.io.IOException; 011import java.io.InputStream; 012import java.nio.file.NoSuchFileException; 013import java.nio.file.Paths; 014import java.util.ArrayList; 015import java.util.Arrays; 016import java.util.List; 017import java.util.Locale; 018import java.util.Optional; 019import java.util.function.DoubleSupplier; 020import java.util.logging.Level; 021 022import javax.imageio.ImageIO; 023 024import org.openstreetmap.gui.jmapviewer.OsmMercator; 025import org.openstreetmap.josm.cli.CLIModule; 026import org.openstreetmap.josm.data.Bounds; 027import org.openstreetmap.josm.data.ProjectionBounds; 028import org.openstreetmap.josm.data.coor.EastNorth; 029import org.openstreetmap.josm.data.coor.LatLon; 030import org.openstreetmap.josm.data.coor.conversion.LatLonParser; 031import org.openstreetmap.josm.data.osm.DataSet; 032import org.openstreetmap.josm.data.preferences.JosmBaseDirectories; 033import org.openstreetmap.josm.data.preferences.JosmUrls; 034import org.openstreetmap.josm.data.projection.Projection; 035import org.openstreetmap.josm.data.projection.ProjectionRegistry; 036import org.openstreetmap.josm.data.projection.Projections; 037import org.openstreetmap.josm.gui.mappaint.RenderingHelper.StyleData; 038import org.openstreetmap.josm.io.Compression; 039import org.openstreetmap.josm.io.IllegalDataException; 040import org.openstreetmap.josm.io.OsmReader; 041import org.openstreetmap.josm.spi.preferences.Config; 042import org.openstreetmap.josm.spi.preferences.MemoryPreferences; 043import org.openstreetmap.josm.tools.Http1Client; 044import org.openstreetmap.josm.tools.HttpClient; 045import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider; 046import org.openstreetmap.josm.tools.Logging; 047import org.openstreetmap.josm.tools.OptionParser; 048import org.openstreetmap.josm.tools.OptionParser.OptionCount; 049import org.openstreetmap.josm.tools.OptionParser.OptionParseException; 050import org.openstreetmap.josm.tools.Stopwatch; 051import org.openstreetmap.josm.tools.Territories; 052 053/** 054 * Command line interface for rendering osm data to an image file. 055 * 056 * @since 12906 057 */ 058public class RenderingCLI implements CLIModule { 059 060 /** 061 * The singleton instance of this class. 062 */ 063 public static final RenderingCLI INSTANCE = new RenderingCLI(); 064 065 private static final double PIXEL_PER_METER = 96 / 2.54 * 100; // standard value of 96 dpi display resolution 066 private static final int DEFAULT_MAX_IMAGE_SIZE = 20000; 067 068 private boolean argDebug; 069 private boolean argTrace; 070 private String argInput; 071 private String argOutput; 072 private List<StyleData> argStyles; 073 private Integer argZoom; 074 private Double argScale; 075 private Bounds argBounds; 076 private LatLon argAnchor; 077 private Double argWidthM; 078 private Double argHeightM; 079 private Integer argWidthPx; 080 private Integer argHeightPx; 081 private String argProjection; 082 private Integer argMaxImageSize; 083 084 private StyleData argCurrentStyle; 085 086 private enum Option { 087 HELP(false, 'h'), 088 DEBUG(false, '*'), 089 TRACE(false, '*'), 090 INPUT(true, 'i'), 091 STYLE(true, 's'), 092 SETTING(true, '*'), 093 OUTPUT(true, 'o'), 094 ZOOM(true, 'z'), 095 SCALE(true, '*'), 096 BOUNDS(true, 'b'), 097 ANCHOR(true, '*'), 098 WIDTH_M(true, '*'), 099 HEIGHT_M(true, '*'), 100 WIDTH_PX(true, '*'), 101 HEIGHT_PX(true, '*'), 102 PROJECTION(true, '*'), 103 MAX_IMAGE_SIZE(true, '*'); 104 105 private final String name; 106 private final boolean requiresArg; 107 private final char shortOption; 108 109 Option(boolean requiresArgument, char shortOption) { 110 this.name = name().toLowerCase(Locale.US).replace('_', '-'); 111 this.requiresArg = requiresArgument; 112 this.shortOption = shortOption; 113 } 114 115 /** 116 * Replies the option name 117 * @return The option name, in lowercase 118 */ 119 public String getName() { 120 return name; 121 } 122 123 /** 124 * Determines if this option requires an argument. 125 * @return {@code true} if this option requires an argument, {@code false} otherwise 126 */ 127 public boolean requiresArgument() { 128 return requiresArg; 129 } 130 131 /** 132 * Replies the short option (single letter) associated with this option. 133 * @return the short option or '*' if there is no short option 134 */ 135 public char getShortOption() { 136 return shortOption; 137 } 138 } 139 140 /** 141 * Data class to hold return values for {@link #determineRenderingArea(DataSet)}. 142 * 143 * Package private access for unit tests. 144 */ 145 static class RenderingArea { 146 public Bounds bounds; 147 public double scale; // in east-north units per pixel (unlike the --scale option, which is in meter per meter) 148 } 149 150 RenderingCLI() { 151 // hide constructor (package private access for unit tests) 152 } 153 154 @Override 155 public String getActionKeyword() { 156 return "render"; 157 } 158 159 @Override 160 public void processArguments(String[] argArray) { 161 try { 162 parseArguments(argArray); 163 initialize(); 164 Stopwatch stopwatch = Stopwatch.createStarted(); 165 String task = tr("Rendering {0} to {1}", argInput, argOutput); 166 System.err.println(task); 167 DataSet ds = loadDataset(); 168 RenderingArea area = determineRenderingArea(ds); 169 RenderingHelper rh = new RenderingHelper(ds, area.bounds, area.scale, argStyles); 170 checkPreconditions(rh); 171 BufferedImage image = rh.render(); 172 writeImageToFile(image); 173 System.err.println(stopwatch.toString(task)); 174 } catch (FileNotFoundException | NoSuchFileException e) { 175 if (Logging.isDebugEnabled()) { 176 e.printStackTrace(); 177 } 178 System.err.println(tr("Error - file not found: ''{0}''", e.getMessage())); 179 System.exit(1); 180 } catch (IllegalArgumentException | IllegalDataException | IOException e) { 181 if (Logging.isDebugEnabled()) { 182 e.printStackTrace(); 183 } 184 if (e.getMessage() != null) { 185 System.err.println(tr("Error: {0}", e.getMessage())); 186 } 187 System.exit(1); 188 } 189 System.exit(0); 190 } 191 192 /** 193 * Parse command line arguments and do some low-level error checking. 194 * @param argArray the arguments array 195 */ 196 void parseArguments(String[] argArray) { 197 Logging.setLogLevel(Level.INFO); 198 199 OptionParser parser = new OptionParser("JOSM rendering"); 200 for (Option o : Option.values()) { 201 if (o.requiresArgument()) { 202 parser.addArgumentParameter(o.getName(), 203 o == Option.SETTING ? OptionCount.MULTIPLE : OptionCount.OPTIONAL, 204 arg -> handleOption(o, arg)); 205 } else { 206 parser.addFlagParameter(o.getName(), () -> handleOption(o)); 207 } 208 if (o.getShortOption() != '*') { 209 parser.addShortAlias(o.getName(), Character.toString(o.getShortOption())); 210 } 211 } 212 213 argCurrentStyle = new StyleData(); 214 argStyles = new ArrayList<>(); 215 216 parser.parseOptionsOrExit(Arrays.asList(argArray)); 217 218 if (argCurrentStyle.styleUrl != null) { 219 argStyles.add(argCurrentStyle); 220 } else if (argStyles.isEmpty()) { 221 argCurrentStyle.styleUrl = "resource://styles/standard/elemstyles.mapcss"; 222 argStyles.add(argCurrentStyle); 223 } 224 } 225 226 private void handleOption(Option o) { 227 switch (o) { 228 case HELP: 229 showHelp(); 230 System.exit(0); 231 break; 232 case DEBUG: 233 argDebug = true; 234 break; 235 case TRACE: 236 argTrace = true; 237 break; 238 default: 239 throw new AssertionError("Unexpected option index: " + o); 240 } 241 } 242 243 private void handleOption(Option o, String arg) { 244 switch (o) { 245 case INPUT: 246 argInput = arg; 247 break; 248 case STYLE: 249 if (argCurrentStyle.styleUrl != null) { 250 argStyles.add(argCurrentStyle); 251 argCurrentStyle = new StyleData(); 252 } 253 argCurrentStyle.styleUrl = arg; 254 break; 255 case OUTPUT: 256 argOutput = arg; 257 break; 258 case ZOOM: 259 try { 260 argZoom = Integer.valueOf(arg); 261 } catch (NumberFormatException nfe) { 262 throw new OptionParseException( 263 tr("Expected integer number for option {0}, but got ''{1}''", "--zoom", arg), nfe); 264 } 265 if (argZoom < 0) { 266 throw new OptionParseException( 267 tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--zoom", arg)); 268 } 269 break; 270 case BOUNDS: 271 if (!"auto".equals(arg)) { 272 try { 273 argBounds = new Bounds(arg, ",", Bounds.ParseMethod.LEFT_BOTTOM_RIGHT_TOP, false); 274 } catch (IllegalArgumentException iae) { // NOPMD 275 throw new OptionParseException( 276 tr("Unable to parse {0} parameter: {1}", "--bounds", iae.getMessage()), iae); 277 } 278 } 279 break; 280 281 case SETTING: 282 String keyval = arg; 283 String[] comp = keyval.split(":", 2); 284 if (comp.length != 2) { 285 throw new OptionParseException( 286 tr("Expected key and value, separated by '':'' character for option {0}, but got ''{1}''", 287 "--setting", arg)); 288 } 289 argCurrentStyle.settings.put(comp[0].trim(), comp[1].trim()); 290 break; 291 case SCALE: 292 try { 293 argScale = JosmDecimalFormatSymbolsProvider.parseDouble(arg); 294 } catch (NumberFormatException nfe) { 295 throw new OptionParseException( 296 tr("Expected floating point number for option {0}, but got ''{1}''", "--scale", arg), nfe); 297 } 298 break; 299 case ANCHOR: 300 String[] parts = arg.split(",", -1); 301 if (parts.length != 2) 302 throw new OptionParseException( 303 tr("Expected two coordinates, separated by comma, for option {0}, but got ''{1}''", "--anchor", 304 arg)); 305 try { 306 double lon = LatLonParser.parseCoordinate(parts[0]); 307 double lat = LatLonParser.parseCoordinate(parts[1]); 308 argAnchor = new LatLon(lat, lon); 309 } catch (IllegalArgumentException iae) { // NOPMD 310 throw new OptionParseException(tr("In option {0}: {1}", "--anchor", iae.getMessage()), iae); 311 } 312 break; 313 case WIDTH_M: 314 try { 315 argWidthM = JosmDecimalFormatSymbolsProvider.parseDouble(arg); 316 } catch (NumberFormatException nfe) { 317 throw new OptionParseException( 318 tr("Expected floating point number for option {0}, but got ''{1}''", "--width-m", arg), nfe); 319 } 320 if (argWidthM <= 0) 321 throw new OptionParseException( 322 tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", arg)); 323 break; 324 case HEIGHT_M: 325 try { 326 argHeightM = JosmDecimalFormatSymbolsProvider.parseDouble(arg); 327 } catch (NumberFormatException nfe) { 328 throw new OptionParseException( 329 tr("Expected floating point number for option {0}, but got ''{1}''", "--height-m", arg), nfe); 330 } 331 if (argHeightM <= 0) 332 throw new OptionParseException( 333 tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", arg)); 334 break; 335 case WIDTH_PX: 336 try { 337 argWidthPx = Integer.valueOf(arg); 338 } catch (NumberFormatException nfe) { 339 throw new OptionParseException( 340 tr("Expected integer number for option {0}, but got ''{1}''", "--width-px", arg), nfe); 341 } 342 if (argWidthPx <= 0) 343 throw new OptionParseException( 344 tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--width-px", arg)); 345 break; 346 case HEIGHT_PX: 347 try { 348 argHeightPx = Integer.valueOf(arg); 349 } catch (NumberFormatException nfe) { 350 throw new OptionParseException( 351 tr("Expected integer number for option {0}, but got ''{1}''", "--height-px", arg), nfe); 352 } 353 if (argHeightPx <= 0) { 354 throw new OptionParseException( 355 tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--height-px", arg)); 356 } 357 break; 358 case PROJECTION: 359 argProjection = arg; 360 break; 361 case MAX_IMAGE_SIZE: 362 try { 363 argMaxImageSize = Integer.valueOf(arg); 364 } catch (NumberFormatException nfe) { 365 throw new OptionParseException( 366 tr("Expected integer number for option {0}, but got ''{1}''", "--max-image-size", arg), nfe); 367 } 368 if (argMaxImageSize < 0) { 369 throw new OptionParseException( 370 tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--max-image-size", arg)); 371 } 372 break; 373 default: 374 throw new AssertionError("Unexpected option index: " + o); 375 } 376 } 377 378 /** 379 * Displays help on the console 380 */ 381 public static void showHelp() { 382 System.out.println(getHelp()); 383 } 384 385 private static String getHelp() { 386 return tr("JOSM rendering command line interface")+"\n\n"+ 387 tr("Usage")+":\n"+ 388 "\tjava -jar josm.jar render <options>\n\n"+ 389 tr("Description")+":\n"+ 390 tr("Renders data and saves the result to an image file.")+"\n\n"+ 391 tr("Options")+":\n"+ 392 "\t--help|-h "+tr("Show this help")+"\n"+ 393 "\t--input|-i <file> "+tr("Input data file name (.osm)")+"\n"+ 394 "\t--output|-o <file> "+tr("Output image file name (.png); defaults to ''{0}''", "out.png")+"\n"+ 395 "\t--style|-s <file> "+tr("Style file to use for rendering (.mapcss or .zip)")+"\n"+ 396 "\t "+tr("This option can be repeated to load multiple styles.")+"\n"+ 397 "\t--setting <key>:<value> "+tr("Style setting (in JOSM accessible in the style list dialog right click menu)")+"\n"+ 398 "\t "+tr("Applies to the last style loaded with the {0} option.", "--style")+"\n"+ 399 "\t--zoom|-z <lvl> "+tr("Select zoom level to render. (integer value, 0=entire earth, 18=street level)")+"\n"+ 400 "\t--scale <scale> "+tr("Select the map scale")+"\n"+ 401 "\t "+tr("A value of 10000 denotes a scale of 1:10000 (1 cm on the map equals 100 m on the ground; " 402 + "display resolution: 96 dpi)")+"\n"+ 403 "\t "+tr("Options {0} and {1} are mutually exclusive.", "--zoom", "--scale")+"\n"+ 404 "\t--bounds|-b auto|<min_lon>,<min_lat>,<max_lon>,<max_lat>\n"+ 405 "\t "+tr("Area to render, default value is ''{0}''", "auto")+"\n"+ 406 "\t "+tr("With keyword ''{0}'', the downloaded area in the .osm input file will be used (if recorded).", 407 "auto")+"\n"+ 408 "\t--anchor <lon>,<lat> "+tr("Specify bottom left corner of the rendering area")+"\n"+ 409 "\t "+tr("Used in combination with width and height options to determine the area to render.")+"\n"+ 410 "\t--width-m <number> "+tr("Width of the rendered area, in meter")+"\n"+ 411 "\t--height-m <number> "+tr("Height of the rendered area, in meter")+"\n"+ 412 "\t--width-px <number> "+tr("Width of the target image, in pixel")+"\n"+ 413 "\t--height-px <number> "+tr("Height of the target image, in pixel")+"\n"+ 414 "\t--projection <code> "+tr("Projection to use, default value ''{0}'' (web-Mercator)", "epsg:3857")+"\n"+ 415 "\t--max-image-size <number> "+tr("Maximum image width/height in pixel (''{0}'' means no limit), default value: {1}", 416 0, Integer.toString(DEFAULT_MAX_IMAGE_SIZE))+"\n"+ 417 "\n"+ 418 tr("To specify the rendered area and scale, the options can be combined in various ways")+":\n"+ 419 " * --bounds (--zoom|--scale|--width-px|--height-px)\n"+ 420 " * --anchor (--width-m|--width-px) (--height-m|--height-px) (--zoom|--scale)\n"+ 421 " * --anchor --width-m --height-m (--width-px|--height-px)\n"+ 422 " * --anchor --width-px --height-px (--width-m|--height-m)\n"+ 423 tr("If neither ''{0}'' nor ''{1}'' is given, the default value {2} takes effect " 424 + "and the bounds of the download area in the .osm input file are used.", 425 "bounds", "anchor", "--bounds=auto")+"\n\n"+ 426 tr("Examples")+":\n"+ 427 " java -jar josm.jar render -i data.osm -s style.mapcss -z 16\n"+ 428 " josm render -i data.osm -s style.mapcss --scale 5000\n"+ 429 " josm render -i data.osm -s style.mapcss -z 16 -o image.png\n"+ 430 " josm render -i data.osm -s elemstyles.mapcss --setting hide_icons:false -z 16\n"+ 431 " josm render -i data.osm -s style.mapcss -s another_style.mapcss -z 16 -o image.png\n"+ 432 " josm render -i data.osm -s style.mapcss --bounds 21.151,51.401,21.152,51.402 -z 16\n"+ 433 " josm render -i data.osm -s style.mapcss --anchor 21.151,51.401 --width-m 500 --height-m 300 -z 16\n"+ 434 " josm render -i data.osm -s style.mapcss --anchor 21.151,51.401 --width-m 500 --height-m 300 --width-px 1800\n"+ 435 " josm render -i data.osm -s style.mapcss --scale 5000 --projection epsg:4326\n"; 436 } 437 438 /** 439 * Initialization. 440 * 441 * Requires arguments to be parsed already ({@link #parseArguments(java.lang.String[])}). 442 */ 443 void initialize() { 444 Logging.setLogLevel(getLogLevel()); 445 HttpClient.setFactory(Http1Client::new); 446 447 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance()); // for right-left-hand traffic cache file 448 Config.setPreferencesInstance(new MemoryPreferences()); 449 Config.setUrlsProvider(JosmUrls.getInstance()); 450 Config.getPref().putBoolean("mappaint.auto_reload_local_styles", false); // unnecessary to listen for external changes 451 String projCode = Optional.ofNullable(argProjection).orElse("epsg:3857"); 452 ProjectionRegistry.setProjection(Projections.getProjectionByCode(projCode.toUpperCase(Locale.US))); 453 454 Territories.initializeInternalData(); 455 } 456 457 private Level getLogLevel() { 458 if (argTrace) { 459 return Logging.LEVEL_TRACE; 460 } else if (argDebug) { 461 return Logging.LEVEL_DEBUG; 462 } else { 463 return Logging.LEVEL_INFO; 464 } 465 } 466 467 /** 468 * Find the area to render and the scale, given certain command line options and the dataset. 469 * @param ds the dataset 470 * @return area to render and the scale 471 */ 472 RenderingArea determineRenderingArea(DataSet ds) { 473 474 Projection proj = ProjectionRegistry.getProjection(); 475 Double scale = null; // scale in east-north units per pixel 476 if (argZoom != null) { 477 scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, argZoom) / OsmMercator.DEFAUL_TILE_SIZE 478 / proj.getMetersPerUnit(); 479 } 480 Bounds bounds = argBounds; 481 ProjectionBounds pb = null; 482 483 if (bounds == null) { 484 if (argAnchor != null) { 485 EastNorth projAnchor = proj.latlon2eastNorth(argAnchor); 486 487 double enPerMeter = Double.NaN; 488 DoubleSupplier getEnPerMeter = () -> { 489 double shiftMeter = 10; 490 EastNorth projAnchorShifted = projAnchor.add(shiftMeter / proj.getMetersPerUnit(), 491 shiftMeter / proj.getMetersPerUnit()); 492 LatLon anchorShifted = proj.eastNorth2latlon(projAnchorShifted); 493 return projAnchor.distance(projAnchorShifted) / argAnchor.greatCircleDistance(anchorShifted); 494 }; 495 496 if (scale == null) { 497 if (argScale != null) { 498 enPerMeter = getEnPerMeter.getAsDouble(); 499 scale = argScale * enPerMeter / PIXEL_PER_METER; 500 } else if (argWidthM != null && argWidthPx != null) { 501 enPerMeter = getEnPerMeter.getAsDouble(); 502 scale = argWidthM / argWidthPx * enPerMeter; 503 } else if (argHeightM != null && argHeightPx != null) { 504 enPerMeter = getEnPerMeter.getAsDouble(); 505 scale = argHeightM / argHeightPx * enPerMeter; 506 } else { 507 throw new IllegalArgumentException( 508 tr("Argument {0} given, but scale cannot be determined from remaining arguments", 509 "--anchor")); 510 } 511 } 512 513 double widthEn; 514 if (argWidthM != null) { 515 if (Double.isNaN(enPerMeter)) { 516 enPerMeter = getEnPerMeter.getAsDouble(); 517 } 518 widthEn = argWidthM * enPerMeter; 519 } else if (argWidthPx != null) { 520 widthEn = argWidthPx * scale; 521 } else { 522 throw new IllegalArgumentException( 523 tr("Argument {0} given, expected {1} or {2}", "--anchor", "--width-m", "--width-px")); 524 } 525 526 double heightEn; 527 if (argHeightM != null) { 528 if (Double.isNaN(enPerMeter)) { 529 enPerMeter = getEnPerMeter.getAsDouble(); 530 } 531 heightEn = argHeightM * enPerMeter; 532 } else if (argHeightPx != null) { 533 heightEn = argHeightPx * scale; 534 } else { 535 throw new IllegalArgumentException( 536 tr("Argument {0} given, expected {1} or {2}", "--anchor", "--height-m", "--height-px")); 537 } 538 pb = new ProjectionBounds(projAnchor); 539 pb.extend(new EastNorth(projAnchor.east() + widthEn, projAnchor.north() + heightEn)); 540 bounds = new Bounds(proj.eastNorth2latlon(pb.getMin()), false); 541 bounds.extend(proj.eastNorth2latlon(pb.getMax())); 542 } else { 543 if (ds.getDataSourceBounds().isEmpty()) { 544 throw new IllegalArgumentException( 545 tr("{0} mode, but no bounds found in osm data input file", "--bounds=auto")); 546 } 547 bounds = ds.getDataSourceBounds().get(0); 548 } 549 } 550 551 if (pb == null) { 552 pb = new ProjectionBounds(); 553 pb.extend(proj.latlon2eastNorth(bounds.getMin())); 554 pb.extend(proj.latlon2eastNorth(bounds.getMax())); 555 } 556 557 if (scale == null) { 558 if (argScale != null) { 559 double enPerMeter = pb.getMin().distance(pb.getMax()) 560 / bounds.getMin().greatCircleDistance(bounds.getMax()); 561 scale = argScale * enPerMeter / PIXEL_PER_METER; 562 } else if (argWidthPx != null) { 563 scale = (pb.maxEast - pb.minEast) / argWidthPx; 564 } else if (argHeightPx != null) { 565 scale = (pb.maxNorth - pb.minNorth) / argHeightPx; 566 } else { 567 throw new IllegalArgumentException( 568 tr("Unable to determine scale, one of the options {0}, {1}, {2} or {3} expected", "--zoom", 569 "--scale", "--width-px", "--height-px")); 570 } 571 } 572 573 RenderingArea ra = new RenderingArea(); 574 ra.bounds = bounds; 575 ra.scale = scale; 576 return ra; 577 } 578 579 private DataSet loadDataset() throws IOException, IllegalDataException { 580 if (argInput == null) { 581 throw new IllegalArgumentException(tr("Missing argument - input data file ({0})", "--input|-i")); 582 } 583 try (InputStream inputStream = Compression.getUncompressedFileInputStream(Paths.get(argInput))) { 584 return OsmReader.parseDataSet(inputStream, null); 585 } catch (IllegalDataException e) { 586 throw new IllegalDataException(tr("In .osm data file ''{0}'' - ", argInput) + e.getMessage(), e); 587 } 588 } 589 590 private void checkPreconditions(RenderingHelper rh) { 591 Dimension imgSize = rh.getImageSize(); 592 Logging.debug("image size (px): {0}x{1}", imgSize.width, imgSize.height); 593 int maxSize = Optional.ofNullable(argMaxImageSize).orElse(DEFAULT_MAX_IMAGE_SIZE); 594 if (maxSize != 0 && (imgSize.width > maxSize || imgSize.height > maxSize)) { 595 throw new IllegalArgumentException( 596 tr("Image dimensions ({0}x{1}) exceeds maximum image size {2} (use option {3} to change limit)", 597 imgSize.width, imgSize.height, maxSize, "--max-image-size")); 598 } 599 } 600 601 private void writeImageToFile(BufferedImage image) throws IOException { 602 String output = Optional.ofNullable(argOutput).orElse("out.png"); 603 ImageIO.write(image, "png", new File(output)); 604 } 605}