Skip to content

Commit aa159ad

Browse files
authored
Merge pull request #36 from hactar/clustering
Adding clustering support and some text support
2 parents 3170249 + 9fa5dc6 commit aa159ad

File tree

8 files changed

+115
-22
lines changed

8 files changed

+115
-22
lines changed

Package.resolved

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let package = Package(
88
name: "MapLibreSwiftUI",
99
platforms: [
1010
.iOS(.v15),
11-
.macOS(.v11),
11+
.macOS(.v12),
1212
],
1313
products: [
1414
.library(
@@ -21,8 +21,8 @@ let package = Package(
2121
),
2222
],
2323
dependencies: [
24-
.package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.1.0"),
25-
.package(url: "https://github.com/stadiamaps/maplibre-swift-macros.git", from: "0.0.2"),
24+
.package(url: "https://github.com/maplibre/maplibre-gl-native-distribution.git", from: "6.4.0"),
25+
.package(url: "https://github.com/stadiamaps/maplibre-swift-macros.git", from: "0.0.3"),
2626
// Testing
2727
.package(url: "https://github.com/Kolos65/Mockable.git", exact: "0.0.3"),
2828
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.15.3"),

Sources/MapLibreSwiftDSL/ShapeDataBuilder.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,28 @@ public enum ShapeData {
2121

2222
public struct ShapeSource: Source {
2323
public let identifier: String
24+
public let options: [MLNShapeSourceOption: Any]?
2425
let data: ShapeData
2526

26-
public init(identifier: String, @ShapeDataBuilder _ makeShapeDate: () -> ShapeData) {
27+
public init(
28+
identifier: String,
29+
options: [MLNShapeSourceOption: Any]? = nil,
30+
@ShapeDataBuilder _ makeShapeDate: () -> ShapeData
31+
) {
2732
self.identifier = identifier
33+
self.options = options
2834
data = makeShapeDate()
2935
}
3036

3137
public func makeMGLSource() -> MLNSource {
3238
// TODO: Options! These should be represented via modifiers like .clustered()
3339
switch data {
3440
case let .geoJSONURL(url):
35-
MLNShapeSource(identifier: identifier, url: url)
41+
MLNShapeSource(identifier: identifier, url: url, options: options)
3642
case let .shapes(shapes):
37-
MLNShapeSource(identifier: identifier, shapes: shapes)
43+
MLNShapeSource(identifier: identifier, shapes: shapes, options: options)
3844
case let .features(features):
39-
MLNShapeSource(identifier: identifier, features: features)
45+
MLNShapeSource(identifier: identifier, features: features, options: options)
4046
}
4147
}
4248
}

Sources/MapLibreSwiftDSL/Style Layers/Circle.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import MapLibreSwiftMacros
77
@MLNStyleProperty<UIColor>("color", supportsInterpolation: false)
88
@MLNStyleProperty<Double>("strokeWidth", supportsInterpolation: true)
99
@MLNStyleProperty<UIColor>("strokeColor", supportsInterpolation: false)
10-
public struct CircleStyleLayer: SourceBoundStyleLayerDefinition {
10+
public struct CircleStyleLayer: SourceBoundVectorStyleLayerDefinition {
1111
public let identifier: String
1212
public var insertionPosition: LayerInsertionPosition = .aboveOthers
1313
public var isVisible: Bool = true
1414
public var maximumZoomLevel: Float? = nil
1515
public var minimumZoomLevel: Float? = nil
1616

1717
public var source: StyleLayerSource
18+
public var predicate: NSPredicate?
1819

1920
public init(identifier: String, source: Source) {
2021
self.identifier = identifier
@@ -74,6 +75,8 @@ private struct CircleStyleLayerInternal: StyleLayer {
7475
result.circleStrokeWidth = definition.strokeWidth
7576
result.circleStrokeColor = definition.strokeColor
7677

78+
result.predicate = definition.predicate
79+
7780
return result
7881
}
7982
}

Sources/MapLibreSwiftDSL/Style Layers/Line.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import MapLibreSwiftMacros
88
@MLNRawRepresentableStyleProperty<LineCap>("lineCap")
99
@MLNRawRepresentableStyleProperty<LineJoin>("lineJoin")
1010
@MLNStyleProperty<Float>("lineWidth", supportsInterpolation: true)
11-
public struct LineStyleLayer: SourceBoundStyleLayerDefinition {
11+
public struct LineStyleLayer: SourceBoundVectorStyleLayerDefinition {
1212
public let identifier: String
1313
public var insertionPosition: LayerInsertionPosition = .aboveOthers
1414
public var isVisible: Bool = true
1515
public var maximumZoomLevel: Float? = nil
1616
public var minimumZoomLevel: Float? = nil
1717

1818
public var source: StyleLayerSource
19+
public var predicate: NSPredicate?
1920

2021
public init(identifier: String, source: Source) {
2122
self.identifier = identifier
@@ -72,6 +73,8 @@ private struct LineStyleLayerInternal: StyleLayer {
7273
result.lineWidth = definition.lineWidth
7374
result.lineJoin = definition.lineJoin
7475

76+
result.predicate = definition.predicate
77+
7578
return result
7679
}
7780
}

Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@ public protocol SourceBoundStyleLayerDefinition: StyleLayerDefinition {
7777
var source: StyleLayerSource { get set }
7878
}
7979

80+
/// Based on MLNVectorStyleLayer
81+
public protocol SourceBoundVectorStyleLayerDefinition: SourceBoundStyleLayerDefinition {
82+
/**
83+
The style layer’s predicate.
84+
85+
Use the style layer’s predicate to include only the features in the source
86+
layer that satisfy a condition that you define.
87+
88+
See the *Predicates and Expressions*
89+
guide for details about the predicate syntax supported by this class:
90+
https://maplibre.org/maplibre-native/ios/api/predicates-and-expressions.html
91+
*/
92+
var predicate: NSPredicate? { get set }
93+
94+
func predicate(_ predicate: NSPredicate) -> Self
95+
}
96+
97+
public extension SourceBoundVectorStyleLayerDefinition {
98+
func predicate(_ predicate: NSPredicate) -> Self {
99+
modified(self) { it in
100+
it.predicate = predicate
101+
}
102+
}
103+
}
104+
80105
extension SourceBoundStyleLayerDefinition {
81106
func addSource(to style: MLNStyle) -> MLNSource {
82107
let tmpSource: MLNSource

Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import MapLibreSwiftMacros
55

66
@MLNStyleProperty<Double>("iconRotation", supportsInterpolation: true)
77
@MLNStyleProperty<UIColor>("iconColor", supportsInterpolation: true)
8+
@MLNStyleProperty<UIColor>("textColor", supportsInterpolation: true)
9+
@MLNStyleProperty<Double>("textFontSize", supportsInterpolation: true)
10+
@MLNStyleProperty<String>("text", supportsInterpolation: false)
11+
@MLNStyleProperty<Bool>("iconAllowsOverlap", supportsInterpolation: false)
812

9-
public struct SymbolStyleLayer: SourceBoundStyleLayerDefinition {
13+
public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition {
1014
public let identifier: String
1115
public var insertionPosition: LayerInsertionPosition = .aboveOthers
1216
public var isVisible: Bool = true
1317
public var maximumZoomLevel: Float? = nil
1418
public var minimumZoomLevel: Float? = nil
1519

1620
public var source: StyleLayerSource
21+
public var predicate: NSPredicate?
1722

1823
public init(identifier: String, source: Source) {
1924
self.identifier = identifier
@@ -61,12 +66,6 @@ public struct SymbolStyleLayer: SourceBoundStyleLayerDefinition {
6166
// it.iconImages = mappings.values + [defaultImage]
6267
// }
6368
// }
64-
65-
public func iconRotation(expression: NSExpression) -> Self {
66-
modified(self) { it in
67-
it.iconRotation = expression
68-
}
69-
}
7069
}
7170

7271
private struct SymbolStyleLayerInternal: StyleLayer {
@@ -105,6 +104,13 @@ private struct SymbolStyleLayerInternal: StyleLayer {
105104
result.iconImageName = definition.iconImageName
106105
result.iconRotation = definition.iconRotation
107106
result.iconColor = definition.iconColor
107+
result.text = definition.text
108+
result.textColor = definition.textColor
109+
result.textFontSize = definition.textFontSize
110+
111+
result.iconAllowsOverlap = definition.iconAllowsOverlap
112+
113+
result.predicate = definition.predicate
108114

109115
return result
110116
}

Sources/MapLibreSwiftUI/Examples/Layers.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ let pointSource = ShapeSource(identifier: "points") {
2121
}
2222
}
2323

24+
@MainActor
25+
let clustered = ShapeSource(identifier: "points", options: [.clustered: true, .clusterRadius: 44]) {
26+
// Uses the DSL to quickly construct point features inline
27+
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.3719))
28+
29+
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.3082, longitude: 16.3719))
30+
31+
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.9719))
32+
33+
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.0082, longitude: 17.9719))
34+
}
35+
2436
#Preview("Rose Tint") {
2537
MapView(styleURL: demoTilesURL) {
2638
// Silly example: a background layer on top of everything to create a tint effect
@@ -76,6 +88,44 @@ let pointSource = ShapeSource(identifier: "points") {
7688
.ignoresSafeArea(.all)
7789
}
7890

91+
#Preview("Clustered Circles with Symbols") {
92+
@State var camera = MapViewCamera.center(
93+
CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.3719),
94+
zoom: 5,
95+
direction: 0
96+
)
97+
return MapView(styleURL: demoTilesURL, camera: $camera) {
98+
// Clusters pins when they would touch
99+
100+
// Cluster == YES shows only those pins that are clustered, using .text
101+
CircleStyleLayer(identifier: "simple-circles-clusters", source: clustered)
102+
.radius(16)
103+
.color(.systemRed)
104+
.strokeWidth(2)
105+
.strokeColor(.white)
106+
.predicate(NSPredicate(format: "cluster == YES"))
107+
108+
SymbolStyleLayer(identifier: "simple-symbols-clusters", source: clustered)
109+
.textColor(.white)
110+
.text(expression: NSExpression(format: "CAST(point_count, 'NSString')"))
111+
.predicate(NSPredicate(format: "cluster == YES"))
112+
113+
// Cluster != YES shows only those pins that are not clustered, using an icon
114+
CircleStyleLayer(identifier: "simple-circles-non-clusters", source: clustered)
115+
.radius(16)
116+
.color(.systemRed)
117+
.strokeWidth(2)
118+
.strokeColor(.white)
119+
.predicate(NSPredicate(format: "cluster != YES"))
120+
121+
SymbolStyleLayer(identifier: "simple-symbols-non-clusters", source: clustered)
122+
.iconImage(UIImage(systemName: "mappin")!.withRenderingMode(.alwaysTemplate))
123+
.iconColor(.white)
124+
.predicate(NSPredicate(format: "cluster != YES"))
125+
}
126+
.ignoresSafeArea(.all)
127+
}
128+
79129
// TODO: Fixme
80130
// #Preview("Multiple Symbol Icons") {
81131
// MapView(styleURL: demoTilesURL) {

0 commit comments

Comments
 (0)