245 lines
7.5 KiB
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
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|