예제 #1
0
        public async Task <IActionResult> Show(
            [FromRoute] string id
            )
        {
            var cta = await Mongo.GetCallToAction(id);

            if (cta == null)
            {
                Logger.LogInformation("Call to action {0} not found", id);
                return(NotFound());
            }

            var filter = (await Mongo.GetCallToActionFilters(id)).FirstOrDefault();

            if (filter == null)
            {
                Logger.LogError("Call to action {0} has no filter", filter);
                return(NotFound());
            }

            // HACK: shows huge Geohash
            var geohashBounds = Geohasher.GetBoundingBox(filter.CoveringGeohash[0].Substring(0, 1));

            return(View("Show", new CallToActionViewModel {
                Id = cta.Id.ToString(),
                From = filter.TimeBegin,
                To = filter.TimeEnd,
                Description = cta.Description,
                BoundingBox = geohashBounds,
                PolygonCoordinates = filter.Geometry.ToRingArray()
            }));
        }
예제 #2
0
        public async Task <IActionResult> UpdateFilter(
            [FromRoute] string id,
            [FromRoute] string filterId,
            [FromForm] DateTime?addedOn,
            [FromForm] DateTime from,
            [FromForm] DateTime to,
            [FromForm] string geojson
            )
        {
            var filter = await Mongo.GetCallToActionFilter(filterId);

            if (filter == null || !filter.CallToActionId.ToString().Equals(id))
            {
                return(NotFound());
            }

            var geometry  = geojson.PolygonFromGeoJson();
            var geohashes = await Geohasher.GenerateCoveringGeohashes(geometry);

            Logger.LogInformation("Geometry converted to {HashCount} geohashes {Hashes}", geohashes.Count, string.Join(",", geohashes));

            filter.AddedOn         = addedOn ?? DateTime.UtcNow;
            filter.TimeBegin       = from;
            filter.TimeEnd         = to;
            filter.Geometry        = geometry;
            filter.CoveringGeohash = geohashes.ToArray();

            await Mongo.ReplaceCallToActionFilter(filter);

            return(RedirectToAction(nameof(ShowCall), "Dashboard", new { id = id }));
        }
        public static RegisteredCredential From(EthEventDTO eventDto)
        {
            var ret = new RegisteredCredential()
            {
                IsRevoked          = false,
                HashId             = eventDto.IndexedParameters[0].Value,
                CitizenAddress     = eventDto.NonIndexedParameters[0].Value,
                SubjectHashId      = eventDto.NonIndexedParameters[1].Value,
                StartDate          = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(eventDto.NonIndexedParameters[2].Value)).DateTime.ToUniversalTime(),
                CredentialCreation = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(eventDto.NonIndexedParameters[3].Value)).DateTime.ToUniversalTime(),
                Sex            = Enum.Parse <Sex>(eventDto.NonIndexedParameters[4].Value),
                Age            = short.Parse(eventDto.NonIndexedParameters[5].Value),
                CredentialType = Enum.Parse <CredentialType>(eventDto.NonIndexedParameters[7].Value),
                Reason         = Enum.Parse <InterruptionReason>(eventDto.NonIndexedParameters[8].Value)
            };

            var hasher  = new Geohasher();
            var hash    = Hex.HexToString(eventDto.NonIndexedParameters[6].Value);
            var decoded = hasher.Decode(hash);

            ret.Location = new Point(decoded.Item2, decoded.Item1);
            ret.Lat      = ret.Location.Coordinate.X;
            ret.Lon      = ret.Location.Coordinate.Y;
            SetSymptoms(ret, eventDto.NonIndexedParameters[9].Value);
            return(ret);
        }
예제 #4
0
        public async System.Threading.Tasks.Task Should_Get_Hashes_For_PolygonAsync()
        {
            var hasher = new Geohasher();

            var geometryFactory = new GeometryFactory();

            var p1 = new Coordinate()
            {
                X = 9.612350463867186, Y = 52.31141727938367
            };
            var p2 = new Coordinate()
            {
                X = 9.914474487304686, Y = 52.31141727938367
            };
            var p3 = new Coordinate()
            {
                X = 9.914474487304686, Y = 52.42252295423907
            };
            var p4 = new Coordinate()
            {
                X = 9.612350463867186, Y = 52.42252295423907
            };

            var polygon = geometryFactory.CreatePolygon(new[] { p1, p2, p3, p4, p1 });

            var result = await hasher.GetHashesAsync(polygon, 6);

            Assert.AreEqual(486, result.Count);
        }
