Mapping, GIS, and GPS

Unity, as we already learned, tracks its objects in 3D space using a point with a Cartesian coordinate reference system of x, y, and z. When we plot a point on a map of the world, it is no different; we need to reference the point, except that now we need to use a spherical or geographic reference system to represent a position on the earth, because as we all know, the earth is spherical. However, converting between a geographic system and Cartesian system is expensive. Many mapping applications, therefore, use an intermediary reference known as earth-centeredearth-fixed (ECEF), which represents mapping data on an earth-fixed Cartesian coordinate reference system. The following is a diagram shows the differences between Cartesian, geographic, and ECEF coordinate reference systems:



Comparison of coordinate reference systems

Now, you may have already noticed that WRLD supports ECEF out of the box. As we mentioned, since ECEF is already in a Cartesian frame of reference, the conversions are much easier and quicker. However, for us, we just want to position the camera at the user's geographic coordinate reference, which we can easily obtain from the user's device using GPS.

Accessing the user's GPS on their device takes a bit of work, but fortunately, we can do this all in one place. Let's open up the SceneController script and make the following modifications:

  1. Add two new properties at the top of the class:
public bool isLoaded;
public string status;
  1. Create a new method just under the constructor:
void Awake()
{
StartCoroutine(GetLocationPoint());
}
  1. The Awake method is a special Unity method that runs when the GameObject first initializes. Inside of the method, we are calling StartCoroutineStartCoroutine is another special method in Unity that allows you to create a coroutine. Coroutines are a way of interrupting or breaking your code flow, doing something else, and then returning to complete your original task. In the call, we are passing in a method call GetLocationPoint(), which sets up that method as a coroutine.
  2. Add the following method to create the coroutine:
IEnumerator GetLocationPoint()
{
}
  1. A coroutine must return IEnumerator. By adding the return type, the method can now yield or interrupt its execution with a yield return statement that returns a YieldInstruction. We will see how to do that shortly.
  2. Just inside GetLocationPoint, add the following line:
AndroidPermissionsManager.RequestPermission(new string[] { "android.permission.ACCESS_FINE_LOCATION" });
  1. This line of code prompts the user for access to the location services, also known as GPS. We do this in order to explicitly identify the user's location, provided that their device's GPS is not being blocked or the user has the location service disabled.
Google has developed their own location service in essence by mapping wireless endpoint MAC addresses to geographic coordinates. Google does this by essentially war driving with its self-driving Street View cars. While those cars drive themselves around, they are also grabbing the MAC address of every wireless device that they can detect at the time of mapping that to a GPS location. As it turns out, this service can actually be more accurate for providing location in more dense metropolitan areas where GPS line of sight is difficult.
  1. Then, add the following:
if (Input.location.isEnabledByUser == false)
{
isLoaded = true;
yield return SetStatus("Location not authorized, starting at 0,0", 1.0f);
yield break;
}
  1. This block of code checks whether the user had GPS enabled; if they don't, there is nothing we can do. We set isLoaded to true, which will be a flag to let outside methods know that we found or didn't find a location. Then, we yield return the results of a call to SetStatus. Remember that because we are in a coroutine, yield return means that we want to interrupt code execution at this point.
  2. Scroll down just past the GetLocationPoint method and add the following new method:
public YieldInstruction SetStatus(string status, float time)
{
this.status = status;
return new WaitForSeconds(time);
}
  1. Inside the method, we are setting our status text, which will be a message we want to display back to the user. Then, we return a new WaitForSeconds(time), where time represents the number of seconds to wait. There are many different forms of YieldInstruction that you can use to break your code. The YieldInstruction here just waits for a set number of seconds and then returns to continue the code where it left off. Keep in mind that after the yield has elapsed, for whatever reason, code will then resume from exactly where it left off.
  2. Return to where we left off in GetLocationPoint. Right after the yield return SetStatus call, we are executing yield break. This line breaks the coroutine and exits the method, which is equivalent to return in a normal method.
  3. Now that we understand coroutines, let's enter the next section of code:
yield return SetStatus("-----STARTING LOCATION SERVICE-----", 1);
Input.location.Start();

// Wait until service initializes
int maxWait = 30;
while (Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
{
yield return new WaitForSeconds(1);
maxWait--;
}
  1. First, we start by setting a status message and letting the user know that we are starting the service, which we then do. After that, we continually loop, breaking every second with yield return new WaitForSeconds(1), adjusting our counter maxWait for every iteration. We need to wait for the location service to initialize; sometimes this can take a while.
  2. Enter the following code to handle when our counter has expired (maxWait<1):
// Service didn't initialize in 20 seconds
if (maxWait < 1)
{
yield return SetStatus("ERROR - Location service timed out, setting to 0,0,0", 10.0f);
isLoaded = true;
yield break;
}
  1. Inside the if block, we set the status and loaded flag. Then, we return from the coroutine with yield break.
  2. Next, we want to handle when the service fails or starts by entering the following:
if (Input.location.status == LocationServiceStatus.Failed)
{
yield return SetStatus("ERROR - Unable to determine device location.", 10.0f);
isLoaded = true;
yield break;
}
else
{
//set the position
yield return SetStatus("-----SETTING LOCATION----", 10.0f);
position = new LatLongAltitude(Input.location.lastData.latitude, Input.location.lastData.longitude, Input.location.lastData.altitude);
isLoaded = true;
}
  1. This code handles the service failure or success. In the failure path, we set an error message and exit. Otherwise, we set a status and wait for 10 seconds. We do this so that the user can read the message. Then, we set the position according to the geographic coordinates the device provides us with.
  2. Finally, we stop the service with this:
 Input.location.Stop();
  1. We stop the service because we don't need to continually get location updates.
    If you want to keep the service open and use it to track the user's location, such as Pokemon Go, then just ensure that you stop the service when the object is being destroyed. You can do this in a method called OnDisable(), which is another special Unity method that is used to clean up the object.
  2. At this point, we also want to update and overload the LoadScene method with the following code:
public void LoadScene(string scene)
{
SceneManager.LoadScene(scene, LoadSceneMode.Single);
}

public void LoadScene(string scene, Camera mapCamera)
{
if (Api.Instance.CameraApi.HasControlledCamera)
{
mapCamera = Api.Instance.CameraApi.GetControlledCamera();
}
else if (mapCamera == null) throw new ArgumentNullException("Camera", "Camera must be set, if map is not controlled.");
position = Api.Instance.CameraApi.ScreenToGeographicPoint(new Vector3(mapCamera.pixelHeight / 2, mapCamera.pixelWidth / 2, mapCamera.nearClipPlane), mapCamera);

Debug.LogFormat("cam position set {0}:{1}:{2}", position.GetLatitude(), position.GetLongitude(), position.GetAltitude());
SceneManager.LoadScene(scene, LoadSceneMode.Single);
}
  1. We overloaded the method in order to allow two different behaviors when switching scenes. The new method we added won't worry about setting the position for the camera. We also added some logging, so we can see what values are being set by looking at our Android debug tools while running the app.
  2. Save the file when you are done.

The code we just set up was originally derived from the Unity sample, but it has been modified for your reuse. Since accessing the location service can take a while, we will add a new scene in order to handle the location service starting up. This will be a splash screen that you can make prettier later on.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181