Skip to main content

Framing to a Set of Locations

Contents

Frame the map view to the bounds of a set of locations. In this case, we will use the Lines On a Map example to frame the map to each set of lines when clicking on the button, utilizing the setVisibleCoordinateBounds() method.

Sample Code

import Foundation
import UIKit
import TrimbleMaps
import TrimbleMapsAccounts

class SampleFramingViewController: UIViewController, AccountManagerDelegate, TMGLMapViewDelegate {

    internal var mapView: TMGLMapView!
    private var coordinatesList: [[CLLocationCoordinate2D]] = []
    private var currentIndex = 0

    let SOURCE_ID = "tristatepoints"
    let LAYER_ID = "tristatepoints"

    override func viewDidLoad() {
        super.viewDidLoad()

        let apiKey =  "Your-API-key-here"
        let account = Account(apiKey: apiKey, region: Region.northAmerica)
        AccountManager.default.account = account
        AccountManager.default.delegate = self

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(backButtonPressed))

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Frame", style: .plain, target: self, action: #selector(frameButtonPressed))

        // Set the view's background color
        self.view.backgroundColor = .white

        // Load the coordinates from the JSON file
        loadCoordinates()
    }

    func stateChanged(newStatus: AccountManagerState) {
        if AccountManager.default.isLicensed(licensedFeature: .mapsSdk) {
            if newStatus == .loaded {
                DispatchQueue.main.async {
                    // Create a map view
                    self.mapView = TMGLMapView(frame: self.view.bounds)
                    self.mapView.delegate = self

                    // Set the map location
                    let center = CLLocationCoordinate2D(latitude: 40.60902838712187, longitude: -97.73800045737227)
                    self.mapView.setCenter(center, zoomLevel: 2.5, animated: false)

                    // Add the map
                    self.view.addSubview(self.mapView)

                }
            }
        } else {
            // Handle the case where the account is not licensed for Maps SDK
            print("Account is not licensed for Maps SDK")
        }
    }

    func mapViewDidFinishLoadingMap(_ mapView: TMGLMapView) {
        // In this example a .json file is being used as the source

        let filePath = Bundle.main.path(forResource: "lines", ofType: "json")!
        let fileUrl = URL(fileURLWithPath: filePath)

        // Create a source and add it to the style. Important to note, sources are linked to styles.
        // If you change the style you may need to re-add your source and layers
        let shapeSource = TMGLShapeSource(identifier: SOURCE_ID, url: fileUrl, options: .none)
        mapView.style?.addSource(shapeSource)

        // Create a LineLayer to display our source information.
        let lineLayer = TMGLLineStyleLayer(identifier: LAYER_ID, source: shapeSource)
        lineLayer.lineWidth = NSExpression(forConstantValue: 6)
        lineLayer.lineColor = NSExpression(forConstantValue: UIColor.blue)
        lineLayer.lineOpacity = NSExpression(forConstantValue: 0.8)

        // add the layer
        mapView.style?.addLayer(lineLayer)
    }

    @objc func moveCamera() {
        guard !coordinatesList.isEmpty else {
            print("No coordinates available")
            return
        }

        // Get the current set of coordinates
        let pointList = coordinatesList[currentIndex]

        // Calculate the bounding box
        guard let boundingBox = calculateBoundingBox(for: pointList) else {
            print("Failed to calculate bounding box")
            return
        }

        // Update the view to the set of lines
        mapView.setVisibleCoordinateBounds(boundingBox, edgePadding: .zero, animated: true, completionHandler: nil)

        // Update the index to the next set of coordinates
        currentIndex = (currentIndex + 1) % coordinatesList.count
    }

    func loadCoordinates() {
        // Load the JSON file
        guard let filePath = Bundle.main.path(forResource: "lines", ofType: "json"),
              let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
              let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
              let features = json["features"] as? [[String: Any]] else {
            print("Failed to load or parse lines.json")
            return
        }

        // Extract coordinates
        for feature in features {
            if let geometry = feature["geometry"] as? [String: Any],
               let coordinates = geometry["coordinates"] as? [[Double]] {
                var pointList = [CLLocationCoordinate2D]()
                for coordinate in coordinates {
                    let point = CLLocationCoordinate2D(latitude: coordinate[1], longitude: coordinate[0])
                    pointList.append(point)
                }
                coordinatesList.append(pointList)
            }
        }
    }

    func calculateBoundingBox(for points: [CLLocationCoordinate2D]) -> TMGLCoordinateBounds? {
        guard !points.isEmpty else { return nil }

        var minLat = points[0].latitude
        var maxLat = points[0].latitude
        var minLon = points[0].longitude
        var maxLon = points[0].longitude

        for point in points {
            if point.latitude < minLat { minLat = point.latitude }
            if point.latitude > maxLat { maxLat = point.latitude }
            if point.longitude < minLon { minLon = point.longitude }
            if point.longitude > maxLon { maxLon = point.longitude }
        }

        let sw = CLLocationCoordinate2D(latitude: minLat, longitude: minLon)
        let ne = CLLocationCoordinate2D(latitude: maxLat, longitude: maxLon)

        return TMGLCoordinateBounds(sw: sw, ne: ne)
    }

    @objc func backButtonPressed() {
        dismiss(animated: true, completion: nil)
    }

    @objc func frameButtonPressed() {
        moveCamera()
    }
}
Last updated January 27, 2025.
Contents