diff --git a/Package.resolved b/Package.resolved index 2d51927..3007947 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "758940d2c868e49bb2655d58674305ea1d9a82c2c785046549ae4ecb7102317a", + "originHash" : "3af0eba2ceae33bf7dae275adeace0982e97d32aa2dae0496bb5c3052cbf0fb2", "pins" : [ { "identity" : "gis-tools", "kind" : "remoteSourceControl", "location" : "https://github.com/Outdooractive/gis-tools", "state" : { - "revision" : "f6259f510548fe7879d3eea51566b8dc7ecda0af", - "version" : "1.13.0" + "revision" : "f7a4d2aa2acf771b79a91150f359bcd84de9ceac", + "version" : "1.13.2" } }, { diff --git a/Package.swift b/Package.swift index 8f2d9cd..e2417fc 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "mvt-tools", platforms: [ .iOS(.v15), - .macOS(.v13), + .macOS(.v14), .tvOS(.v15), .watchOS(.v8), ], @@ -19,9 +19,9 @@ let package = Package( targets: ["MVTTools"]), ], dependencies: [ - .package(url: "https://github.com/Outdooractive/gis-tools", from: "1.13.0"), + .package(url: "https://github.com/Outdooractive/gis-tools", from: "1.13.2"), .package(url: "https://github.com/1024jp/GzipSwift.git", from: "5.2.0"), - .package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"), .package(url: "https://github.com/apple/swift-log.git", from: "1.6.4"), .package(url: "https://github.com/apple/swift-protobuf", from: "1.30.0"), ], diff --git a/Sources/MVTTools/Merge.swift b/Sources/MVTTools/Merge.swift index 6365d8b..3ad76c0 100644 --- a/Sources/MVTTools/Merge.swift +++ b/Sources/MVTTools/Merge.swift @@ -29,7 +29,8 @@ extension VectorTile { } for layerName in other.layerNames { - guard let features = other.features(for: layerName) else { continue } + let features = other.features(for: layerName) + guard features.isNotEmpty else { continue } appendFeatures(features, to: layerName) } diff --git a/Sources/MVTTools/VectorTile.swift b/Sources/MVTTools/VectorTile.swift index 8ea3826..5af5276 100644 --- a/Sources/MVTTools/VectorTile.swift +++ b/Sources/MVTTools/VectorTile.swift @@ -423,8 +423,8 @@ extension VectorTile { extension VectorTile { /// Returns an array of GeoJson Features from the given layer - public func features(for layerName: String) -> [Feature]? { - layers[layerName]?.features + public func features(for layerName: String) -> [Feature] { + layers[layerName]?.features ?? [] } /// Replace or add a layer with `features` diff --git a/Tests/MVTToolsTests/ArrayExtensionsTests.swift b/Tests/MVTToolsTests/ArrayExtensionsTests.swift deleted file mode 100644 index e3d5b3c..0000000 --- a/Tests/MVTToolsTests/ArrayExtensionsTests.swift +++ /dev/null @@ -1,53 +0,0 @@ -import XCTest - -@testable import MVTTools - -final class ArrayExtensionsTests: XCTestCase { - - func testPairs() { - let even: [Int] = [1, 2, 3, 4, 5, 6] - let uneven: [Int] = [1, 2, 3, 4, 5] - - let evenPairs = even.pairs() - let unevenPairs = uneven.pairs() - - XCTAssertEqual(evenPairs.count, 3) - XCTAssertEqual(unevenPairs.count, 2) - - XCTAssertEqual(evenPairs[0].first, 1) - XCTAssertEqual(evenPairs[0].second, 2) - XCTAssertEqual(evenPairs[1].first, 3) - XCTAssertEqual(evenPairs[1].second, 4) - XCTAssertEqual(evenPairs[2].first, 5) - XCTAssertEqual(evenPairs[2].second, 6) - - XCTAssertEqual(unevenPairs[0].first, 1) - XCTAssertEqual(unevenPairs[0].second, 2) - XCTAssertEqual(unevenPairs[1].first, 3) - XCTAssertEqual(unevenPairs[1].second, 4) - } - - func testSmallPairs() { - let empty: [Int] = [] - let small = [1] - - let emptyPairs = empty.pairs() - let smallPairs = small.pairs() - - XCTAssertEqual(emptyPairs.count, 0) - XCTAssertEqual(smallPairs.count, 0) - } - - func testGet() { - let array = [0, 1, 2, 3, 4, 5, 6] - - XCTAssertEqual(array.get(at: 0), 0) - XCTAssertEqual(array.get(at: 4), 4) - XCTAssertEqual(array.get(at: -1), 6) - XCTAssertEqual(array.get(at: -5), 2) - - XCTAssertNil(array.get(at: 7)) - XCTAssertNil(array.get(at: -8)) - } - -} diff --git a/Tests/MVTToolsTests/MVTDecoderTests.swift b/Tests/MVTToolsTests/Coders/MVTDecoderTests.swift similarity index 54% rename from Tests/MVTToolsTests/MVTDecoderTests.swift rename to Tests/MVTToolsTests/Coders/MVTDecoderTests.swift index bcf6381..49c6f6d 100644 --- a/Tests/MVTToolsTests/MVTDecoderTests.swift +++ b/Tests/MVTToolsTests/Coders/MVTDecoderTests.swift @@ -3,41 +3,54 @@ #endif import GISTools import struct GISTools.Polygon -import XCTest - @testable import MVTTools +import Testing -final class MVTDecoderTests: XCTestCase { +struct MVTDecoderTests { - func testFeatureGeometryDecoder() async throws { + @Test + func featureGeometryDecoder() async throws { // Point let geometry1: [UInt32] = [9, 50, 34] - let coordinates1 = try XCTUnwrap(MVTDecoder.multiCoordinatesFrom(geometryIntegers: geometry1, ofType: .point, projectionFunction: MVTDecoder.passThroughFromTile).first?.first) + let coordinates1 = try #require(MVTDecoder.multiCoordinatesFrom( + geometryIntegers: geometry1, + ofType: .point, + projectionFunction: MVTDecoder.passThroughFromTile + ).first?.first) let result1 = Coordinate3D(x: 25.0, y: 17.0, projection: .noSRID) - XCTAssertEqual(coordinates1, result1) + #expect(coordinates1 == result1) // MultiPoint let geometry2: [UInt32] = [17, 10, 14, 3, 9] - let coordinates2 = try XCTUnwrap(MVTDecoder.multiCoordinatesFrom(geometryIntegers: geometry2, ofType: .point, projectionFunction: MVTDecoder.passThroughFromTile)) + let coordinates2 = MVTDecoder.multiCoordinatesFrom( + geometryIntegers: geometry2, + ofType: .point, + projectionFunction: MVTDecoder.passThroughFromTile) let result2 = [ [Coordinate3D(x: 5.0, y: 7.0, projection: .noSRID)], [Coordinate3D(x: 3.0, y: 2.0, projection: .noSRID)], ] - XCTAssertEqual(coordinates2, result2) + #expect(coordinates2 == result2) // Linestring let geometry3: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] - let coordinates3 = try XCTUnwrap(MVTDecoder.multiCoordinatesFrom(geometryIntegers: geometry3, ofType: .linestring, projectionFunction: MVTDecoder.passThroughFromTile)) + let coordinates3 = MVTDecoder.multiCoordinatesFrom( + geometryIntegers: geometry3, + ofType: .linestring, + projectionFunction: MVTDecoder.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), ]] - XCTAssertEqual(coordinates3, result3) + #expect(coordinates3 == result3) // MultiLinestring let geometry4: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] - let coordinates4 = try XCTUnwrap(MVTDecoder.multiCoordinatesFrom(geometryIntegers: geometry4, ofType: .linestring, projectionFunction: MVTDecoder.passThroughFromTile)) + let coordinates4 = MVTDecoder.multiCoordinatesFrom( + geometryIntegers: geometry4, + ofType: .linestring, + projectionFunction: MVTDecoder.passThroughFromTile) let result4 = [[ Coordinate3D(x: 2.0, y: 2.0, projection: .noSRID), Coordinate3D(x: 2.0, y: 10.0, projection: .noSRID), @@ -46,22 +59,28 @@ final class MVTDecoderTests: XCTestCase { Coordinate3D(x: 1.0, y: 1.0, projection: .noSRID), Coordinate3D(x: 3.0, y: 5.0, projection: .noSRID), ]] - XCTAssertEqual(coordinates4, result4) + #expect(coordinates4 == result4) // Polygon let geometry5: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] - let coordinates5 = try XCTUnwrap(MVTDecoder.multiCoordinatesFrom(geometryIntegers: geometry5, ofType: .linestring, projectionFunction: MVTDecoder.passThroughFromTile)) + let coordinates5 = MVTDecoder.multiCoordinatesFrom( + geometryIntegers: geometry5, + ofType: .linestring, + projectionFunction: MVTDecoder.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), ]] - XCTAssertEqual(coordinates5, result5) + #expect(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 = try XCTUnwrap( MVTDecoder.multiCoordinatesFrom(geometryIntegers: geometry6, ofType: .linestring, projectionFunction: MVTDecoder.passThroughFromTile)) + let coordinates6 = MVTDecoder.multiCoordinatesFrom( + geometryIntegers: geometry6, + ofType: .linestring, + projectionFunction: MVTDecoder.passThroughFromTile) let result6 = [[ Coordinate3D(x: 0.0, y: 0.0, projection: .noSRID), Coordinate3D(x: 10.0, y: 0.0, projection: .noSRID), @@ -81,134 +100,104 @@ final class MVTDecoderTests: XCTestCase { Coordinate3D(x: 17.0, y: 13.0, projection: .noSRID), Coordinate3D(x: 13.0, y: 13.0, projection: .noSRID), ]] - XCTAssertEqual(coordinates6, result6) + #expect(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") + #expect(rings[0].isUnprojectedClockwise, "First polygon ring is not oriented clockwise") + #expect(rings[1].isUnprojectedClockwise, "Second polygon ring is not oriented clockwise") + #expect(rings[2].isUnprojectedCounterClockwise, "Third polygon ring is not oriented counter-clockwise") } - func testFeatureConversion() { + @Test + func featureConversion() async throws { // Point let geometry1: [UInt32] = [9, 50, 34] - let feature1 = MVTDecoder.convertToLayerFeature( + let feature1 = try #require(MVTDecoder.convertToLayerFeature( geometryIntegers: geometry1, ofType: .point, - projectionFunction: MVTDecoder.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") - + projectionFunction: MVTDecoder.passThroughFromTile)) + let point1: Point = try #require(feature1.geometry as? Point) + let boundingBox1: BoundingBox = try #require(feature1.boundingBox) let result1 = Point(Coordinate3D(x: 25.0, y: 17.0, projection: .noSRID)) - XCTAssertEqual(point1, result1) - XCTAssertEqual(boundingBox1, result1.calculateBoundingBox()) + #expect(point1 == result1) + #expect(boundingBox1 == result1.calculateBoundingBox()) // MultiPoint let geometry2: [UInt32] = [17, 10, 14, 3, 9] - let feature2 = MVTDecoder.convertToLayerFeature( + let feature2 = try #require(MVTDecoder.convertToLayerFeature( geometryIntegers: geometry2, ofType: .point, - projectionFunction: MVTDecoder.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([ + projectionFunction: MVTDecoder.passThroughFromTile)) + let multiPoint2: MultiPoint = try #require(feature2.geometry as? MultiPoint) + let boundingBox2: BoundingBox = try #require(feature2.boundingBox) + let result2 = try #require(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()) + ])) + #expect(multiPoint2 == result2) + #expect(boundingBox2 == result2.calculateBoundingBox()) // Linestring let geometry3: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] - let feature3 = MVTDecoder.convertToLayerFeature( + let feature3 = try #require(MVTDecoder.convertToLayerFeature( geometryIntegers: geometry3, ofType: .linestring, - projectionFunction: MVTDecoder.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([ + projectionFunction: MVTDecoder.passThroughFromTile)) + let lineString3: LineString = try #require(feature3.geometry as? LineString) + let boundingBox3: BoundingBox = try #require(feature3.boundingBox) + let result3 = try #require(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()) + ])) + #expect(lineString3 == result3) + #expect(boundingBox3 == result3.calculateBoundingBox()) // MultiLinestring let geometry4: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] - let feature4 = MVTDecoder.convertToLayerFeature( + let feature4 = try #require(MVTDecoder.convertToLayerFeature( geometryIntegers: geometry4, ofType: .linestring, - projectionFunction: MVTDecoder.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([[ + projectionFunction: MVTDecoder.passThroughFromTile)) + let multiLineString4: MultiLineString = try #require(feature4.geometry as? MultiLineString) + let boundingBox4: BoundingBox = try #require(feature4.boundingBox) + let result4 = try #require(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()) + ]])) + #expect(multiLineString4 == result4) + #expect(boundingBox4 == result4.calculateBoundingBox()) // Polygon let geometry5: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] - let feature5 = MVTDecoder.convertToLayerFeature( + let feature5 = try #require(MVTDecoder.convertToLayerFeature( geometryIntegers: geometry5, ofType: .polygon, - projectionFunction: MVTDecoder.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([[ + projectionFunction: MVTDecoder.passThroughFromTile)) + let polygon5: Polygon = try #require(feature5.geometry as? Polygon) + let boundingBox5: BoundingBox = try #require(feature5.boundingBox) + let result5 = try #require(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()) + ]])) + #expect(polygon5 == result5) + #expect(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 = MVTDecoder.convertToLayerFeature( + let feature6 = try #require(MVTDecoder.convertToLayerFeature( geometryIntegers: geometry6, ofType: .polygon, - projectionFunction: MVTDecoder.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([[[ + projectionFunction: MVTDecoder.passThroughFromTile)) + let multiPolygon6: MultiPolygon = try #require(feature6.geometry as? MultiPolygon) + let boundingBox6: BoundingBox = try #require(feature6.boundingBox) + let result6 = try #require(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), @@ -226,9 +215,9 @@ final class MVTDecoderTests: XCTestCase { 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()) + ]]])) + #expect(multiPolygon6 == result6) + #expect(boundingBox6 == result6.calculateBoundingBox()) } } diff --git a/Tests/MVTToolsTests/Coders/MVTEncoderTests.swift b/Tests/MVTToolsTests/Coders/MVTEncoderTests.swift new file mode 100644 index 0000000..6ff72b0 --- /dev/null +++ b/Tests/MVTToolsTests/Coders/MVTEncoderTests.swift @@ -0,0 +1,275 @@ +#if !os(Linux) + import CoreLocation +#endif +import GISTools +import struct GISTools.Polygon +@testable import MVTTools +import Testing + +struct MVTEncoderTests { + + @Test + func featureGeometryEncoder() { + // Point + let point = Coordinate3D(latitude: 17.0, longitude: 25.0) + let pointGeometryIntegers = MVTEncoder.geometryIntegers( + fromMultiCoordinates: [[point]], + ofType: .point, + projectionFunction: MVTEncoder.passThroughToTile()) + let pointResult: [UInt32] = [9, 50, 34] + #expect(pointGeometryIntegers == pointResult) + + // MultiPoint + let multiPoint = [ + [Coordinate3D(latitude: 7.0, longitude: 5.0)], + [Coordinate3D(latitude: 2.0, longitude: 3.0)], + ] + let multiPointGeometryIntegers = MVTEncoder.geometryIntegers( + fromMultiCoordinates: multiPoint, + ofType: .point, + projectionFunction: MVTEncoder.passThroughToTile()) + let multiPointResult: [UInt32] = [17, 10, 14, 3, 9] + #expect(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 = MVTEncoder.geometryIntegers( + fromMultiCoordinates: lineString, + ofType: .linestring, + projectionFunction: MVTEncoder.passThroughToTile()) + let lineStringResult: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] + #expect(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 = MVTEncoder.geometryIntegers( + fromMultiCoordinates: multiLineString, + ofType: .linestring, + projectionFunction: MVTEncoder.passThroughToTile()) + let multiLineStringResult: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] + #expect(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 = MVTEncoder.geometryIntegers( + fromMultiCoordinates: polygon, + ofType: .polygon, + projectionFunction: MVTEncoder.passThroughToTile()) + let polygonResult: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] + #expect(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 = MVTEncoder.geometryIntegers( + fromMultiCoordinates: multiPolygon, + ofType: .polygon, + projectionFunction: MVTEncoder.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] + #expect(multiPolygonGeometryIntegers == multiPolygonResult) + } + + @Test + func featureConversion() async throws { + // Point + let point = Feature(Point(Coordinate3D(latitude: 17.0, longitude: 25.0)), id: .int(500)) + let pointFeature = try #require(MVTEncoder.vectorTileFeature( + from: point, + projectionFunction: MVTEncoder.passThroughToTile())) + let pointGeometry: [UInt32] = [9, 50, 34] + #expect(pointFeature.geometry == pointGeometry) + #expect(pointFeature.type == VectorTile_Tile.GeomType.point) + #expect(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 = try #require(MVTEncoder.vectorTileFeature( + from: multiPoint, + projectionFunction: MVTEncoder.passThroughToTile())) + let multiPointGeometry: [UInt32] = [17, 10, 14, 3, 9] + #expect(multiPointFeature.geometry == multiPointGeometry) + #expect(multiPointFeature.type == VectorTile_Tile.GeomType.point) + #expect(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 = try #require(MVTEncoder.vectorTileFeature( + from: lineString, + projectionFunction: MVTEncoder.passThroughToTile())) + let lineStringGeometry: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0] + #expect(lineStringFeature.geometry == lineStringGeometry) + #expect(lineStringFeature.type == VectorTile_Tile.GeomType.linestring) + #expect(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 = try #require(MVTEncoder.vectorTileFeature( + from: multiLineString, + projectionFunction: MVTEncoder.passThroughToTile())) + let multiLineStringGeometry: [UInt32] = [9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8] + #expect(multiLineStringFeature.geometry == multiLineStringGeometry) + #expect(multiLineStringFeature.type == VectorTile_Tile.GeomType.linestring) + #expect(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 = try #require(MVTEncoder.vectorTileFeature( + from: polygon, + projectionFunction: MVTEncoder.passThroughToTile())) + let polygonGeometry: [UInt32] = [9, 6, 12, 18, 10, 12, 24, 44, 15] + #expect(polygonFeature.geometry == polygonGeometry) + #expect(polygonFeature.type == VectorTile_Tile.GeomType.polygon) + #expect(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 = try #require(MVTEncoder.vectorTileFeature( + from: multiPolygon, + projectionFunction: MVTEncoder.passThroughToTile())) + 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] + #expect(multiPolygonFeature.geometry == multiPolygonGeometry) + #expect(multiPolygonFeature.type == VectorTile_Tile.GeomType.polygon) + #expect(multiPolygonFeature.id == 505) + } + + @Test + func encodeDecode() async throws { + var tile = try #require(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") + #expect(features.count == 1) + #expect(features[0].geometry as! Point == point.geometry as! Point) + #expect(features[0].id == .int(600)) + + let tileData = try #require(tile.data()) + let decodedTile = try #require(VectorTile(data: tileData, x: 0, y: 0, z: 0)) + + let decodedTileFeatures = decodedTile.features(for: "test") + #expect(decodedTileFeatures.count == 1) + #expect(abs((decodedTileFeatures[0].geometry as! Point).coordinate.latitude - 25) < 0.1) + #expect(abs((decodedTileFeatures[0].geometry as! Point).coordinate.longitude - 25) < 0.1) + #expect(decodedTileFeatures[0].id == .int(600)) + } + + @Test + func compressOption() async throws { + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) + + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14)) + let compressed = try #require(tile.data(options: .init(compression: .default))) + + #expect(compressed.isGzipped) + #expect(compressed.count < mvt.count, "Compressed tile should be smaller") + } + + @Test + func bufferSizeOption() async throws { + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) + + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14, layerWhitelist: ["building_label"])) + + let bufferedTileData = try #require(tile.data(options: .init(bufferSize: .extent(0)))) + let bufferedTile = try #require(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) + + #expect(features.count > 0) + #expect(features.allSatisfy({ bounds.contains($0.coordinate) })) + } + + @Test + func simplifyOption() async throws { + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) + + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14, layerWhitelist: ["road"])) + + let simplifiedTileData = try #require(tile.data(options: .init(bufferSize: .extent(4096), simplifyFeatures: .extent(1024)))) + let simplifiedTile = try #require(VectorTile(data: simplifiedTileData, x: 8716, y: 8015, z: 14)) + #expect(tile.features(for: "road").count == simplifiedTile.features(for: "road").count) + } + +} + +extension Data { + + private func utf8EncodedString() -> String? { + String(data: self, encoding: .utf8) + } + +} diff --git a/Tests/MVTToolsTests/DictionaryExtensionsTests.swift b/Tests/MVTToolsTests/DictionaryExtensionsTests.swift deleted file mode 100644 index 91ea598..0000000 --- a/Tests/MVTToolsTests/DictionaryExtensionsTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -import XCTest - -@testable import MVTTools - -final class DictionaryExtensionsTests: XCTestCase { - - func testHasKey() async throws { - let dict: [String: Any] = [ - "a": "value", - ] - - XCTAssertTrue(dict.hasKey("a")) - XCTAssertFalse(dict.hasKey("b")) - } - -} diff --git a/Tests/MVTToolsTests/Extensions/ArrayExtensionsTests.swift b/Tests/MVTToolsTests/Extensions/ArrayExtensionsTests.swift new file mode 100644 index 0000000..db62b98 --- /dev/null +++ b/Tests/MVTToolsTests/Extensions/ArrayExtensionsTests.swift @@ -0,0 +1,55 @@ +@testable import MVTTools +import Testing + +struct ArrayExtensionsTests { + + @Test + func pairs() async throws { + let even: [Int] = [1, 2, 3, 4, 5, 6] + let uneven: [Int] = [1, 2, 3, 4, 5] + + let evenPairs = even.pairs() + let unevenPairs = uneven.pairs() + + #expect(evenPairs.count == 3) + #expect(unevenPairs.count == 2) + + #expect(evenPairs[0].first == 1) + #expect(evenPairs[0].second == 2) + #expect(evenPairs[1].first == 3) + #expect(evenPairs[1].second == 4) + #expect(evenPairs[2].first == 5) + #expect(evenPairs[2].second == 6) + + #expect(unevenPairs[0].first == 1) + #expect(unevenPairs[0].second == 2) + #expect(unevenPairs[1].first == 3) + #expect(unevenPairs[1].second == 4) + } + + @Test + func smallPairs() async throws { + let empty: [Int] = [] + let small = [1] + + let emptyPairs = empty.pairs() + let smallPairs = small.pairs() + + #expect(emptyPairs.count == 0) + #expect(smallPairs.count == 0) + } + + @Test + func get() async throws { + let array = [0, 1, 2, 3, 4, 5, 6] + + #expect(array.get(at: 0) == 0) + #expect(array.get(at: 4) == 4) + #expect(array.get(at: -1) == 6) + #expect(array.get(at: -5) == 2) + + #expect(array.get(at: 7) == nil) + #expect(array.get(at: -8) == nil) + } + +} diff --git a/Tests/MVTToolsTests/Extensions/DictionaryExtensionsTests.swift b/Tests/MVTToolsTests/Extensions/DictionaryExtensionsTests.swift new file mode 100644 index 0000000..2992dd0 --- /dev/null +++ b/Tests/MVTToolsTests/Extensions/DictionaryExtensionsTests.swift @@ -0,0 +1,16 @@ +@testable import MVTTools +import Testing + +struct DictionaryExtensionsTests { + + @Test + func hasKey() async throws { + let dict: [String: Any] = [ + "a": "value", + ] + + #expect(dict.hasKey("a")) + #expect(dict.hasKey("b") == false) + } + +} diff --git a/Tests/MVTToolsTests/FixtureInfo.swift b/Tests/MVTToolsTests/FixtureInfo.swift deleted file mode 100644 index def37a5..0000000 --- a/Tests/MVTToolsTests/FixtureInfo.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -struct FixtureInfo: Codable { - - struct Validity: Codable { - var v1: Bool - var v2: Bool - } - - var description: String - var specificationReference: String - var validity: Validity - var proto: String - - enum CodingKeys: String, CodingKey { - case description - case specificationReference = "specification_reference" - case validity - case proto - } - -} diff --git a/Tests/MVTToolsTests/GeoJsonTests.swift b/Tests/MVTToolsTests/GeoJsonTests.swift index 7b22607..a6261cb 100644 --- a/Tests/MVTToolsTests/GeoJsonTests.swift +++ b/Tests/MVTToolsTests/GeoJsonTests.swift @@ -1,45 +1,46 @@ -import XCTest - import CommonCrypto import GISTools @testable import MVTTools +import Testing -final class GeoJsonTests: XCTestCase { +struct GeoJsonTests { - func testToGeoJSON() async throws { - let tileName = "14_8716_8015.vector.mvt" - let mvt = TestData.dataFromFile(name: tileName) - XCTAssertFalse(mvt.isEmpty) + @Test + func toGeoJSON() async throws { + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) - let tile = try XCTUnwrap(VectorTile(data: mvt, x: 8716, y: 8015, z: 14)) + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14)) // Export all layers - let allLayersFc = try XCTUnwrap(FeatureCollection(jsonData: try XCTUnwrap(tile.toGeoJson(layerProperty: VectorTile.defaultLayerPropertyName)))) - let allLayersLayerList = Set(try XCTUnwrap(allLayersFc.features.compactMap({ $0.properties[VectorTile.defaultLayerPropertyName] as? String }))) - XCTAssertEqual(Set(tile.layersWithContent.map(\.0)), allLayersLayerList) + let allLayersJSONData = try #require(tile.toGeoJson(layerProperty: VectorTile.defaultLayerPropertyName)) + let allLayersFc = try #require(FeatureCollection(jsonData: allLayersJSONData)) + let allLayersLayerList = Set(try #require(allLayersFc.features.compactMap({ $0.properties[VectorTile.defaultLayerPropertyName] as? String }))) + #expect(Set(tile.layersWithContent.map(\.0)) == allLayersLayerList) // Export some layers let someLayers = ["landuse", "waterway", "water"] - let someLayersFc = try XCTUnwrap(FeatureCollection(jsonData: try XCTUnwrap(tile.toGeoJson(layerNames: someLayers, additionalFeatureProperties: ["test": "test"], layerProperty: VectorTile.defaultLayerPropertyName)))) - let someLayersLayerList = Set(try XCTUnwrap(someLayersFc.features.compactMap({ $0.properties[VectorTile.defaultLayerPropertyName] as? String }))) - XCTAssertEqual(Set(someLayers), someLayersLayerList) - XCTAssertTrue(someLayersFc.features.allSatisfy({ ($0.properties["test"] as? String) == "test" })) + let someLayersJSONData = try #require(tile.toGeoJson(layerNames: someLayers, additionalFeatureProperties: ["test": "test"], layerProperty: VectorTile.defaultLayerPropertyName)) + let someLayersFc = try #require(FeatureCollection(jsonData: someLayersJSONData)) + let someLayersLayerList = Set(try #require(someLayersFc.features.compactMap({ $0.properties[VectorTile.defaultLayerPropertyName] as? String }))) + #expect(Set(someLayers) == someLayersLayerList) + #expect(someLayersFc.features.allSatisfy({ ($0.properties["test"] as? String) == "test" })) } - func testGeoJSONWithNull() throws { + @Test + func geoJSONWithNull() throws { let fc = FeatureCollection(Feature(Point(Coordinate3D(latitude: 47.56, longitude: 10.22, m: 1234)))) - var tile = try XCTUnwrap(VectorTile(x: 8657, y: 5725, z: 14)) + var tile = try #require(VectorTile(x: 8657, y: 5725, z: 14)) tile.addGeoJson(geoJson: fc, layerName: "test") - let data = try XCTUnwrap(tile.data()) - - let decodedTile = try XCTUnwrap(VectorTile(data: data, x: 8657, y: 5725, z: 14)) - let decodedFc = try XCTUnwrap(decodedTile.features(for: "test")?.first) - let decodedCoordinate = try XCTUnwrap(decodedFc.geometry.allCoordinates.first) + let data = try #require(tile.data()) + let decodedTile = try #require(VectorTile(data: data, x: 8657, y: 5725, z: 14)) + let decodedFc = try #require(decodedTile.features(for: "test").first) + let decodedCoordinate = try #require(decodedFc.geometry.allCoordinates.first) // Note: The MVT format doesn't encode altitude/m values, they will get lost - XCTAssertEqual(decodedCoordinate.latitude, 47.56, accuracy: 0.00001) - XCTAssertEqual(decodedCoordinate.longitude, 10.22, accuracy: 0.00001) + #expect(abs(decodedCoordinate.latitude - 47.56) < 0.00001) + #expect(abs(decodedCoordinate.longitude - 10.22) < 0.00001) } } diff --git a/Tests/MVTToolsTests/MVTEncoderTests.swift b/Tests/MVTToolsTests/MVTEncoderTests.swift deleted file mode 100644 index f991982..0000000 --- a/Tests/MVTToolsTests/MVTEncoderTests.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 MVTEncoderTests: XCTestCase { - - func testFeatureGeometryEncoder() { - // Point - let point = Coordinate3D(latitude: 17.0, longitude: 25.0) - let pointGeometryIntegers = MVTEncoder.geometryIntegers(fromMultiCoordinates: [[point]], ofType: .point, projectionFunction: MVTEncoder.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 = MVTEncoder.geometryIntegers(fromMultiCoordinates: multiPoint, ofType: .point, projectionFunction: MVTEncoder.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 = MVTEncoder.geometryIntegers(fromMultiCoordinates: lineString, ofType: .linestring, projectionFunction: MVTEncoder.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 = MVTEncoder.geometryIntegers(fromMultiCoordinates: multiLineString, ofType: .linestring, projectionFunction: MVTEncoder.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 = MVTEncoder.geometryIntegers(fromMultiCoordinates: polygon, ofType: .polygon, projectionFunction: MVTEncoder.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 = MVTEncoder.geometryIntegers(fromMultiCoordinates: multiPolygon, ofType: .polygon, projectionFunction: MVTEncoder.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 = MVTEncoder.vectorTileFeature(from: point, projectionFunction: MVTEncoder.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 = MVTEncoder.vectorTileFeature(from: multiPoint, projectionFunction: MVTEncoder.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 = MVTEncoder.vectorTileFeature(from: lineString, projectionFunction: MVTEncoder.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 = MVTEncoder.vectorTileFeature(from: multiLineString, projectionFunction: MVTEncoder.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 = MVTEncoder.vectorTileFeature(from: polygon, projectionFunction: MVTEncoder.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 = MVTEncoder.vectorTileFeature(from: multiPolygon, projectionFunction: MVTEncoder.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: .init(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: .init(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: .init(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) - } - -} diff --git a/Tests/MVTToolsTests/ProjectionTests.swift b/Tests/MVTToolsTests/ProjectionTests.swift index 86f0882..75a7b13 100644 --- a/Tests/MVTToolsTests/ProjectionTests.swift +++ b/Tests/MVTToolsTests/ProjectionTests.swift @@ -3,38 +3,39 @@ #endif import Foundation import GISTools -import XCTest - @testable import MVTTools +import Testing -final class ProjectionTests: XCTestCase { +struct ProjectionTests { - func testProjectToEpsg3857() { + @Test + func projectToEpsg3857() async throws { let projection1 = Coordinate3D(latitude: 20.5, longitude: 10.5).projected(to: .epsg3857) - XCTAssertEqual(projection1.latitude, 2_332_357.812619, accuracy: 0.00001) - XCTAssertEqual(projection1.longitude, 1_168_854.653329, accuracy: 0.00001) + #expect(abs(projection1.latitude - 2_332_357.812619) < 0.00001) + #expect(abs(projection1.longitude - 1_168_854.653329) < 0.00001) let projection2 = Coordinate3D(latitude: 45.0, longitude: -180.0).projected(to: .epsg3857) - XCTAssertEqual(projection2.latitude, 5_621_521.486192, accuracy: 0.00001) - XCTAssertEqual(projection2.longitude, -20_037_508.342789, accuracy: 0.00001) + #expect(abs(projection2.latitude - 5_621_521.486192) < 0.00001) + #expect(abs(projection2.longitude - -20_037_508.342789) < 0.00001) let projection3 = Coordinate3D(latitude: -45.0, longitude: 45.0).projected(to: .epsg3857) - XCTAssertEqual(projection3.latitude, -5_621_521.486192, accuracy: 0.00001) - XCTAssertEqual(projection3.longitude, 5_009_377.085697, accuracy: 0.00001) + #expect(abs(projection3.latitude - -5_621_521.486192) < 0.00001) + #expect(abs(projection3.longitude - 5_009_377.085697) < 0.00001) } - func testProjectToEpsg4326() { + @Test + func projectToEpsg4326() async throws { let projection1 = Coordinate3D(x: 1_168_854.653329, y: 2_332_357.812619).projected(to: .epsg4326) - XCTAssertEqual(projection1.latitude, 20.5, accuracy: 0.00001) - XCTAssertEqual(projection1.longitude, 10.5, accuracy: 0.00001) + #expect(abs(projection1.latitude - 20.5) < 0.00001) + #expect(abs(projection1.longitude - 10.5) < 0.00001) let projection2 = Coordinate3D(x: -20_037_508.342789, y: 5_621_521.486192).projected(to: .epsg4326) - XCTAssertEqual(projection2.latitude, 45.0, accuracy: 0.00001) - XCTAssertEqual(projection2.longitude, -180.0, accuracy: 0.00001) + #expect(abs(projection2.latitude - 45.0) < 0.00001) + #expect(abs(projection2.longitude - -180.0) < 0.00001) let projection3 = Coordinate3D(x: 5_009_377.09, y: -5_621_521.49).projected(to: .epsg4326) - XCTAssertEqual(projection3.latitude, -45.0, accuracy: 0.00001) - XCTAssertEqual(projection3.longitude, 45.0, accuracy: 0.00001) + #expect(abs(projection3.latitude - -45.0) < 0.00001) + #expect(abs(projection3.longitude - 45.0) < 0.00001) } } diff --git a/Tests/MVTToolsTests/QueryParserTests.swift b/Tests/MVTToolsTests/QueryParserTests.swift index 49eafff..e9c7fc8 100644 --- a/Tests/MVTToolsTests/QueryParserTests.swift +++ b/Tests/MVTToolsTests/QueryParserTests.swift @@ -1,9 +1,8 @@ import GISTools -import XCTest - @testable import MVTTools +import Testing -final class QueryParserTests: XCTestCase { +struct QueryParserTests { private static let properties: [String: Sendable] = [ "foo": [ @@ -28,42 +27,46 @@ final class QueryParserTests: XCTestCase { QueryParser(string: query)?.pipeline ?? [] } - func testValues() throws { - XCTAssertTrue(result(for: [.value([.key("foo")])])) - XCTAssertTrue(result(for: [.value([.key("foo"), .key("bar")])])) - XCTAssertFalse(result(for: [.value([.key("foo"), .key("x")])])) - XCTAssertFalse(result(for: [.value([.key("foo.bar")])])) - XCTAssertFalse(result(for: [.value([.key("foo"), .index(0)])])) - XCTAssertTrue(result(for: [.value([.key("some"), .index(0)])])) + @Test + func values() async throws { + #expect(result(for: [.value([.key("foo")])])) + #expect(result(for: [.value([.key("foo"), .key("bar")])])) + #expect(result(for: [.value([.key("foo"), .key("x")])]) == false) + #expect(result(for: [.value([.key("foo.bar")])]) == false) + #expect(result(for: [.value([.key("foo"), .index(0)])]) == false) + #expect(result(for: [.value([.key("some"), .index(0)])])) } - func testNear() throws { - XCTAssertEqual(pipeline(for: "near(10.0, 20.0, 1000)"), [ + @Test + func near() async throws { + #expect(pipeline(for: "near(10.0, 20.0, 1000)") == [ .near(Coordinate3D(latitude: 10.0, longitude: 20.0), 1000.0), ]) } - func testComparisons() throws { - XCTAssertFalse(result(for: [.value([.key("value")]), .literal("bar"), .comparison(.equals)])) - XCTAssertTrue(result(for: [.value([.key("value")]), .literal(1), .comparison(.equals)])) - XCTAssertTrue(result(for: [.value([.key("value")]), .literal(1.0), .comparison(.equals)])) - XCTAssertFalse(result(for: [.value([.key("value")]), .literal(1), .comparison(.notEquals)])) - XCTAssertFalse(result(for: [.value([.key("value")]), .literal(1), .comparison(.greaterThan)])) - XCTAssertTrue(result(for: [.value([.key("value")]), .literal(1), .comparison(.greaterThanOrEqual)])) - XCTAssertTrue(result(for: [.value([.key("value")]), .literal(0.5), .comparison(.greaterThanOrEqual)])) - XCTAssertFalse(result(for: [.value([.key("value")]), .literal(1), .comparison(.lessThan)])) - XCTAssertTrue(result(for: [.value([.key("value")]), .literal(1), .comparison(.lessThanOrEqual)])) - XCTAssertTrue(result(for: [.value([.key("value")]), .literal(1.5), .comparison(.lessThanOrEqual)])) - XCTAssertTrue(result(for: [.value([.key("foo"), .key("baz")]), .literal(10), .comparison(.equals)])) - XCTAssertFalse(result(for: [.value([.key("x")]), .literal(1), .comparison(.equals)])) - XCTAssertTrue(result(for: [.value([.key("string")]), .literal("name$"), .comparison(.regex)])) - XCTAssertTrue(result(for: [.value([.key("string")]), .literal("/[Ss]ome/"), .comparison(.regex)])) - XCTAssertFalse(result(for: [.value([.key("string")]), .literal("^some"), .comparison(.regex)])) - XCTAssertTrue(result(for: [.value([.key("string")]), .literal("/^some/i"), .comparison(.regex)])) + @Test + func comparisons() async throws { + #expect(result(for: [.value([.key("value")]), .literal("bar"), .comparison(.equals)]) == false) + #expect(result(for: [.value([.key("value")]), .literal(1), .comparison(.equals)])) + #expect(result(for: [.value([.key("value")]), .literal(1.0), .comparison(.equals)])) + #expect(result(for: [.value([.key("value")]), .literal(1), .comparison(.notEquals)]) == false) + #expect(result(for: [.value([.key("value")]), .literal(1), .comparison(.greaterThan)]) == false) + #expect(result(for: [.value([.key("value")]), .literal(1), .comparison(.greaterThanOrEqual)])) + #expect(result(for: [.value([.key("value")]), .literal(0.5), .comparison(.greaterThanOrEqual)])) + #expect(result(for: [.value([.key("value")]), .literal(1), .comparison(.lessThan)]) == false) + #expect(result(for: [.value([.key("value")]), .literal(1), .comparison(.lessThanOrEqual)])) + #expect(result(for: [.value([.key("value")]), .literal(1.5), .comparison(.lessThanOrEqual)])) + #expect(result(for: [.value([.key("foo"), .key("baz")]), .literal(10), .comparison(.equals)])) + #expect(result(for: [.value([.key("x")]), .literal(1), .comparison(.equals)]) == false) + #expect(result(for: [.value([.key("string")]), .literal("name$"), .comparison(.regex)])) + #expect(result(for: [.value([.key("string")]), .literal("/[Ss]ome/"), .comparison(.regex)])) + #expect(result(for: [.value([.key("string")]), .literal("^some"), .comparison(.regex)]) == false) + #expect(result(for: [.value([.key("string")]), .literal("/^some/i"), .comparison(.regex)])) } - func testConditions() throws { - XCTAssertTrue(result(for: [ + @Test + func conditions() async throws { + #expect(result(for: [ .value([.key("foo"), .key("bar")]), .literal(1), .comparison(.equals), @@ -72,7 +75,7 @@ final class QueryParserTests: XCTestCase { .comparison(.equals), .condition(.and), ])) - XCTAssertFalse(result(for: [ + #expect(result(for: [ .value([.key("foo")]), .literal(1), .comparison(.equals), @@ -80,8 +83,8 @@ final class QueryParserTests: XCTestCase { .literal(2), .comparison(.equals), .condition(.or), - ])) - XCTAssertTrue(result(for: [ + ]) == false) + #expect(result(for: [ .value([.key("foo")]), .literal(1), .comparison(.equals), @@ -90,74 +93,77 @@ final class QueryParserTests: XCTestCase { .comparison(.equals), .condition(.or), ])) - XCTAssertFalse(result(for: [ + #expect(result(for: [ .value([.key("foo")]), .condition(.not), - ])) - XCTAssertTrue(result(for: [ + ]) == false) + #expect(result(for: [ .value([.key("foo")]), .value([.key("bar")]), .condition(.and), .condition(.not), ])) - XCTAssertFalse(result(for: [ + #expect(result(for: [ .value([.key("foo")]), .value([.key("some")]), .condition(.and), .condition(.not), - ])) - XCTAssertFalse(result(for: [ + ]) == false) + #expect(result(for: [ .value([.key("foo")]), .value([.key("bar")]), .condition(.or), .condition(.not), - ])) - XCTAssertTrue(result(for: [ + ]) == false) + #expect(result(for: [ .value([.key("x")]), .value([.key("y")]), .condition(.or), .condition(.not), ])) - XCTAssertFalse(result(for: [ + #expect(result(for: [ .value([.key("foo"), .key("bar")]), .condition(.not), - ])) - XCTAssertTrue(result(for: [ + ]) == false) + #expect(result(for: [ .value([.key("foo"), .key("x")]), .condition(.not), ])) } - func testValueQueries() throws { - XCTAssertEqual(pipeline(for: ".foo"), [.value([.key("foo")])]) - XCTAssertEqual(pipeline(for: ".foo.bar"), [.value([.key("foo"), .key("bar")])]) - XCTAssertEqual(pipeline(for: ".foo.x"), [.value([.key("foo"), .key("x")])]) - XCTAssertEqual(pipeline(for: ".\"foo\".\"bar\""), [.value([.key("foo"), .key("bar")])]) - XCTAssertEqual(pipeline(for: ".\"foo.bar\""), [.value([.key("foo.bar")])]) - XCTAssertEqual(pipeline(for: ".foo.[0]"), [.value([.key("foo"), .index(0)])]) - XCTAssertEqual(pipeline(for: ".some.0"), [.value([.key("some"), .index(0)])]) + @Test + func valueQueries() async throws { + #expect(pipeline(for: ".foo") == [.value([.key("foo")])]) + #expect(pipeline(for: ".foo.bar") == [.value([.key("foo"), .key("bar")])]) + #expect(pipeline(for: ".foo.x") == [.value([.key("foo"), .key("x")])]) + #expect(pipeline(for: ".\"foo\".\"bar\"") == [.value([.key("foo"), .key("bar")])]) + #expect(pipeline(for: ".\"foo.bar\"") == [.value([.key("foo.bar")])]) + #expect(pipeline(for: ".foo.[0]") == [.value([.key("foo"), .index(0)])]) + #expect(pipeline(for: ".some.0") == [.value([.key("some"), .index(0)])]) } - func testComparisonQueries() throws { - XCTAssertEqual(pipeline(for: ".value == \"bar\""), [.value([.key("value")]), .literal("bar"), .comparison(.equals)]) - XCTAssertEqual(pipeline(for: ".value == 'bar'"), [.value([.key("value")]), .literal("bar"), .comparison(.equals)]) - XCTAssertEqual(pipeline(for: ".value == 'bar\"baz'"), [.value([.key("value")]), .literal("bar\"baz"), .comparison(.equals)]) + @Test + func comparisonQueries() async throws { + #expect(pipeline(for: ".value == \"bar\"") == [.value([.key("value")]), .literal("bar"), .comparison(.equals)]) + #expect(pipeline(for: ".value == 'bar'") == [.value([.key("value")]), .literal("bar"), .comparison(.equals)]) + #expect(pipeline(for: ".value == 'bar\"baz'") == [.value([.key("value")]), .literal("bar\"baz"), .comparison(.equals)]) - XCTAssertEqual(pipeline(for: ".value == 1"), [.value([.key("value")]), .literal(1), .comparison(.equals)]) - XCTAssertEqual(pipeline(for: ".value != 1"), [.value([.key("value")]), .literal(1), .comparison(.notEquals)]) - XCTAssertEqual(pipeline(for: ".value > 1"), [.value([.key("value")]), .literal(1), .comparison(.greaterThan)]) - XCTAssertEqual(pipeline(for: ".value >= 1"), [.value([.key("value")]), .literal(1), .comparison(.greaterThanOrEqual)]) - XCTAssertEqual(pipeline(for: ".value < 1"), [.value([.key("value")]), .literal(1), .comparison(.lessThan)]) - XCTAssertEqual(pipeline(for: ".value <= 1"), [.value([.key("value")]), .literal(1), .comparison(.lessThanOrEqual)]) + #expect(pipeline(for: ".value == 1") == [.value([.key("value")]), .literal(1), .comparison(.equals)]) + #expect(pipeline(for: ".value != 1") == [.value([.key("value")]), .literal(1), .comparison(.notEquals)]) + #expect(pipeline(for: ".value > 1") == [.value([.key("value")]), .literal(1), .comparison(.greaterThan)]) + #expect(pipeline(for: ".value >= 1") == [.value([.key("value")]), .literal(1), .comparison(.greaterThanOrEqual)]) + #expect(pipeline(for: ".value < 1") == [.value([.key("value")]), .literal(1), .comparison(.lessThan)]) + #expect(pipeline(for: ".value <= 1") == [.value([.key("value")]), .literal(1), .comparison(.lessThanOrEqual)]) - XCTAssertEqual(pipeline(for: ".string =~ /[Ss]ome/"), [.value([.key("string")]), .literal("/[Ss]ome/"), .comparison(.regex)]) - XCTAssertEqual(pipeline(for: ".string =~ /some/"), [.value([.key("string")]), .literal("/some/"), .comparison(.regex)]) - XCTAssertEqual(pipeline(for: ".string =~ /some/i"), [.value([.key("string")]), .literal("/some/i"), .comparison(.regex)]) - XCTAssertEqual(pipeline(for: ".string =~ \"^Some\""), [.value([.key("string")]), .literal("^Some"), .comparison(.regex)]) + #expect(pipeline(for: ".string =~ /[Ss]ome/") == [.value([.key("string")]), .literal("/[Ss]ome/"), .comparison(.regex)]) + #expect(pipeline(for: ".string =~ /some/") == [.value([.key("string")]), .literal("/some/"), .comparison(.regex)]) + #expect(pipeline(for: ".string =~ /some/i") == [.value([.key("string")]), .literal("/some/i"), .comparison(.regex)]) + #expect(pipeline(for: ".string =~ \"^Some\"") == [.value([.key("string")]), .literal("^Some"), .comparison(.regex)]) } - func testConditionQueries() throws { - XCTAssertEqual(pipeline(for: ".foo.bar == 1 and .value == 1"), [ + @Test + func conditionQueries() async throws { + #expect(pipeline(for: ".foo.bar == 1 and .value == 1") == [ .value([.key("foo"), .key("bar")]), .literal(1), .comparison(.equals), @@ -166,7 +172,7 @@ final class QueryParserTests: XCTestCase { .comparison(.equals), .condition(.and), ]) - XCTAssertEqual(pipeline(for: ".foo == 1 or .bar == 2"), [ + #expect(pipeline(for: ".foo == 1 or .bar == 2") == [ .value([.key("foo")]), .literal(1), .comparison(.equals), @@ -175,7 +181,7 @@ final class QueryParserTests: XCTestCase { .comparison(.equals), .condition(.or), ]) - XCTAssertEqual(pipeline(for: ".foo == 1 or .value == 1"), [ + #expect(pipeline(for: ".foo == 1 or .value == 1") == [ .value([.key("foo")]), .literal(1), .comparison(.equals), @@ -184,27 +190,27 @@ final class QueryParserTests: XCTestCase { .comparison(.equals), .condition(.or), ]) - XCTAssertEqual(pipeline(for: ".foo not"), [ + #expect(pipeline(for: ".foo not") == [ .value([.key("foo")]), .condition(.not), ]) - XCTAssertEqual(pipeline(for: ".foo and .bar not"), [ + #expect(pipeline(for: ".foo and .bar not") == [ .value([.key("foo")]), .value([.key("bar")]), .condition(.and), .condition(.not), ]) - XCTAssertEqual(pipeline(for: ".foo or .bar not"), [ + #expect(pipeline(for: ".foo or .bar not") == [ .value([.key("foo")]), .value([.key("bar")]), .condition(.or), .condition(.not), ]) - XCTAssertEqual(pipeline(for: ".foo.bar not"), [ + #expect(pipeline(for: ".foo.bar not") == [ .value([.key("foo"), .key("bar")]), .condition(.not), ]) - XCTAssertEqual(pipeline(for: ".foo == 'not'"), [ + #expect(pipeline(for: ".foo == 'not'") == [ .value([.key("foo")]), .literal("not"), .comparison(.equals), diff --git a/Tests/MVTToolsTests/QueryTests.swift b/Tests/MVTToolsTests/QueryTests.swift index 6dce526..a2567c2 100644 --- a/Tests/MVTToolsTests/QueryTests.swift +++ b/Tests/MVTToolsTests/QueryTests.swift @@ -2,55 +2,41 @@ import CoreLocation #endif import GISTools -import XCTest - @testable import MVTTools +import Testing -final class QueryTests: XCTestCase { +struct QueryTests { - func testQueryBoundingBox() { + @Test + func queryBoundingBox() async throws { let coordinate = Coordinate3D(latitude: 47.0, longitude: -120.0) let queryBoundingBox = VectorTile.queryBoundingBox(at: coordinate, tolerance: 15.0, projection: .epsg4326) - XCTAssertGreaterThan(queryBoundingBox.northEast.latitude, queryBoundingBox.southWest.latitude) - XCTAssertGreaterThan(queryBoundingBox.northEast.longitude, queryBoundingBox.southWest.longitude) + #expect(queryBoundingBox.northEast.latitude > queryBoundingBox.southWest.latitude) + #expect(queryBoundingBox.northEast.longitude > queryBoundingBox.southWest.longitude) } - func testQuery() { - 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 - } + @Test + func query() async throws { + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) - XCTAssertFalse(tile.isIndexed) + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14)) + #expect(tile.isIndexed == false) - measure { - let result = tile.query(at: Coordinate3D(latitude: 3.870163, longitude: 11.518585), tolerance: 100.0) - XCTAssertNotNil(result) - XCTAssertEqual(result.count, 107) - } + let result = tile.query(at: Coordinate3D(latitude: 3.870163, longitude: 11.518585), tolerance: 100.0) + #expect(result.count == 107) } - func testQueryWithIndex() { - 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, indexed: .hilbert) else { - XCTAssert(false, "Unable to parse the vector tile \(tileName)") - return - } + @Test + func queryWithIndex() async throws { + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) - XCTAssertTrue(tile.isIndexed) + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14, indexed: .hilbert)) + #expect(tile.isIndexed) - measure { - let resultWithIndex = tile.query(at: Coordinate3D(latitude: 3.870163, longitude: 11.518585), tolerance: 100.0) - XCTAssertNotNil(resultWithIndex) - XCTAssertEqual(resultWithIndex.count, 107) - } + let resultWithIndex = tile.query(at: Coordinate3D(latitude: 3.870163, longitude: 11.518585), tolerance: 100.0) + #expect(resultWithIndex.count == 107) } } diff --git a/Tests/MVTToolsTests/TestData.swift b/Tests/MVTToolsTests/TestData.swift index 7a45326..7609bae 100644 --- a/Tests/MVTToolsTests/TestData.swift +++ b/Tests/MVTToolsTests/TestData.swift @@ -1,44 +1,24 @@ import Foundation import XCTest -class TestData { +struct TestData { - class func stringFromFile(name: String) -> String { + static func stringFromFile(name: String) throws -> String { let path = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .appendingPathComponent("TestData") .appendingPathComponent(name) - do { - if try !(path.checkResourceIsReachable()) { - XCTAssert(false, "Fixture \(name) not found.") - return "" - } - return try String(contentsOf: path, encoding: .utf8) - } - catch { - XCTAssert(false, "Unable to decode fixture at \(path): \(error).") - return "" - } + return try String(contentsOf: path, encoding: .utf8) } - class func dataFromFile(name: String) -> Data { + static func dataFromFile(name: String) throws -> Data { let path = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .appendingPathComponent("TestData") .appendingPathComponent(name) - do { - if try !(path.checkResourceIsReachable()) { - XCTAssert(false, "Fixture \(name) not found.") - return Data() - } - return try Data(contentsOf: path) - } - catch { - XCTAssert(false, "Unable to decode fixture at \(path): \(error).") - return Data() - } + return try Data(contentsOf: path) } } diff --git a/Tests/MVTToolsTests/VectorTileTests.swift b/Tests/MVTToolsTests/VectorTileTests.swift index 4e10eb3..202015e 100644 --- a/Tests/MVTToolsTests/VectorTileTests.swift +++ b/Tests/MVTToolsTests/VectorTileTests.swift @@ -3,38 +3,34 @@ #endif import GISTools import struct GISTools.Polygon -import XCTest - @testable import MVTTools +import Testing -final class VectorTileTests: XCTestCase { +struct VectorTileTests { - func testLoadMvt() async throws { - let tileName = "14_8716_8015.vector.mvt" + @Test + func loadMvt() async throws { let tileLayerNames: [String] = ["landuse", "waterway", "water", "aeroway", "barrier_line", "building", "landuse_overlay", "tunnel", "road", "bridge", "admin", "country_label_line", "country_label", "marine_label", "state_label", "place_label", "water_label", "area_label", "rail_station_label", "airport_label", "road_label", "waterway_label", "building_label"].sorted() - let mvt = TestData.dataFromFile(name: tileName) - XCTAssertFalse(mvt.isEmpty) + let mvt = try TestData.dataFromFile(name: "14_8716_8015.vector.mvt") + #expect(mvt.isEmpty == false) let layerNames = VectorTile.layerNames(from: mvt)?.sorted() - XCTAssertEqual(layerNames, tileLayerNames) + #expect(layerNames == tileLayerNames) - let tile = try XCTUnwrap(VectorTile(data: mvt, x: 8716, y: 8015, z: 14)) - XCTAssertEqual(tile.layerNames.sorted(), tileLayerNames) + let tile = try #require(VectorTile(data: mvt, x: 8716, y: 8015, z: 14)) + #expect(tile.layerNames.sorted() == tileLayerNames) - let tileAsJsonData: Data? = tile.toGeoJson(prettyPrinted: true) - XCTAssertNotNil(tileAsJsonData) + let _ = try #require(tile.toGeoJson(prettyPrinted: true)) let result = tile.query(at: Coordinate3D(latitude: 3.870163, longitude: 11.518585), tolerance: 100.0) let resultLayerNames: [String] = Set(result.map({ $0.layerName })).sorted() - XCTAssertEqual(resultLayerNames, ["barrier_line", "building", "building_label", "landuse", "road", "road_label"]) - -// let string = String(data: tileAsJsonData!, encoding: .utf8) -// try? string?.write(to: URL(fileURLWithPath: "/\(NSHomeDirectory())/Desktop/test.json"), atomically: true, encoding: .utf8) + #expect(resultLayerNames == ["barrier_line", "building", "building_label", "landuse", "road", "road_label"]) } - func testWriteMvt() async throws { - var tile = try XCTUnwrap(VectorTile(x: 8716, y: 8015, z: 14)) + @Test + func writeMvt() async throws { + var tile = try #require(VectorTile(x: 8716, y: 8015, z: 14)) var feature = Feature(Point(Coordinate3D(latitude: 3.870163, longitude: 11.518585))) feature.properties = [ @@ -48,59 +44,61 @@ final class VectorTileTests: XCTestCase { ] tile.setFeatures([feature], for: "test") - let tileData = tile.data() - XCTAssertNotNil(tileData) - -// tile.write(to: URL(fileURLWithPath: "/\(NSHomeDirectory())/Desktop/14_8716_8015.test.mvt")) + let _ = try #require(tile.data()) } - func testTileInfo() async throws { + @Test + func tileInfo() async throws { let tileName = "14_8716_8015.vector.mvt" - let mvt = TestData.dataFromFile(name: tileName) - XCTAssertFalse(mvt.isEmpty) + let mvt = try TestData.dataFromFile(name: tileName) + #expect(mvt.isEmpty == false) - let layers = try XCTUnwrap(VectorTile.tileInfo(from: mvt)) - XCTAssertEqual(layers.count, 23) + let layers = try #require(VectorTile.tileInfo(from: mvt)) + #expect(layers.count == 23) } - func testMerge() { - var tile1 = VectorTile(x: 0, y: 0, z: 0)! - var tile2 = VectorTile(x: 0, y: 0, z: 0)! - var tile3 = VectorTile(x: 0, y: 0, z: 0)! + @Test + func merge() async throws { + var tile1 = try #require(VectorTile(x: 0, y: 0, z: 0)) + var tile2 = try #require(VectorTile(x: 0, y: 0, z: 0)) + var tile3 = try #require(VectorTile(x: 0, y: 0, z: 0)) + + #expect(tile1.features(for: "test1").count == 0) + #expect(tile1.features(for: "test2").count == 0) let feature1 = Feature(Point(Coordinate3D(latitude: 10.0, longitude: 10.0))) let feature2 = Feature(Point(Coordinate3D(latitude: -10.0, longitude: -10.0))) - XCTAssertEqual(tile1.features(for: "test1")?.count ?? 0, 0) - XCTAssertEqual(tile1.features(for: "test2")?.count ?? 0, 0) - tile1.appendFeatures([feature1], to: "test1") tile2.appendFeatures([feature2], to: "test1") tile3.appendFeatures([feature2], to: "test2") - XCTAssertEqual(tile1.features(for: "test1")!.count, 1) - XCTAssertTrue(tile1.merge(tile2)) - XCTAssertEqual(tile1.features(for: "test1")!.count, 2) + #expect(tile1.features(for: "test1").count == 1) + let tile1and2mergeResult = tile1.merge(tile2) + #expect(tile1and2mergeResult) + #expect(tile1.features(for: "test1").count == 2) - XCTAssertEqual(tile1.features(for: "test2")?.count ?? 0, 0) - XCTAssertTrue(tile1.merge(tile3)) - XCTAssertEqual(tile1.features(for: "test1")!.count, 2) - XCTAssertEqual(tile1.features(for: "test2")!.count, 1) + #expect(tile1.features(for: "test2").count == 0) + let tile1and3mergeResult = tile1.merge(tile3) + #expect(tile1and3mergeResult) + #expect(tile1.features(for: "test1").count == 2) + #expect(tile1.features(for: "test2").count == 1) } - func testEncodeDecodeBigInt() throws { - let feature = try XCTUnwrap(Feature(jsonData: TestData.dataFromFile(name: "bigint_id.geojson"))) - XCTAssertEqual(feature.id, .uint(18_446_744_073_638_380_036)) + @Test + func encodeDecodeBigInt() async throws { + let feature = try #require(Feature(jsonData: TestData.dataFromFile(name: "bigint_id.geojson"))) + #expect(feature.id == .uint(18_446_744_073_638_380_036)) - var tile = try XCTUnwrap(VectorTile(x: 10, y: 25, z: 6)) + var tile = try #require(VectorTile(x: 10, y: 25, z: 6)) tile.setFeatures([feature], for: "test") - let tileData = try XCTUnwrap(tile.data()) - XCTAssertFalse(tileData.isEmpty) + let tileData = try #require(tile.data()) + #expect(tileData.isEmpty == false) - let tile2 = try XCTUnwrap(VectorTile(data: tileData, x: 10, y: 25, z: 6)) - let feature2: Feature = try XCTUnwrap(tile2.features(for: "test")?.first) + let tile2 = try #require(VectorTile(data: tileData, x: 10, y: 25, z: 6)) + let feature2: Feature = try #require(tile2.features(for: "test").first) - XCTAssertEqual(feature.id, feature2.id) + #expect(feature.id == feature2.id) } }