iOS Documentation: Snapshotter (#2543)

This commit is contained in:
Bart Louwers
2024-07-08 16:57:06 +02:00
committed by GitHub
parent 51e4c777eb
commit 6a8adaa5c3
15 changed files with 619 additions and 31 deletions

View File

@@ -77,6 +77,12 @@ MLN_EXPORT
// MARK: - Configuring the Map
/**
:nodoc:
Whether to include the MapLibre logo. Note this is not required.
*/
@property (nonatomic, readwrite) BOOL showsLogo;
/**
URL of the map style to snapshot.
*/

View File

@@ -130,15 +130,6 @@ private:
#endif
@end
@interface MLNMapSnapshotOptions ()
/**
:nodoc:
Whether to include the MapLibre logo. Note this is not required.
*/
@property (nonatomic, readwrite) BOOL showsLogo;
@end
@implementation MLNMapSnapshotOptions
- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MLNMapCamera *)camera size:(CGSize)size

View File

@@ -21,7 +21,7 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const MLNInvalidStyleSourceExcepti
Create instances of `MLNShapeSource`, `MLNComputedShapeSource`,
`MLNImageSource`, and the concrete subclasses of `MLNTileSource`
(`MLNVectorTileSource` and `MLNRasterTileSource`) in order to use `MLNSource`’s
(``MLNVectorTileSource`` and `MLNRasterTileSource`) in order to use `MLNSource`’s
properties and methods. Do not create instances of `MLNSource` directly, and do
not create your own subclasses of this class.
*/

View File

@@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
class may be a suitable alternative.
Each
<a href="https://maplibre.org/maplibre-style-spec/#sources-vector"><code>vector</code></a>
<a href="https://maplibre.org/maplibre-style-spec/sources/#vector"><code>vector</code></a>
source defined by the style JSON file is represented at runtime by an
`MLNVectorTileSource` object that you can use to initialize new style layers.
You can also add and remove sources dynamically using methods such as

View File