예제 #5
0
        public void Should_Encode_WithDefaultPrecison()
        {
            var hasher = new Geohasher();

            var hash = hasher.Encode(52.5174, 13.409);

            Assert.Equal("u33dc0", hash);
        }
예제 #6
0
        public void Should_Give_Subhashes_1()
        {
            var hasher = new Geohasher();

            var subhashes = hasher.GetSubhashes("u");

            Assert.Equal(32, subhashes.Length);
        }
예제 #7
0
        public void Should_Encode_WithGivenPrecision_11()
        {
            var hasher = new Geohasher();

            var hash = hasher.Encode(52.517395, 13.408813, 11);

            Assert.Equal("u33dc07zzzz", hash);
        }
예제 #8
0
        public void Should_Decode_Precision12()
        {
            var hasher = new Geohasher();

            var hash = hasher.Decode("u33dc07zzzzx");

            Assert.Equal(52.51739494, Math.Round(hash.Item1, 8));
            Assert.Equal(13.40881297, Math.Round(hash.Item2, 8));
        }
예제 #9
0
        public void Should_Decode_Precision6()
        {
            var hasher = new Geohasher();

            var hash = hasher.Decode("u33dc0");

            Assert.Equal(52.5174, Math.Round(hash.Item1, 4));
            Assert.Equal(13.409, Math.Round(hash.Item2, 3));
        }
예제 #10
0
        public static Task <List <string> > GenerateCoveringGeohashes(this Geohasher hasher, GeoJsonPolygon <GeoJson2DGeographicCoordinates> polygon, int precision = 4)
        {
            var outerRing = new NetTopologySuite.Geometries.LinearRing(
                (from coord in polygon.Coordinates.Exterior.Positions
                 select new NetTopologySuite.Geometries.Coordinate(coord.Latitude, coord.Longitude)).ToArray()
                );
            var p = new NetTopologySuite.Geometries.Polygon(outerRing);

            return(hasher.GetHashesAsync(p, precision, Mode.Intersect));
        }
예제 #11
0
        public void LargeCompressionTest()
        {
            var compressor = new GeohashCompressor();

            var hasher = new Geohasher();

            var compressed = compressor.Compress(GetHashes().ToArray());

            Assert.AreEqual(152, compressed.Count);
        }
예제 #12
0
        public void Should_Get_BoundingBox()
        {
            var hasher = new Geohasher();

            var envelope = hasher.GetBoundingBox("u");

            Assert.Equal(90, envelope.MaxX);
            Assert.Equal(45, envelope.MaxY);
            Assert.Equal(45, envelope.MinX);
            Assert.Equal(0, envelope.MinY);
        }
