Skip to content

Commit

Permalink
Merge pull request #23 from hactar/enhancement/camera-bounding-box
Browse files Browse the repository at this point in the history
Add support for camera bounding box
  • Loading branch information
ianthetechie authored Mar 13, 2024
2 parents 6b59504 + dfc7526 commit 497ab5f
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ protocol MLNMapViewCameraUpdating: AnyObject {
direction: CLLocationDirection,
animated: Bool)
func setZoomLevel(_ zoomLevel: Double, animated: Bool)
func setVisibleCoordinateBounds(
_ bounds: MLNCoordinateBounds,
edgePadding: UIEdgeInsets,
animated: Bool,
completionHandler: (() -> Void)?
)
}

extension MLNMapView: MLNMapViewCameraUpdating {
Expand Down
7 changes: 6 additions & 1 deletion Sources/MapLibreSwiftUI/MapViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ public class MapViewCoordinator: NSObject {
case .trackingUserLocationWithCourse:
mapView.userTrackingMode = .followWithCourse
mapView.setZoomLevel(camera.zoom, animated: false)
case .rect, .showcase:
case let .rect(boundingBox, padding):
mapView.setVisibleCoordinateBounds(boundingBox,
edgePadding: padding,
animated: animated,
completionHandler: nil)
case .showcase:
// TODO: Need a method these/or to finalize a goal here.
break
}
Expand Down
29 changes: 26 additions & 3 deletions Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ public enum CameraState: Hashable {
case trackingUserLocationWithCourse

/// Centered on a bounding box/rectangle.
case rect(northeast: CLLocationCoordinate2D, southwest: CLLocationCoordinate2D) // TODO: make a bounding box?
case rect(
boundingBox: MLNCoordinateBounds,
edgePadding: UIEdgeInsets = .init(top: 20, left: 20, bottom: 20, right: 20)
)

/// Showcasing GeoJSON, Polygons, etc.
case showcase(shapeCollection: MLNShapeCollection)
Expand All @@ -42,10 +45,30 @@ extension CameraState: CustomDebugStringConvertible {
"CameraState.trackingUserLocationWithHeading"
case .trackingUserLocationWithCourse:
"CameraState.trackingUserLocationWithCourse"
case let .rect(northeast: northeast, southwest: southwest):
"CameraState.rect(northeast: \(northeast), southwest: \(southwest))"
case let .rect(boundingBox: boundingBox, _):
"CameraState.rect(northeast: \(boundingBox.ne), southwest: \(boundingBox.sw))"
case let .showcase(shapeCollection: shapeCollection):
"CameraState.showcase(shapeCollection: \(shapeCollection))"
}
}
}

extension MLNCoordinateBounds: Equatable, Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(ne)
hasher.combine(sw)
}

public static func == (lhs: MLNCoordinateBounds, rhs: MLNCoordinateBounds) -> Bool {
lhs.ne == rhs.ne && lhs.sw == rhs.sw
}
}

extension UIEdgeInsets: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(left)
hasher.combine(right)
hasher.combine(top)
hasher.combine(bottom)
}
}
18 changes: 17 additions & 1 deletion Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,21 @@ public struct MapViewCamera: Hashable {
lastReasonForChange: .programmatic)
}

// TODO: Create init methods for other camera states once supporting materials are understood (e.g. BoundingBox)
/// Positions the camera to show a specific region in the MapView.
///
/// - Parameters:
/// - box: Set the desired bounding box. This is a one time event and the user can manipulate by moving the map.
/// - edgePadding: Set the edge insets that should be applied before positioning the map.
/// - Returns: The MapViewCamera representing the scenario
public static func boundingBox(
_ box: MLNCoordinateBounds,
edgePadding: UIEdgeInsets = .init(top: 20, left: 20, bottom: 20, right: 20)
) -> MapViewCamera {
// zoom, pitch & direction are ignored.
MapViewCamera(state: .rect(boundingBox: box, edgePadding: edgePadding),
zoom: 1,
pitch: Defaults.pitch,
direction: Defaults.direction,
lastReasonForChange: .programmatic)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ final class CameraStateTests: XCTestCase {
let northeast = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)
let southwest = CLLocationCoordinate2D(latitude: 34.5, longitude: 45.6)

let state: CameraState = .rect(northeast: northeast, southwest: southwest)
XCTAssertEqual(state, .rect(northeast: northeast, southwest: southwest))
let state: CameraState = .rect(boundingBox: .init(sw: southwest, ne: northeast))
XCTAssertEqual(state, .rect(boundingBox: .init(sw: southwest, ne: northeast)))

XCTAssertEqual(
String(describing: state),
"CameraState.rect(northeast: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), southwest: CLLocationCoordinate2D(latitude: 34.5, longitude: 45.6))"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CoreLocation
import MapLibre
import XCTest
@testable import MapLibreSwiftUI

Expand Down Expand Up @@ -45,5 +46,20 @@ final class MapViewCameraTests: XCTestCase {
XCTAssertEqual(camera.direction, 0)
}

func testBoundingBox() {
let southwest = CLLocationCoordinate2D(latitude: 24.6056011, longitude: 46.67369842529297)
let northeast = CLLocationCoordinate2D(latitude: 24.6993808, longitude: 46.7709285)
let bounds = MLNCoordinateBounds(sw: southwest, ne: northeast)
let camera = MapViewCamera.boundingBox(bounds)

XCTAssertEqual(
camera.state,
.rect(boundingBox: bounds, edgePadding: .init(top: 20, left: 20, bottom: 20, right: 20))
)
XCTAssertEqual(camera.zoom, 1)
XCTAssertEqual(camera.pitch, .free)
XCTAssertEqual(camera.direction, 0)
}

// TODO: Add additional camera tests once behaviors are added (e.g. rect)
}

0 comments on commit 497ab5f

Please sign in to comment.