private void PruneExpiredAndOutOfRangeLocations(GPSLatLong currentLatLong)
        {
            var cachedS2CellsToRemove = new HashSet <ulong>();
            var utcNow = DateTime.UtcNow;

            foreach (var cell in this.s2CellsCache.Values)
            {
                bool isEntireCellExpired = cell.ExpirationUtc < utcNow;

                if (isEntireCellExpired)
                {
                    cachedS2CellsToRemove.Add(cell.S2CellId);
                }

                // Removing Locations
                foreach (var location in cell.Locations)
                {
                    if (isEntireCellExpired || this.IsLocationInLoadRange(location, currentLatLong) == false)
                    {
                        this.RemoveLocationFromMap(location);
                    }
                }
            }

            foreach (var s2CellId in cachedS2CellsToRemove)
            {
                this.s2CellsCache.Remove(s2CellId);
            }
        }
        private void OnGPSReceived(GPSLatLong latLong)
        {
            if (this.isInitialized == false)
            {
                this.isInitialized = true;
                this.InitializeMap(latLong);
                return;
            }

            // Keeping tracking of the current LatLong
            this.currentLatLong = latLong;

            // Tell the map service of our new location
            if (this.isMapServiceLoaded)
            {
                this.mapsService.MoveFloatingOrigin(new LatLng(this.currentLatLong.Latitude, this.currentLatLong.Longitude));

                // Checking out if we need to reload maps content
                var lastLoadDistance = GPSUtil.DistanceInMeters(this.lastLoadLatLng, this.currentLatLong);

                if (lastLoadDistance > this.reloadDistanceInMeters)
                {
                    if (this.printDebugOutput)
                    {
                        Debug.Log("GoogleMapsManager Reloading Map");
                    }

                    this.LoadMap();
                }
            }
        }
 private async void OnGPSChanged(GPSLatLong latLong)
 {
     if (this.isInitialized == false || GPSUtil.DistanceInMeters(this.lastUpdateLatLong, latLong) > this.reloadDistanceInMeters)
     {
         this.isInitialized = true;
         await this.UpdateLocation(latLong);
     }
 }
        private async Task UpdateLocation(GPSLatLong currentLatLong)
        {
            this.lastUpdateLatLong = currentLatLong;
            this.PruneExpiredAndOutOfRangeLocations(currentLatLong);
            await this.FindAndAddAllNewLocations(currentLatLong);

            // Since we moved, lets update the locations for all the lbe locations
            foreach (var location in this.locationIdToGameObject)
            {
                var locationId  = location.Key;
                var lbeLocation = this.locationIdToLBELocation[locationId];
                location.Value.transform.localPosition = GoogleMapsManager.Instance.GetPosition(lbeLocation.LatLong);
            }
        }
        private async Task FindAndAddAllNewLocations(GPSLatLong currentLatLong)
        {
            foreach (var s2CellId in this.GetCurrentS2CellIds(currentLatLong))
            {
                var s2Cell = await this.GetS2Cell(s2CellId.Id);

                foreach (var location in s2Cell.Locations)
                {
                    if (this.IsLocationInLoadRange(location, currentLatLong))
                    {
                        this.AddLocationToMap(location);
                    }
                }
            }
        }
        private void LoadMap()
        {
            this.lastLoadLatLng = this.currentLatLong;

            // Load map with default options
            this.mapsService
            .MakeMapLoadRegion()
            .AddCircle(Vector3.zero, this.loadRadiusInMeters)
            .Load(this.gameObjectOptions);

            // Adding flags so we know when the map is loading, and has finished being loaded
            this.mapsService.Events.MapEvents.Loaded.AddListener(this.MapLoadFinished);

            // If the serverice is already loaded, then probably have things to unload
            if (this.isMapServiceLoaded)
            {
                this.ExecuteDelayed(1.0f, this.UnloadOutside);
            }
        }
        private IEnumerable <S2CellId> GetCurrentS2CellIds(GPSLatLong currentLatLong)
        {
            // Info on S2 Geometry - https://s2geometry.io/
            // Code taken from this video - https://www.youtube.com/watch?v=UZsf3bqmmKs  (6:26)
            // Min/Max Level and Max Cells taken from here - https://s2.sidewalklabs.com/regioncoverer/
            // Using Nuget S2Geometry vs 1.0.3 (https://www.nuget.org/packages/S2Geometry/)
            S2RegionCoverer rc = new S2RegionCoverer();

            rc.MaxCells = S2RegionCoverer.DefaultMaxCells;
            rc.MinLevel = rc.MaxLevel = 14;

            // var southwestLat = new GPSLatLong { Latitude = 47.013859, Longitude = -122.920682 };
            // var northeast = new GPSLatLong { Latitude = 47.033532, Longitude = -122.894687 };
            //
            // S2LatLng low = S2LatLng.FromDegrees(southwestLat.Latitude, southwestLat.Longitude);
            // S2LatLng high = S2LatLng.FromDegrees(northeast.Latitude, northeast.Longitude);
            //
            // S2LatLngRect latLngRect = new S2LatLngRect(low, high);
            // return rc.GetCovering(latLngRect);


            //// https://stackoverflow.com/questions/7477003/calculating-new-longitude-latitude-from-old-n-meters
            //// number of km per degree = ~111km (111.32 in google maps, but range varies
            //// between 110.567km at the equator and 111.699km at the poles)
            //// 1km in degree = 1 / 111.32km = 0.0089
            //// 1m in degree = 0.0089 / 1000 = 0.0000089
            double meters   = this.loadRadiusInMeters;
            double coef     = meters * 0.0000089;
            double new_lat  = /*currentLatLong.Latitude +*/ coef;
            double new_long = /*currentLatLong.Longitude +*/ coef / Math.Cos(currentLatLong.Latitude * (Math.PI / 180.0));

            S2LatLng center = S2LatLng.FromDegrees(currentLatLong.Latitude, currentLatLong.Longitude);
            S2LatLng size   = S2LatLng.FromDegrees(new_lat, new_long);

            return(rc.GetCovering(S2LatLngRect.FromCenterSize(center, size)));
        }
 private bool IsLocationInLoadRange(LBELocation location, GPSLatLong currentLatLong)
 {
     return(GPSUtil.DistanceInMeters(location.LatLong, currentLatLong) < this.loadRadiusInMeters);
 }
 public Vector3 GetPosition(GPSLatLong latLong)
 {
     return(this.mapsService.Projection.FromLatLngToVector3(new LatLng(latLong.Latitude, latLong.Longitude)));
 }
        private void InitializeMap(GPSLatLong latLong)
        {
            if (this.printDebugOutput)
            {
                Debug.Log($"GoogleMapsManager Initializing Map To ({latLong})");
            }

            this.currentLatLong = latLong;

            // Registering for error handling (Taken from BaseMapLoader.cs)
            this.mapsService.Events.MapEvents.LoadError.AddListener(args =>
            {
                switch (args.DetailedErrorCode)
                {
                case MapLoadErrorArgs.DetailedErrorEnum.NetworkError:
                    {
                        // Handle errors caused by a lack of internet connectivity (or other network problems).
                        if (Application.internetReachability == NetworkReachability.NotReachable)
                        {
                            Debug.LogError("The Maps SDK for Unity must have internet access in order to run.");
                        }
                        else
                        {
                            Debug.LogErrorFormat(
                                "The Maps SDK for Unity was not able to get a HTTP response after " +
                                "{0} attempts.\nThis suggests an issue with the network, or with the " +
                                "online Semantic Tile API, or that the request exceeded its deadline  " +
                                "(consider using MapLoadErrorArgs.TimeoutSeconds).\n{1}",
                                args.Attempts,
                                string.IsNullOrEmpty(args.Message)
                                    ? string.Concat("Specific error message received: ", args.Message)
                                    : "No error message received.");
                        }

                        return;
                    }

                case MapLoadErrorArgs.DetailedErrorEnum.UnsupportedClientVersion:
                    {
                        Debug.LogError(
                            "The specific version of the Maps SDK for Unity being used is no longer " +
                            "supported (possibly in combination with the specific API key used).");

                        return;
                    }
                }

                // For all other types of errors, just show the given error message, as this should describe
                // the specific nature of the problem.
                Debug.LogError(args.Message);

                // Note that the Maps SDK for Unity will automatically retry failed attempts, unless
                // args.Retry is specifically set to false during this callback.
            });

            // Set real-world location to load
            this.mapsService.InitFloatingOrigin(new LatLng(latLong.Latitude, latLong.Longitude));

            // Configure Map Styling
            this.gameObjectOptions = this.GetMapStyle();

            this.LoadMap();
        }