Skip to content

Commit ac9e5f2

Browse files
evil159pjleonard37
andauthored
Expose maxOverscaleFactorForParentTiles for custom geometry source (#2429)
Co-authored-by: Patrick Leonard <[email protected]>
1 parent bf0bb4b commit ac9e5f2

File tree

7 files changed

+250
-100
lines changed

7 files changed

+250
-100
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Mapbox welcomes participation and contributions from everyone.
44

55
## main
66

7-
* Added support for the `maxOverscaleFactorForParentTiles` property in `CustomRasterSource`, allowing greater control over tile overscaling behavior when rendering custom raster tiles.
7+
* Add support for the `maxOverscaleFactorForParentTiles` property in `CustomRasterSource` and `CustomGeometrySource`, allowing greater control over tile overscaling behavior when rendering custom raster tiles.
88

99
## 11.10.0-beta.1 - 20 January, 2025
1010

Examples.xcodeproj/project.pbxproj

Lines changed: 120 additions & 96 deletions
Large diffs are not rendered by default.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import SwiftUI
2+
import MapboxMaps
3+
4+
struct CustomGeometrySourceExample: View {
5+
@StateObject private var model = Model()
6+
7+
var body: some View {
8+
MapReader { proxy in
9+
Map {
10+
if let options = model.options {
11+
CustomGeometrySource(id: .customGeometrySource, options: options)
12+
LineLayer(id: "grid_layer", source: .customGeometrySource)
13+
.lineColor(.red)
14+
}
15+
}
16+
.ignoresSafeArea()
17+
.overlay(alignment: .bottom) {
18+
sliderPanel
19+
}
20+
.onAppear {
21+
model.options = model.makeCustomGeometrySourceOptions(for: proxy.map!)
22+
}
23+
.onChange(of: model.gridSpacing) { _ in
24+
try? proxy.map!.invalidateCustomGeometrySourceRegion(forSourceId: .customGeometrySource, bounds: .world)
25+
}
26+
}
27+
}
28+
29+
@ViewBuilder
30+
var sliderPanel: some View {
31+
VStack {
32+
Text("Grid Spacing: \(model.gridSpacing, specifier: "%.2f")")
33+
Slider(value: $model.gridSpacing, in: 0.01...10) {
34+
Text("Grid Spacing")
35+
} minimumValueLabel: {
36+
Image(systemName: "grid")
37+
.font(.system(size: 12))
38+
} maximumValueLabel: {
39+
Image(systemName: "grid")
40+
.font(.system(size: 24))
41+
}
42+
}
43+
.padding(10)
44+
.floating(RoundedRectangle(cornerRadius: 10))
45+
.limitPaneWidth()
46+
}
47+
}
48+
49+
private extension String {
50+
static let customGeometrySource = "custom-raster-source"
51+
}
52+
53+
private class Model: ObservableObject {
54+
@Published var gridSpacing: Double = 7
55+
@Published var options: CustomGeometrySourceOptions?
56+
57+
func makeCustomGeometrySourceOptions(for mapboxMap: MapboxMap) -> CustomGeometrySourceOptions {
58+
return CustomGeometrySourceOptions(
59+
fetchTileFunction: { [weak self] tileId in
60+
guard let self else { return }
61+
62+
let neighborTile = CanonicalTileID(z: tileId.z, x: tileId.x + 1, y: tileId.y + 1)
63+
let bounds = CoordinateBounds(
64+
southwest: CLLocationCoordinate2D(latitude: neighborTile.latitude, longitude: tileId.longitude),
65+
northeast: CLLocationCoordinate2D(latitude: tileId.latitude, longitude: neighborTile.longitude)
66+
)
67+
68+
let latFrom = ceil(bounds.northeast.latitude / gridSpacing) * gridSpacing
69+
let latTo = floor(bounds.southwest.latitude / gridSpacing) * gridSpacing
70+
let latLines = stride(from: latFrom, through: latTo, by: -gridSpacing).map { lat in
71+
LineString([
72+
CLLocationCoordinate2D(latitude: lat, longitude: bounds.southwest.longitude),
73+
CLLocationCoordinate2D(latitude: lat, longitude: bounds.northeast.longitude)
74+
])
75+
}
76+
77+
let lonFrom = floor(bounds.southwest.longitude / gridSpacing) * gridSpacing
78+
let lonTo = ceil(bounds.northeast.longitude / gridSpacing) * gridSpacing
79+
let lonLines = stride(from: lonFrom, through: lonTo, by: gridSpacing).map { lng in
80+
LineString([
81+
CLLocationCoordinate2D(latitude: bounds.southwest.latitude, longitude: lng),
82+
CLLocationCoordinate2D(latitude: bounds.northeast.latitude, longitude: lng)
83+
])
84+
}
85+
try! mapboxMap.setCustomGeometrySourceTileData(
86+
forSourceId: .customGeometrySource,
87+
tileId: tileId,
88+
features: (latLines + lonLines).map(Feature.init)
89+
)
90+
},
91+
cancelTileFunction: { _ in },
92+
tileOptions: TileOptions()
93+
)
94+
}
95+
}
96+
97+
extension CanonicalTileID {
98+
var latitude: CLLocationDegrees {
99+
let n = Double.pi - 2.0 * Double.pi * Double(y) / pow(2.0, Double(z))
100+
return (180.0 / .pi) * atan(0.5 * (exp(n) - exp(-n)))
101+
}
102+
103+
var longitude: CLLocationDegrees {
104+
return Double(x) / pow(2.0, Double(z)) * 360.0 - 180.0
105+
}
106+
}

Sources/Examples/SwiftUI Examples/SwiftUIRoot.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ struct SwiftUIRoot: View {
5454
}
5555
#endif
5656
ExampleLink("Precipitation", note: "Show show and rain", destination: PrecipitationExample())
57+
ExampleLink("Custom geometry", note: "Supply custom geometry to the map", destination: CustomGeometrySourceExample())
5758

5859
} header: { Text("Testing Examples") }
5960
}

