Skip to content

Commit 34313b2

Browse files
authored
Image expression options (#2369)
1 parent c2bb275 commit 34313b2

File tree

8 files changed

+119
-9
lines changed

8 files changed

+119
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ In order to continue use them use the following import `@_spi(Experimental) impo
1818
That initializer doesn't require to wrap arguments in `Argument` cases. For example, `Exp(.eq, Exp(.get, "extrude"), "true")`.
1919
* Expose a `TileStore/clearAmbientCache()` method to clear ambient cache.
2020
* Add new experimental `radius` parameter to `TapInteraction`, `LongPressInteraction` and interaction managers to control the radius of a tappable area.
21+
* Add a way to specify image expression options.
22+
* Bump core maps version to 11.9.0-beta.1 and common sdk to 24.9.0-beta.1
2123

2224
## 11.8.0 - 11 November, 2024
2325

LICENSE.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MapboxMaps.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ Pod::Spec.new do |m|
2121
m.source_files = 'Sources/MapboxMaps/**/*.{swift,h}'
2222
m.resource_bundles = { 'MapboxMapsResources' => ['Sources/**/*.{xcassets,strings}', 'Sources/MapboxMaps/MapboxMaps.json', 'Sources/MapboxMaps/PrivacyInfo.xcprivacy'] }
2323

24-
m.dependency 'MapboxCoreMaps', '11.9.0-SNAPSHOT.1122T1014Z.b9d442a'
25-
m.dependency 'MapboxCommon', '24.9.0-SNAPSHOT.1112T0225Z.a361369'
24+
m.dependency 'MapboxCoreMaps', '11.9.0-beta.1'
25+
m.dependency 'MapboxCommon', '24.9.0-beta.1'
2626
m.dependency 'Turf', '4.0.0-beta.1'
2727

2828
end

Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import PackageDescription
55
import Foundation
66

7-
let coreMaps = MapsDependency.coreMaps(version: "11.9.0-SNAPSHOT.1122T1014Z.b9d442a", checksum: "ec2407a32601b0fca1c36fffb10707214473091e8bab2992f74fd2185a480f83")
8-
let common = MapsDependency.common(version: "24.9.0-SNAPSHOT.1112T0225Z.a361369", checksum: "e828f210cc591daf087206066d5117c0d4c10e8ec07c233ab1cd8d800a834a63")
7+
let coreMaps = MapsDependency.coreMaps(version: "11.9.0-beta.1")
8+
9+
let common = MapsDependency.common(version: "24.9.0-beta.1")
910

1011
let mapboxMapsPath: String? = nil
1112

Sources/MapboxMaps/Documentation.docc/API Catalogs/Expressions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
- ``FormatOptions``
1010
- ``NumberFormatOptions``
1111
- ``CollatorOptions``
12+
- ``ImageOptions``
1213
- ``ExpressionArgumentConvertible``
1314
- ``ExpressionArgumentBuilder``

Sources/MapboxMaps/Style/Types/ExpressionOptions.swift

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import Foundation
22

3+
private enum ExpDecodingError: Error {
4+
case noKeysFound
5+
}
36
extension Exp {
47

58
public enum Option: Codable, Equatable, Sendable {
69
case format(FormatOptions)
710
case numberFormat(NumberFormatOptions)
811
case collator(CollatorOptions)
12+
case image(ImageOptions)
913

1014
public init(from decoder: Decoder) throws {
1115
let container = try decoder.singleValueContainer()
1216

13-
if let validFormatOptions = try? container.decode(FormatOptions.self) {
14-
self = .format(validFormatOptions)
17+
if let validImageOptions = try? container.decode(ImageOptions.self) {
18+
self = .image(validImageOptions)
1519

1620
} else if let validNumberFormatOptions = try? container.decode(NumberFormatOptions.self) {
1721
self = .numberFormat(validNumberFormatOptions)
1822

23+
} else if let validFormatOptions = try? container.decode(FormatOptions.self) {
24+
self = .format(validFormatOptions)
25+
1926
} else if let validCollatorOptions = try? container.decode(CollatorOptions.self) {
2027
self = .collator(validCollatorOptions)
2128

@@ -36,6 +43,8 @@ extension Exp {
3643
try container.encode(option)
3744
case .numberFormat(let option):
3845
try container.encode(option)
46+
case .image(let option):
47+
try container.encode(option)
3948
}
4049
}
4150

@@ -104,6 +113,19 @@ public struct NumberFormatOptions: Codable, Equatable, ExpressionArgumentConvert
104113
self.minFractionDigits = minFractionDigits
105114
self.maxFractionDigits = maxFractionDigits
106115
}
116+
117+
public init(from decoder: Decoder) throws {
118+
let container = try decoder.container(keyedBy: CodingKeys.self)
119+
120+
guard container.containsAnyKey() else {
121+
throw ExpDecodingError.noKeysFound
122+
}
123+
124+
locale = try container.decodeIfPresent(String.self, forKey: .locale)
125+
currency = try container.decodeIfPresent(String.self, forKey: .currency)
126+
minFractionDigits = try container.decodeIfPresent(Int.self, forKey: .minFractionDigits)
127+
maxFractionDigits = try container.decodeIfPresent(Int.self, forKey: .maxFractionDigits)
128+
}
107129
}
108130

109131
public struct CollatorOptions: Codable, Equatable, ExpressionArgumentConvertible, Sendable {
@@ -133,5 +155,39 @@ public struct CollatorOptions: Codable, Equatable, ExpressionArgumentConvertible
133155
self.diacriticSensitive = diacriticSensitive
134156
self.locale = locale
135157
}
158+
}
159+
160+
/// Image options container.
161+
public struct ImageOptions: Codable, Equatable, ExpressionArgumentConvertible, Sendable {
162+
public typealias ColorLike = Value<StyleColor>
163+
164+
/// Vector image parameters.
165+
public var options: [String: ColorLike]
166+
167+
public var expressionArguments: [Exp.Argument] {
168+
return [.option(.image(self))]
169+
}
170+
171+
public init(_ options: [String: ColorLike]) {
172+
self.options = options
173+
}
174+
175+
enum CodingKeys: String, CodingKey {
176+
case options = "params"
177+
}
178+
179+
public init(from decoder: any Decoder) throws {
180+
let container = try decoder.container(keyedBy: CodingKeys.self)
181+
182+
guard container.containsAnyKey() else {
183+
throw ExpDecodingError.noKeysFound
184+
}
185+
186+
options = try container.decode([String: ColorLike].self, forKey: .options)
187+
}
188+
}
189+
190+
extension KeyedDecodingContainer {
191+
func containsAnyKey() -> Bool { allKeys.contains(where: contains) }
136192

137193
}

Tests/MapboxMapsTests/Style/ExpressionTests/ExpressionTests.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,54 @@ final class ExpressionTests: XCTestCase {
326326

327327
XCTAssertNotEqual(shortFormExpression, longFormExpression)
328328
}
329+
330+
func testEncodeImageOptions() throws {
331+
let color = StyleColor(UIColor.black)
332+
let empty = ImageOptions([:])
333+
let constantValues = ImageOptions([
334+
"key": .constant(StyleColor("red")),
335+
"key2": .constant(color)
336+
337+
])
338+
let expression = Expression(.toRgba, Exp(.get, "color"))
339+
let expression2 = Exp(.switchCase) {
340+
Exp(.gte) {
341+
Exp(.toNumber) {
342+
Exp(.get) { "point_count" }
343+
}
344+
4
345+
}
346+
"#ffffff"
347+
"#000000"
348+
}
349+
let expressionValues = ImageOptions([
350+
"key": .expression(expression),
351+
"key2": .expression(expression2)
352+
])
353+
354+
let emptyEncoded = try DictionaryEncoder().encode(empty)
355+
let constantValuesEncoded = try DictionaryEncoder().encode(constantValues)
356+
let expressionValuesParamsEncoded = try DictionaryEncoder().encode(expressionValues)["params"] as? [String: Any]
357+
358+
XCTAssertEqual(emptyEncoded["params"] as? [String: String], [:])
359+
XCTAssertEqual(constantValuesEncoded["params"] as? [String: String], ["key": "red", "key2": color.rawValue])
360+
XCTAssertEqual(
361+
String(data: try JSONSerialization.data(withJSONObject: expressionValuesParamsEncoded?["key"] as Any), encoding: .utf8),
362+
##"["to-rgba",["get","color"]]"##
363+
)
364+
XCTAssertEqual(
365+
String(data: try JSONSerialization.data(withJSONObject: expressionValuesParamsEncoded?["key2"] as Any), encoding: .utf8),
366+
##"["case",[">=",["to-number",["get","point_count"]],4],"#ffffff","#000000"]"##
367+
)
368+
}
369+
370+
func testDecodeImageOptions() throws {
371+
let jsonString = #"{"params": {"expression": ["get", "color"], "constant": "red", "rgb": "rgba(0, 0, 0, 1)"}}"#
372+
373+
let imageOptions = try JSONDecoder().decode(ImageOptions.self, from: try XCTUnwrap(jsonString.data(using: .utf8)))
374+
375+
XCTAssertEqual(imageOptions.options["expression"], Value.expression(Exp(.get) { "color" }))
376+
XCTAssertEqual(imageOptions.options["constant"], Value.constant(StyleColor(rawValue: "red")))
377+
XCTAssertEqual(imageOptions.options["rgb"], Value.constant(StyleColor("rgba(0, 0, 0, 1)")))
378+
}
329379
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"MapboxCoreMaps": "11.9.0-SNAPSHOT.1122T1014Z.b9d442a",
3-
"MapboxCommon": "24.9.0-SNAPSHOT.1112T0225Z.a361369",
2+
"MapboxCoreMaps": "11.9.0-beta.1",
3+
"MapboxCommon": "24.9.0-beta.1",
44
"Turf": "4.0.0-beta.1"
55
}

0 commit comments

Comments
 (0)