예제 #13
0
        public void Should_Give_Neighbor()
        {
            var hasher = new Geohasher();

            Assert.Equal("u33dc1", hasher.GetNeighbor("u33dc0", Direction.North));
            Assert.Equal("u33dc3", hasher.GetNeighbor("u33dc0", Direction.NorthEast));
            Assert.Equal("u33dc2", hasher.GetNeighbor("u33dc0", Direction.East));
            Assert.Equal("u33d9r", hasher.GetNeighbor("u33dc0", Direction.SouthEast));
            Assert.Equal("u33d9p", hasher.GetNeighbor("u33dc0", Direction.South));
            Assert.Equal("u33d8z", hasher.GetNeighbor("u33dc0", Direction.SouthWest));
            Assert.Equal("u33dbb", hasher.GetNeighbor("u33dc0", Direction.West));
            Assert.Equal("u33dbc", hasher.GetNeighbor("u33dc0", Direction.NorthWest));
        }
        public async Task FullDump()
        {
            var lastAccess = await Mongo.GetLastDailyStats();

            Response.Headers[HeaderNames.LastModified] = lastAccess.Date.ToUniversalTime().ToString("R");
            Response.Headers[HeaderNames.ContentType]  = "text/csv";
            Response.StatusCode = (int)HttpStatusCode.OK;

            await Response.WriteAsync("Date,TotalMinutesTracked,CentroidGeohash,CentroidLat,CentroidLong,GeohashBoxJSON,LocationCount,VehicleCount,EventCount,SampleCount,DiscardedSampleCount,BoundingBoxDiagonal,MinAtHome,MinAtWork,MinAtSchool,MinAtLocations,MinElsewhere" + Environment.NewLine);

            var cursor = await Mongo.FetchAllDailyStats();

            while (await cursor.MoveNextAsync())
            {
                foreach (var stat in cursor.Current)
                {
                    var centroid = stat.CentroidHash ?? Geohasher.Encode(stat.Centroid.Coordinates.Latitude, stat.Centroid.Coordinates.Longitude, 5);
                    var bbox     = Geohasher.GetBoundingBox(centroid);
                    var polygon  = new Polygon(new LineString[] {
                        new LineString(new IPosition[] {
                            new Position(bbox[0], bbox[2]),
                            new Position(bbox[0], bbox[3]),
                            new Position(bbox[1], bbox[3]),
                            new Position(bbox[1], bbox[2]),
                            new Position(bbox[0], bbox[2])
                        })
                    });

                    await Response.WriteAsync(string.Join(",",
                                                          stat.Date.ToString("yyyy-MM-dd"),
                                                          stat.TotalMinutesTracked,
                                                          centroid,
                                                          stat.Centroid.Coordinates.Latitude.ToString("F5"),
                                                          stat.Centroid.Coordinates.Longitude.ToString("F5"),
                                                          "\"" + JsonConvert.SerializeObject(polygon).Replace("\"", "\"\"") + "\"",
                                                          stat.LocationCount,
                                                          stat.VehicleCount,
                                                          stat.EventCount,
                                                          stat.SampleCount,
                                                          stat.DiscardedSampleCount,
                                                          stat.BoundingBoxDiagonal.ToString("F2"),
                                                          stat.LocationTracking.MinutesAtHome,
                                                          stat.LocationTracking.MinutesAtWork,
                                                          stat.LocationTracking.MinutesAtSchool,
                                                          stat.LocationTracking.MinutesAtOtherKnownLocations,
                                                          stat.LocationTracking.MinutesElsewhere
                                                          ) + Environment.NewLine);
                }
            }
        }
예제 #15
0
        public void Should_Give_Neighbors()
        {
            var hasher = new Geohasher();

            var subhashes = hasher.GetNeighbors("u33dc0");

            Assert.Equal("u33dc1", subhashes[Direction.North]);
            Assert.Equal("u33dc3", subhashes[Direction.NorthEast]);
            Assert.Equal("u33dc2", subhashes[Direction.East]);
            Assert.Equal("u33d9r", subhashes[Direction.SouthEast]);
            Assert.Equal("u33d9p", subhashes[Direction.South]);
            Assert.Equal("u33d8z", subhashes[Direction.SouthWest]);
            Assert.Equal("u33dbb", subhashes[Direction.West]);
            Assert.Equal("u33dbc", subhashes[Direction.NorthWest]);
        }
예제 #16
0
        public void Should_Give_Neighbors_EdgeWest()
        {
            var hasher = new Geohasher();

            var subhashes = hasher.GetNeighbors("9");

            Assert.Equal("c", subhashes[Direction.North]);
            Assert.Equal("b", subhashes[Direction.NorthWest]);
            Assert.Equal("f", subhashes[Direction.NorthEast]);
            Assert.Equal("d", subhashes[Direction.East]);
            Assert.Equal("3", subhashes[Direction.South]);
            Assert.Equal("2", subhashes[Direction.SouthWest]);
            Assert.Equal("6", subhashes[Direction.SouthEast]);
            Assert.Equal("8", subhashes[Direction.West]);
        }
예제 #17
0
        public void Should_Give_Neighbors_EdgeNorth()
        {
            var hasher = new Geohasher();

            var subhashes = hasher.GetNeighbors("u");

            Assert.Equal("h", subhashes[Direction.North]);
            Assert.Equal("5", subhashes[Direction.NorthWest]);
            Assert.Equal("j", subhashes[Direction.NorthEast]);
            Assert.Equal("v", subhashes[Direction.East]);
            Assert.Equal("s", subhashes[Direction.South]);
            Assert.Equal("e", subhashes[Direction.SouthWest]);
            Assert.Equal("t", subhashes[Direction.SouthEast]);
            Assert.Equal("g", subhashes[Direction.West]);
        }
