diff --git a/Package.resolved b/Package.resolved index 816bfbd..8b80b5a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git", "state" : { - "revision" : "92505cfbad5c5ed6a93e0f3cd70872aaa98a12ac", - "version" : "6.2.0" + "revision" : "6d0071977ed1f2380c739715f82ac650f99b0824", + "version" : "6.4.0" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stadiamaps/maplibre-swift-macros.git", "state" : { - "revision" : "9f15cbb11d2b5248ead47aecae5be8a1d4d5f463", - "version" : "0.0.2" + "revision" : "d52adbcbfaf96bd0723a156bd838826916ff7a69", + "version" : "0.0.3" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "5b0c434778f2c1a4c9b5ebdb8682b28e84dd69bd", - "version" : "1.15.4" + "revision" : "625ccca8570773dd84a34ee51a81aa2bc5a4f97a", + "version" : "1.16.0" } }, { diff --git a/Package.swift b/Package.swift index bc273c8..06a554e 100644 --- a/Package.swift +++ b/Package.swift @@ -8,7 +8,7 @@ let package = Package( name: "MapLibreSwiftUI", platforms: [ .iOS(.v15), - .macOS(.v11), + .macOS(.v12), ], products: [ .library( @@ -21,8 +21,8 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.1.0"), - .package(url: "https://github.com/stadiamaps/maplibre-swift-macros.git", from: "0.0.2"), + .package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.4.0"), + .package(url: "https://github.com/stadiamaps/maplibre-swift-macros.git", from: "0.0.3"), // Testing .package(url: "https://github.com/Kolos65/Mockable.git", exact: "0.0.3"), .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.15.3"), diff --git a/Sources/MapLibreSwiftDSL/ShapeDataBuilder.swift b/Sources/MapLibreSwiftDSL/ShapeDataBuilder.swift index ea5b3b0..84c6eb4 100644 --- a/Sources/MapLibreSwiftDSL/ShapeDataBuilder.swift +++ b/Sources/MapLibreSwiftDSL/ShapeDataBuilder.swift @@ -21,10 +21,16 @@ public enum ShapeData { public struct ShapeSource: Source { public let identifier: String + public let options: [MLNShapeSourceOption: Any]? let data: ShapeData - public init(identifier: String, @ShapeDataBuilder _ makeShapeDate: () -> ShapeData) { + public init( + identifier: String, + options: [MLNShapeSourceOption: Any]? = nil, + @ShapeDataBuilder _ makeShapeDate: () -> ShapeData + ) { self.identifier = identifier + self.options = options data = makeShapeDate() } @@ -32,11 +38,11 @@ public struct ShapeSource: Source { // TODO: Options! These should be represented via modifiers like .clustered() switch data { case let .geoJSONURL(url): - MLNShapeSource(identifier: identifier, url: url) + MLNShapeSource(identifier: identifier, url: url, options: options) case let .shapes(shapes): - MLNShapeSource(identifier: identifier, shapes: shapes) + MLNShapeSource(identifier: identifier, shapes: shapes, options: options) case let .features(features): - MLNShapeSource(identifier: identifier, features: features) + MLNShapeSource(identifier: identifier, features: features, options: options) } } } diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift b/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift index f835f98..02d49fa 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift @@ -7,7 +7,7 @@ import MapLibreSwiftMacros @MLNStyleProperty("color", supportsInterpolation: false) @MLNStyleProperty("strokeWidth", supportsInterpolation: true) @MLNStyleProperty("strokeColor", supportsInterpolation: false) -public struct CircleStyleLayer: SourceBoundStyleLayerDefinition { +public struct CircleStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public var insertionPosition: LayerInsertionPosition = .aboveOthers public var isVisible: Bool = true @@ -15,6 +15,7 @@ public struct CircleStyleLayer: SourceBoundStyleLayerDefinition { public var minimumZoomLevel: Float? = nil public var source: StyleLayerSource + public var predicate: NSPredicate? public init(identifier: String, source: Source) { self.identifier = identifier @@ -74,6 +75,8 @@ private struct CircleStyleLayerInternal: StyleLayer { result.circleStrokeWidth = definition.strokeWidth result.circleStrokeColor = definition.strokeColor + result.predicate = definition.predicate + return result } } diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Line.swift b/Sources/MapLibreSwiftDSL/Style Layers/Line.swift index 93cd143..c6d65cb 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Line.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Line.swift @@ -8,7 +8,7 @@ import MapLibreSwiftMacros @MLNRawRepresentableStyleProperty("lineCap") @MLNRawRepresentableStyleProperty("lineJoin") @MLNStyleProperty("lineWidth", supportsInterpolation: true) -public struct LineStyleLayer: SourceBoundStyleLayerDefinition { +public struct LineStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public var insertionPosition: LayerInsertionPosition = .aboveOthers public var isVisible: Bool = true @@ -16,6 +16,7 @@ public struct LineStyleLayer: SourceBoundStyleLayerDefinition { public var minimumZoomLevel: Float? = nil public var source: StyleLayerSource + public var predicate: NSPredicate? public init(identifier: String, source: Source) { self.identifier = identifier @@ -72,6 +73,8 @@ private struct LineStyleLayerInternal: StyleLayer { result.lineWidth = definition.lineWidth result.lineJoin = definition.lineJoin + result.predicate = definition.predicate + return result } } diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift b/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift index 6dda066..814f754 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift @@ -77,6 +77,31 @@ public protocol SourceBoundStyleLayerDefinition: StyleLayerDefinition { var source: StyleLayerSource { get set } } +/// Based on MLNVectorStyleLayer +public protocol SourceBoundVectorStyleLayerDefinition: SourceBoundStyleLayerDefinition { + /** + The style layer’s predicate. + + Use the style layer’s predicate to include only the features in the source + layer that satisfy a condition that you define. + + See the *Predicates and Expressions* + guide for details about the predicate syntax supported by this class: + https://maplibre.org/maplibre-native/ios/api/predicates-and-expressions.html + */ + var predicate: NSPredicate? { get set } + + func predicate(_ predicate: NSPredicate) -> Self +} + +public extension SourceBoundVectorStyleLayerDefinition { + func predicate(_ predicate: NSPredicate) -> Self { + modified(self) { it in + it.predicate = predicate + } + } +} + extension SourceBoundStyleLayerDefinition { func addSource(to style: MLNStyle) -> MLNSource { let tmpSource: MLNSource diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift b/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift index 3319251..45338cf 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift @@ -5,8 +5,12 @@ import MapLibreSwiftMacros @MLNStyleProperty("iconRotation", supportsInterpolation: true) @MLNStyleProperty("iconColor", supportsInterpolation: true) +@MLNStyleProperty("textColor", supportsInterpolation: true) +@MLNStyleProperty("textFontSize", supportsInterpolation: true) +@MLNStyleProperty("text", supportsInterpolation: false) +@MLNStyleProperty("iconAllowsOverlap", supportsInterpolation: false) -public struct SymbolStyleLayer: SourceBoundStyleLayerDefinition { +public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public var insertionPosition: LayerInsertionPosition = .aboveOthers public var isVisible: Bool = true @@ -14,6 +18,7 @@ public struct SymbolStyleLayer: SourceBoundStyleLayerDefinition { public var minimumZoomLevel: Float? = nil public var source: StyleLayerSource + public var predicate: NSPredicate? public init(identifier: String, source: Source) { self.identifier = identifier @@ -61,12 +66,6 @@ public struct SymbolStyleLayer: SourceBoundStyleLayerDefinition { // it.iconImages = mappings.values + [defaultImage] // } // } - - public func iconRotation(expression: NSExpression) -> Self { - modified(self) { it in - it.iconRotation = expression - } - } } private struct SymbolStyleLayerInternal: StyleLayer { @@ -105,6 +104,13 @@ private struct SymbolStyleLayerInternal: StyleLayer { result.iconImageName = definition.iconImageName result.iconRotation = definition.iconRotation result.iconColor = definition.iconColor + result.text = definition.text + result.textColor = definition.textColor + result.textFontSize = definition.textFontSize + + result.iconAllowsOverlap = definition.iconAllowsOverlap + + result.predicate = definition.predicate return result } diff --git a/Sources/MapLibreSwiftUI/Examples/Layers.swift b/Sources/MapLibreSwiftUI/Examples/Layers.swift index 4c464ef..05b7013 100644 --- a/Sources/MapLibreSwiftUI/Examples/Layers.swift +++ b/Sources/MapLibreSwiftUI/Examples/Layers.swift @@ -21,6 +21,18 @@ let pointSource = ShapeSource(identifier: "points") { } } +@MainActor +let clustered = ShapeSource(identifier: "points", options: [.clustered: true, .clusterRadius: 44]) { + // Uses the DSL to quickly construct point features inline + MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.3719)) + + MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.3082, longitude: 16.3719)) + + MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.9719)) + + MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.0082, longitude: 17.9719)) +} + #Preview("Rose Tint") { MapView(styleURL: demoTilesURL) { // Silly example: a background layer on top of everything to create a tint effect @@ -76,6 +88,44 @@ let pointSource = ShapeSource(identifier: "points") { .ignoresSafeArea(.all) } +#Preview("Clustered Circles with Symbols") { + @State var camera = MapViewCamera.center( + CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.3719), + zoom: 5, + direction: 0 + ) + return MapView(styleURL: demoTilesURL, camera: $camera) { + // Clusters pins when they would touch + + // Cluster == YES shows only those pins that are clustered, using .text + CircleStyleLayer(identifier: "simple-circles-clusters", source: clustered) + .radius(16) + .color(.systemRed) + .strokeWidth(2) + .strokeColor(.white) + .predicate(NSPredicate(format: "cluster == YES")) + + SymbolStyleLayer(identifier: "simple-symbols-clusters", source: clustered) + .textColor(.white) + .text(expression: NSExpression(format: "CAST(point_count, 'NSString')")) + .predicate(NSPredicate(format: "cluster == YES")) + + // Cluster != YES shows only those pins that are not clustered, using an icon + CircleStyleLayer(identifier: "simple-circles-non-clusters", source: clustered) + .radius(16) + .color(.systemRed) + .strokeWidth(2) + .strokeColor(.white) + .predicate(NSPredicate(format: "cluster != YES")) + + SymbolStyleLayer(identifier: "simple-symbols-non-clusters", source: clustered) + .iconImage(UIImage(systemName: "mappin")!.withRenderingMode(.alwaysTemplate)) + .iconColor(.white) + .predicate(NSPredicate(format: "cluster != YES")) + } + .ignoresSafeArea(.all) +} + // TODO: Fixme // #Preview("Multiple Symbol Icons") { // MapView(styleURL: demoTilesURL) {