Sources/MapboxMaps/Style/CustomSources/CustomGeometrySource.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ public struct CustomGeometrySource: Source {
1919
/// - Note: Current implementation does not take into account resources allocated by the visible tiles.
2020
public var tileCacheBudget: TileCacheBudgetSize?
2121

22-
public init(id: String, options: CustomGeometrySourceOptions) {
22+
/// When a set of tiles for a current zoom level is being rendered and some of the ideal tiles that cover the screen are not yet loaded, parent tiles could be used instead. Note that this might introduce unwanted rendering side-effects, especially for raster tiles that are overscaled multiple times. This property sets the maximum limit for how much a parent tile can be overscaled.
23+
@_documentation(visibility: public)
24+
public var maxOverscaleFactorForParentTiles: UInt8?
25+
26+
public init(id: String, options: CustomGeometrySourceOptions, maxOverscaleFactorForParentTiles: UInt8? = nil) {
2327
self.type = .customGeometry
2428
self.id = id
2529
self.options = options
30+
self.maxOverscaleFactorForParentTiles = maxOverscaleFactorForParentTiles
2631
}
2732
}
2833

@@ -31,6 +36,7 @@ extension CustomGeometrySource {
3136
case id
3237
case type
3338
case tileCacheBudget = "tile-cache-budget"
39+
case maxOverscaleFactorForParentTiles = "max-overscale-factor-for-parent-tiles"
3440
}
3541

3642
/// Init from a decoder, note that the CustomGeometrySourceOptions are not decodable and need to be set separately
@@ -40,6 +46,7 @@ extension CustomGeometrySource {
4046
type = try container.decode(SourceType.self, forKey: .type)
4147
tileCacheBudget = try container.decodeIfPresent(TileCacheBudgetSize.self, forKey: .tileCacheBudget)
4248
options = nil
49+
maxOverscaleFactorForParentTiles = try container.decodeIfPresent(UInt8.self, forKey: .maxOverscaleFactorForParentTiles)
4350
}
4451

4552
/// Encode, note that options will not be included
@@ -58,6 +65,7 @@ extension CustomGeometrySource {
5865

5966
private func encodeVolatile(to encoder: Encoder, into container: inout KeyedEncodingContainer<CodingKeys>) throws {
6067
try container.encodeIfPresent(tileCacheBudget, forKey: .tileCacheBudget)
68+
try container.encodeIfPresent(maxOverscaleFactorForParentTiles, forKey: .maxOverscaleFactorForParentTiles)
6169
}
6270

6371
private func encodeNonVolatile(to encoder: Encoder, into container: inout KeyedEncodingContainer<CodingKeys>) throws {

Tests/MapboxMapsTests/Style/CustomSourcesIntegrationTests.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,18 @@ final class CustomSourcesIntegrationTests: MapViewIntegrationTestCase {
5353
successfullyRetrievedSourceExpectation.expectedFulfillmentCount = 1
5454

5555
mapView.mapboxMap.styleURI = .standard
56+
let maxOverscaleFactorForParentTiles = UInt8(83)
5657

5758
didFinishLoadingStyle = { mapView in
58-
var source = CustomGeometrySource(id: "test-source", options: CustomGeometrySourceOptions(fetchTileFunction: { _ in }, cancelTileFunction: { _ in }, tileOptions: TileOptions()))
59+
var source = CustomGeometrySource(
60+
id: "test-source",
61+
options: CustomGeometrySourceOptions(
62+
fetchTileFunction: { _ in },
63+
cancelTileFunction: { _ in },
64+
tileOptions: TileOptions()
65+
),
66+
maxOverscaleFactorForParentTiles: maxOverscaleFactorForParentTiles
67+
)
5968
source.tileCacheBudget = .testSourceValue(.megabytes(7))
6069

6170
// Add source
@@ -70,6 +79,7 @@ final class CustomSourcesIntegrationTests: MapViewIntegrationTestCase {
7079
do {
7180
let retrievedSource = try mapView.mapboxMap.source(withId: "test-source", type: CustomGeometrySource.self)
7281
XCTAssertEqual(retrievedSource.tileCacheBudget, .testSourceValue(.megabytes(7)))
82+
XCTAssertEqual(retrievedSource.maxOverscaleFactorForParentTiles, maxOverscaleFactorForParentTiles)
7383

7484
successfullyRetrievedSourceExpectation.fulfill()
7585
} catch {

scripts/api-compatibility-check/breakage_allowlist.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2064,5 +2064,6 @@ Func StyleManager.removeSnow() is now with @_documentation
20642064
Func StyleManager.setRain(_:) is now with @_documentation
20652065
Func StyleManager.setSnow(_:) is now with @_documentation
20662066

2067-
# Add maxOverscaleFactorForParentTiles to CustomRasterSource constructor
2067+
# Add maxOverscaleFactorForParentTiles to CustomRasterSource and CustomGeometrySource constructors
20682068
Constructor CustomRasterSource.init(id:options:) has been removed
2069+
Constructor CustomGeometrySource.init(id:options:) has been removed

0 commit comments

Comments
 (0)