# 地圖/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)
>
> 手機實測學校圖書館座標
> 
>
> google map找的座標
> 
===================================================
### 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;
}
}
}
```