Day 19 : Geo-Tagging Location in AR using Unity and AR Foundation


This is part of a 30 day sprint where we try to publish 30 projects in 30 days, this means building full projects from scratch. Double checking the code, writing the tutorial and then posting it . If there are any typos do let us know and I hope you enjoy this tutorial and project.

Introduction

In today’s article, we’ll create an AR Application where we can tag our geolocation and save them to a file on the device. We will also write up a small script that calculates the distance between two geo-locations. 

This information can then be fetched and manipulated in any way possible. For example,  the location cards can come up if you’re a certain distance from them. You can also build a game like Pokemon Go using it, where objects could be spawned at those geo-locations in the AR World.

We’ll save a name for the geo-location and show them on an AR Card.  All the geotags will be saved on the local storage using JSON Serialization. These tags can then also be fetched using JSON  deserialization.

This is what the end result will look like.

Getting Started

We will use the AR Foundation setup template we created in the article here. You can read more projects and articles on AR Development here. We also used location data to fetch weather information in a previous article.

Prerequisites

  1. Unity 2019.3
  2. AR Foundation setup from the linked article
  3. C# programming and understanding
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close

Implementation

We’ll create a main GeoTagManager script that will manage the location and create the Geo-Tag Canvas prefab. Also, a GeoTag script that we will use to save the GeoTag data. Finally, we’ll serialize these GeoTag classes into a JSON file.

We will have a UI, where we enter the name for the geo-tag with a create button. When the button is clicked, we’ll fetch the current location and save it to the GeoTag object and it will be added to a global list. We will then serialize the list into a JSON object and finally into a string. The string will then be saved into a file.

GeoTag

Create a new script called GeoTag.cs

This is the whole script

using System;
using SimpleJSON;

[Serializable]
public class GeoTag 
{
    public float latitude;
    public float longitude;
    public string name;
}

Notice here that the class has a Serializable property. Serialization is basically converting an object into bytes so it can either be stored or transmitted. Here we mark the class as serializable because we want the instances of this class ie., the objects to be converted into bytes. These can then be converted into text for our JSON format and stored.

Also, note that not all data types can be serialized. But the common ones can be.

Our class will consist of the latitude, longitude and the name of the geotag.

GeoTagDisplay Prefab

Next, Let’s create a prefab that we’ll use to show the information on.

Create a world space canvas and name it GeoTag with the following transform properties.

Create two UI text as their children and name then Title and Location. Set their size appropriately. The source code attached to this article has everything setup.

Add a script to it named GeoTagDisplay

This the whole script

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class GeoTagDisplay : MonoBehaviour
{
    public TextMeshProUGUI title;
    public TextMeshProUGUI location;

    public void Initialize(GeoTag geoTag)
    {
        this.title.text = geoTag.name;
        this.location.text = geoTag.latitude.ToString() +  ", " + geoTag.longitude;
    }
}

The Initialize method is what we’ll call in the GeoTagManager script next to set the appropriate properties. Since this displays the geotag info, we can just pass a GeoTag object and read from that.

Save the script and in the Unity Editor, assign the proper references to the UI Text gameobjects that we created before.

Now, to make this a prefab, simply select the GeoTag gameobject and drag & drop it into the Project View. Once done, the gameobject can be deleted from the Hierarchy.

JSON library using LitJson

Although Unity does provide some JSON serialization, it has its limitations. To make it easier, we’ll use an open library called LitJson. You can download it from here. Just copy and LitJson.dll into the project root and that’s it.

GeoTagManager

Create a new script and name in GeoTagManager

This is the whole script.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using LitJson;
using System;

public class GeoTagManager : MonoBehaviour
{
    public Text statusText;
    public InputField geoTagName;
    public CanvasGroup createGeoTagCanvas;
    public GameObject displayPrefab;

    private bool isCreating = false;
    private string savePath;
    private List<GeoTag> geoTags = new List<GeoTag>();
    private LocationInfo currentLocation;

    private void Start()
    {
        savePath = Application.persistentDataPath + "/tags.json";
        FetchTags();
    }

    public void AddGeoTag()
    {
        createGeoTagCanvas.alpha = 1;
        createGeoTagCanvas.blocksRaycasts = true;
    }

    public void CreateNewGeoTag()
    {
        if(!isCreating)
            StartCoroutine(FetchLocationData());
    }

