Skip to content

Commit

Permalink
Merge pull request #36 from hactar/clustering
Browse files Browse the repository at this point in the history
Adding clustering support and some text support
  • Loading branch information
ianthetechie authored May 3, 2024
2 parents 3170249 + 9fa5dc6 commit aa159ad
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 22 deletions.
12 changes: 6 additions & 6 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
"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"
}
},
{
"identity" : "maplibre-swift-macros",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stadiamaps/maplibre-swift-macros.git",
"state" : {
"revision" : "9f15cbb11d2b5248ead47aecae5be8a1d4d5f463",
"version" : "0.0.2"
"revision" : "d52adbcbfaf96bd0723a156bd838826916ff7a69",
"version" : "0.0.3"
}
},
{
Expand All @@ -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"
}
},
{
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ let package = Package(
name: "MapLibreSwiftUI",
platforms: [
.iOS(.v15),
.macOS(.v11),
.macOS(.v12),
],
products: [
.library(
Expand All @@ -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"),
Expand Down
14 changes: 10 additions & 4 deletions Sources/MapLibreSwiftDSL/ShapeDataBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,28 @@ 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()
}

public func makeMGLSource() -> MLNSource {
// 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)
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/MapLibreSwiftDSL/Style Layers/Circle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import MapLibreSwiftMacros
@MLNStyleProperty<UIColor>("color", supportsInterpolation: false)
@MLNStyleProperty<Double>("strokeWidth", supportsInterpolation: true)
@MLNStyleProperty<UIColor>("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
public var maximumZoomLevel: Float? = nil
public var minimumZoomLevel: Float? = nil

public var source: StyleLayerSource
public var predicate: NSPredicate?

public init(identifier: String, source: Source) {
self.identifier = identifier
Expand Down Expand Up @@ -74,6 +75,8 @@ private struct CircleStyleLayerInternal: StyleLayer {
result.circleStrokeWidth = definition.strokeWidth
result.circleStrokeColor = definition.strokeColor

result.predicate = definition.predicate

return result
}
}
5 changes: 4 additions & 1 deletion Sources/MapLibreSwiftDSL/Style Layers/Line.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import MapLibreSwiftMacros
@MLNRawRepresentableStyleProperty<LineCap>("lineCap")
@MLNRawRepresentableStyleProperty<LineJoin>("lineJoin")
@MLNStyleProperty<Float>("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
public var maximumZoomLevel: Float? = nil
public var minimumZoomLevel: Float? = nil

public var source: StyleLayerSource
public var predicate: NSPredicate?

public init(identifier: String, source: Source) {
self.identifier = identifier
Expand Down Expand Up @@ -72,6 +73,8 @@ private struct LineStyleLayerInternal: StyleLayer {
result.lineWidth = definition.lineWidth
result.lineJoin = definition.lineJoin

result.predicate = definition.predicate

return result
}
}
25 changes: 25 additions & 0 deletions Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import MapLibreSwiftMacros

@MLNStyleProperty<Double>("iconRotation", supportsInterpolation: true)
@MLNStyleProperty<UIColor>("iconColor", supportsInterpolation: true)
@MLNStyleProperty<UIColor>("textColor", supportsInterpolation: true)
@MLNStyleProperty<Double>("textFontSize", supportsInterpolation: true)
@MLNStyleProperty<String>("text", supportsInterpolation: false)
@MLNStyleProperty<Bool>("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
public var maximumZoomLevel: Float? = nil
public var minimumZoomLevel: Float? = nil

public var source: StyleLayerSource
public var predicate: NSPredicate?

public init(identifier: String, source: Source) {
self.identifier = identifier
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down
50 changes: 50 additions & 0 deletions Sources/MapLibreSwiftUI/Examples/Layers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit aa159ad

Please sign in to comment.