---
# System prepended metadata

title: AR+GPS

---

# 地圖/GIS的API:
1.Google和Mapbox要付費或綁信用卡

2.MapTiler免費，還在研究https://cloud.maptiler.com/maps/

3.Trend-Go Maps API：
台灣的API，套在網站上的https://api.map.com.tw/API_Sample/Default.aspx

# GPS權限:
可參考:https://youtu.be/8FLTbE2VqrU?si=hgtA6AVRDqwbze3g
我剛開始缺android的相關設定選項，剛剛才發現要在unity hub的installs在6.0版本那裏新增android的module
> 
> 直接把影片教學的code搬過了(LocationStuff.cs)(https://github.com/TemporalCoder/UnityAR)
> 
> 手機實測學校圖書館座標
> ![1000026648](https://hackmd.io/_uploads/BkfUv1fTeg.jpg)
> 
> google map找的座標
> ![image](https://hackmd.io/_uploads/B1MJ_kzaxx.png)


===================================================
### PlayerLocationManager.cs
基本上在Hierachy創一個GameObject把這個cs掛上去就可以用了，要讀位置用getPlayerLocation()即可，回傳Vector2 currLoc(目前位置)
currLoc[0]是lat座標，currLoc[1]是lon座標
debugMod設true座標不再依現實座標更新，可用setPlayerLat和setPlayerLon設座標
```
using System.Collections;                                    
using UnityEngine;
using TMPro;
using System;

public class PlayerLocationManager : MonoBehaviour
{
    [SerializeField]
    private char unit = 'K';

    public TMP_Text debugTxt;
    public bool gps_ok = false;
    public bool debugMod = false;
    Vector2 currLoc = new(0, 0);
    float horizontalAccuracy = -1;

    // Start is called before the first frame update
    IEnumerator Start()
    {
        // Check if the user has location service enabled.
        if (!Input.location.isEnabledByUser)
        {
            Debug.Log("Location not enabled on device or app does not have permission to access location");
            debugTxt.text = "Location not enabled on device or app does not have permission to access location";
        }
        // Starts the location service.
        Input.location.Start();

        // Waits until the location service initializes
        int maxWait = 20;
        while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
        {
            yield return new WaitForSeconds(1);
            maxWait--;
        }

        // If the service didn't initialize in 20 seconds this cancels location service use.
        if (maxWait < 1)
        {
            Debug.Log("Timed out");
            debugTxt.text += "\nTimed Out";
            yield break;
        }

        // If the connection failed this cancels location service use.
        if (Input.location.status == LocationServiceStatus.Failed)
        {
            Debug.LogError("Unable to determine device location");
            debugTxt.text += ("\nUnable to determine device location");
            yield break;
        }
        else
        {
            // If the connection succeeded, this retrieves the device's current location and displays it in the Console window.
            Debug.Log("Location:\n" + Input.location.lastData.latitude +
                    "\n" + Input.location.lastData.longitude +
                    "\n" + Input.location.lastData.altitude +
                    "\n" + Input.location.lastData.horizontalAccuracy +
                    "\n" + Input.location.lastData.timestamp);

            debugTxt.text
                = "Location:"
                + "\nLatitude: " + Input.location.lastData.latitude
                + "\nLongitude: " + Input.location.lastData.longitude
                + "\nHorizontalAccuracy: " + Input.location.lastData.horizontalAccuracy
                + "\nTime: " + Input.location.lastData.timestamp;

            gps_ok = true;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (gps_ok && !debugMod)
        {
            currLoc[0] = Input.location.lastData.latitude;
            currLoc[1] = Input.location.lastData.longitude;
            horizontalAccuracy = Input.location.lastData.horizontalAccuracy;
        }
        debugTxt.text = "GPS:...";

        debugTxt.text
            = "Location:"
            + "\nLatitude: " + currLoc[0]
            + "\nLongitude: " + currLoc[1]
            + "\nHorizontalAccuracy: " + horizontalAccuracy;

    }

    public void StopGPS()
    {
        Input.location.Stop();
    }

    public Vector2 getPlayerLocation()
    {
        return currLoc;
    }

    public void setPlayerLat(float lat)
    {
        currLoc[0] = lat;
    }

    public void setPlayerLon(float lon)
    {
        currLoc[1] = lon;
    }

    public void setDebugMode(bool b)
    {
        debugMod = b;
    }
}
```
=================================================
### LocationCalculator.cs
做地理座標運算用的
```
using System;

//用於做地理座標運算
public class LocationCalculator
{
    //https://www.geodatasource.com/resources/tutorials/how-to-calculate-the-distance-between-2-locations-using-c/
    //兩座實際標距離
    //unit = the unit you desire for results where
    //‘M’ is the statute miles (default)
    //‘K’ is kilometers
    //‘N’ is nautical miles
    public double distance(double lat1, double lon1, double lat2, double lon2, char unit = 'K')
    {
        if ((lat1 == lat2) && (lon1 == lon2))
        {
            return 0;
        }
        else
        {
            double theta = lon1 - lon2;
            double dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta));
            dist = Math.Acos(dist);
            dist = rad2deg(dist);
            dist = dist * 60 * 1.1515;
            if (unit == 'K')
            {
                dist = dist * 1.609344;
            }
            else if (unit == 'N')
            {
                dist = dist * 0.8684;
            }
            return (dist);
        }
    }

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    //::  This function converts decimal degrees to radians             :::
    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    public double deg2rad(double deg)
    {
        return (deg * Math.PI / 180.0);
    }

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    //::  This function converts radians to decimal degrees             :::
    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    public double rad2deg(double rad)
    {
        return (rad / Math.PI * 180.0);
    }
}
```

=========================================================
### AR_GameObjectManager.cs
看Update那邊就行了
用LocationCalculator算玩家和NPC/收集物件的距離，在visiableDistance範圍內會顯示(或者說生成)物件，超過destroyDistance就會將物件消除
visiableDistance < destroyDistance (避免物件在特定距離生成又消除)

```
using UnityEngine;
using System.Collections.Generic;
using TMPro;

public class AR_GameObjectManager : MonoBehaviour
{
    private string AR_GameObjectInfodir = ""; //for reading info like location and isCollected
    public double visiableDistance = 0.01; //收集物件的可見範圍(km)
    public double destroyDistance = 0.02; //收集物件超過此範圍(km)則移除
    public PlayerLocationManager playerLocationManager;
    private LocationCalculator locationCalculator = new LocationCalculator();
    public Vector2 playerLocation;
    //List<GameObject> gameObjects = new List<GameObject>();
    public GameObject prefab;
    private GameObject spawnedGameObject;
    private bool isSpawned = false;
    private bool isVisible = false;
    public Vector3 callingSpawnPoint = new(0, 0, 0);
    public Vector3 spawnPoint = new(0, 0, 0); //spawn point at virtual environment
    public Vector2 objectLocation = new(10, 10); //object location in real world
    public TMP_Text debugTxt;

    void Start()
    {
        playerLocation = playerLocationManager.getPlayerLocation();
    }

    void Update()
    {
        playerLocation = playerLocationManager.getPlayerLocation();
        
        double distance = locationCalculator.distance(
            playerLocation[0], playerLocation[1],
            objectLocation[0], objectLocation[1]
        );

        isVisible = (distance < visiableDistance);
 
        if(prefab != null)
        {
            if(!isSpawned && isVisible)
            {
                spawnGameObject();
                isSpawned = true;
            }
            else if(isSpawned && distance > destroyDistance)
            {
                destroyGameObject();
                isSpawned = false;
            }
            Debug.Log("Distance: " + locationCalculator.distance(playerLocation[0], playerLocation[1], objectLocation[0], objectLocation[1]));
            debugTxt.text = "Distance: " + locationCalculator.distance(playerLocation[0], playerLocation[1], objectLocation[0], objectLocation[1]);
        }
    }

    void spawnGameObject()
    {
        if(spawnedGameObject == null)
        {
            spawnedGameObject = Instantiate(prefab, spawnPoint, new Quaternion(0, 90, 0, 0));
        }
    }

    void destroyGameObject()
    {
        if (spawnedGameObject != null)
        {
            Destroy(spawnedGameObject);
            spawnedGameObject = null;
        }
    }

    void setPrefab(GameObject gameObject)
    {
        prefab = gameObject;
    }

    public void callNPC()
    {
        if(isVisible)
        {
            spawnedGameObject.transform.position = callingSpawnPoint;
        }
    }
}
```