TreeMeasurementApp/TreeMeasure/ViewController.swift

245 lines
7.5 KiB
Swift

//
// ViewController.swift
// TreeMeasure
//
// Created by Basti SK on 13.04.24.
//
import UIKit
import AVFoundation
import CoreMotion
class ViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var inputRefHeight: UITextField!
@IBOutlet weak var statusText: UITextView!
@IBOutlet weak var centerButton: UIButton!
@IBOutlet weak var previewView: UIView!
@IBOutlet var mainView: UIView!
// Cam Variables
var captureSession: AVCaptureSession!
var videoPreviewLayer: AVCaptureVideoPreviewLayer!
var globalQueue: OperationQueue!
@IBOutlet weak var distanceText: UITextView!
var backCamera: AVCaptureDevice!
// VariablesForCalculations
var globalDegrees: Double!
var globalAttitude: Double!
var globalGravityAngle: Double!
var topPole: Double!
var bottomPole: Double!
var topTree: Double!
// Motion Variables
var motionManager: CMMotionManager!
override func viewDidLoad() {
super.viewDidLoad()
// init motion manager
motionManager = CMMotionManager();
// init vars for calculations
topPole = 0;
bottomPole = 0;
topTree = 0;
statusText.text = "To start, please snapshot the top of the reference height."
// enable tap out of edit for ref. heigth
let tap = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// start and prepare cam related things
captureSession = AVCaptureSession()
backCamera = AVCaptureDevice.default(for: AVMediaType.video)
do {
let input = try AVCaptureDeviceInput(device: backCamera)
if(captureSession.canAddInput(input)) {
captureSession.addInput(input)
setupLivePreview()
}
}
catch let error {
print("Error Unable to initialize back camera: \(error.localizedDescription)")
}
// start monitoring the motion sensor
startMotionMonitor();
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.captureSession.stopRunning()
motionManager.stopDeviceMotionUpdates()
}
@IBAction func reset() {
topTree = 0
bottomPole = 0
topPole = 0
statusText.text = "To start, please snapshot the top of the reference height."
distanceText.text = ""
}
@IBAction func addMarker() {
let l = Double(inputRefHeight.text ?? "1.0") ?? 1.0
if(topPole == 0) {
topPole = globalAttitude
if globalGravityAngle < 0 {
topPole = globalAttitude * -1
}
statusText.text = "Snaphost Angle: " + String(format: "%.2f", globalDegrees) + "°.\nNext, snapshot bottom of reference height."
return;
}
if(bottomPole == 0) {
bottomPole = globalAttitude
if globalGravityAngle < 0 {
bottomPole = bottomPole * -1
}
statusText.text = "Snapshot Angle: " + String(format: "%.2f", globalDegrees) + "°\nFinally, snapshot top ofx tree."
let alpha3 = topPole - Double.pi / 2
let alpha2 = bottomPole - Double.pi / 2
let e = abs(l / ( tan(alpha3) - tan(alpha2)))
distanceText.text = "Distance: " + String(format: "%.2f", e) + " m"
return;
}
if(topTree == 0) {
topTree = globalAttitude
if globalGravityAngle < 0 {
topTree = topTree * -1
}
}
if((topPole != 0) && (bottomPole != 0) && (topTree != 0)) {
let alpha3 = topPole - Double.pi / 2
let alpha2 = bottomPole - Double.pi / 2
let alpha1 = topTree - Double.pi / 2
let e = abs(l / ( tan(alpha3) - tan(alpha2)))
let h1 = e * tan(alpha1)
let h2 = e * tan(alpha2)
let h = abs(h1 - h2)
statusText.text = "Height: " + String( format: "%.4f", h) + " m"
//reset
topTree = 0
bottomPole = 0
topPole = 0
}
}
@IBAction func changeZoom() {
if(backCamera.videoZoomFactor > 1) {
setZoomFactor(factor: backCamera.videoZoomFactor + 10)
} else {
setZoomFactor(factor: backCamera.maxAvailableVideoZoomFactor)
}
}
@IBAction func zoomOut() {
if(backCamera.videoZoomFactor > 1) {
setZoomFactor(factor: backCamera.videoZoomFactor - 1)
}
}
@IBAction func zoomIn() {
if(backCamera.videoZoomFactor < backCamera.maxAvailableVideoZoomFactor) {
setZoomFactor(factor: backCamera.videoZoomFactor + 1)
}
}
func setZoomFactor(factor: CGFloat) {
do {
try backCamera.lockForConfiguration()
if(factor <= backCamera.maxAvailableVideoZoomFactor) {
backCamera.videoZoomFactor = factor
}
backCamera.unlockForConfiguration()
} catch let error {
print("Error Unable to set zoom for back camera: \(error.localizedDescription)")
}
}
func startMotionMonitor() {
globalQueue = OperationQueue()
if motionManager.isDeviceMotionAvailable == true {
motionManager.deviceMotionUpdateInterval = 0.5
motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical, to: self.globalQueue, withHandler: { (data, error) in
if let attitude = data?.attitude {
// Get the pitch (in radians) and convert to degrees for display.
self.globalDegrees = (attitude.pitch * 180.0/Double.pi)
self.globalAttitude = attitude.pitch
DispatchQueue.main.async {
self.textView.text = "Angle: " + String(format: "%.2f", self.globalDegrees) + "°";
}
}
if let gravity = data?.gravity {
self.globalGravityAngle = gravity.z;
}
// we are looking up!
if self.globalGravityAngle > 0 {
self.globalDegrees = 180 - self.globalDegrees;
}
})
}
}
func setupLivePreview() {
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer.connection?.videoOrientation = .portrait
previewView.layer.addSublayer(videoPreviewLayer)
DispatchQueue.global(qos: .userInitiated).async { //[weak self] in
self.captureSession.startRunning()
DispatchQueue.main.async {
self.videoPreviewLayer.frame = self.previewView.bounds
}
}
}
}