From 47c805c30cab40289870807f47e108458510a89b Mon Sep 17 00:00:00 2001 From: Thomas Rasch Date: Mon, 3 Jun 2024 17:17:06 +0200 Subject: [PATCH] Code reorganization --- README.md | 2 +- Sources/MVTCLI/CLI.swift | 2 +- Sources/MVTCLI/Import.swift | 11 +- Sources/MVTCLI/Merge.swift | 11 +- .../MVTDecoder.swift} | 28 +- .../MVTEncoder.swift} | 34 +-- Sources/MVTTools/ExportOptions.swift | 72 +++++ ...{VectorTileGeoJson.swift => GeoJson.swift} | 0 .../{VectorTileInfo.swift => Info.swift} | 4 +- .../{VectorTileMerge.swift => Merge.swift} | 0 .../{VectorTileQuery.swift => Query.swift} | 0 Sources/MVTTools/VectorTile.swift | 16 +- .../MVTTools/VectorTileExportOptions.swift | 65 ----- Tests/MVTToolsTests/DecoderTests.swift | 240 ---------------- Tests/MVTToolsTests/EncoderTests.swift | 270 ------------------ 15 files changed, 127 insertions(+), 628 deletions(-) rename Sources/MVTTools/{VectorTileDecoder.swift => Coders/MVTDecoder.swift} (94%) rename Sources/MVTTools/{VectorTileEncoder.swift => Coders/MVTEncoder.swift} (94%) create mode 100644 Sources/MVTTools/ExportOptions.swift rename Sources/MVTTools/{VectorTileGeoJson.swift => GeoJson.swift} (100%) rename Sources/MVTTools/{VectorTileInfo.swift => Info.swift} (93%) rename Sources/MVTTools/{VectorTileMerge.swift => Merge.swift} (100%) rename Sources/MVTTools/{VectorTileQuery.swift => Query.swift} (100%) delete mode 100644 Sources/MVTTools/VectorTileExportOptions.swift delete mode 100644 Tests/MVTToolsTests/DecoderTests.swift delete mode 100644 Tests/MVTToolsTests/EncoderTests.swift diff --git a/README.md b/README.md index cd4bedf..fa8b406 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ feature.properties = [ tile.setFeatures([feature], for: "test") -// Also have a look at ``VectorTileExportOptions`` +// Also have a look at ``VectorTile.ExportOptions`` let tileData = tile.data() ... ``` diff --git a/Sources/MVTCLI/CLI.swift b/Sources/MVTCLI/CLI.swift index 976680d..04898be 100644 --- a/Sources/MVTCLI/CLI.swift +++ b/Sources/MVTCLI/CLI.swift @@ -11,7 +11,7 @@ struct CLI: AsyncParsableCommand { static var configuration = CommandConfiguration( commandName: "mvt", abstract: "A utility for inspecting and working with vector tiles.", - version: "1.4.0", + version: "1.5.0", subcommands: [Dump.self, Info.self, Merge.self, Query.self, Export.self, Import.self], defaultSubcommand: Dump.self) diff --git a/Sources/MVTCLI/Import.swift b/Sources/MVTCLI/Import.swift index 03708d6..2e043a7 100644 --- a/Sources/MVTCLI/Import.swift +++ b/Sources/MVTCLI/Import.swift @@ -54,11 +54,12 @@ extension CLI { tile.addGeoJson(geoJson: otherGeoJSON, layerName: layer) } - let exportOptions = VectorTileExportOptions( - bufferSize: .extent(512), - compression: .level(9), - simplifyFeatures: .no) - tile.write(to: url, options: exportOptions) + tile.write( + to: url, + options: .init( + bufferSize: .extent(512), + compression: .level(9), + simplifyFeatures: .no)) } } diff --git a/Sources/MVTCLI/Merge.swift b/Sources/MVTCLI/Merge.swift index 0081d41..0cca907 100644 --- a/Sources/MVTCLI/Merge.swift +++ b/Sources/MVTCLI/Merge.swift @@ -63,11 +63,12 @@ extension CLI { tile.merge(otherTile) } - let exportOptions = VectorTileExportOptions( - bufferSize: .extent(512), - compression: .level(9), - simplifyFeatures: .no) - tile.write(to: outputUrl, options: exportOptions) + tile.write( + to: outputUrl, + options: .init( + bufferSize: .extent(512), + compression: .level(9), + simplifyFeatures: .no)) } } diff --git a/Sources/MVTTools/VectorTileDecoder.swift b/Sources/MVTTools/Coders/MVTDecoder.swift similarity index 94% rename from Sources/MVTTools/VectorTileDecoder.swift rename to Sources/MVTTools/Coders/MVTDecoder.swift index f6c6b1d..559c7e6 100644 --- a/Sources/MVTTools/VectorTileDecoder.swift +++ b/Sources/MVTTools/Coders/MVTDecoder.swift @@ -8,10 +8,10 @@ import Logging // MARK: Reading vector tiles -extension VectorTile { +enum MVTDecoder { - static func vectorTile(from data: Data) -> VectorTile_Tile? { - var data = data + static func vectorTile(from mvtData: Data) -> VectorTile_Tile? { + var data = mvtData if data.isGzipped { data = (try? data.gunzipped()) ?? data } @@ -19,26 +19,26 @@ extension VectorTile { return try? VectorTile_Tile(serializedData: data) } - static func loadTileFrom( - data: Data, + static func layers( + from mvtData: Data, x: Int, y: Int, z: Int, projection: Projection = .epsg4326, layerWhitelist: Set?, logger: Logger?) - -> [String: LayerContainer]? + -> [String: VectorTile.LayerContainer]? { - if data.isGzipped { + if mvtData.isGzipped { (logger ?? VectorTile.logger)?.info("\(z)/\(x)/\(y): Input data is gzipped") } - guard let tile = vectorTile(from: data) else { + guard let tile = vectorTile(from: mvtData) else { (logger ?? VectorTile.logger)?.warning("\(z)/\(x)/\(y): Failed to create a vector tile from data") return nil } - var layers: [String: LayerContainer] = [:] + var layers: [String: VectorTile.LayerContainer] = [:] var lastExtent = 0 var projectionFunction: ((_ x: Int, _ y: Int) -> Coordinate3D) = passThroughFromTile @@ -73,7 +73,7 @@ extension VectorTile { layerBoundingBox = boundingBoxes.reduce(boundingBoxes[0], +) } - layers[name] = LayerContainer( + layers[name] = VectorTile.LayerContainer( features: layerFeatures, boundingBox: layerBoundingBox) @@ -269,7 +269,7 @@ extension VectorTile { commandCount = Int(commandInteger >> 3) // ClosePath has no parameter - if commandId == VectorTile.commandIdClosePath { + if commandId == MVTDecoder.commandIdClosePath { guard featureType != .point, commandCount == 1, coordinates.count > 1 @@ -291,10 +291,10 @@ extension VectorTile { let dx: UInt32 = geometryIntegers[index] let dy: UInt32 = geometryIntegers[index + 1] - x += VectorTile.zigZagDecode(Int(dx)) - y += VectorTile.zigZagDecode(Int(dy)) + x += MVTDecoder.zigZagDecode(Int(dx)) + y += MVTDecoder.zigZagDecode(Int(dy)) - if commandId == VectorTile.commandIdMoveTo, + if commandId == MVTDecoder.commandIdMoveTo, !coordinates.isEmpty { result.append(coordinates) diff --git a/Sources/MVTTools/VectorTileEncoder.swift b/Sources/MVTTools/Coders/MVTEncoder.swift similarity index 94% rename from Sources/MVTTools/VectorTileEncoder.swift rename to Sources/MVTTools/Coders/MVTEncoder.swift index 31188e6..087baca 100644 --- a/Sources/MVTTools/VectorTileEncoder.swift +++ b/Sources/MVTTools/Coders/MVTEncoder.swift @@ -8,15 +8,15 @@ import Gzip // MARK: Writing vector tiles -extension VectorTile { +enum MVTEncoder { - static func tileDataFor( - layers: [String: LayerContainer], + static func mvtDataFor( + layers: [String: VectorTile.LayerContainer], x: Int, y: Int, z: Int, projection: Projection = .epsg4326, - options: VectorTileExportOptions) + options: VectorTile.ExportOptions) -> Data? { var tile = VectorTile_Tile() @@ -303,7 +303,7 @@ extension VectorTile { // Encode points if featureType == .point { - commandId = VectorTile.commandIdMoveTo + commandId = MVTEncoder.commandIdMoveTo commandCount = UInt32(multiCoordinates.count) commandInteger = (commandId & 0x7) | (commandCount << 3) geometryIntegers.append(commandInteger) @@ -312,8 +312,8 @@ extension VectorTile { guard let moveToCoordinate = coordinates.first else { continue } let (x, y) = projectionFunction(moveToCoordinate) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(x) - dx))) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(y) - dy))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(x) - dx))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(y) - dy))) dx = x dy = y } @@ -329,50 +329,50 @@ extension VectorTile { let moveToCoordinate = coordinates.first else { continue } - commandId = VectorTile.commandIdMoveTo + commandId = MVTEncoder.commandIdMoveTo commandCount = 1 commandInteger = (commandId & 0x7) | (commandCount << 3) geometryIntegers.append(commandInteger) let (x, y) = projectionFunction(moveToCoordinate) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(x) - dx))) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(y) - dy))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(x) - dx))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(y) - dy))) dx = x dy = y if featureType == .linestring || coordinates.get(at: 0) != coordinates.get(at: -1) { - commandId = VectorTile.commandIdLineTo + commandId = MVTEncoder.commandIdLineTo commandCount = UInt32(coordinates.count - 1) commandInteger = (commandId & 0x7) | (commandCount << 3) geometryIntegers.append(commandInteger) for coordinate in coordinates[1...] { let (x, y) = projectionFunction(coordinate) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(x) - dx))) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(y) - dy))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(x) - dx))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(y) - dy))) dx = x dy = y } } else { - commandId = VectorTile.commandIdLineTo + commandId = MVTEncoder.commandIdLineTo commandCount = UInt32(coordinates.count - 2) commandInteger = (commandId & 0x7) | (commandCount << 3) geometryIntegers.append(commandInteger) for coordinate in coordinates[1 ..< coordinates.count - 1] { let (x, y) = projectionFunction(coordinate) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(x) - dx))) - geometryIntegers.append(UInt32(VectorTile.zigZagEncode(Int(y) - dy))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(x) - dx))) + geometryIntegers.append(UInt32(MVTEncoder.zigZagEncode(Int(y) - dy))) dx = x dy = y } } if featureType == .polygon { - commandId = VectorTile.commandIdClosePath + commandId = MVTEncoder.commandIdClosePath commandCount = 1 commandInteger = (commandId & 0x7) | (commandCount << 3) geometryIntegers.append(commandInteger) diff --git a/Sources/MVTTools/ExportOptions.swift b/Sources/MVTTools/ExportOptions.swift new file mode 100644 index 0000000..45735f6 --- /dev/null +++ b/Sources/MVTTools/ExportOptions.swift @@ -0,0 +1,72 @@ +#if !os(Linux) + import CoreLocation +#endif +import Foundation +import GISTools + +@available(*, unavailable, renamed: "VectorTile.ExportOptions", message: "This struct has been moved to the VectorTile namespace") +public struct VectorTileExportOptions {} + +extension VectorTile { + + /// Various export options. + public struct ExportOptions { + + /// Options for the buffer around tiles. + public enum BufferSizeOptions { + /// Use the same dimension as ``ExportOptions.extent``. + case extent(Int) + /// Use pixels (see ``ExportOptions.tileSize``). + case pixel(Int) + } + + /// Gzip options. + public enum CompressionOptions: Equatable { + /// Don't compress the vector tile data. + case no + /// The default compression level (*6*). + case `default` + /// A compression level, between *0* (no compression) and *9* (best compression). + case level(Int) + } + + /// Options for Feature simplification. + public enum SimplifyFeaturesOptions { + /// Don't simplify featutes. + case no + /// Use the same dimension as ``ExportOptions.extent``. + case extent(Int) + /// Use meters. + case meters(CLLocationDistance) + } + + /// The grid width and height of one tile. Always 4096. + public let extent = 4096 + + /// The tile size in pixels. Always 256. + public 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) + + /// Whether to enable compression or not (default: **no**) + /// + /// Uses Gzip. + public var compression: CompressionOptions = .no + + /// Simplify features before encoding them (default: **no**). + public var simplifyFeatures: SimplifyFeaturesOptions = .no + + public init( + bufferSize: BufferSizeOptions = .extent(0), + compression: CompressionOptions = .no, + simplifyFeatures: SimplifyFeaturesOptions = .no) + { + self.bufferSize = bufferSize + self.compression = compression + self.simplifyFeatures = simplifyFeatures + } + + } + +} diff --git a/Sources/MVTTools/VectorTileGeoJson.swift b/Sources/MVTTools/GeoJson.swift similarity index 100% rename from Sources/MVTTools/VectorTileGeoJson.swift rename to Sources/MVTTools/GeoJson.swift diff --git a/Sources/MVTTools/VectorTileInfo.swift b/Sources/MVTTools/Info.swift similarity index 93% rename from Sources/MVTTools/VectorTileInfo.swift rename to Sources/MVTTools/Info.swift index 25ad7ef..d84b494 100644 --- a/Sources/MVTTools/VectorTileInfo.swift +++ b/Sources/MVTTools/Info.swift @@ -10,7 +10,7 @@ extension VectorTile { /// Read a tile from `data` and return its layer names public static func layerNames(from data: Data) -> [String]? { - guard let tile = vectorTile(from: data) else { return nil } + guard let tile = MVTDecoder.vectorTile(from: data) else { return nil } return tile.layers.map { $0.name } } @@ -33,7 +33,7 @@ extension VectorTile { /// Read a tile from `data` and return some information about the tile public static func tileInfo(from data: Data) -> [String: Any]? { - guard let tile = vectorTile(from: data) else { return nil } + guard let tile = MVTDecoder.vectorTile(from: data) else { return nil } var layers: [[String: Any]] = [] diff --git a/Sources/MVTTools/VectorTileMerge.swift b/Sources/MVTTools/Merge.swift similarity index 100% rename from Sources/MVTTools/VectorTileMerge.swift rename to Sources/MVTTools/Merge.swift diff --git a/Sources/MVTTools/VectorTileQuery.swift b/Sources/MVTTools/Query.swift similarity index 100% rename from Sources/MVTTools/VectorTileQuery.swift rename to Sources/MVTTools/Query.swift diff --git a/Sources/MVTTools/VectorTile.swift b/Sources/MVTTools/VectorTile.swift index dbc8f4a..40a13da 100644 --- a/Sources/MVTTools/VectorTile.swift +++ b/Sources/MVTTools/VectorTile.swift @@ -171,8 +171,8 @@ public struct VectorTile: Sendable { nil } - guard let parsedLayers = VectorTile.loadTileFrom( - data: data, + guard let parsedLayers = MVTDecoder.layers( + from: data, x: x, y: y, z: z, @@ -260,8 +260,8 @@ public struct VectorTile: Sendable { return nil } - guard let parsedLayers = VectorTile.loadTileFrom( - data: data, + guard let parsedLayers = MVTDecoder.layers( + from: data, x: x, y: y, z: z, @@ -315,21 +315,21 @@ public struct VectorTile: Sendable { extension VectorTile { /// Returns the tile's content as MVT data - public func data(options: VectorTileExportOptions? = nil) -> Data? { - VectorTile.tileDataFor( + public func data(options: ExportOptions? = nil) -> Data? { + MVTEncoder.mvtDataFor( layers: layers, x: x, y: y, z: z, projection: projection, - options: options ?? VectorTileExportOptions()) + options: options ?? ExportOptions()) } /// Writes the tile's content to `url` in MVT format @discardableResult public func write( to url: URL, - options: VectorTileExportOptions? = nil) + options: ExportOptions? = nil) -> Bool { guard let data: Data = data(options: options) else { return false } diff --git a/Sources/MVTTools/VectorTileExportOptions.swift b/Sources/MVTTools/VectorTileExportOptions.swift deleted file mode 100644 index 6db263a..0000000 --- a/Sources/MVTTools/VectorTileExportOptions.swift +++ /dev/null @@ -1,65 +0,0 @@ -#if !os(Linux) - import CoreLocation -#endif -import Foundation -import GISTools - -/// Various export options. -public struct VectorTileExportOptions { - - /// Options for the buffer around tiles. - public enum BufferSizeOptions { - /// Use the same dimension as ``VectorTileExportOptions.extent``. - case extent(Int) - /// Use pixels (see ``VectorTileExportOptions.tileSize``). - case pixel(Int) - } - - /// Gzip options. - public enum CompressionOptions: Equatable { - /// Don't compress the vector tile data. - case no - /// The default compression level (*6*). - case `default` - /// A compression level, between *0* (no compression) and *9* (best compression). - case level(Int) - } - - /// Options for Feature simplification. - public enum SimplifyFeaturesOptions { - /// Don't simplify featutes. - case no - /// Use the same dimension as ``VectorTileExportOptions.extent``. - case extent(Int) - /// Use meters. - case meters(CLLocationDistance) - } - - /// The grid width and height of one tile. Always 4096. - public let extent = 4096 - - /// The tile size in pixels. Always 256. - public 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) - - /// Whether to enable compression or not (default: **no**) - /// - /// Uses Gzip. - public var compression: CompressionOptions = .no - - /// Simplify features before encoding them (default: **no**). - public var simplifyFeatures: SimplifyFeaturesOptions = .no - - public init( - bufferSize: BufferSizeOptions = .extent(0), - compression: CompressionOptions = .no, - simplifyFeatures: SimplifyFeaturesOptions = .no) - { - self.bufferSize = bufferSize - self.compression = compression - self.simplifyFeatures = simplifyFeatures - } - -} diff --git a/Tests/MVTToolsTests/DecoderTests.swift b/Tests/MVTToolsTests/DecoderTests.swift deleted file mode 100644 index 09e8fba..0000000 --- a/Tests/MVTToolsTests/DecoderTests.swift +++ /dev/null @@ -1,240 +0,0 @@ -#if !os(Linux) - import CoreLocation -#endif -import GISTools -import struct GISTools.Polygon -import XCTest - -@testable import MVTTools - -final class DecoderTests: XCTestCase { - - func testFeatureGeometryDecoder() { - // Point - let geometry1: [UInt32] = [9, 50, 34] - let coordinates1 = VectorTile.multiCoordinatesFrom(geometryIntegers: geometry1, ofType: .point, projectionFunction: VectorTile.passThroughFromTile).first?.first - let result1 = Coordinate3D(x: 25.0, y: 17.0, projection: .noSRID) - XCTAssertNotNil(coordinates1, "Failed to parse a POINT") - XCTAssertEqual(coordinates1, result1) - - // MultiPoint - let geometry2: [UInt32] = [17, 10, 14, 3, 9] - let coordinates2 = VectorTile.multiCoordinatesFrom(geometryIntegers: geometry2, ofType: .point, projectionFunction: VectorTile.passThroughFromTile) - let result2 = [ - [Coordinate3D(x: 5.0, y: 7.0, projection: .noSRID)], - [Coordinate3D(x: 3.0, y: 2.0, projection: .noSRID)], - ] - XCTAssertNotNil(coordinates2, "Failed to parse a MULTIPOINT") - XCTAssertEqual(coordinates2, result2) - - // Linestring - let geometry3: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] - let coordinates3 = VectorTile.multiCoordinatesFrom(geometryIntegers: geometry3, ofType: .linestring, projectionFunction: VectorTile.passThroughFromTile) - let result3 = [[ - Coordinate3D(x: 2.0, y: 2.0, projection: .noSRID), - Coordinate3D(x: 2.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 10.0, projection: .noSRID), - ]] - XCTAssertNotNil(coordinates3, "Failed to parse a LINESTRING") - XCTAssertEqual(coordinates3, result3) - - // MultiLinestring - let geometry4: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] - let coordinates4 = VectorTile.multiCoordinatesFrom(geometryIntegers: geometry4, ofType: .linestring, projectionFunction: VectorTile.passThroughFromTile) - let result4 = [[ - Coordinate3D(x: 2.0, y: 2.0, projection: .noSRID), - Coordinate3D(x: 2.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 10.0, projection: .noSRID), - ], [ - Coordinate3D(x: 1.0, y: 1.0, projection: .noSRID), - Coordinate3D(x: 3.0, y: 5.0, projection: .noSRID), - ]] - XCTAssertNotNil(coordinates4, "Failed to parse a MULTILINESTRING") - XCTAssertEqual(coordinates4, result4) - - // Polygon - let geometry5: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] - let coordinates5 = VectorTile.multiCoordinatesFrom(geometryIntegers: geometry5, ofType: .linestring, projectionFunction: VectorTile.passThroughFromTile) - let result5 = [[ - Coordinate3D(x: 3.0, y: 6.0, projection: .noSRID), - Coordinate3D(x: 8.0, y: 12.0, projection: .noSRID), - Coordinate3D(x: 20.0, y: 34.0, projection: .noSRID), - Coordinate3D(x: 3.0, y: 6.0, projection: .noSRID), - ]] - XCTAssertNotNil(coordinates5, "Failed to parse a Polygon") - XCTAssertEqual(coordinates5, result5) - - // MultiPolygon - let geometry6: [UInt32] = [9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18, 0, 0, 18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15] - let coordinates6 = VectorTile.multiCoordinatesFrom(geometryIntegers: geometry6, ofType: .linestring, projectionFunction: VectorTile.passThroughFromTile) - let result6 = [[ - Coordinate3D(x: 0.0, y: 0.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 0.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 0.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 0.0, y: 0.0, projection: .noSRID), - ], [ - Coordinate3D(x: 11.0, y: 11.0, projection: .noSRID), - Coordinate3D(x: 20.0, y: 11.0, projection: .noSRID), - Coordinate3D(x: 20.0, y: 20.0, projection: .noSRID), - Coordinate3D(x: 11.0, y: 20.0, projection: .noSRID), - Coordinate3D(x: 11.0, y: 11.0, projection: .noSRID), - ], [ - Coordinate3D(x: 13.0, y: 13.0, projection: .noSRID), - Coordinate3D(x: 13.0, y: 17.0, projection: .noSRID), - Coordinate3D(x: 17.0, y: 17.0, projection: .noSRID), - Coordinate3D(x: 17.0, y: 13.0, projection: .noSRID), - Coordinate3D(x: 13.0, y: 13.0, projection: .noSRID), - ]] - XCTAssertNotNil(coordinates6, "Failed to parse a MULTIPOLYGON") - XCTAssertEqual(coordinates6, result6) - - let rings: [Ring] = coordinates6.map { Ring($0)! } - XCTAssertTrue(rings[0].isUnprojectedClockwise, "First polygon ring is not oriented clockwise") - XCTAssertTrue(rings[1].isUnprojectedClockwise, "Second polygon ring is not oriented clockwise") - XCTAssertTrue(rings[2].isUnprojectedCounterClockwise, "Third polygon ring is not oriented counter-clockwise") - } - - func testFeatureConversion() { - // Point - let geometry1: [UInt32] = [9, 50, 34] - let feature1 = VectorTile.convertToLayerFeature( - geometryIntegers: geometry1, - ofType: .point, - projectionFunction: VectorTile.passThroughFromTile) - XCTAssertNotNil(feature1, "Failed to parse a POINT") - - let point1: Point? = feature1?.geometry as? Point - let boundingBox1: BoundingBox? = feature1?.boundingBox - XCTAssertNotNil(point1, "Failed to parse a POINT") - XCTAssertNotNil(boundingBox1, "FEATURE(POINT) without bounding box") - - let result1 = Point(Coordinate3D(x: 25.0, y: 17.0, projection: .noSRID)) - XCTAssertEqual(point1, result1) - XCTAssertEqual(boundingBox1, result1.calculateBoundingBox()) - - // MultiPoint - let geometry2: [UInt32] = [17, 10, 14, 3, 9] - let feature2 = VectorTile.convertToLayerFeature( - geometryIntegers: geometry2, - ofType: .point, - projectionFunction: VectorTile.passThroughFromTile) - XCTAssertNotNil(feature2, "Failed to parse a MULTIPOINT") - - let multiPoint2: MultiPoint? = feature2?.geometry as? MultiPoint - let boundingBox2: BoundingBox? = feature2?.boundingBox - XCTAssertNotNil(multiPoint2, "Failed to parse a MULTIPOINT") - XCTAssertNotNil(boundingBox2, "FEATURE(MULTIPOINT) without bounding box") - - let result2 = MultiPoint([ - Coordinate3D(x: 5.0, y: 7.0, projection: .noSRID), - Coordinate3D(x: 3.0, y: 2.0, projection: .noSRID), - ])! - XCTAssertEqual(multiPoint2, result2) - XCTAssertEqual(boundingBox2, result2.calculateBoundingBox()) - - // Linestring - let geometry3: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] - let feature3 = VectorTile.convertToLayerFeature( - geometryIntegers: geometry3, - ofType: .linestring, - projectionFunction: VectorTile.passThroughFromTile) - XCTAssertNotNil(feature3, "Failed to parse a LINESTRING") - - let lineString3: LineString? = feature3?.geometry as? LineString - let boundingBox3: BoundingBox? = feature3?.boundingBox - XCTAssertNotNil(lineString3, "Failed to parse a LINESTRING") - XCTAssertNotNil(boundingBox3, "FEATURE(LINESTRING) without bounding box") - - let result3 = LineString([ - Coordinate3D(x: 2.0, y: 2.0, projection: .noSRID), - Coordinate3D(x: 2.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 10.0, projection: .noSRID), - ])! - XCTAssertEqual(lineString3, result3) - XCTAssertEqual(boundingBox3, result3.calculateBoundingBox()) - - // MultiLinestring - let geometry4: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] - let feature4 = VectorTile.convertToLayerFeature( - geometryIntegers: geometry4, - ofType: .linestring, - projectionFunction: VectorTile.passThroughFromTile) - XCTAssertNotNil(feature4, "Failed to parse a MULTILINESTRING") - - let multiLineString4: MultiLineString? = feature4?.geometry as? MultiLineString - let boundingBox4: BoundingBox? = feature4?.boundingBox - XCTAssertNotNil(multiLineString4, "Failed to parse a MULTILINESTRING") - XCTAssertNotNil(boundingBox4, "FEATURE(MULTILINESTRING) without bounding box") - - let result4 = MultiLineString([[ - Coordinate3D(x: 2.0, y: 2.0, projection: .noSRID), - Coordinate3D(x: 2.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 10.0, projection: .noSRID), - ], [ - Coordinate3D(x: 1.0, y: 1.0, projection: .noSRID), - Coordinate3D(x: 3.0, y: 5.0, projection: .noSRID), - ]])! - XCTAssertEqual(multiLineString4, result4) - XCTAssertEqual(boundingBox4, result4.calculateBoundingBox()) - - // Polygon - let geometry5: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] - let feature5 = VectorTile.convertToLayerFeature( - geometryIntegers: geometry5, - ofType: .polygon, - projectionFunction: VectorTile.passThroughFromTile) - XCTAssertNotNil(feature5, "Failed to parse a POLYGON") - - let polygon5: Polygon? = feature5?.geometry as? Polygon - let boundingBox5: BoundingBox? = feature5?.boundingBox - XCTAssertNotNil(polygon5, "Failed to parse a POLYGON") - XCTAssertNotNil(boundingBox5, "FEATURE(POLYGON) without bounding box") - - let result5: Polygon? = Polygon([[ - Coordinate3D(x: 3.0, y: 6.0, projection: .noSRID), - Coordinate3D(x: 8.0, y: 12.0, projection: .noSRID), - Coordinate3D(x: 20.0, y: 34.0, projection: .noSRID), - Coordinate3D(x: 3.0, y: 6.0, projection: .noSRID), - ]]) - XCTAssertNotNil(result5) - XCTAssertEqual(polygon5, result5) - XCTAssertEqual(boundingBox5, result5?.calculateBoundingBox()) - - // MultiPolygon - let geometry6: [UInt32] = [9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18, 0, 0, 18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15] - let feature6 = VectorTile.convertToLayerFeature( - geometryIntegers: geometry6, - ofType: .polygon, - projectionFunction: VectorTile.passThroughFromTile) - XCTAssertNotNil(feature6, "Failed to parse a MULTIPOLYGON") - - let multiPolygon6: MultiPolygon? = feature6?.geometry as? MultiPolygon - let boundingBox6: BoundingBox? = feature6?.boundingBox - XCTAssertNotNil(multiPolygon6, "Failed to parse a MULTIPOLYGON") - XCTAssertNotNil(boundingBox6, "FEATURE(MULTIPOLYGON) without bounding box") - - let result6 = MultiPolygon([[[ - Coordinate3D(x: 0.0, y: 0.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 0.0, projection: .noSRID), - Coordinate3D(x: 10.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 0.0, y: 10.0, projection: .noSRID), - Coordinate3D(x: 0.0, y: 0.0, projection: .noSRID), - ]], [[ - Coordinate3D(x: 11.0, y: 11.0, projection: .noSRID), - Coordinate3D(x: 20.0, y: 11.0, projection: .noSRID), - Coordinate3D(x: 20.0, y: 20.0, projection: .noSRID), - Coordinate3D(x: 11.0, y: 20.0, projection: .noSRID), - Coordinate3D(x: 11.0, y: 11.0, projection: .noSRID), - ], [ - Coordinate3D(x: 13.0, y: 13.0, projection: .noSRID), - Coordinate3D(x: 13.0, y: 17.0, projection: .noSRID), - Coordinate3D(x: 17.0, y: 17.0, projection: .noSRID), - Coordinate3D(x: 17.0, y: 13.0, projection: .noSRID), - Coordinate3D(x: 13.0, y: 13.0, projection: .noSRID), - ]]])! - XCTAssertEqual(multiPolygon6, result6) - XCTAssertEqual(boundingBox6, result6.calculateBoundingBox()) - } - -} diff --git a/Tests/MVTToolsTests/EncoderTests.swift b/Tests/MVTToolsTests/EncoderTests.swift deleted file mode 100644 index 7e62db2..0000000 --- a/Tests/MVTToolsTests/EncoderTests.swift +++ /dev/null @@ -1,270 +0,0 @@ -#if !os(Linux) - import CoreLocation -#endif -import GISTools -import struct GISTools.Polygon -import XCTest - -@testable import MVTTools - -final class EncoderTests: XCTestCase { - - func testFeatureGeometryEncoder() { - // Point - let point = Coordinate3D(latitude: 17.0, longitude: 25.0) - let pointGeometryIntegers = VectorTile.geometryIntegers(fromMultiCoordinates: [[point]], ofType: .point, projectionFunction: VectorTile.passThroughToTile()) - let pointResult: [UInt32] = [9, 50, 34] - XCTAssertEqual(pointGeometryIntegers, pointResult) - - // MultiPoint - let multiPoint = [ - [Coordinate3D(latitude: 7.0, longitude: 5.0)], - [Coordinate3D(latitude: 2.0, longitude: 3.0)], - ] - let multiPointGeometryIntegers = VectorTile.geometryIntegers(fromMultiCoordinates: multiPoint, ofType: .point, projectionFunction: VectorTile.passThroughToTile()) - let multiPointResult: [UInt32] = [17, 10, 14, 3, 9] - XCTAssertEqual(multiPointGeometryIntegers, multiPointResult) - - // Linestring - let lineString = [[ - Coordinate3D(latitude: 2.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 10.0), - ]] - let lineStringGeometryIntegers = VectorTile.geometryIntegers(fromMultiCoordinates: lineString, ofType: .linestring, projectionFunction: VectorTile.passThroughToTile()) - let lineStringResult: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] - XCTAssertEqual(lineStringGeometryIntegers, lineStringResult) - - // MultiLinestring - let multiLineString = [[ - Coordinate3D(latitude: 2.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 10.0), - ], [ - Coordinate3D(latitude: 1.0, longitude: 1.0), - Coordinate3D(latitude: 5.0, longitude: 3.0), - ]] - let multiLineStringGeometryIntegers = VectorTile.geometryIntegers(fromMultiCoordinates: multiLineString, ofType: .linestring, projectionFunction: VectorTile.passThroughToTile()) - let multiLineStringResult: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] - XCTAssertEqual(multiLineStringGeometryIntegers, multiLineStringResult) - - // Polygon - let polygon = [[ - Coordinate3D(latitude: 6.0, longitude: 3.0), - Coordinate3D(latitude: 12.0, longitude: 8.0), - Coordinate3D(latitude: 34.0, longitude: 20.0), - Coordinate3D(latitude: 6.0, longitude: 3.0), - ]] - let polygonGeometryIntegers = VectorTile.geometryIntegers(fromMultiCoordinates: polygon, ofType: .polygon, projectionFunction: VectorTile.passThroughToTile()) - let polygonResult: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] - XCTAssertEqual(polygonGeometryIntegers, polygonResult) - - // MultiPolygon - let multiPolygon = [[ - Coordinate3D(latitude: 0.0, longitude: 0.0), - Coordinate3D(latitude: 0.0, longitude: 10.0), - Coordinate3D(latitude: 10.0, longitude: 10.0), - Coordinate3D(latitude: 10.0, longitude: 0.0), - Coordinate3D(latitude: 0.0, longitude: 0.0), - ], [ - Coordinate3D(latitude: 11.0, longitude: 11.0), - Coordinate3D(latitude: 11.0, longitude: 20.0), - Coordinate3D(latitude: 20.0, longitude: 20.0), - Coordinate3D(latitude: 20.0, longitude: 11.0), - Coordinate3D(latitude: 11.0, longitude: 11.0), - ], [ - Coordinate3D(latitude: 13.0, longitude: 13.0), - Coordinate3D(latitude: 17.0, longitude: 13.0), - Coordinate3D(latitude: 17.0, longitude: 17.0), - Coordinate3D(latitude: 13.0, longitude: 17.0), - Coordinate3D(latitude: 13.0, longitude: 13.0), - ]] - let multiPolygonGeometryIntegers = VectorTile.geometryIntegers(fromMultiCoordinates: multiPolygon, ofType: .polygon, projectionFunction: VectorTile.passThroughToTile()) - let multiPolygonResult: [UInt32] = [9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18, 0, 0, 18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15] - XCTAssertEqual(multiPolygonGeometryIntegers, multiPolygonResult) - } - - func testFeatureConversion() { - // Point - let point = Feature(Point(Coordinate3D(latitude: 17.0, longitude: 25.0)), id: .int(500)) - let pointFeature = VectorTile.vectorTileFeature(from: point, projectionFunction: VectorTile.passThroughToTile()) - XCTAssertNotNil(pointFeature, "Failed to encode a POINT") - - let pointGeometry: [UInt32] = [9, 50, 34] - XCTAssertEqual(pointFeature?.geometry, pointGeometry) - XCTAssertEqual(pointFeature?.type, VectorTile_Tile.GeomType.point) - XCTAssertEqual(pointFeature?.id, 500) - - // MultiPoint - let multiPoint = Feature(MultiPoint([ - Coordinate3D(latitude: 7.0, longitude: 5.0), - Coordinate3D(latitude: 2.0, longitude: 3.0), - ])!, id: .int(501)) - let multiPointFeature = VectorTile.vectorTileFeature(from: multiPoint, projectionFunction: VectorTile.passThroughToTile()) - XCTAssertNotNil(multiPointFeature, "Failed to encode a MULTIPOINT") - - let multiPointGeometry: [UInt32] = [17, 10, 14, 3, 9] - XCTAssertEqual(multiPointFeature?.geometry, multiPointGeometry) - XCTAssertEqual(multiPointFeature?.type, VectorTile_Tile.GeomType.point) - XCTAssertEqual(multiPointFeature?.id, 501) - - // Linestring - let lineString = Feature(LineString([ - Coordinate3D(latitude: 2.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 10.0), - ])!, id: .int(502)) - let lineStringFeature = VectorTile.vectorTileFeature(from: lineString, projectionFunction: VectorTile.passThroughToTile()) - XCTAssertNotNil(lineStringFeature, "Failed to encode a LINESTRING") - - let lineStringGeometry: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] - XCTAssertEqual(lineStringFeature?.geometry, lineStringGeometry) - XCTAssertEqual(lineStringFeature?.type, VectorTile_Tile.GeomType.linestring) - XCTAssertEqual(lineStringFeature?.id, 502) - - // MultiLinestring - let multiLineString = Feature(MultiLineString([[ - Coordinate3D(latitude: 2.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 2.0), - Coordinate3D(latitude: 10.0, longitude: 10.0), - ], [ - Coordinate3D(latitude: 1.0, longitude: 1.0), - Coordinate3D(latitude: 5.0, longitude: 3.0), - ]])!, id: .int(503)) - let multiLineStringFeature = VectorTile.vectorTileFeature(from: multiLineString, projectionFunction: VectorTile.passThroughToTile()) - XCTAssertNotNil(multiLineStringFeature, "Failed to encode a MULTILINESTRING") - - let multiLineStringGeometry: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] - XCTAssertEqual(multiLineStringFeature?.geometry, multiLineStringGeometry) - XCTAssertEqual(multiLineStringFeature?.type, VectorTile_Tile.GeomType.linestring) - XCTAssertEqual(multiLineStringFeature?.id, 503) - - // Polygon - let polygon = Feature(Polygon([[ - Coordinate3D(latitude: 6.0, longitude: 3.0), - Coordinate3D(latitude: 12.0, longitude: 8.0), - Coordinate3D(latitude: 34.0, longitude: 20.0), - Coordinate3D(latitude: 6.0, longitude: 3.0), - ]])!, id: .int(504)) - let polygonFeature = VectorTile.vectorTileFeature(from: polygon, projectionFunction: VectorTile.passThroughToTile()) - XCTAssertNotNil(polygonFeature, "Failed to encode a POLYGON") - - let polygonGeometry: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] - XCTAssertEqual(polygonFeature?.geometry, polygonGeometry) - XCTAssertEqual(polygonFeature?.type, VectorTile_Tile.GeomType.polygon) - XCTAssertEqual(polygonFeature?.id, 504) - - // MultiPolygon - let multiPolygon = Feature(MultiPolygon([[[ - Coordinate3D(latitude: 0.0, longitude: 0.0), - Coordinate3D(latitude: 0.0, longitude: 10.0), - Coordinate3D(latitude: 10.0, longitude: 10.0), - Coordinate3D(latitude: 10.0, longitude: 0.0), - Coordinate3D(latitude: 0.0, longitude: 0.0), - ]], [[ - Coordinate3D(latitude: 11.0, longitude: 11.0), - Coordinate3D(latitude: 11.0, longitude: 20.0), - Coordinate3D(latitude: 20.0, longitude: 20.0), - Coordinate3D(latitude: 20.0, longitude: 11.0), - Coordinate3D(latitude: 11.0, longitude: 11.0), - ], [ - Coordinate3D(latitude: 13.0, longitude: 13.0), - Coordinate3D(latitude: 17.0, longitude: 13.0), - Coordinate3D(latitude: 17.0, longitude: 17.0), - Coordinate3D(latitude: 13.0, longitude: 17.0), - Coordinate3D(latitude: 13.0, longitude: 13.0), - ]]])!, id: .int(505)) - let multiPolygonFeature = VectorTile.vectorTileFeature(from: multiPolygon, projectionFunction: VectorTile.passThroughToTile()) - XCTAssertNotNil(polygonFeature, "Failed to encode a MULTIPOLYGON") - - let multiPolygonGeometry: [UInt32] = [9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18, 0, 0, 18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15] - XCTAssertEqual(multiPolygonFeature?.geometry, multiPolygonGeometry) - XCTAssertEqual(multiPolygonFeature?.type, VectorTile_Tile.GeomType.polygon) - XCTAssertEqual(multiPolygonFeature?.id, 505) - } - - func testEncodeDecode() { - var tile = VectorTile(x: 0, y: 0, z: 0, projection: .epsg4326)! - let point = Feature(Point(Coordinate3D(latitude: 25.0, longitude: 25.0)), id: .int(600)) - tile.addGeoJson(geoJson: point, layerName: "test") - - let features = tile.features(for: "test")! - XCTAssertEqual(features.count, 1) - XCTAssertEqual(features[0].geometry as! Point, point.geometry as! Point) - XCTAssertEqual(features[0].id, .int(600)) - - let tileData = tile.data()! - let decodedTile = VectorTile(data: tileData, x: 0, y: 0, z: 0)! - - let decodedTileFeatures = decodedTile.features(for: "test")! - XCTAssertEqual(decodedTileFeatures.count, 1) - XCTAssertEqual((decodedTileFeatures[0].geometry as! Point).coordinate.latitude, 25, accuracy: 0.1) - XCTAssertEqual((decodedTileFeatures[0].geometry as! Point).coordinate.longitude, 25, accuracy: 0.1) - XCTAssertEqual(decodedTileFeatures[0].id, .int(600)) - } - - func testCompressOption() { - let tileName = "14_8716_8015.vector.mvt" - let mvt = TestData.dataFromFile(name: tileName) - XCTAssertFalse(mvt.isEmpty) - - guard let tile = VectorTile(data: mvt, x: 8716, y: 8015, z: 14) else { - XCTAssert(false, "Unable to parse the vector tile \(tileName)") - return - } - guard let compressed = tile.data(options: VectorTileExportOptions(compression: .default)) else { - XCTAssert(false, "Unable to get compressed tile data") - return - } - - XCTAssertTrue(compressed.isGzipped) - XCTAssertLessThan(compressed.count, mvt.count, "Compressed tile should be smaller") - } - - func testBufferSizeOption() { - let tileName = "14_8716_8015.vector.mvt" - let mvt = TestData.dataFromFile(name: tileName) - XCTAssertFalse(mvt.isEmpty) - - guard let tile = VectorTile(data: mvt, x: 8716, y: 8015, z: 14, layerWhitelist: ["building_label"]) else { - XCTAssert(false, "Unable to parse the vector tile \(tileName)") - return - } - - let bufferedTileData = tile.data(options: VectorTileExportOptions(bufferSize: .extent(0)))! - let bufferedTile = VectorTile(data: bufferedTileData, x: 8716, y: 8015, z: 14)! - - let features: [Point] = bufferedTile.features(for: "building_label")!.compactMap({ $0.geometry as? Point }) - let bounds = MapTile(x: 8716, y: 8015, z: 14).boundingBox(projection: .epsg4326) - - XCTAssertGreaterThan(features.count, 0) - XCTAssertTrue(features.allSatisfy({ bounds.contains($0.coordinate) })) - } - - func testSimplifyOption() { - let tileName = "14_8716_8015.vector.mvt" - let mvt = TestData.dataFromFile(name: tileName) - XCTAssertFalse(mvt.isEmpty) - - guard let tile = VectorTile(data: mvt, x: 8716, y: 8015, z: 14, layerWhitelist: ["road"]) else { - XCTAssert(false, "Unable to parse the vector tile \(tileName)") - return - } - - let simplifiedTileData = tile.data(options: VectorTileExportOptions(bufferSize: .extent(4096), simplifyFeatures: .extent(1024)))! - let simplifiedTile = VectorTile(data: simplifiedTileData, x: 8716, y: 8015, z: 14)! - - XCTAssertEqual(tile.features(for: "road")!.count, simplifiedTile.features(for: "road")!.count) - -// print(simplifiedTile.toGeoJson(prettyPrinted: true)!.utf8EncodedString() ?? "") - } - -} - -extension Data { - - private func utf8EncodedString() -> String? { - String(data: self, encoding: .utf8) - } - -}