예제 #18
0
        public bool Create(TrackingBinding binding)
        {
            using (var db = GetMainContext())
            {
                var geohasher = new Geohasher();

                var tracking = binding.ToEntity();
                tracking.Geohash = geohasher.Encode((double)binding.Latitude, (double)binding.Longitude, 9);
                tracking.UserId  = UserId;

                db.Trackings.Add(tracking);
                db.SaveChanges();

                return(true);
            }
        }
예제 #19
0
        public async Task Create(IEnumerable <TrackingBinding> binding)
        {
            var geohasher = new Geohasher();

            using (var db = GetMainContext())
            {
                await db.Trackings.AddRangeAsync(binding.Select(x =>
                {
                    var entity     = x.ToEntity();
                    entity.Geohash = geohasher.Encode((double)x.Latitude, (double)x.Longitude, 9);
                    entity.UserId  = UserId;
                    return(entity);
                }));

                await db.SaveChangesAsync();
            }
        }
예제 #20
0
        public async System.Threading.Tasks.Task Should_Get_Hashes_For_PolygonAsync_IntersectMode()
        {
            var hasher = new Geohasher();

            var geometryFactory = new GeometryFactory();

            var progess = new Progress <HashingProgress>();

            progess.ProgressChanged += (e, hp) =>
            {
                Debug.WriteLine($"Processed: {hp.HashesProcessed}, Queued: {hp.QueueSize}, Running Since: {hp.RunningSince}");
            };

            Polygon polygon = GetTestPolygon(geometryFactory);

            var result = await hasher.GetHashesAsync(polygon, 4, mode : Mode.Intersect, progress : progess);

            Assert.AreEqual(183, result.Count);
        }
예제 #21
0
        private void btnGenerateGeohash_Click(object sender, EventArgs e)
        {
            var hasher = new Geohasher();

            var     geometryFactory = new GeometryFactory();
            Polygon polygon         = GetTestPolygon(geometryFactory);

            List <string> res = hasher.GetHashesAsync(polygon).Result;

            MessageBox.Show(res[0], "Test1");


            //Task<List<string>> inner_polygon = Should_Get_Hashes_For_PolygonAsync_LongRunning();
            //List<string> inner_polygon_Loaded =  inner_polygon.Result;

            //string[] arr_result = inner_polygon_Loaded.ToArray();
            //MessageBox.Show(arr_result[0], "sss");
            int a = 3;
        }
예제 #22
0
        public void SmallCompressionTest()
        {
            var compressor = new GeohashCompressor();

            var hasher = new Geohasher();

            var list = new List <string>();

            list.AddRange(hasher.GetSubhashes("ABC"));

            list.AddRange(hasher.GetSubhashes("ABF"));

            list.AddRange(hasher.GetSubhashes("AFF"));

            list.AddRange(new List <string> {
                "KK", "F", "FKUVC", "FKUVX"
            });

            var compressed = compressor.Compress(list.ToArray());

            Assert.AreEqual(7, compressed.Count);
        }
예제 #23
0
        public async System.Threading.Tasks.Task <List <string> > Should_Get_Hashes_For_PolygonAsync_LongRunning()
        {
            var hasher = new Geohasher();

            var geometryFactory = new GeometryFactory();

            var progess = new Progress <HashingProgress>();

            progess.ProgressChanged += (e, hp) =>
            {
                Debug.WriteLine($"Processed: {hp.HashesProcessed}, Queued: {hp.QueueSize}, Running Since: {hp.RunningSince}");
            };

            Polygon polygon = GetTestPolygon(geometryFactory);

//            var result = await hasher.GetHashesAsync(polygon, 4, mode: Mode.Contains, progress: progess);
            var result = await hasher.GetHashesAsync(polygon, 1, mode : Mode.Contains);

            //var result = await hasher.GetHashesAsync(polygon, 4, mode: Mode.Contains);

            return(result);
            //Assert.AreEqual(112, result.Count);
        }