    private IEnumerator FetchLocationData()
    {
        // First, check if user has location service enabled
        if (!Input.location.isEnabledByUser)
            yield break;

        isCreating = true;

        // Start service before querying location
        Input.location.Start();

        statusText.text = "Fetching Location..";

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

        // Service didn't initialize in 20 seconds
        if (maxWait < 1)
        {
            statusText.text = "Location Timed out";
            yield break;
        }

        // Connection has failed
        if (Input.location.status == LocationServiceStatus.Failed)
        {
            statusText.text = "Unable to determine device location";
            yield break;
        }
        else
        {
            //Create GeoTag
            GeoTag geoTag = new GeoTag();
            geoTag.latitude = Input.location.lastData.latitude;
            geoTag.longitude = Input.location.lastData.longitude;
            geoTag.name = geoTagName.text;
            geoTags.Add(geoTag);
            SaveTags();

            //Create tag display
            var obj = Instantiate(displayPrefab, Camera.main.transform.position + (Camera.main.transform.forward * 1), Quaternion.identity);
            obj.transform.rotation = Quaternion.LookRotation(transform.position - Camera.main.transform.position);
            obj.GetComponent<GeoTagDisplay>().Initialize(geoTag);
        }

        createGeoTagCanvas.alpha = 0;
        createGeoTagCanvas.blocksRaycasts = false;
        Input.location.Stop();
        isCreating = false;
    }

    private void FetchTags()
    {
        JsonData jsonData = JsonMapper.ToObject(File.ReadAllText(savePath));
        for(int i = 0; i < jsonData.Count; i++)
        {
            GeoTag geoTag = new GeoTag();
            geoTag.name = (string)jsonData[i]["name"];
            geoTag.latitude = (float)jsonData[i]["latitude"];
            geoTag.longitude = (float)jsonData[i]["longitude"];
            geoTags.Add(geoTag);
        }
    }

    private void SaveTags()
    {
        string jsonData = JsonMapper.ToJson(geoTags);
        File.WriteAllText(savePath, jsonData);
    }


    private float GetDistance(float lat1, float lon1, float lat2, float lon2, char unit)
    {
        if ((Math.Abs(lat1 - lat2) < Mathf.Epsilon) && (Math.Abs(lon1 - lon2) < Mathf.Epsilon))
        {
            return 0;
        }
        else
        {
            double theta = lon1 - lon2;
            double dist = Math.Sin((lat1 * Mathf.Deg2Rad)) * Math.Sin((lat2 * Mathf.Deg2Rad)) + Math.Cos((lat1 * Mathf.Deg2Rad)) * Math.Cos((lat2 * Mathf.Deg2Rad)) * Math.Cos((theta * Mathf.Deg2Rad));
            dist = Math.Acos(dist);
            dist = dist * Mathf.Deg2Rad;
            dist = dist * 60 * 1.1515;
            if (unit == 'K')
            {
                dist = dist * 1.609344;
            }
            else if (unit == 'N')
            {
                dist = dist * 0.8684;
            }
            return (float)dist;
        }
    }
}

We’ll create a canvas that has an input field to enter the geotag name and a button whose action will be to call the CreateGeoTag method. The canvas will be initially hidden and will show up by setting another button action to AddGeoTag. We show and hide the Canvas group by adding a CanvasGroup component to it. CanvasGroup has an alpha and blockraycasts property that we can set to show/hide and allow/block interactions on it.

Next, in the FetchLocationData coroutine, we’ll enable LocationService and wait till it’s running. If no errors have occurred, we will fetch the current location. Then we create a new GeoTag object with the current latitude and longitude. Finally, we create a new GeoTagDisplay gameobject, set it to face the user, and call the Initialize() method on it with the created GeoTag object.

Here are how the button actions are assigned. Simply, drag the GeoTagManager gameobject which has the GeoTagManager script onto the OnClick() property of the button. Then select the public method from the dropdown.

Saving and Reading GeoTags

Now that we have created the geo-tags. We can save them using JSON serialization into a file. This file will contain all the tags and their properties in the JSON format.

The save location is set to Application.persistentDataPath/tags.json. For more info about the persistentDataPath API, read here.

Whenever we create a new GeoTag, we add it to the global List<GeoTag> list called geotags. We will serialize this array.

In the SaveTags() method, we can simply call JsonMapper.ToJson() from the LitJson library and pass it the geotags array object. It will then convert the array into a JSON string. Finally, we can write this to our save file using File.WriteAllText method.

Distance and Measurements

Finally, if you would like to find the distance between two geotags we have a method for that as well.

Since geo-coordinates are locations on a sphere, they cannot be resolved by using calculations on a plane. This method returns the distance in either Meters or KM’s bypassing in ‘M’ or ‘KM’ as the last parameter. The first parameters are the two lats and long values.

To convert any geo-location coordinates into 3D space coordinates for placing any object at a geo-location in AR, like pokemon-go we have to use the Universal Transverse Mercator coordinate system to convert them into 3D space.

Following is the simplest form.

x = r * sin (lat) * cos (lon)
y = r * sin (lat) * sin (lon)
z = r * cos (lat)

Where r is radius as -> altitude = r + height_above_r

That’s it. Now you can build the project and test it on your Android or iOS device.

Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close

Leave a Reply

Your email address will not be published. Required fields are marked *

Join 30 AR projects in 30 days and become a better AR developer
GET FREE LESSONS

Learn AR projects & source code

We shall send you an email with the link to the best starter lesson in 5 minutes
Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close
Download source code for this project & get updates of future projects
Download Source Code

Download Source Code

Close