Skip to content

Commit

Permalink
Merge pull request #42 from HudHud-Maps/mapstyle-pois
Browse files Browse the repository at this point in the history
More SymbolLayer properties + sourceLayerIdentifier support + zoom levels + local image support
  • Loading branch information
ianthetechie authored Jul 31, 2024
2 parents 258a8c9 + 43b2fff commit e958f2e
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 25 deletions.
6 changes: 5 additions & 1 deletion Sources/MapLibreSwiftDSL/Style Layers/Circle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import MapLibreSwiftMacros
@MLNStyleProperty<UIColor>("strokeColor", supportsInterpolation: false)
public struct CircleStyleLayer: SourceBoundVectorStyleLayerDefinition {
public let identifier: String
public let sourceLayerIdentifier: String?
public var insertionPosition: LayerInsertionPosition = .aboveOthers
public var isVisible: Bool = true
public var maximumZoomLevel: Float? = nil
Expand All @@ -20,11 +21,13 @@ public struct CircleStyleLayer: SourceBoundVectorStyleLayerDefinition {
public init(identifier: String, source: Source) {
self.identifier = identifier
self.source = .source(source)
sourceLayerIdentifier = nil
}

public init(identifier: String, source: MLNSource) {
public init(identifier: String, source: MLNSource, sourceLayerIdentifier: String? = nil) {
self.identifier = identifier
self.source = .mglSource(source)
self.sourceLayerIdentifier = sourceLayerIdentifier
}

public func makeStyleLayer(style: MLNStyle) -> StyleLayer {
Expand Down Expand Up @@ -69,6 +72,7 @@ private struct CircleStyleLayerInternal: StyleLayer {
public func makeMLNStyleLayer() -> MLNStyleLayer {
let result = MLNCircleStyleLayer(identifier: identifier, source: mglSource)

result.sourceLayerIdentifier = definition.sourceLayerIdentifier
result.circleRadius = definition.radius
result.circleColor = definition.color

Expand Down
5 changes: 4 additions & 1 deletion Sources/MapLibreSwiftDSL/Style Layers/Line.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import MapLibreSwiftMacros
@MLNStyleProperty<Float>("lineWidth", supportsInterpolation: true)
public struct LineStyleLayer: SourceBoundVectorStyleLayerDefinition {
public let identifier: String
public let sourceLayerIdentifier: String?
public var insertionPosition: LayerInsertionPosition = .aboveOthers
public var isVisible: Bool = true
public var maximumZoomLevel: Float? = nil
Expand All @@ -21,11 +22,13 @@ public struct LineStyleLayer: SourceBoundVectorStyleLayerDefinition {
public init(identifier: String, source: Source) {
self.identifier = identifier
self.source = .source(source)
sourceLayerIdentifier = nil
}

public init(identifier: String, source: MLNSource) {
public init(identifier: String, source: MLNSource, sourceLayerIdentifier: String? = nil) {
self.identifier = identifier
self.source = .mglSource(source)
self.sourceLayerIdentifier = sourceLayerIdentifier
}

public func makeStyleLayer(style: MLNStyle) -> StyleLayer {
Expand Down
12 changes: 12 additions & 0 deletions Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public protocol StyleLayerDefinition {

public protocol SourceBoundStyleLayerDefinition: StyleLayerDefinition {
var source: StyleLayerSource { get set }

var sourceLayerIdentifier: String? { get }
}

/// Based on MLNVectorStyleLayer
Expand Down Expand Up @@ -161,3 +163,13 @@ public extension StyleLayer {
modified(self) { $0.insertionPosition = .belowOthers }
}
}

public extension StyleLayerDefinition {
func minimumZoomLevel(_ value: Float) -> Self {
modified(self) { $0.minimumZoomLevel = value }
}

func maximumZoomLevel(_ value: Float) -> Self {
modified(self) { $0.maximumZoomLevel = value }
}
}
87 changes: 69 additions & 18 deletions Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@ import MapLibreSwiftMacros

@MLNStyleProperty<Double>("iconRotation", supportsInterpolation: true)
@MLNStyleProperty<UIColor>("iconColor", supportsInterpolation: true)
@MLNStyleProperty<Bool>("iconAllowsOverlap", supportsInterpolation: false)

@MLNStyleProperty<UIColor>("textColor", supportsInterpolation: true)
@MLNStyleProperty<Double>("textFontSize", supportsInterpolation: true)
@MLNStyleProperty<String>("text", supportsInterpolation: false)
@MLNStyleProperty<Bool>("iconAllowsOverlap", supportsInterpolation: false)
// An enum would probably be better?
@MLNStyleProperty<String>("textAnchor", supportsInterpolation: false)
@MLNStyleProperty<CGVector>("textOffset", supportsInterpolation: true)
@MLNStyleProperty<Double>("maximumTextWidth", supportsInterpolation: true)

@MLNStyleProperty<UIColor>("textHaloColor", supportsInterpolation: true)
@MLNStyleProperty<Double>("textHaloWidth", supportsInterpolation: true)
@MLNStyleProperty<Double>("textHaloBlur", supportsInterpolation: true)

public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition {
public let identifier: String
public let sourceLayerIdentifier: String?
public var insertionPosition: LayerInsertionPosition = .aboveOthers
public var isVisible: Bool = true
public var maximumZoomLevel: Float? = nil
Expand All @@ -22,11 +32,13 @@ public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition {

public init(identifier: String, source: Source) {
self.identifier = identifier
sourceLayerIdentifier = nil
self.source = .source(source)
}

public init(identifier: String, source: MLNSource) {
public init(identifier: String, source: MLNSource, sourceLayerIdentifier: String? = nil) {
self.identifier = identifier
self.sourceLayerIdentifier = sourceLayerIdentifier
self.source = .mglSource(source)
}

Expand All @@ -40,10 +52,9 @@ public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition {
return SymbolStyleLayerInternal(definition: self, mglSource: styleSource)
}

// TODO: Other properties and their modifiers
fileprivate var iconImageName: NSExpression?
public var iconImageName: NSExpression?

private var iconImages = [UIImage]()
public var iconImages = [UIImage]()

// MARK: - Modifiers

Expand All @@ -54,18 +65,42 @@ public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition {
}
}

// FIXME: This appears to be broken upstream; waiting for a new release
// public func iconImage(attribute: String, mappings: [AnyHashable: UIImage], default defaultImage: UIImage) -> Self
// {
// return modified(self) { it in
// it.iconImageName = NSExpression(forMLNMatchingKey: NSExpression(forConstantValue: attribute),
// in: Dictionary(uniqueKeysWithValues: mappings.map({ (k, v) in
// (NSExpression(forConstantValue: k), NSExpression(forConstantValue: v.sha256()))
// })),
// default: NSExpression(forConstantValue: defaultImage.sha256()))
// it.iconImages = mappings.values + [defaultImage]
// }
// }
public func iconImage(featurePropertyNamed keyPath: String) -> Self {
var copy = self
copy.iconImageName = NSExpression(forKeyPath: keyPath)
return copy
}

/// Add an icon image that can be dynamic and use UIImages in your app, based on a feature property of the source.
/// For example, your feature could have a property called "icon-name". This name is then resolved against the key
/// in the mappings dictionary and used to find a UIImage to display on the map for that feature.
/// - Parameters:
/// - keyPath: The keypath to the feature property containing the icon to use, for example "icon-name".
/// - mappings: A lookup dictionary containing the keys found in "keyPath" and a UIImage for each keyPath. The key
/// of the mappings dictionary needs to match the value type stored at keyPath, for example `String`.
/// - defaultImage: A UIImage that MapLibre should fall back to if the key in your feature is not found in the
/// mappings table
public func iconImage(
featurePropertyNamed keyPath: String,
mappings: [AnyHashable: UIImage],
default defaultImage: UIImage
) -> Self {
modified(self) { it in
let attributeExpression = NSExpression(forKeyPath: keyPath)
let mappingExpressions = mappings.mapValues { image in
NSExpression(forConstantValue: image.sha256())
}
let mappingDictionary = NSDictionary(dictionary: mappingExpressions)
let defaultExpression = NSExpression(forConstantValue: defaultImage.sha256())

it.iconImageName = NSExpression(
forMLNMatchingKey: attributeExpression,
in: mappingDictionary as! [NSExpression: NSExpression],
default: defaultExpression
)
it.iconImages = mappings.values + [defaultImage]
}
}
}

private struct SymbolStyleLayerInternal: StyleLayer {
Expand Down Expand Up @@ -100,18 +135,34 @@ private struct SymbolStyleLayerInternal: StyleLayer {

public func makeMLNStyleLayer() -> MLNStyleLayer {
let result = MLNSymbolStyleLayer(identifier: identifier, source: mglSource)
result.sourceLayerIdentifier = definition.sourceLayerIdentifier

result.iconImageName = definition.iconImageName
result.iconRotation = definition.iconRotation
result.iconAllowsOverlap = definition.iconAllowsOverlap
result.iconColor = definition.iconColor

result.text = definition.text
result.textColor = definition.textColor
result.textFontSize = definition.textFontSize
result.maximumTextWidth = definition.maximumTextWidth
result.textAnchor = definition.textAnchor
result.textOffset = definition.textOffset

result.iconAllowsOverlap = definition.iconAllowsOverlap
result.textHaloColor = definition.textHaloColor
result.textHaloWidth = definition.textHaloWidth
result.textHaloBlur = definition.textHaloBlur

result.predicate = definition.predicate

if let minimumZoomLevel = definition.minimumZoomLevel {
result.minimumZoomLevel = minimumZoomLevel
}

if let maximumZoomLevel = definition.maximumZoomLevel {
result.maximumZoomLevel = maximumZoomLevel
}

return result
}
}
11 changes: 6 additions & 5 deletions Sources/MapLibreSwiftUI/Examples/Layers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,18 @@ let clustered = ShapeSource(identifier: "points", options: [.clustered: true, .c
.ignoresSafeArea(.all)
}

// TODO: Fixme
// This example does not work within a package? But it does work when in a real app
// #Preview("Multiple Symbol Icons") {
// MapView(styleURL: demoTilesURL) {
// // Simple symbol layer demonstration with an icon
// SymbolStyleLayer(identifier: "simple-symbols", source: pointSource)
// .iconImage(attribute: "icon",
// .iconImage(featurePropertyNamed: "icon",
// mappings: [
// "missing": UIImage(systemName: "mappin.slash")!,
// "club": UIImage(systemName: "figure.dance")!
// "missing": UIImage(systemName: "mappin.slash")!,
// "club": UIImage(systemName: "figure.dance")!,
// ],
// default: UIImage(systemName: "mappin")!)
// .iconColor(.red)
// }
// .edgesIgnoringSafeArea(.all)
// .ignoresSafeArea(.all)
// }

0 comments on commit e958f2e

Please sign in to comment.