require_relative 'formats/svg'
require_relative 'formats/kmz'
require_relative 'formats/mbtiles'
require_relative 'formats/gemf'
require_relative 'formats/zip'
require_relative 'formats/pdf'
require_relative 'formats/svgz'
module NSWTopo
module Formats
using Helpers
include Log
PPI = 300
TILE = 1500
CHROME_ARGS = %w[--force-gpu-mem-available-mb=4096]
CHROME_INSTANCES = (ThreadPool::CORES / 4).clamp(1, 6)
def self.extensions
instance_methods.grep(/^render_([a-z]+)/) { $1 }
end
def self.===(ext)
extensions.any? ext
end
def render_png(png_path, ppi: PPI, dither: false, **options)
ppm = (ppi / 0.0254).round
OS.exiftool yield(ppi: ppi, dither: dither), *%W[
-PNG:PixelsPerUnitX=#{ppm}
-PNG:PixelsPerUnitY=#{ppm}
-o #{png_path}
]
rescue OS::Missing
FileUtils.cp yield(ppi: ppi, dither: dither), png_path
end
def render_tif(tif_path, ppi: PPI, dither: false, **options)
OS.gdal_translate yield(ppi: ppi, dither: dither), *%W[
-of GTiff
-co COMPRESS=DEFLATE
-co ZLEVEL=9
-mo TIFFTAG_XRESOLUTION=#{ppi}
-mo TIFFTAG_YRESOLUTION=#{ppi}
-mo TIFFTAG_RESOLUTIONUNIT=2
], tif_path
end
def render_jpg(jpg_path, ppi: PPI, **options)
OS.gdal_translate yield(ppi: ppi), *%W[
-of JPEG
-co QUALITY=90
-mo EXIF_XResolution=#{ppi}
-mo EXIF_YResolution=#{ppi}
-mo EXIF_ResolutionUnit=2
], jpg_path
end
def rasterise(png_path, background:, ppi: nil, resolution: nil)
Dir.mktmppath do |temp_dir|
svg_path = temp_dir / "map.svg"
vrt_path = temp_dir / "map.vrt"
render_svg svg_path, background: background
case
when ppi
ppi_info = "%i ppi" % ppi
mm_per_px = 25.4 / ppi
when resolution
ppi_info = "%.1f m/px" % resolution
mm_per_px = to_mm(resolution)
end
viewport_size = [TILE * mm_per_px] * 2
raster_size = @dimensions.map { |dimension| (dimension / mm_per_px).ceil }
megapixels = raster_size.inject(&:*) / 1024.0 / 1024.0
raster_info = "%i×%i (%.1fMpx) map raster at %s" % [*raster_size, megapixels, ppi_info]
log_update "chrome: creating #{raster_info}"
raster_size.map do |px|
(0...px).step(TILE).map do |px|
[px, px * mm_per_px]
end
end.inject(&:product).map(&:transpose).map do |raster_offset, viewport_offset|
next raster_offset, viewport_offset, temp_dir.join("tile.%i.%i.png" % raster_offset)
end.inject(ThreadPool.new(CHROME_INSTANCES), &:<<).in_groups do |*grid|
NSWTopo::Chrome.with_browser "file://#{svg_path}", width: TILE, height: TILE, args: CHROME_ARGS do |browser|
svg = browser.query_selector "svg"
svg[:width], svg[:height] = nil, nil
grid.each do |raster_offset, viewport_offset, tile_path|
svg[:viewBox] = [*viewport_offset, *viewport_size].join(?\s)
browser.screenshot tile_path
end
end
end.map do |raster_offset, viewport_offset, tile_path|
REXML::Document.new(OS.gdal_translate "-of", "VRT", tile_path, "/vsistdout/").tap do |vrt|
vrt.elements.each("VRTDataset/VRTRasterBand/SimpleSource/DstRect") do |dst_rect|
dst_rect.add_attributes "xOff" => raster_offset[0], "yOff" => raster_offset[1]
end
end
end.inject do |vrt, tile_vrt|
vrt.elements["VRTDataset/VRTRasterBand[@band='1']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='1']/SimpleSource"]
vrt.elements["VRTDataset/VRTRasterBand[@band='2']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='2']/SimpleSource"]
vrt.elements["VRTDataset/VRTRasterBand[@band='3']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='3']/SimpleSource"]
vrt.elements["VRTDataset/VRTRasterBand[@band='4']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='4']/SimpleSource"]
vrt
end.tap do |vrt|
vrt.elements.each("VRTDataset/VRTRasterBand/@blockYSize", &:remove)
vrt.elements.each("VRTDataset/Metadata", &:remove)
vrt.elements["VRTDataset"].add_attributes "rasterXSize" => raster_size[0], "rasterYSize" => raster_size[1]
File.write vrt_path, vrt
end
log_update "nswtopo: finalising #{raster_info}"
OS.gdal_translate vrt_path, png_path
end
end
end
end