public bool IsThereNewPoints(string kmlFeedresult, KMLInfo track)
        {
            string              LastPointTime = track.LastPointTimestamp;
            XDocument           xmlTrack      = XDocument.Parse(kmlFeedresult);
            XmlNamespaceManager ns            = new XmlNamespaceManager(new NameTable());

            ns.AddNamespace("kml", "http://www.opengis.net/kml/2.2");

            //set placemark as a root element
            var placemarks       = xmlTrack.XPathSelectElements("//kml:Placemark", ns);
            var NewLastPointTime = NewLastTimestamp(placemarks, ns);

            DateTime dTLast = new DateTime();
            DateTime dTNew  = DateTime.UtcNow.ToUniversalTime();

            if (!string.IsNullOrEmpty(NewLastPointTime))
            {
                dTNew = DateTime.Parse(NewLastPointTime).ToUniversalTime();
            }
            if (!string.IsNullOrEmpty(LastPointTime))
            {
                dTLast = DateTime.SpecifyKind(DateTime.Parse(LastPointTime, CultureInfo.CreateSpecificCulture("en-US")), DateTimeKind.Utc);
            }

            if (dTNew > dTLast)
            {
                return(true);
            }
            return(false);
        }
Example #2
0
        public static async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "DeleteInReachFeedFromCosmos/{userWebId}/{trackId}")] HttpRequest req,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree",
                 PartitionKey = "{userWebId}",
                 Id = "{trackId}"
                 )] KMLInfo kMLInfo,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree",
                 SqlQuery = "SELECT * FROM c WHERE c.groupid = 'user'"
                 )] IEnumerable <InReachUser> inReachUsers,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree"
                 )] DocumentClient documentClient,
            ExecutionContext context)
        {
            var config = new ConfigurationBuilder()
                         .SetBasePath(context.FunctionAppDirectory)
                         .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                         .AddEnvironmentVariables()
                         .Build();
            var StorageContainerConnectionString = config["StorageContainerConnectionString"];
            CloudStorageAccount storageAccount   = CloudStorageAccount.Parse(StorageContainerConnectionString);
            CloudBlobClient     blobClient       = storageAccount.CreateCloudBlobClient();

            HelperKMLParse  helperKMLParse  = new HelperKMLParse();
            ClaimsPrincipal Identities      = req.HttpContext.User;
            var             checkUser       = new HelperCheckUser();
            var             LoggedInUser    = checkUser.LoggedInUser(inReachUsers, Identities);
            var             IsAuthenticated = false;

            if (LoggedInUser.status == Status.ExistingUser)
            {
                IsAuthenticated = true;
            }

            if (IsAuthenticated)
            {
                //selfLink is like this: "dbs/auo6AA==/colls/auo6AOdfluE=/docs/auo6AOdfluEnFwIAAAAAAA==/";
                await documentClient.DeleteDocumentAsync(kMLInfo._self, new RequestOptions { PartitionKey = new PartitionKey(LoggedInUser.userWebId) });

                //delete blobs
                foreach (var blob in helperKMLParse.Blobs)
                {
                    var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/{blob.BlobName}.kml";
                    await helperKMLParse.RemoveBlobAsync(blobName, blobClient);
                }
            }
            return(new OkObjectResult(IsAuthenticated));
        }