@@ -5,7 +5,7 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C
## main
- Add [Privacy Manifest](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files). MapLibre Native iOS has no built-in tracking, but it does use some system APIs for functional purposes that are marked by Apple as privacy sensitive. ([#1866](https://github.com/maplibre/maplibre-native/issues/1866)).
- Change default `MLNMapSnapshotter` logo to the MapLibre logo ([#2541](https://github.com/maplibre/maplibre-native/pull/2541)). Note that showing the MapLibre logo is never required. Check with your tile provider if you need to show a logo.
- Change default `MLNMapSnapshotter` logo to the MapLibre logo ([#2541](https://github.com/maplibre/maplibre-native/pull/2541)). Note that showing the MapLibre logo is never required. You can configure whether to show the logo with the (now public) `showsLogo` property of `MLNMapSnapshotterOptions`. Check with your tile provider if you need to show a logo.
## 6.5.0

View File

@@ -0,0 +1,55 @@
# Vector Tile Sources
Add and style a vector tile source
> This example uses UIKit
This example shows how a vector data source can be added and a style for it can be configured dynamically.
The tiles are [tiles around Innsbruck, Austria](https://github.com/maplibre/demotiles/tree/gh-pages/tiles-omt) that use the OpenMapTiles schema. We are interested in the [POIs](https://openmaptiles.org/schema/#poi) that are in the `poi` layer, and filter this further with an `NSPredicate` to only show POIs with a `class` of shop. Each POI has a `rank` which is normally used to reduce label density. In this example, we use it to demonstrate how a numeric attribute can be used for styling with the [step](https://maplibre.org/maplibre-style-spec/expressions/#step) expression. POIs with a rank between 0 and 10 get a red color, between 10 and 20 green, etc.
<!-- include-example(DDSCircleLayerExample) -->
```swift
class DDSCircleLayerExample: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MLNMapView(frame: view.bounds, styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.tintColor = .darkGray
// Set the map’s center coordinate and zoom level.
let innsbruck = CLLocationCoordinate2D(latitude: 47.26497, longitude: 11.4088)
mapView.setCenter(innsbruck, animated: false)
mapView.zoomLevel = 14
mapView.delegate = self
view.addSubview(mapView)
}
// Wait until the style is loaded before modifying the map style.
func mapView(_: MLNMapView, didFinishLoading style: MLNStyle) {
let source = MLNVectorTileSource(identifier: "demotiles", configurationURL: URL(string: "https://demotiles.maplibre.org/tiles-omt/tiles.json")!)
style.addSource(source)
let layer = MLNCircleStyleLayer(identifier: "poi-shop-style", source: source)
layer.sourceLayerIdentifier = "poi"
layer.predicate = NSPredicate(format: "class == %@", "shop")
// Style the circle layer color based on the rank
layer.circleColor = NSExpression(mglJSONObject: ["step", ["get", "rank"], 0, "red", 10, "green", 20, "blue", 30, "purple", 40, "yellow"] as [Any])
layer.circleRadius = NSExpression(forConstantValue: 3)
style.addLayer(layer)
}
}
```
![](DDSCircleLayerExample.png)
Map data from OpenStreetMap. Style uses OpenMapTiles.

View File

@@ -1,9 +1,9 @@
# Using GeoJSON with a line style layer
> Note: This example uses UIKit.
Adding an ``MLNLineStyleLayer`` to the map using a GeoJSON file.
> Note: This example uses UIKit.
<!-- include-example(LineStyleLayerExample) -->
```swift

View File

@@ -15,19 +15,32 @@ Powerful, free and open-source mapping toolkit with full control over data sourc
[MapLibre Native](https://github.com/maplibre/maplibre-native) is a map rendering toolkit with support for iOS. It can be used as an alternative to MapKit. You have full control over the data sources used for rendering the map, as well as the styling. You can even participate in the development as MapLibre Native is free and open-source project.
> Note: For information on creating and modifying map styles, see the [MapLibre Style Spec documentation](https://maplibre.org/maplibre-style-spec/).
## Guides
## Topics
### Essentials
- <doc:GettingStarted>
- <doc:LineOnUserTap>
- <doc:LocationPrivacyExample>
- <doc:BlockingGesturesExample>
- <doc:LineStyleLayerExample>
- <doc:WebAPIDataExample>
- <doc:AddMarkerSymbolExample>
### Styling and Dynamic Data
- <doc:AnimatedLineExample>
- <doc:WebAPIDataExample>
- <doc:LineStyleLayerExample>
- <doc:DDSCircleLayerExample>
- <doc:POIAlongRouteExample>
### Map Interaction
- <doc:LineOnUserTap>
- <doc:BlockingGesturesExample>
- <doc:AnnotationViewExample>
- <doc:BuildingLightExample>
## Topics
### Features
- <doc:LocationPrivacyExample>
- <doc:StaticSnapshotExample>
### Map

View File

@@ -0,0 +1,159 @@
# POI Along a Route
Use an `NSPredicate` to show POI and road labels along a route.
> This example uses UIKit.
This example adds a dynamically styled GeoJSON route to the map, similar to <doc:LineStyleLayerExample>. However, two existing layers: the `poi` layer and the `road_label` part of the Americana style are adjusted as well. The contents of these layers are shown or hidden, based on whether they lay inside a polygon around the route. In this example, both the route and the area of the polygon along the route are hardcoded.
The route is styled with three `MLNLineStyleLayer`s. We make use of the [interpolate expression](https://maplibre.org/maplibre-style-spec/expressions/#interpolate) to set the widths of these line layers at various zoom levels.
<!-- include-example(POIAlongRouteExample) -->
```swift
class POIAlongRouteExample: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MLNMapView(frame: view.bounds, styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(
CLLocationCoordinate2D(latitude: 45.52214, longitude: -122.63748),
zoomLevel: 18,
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()
restrictPOIVisibleShape()
setCamera()
}
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 setCamera() {
let camera = mapView.camera
camera.heading = 249.37706203842038
camera.pitch = 60
camera.centerCoordinate.latitude = 45.52199780570582
camera.centerCoordinate.longitude = -122.6418837958432
mapView.setCamera(camera, animated: false)
mapView.setZoomLevel(15.062187320447523, animated: false)
}
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 expression to smoothly adjust the line width from 2pt to 20pt between zoom levels 14 and 18.
layer.lineWidth = NSExpression(mglJSONObject: ["interpolate", ["linear"], ["zoom"], 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 expression to gradually increase the stroke width between zoom levels 14 and 18.
casingLayer.lineWidth = NSExpression(mglJSONObject: ["interpolate", ["linear"], ["zoom"], 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])
guard let poiLayer = mapView.style?.layer(withIdentifier: "poi") as? MLNSymbolStyleLayer else {
print("Could not find poi layer")
return
}
style.insertLayer(layer, below: poiLayer)
style.insertLayer(dashedLayer, above: layer)
style.insertLayer(casingLayer, below: layer)
}
func restrictPOIVisibleShape() {
// find poi-label layer
guard let poiLayer = mapView.style?.layer(withIdentifier: "poi") as? MLNSymbolStyleLayer else {
print("Could not find poi layer")
return
}
// find road-label layer
guard let roadLabelLayer = mapView.style?.layer(withIdentifier: "road_label") as? MLNSymbolStyleLayer else {
print("Could not find road_label layer")
return
}
// show the POI and road that is within this polygon
let polygonShape = [
[-122.63730626171188, 45.52288837762333],
[-122.65455070022612, 45.52299746891552],
[-122.65747018755947, 45.52177017968134],
[-122.65992255691913, 45.51931552089448],
[-122.66015611590598, 45.513696676587045],
[-122.66696825301655, 45.51375123117057],
[-122.6672018120034, 45.51222368283956],
[-122.6571977020749, 45.51225096085216],
[-122.6570419960839, 45.51822452705878],
[-122.65392787626189, 45.52106106703124],
[-122.63567134880579, 45.52114288817623],
[-122.63657745074761, 45.52288036393409],
[-122.6373404839605, 45.52291377640398],
]
// create a polygon class
let coordinates = polygonShape.map { CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0]) }
let bufferedRoutePolygon = MLNPolygon(coordinates: coordinates, count: UInt(coordinates.count), interiorPolygons: nil)
// apply predicates to these two layers
poiLayer.predicate = NSPredicate(format: "SELF IN %@", bufferedRoutePolygon)
roadLabelLayer.predicate = NSPredicate(format: "SELF IN %@", bufferedRoutePolygon)
}
}
```

View File

@@ -0,0 +1,66 @@
# Making Snapshots
Use ``MLNMapSnapshotter`` to create snapshots of the map.
> This example uses UIKit.
This example demonstrates how snapshots can be made of the map. The result is an `UIImage` that you can let the user save to their camera roll or share with a friend. In this demo, the resulting image is shown back with an `UIImageView`. Attribution is read from the sources and added to the snapshot (when applicable). By default a MapLibre logo is included on the image, but it is not required to show it. You can use ``MLNMapSnapshotOptions/showsLogo`` to configure whether to include the logo. Check with your tile provider if you need to show their logo.
<!-- include-example(StaticSnapshotExample) -->
```swift
class StaticSnapshotExample: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!
var button: UIButton!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MLNMapView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height / 2), styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
// Center map on the Giza Pyramid Complex in Egypt.
let center = CLLocationCoordinate2D(latitude: 29.9773, longitude: 31.1325)
mapView.setCenter(center, zoomLevel: 14, animated: false)
view.addSubview(mapView)
// Create a button to take a map snapshot.
button = UIButton(frame: CGRect(x: mapView.bounds.width / 2 - 40, y: mapView.bounds.height - 40, width: 80, height: 30))
button.layer.cornerRadius = 15
button.backgroundColor = UIColor(red: 0.96, green: 0.65, blue: 0.14, alpha: 1.0)
button.setImage(UIImage(named: "camera"), for: .normal)
button.addTarget(self, action: #selector(createSnapshot), for: .touchUpInside)
view.addSubview(button)
// Create a UIImageView that will store the map snapshot.
imageView = UIImageView(frame: CGRect(x: 0, y: view.bounds.height / 2, width: view.bounds.width, height: view.bounds.height / 2))
imageView.backgroundColor = .black
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(imageView)
}
@objc func createSnapshot() {
// Use the map's style, camera, size, and zoom level to set the snapshot's options.
let options = MLNMapSnapshotOptions(styleURL: mapView.styleURL, camera: mapView.camera, size: mapView.bounds.size)
options.zoomLevel = mapView.zoomLevel
// Add an activity indicator to show that the snapshot is loading.
let indicator = UIActivityIndicatorView(frame: CGRect(x: imageView.center.x - 30, y: imageView.center.y - 30, width: 60, height: 60))
view.addSubview(indicator)
indicator.startAnimating()
// Create the map snapshot.
let snapshotter: MLNMapSnapshotter? = MLNMapSnapshotter(options: options)
snapshotter?.start { snapshot, error in
if error != nil {
print("Unable to create a map snapshot.")
} else if let snapshot {
// Add the map snapshot's image to the image view.
indicator.stopAnimating()
self.imageView.image = snapshot.image
}
}
}
}
```

View File

@@ -1,9 +1,9 @@
# Showing data from an API
> Note: This example uses UIKit.
Showing data from an API with custom styling and interaction
> Note: This example uses UIKit.
This example loads lighthouses in the United States from [WikiData](http://tinyurl.com/zrl2jc4). It adds points to the map and applies dynamic styling to these points. When zooming in the dots become larger circles with a custom icon and the name of the lighthouse shown next to it. When tapping a callout is shown with the name of the lighthouse that was tapped on.
<!-- include-example(WebAPIDataExample) -->

View File

@@ -0,0 +1,55 @@
import MapLibre
import SwiftUI
import UIKit
// #-example-code(DDSCircleLayerExample)
class DDSCircleLayerExample: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MLNMapView(frame: view.bounds, styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.tintColor = .darkGray
// Set the map’s center coordinate and zoom level.
let innsbruck = CLLocationCoordinate2D(latitude: 47.26497, longitude: 11.4088)
mapView.setCenter(innsbruck, animated: false)
mapView.zoomLevel = 14
mapView.delegate = self
view.addSubview(mapView)
}
// Wait until the style is loaded before modifying the map style.
func mapView(_: MLNMapView, didFinishLoading style: MLNStyle) {
let source = MLNVectorTileSource(identifier: "demotiles", configurationURL: URL(string: "https://demotiles.maplibre.org/tiles-omt/tiles.json")!)
style.addSource(source)
let layer = MLNCircleStyleLayer(identifier: "poi-shop-style", source: source)
layer.sourceLayerIdentifier = "poi"
layer.predicate = NSPredicate(format: "class == %@", "shop")
// Style the circle layer color based on the rank
layer.circleColor = NSExpression(mglJSONObject: ["step", ["get", "rank"], 0, "red", 10, "green", 20, "blue", 30, "purple", 40, "yellow"] as [Any])
layer.circleRadius = NSExpression(forConstantValue: 3)
style.addLayer(layer)
}
}
// #-end-example-code
struct DDSCircleLayerExampleUIViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = DDSCircleLayerExample
func makeUIViewController(context _: Context) -> DDSCircleLayerExample {
DDSCircleLayerExample()
}
func updateUIViewController(_: DDSCircleLayerExample, context _: Context) {}
}

View File

@@ -26,14 +26,25 @@ struct MapLibreNavigationView: View {
NavigationLink("AddMarkerExample") {
AddMarkerSymbolExampleUIViewControllerRepresentable()
}
NavigationLink("AnimatedLineExample") {
AnimatedLineExampleUIViewControllerRepresentable()
}
NavigationLink("AnnotationViewExample") {
AnnotationViewExampleUIViewControllerRepresentable()
}
NavigationLink("BuildingLightExample") {
BuildingLightExampleUIViewControllerRepresentable()
Group {
NavigationLink("AnimatedLineExample") {
AnimatedLineExampleUIViewControllerRepresentable()
}
NavigationLink("AnnotationViewExample") {
AnnotationViewExampleUIViewControllerRepresentable()
}
NavigationLink("BuildingLightExample") {
BuildingLightExampleUIViewControllerRepresentable()
}
NavigationLink("StaticSnapshotExample") {
StaticSnapshotExampleUIViewControllerRepresentable()
}
NavigationLink("DDSCircleLayerExample") {
DDSCircleLayerExampleUIViewControllerRepresentable().edgesIgnoringSafeArea(.all)
}
NavigationLink("POIAlongRouteExample") {
POIAlongRouteExampleUIViewControllerRepresentable()
}
}
}
}

View File

@@ -0,0 +1,161 @@
import MapLibre
import SwiftUI
import UIKit
// #-example-code(POIAlongRouteExample)
class POIAlongRouteExample: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MLNMapView(frame: view.bounds, styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(
CLLocationCoordinate2D(latitude: 45.52214, longitude: -122.63748),
zoomLevel: 18,
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()
restrictPOIVisibleShape()
setCamera()
}
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 setCamera() {
let camera = mapView.camera
camera.heading = 249.37706203842038
camera.pitch = 60
camera.centerCoordinate.latitude = 45.52199780570582
camera.centerCoordinate.longitude = -122.6418837958432
mapView.setCamera(camera, animated: false)
mapView.setZoomLevel(15.062187320447523, animated: false)
}
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 expression to smoothly adjust the line width from 2pt to 20pt between zoom levels 14 and 18.
layer.lineWidth = NSExpression(mglJSONObject: ["interpolate", ["linear"], ["zoom"], 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 expression to gradually increase the stroke width between zoom levels 14 and 18.
casingLayer.lineWidth = NSExpression(mglJSONObject: ["interpolate", ["linear"], ["zoom"], 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])
guard let poiLayer = mapView.style?.layer(withIdentifier: "poi") as? MLNSymbolStyleLayer else {
print("Could not find poi layer")
return
}
style.insertLayer(layer, below: poiLayer)
style.insertLayer(dashedLayer, above: layer)
style.insertLayer(casingLayer, below: layer)
}
func restrictPOIVisibleShape() {
// find poi-label layer
guard let poiLayer = mapView.style?.layer(withIdentifier: "poi") as? MLNSymbolStyleLayer else {
print("Could not find poi layer")
return
}
// find road-label layer
guard let roadLabelLayer = mapView.style?.layer(withIdentifier: "road_label") as? MLNSymbolStyleLayer else {
print("Could not find road_label layer")
return
}
// show the POI and road that is within this polygon
let polygonShape = [
[-122.63730626171188, 45.52288837762333],
[-122.65455070022612, 45.52299746891552],
[-122.65747018755947, 45.52177017968134],
[-122.65992255691913, 45.51931552089448],
[-122.66015611590598, 45.513696676587045],
[-122.66696825301655, 45.51375123117057],
[-122.6672018120034, 45.51222368283956],
[-122.6571977020749, 45.51225096085216],
[-122.6570419960839, 45.51822452705878],
[-122.65392787626189, 45.52106106703124],
[-122.63567134880579, 45.52114288817623],
[-122.63657745074761, 45.52288036393409],
[-122.6373404839605, 45.52291377640398],
]
// create a polygon class
let coordinates = polygonShape.map { CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0]) }
let bufferedRoutePolygon = MLNPolygon(coordinates: coordinates, count: UInt(coordinates.count), interiorPolygons: nil)
// apply predicates to these two layers
poiLayer.predicate = NSPredicate(format: "SELF IN %@", bufferedRoutePolygon)
roadLabelLayer.predicate = NSPredicate(format: "SELF IN %@", bufferedRoutePolygon)
}
}
// #-end-example-code
struct POIAlongRouteExampleUIViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = POIAlongRouteExample
func makeUIViewController(context _: Context) -> POIAlongRouteExample {
POIAlongRouteExample()
}
func updateUIViewController(_: POIAlongRouteExample, context _: Context) {}
}

View File

@@ -0,0 +1,71 @@
import MapLibre
import SwiftUI
import UIKit
// #-example-code(StaticSnapshotExample)
class StaticSnapshotExample: UIViewController, MLNMapViewDelegate {
var mapView: MLNMapView!
var button: UIButton!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MLNMapView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height / 2), styleURL: AMERICANA_STYLE)
mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
// Center map on the Giza Pyramid Complex in Egypt.
let center = CLLocationCoordinate2D(latitude: 29.9773, longitude: 31.1325)
mapView.setCenter(center, zoomLevel: 14, animated: false)
view.addSubview(mapView)
// Create a button to take a map snapshot.
button = UIButton(frame: CGRect(x: mapView.bounds.width / 2 - 40, y: mapView.bounds.height - 40, width: 80, height: 30))
button.layer.cornerRadius = 15
button.backgroundColor = UIColor(red: 0.96, green: 0.65, blue: 0.14, alpha: 1.0)
button.setImage(UIImage(named: "camera"), for: .normal)
button.addTarget(self, action: #selector(createSnapshot), for: .touchUpInside)
view.addSubview(button)
// Create a UIImageView that will store the map snapshot.
imageView = UIImageView(frame: CGRect(x: 0, y: view.bounds.height / 2, width: view.bounds.width, height: view.bounds.height / 2))
imageView.backgroundColor = .black
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(imageView)
}
@objc func createSnapshot() {
// Use the map's style, camera, size, and zoom level to set the snapshot's options.
let options = MLNMapSnapshotOptions(styleURL: mapView.styleURL, camera: mapView.camera, size: mapView.bounds.size)
options.zoomLevel = mapView.zoomLevel
// Add an activity indicator to show that the snapshot is loading.
let indicator = UIActivityIndicatorView(frame: CGRect(x: imageView.center.x - 30, y: imageView.center.y - 30, width: 60, height: 60))
view.addSubview(indicator)
indicator.startAnimating()
// Create the map snapshot.
let snapshotter: MLNMapSnapshotter? = MLNMapSnapshotter(options: options)
snapshotter?.start { snapshot, error in
if error != nil {
print("Unable to create a map snapshot.")
} else if let snapshot {
// Add the map snapshot's image to the image view.
indicator.stopAnimating()
self.imageView.image = snapshot.image
}
}
}
}
// #-end-example-code
struct StaticSnapshotExampleUIViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = StaticSnapshotExample
func makeUIViewController(context _: Context) -> StaticSnapshotExample {
StaticSnapshotExample()
}
func updateUIViewController(_: StaticSnapshotExample, context _: Context) {}
}