From 1df592c9e1d80b1ad5ec92be4c3cbba276b60d2c Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 21 Jun 2024 17:30:42 +0200 Subject: [PATCH] Add line style layer example to iOS docs (#2525) --- platform/ios/CONTRIBUTING.md | 10 - .../LineStyleLayerExampleUIKit.md | 108 ++++++ .../MapLibre.docc/LocationPrivacyExample.md | 12 +- platform/ios/MapLibre.docc/MapLibre.md | 1 + platform/ios/app-swift/BUILD.bazel | 3 + platform/ios/app-swift/Data/example.geojson | 308 ++++++++++++++++++ .../Sources/LineStyleLayerExampleUIKit.swift | 113 +++++++ .../Sources/MapLibreNavigationView.swift | 3 + 8 files changed, 542 insertions(+), 16 deletions(-) create mode 100644 platform/ios/MapLibre.docc/LineStyleLayerExampleUIKit.md create mode 100644 platform/ios/app-swift/Data/example.geojson create mode 100644 platform/ios/app-swift/Sources/LineStyleLayerExampleUIKit.swift diff --git a/platform/ios/CONTRIBUTING.md b/platform/ios/CONTRIBUTING.md index 51104902c..17d1f2b6a 100644 --- a/platform/ios/CONTRIBUTING.md +++ b/platform/ios/CONTRIBUTING.md @@ -108,16 +108,6 @@ If you want to get direct access you need an AWS account to get permissions to u aws sts get-caller-identity ``` -When the needed resources are uploaded the filenames need to be added to the `resources` array in `platform/ios/scripts/docc.sh`. - -```bash -resources=( - "AddPackageDependencies@2x.png" - # ... - # add here -) -``` - ### Examples The code samples in the documentation should ideally be compiled on CI so they do not go out of date. diff --git a/platform/ios/MapLibre.docc/LineStyleLayerExampleUIKit.md b/platform/ios/MapLibre.docc/LineStyleLayerExampleUIKit.md new file mode 100644 index 000000000..8ad3d9159 --- /dev/null +++ b/platform/ios/MapLibre.docc/LineStyleLayerExampleUIKit.md @@ -0,0 +1,108 @@ +# Using GeoJSON with a line style layer (UIKit) + +Adding an ``MLNLineStyleLayer`` to the map using a GeoJSON file. + + + +```swift +class LineStyleLayerExampleUIKit: UIViewController, MLNMapViewDelegate { + var mapView: MLNMapView! + + override func viewDidLoad() { + super.viewDidLoad() + + mapView = MLNMapView(frame: view.bounds, styleURL: VERSATILES_COLORFUL_STYLE) + mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + mapView.setCenter( + CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736), + zoomLevel: 11, + animated: false + ) + view.addSubview(mapView) + + mapView.delegate = self + } + + // Wait until the map is loaded before adding to the map. + func mapView(_: MLNMapView, didFinishLoading _: MLNStyle) { + loadGeoJson() + } + + func loadGeoJson() { + DispatchQueue.global().async { + // Get the path for example.geojson in the app’s bundle. + guard let jsonUrl = Bundle.main.url(forResource: "example", withExtension: "geojson") else { + preconditionFailure("Failed to load local GeoJSON file") + } + + guard let jsonData = try? Data(contentsOf: jsonUrl) else { + preconditionFailure("Failed to parse GeoJSON file") + } + + DispatchQueue.main.async { + self.drawPolyline(geoJson: jsonData) + } + } + } + + func drawPolyline(geoJson: Data) { + // Add our GeoJSON data to the map as an MLNGeoJSONSource. + // We can then reference this data from an MLNStyleLayer. + + // MLNMapView.style is optional, so you must guard against it not being set. + guard let style = mapView.style else { return } + + guard let shapeFromGeoJSON = try? MLNShape(data: geoJson, encoding: String.Encoding.utf8.rawValue) else { + fatalError("Could not generate MLNShape") + } + + let source = MLNShapeSource(identifier: "polyline", shape: shapeFromGeoJSON, options: nil) + style.addSource(source) + + // Create new layer for the line. + let layer = MLNLineStyleLayer(identifier: "polyline", source: source) + + // Set the line join and cap to a rounded end. + layer.lineJoin = NSExpression(forConstantValue: "round") + layer.lineCap = NSExpression(forConstantValue: "round") + + // Set the line color to a constant blue color. + layer.lineColor = NSExpression(forConstantValue: UIColor(red: 59 / 255, green: 178 / 255, blue: 208 / 255, alpha: 1)) + + // Use `NSExpression` to smoothly adjust the line width from 2pt to 20pt between zoom levels 14 and 18. The `interpolationBase` parameter allows the values to interpolate along an exponential curve. + layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + [14: 2, 18: 20]) + + // We can also add a second layer that will draw a stroke around the original line. + let casingLayer = MLNLineStyleLayer(identifier: "polyline-case", source: source) + // Copy these attributes from the main line layer. + casingLayer.lineJoin = layer.lineJoin + casingLayer.lineCap = layer.lineCap + // Line gap width represents the space before the outline begins, so should match the main line’s line width exactly. + casingLayer.lineGapWidth = layer.lineWidth + // Stroke color slightly darker than the line color. + casingLayer.lineColor = NSExpression(forConstantValue: UIColor(red: 41 / 255, green: 145 / 255, blue: 171 / 255, alpha: 1)) + // Use `NSExpression` to gradually increase the stroke width between zoom levels 14 and 18. + casingLayer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", [14: 1, 18: 4]) + + // Just for fun, let’s add another copy of the line with a dash pattern. + let dashedLayer = MLNLineStyleLayer(identifier: "polyline-dash", source: source) + dashedLayer.lineJoin = layer.lineJoin + dashedLayer.lineCap = layer.lineCap + dashedLayer.lineColor = NSExpression(forConstantValue: UIColor.white) + dashedLayer.lineOpacity = NSExpression(forConstantValue: 0.5) + dashedLayer.lineWidth = layer.lineWidth + // Dash pattern in the format [dash, gap, dash, gap, ...]. You’ll want to adjust these values based on the line cap style. + dashedLayer.lineDashPattern = NSExpression(forConstantValue: [0, 1.5]) + + style.addLayer(layer) + style.addLayer(dashedLayer) + style.insertLayer(casingLayer, below: layer) + } +} +``` + +![](LineStyleLayerExample.png) + +Map Data from OpenStreetMap. \ No newline at end of file diff --git a/platform/ios/MapLibre.docc/LocationPrivacyExample.md b/platform/ios/MapLibre.docc/LocationPrivacyExample.md index f78d403b3..2d40600a3 100644 --- a/platform/ios/MapLibre.docc/LocationPrivacyExample.md +++ b/platform/ios/MapLibre.docc/LocationPrivacyExample.md @@ -58,17 +58,17 @@ class PrivacyExampleViewModel: NSObject, ObservableObject { class PrivacyExampleCoordinator: NSObject, MLNMapViewDelegate { @ObservedObject private var mapViewModel: PrivacyExampleViewModel private var pannedToUserLocation = false - + init(mapViewModel: PrivacyExampleViewModel) { self.mapViewModel = mapViewModel super.init() } - + @MainActor func mapView(_: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) { guard let accuracySetting = manager.accuracyAuthorization else { return } - + switch accuracySetting() { case .fullAccuracy: mapViewModel.locationAccuracy = .fullAccuracy @@ -93,14 +93,14 @@ class PrivacyExampleCoordinator: NSObject, MLNMapViewDelegate { struct PrivacyExampleRepresentable: UIViewRepresentable { @ObservedObject var mapViewModel: PrivacyExampleViewModel - + func makeCoordinator() -> PrivacyExampleCoordinator { PrivacyExampleCoordinator(mapViewModel: mapViewModel) } func makeUIView(context: Context) -> MLNMapView { let mapView = MLNMapView() - + mapView.delegate = context.coordinator mapView.showsUserLocation = true return mapView @@ -119,7 +119,7 @@ struct PrivacyExampleRepresentable: UIViewRepresentable { struct LocationPrivacyExampleView: View { @StateObject private var viewModel = PrivacyExampleViewModel() - + var body: some View { VStack { PrivacyExampleRepresentable(mapViewModel: viewModel) diff --git a/platform/ios/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md index 6334e24e7..2d95991da 100644 --- a/platform/ios/MapLibre.docc/MapLibre.md +++ b/platform/ios/MapLibre.docc/MapLibre.md @@ -17,6 +17,7 @@ Powerful, free and open-source mapping toolkit with full control over data sourc - - - +- ## Topics diff --git a/platform/ios/app-swift/BUILD.bazel b/platform/ios/app-swift/BUILD.bazel index 92c4999c2..bcbff495c 100644 --- a/platform/ios/app-swift/BUILD.bazel +++ b/platform/ios/app-swift/BUILD.bazel @@ -27,6 +27,9 @@ ios_application( infoplists = [":Info.plist"], minimum_os_version = "16.0", provisioning_profile = "xcode_profile", + resources = glob([ + "Data/*", + ]), visibility = ["@rules_xcodeproj//xcodeproj:generated"], deps = [":Sources"], ) diff --git a/platform/ios/app-swift/Data/example.geojson b/platform/ios/app-swift/Data/example.geojson new file mode 100644 index 000000000..a8df362ba --- /dev/null +++ b/platform/ios/app-swift/Data/example.geojson @@ -0,0 +1,308 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "name": "Crema to Council Crest" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -122.63748, + 45.52214 + ], + [ + -122.64855, + 45.52218 + ], + [ + -122.6545, + 45.52219 + ], + [ + -122.65497, + 45.52196 + ], + [ + -122.65631, + 45.52104 + ], + [ + -122.6578, + 45.51935 + ], + [ + -122.65867, + 45.51848 + ], + [ + -122.65872, + 45.51293 + ], + [ + -122.66576, + 45.51295 + ], + [ + -122.66745, + 45.51252 + ], + [ + -122.66813, + 45.51244 + ], + [ + -122.67359, + 45.51385 + ], + [ + -122.67415, + 45.51406 + ], + [ + -122.67481, + 45.51484 + ], + [ + -122.676, + 45.51532 + ], + [ + -122.68106, + 45.51668 + ], + [ + -122.68503, + 45.50934 + ], + [ + -122.68546, + 45.50858 + ], + [ + -122.6852, + 45.50783 + ], + [ + -122.68424, + 45.50714 + ], + [ + -122.68433, + 45.50585 + ], + [ + -122.68429, + 45.50521 + ], + [ + -122.68456, + 45.50445 + ], + [ + -122.68538, + 45.50371 + ], + [ + -122.68653, + 45.50311 + ], + [ + -122.68731, + 45.50292 + ], + [ + -122.68742, + 45.50253 + ], + [ + -122.6867, + 45.50239 + ], + [ + -122.68545, + 45.5026 + ], + [ + -122.68407, + 45.50294 + ], + [ + -122.68357, + 45.50271 + ], + [ + -122.68236, + 45.50055 + ], + [ + -122.68233, + 45.49994 + ], + [ + -122.68267, + 45.49955 + ], + [ + -122.68257, + 45.49919 + ], + [ + -122.68376, + 45.49842 + ], + [ + -122.68428, + 45.49821 + ], + [ + -122.68573, + 45.49798 + ], + [ + -122.68923, + 45.49805 + ], + [ + -122.68926, + 45.49857 + ], + [ + -122.68814, + 45.49911 + ], + [ + -122.68865, + 45.49921 + ], + [ + -122.6897, + 45.49905 + ], + [ + -122.69346, + 45.49917 + ], + [ + -122.69404, + 45.49902 + ], + [ + -122.69438, + 45.49796 + ], + [ + -122.69504, + 45.49697 + ], + [ + -122.69624, + 45.49661 + ], + [ + -122.69781, + 45.4955 + ], + [ + -122.69803, + 45.49517 + ], + [ + -122.69711, + 45.49508 + ], + [ + -122.69688, + 45.4948 + ], + [ + -122.69744, + 45.49368 + ], + [ + -122.69702, + 45.49311 + ], + [ + -122.69665, + 45.49294 + ], + [ + -122.69788, + 45.49212 + ], + [ + -122.69771, + 45.49264 + ], + [ + -122.69835, + 45.49332 + ], + [ + -122.7007, + 45.49334 + ], + [ + -122.70167, + 45.49358 + ], + [ + -122.70215, + 45.49401 + ], + [ + -122.70229, + 45.49439 + ], + [ + -122.70185, + 45.49566 + ], + [ + -122.70215, + 45.49635 + ], + [ + -122.70346, + 45.49674 + ], + [ + -122.70517, + 45.49758 + ], + [ + -122.70614, + 45.49736 + ], + [ + -122.70663, + 45.49736 + ], + [ + -122.70807, + 45.49767 + ], + [ + -122.70807, + 45.49798 + ], + [ + -122.70717, + 45.49798 + ], + [ + -122.70713, + 45.4984 + ], + [ + -122.70774, + 45.49893 + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/platform/ios/app-swift/Sources/LineStyleLayerExampleUIKit.swift b/platform/ios/app-swift/Sources/LineStyleLayerExampleUIKit.swift new file mode 100644 index 000000000..fd8de6076 --- /dev/null +++ b/platform/ios/app-swift/Sources/LineStyleLayerExampleUIKit.swift @@ -0,0 +1,113 @@ +import MapLibre +import SwiftUI +import UIKit + +// #-example-code(LineStyleLayerExampleUIKit) +class LineStyleLayerExampleUIKit: UIViewController, MLNMapViewDelegate { + var mapView: MLNMapView! + + override func viewDidLoad() { + super.viewDidLoad() + + mapView = MLNMapView(frame: view.bounds, styleURL: VERSATILES_COLORFUL_STYLE) + mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + mapView.setCenter( + CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736), + zoomLevel: 11, + animated: false + ) + view.addSubview(mapView) + + mapView.delegate = self + } + + // Wait until the map is loaded before adding to the map. + func mapView(_: MLNMapView, didFinishLoading _: MLNStyle) { + loadGeoJson() + } + + func loadGeoJson() { + DispatchQueue.global().async { + // Get the path for example.geojson in the app’s bundle. + guard let jsonUrl = Bundle.main.url(forResource: "example", withExtension: "geojson") else { + preconditionFailure("Failed to load local GeoJSON file") + } + + guard let jsonData = try? Data(contentsOf: jsonUrl) else { + preconditionFailure("Failed to parse GeoJSON file") + } + + DispatchQueue.main.async { + self.drawPolyline(geoJson: jsonData) + } + } + } + + func drawPolyline(geoJson: Data) { + // Add our GeoJSON data to the map as an MLNGeoJSONSource. + // We can then reference this data from an MLNStyleLayer. + + // MLNMapView.style is optional, so you must guard against it not being set. + guard let style = mapView.style else { return } + + guard let shapeFromGeoJSON = try? MLNShape(data: geoJson, encoding: String.Encoding.utf8.rawValue) else { + fatalError("Could not generate MLNShape") + } + + let source = MLNShapeSource(identifier: "polyline", shape: shapeFromGeoJSON, options: nil) + style.addSource(source) + + // Create new layer for the line. + let layer = MLNLineStyleLayer(identifier: "polyline", source: source) + + // Set the line join and cap to a rounded end. + layer.lineJoin = NSExpression(forConstantValue: "round") + layer.lineCap = NSExpression(forConstantValue: "round") + + // Set the line color to a constant blue color. + layer.lineColor = NSExpression(forConstantValue: UIColor(red: 59 / 255, green: 178 / 255, blue: 208 / 255, alpha: 1)) + + // Use `NSExpression` to smoothly adjust the line width from 2pt to 20pt between zoom levels 14 and 18. The `interpolationBase` parameter allows the values to interpolate along an exponential curve. + layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + [14: 2, 18: 20]) + + // We can also add a second layer that will draw a stroke around the original line. + let casingLayer = MLNLineStyleLayer(identifier: "polyline-case", source: source) + // Copy these attributes from the main line layer. + casingLayer.lineJoin = layer.lineJoin + casingLayer.lineCap = layer.lineCap + // Line gap width represents the space before the outline begins, so should match the main line’s line width exactly. + casingLayer.lineGapWidth = layer.lineWidth + // Stroke color slightly darker than the line color. + casingLayer.lineColor = NSExpression(forConstantValue: UIColor(red: 41 / 255, green: 145 / 255, blue: 171 / 255, alpha: 1)) + // Use `NSExpression` to gradually increase the stroke width between zoom levels 14 and 18. + casingLayer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", [14: 1, 18: 4]) + + // Just for fun, let’s add another copy of the line with a dash pattern. + let dashedLayer = MLNLineStyleLayer(identifier: "polyline-dash", source: source) + dashedLayer.lineJoin = layer.lineJoin + dashedLayer.lineCap = layer.lineCap + dashedLayer.lineColor = NSExpression(forConstantValue: UIColor.white) + dashedLayer.lineOpacity = NSExpression(forConstantValue: 0.5) + dashedLayer.lineWidth = layer.lineWidth + // Dash pattern in the format [dash, gap, dash, gap, ...]. You’ll want to adjust these values based on the line cap style. + dashedLayer.lineDashPattern = NSExpression(forConstantValue: [0, 1.5]) + + style.addLayer(layer) + style.addLayer(dashedLayer) + style.insertLayer(casingLayer, below: layer) + } +} + +// #-end-example-code + +struct LineStyleLayerExampleUIViewControllerRepresentable: UIViewControllerRepresentable { + typealias UIViewControllerType = LineStyleLayerExampleUIKit + + func makeUIViewController(context _: Context) -> LineStyleLayerExampleUIKit { + LineStyleLayerExampleUIKit() + } + + func updateUIViewController(_: LineStyleLayerExampleUIKit, context _: Context) {} +} diff --git a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift index a5fa59c08..efca188fb 100644 --- a/platform/ios/app-swift/Sources/MapLibreNavigationView.swift +++ b/platform/ios/app-swift/Sources/MapLibreNavigationView.swift @@ -17,6 +17,9 @@ struct MapLibreNavigationView: View { NavigationLink("BlockingGesturesExample") { BlockingGesturesExample() } + NavigationLink("LineStyleLayerExample") { + LineStyleLayerExampleUIViewControllerRepresentable() + } } } }