Example #3
0
        public async Task <string> GetKMLAsync(KMLInfo kMLInfo)
        {
            string userUrl   = $"{kMLInfo.InReachWebAddress}{CreateDateParameter(kMLInfo.d1, "d1")}{CreateDateParameter(kMLInfo.d2, "d2")}";
            string garminUrl = $"https://share.garmin.com/Feed/Share/{userUrl}";

            //get the data from garmin based on start date in every 5 minutes
            var http = new HttpClient();
            var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{kMLInfo.InReachWebPassword}"));

            http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
            HttpResponseMessage response = http.GetAsync(garminUrl).Result;

            return(await response.Content.ReadAsStringAsync());
        }
        public static async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree"
                 )]
            IAsyncCollector <KMLInfo> asyncCollectorKMLInfo,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree",
                 SqlQuery = "SELECT * FROM c WHERE c.groupid = 'user'"
                 )] IEnumerable <InReachUser> inReachUsers,
            ExecutionContext context
            )
        {
            var config = new ConfigurationBuilder()
                         .SetBasePath(context.FunctionAppDirectory)
                         .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                         .AddEnvironmentVariables()
                         .Build();
            var StorageContainerConnectionString = config["StorageContainerConnectionString"];

            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageContainerConnectionString);
            CloudBlobClient     blobClient     = storageAccount.CreateCloudBlobClient();

            ClaimsPrincipal Identities      = req.HttpContext.User;
            var             checkUser       = new HelperCheckUser();
            var             LoggedInUser    = checkUser.LoggedInUser(inReachUsers, Identities);
            var             IsAuthenticated = false;

            if (LoggedInUser.status == Status.ExistingUser)
            {
                IsAuthenticated = true;
            }

            if (IsAuthenticated)
            {
                string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                if (requestBody != "")
                {
                    var kMLFull = JsonConvert.DeserializeObject <KMLFull>(requestBody);

                    KMLInfo kMLInfo = new KMLInfo()
                    {
                        id                 = kMLFull.id,
                        Title              = kMLFull.Title,
                        d1                 = kMLFull.d1,
                        d2                 = kMLFull.d2,
                        InReachWebAddress  = kMLFull.InReachWebAddress,
                        InReachWebPassword = kMLFull.InReachWebPassword,
                        UserTimezone       = kMLFull.UserTimezone
                    };
                    var blobs = helperKMLParse.Blobs;
                    blobs.ForEach(x => x.BlobValue = "");
                    blobs.First(x => x.BlobName == "plannedtrack").BlobValue = kMLFull.PlannedTrack;

                    //1. replace ö->o ä->a etc
                    //2. first: UrlEncode is removing all weird charactes and spaces
                    //3. second: HttpUtility.UrlEncode is removing some not named weird characters, just in case
                    //4. third: UrlEncode again is removing possible %-marks
                    //setting id field only on initial track creation
                    if (string.IsNullOrEmpty(kMLInfo.id))
                    {
                        string id = RemoveDiacritics(kMLInfo.Title);
                        id         = UrlEncode(HttpUtility.UrlEncode(UrlEncode(id)));
                        kMLInfo.id = id;
                    }
                    kMLInfo.LastPointTimestamp = "";
                    kMLInfo.groupid            = LoggedInUser.userWebId;
                    kMLInfo.IsLongTrack        = false;

                    var      dateD1   = DateTime.Parse(kMLInfo.d1).AddHours(-kMLInfo.UserTimezone);
                    var      dateD2   = DateTime.Parse(kMLInfo.d2).AddDays(1).AddHours(-kMLInfo.UserTimezone);
                    TimeSpan timeSpan = dateD2 - dateD1;
                    //this setting affects of parsing all points (slow) or not. Depending on the duration of the track
                    if (timeSpan.TotalDays > 2)
                    {
                        kMLInfo.IsLongTrack = true;
                    }

                    kMLInfo.d1 = dateD1.ToString("yyyy-MM-ddTHH:mm:ssZ");
                    kMLInfo.d2 = dateD2.ToString("yyyy-MM-ddTHH:mm:ssZ");

                    HelperGetKMLFromGarmin helperGetKMLFromGarmin = new HelperGetKMLFromGarmin();
                    //get feed grom garmin
                    var kmlFeedresult = await helperGetKMLFromGarmin.GetKMLAsync(kMLInfo);

                    //parse and transform the feed and save to database
                    helperKMLParse.ParseKMLFile(kmlFeedresult, kMLInfo, blobs, new List <Emails>(), LoggedInUser);
                    await asyncCollectorKMLInfo.AddAsync(kMLInfo);

                    //save blobs
                    foreach (var blob in blobs)
                    {
                        var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/{blob.BlobName}.kml";
                        await helperKMLParse.AddToBlobAsync(blobName, blob.BlobValue, blobClient);
                    }
                }
            }
            return(new OkObjectResult(IsAuthenticated));
        }
        static async Task ManageTodayTrack(InReachUser LoggedInUser, IAsyncCollector <KMLInfo> addDocuments, DocumentClient client, Uri collectionUri, string TodayTrackId, string SendEmailFunctionUrl, string SendEmailFunctionKey, string WebSiteUrl, CloudBlobClient blobClient)
        {
            var dated1     = DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-dd");
            var dated2     = DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-dd");
            var dateTimed1 = DateTime.Parse(dated1).AddHours(-LoggedInUser.UserTimezone).ToString("yyyy-MM-ddTHH:mm:ssZ");
            var dateTimed2 = DateTime.Parse(dated2).AddDays(1).AddHours(-LoggedInUser.UserTimezone).ToString("yyyy-MM-ddTHH:mm:ssZ");

            KMLInfo kMLInfo = new KMLInfo()
            {
                id                 = TodayTrackId,
                Title              = "Today's Live Track",
                d1                 = dateTimed1,
                d2                 = dateTimed2,
                groupid            = LoggedInUser.userWebId,
                InReachWebAddress  = LoggedInUser.InReachWebAddress,
                InReachWebPassword = LoggedInUser.InReachWebPassword,
                UserTimezone       = LoggedInUser.UserTimezone,
                IsLongTrack        = false
            };

            //create Today's track
            if (LoggedInUser.Active)
            {
                HelperGetKMLFromGarmin helperGetKMLFromGarmin = new HelperGetKMLFromGarmin();

                var emails = new List <Emails>();
                //get feed grom garmin
                var kmlFeedresult = await helperGetKMLFromGarmin.GetKMLAsync(kMLInfo);

                var blobs = helperKMLParse.Blobs;
                //parse and transform the feed and save to database
                helperKMLParse.ParseKMLFile(kmlFeedresult, kMLInfo, blobs, emails, LoggedInUser, WebSiteUrl);
                await addDocuments.AddAsync(kMLInfo);

                //save blobs
                foreach (var blob in blobs)
                {
                    var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/{blob.BlobName}.kml";
                    await helperKMLParse.AddToBlobAsync(blobName, blob.BlobValue, blobClient);
                }

                //sending out emails
                if (emails.Any())
                {
                    HttpClient httpClient           = new HttpClient();
                    Uri        SendEmailFunctionUri = new Uri($"{SendEmailFunctionUrl}?code={SendEmailFunctionKey}");
                    var        returnMessage        = await httpClient.PostAsJsonAsync(SendEmailFunctionUri, emails);
                }
            }
            //delete Today's track
            if (!LoggedInUser.Active)
            {
                //select and delete document
                var queryOne = new SqlQuerySpec("SELECT c._self, c.groupid, c.id FROM c WHERE c.id = @id",
                                                new SqlParameterCollection(new SqlParameter[] { new SqlParameter {
                                                                                                    Name = "@id", Value = kMLInfo.id
                                                                                                } }));
                KMLInfo kML = client.CreateDocumentQuery(collectionUri, queryOne, new FeedOptions {
                    PartitionKey = new PartitionKey(kMLInfo.groupid)
                }).AsEnumerable().FirstOrDefault();
                if (!(kML is null))
                {
                    //delete metadata
                    await client.DeleteDocumentAsync(kML._self, new RequestOptions { PartitionKey = new PartitionKey(kML.groupid) });

                    //delete blobs
                    foreach (var blob in helperKMLParse.Blobs)
                    {
                        var blobName = $"{kML.groupid}/{kML.id}/{blob.BlobName}.kml";
                        await helperKMLParse.RemoveBlobAsync(blobName, blobClient);
                    }
                }
            }
        }
        public static async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "GetKMLFeedMetadata/{GroupId}/{id}")] HttpRequest req,
            string GroupId,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree",
                 PartitionKey = "{GroupId}",
                 Id = "{id}"
                 )]
            KMLInfo kMLInfo,
            [CosmosDB(
                 databaseName: "FreeCosmosDB",
                 collectionName: "TrackMe",
                 ConnectionStringSetting = "CosmosDBForFree",
                 SqlQuery = "SELECT * FROM c WHERE c.groupid = 'user'"
                 )] IEnumerable <InReachUser> inReachUsers,
            ExecutionContext context)
        {
            var config = new ConfigurationBuilder()
                         .SetBasePath(context.FunctionAppDirectory)
                         .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                         .AddEnvironmentVariables()
                         .Build();
            var StorageContainerConnectionString = config["StorageContainerConnectionString"];

            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageContainerConnectionString);
            CloudBlobClient     blobClient     = storageAccount.CreateCloudBlobClient();

            ClaimsPrincipal Identities      = req.HttpContext.User;
            var             checkUser       = new HelperCheckUser();
            var             LoggedInUser    = checkUser.LoggedInUser(inReachUsers, Identities);
            var             IsAuthenticated = false;

            if (LoggedInUser.status == Status.ExistingUser)
            {
                IsAuthenticated = true;
            }

            KMLData kMLData = new KMLData()
            {
                id                 = kMLInfo.id,
                d1                 = kMLInfo.d1,
                d2                 = kMLInfo.d2,
                Title              = kMLInfo.Title,
                InReachWebAddress  = kMLInfo.InReachWebAddress,
                InReachWebPassword = kMLInfo.InReachWebPassword
            };
            var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/plannedtrack.kml";

            kMLData.PlannedTrack = await helperKMLParse.GetFromBlobAsync(blobName, blobClient);

            if (IsAuthenticated)
            {
                if (GroupId == LoggedInUser.userWebId)
                {
                    return(new OkObjectResult(kMLData));
                }
                else
                {
                    return(new OkObjectResult("Not your track"));
                }
            }
            return(new OkObjectResult(IsAuthenticated));
        }
        public bool ParseKMLFile(string kmlFeedresult, KMLInfo kMLInfo, List <Blob> blobs, List <Emails> emails, InReachUser user = null, string webSiteUrl = "")
        {
            //open and parse KMLfeed
            XDocument           xmlTrack = XDocument.Parse(kmlFeedresult);
            XmlNamespaceManager ns       = new XmlNamespaceManager(new NameTable());

            ns.AddNamespace("kml", "http://www.opengis.net/kml/2.2");
            var       defaultns = xmlTrack.Root.GetDefaultNamespace();
            XDocument xmlLineString;
            XDocument xmlPlacemarks;
            XDocument xmlPlacemarksWithMessages;
            XDocument xmlLastPlacemark;

            XElement NewPlacemark =
                new XElement(defaultns + "Folder",
                             new XElement(defaultns + "Placemark",
                                          new XElement(defaultns + "ExtendedData",
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Time"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Velocity"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Distance"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "TimeElapsed"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Elevation"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Course"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Text"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Latitude"),
                                                                    new XElement(defaultns + "value")),
                                                       new XElement(defaultns + "Data",
                                                                    new XAttribute("name", "Longitude"),
                                                                    new XElement(defaultns + "value"))
                                                       ),
                                          new XElement(defaultns + "styleUrl"),
                                          new XElement(defaultns + "Point",
                                                       new XElement(defaultns + "coordinates")
                                                       )));

            XElement newDoc = new XElement(defaultns + "kml", new XElement(defaultns + "Document"));

            XElement newLineString =
                new XElement(defaultns + "Folder",
                             new XElement(defaultns + "Placemark",
                                          new XElement(defaultns + "name"),
                                          new XElement(defaultns + "description"),
                                          new XElement(defaultns + "LineString",
                                                       new XElement(defaultns + "coordinates"
                                                                    ))));
            var PlacemarksAll = blobs.First(x => x.BlobName == "placemarksall").BlobValue;
            var PlacemarksMsg = blobs.First(x => x.BlobName == "placemarksmsg").BlobValue;
            var TrackLine     = blobs.First(x => x.BlobName == "trackline").BlobValue;
            var LastPlacemark = string.Empty;

            //create Xdocuments if they are empty
            if (!string.IsNullOrEmpty(PlacemarksAll))
            {
                xmlPlacemarks = XDocument.Parse(PlacemarksAll);
            }
            else
            {
                xmlPlacemarks = new XDocument(new XElement(newDoc));
                AddStyles(xmlPlacemarks.XPathSelectElement("//kml:Document", ns), defaultns);
            }
            if (!string.IsNullOrEmpty(PlacemarksMsg))
            {
                xmlPlacemarksWithMessages = XDocument.Parse(PlacemarksMsg);
            }
            else
            {
                xmlPlacemarksWithMessages = new XDocument(new XElement(newDoc));
                AddStyles(xmlPlacemarksWithMessages.XPathSelectElement("//kml:Document", ns), defaultns);
            }
            if (!string.IsNullOrEmpty(TrackLine))
            {
                xmlLineString = XDocument.Parse(TrackLine);
            }
            else
            {
                xmlLineString = new XDocument(new XElement(newDoc));
                var documentLineString = xmlLineString.XPathSelectElement("//kml:Document", ns);
                documentLineString.Add(newLineString);
            }

            xmlLastPlacemark = new XDocument(new XElement(newDoc));
            AddStyles(xmlLastPlacemark.XPathSelectElement("//kml:Document", ns), defaultns);

            var documentLastPlacemark     = xmlLastPlacemark.XPathSelectElement("//kml:Document", ns);
            var documentPlacemarkMessages = xmlPlacemarksWithMessages.XPathSelectElement("//kml:Document", ns);
            var documentPlacemark         = xmlPlacemarks.XPathSelectElement("//kml:Document", ns);

            //set placemark as a root element
            var placemarks = xmlTrack.XPathSelectElements("//kml:Placemark", ns);

            double   lastLatitude  = kMLInfo.LastLatitude;
            double   lastLongitude = kMLInfo.LastLongitude;
            double   totalDistance = kMLInfo.LastTotalDistance;
            TimeSpan totalTime     = new TimeSpan();

            TimeSpan.TryParse(kMLInfo.LastTotalTime, out totalTime);

            var      lastpointTs = kMLInfo.LastPointTimestamp;
            DateTime lastDate    = new DateTime();

            if (!string.IsNullOrEmpty(lastpointTs))
            {
                lastDate = DateTime.Parse(lastpointTs, CultureInfo.InvariantCulture).AddHours(kMLInfo.UserTimezone);
            }
            var      lineStringMessage    = string.Empty;
            DateTime trackStarted         = new DateTime();
            var      LastPointTimestamp   = string.Empty;
            bool     isTheLastPointIsZero = true;

            //iterate through each Placemark
            foreach (var placemark in placemarks)
            {
                //Placemarks without ExtendedData not proceed
                var check = placemark.XPathSelectElement("./kml:ExtendedData", ns);
                if (check != null)
                {
                    //copy coordinates
                    NewPlacemark.XPathSelectElement("//kml:Point/kml:coordinates", ns).Value =
                        placemark.XPathSelectElement("./kml:Point/kml:coordinates", ns).Value;
                    //copy LatLon
                    var thisLatitude =
                        NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Latitude']/kml:value", ns).Value =
                            placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Latitude']/kml:value", ns).Value;
                    var thisLongitude =
                        NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Longitude']/kml:value", ns).Value =
                            placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Longitude']/kml:value", ns).Value;

                    //calculate distance
                    double.TryParse(thisLatitude, NumberStyles.Any, CultureInfo.InvariantCulture, out double thisLatitudeDouble);
                    double.TryParse(thisLongitude, NumberStyles.Any, CultureInfo.InvariantCulture, out double thisLongitudeDouble);
                    GeoCoordinate pin1 = new GeoCoordinate(thisLatitudeDouble, thisLongitudeDouble);
                    GeoCoordinate pin2 = new GeoCoordinate(lastLatitude, lastLongitude);
                    if (lastLatitude != 0 && lastLongitude != 0)
                    {
                        totalDistance       += pin1.GetDistanceTo(pin2) / 1000;
                        isTheLastPointIsZero = false;
                    }
                    lastLatitude  = thisLatitudeDouble;
                    lastLongitude = thisLongitudeDouble;
                    var distance = totalDistance.ToString("0") + " km";
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Distance']/kml:value", ns).Value = distance;

                    //select Speed element end remove fraction after comma: 12 km/m
                    string speed = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Velocity']/kml:value", ns).Value;
                    speed = speed.Substring(0, speed.IndexOf(".")) + " km/h";
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Velocity']/kml:value", ns).Value = speed;

                    //select Course element and transform it compass style heading and remove fractions
                    string heading = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Course']/kml:value", ns).Value;
                    heading = heading.Substring(0, heading.IndexOf("."));
                    Int32.TryParse(heading, out int h);
                    //setting placemark to use style according to heading: NE 45°
                    heading = GetHeading(h, out int idx).ToUpper() + " " + heading + "°";
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Course']/kml:value", ns).Value = heading;
                    NewPlacemark.XPathSelectElement("//kml:styleUrl", ns).Value = $"#{idx}";

                    //select Elevation element end remove fraction after comma: 124 m
                    string elevation = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Elevation']/kml:value", ns).Value;
                    elevation = elevation.Substring(0, elevation.IndexOf(".")) + " m";
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Elevation']/kml:value", ns).Value = elevation;

                    //select Time element and covert
                    var      dateTimeString      = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Time']/kml:value", ns).Value;
                    DateTime dT                  = DateTime.Parse(dateTimeString, CultureInfo.CreateSpecificCulture("en-US"));
                    var      dateTimeToPlacemark = $"{dT:HH:mm dd.MM.yyyy}";
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Time']/kml:value", ns).Value = dateTimeToPlacemark;

                    //select lastpoint timestamp. The nevest placemark is always the last placemark.
                    LastPointTimestamp = placemark.XPathSelectElement("./kml:TimeStamp/kml:when", ns).Value;

                    //calculate total time
                    if (lastDate != DateTime.MinValue)
                    {
                        totalTime += dT.Subtract(lastDate);
                    }
                    lastDate = dT;
                    var totalTimeStr = $"{totalTime:%d} day(s) {totalTime:%h\\:mm}";
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'TimeElapsed']/kml:value", ns).Value = totalTimeStr;

                    //select events and messages
                    string textValue = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value;
                    string eventType = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Event']/kml:value", ns).Value;
                    NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value = textValue;

                    //check if the eventType or receivedText contains keywords, if yes, then attach the style to the placemark
                    foreach (var styleKeyword in StyleKeywords)
                    {
                        //setting style for inReach turned on or any message received
                        if (styleKeyword.Keywords.Any(eventType.Contains))
                        {
                            NewPlacemark.XPathSelectElement("//kml:styleUrl", ns).Value = $"#{styleKeyword.StyleName}";
                        }
                        //setting style for the keywords found in message body
                        if (styleKeyword.Keywords.Any(textValue.ToLower().Contains))
                        {
                            NewPlacemark.XPathSelectElement("//kml:styleUrl", ns).Value = $"#{styleKeyword.StyleName}";
                        }
                    }
                    //if tracking turned on on device then copy Event into Text field
                    if (eventType == InReachEvents[0])
                    {
                        NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value = InReachEvents[0];
                        trackStarted = lastDate;
                        if (string.IsNullOrEmpty(kMLInfo.TrackStartTime))
                        {
                            kMLInfo.TrackStartTime = trackStarted.ToString("HH:mm dd.MM.yyyy");
                        }
                    }

                    //get the sender name as a Garmin Map Display Name. Not used.
                    //var senderName = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Map Display Name']/kml:value", ns).Value;

                    var inReachMessage = NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value;
                    lineStringMessage = $"Track started on {kMLInfo.TrackStartTime}.<br>Total distance traveled { distance} in { totalTimeStr}.";
                    var eMailMessage = $"Hello, <br><br><h3>{user.name} is on {kMLInfo.Title}.</h3>" +
                                       $"What just happened? <b>{inReachMessage}</b>.<br>" +
                                       $"{lineStringMessage}<br>" +
                                       $"Follow me on the map <a href='{webSiteUrl}/{kMLInfo.groupid}?id={kMLInfo.id}'></a>{webSiteUrl}/{kMLInfo.groupid}<br><br>" +
                                       $"This message was sent in {lastDate:HH:mm dd.MM.yyyy}, at location LatLon: {lastLatitude}, {lastLongitude}. " +
                                       $"<a href='https://www.google.com/maps/search/?api=1&query={lastLatitude},{lastLongitude}'>Open in google maps</a>.<br><br>" +
                                       $"Best regards,<br>Whoever is carrying this device.<br><br>" +
                                       $"<small>Disclaimer<br>" +
                                       $"You are getting this e-mail because you subscribed to receive {user.name} inReach messages.<br>" +
                                       $"Click here to unsubscribe:<a href='{webSiteUrl}/unsubscribe?userWebId={kMLInfo.groupid}'>Remove me from {user.name} inReach notifications</a>.<br>" +
                                       $"Sorry! It's not working yet. You cannot unsubscribe. You have to follow me forever.</small>";
                    var eMailSubject = $"{user.name} at {lastDate:HH:mm}: {inReachMessage}";
                    //if inReach has sent out a message then add a Placemark
                    if (InReachEvents.Any(eventType.Contains))
                    {
                        documentPlacemarkMessages.Add(new XElement(NewPlacemark));
                        emails.Add(new Emails
                        {
                            EmailBody    = eMailMessage,
                            EmailSubject = eMailSubject,
                            UserWebId    = kMLInfo.groupid,
                            DateTime     = dateTimeString,
                            EmailFrom    = user.email,
                            Name         = user.name,
                            EmailTo      = user.subscibers
                        });
                    }
                    //add full placemarks only for short less than 2 days tracks
                    if (!kMLInfo.IsLongTrack)
                    {
                        documentPlacemark.Add(new XElement(NewPlacemark));
                    }
                }
                else
                {
                    //build a linestring, add new coordinates into end of existing list
                    string NewCoordinates = ReturnValue(xmlTrack.XPathSelectElement("//kml:Placemark/kml:LineString/kml:coordinates", ns));
                    string Coordinates    = ReturnValue(xmlLineString.XPathSelectElement("//kml:Placemark/kml:LineString/kml:coordinates", ns));
                    Coordinates = Coordinates + "\r\n" + NewCoordinates;
                    xmlLineString.XPathSelectElement("//kml:Placemark/kml:LineString/kml:coordinates", ns).Value = Coordinates;
                    xmlLineString.XPathSelectElement("//kml:Placemark/kml:name", ns).Value        = kMLInfo.Title;
                    xmlLineString.XPathSelectElement("//kml:Placemark/kml:description", ns).Value = lineStringMessage;
                }
            }
            kMLInfo.LastTotalDistance  = totalDistance;
            kMLInfo.LastLatitude       = lastLatitude;
            kMLInfo.LastLongitude      = lastLongitude;
            kMLInfo.LastTotalTime      = totalTime.ToString();
            kMLInfo.LastPointTimestamp = LastPointTimestamp;

            //create a layer just with a last placemark if it is not zero
            if (!isTheLastPointIsZero)
            {
                documentLastPlacemark.Add(new XElement(NewPlacemark));
            }

            LastPlacemark = xmlString + xmlLastPlacemark.ToString();
            PlacemarksMsg = xmlString + xmlPlacemarksWithMessages.ToString();
            TrackLine     = xmlString + xmlLineString.ToString();
            //add full placemarks only for track with duration less than 2 day
            if (!kMLInfo.IsLongTrack)
            {
                PlacemarksAll = xmlString + xmlPlacemarks.ToString();
            }

            blobs.First(x => x.BlobName == "trackline").BlobValue     = TrackLine;
            blobs.First(x => x.BlobName == "placemarksall").BlobValue = PlacemarksAll;
            blobs.First(x => x.BlobName == "placemarksmsg").BlobValue = PlacemarksMsg;
            blobs.First(x => x.BlobName == "lastplacemark").BlobValue = LastPlacemark;

            return(true);
        }