Skip to content
DOHYUN CHUNG edited this page Nov 21, 2022 · 4 revisions

Generic badge Generic badge Generic badge Generic badge Generic badge

AR-Evacuation With Beacons is the name of the application and made for iOS devices. AR-Evacuation With Beacons detects the beacons inside of K-SW Square building which is in the Purdue University. The application estimates user's current location indoor and inform the optimized path to each user to flee safely from fire situation.

How does it works?

ARNavigationViewController

ARNavigationViewController mainly displays the AR Navigation system. The application is made of MVC pattern. So this viewcontroller does lots of works. Mainly this viewcontroller change angle of 3D AR arrow and spawn the 3d object in next route based on given angle and distance from VectorService and IndoorLocationManager. With these information, the app can help evacuee flee safely.

This code block is main function of the viewcontroller. When the vector is set, the ARKit make the arrow change the angle in every 60 frames. The important things in AR world is that the app is using the gravityAndheading world configuration. With this, we are able to use absolute orientation, which means we can make our own 2d coordinate systems.

@objc private func movenotification(_ noti: Notification) {
        guard let userLocation = noti.object as? Position else {return}
        mapContentScrollView.scroll(to: map2DViewController.annotationView.currentPoint)
        
        // 1.when the path is there
        guard !path.path.isEmpty else {return}
        // 2. find the location
        guard let index = path.path.firstIndex(of: userLocation) else {return}
        // 3. if the location is not the destination
        if index < path.path.count - 1 {
            // find the vector bwt 2 points
            let start = VectorService.transformCellToCGPoint(cellname: path.path[index])
            let end = VectorService.transformCellToCGPoint(cellname:path.path[index+1])
            let vector = VectorService.vectorBetween2Points(from: start, to: end)
            
            // get angle
            directionDegree = vector.angle
            // get dist
            let dist = vector.dist
            
            // generate sphere to next cell
            // 1 cell = 36cm
            let newnode = generateSphereNode()
            sceneView.scene.rootNode.addChildNode(newnode)
            newnode.position = SCNVector3(x: arrow.position.x, y: arrow.position.y, z: arrow.position.z - (Float(dist) / 10 * 0.36) + 1.0)
            
        } else {
            bannerText = "Safely Exit"
            let alert = UIAlertController(title: "YOU ARE SAFE😀", message: nil, preferredStyle: .alert)
            self.present(alert, animated: true)
        }
        
    }

Map2DViewController

Map2DViewController can user see the 2D map. Map2DViewController contains 3 views drawn by UIBeizerPath and 1 view that annotate user's location. The view of viewcontroller is ported in the scrollview inside ARNavigationViewController. Also, Map2DViewController can display the status of fire situation. Green cell is the optimized path. Red cell is the fire cell. Orange Cell is the fire nearby cell. Yellow cell is the conjestion cell.

IndoorLocationManager

IndoorLocationManager manage the beacon part and heading part from CoreLocation. The app use trained model to estimate the user's location with beacons' RSSI. Also, in order to adjust the user's location, we fusioned the result of the model and compass sensor.

 private func adjustLocation(from previousLocation: Position, to currentLocation: Position, with heading: Double) -> Position {
        if previousLocation == .unknown || currentLocation == .unknown {return previousLocation}
        let adjacentCells = adjacentCells(nearby: previousLocation)
        let currentDirection = headingToDirection(with: heading)
        if adjacentCells.contains(currentLocation) {
            return locationWhenAdjacentCellInCandidateCells(from: previousLocation,
                                                            to: currentLocation,
                                                            with: currentDirection)
        }
        return locationWhenAdjacentCellNotInCandidateCells(from: previousLocation,
                                                           to: currentLocation,
                                                           with: currentDirection)
    }

 private func locationWhenAdjacentCellNotInCandidateCells(from previousLocation: Position, to currentLocation: Position, with currentDirection: Direction) -> Position {
        // if not adjacent cell, but heading of user and angle between previous location and the result are same, we judge the user moves fast.
         let currentPosPoint = VectorService.transformCellToCGPoint(cellname: currentLocation)
         let prevPosPoint = VectorService.transformCellToCGPoint(cellname: previousLocation)
         let indicatedHeading = Double(VectorService.vectorBetween2Points(from: prevPosPoint, to: currentPosPoint).angle)
         let direction = headingToDirection(with: indicatedHeading)
         
         if direction == currentDirection {
             return candidateCells(by: previousLocation, with: direction).first ?? previousLocation
         }
        return previousLocation
    }
    
    private func candidateCells(by previousLocation: Position, with direction: Direction) -> [Position] {
        var candidateCells = previousLocation.adjacentCell[direction.rawValue]
        removeUnknownCellFromCandidateCells(candidateCells: &candidateCells)
        return candidateCells
    }
    
    private func locationWhenAdjacentCellInCandidateCells(from previousLocation: Position, to currentLocation: Position, with currentDirection: Direction) -> Position {
        
        let candidateCells = candidateCells(by: previousLocation, with: currentDirection)

        if candidateCells.contains(currentLocation) {
            return currentLocation
        }
        return previousLocation
    }
    
    private func removeUnknownCellFromCandidateCells(candidateCells: inout [Position]) {
        candidateCells.removeAll { $0 == .unknown }
    }

KalmanFilter

Kalman Filter is used to smooth the RSSI from beacons. Kalman Filter runs recursively to estimate the next state with current state. This code is the init of KalmanFilter

    init(R: Float, Q: Float, stateVector: Float = 1, controlVector: Float = 0, measureVector: Float = 1) {
        self.R = R
        self.Q = Q
        self.stateVector = stateVector
        self.controlVector = controlVector
        self.measureVector = measureVector
    }

R is system noise, Q is measurement noise, and there are 3 vector. stateVector is used to estimate the next statement with equation. measureVector is used to estimate the measure of RSSI. three vector are usually set by the machine that we use. We reinit the filter when beacon's RSSI exceed the threshold. The Threshold will be selected by top 5% and bottom 5% in normal distribution.

VectorService

In VectorService, we calculate the angle to direction in 2d coordinate. Also, we use atan2 function to find out angle between 2 points.

image
static func vectorBetween2Points(from: CGPoint, to: CGPoint) -> (angle: Float, dist: Double) {
        var degree: Float = 0.0
        let tan = atan2(from.x - to.x, from.y - to.y) * 180 / .pi
        degree  = 180 - Float(tan)
        return (angle: degree, dist: sqrt(pow(from.x - to.x, 2) + pow(from.y - to.y, 2)))
    }

Clone this wiki locally