예제 #24
0
        public async Task <IEnumerable <string> > GetGeohashes(GeohashGetBinding binding)
        {
            var geohasher = new Geohasher();

            using (var context = GetMainContext())
            {
                var neighbours = binding.Geohash is null ? null : geohasher.GetNeighbors(binding.Geohash);

                return(await context.Trackings.WhereUser(UserId)
                       .WhereTimestampInclusive(binding)
                       .WhereIf(neighbours is not null, x => x.Geohash.StartsWith(binding.Geohash) ||
                                x.Geohash.StartsWith(neighbours[Direction.North]) ||
                                x.Geohash.StartsWith(neighbours[Direction.South]) ||
                                x.Geohash.StartsWith(neighbours[Direction.East]) ||
                                x.Geohash.StartsWith(neighbours[Direction.West]) ||
                                x.Geohash.StartsWith(neighbours[Direction.NorthEast]) ||
                                x.Geohash.StartsWith(neighbours[Direction.NorthWest]) ||
                                x.Geohash.StartsWith(neighbours[Direction.SouthEast]) ||
                                x.Geohash.StartsWith(neighbours[Direction.SouthWest]))
                       .Select(x => x.Geohash.Substring(0, binding.Precision))
                       .Distinct()
                       .ToListAsync());
            }
        }
예제 #25
0
        public async Task <IActionResult> AddFilter(
            [FromRoute] string id,
            [FromForm] DateTime?addedOn,
            [FromForm] DateTime from,
            [FromForm] DateTime to,
            [FromForm] string geojson
            )
        {
            var geometry  = geojson.PolygonFromGeoJson();
            var geohashes = await Geohasher.GenerateCoveringGeohashes(geometry);

            Logger.LogInformation("Geometry converted to {HashCount} geohashes {Hashes}", geohashes.Count, string.Join(",", geohashes));

            var filter = new CallToActionFilter {
                AddedOn         = addedOn ?? DateTime.UtcNow,
                TimeBegin       = from,
                TimeEnd         = to,
                Geometry        = geometry,
                CoveringGeohash = geohashes.ToArray()
            };
            await Mongo.AddCallToActionFilter(id, filter);

            return(RedirectToAction(nameof(ShowCall), "Dashboard", new { id = id }));
        }
예제 #26
0
        public void Should_Give_Parent()
        {
            var hasher = new Geohasher();

            Assert.Equal("u33db", hasher.GetParent("u33dbc"));
        }
예제 #27
0
        public void Should_Throw_Given_Incorrect_Lng()
        {
            var hasher = new Geohasher();

            Assert.Throws <ArgumentException>(() => hasher.Encode(52.517395, 183.408813, 12));
        }
