diff --git a/Sources/MVTCLI/Dump.swift b/Sources/MVTCLI/Dump.swift index 8953eb4..1d118c9 100644 --- a/Sources/MVTCLI/Dump.swift +++ b/Sources/MVTCLI/Dump.swift @@ -22,12 +22,17 @@ extension CLI { @Flag( name: [.customLong("Di", withSingleDash: true), .long], help: "Don't parse the layer name (option 'property-name') from Feature properties in the input GeoJSONs. Might speed up GeoJSON parsing considerably.") - var disableInputLayerProperty: Bool = false + var disableInputLayerProperty = false @Flag( name: [.customLong("Do", withSingleDash: true), .long], help: "Don't add the layer name (option 'property-name') as a Feature property in the output GeoJSONs.") - var disableOutputLayerProperty: Bool = false + var disableOutputLayerProperty = false + + @Option( + name: [.customLong("oSm", withSingleDash: true), .long], + help: "Simplify output features using meters.") + var simplifyMeters: Int? @OptionGroup var xyzOptions: XYZOptions @@ -77,10 +82,15 @@ extension CLI { } } + var exportOptions = VectorTile.ExportOptions() + if let simplifyMeters, simplifyMeters > 0 { + exportOptions.simplifyFeatures = .meters(Double(simplifyMeters)) + } + if options.verbose { print("Dumping \(tile.origin) tile '\(url.lastPathComponent)' [\(tile.x),\(tile.y)]@\(tile.z)") - print("Property name: \(propertyName)") + print("Layer property name: \(propertyName)") if disableInputLayerProperty { print(" - disable input layer property") } @@ -96,15 +106,22 @@ extension CLI { if tile.origin == .mvt || !disableInputLayerProperty, - let layerAllowlist + let layerAllowlist { print("Layers: '\(layerAllowlist.joined(separator: ","))'") } + + print("Output options:") + print(" - Pretty print: true") + print(" - Simplification: \(exportOptions.simplifyFeatures)") + + print("GeoJSON:") } guard let data = tile.toGeoJson( prettyPrinted: true, - layerProperty: disableOutputLayerProperty ? nil : propertyName) + layerProperty: disableOutputLayerProperty ? nil : propertyName, + options: exportOptions) else { throw CLIError("Failed to extract the tile data as GeoJSON") } print(String(data: data, encoding: .utf8) ?? "", terminator: "") diff --git a/Sources/MVTCLI/Export.swift b/Sources/MVTCLI/Export.swift index dbd12f3..77b07e2 100644 --- a/Sources/MVTCLI/Export.swift +++ b/Sources/MVTCLI/Export.swift @@ -15,6 +15,16 @@ extension CLI { completion: .file(extensions: ["geojson", "json"])) var outputFile: String + @Option( + name: [.customLong("oC", withSingleDash: true), .long], + help: "Output file compression level, between 0=none to 9=best. (default: none)") + var compressionLevel: Int? + + @Option( + name: [.customLong("oSm", withSingleDash: true), .long], + help: "Simplify output features using meters.") + var simplifyMeters: Int? + @Flag( name: .shortAndLong, help: "Overwrite existing files.") @@ -31,9 +41,9 @@ extension CLI { var propertyName: String = VectorTile.defaultLayerPropertyName @Flag( - name: [.customLong("Do", withSingleDash: true), .long], + name: [.customLong("Do", withSingleDash: true), .long], help: "Don't add the layer name (option 'property-name') as a Feature property in the output GeoJSONs.") - var disableOutputLayerProperty: Bool = false + var disableOutputLayerProperty = false @Flag( name: .shortAndLong, @@ -57,19 +67,6 @@ extension CLI { let layerAllowlist = layer.nonempty let outputUrl = URL(fileURLWithPath: outputFile) - if options.verbose { - print("Dumping tile '\(url.lastPathComponent)' [\(x),\(y)]@\(z) to '\(outputUrl.lastPathComponent)'") - print("Property name: \(propertyName)") - - if disableOutputLayerProperty { - print(" - disable output layer property") - } - - if let layerAllowlist { - print("Layers: '\(layerAllowlist.joined(separator: ","))'") - } - } - if (try? outputUrl.checkResourceIsReachable()) ?? false { if forceOverwrite { if options.verbose { @@ -90,9 +87,36 @@ extension CLI { logger: options.verbose ? CLI.logger : nil) else { throw CLIError("Failed to parse the resource at '\(path)'") } + var exportOptions = VectorTile.ExportOptions() + if let simplifyMeters, simplifyMeters > 0 { + exportOptions.simplifyFeatures = .meters(Double(simplifyMeters)) + } + if let compressionLevel, compressionLevel > 0 { + exportOptions.compression = .level(max(0, min(9, compressionLevel))) + } + + if options.verbose { + print("Dumping tile '\(url.lastPathComponent)' [\(x),\(y)]@\(z) to '\(outputUrl.lastPathComponent)'") + + print("Layer property name: \(propertyName)") + if disableOutputLayerProperty { + print(" - disable output layer property") + } + + if let layerAllowlist { + print("Layers: '\(layerAllowlist.joined(separator: ","))'") + } + + print("Output options:") + print(" - Pretty print: \(prettyPrint)") + print(" - Compression: \(exportOptions.compression)") + print(" - Simplification: \(exportOptions.simplifyFeatures)") + } + guard let data = tile.toGeoJson( prettyPrinted: prettyPrint, - layerProperty: disableOutputLayerProperty ? nil : propertyName) + layerProperty: disableOutputLayerProperty ? nil : propertyName, + options: exportOptions) else { throw CLIError("Failed to extract the tile data as GeoJSON") } try data.write(to: outputUrl, options: .atomic) diff --git a/Sources/MVTCLI/Import.swift b/Sources/MVTCLI/Import.swift index 6b33d2e..f56c013 100644 --- a/Sources/MVTCLI/Import.swift +++ b/Sources/MVTCLI/Import.swift @@ -19,7 +19,7 @@ extension CLI { @Option( name: [.customLong("oC", withSingleDash: true), .long], help: "Output file compression level, between 0=none to 9=best.") - var compressionLevel: Int = 9 + var compressionLevel = 9 @Option( name: [.customLong("oBe", withSingleDash: true), .long], @@ -69,7 +69,7 @@ extension CLI { @Flag( name: [.customLong("Di", withSingleDash: true), .long], help: "Don't parse the layer name (option 'property-name') from Feature properties in the input GeoJSONs, just use 'layer-name' or a default. Might speed up GeoJSON parsing considerably.") - var disableInputLayerProperty: Bool = false + var disableInputLayerProperty = false @OptionGroup var xyzOptions: XYZOptions @@ -126,9 +126,9 @@ extension CLI { guard var tile else { throw CLIError("Failed to create a tile [\(x),\(y)]@\(z)") } if options.verbose { - print("Import into \(tile.origin) tile '\(outputUrl.lastPathComponent)' [\(x),\(y)]@\(z)") - print("Property name: \(propertyName)") + print("Import into \(tile.origin == .none ? "new" : tile.origin.rawValue) tile '\(outputUrl.lastPathComponent)' [\(x),\(y)]@\(z)") + print("Layer property name: \(propertyName)") if disableInputLayerProperty { print(" - disable input layer property") } @@ -136,6 +136,12 @@ extension CLI { if let layerName { print("Fallback layer name: \(layerName)") } + + if !disableInputLayerProperty, + let layerAllowlist + { + print("Layers: '\(layerAllowlist.joined(separator: ","))'") + } } for path in other { @@ -157,7 +163,7 @@ extension CLI { throw CLIError("Failed to parse the GeoJSON at '\(path)'") } - print("- \(otherUrl.lastPathComponent)") + print("- \(otherUrl.lastPathComponent) (geojson)") if !disableInputLayerProperty, let layerAllowlist @@ -201,18 +207,18 @@ extension CLI { .no } - if options.verbose { - print("Output options:") - print(" - Buffer size: \(bufferSize)") - print(" - Compression: \(compression)") - print(" - Simplification: \(simplifyFeatures)") - } - let exportOptions: VectorTile.ExportOptions = .init( bufferSize: bufferSize, compression: compression, simplifyFeatures: simplifyFeatures) + if options.verbose { + print("Output options:") + print(" - Buffer size: \(exportOptions.bufferSize)") + print(" - Compression: \(exportOptions.compression)") + print(" - Simplification: \(exportOptions.simplifyFeatures)") + } + tile.write( to: outputUrl, options: exportOptions) diff --git a/Sources/MVTCLI/Merge.swift b/Sources/MVTCLI/Merge.swift index 7362edc..0505b15 100644 --- a/Sources/MVTCLI/Merge.swift +++ b/Sources/MVTCLI/Merge.swift @@ -191,8 +191,7 @@ extension CLI { print("Dumping the merged tile to the console") } - print("Property name: \(propertyName)") - + print("Layer property name: \(propertyName)") if disableInputLayerProperty { print(" - disable input layer property") } @@ -238,16 +237,14 @@ extension CLI { x: x, y: y, z: z, - layerWhitelist: layerAllowlist, - logger: options.verbose ? CLI.logger : nil) + layerWhitelist: layerAllowlist) { otherTile = other } else if let other = VectorTile( contentsOfGeoJson: otherUrl, layerProperty: disableInputLayerProperty ? nil : propertyName, - layerWhitelist: disableInputLayerProperty ? nil : layerAllowlist, - logger: options.verbose ? CLI.logger : nil) + layerWhitelist: disableInputLayerProperty ? nil : layerAllowlist) { otherTile = other } @@ -270,58 +267,50 @@ extension CLI { // Export - let bufferSize: VectorTile.ExportOptions.BufferSizeOptions = if let bufferPixels, bufferPixels > 0 { - .pixel(bufferPixels) + var exportOptions = VectorTile.ExportOptions() + + if let bufferPixels, bufferPixels > 0 { + exportOptions.bufferSize = .pixel(bufferPixels) } else if let bufferExtents, bufferExtents > 0 { - .extent(bufferExtents) + exportOptions.bufferSize = .extent(bufferExtents) } else if outputFormatToUse == .geojson { - .extent(0) + exportOptions.bufferSize = .extent(0) } else { - .extent(512) + exportOptions.bufferSize = .extent(512) } - var compression: VectorTile.ExportOptions.CompressionOptions = .no if outputUrl != nil { // don't gzip output to the console if let compressionLevel { if compressionLevel > 0 { - compression = .level(max(0, min(9, compressionLevel))) + exportOptions.compression = .level(max(0, min(9, compressionLevel))) } } else if outputFormatToUse == .mvt { - compression = .level(9) + exportOptions.compression = .level(9) } } - let simplifyFeatures: VectorTile.ExportOptions.SimplifyFeaturesOptions = if let simplifyMeters, simplifyMeters > 0 { - .meters(Double(simplifyMeters)) + if let simplifyMeters, simplifyMeters > 0 { + exportOptions.simplifyFeatures = .meters(Double(simplifyMeters)) } else if let simplifyExtents, simplifyExtents > 0 { - .extent(simplifyExtents) - } - else { - .no + exportOptions.simplifyFeatures = .extent(simplifyExtents) } if options.verbose { print("Output options:") - print(" - File format: \(outputFormatToUse)") - print(" - Buffer size: \(bufferSize)") - print(" - Compression: \(compression)") - print(" - Simplification: \(simplifyFeatures)") - if outputFormatToUse == .geojson || outputUrl == nil { print(" - Pretty print: \(prettyPrint)") } + print(" - File format: \(outputFormatToUse)") + print(" - Buffer size: \(exportOptions.bufferSize)") + print(" - Compression: \(exportOptions.compression)") + print(" - Simplification: \(exportOptions.simplifyFeatures)") } - let exportOptions: VectorTile.ExportOptions = .init( - bufferSize: bufferSize, - compression: compression, - simplifyFeatures: simplifyFeatures) - if let outputUrl { if outputFormatToUse == .geojson { if let data = tile.toGeoJson( diff --git a/Sources/MVTCLI/Query.swift b/Sources/MVTCLI/Query.swift index 54206f9..d0baa56 100644 --- a/Sources/MVTCLI/Query.swift +++ b/Sources/MVTCLI/Query.swift @@ -1,9 +1,10 @@ import ArgumentParser #if !os(Linux) -import CoreLocation + import CoreLocation #endif import Foundation import GISTools +import Gzip import MVTTools extension CLI { @@ -19,6 +20,16 @@ extension CLI { completion: .file(extensions: ["geojson", "json"])) var outputFile: String? + @Option( + name: [.customLong("oC", withSingleDash: true), .long], + help: "Output file compression level, between 0=none to 9=best. (default: none)") + var compressionLevel: Int? + + @Option( + name: [.customLong("oSm", withSingleDash: true), .long], + help: "Simplify output features using meters.") + var simplifyMeters: Int? + @Flag( name: .shortAndLong, help: "Overwrite existing files.") @@ -37,12 +48,12 @@ extension CLI { @Flag( name: [.customLong("Di", withSingleDash: true), .long], help: "Don't parse the layer name (option 'property-name') from Feature properties in the input GeoJSONs. Might speed up GeoJSON parsing considerably.") - var disableInputLayerProperty: Bool = false + var disableInputLayerProperty = false @Flag( name: [.customLong("Do", withSingleDash: true), .long], help: "Don't add the layer name (option 'property-name') as a Feature property in the output GeoJSONs.") - var disableOutputLayerProperty: Bool = false + var disableOutputLayerProperty = false @Flag( name: .shortAndLong, @@ -120,8 +131,8 @@ extension CLI { if options.verbose { print("Searching in tile '\(url.lastPathComponent)' [\(tile.x),\(tile.y)]@\(tile.z)") - print("Property name: \(propertyName)") + print("Layer property name: \(propertyName)") if disableInputLayerProperty { print(" - disable input layer property") } @@ -137,7 +148,7 @@ extension CLI { if tile.origin == .mvt || !disableInputLayerProperty, - let layerAllowlist + let layerAllowlist { print("Layers: '\(layerAllowlist.joined(separator: ","))'") } @@ -166,10 +177,44 @@ extension CLI { in: tile) } + if options.verbose { + print("Output options:") + print(" - Pretty print: \(prettyPrint)") + } + + var exportCompressionLevel: VectorTile.ExportOptions.CompressionOptions = .no + if outputFile != nil { // don't gzip output to the console + if let compressionLevel, compressionLevel > 0 { + exportCompressionLevel = .level(max(0, min(9, compressionLevel))) + } + + if options.verbose { + print(" - Compression: \(exportCompressionLevel)") + } + } + + if let simplifyMeters, simplifyMeters > 0 { + if options.verbose { + print(" - Simplification: \(simplifyMeters > 0 ? ".meters(\(simplifyMeters)" : ".no")") + } + result = result?.simplified(tolerance: Double(simplifyMeters)) + } + if let result { if let outputFile { let outputUrl = URL(fileURLWithPath: outputFile) - try result.asJsonData(prettyPrinted: prettyPrint)?.write(to: outputUrl, options: .atomic) + var serializedData = result.asJsonData(prettyPrinted: prettyPrint) + + if exportCompressionLevel != .no { + var value = 6 // default + if case let .level(compressionLevel) = exportCompressionLevel { + value = max(0, min(9, compressionLevel)) + } + let level = CompressionLevel(rawValue: Int32(value)) + serializedData = (try? serializedData?.gzipped(level: level)) ?? serializedData + } + + try serializedData?.write(to: outputUrl, options: .atomic) } else if let resultGeoJson = result.asJsonString(prettyPrinted: prettyPrint) { print(resultGeoJson, terminator: "") diff --git a/Sources/MVTCLI/Version.swift b/Sources/MVTCLI/Version.swift index 9065df7..299b6ac 100644 --- a/Sources/MVTCLI/Version.swift +++ b/Sources/MVTCLI/Version.swift @@ -1,4 +1,4 @@ // This file will be overwritten by the // homebrew Github action. // DO NOT change this file. -public let cliVersion = "v1.7.0" +public let cliVersion = "v1.8.1" diff --git a/Sources/MVTTools/Coders/MVTEncoder.swift b/Sources/MVTTools/Coders/MVTEncoder.swift index d76d139..132b0cc 100644 --- a/Sources/MVTTools/Coders/MVTEncoder.swift +++ b/Sources/MVTTools/Coders/MVTEncoder.swift @@ -38,6 +38,8 @@ enum MVTEncoder { var bufferSize = 0 switch options.bufferSize { + case .no: + bufferSize = 0 case let .extent(extent): bufferSize = extent case let .pixel(pixel): diff --git a/Sources/MVTTools/ExportOptions.swift b/Sources/MVTTools/ExportOptions.swift index d22432a..eed7d33 100644 --- a/Sources/MVTTools/ExportOptions.swift +++ b/Sources/MVTTools/ExportOptions.swift @@ -14,6 +14,8 @@ extension VectorTile { /// Options for the buffer around tiles. public enum BufferSizeOptions { + /// No buffering. + case no /// Use the same dimension as ``ExportOptions.extent``. case extent(Int) /// Use pixels (see ``ExportOptions.tileSize``). @@ -46,8 +48,8 @@ extension VectorTile { /// The tile size in pixels. Always 256. public static let tileSize = 256 - /// The buffer around the tile, either in pixels (see ``tileSize``) or in the same dimension as ``extent`` (default: **0**). - public var bufferSize: BufferSizeOptions = .extent(0) + /// The buffer around the tile, either in pixels (see ``tileSize``) or in the same dimension as ``extent`` (default: **no**). + public var bufferSize: BufferSizeOptions = .no /// Whether to enable compression or not (default: **no**) /// @@ -58,7 +60,7 @@ extension VectorTile { public var simplifyFeatures: SimplifyFeaturesOptions = .no public init( - bufferSize: BufferSizeOptions = .extent(0), + bufferSize: BufferSizeOptions = .no, compression: CompressionOptions = .no, simplifyFeatures: SimplifyFeaturesOptions = .no) { diff --git a/Sources/MVTTools/GeoJson.swift b/Sources/MVTTools/GeoJson.swift index 9182659..5b499a5 100644 --- a/Sources/MVTTools/GeoJson.swift +++ b/Sources/MVTTools/GeoJson.swift @@ -23,8 +23,9 @@ extension VectorTile { if let options { var bufferSize = 0 - switch options.bufferSize { + case .no: + bufferSize = 0 case let .extent(extent): bufferSize = extent case let .pixel(pixel):