예제 #28
0
        public async Task <IActionResult> Upload(
            [FromBody] DailyStats stats
            )
        {
            if (!ModelState.IsValid)
            {
                Logger.LogError("Failed to parse input data: {0}", ModelState);
                return(BadRequest(ModelState));
            }

            Logger.LogInformation("Receiving daily stats from device {0} for {1}", stats.InstallationId, stats.Date.ToString("d", CultureInfo.InvariantCulture));

            // Safety checks
            if (stats.Date < MinDate)
            {
                Logger.LogError("Daily statistics for unacceptable date {0}", stats.Date);
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "Unacceptable date (out of valid range)",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-date"
                                                                                      )));
            }
            if (stats.TotalMinutesTracked > MinutesADay)
            {
                Logger.LogError("Total minutes tracked ({0}) exceeds minutes in a day", stats.TotalMinutesTracked);
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "Total minutes tracked exceeds minutes in a day",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-data"
                                                                                      )));
            }
            if (stats.Date >= DateTime.UtcNow.Date)
            {
                Logger.LogError("Daily statistics for non-elapsed day {0}", stats.Date.Date);
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "Unacceptable date (future date)",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-date"
                                                                                      )));
            }

            GeoJsonPoint <GeoJson2DGeographicCoordinates> position;
            string geohash;

            try {
                geohash = stats.CentroidHash.Substring(0, 5);
                var decoded = Geohasher.Decode(geohash);
                position = new GeoJsonPoint <GeoJson2DGeographicCoordinates>(new GeoJson2DGeographicCoordinates(decoded.Item2, decoded.Item1));
                Logger.LogInformation("GeoHash {0} decoded as {1:F5},{2:F5}", geohash, position.Coordinates.Latitude, position.Coordinates.Longitude);
            }
            catch (Exception ex) {
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "Cannot decode geohash",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-data",
                                                                                      detail: ex.Message
                                                                                      )));
            }

            if (stats.LocationTracking == null)
            {
                Logger.LogError("Payload does not contain location tracking section");
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "Payload does not contain location tracking section",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-data"
                                                                                      )));
            }
            if (stats.LocationTracking.MinutesAtHome < 0 ||
                stats.LocationTracking.MinutesAtWork < 0 ||
                stats.LocationTracking.MinutesAtSchool < 0 ||
                stats.LocationTracking.MinutesAtOtherKnownLocations < 0 ||
                stats.LocationTracking.MinutesElsewhere < 0)
            {
                Logger.LogError("Location tracking minutes cannot be negative");
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "Negative location tracking value",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-data"
                                                                                      )));
            }
            if (stats.LocationTracking.MinutesAtHome > MinutesADay ||
                stats.LocationTracking.MinutesAtWork > MinutesADay ||
                stats.LocationTracking.MinutesAtSchool > MinutesADay ||
                stats.LocationTracking.MinutesAtOtherKnownLocations > MinutesADay ||
                stats.LocationTracking.MinutesElsewhere > MinutesADay)
            {
                Logger.LogError("One entry in the location tracking section exceeds minutes in a day");
                return(UnprocessableEntity(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                                      title: "One entry in the location tracking section exceeds minutes in a day",
                                                                                      type: "https://arianna.digit.srl/api/problems/invalid-data"
                                                                                      )));
            }

            // Check for duplicates
            var existingStats = await Mongo.GetDailyStats(stats.InstallationId, stats.Date);

            if (existingStats != null)
            {
                Logger.LogError("Duplicate statistics from device ID {0} for date {1}", stats.InstallationId, stats.Date.ToString("d", CultureInfo.InvariantCulture));
                return(Conflict(ProblemDetailsFactory.CreateProblemDetails(HttpContext,
                                                                           title: "Duplicate statistics for date",
                                                                           type: "https://arianna.digit.srl/api/problems/duplicate"
                                                                           )));
            }

            // Compute voucher amounts
            int stayAtHomeBonus = 0;
            int womCount        = (int)Math.Ceiling(stats.TotalMinutesTracked / 60.0) + stayAtHomeBonus;

            Logger.LogInformation("Generating {0} WOM vouchers for {1} total minutes and {2} minutes at home ({3} stay at home bonus)",
                                  womCount, stats.TotalMinutesTracked, stats.LocationTracking.MinutesAtHome, stayAtHomeBonus);
            var voucherRequest = await Wom.Instrument.RequestVouchers(new VoucherCreatePayload.VoucherInfo[] {
                new VoucherCreatePayload.VoucherInfo {
                    Aim       = "P",
                    Count     = womCount,
                    Latitude  = position.Coordinates.Latitude,
                    Longitude = position.Coordinates.Longitude,
                    Timestamp = stats.Date.Date.AddHours(23.999)
                }
            });

            // OK-dokey
            await Mongo.AddDailyStats(new DataModels.DailyStats {
                InstallationId         = stats.InstallationId,
                Date                   = stats.Date.Date,
                TotalMinutesTracked    = stats.TotalMinutesTracked,
                TotalWomVouchersEarned = womCount,
                Centroid               = position,
                CentroidHash           = geohash,
                LocationCount          = stats.LocationCount,
                VehicleCount           = stats.VehicleCount,
                EventCount             = stats.EventCount,
                SampleCount            = stats.SampleCount,
                DiscardedSampleCount   = stats.DiscardedSampleCount,
                BoundingBoxDiagonal    = stats.BoundingBoxDiagonal,
                LocationTracking       = new DataModels.LocationTrackingStats {
                    MinutesAtHome   = stats.LocationTracking.MinutesAtHome,
                    MinutesAtWork   = stats.LocationTracking.MinutesAtWork,
                    MinutesAtSchool = stats.LocationTracking.MinutesAtSchool,
                    MinutesAtOtherKnownLocations = stats.LocationTracking.MinutesAtOtherKnownLocations,
                    MinutesElsewhere             = stats.LocationTracking.MinutesElsewhere
                }
            });

            return(Ok(new UploadConfirmation {
                WomLink = voucherRequest.Link,
                WomPassword = voucherRequest.Password,
                WomCount = womCount
            }));
        }