private static void FindNearbyStructuresRecursive(double[] pt, HashSet <ArkStructure> points, KDTree <ArkStructure> tree, Area area, MinMaxCoords minmax, ref int pointsRemoved, Action <Area, ArkStructure, MinMaxCoords> addStructureToArea) { var near = tree.Nearest(pt, _coordRadius).Where(y => points.Contains(y.Node.Value)).ToArray(); foreach (var item in near) { addStructureToArea(area, item.Node.Value, minmax); points.Remove(item.Node.Value); } pointsRemoved += near.Length; if (points.Count == 0) { return; } if (pointsRemoved >= 50) { tree = KDTree.FromData(points.Select(y => new double[] { y.Location.Latitude.Value, y.Location.Longitude.Value }).ToArray(), points.ToArray()); pointsRemoved = 0; } foreach (var item in near) { FindNearbyStructuresRecursive(item.Node.Position, points, tree, area, minmax, ref pointsRemoved, addStructureToArea); } }
public StructuresViewModel Get(string id) { var context = _contextManager.GetServer(id); if (context == null) { return(null); } var demoMode = IsDemoMode() ? new DemoMode() : null; var result = new StructuresViewModel { MapName = context.SaveState?.MapName }; var ids = new ConcurrentDictionary <string, int>(); var types = new ConcurrentDictionary <string, StructureTypeViewModel>(StringComparer.OrdinalIgnoreCase); var owners = new ConcurrentDictionary <int, StructureOwnerViewModel>(); var addStructureToArea = new Action <Area, ArkStructure, MinMaxCoords>((area, x, minmax) => { //todo: this method may call the update callback even when the key ends up already being in the dict var type = types.GetOrAdd(x.ClassName, (key) => { return(new StructureTypeViewModel(new Lazy <int>(() => ids.AddOrUpdate("typeId", 0, (key2, value) => value + 1))) { ClassName = x.ClassName, Name = x.ClassName //todo: we do not have names for structures yet }); }); type.Id = type._generateId.Value; bool isCrapTier = false; if (_trashTier.TryGetValue(type.ClassName, out isCrapTier) && isCrapTier) { area.TrashTierCount += 1; } area.Structures.Add(Tuple.Create(type.Id, x)); var loc = x.Location; if (minmax.MinY == null || loc.Y < minmax.MinY.Y) { minmax.MinY = loc; } if (minmax.MaxY == null || loc.Y > minmax.MaxY.Y) { minmax.MaxY = loc; } if (minmax.MinX == null || loc.X < minmax.MinX.X) { minmax.MinX = loc; } if (minmax.MaxX == null || loc.X > minmax.MaxX.X) { minmax.MaxX = loc; } if (minmax.MinZ == null || loc.Z < minmax.MinZ.Z) { minmax.MinZ = loc; } if (minmax.MaxZ == null || loc.Z > minmax.MaxZ.Z) { minmax.MaxZ = loc; } }); if (context.Structures != null) { // make fake structure objects out of rafts to include them in the clustering var rafts = context.Rafts?.Select(x => new ArkStructure { ClassName = x.ClassName, Location = x.Location, OwnerName = x.OwningPlayerName ?? x.TribeName, OwningPlayerId = x.OwningPlayerId, TargetingTeam = x.TargetingTeam }).ToArray(); if (rafts == null) { rafts = new List <ArkStructure>().ToArray(); } var structureAreas = context.Structures.Concat(rafts).Where(x => (x.TargetingTeam.HasValue || x.OwningPlayerId.HasValue) && x.Location?.Latitude != null && x.Location?.Longitude != null) .GroupBy(x => x.TargetingTeam ?? x.OwningPlayerId ?? 0) .AsParallel() .SelectMany(x => { var first = x.First(); var arkOwnerId = first.TargetingTeam ?? first.OwningPlayerId ?? 0; var isTribe = first.TargetingTeam.HasValue && (!first.OwningPlayerId.HasValue || first.TargetingTeam.Value != first.OwningPlayerId.Value); var owner = owners.GetOrAdd(arkOwnerId, (key) => { var tribe = isTribe ? context.Tribes.FirstOrDefault(y => y.Id == first.TargetingTeam.Value) : null; var player = !isTribe ? context.Players.FirstOrDefault(y => y.Id == first.OwningPlayerId) : null; var lastActiveTime = isTribe ? tribe?.LastActiveTime : player?.LastActiveTime; //check saved player last active times for cross server activity var externalLastActiveTime = (DateTime?)null; if (isTribe && tribe != null && tribe.Members.Length > 0) { //for tribes check all last active times for member steamIds (across all servers/clusters) var memberIds = tribe.Members.Select(y => y.SteamId).ToList(); var states = _savedState.PlayerLastActive.Where(y => y.SteamId != null && memberIds.Contains(y.SteamId, StringComparer.OrdinalIgnoreCase)).ToArray(); if (states?.Length > 0) { externalLastActiveTime = states.Max(y => y.LastActiveTime); } } else if (!isTribe && player != null) { //for players check all last active times for player steamid (across all servers/clusters) var states = _savedState.PlayerLastActive.Where(y => y.SteamId != null && y.SteamId.Equals(player.SteamId, StringComparison.OrdinalIgnoreCase)).ToArray(); if (states?.Length > 0) { externalLastActiveTime = states.Max(y => y.LastActiveTime); } } //set last active time to cross server time if it is more recent if ((externalLastActiveTime.HasValue && !lastActiveTime.HasValue) || (externalLastActiveTime.HasValue && lastActiveTime.HasValue && externalLastActiveTime.Value > lastActiveTime.Value)) { lastActiveTime = externalLastActiveTime; } return(new StructureOwnerViewModel(new Lazy <int>(() => ids.AddOrUpdate("ownerId", 0, (key2, value) => value + 1))) { OwnerId = arkOwnerId, Name = demoMode != null ? isTribe ? demoMode.GetTribeName(arkOwnerId) : demoMode.GetPlayerName(arkOwnerId) : first.OwnerName, Type = isTribe ? "tribe" : "player", LastActiveTime = lastActiveTime, CreatureCount = (isTribe ? tribe?.Creatures.Count() : player?.Creatures.Count()) ?? 0 }); }); owner.Id = owner._generateId.Value; var areas = new List <StructureAreaViewModel>(); var structures = new HashSet <ArkStructure>(x); var tree = KDTree.FromData(x.Select(y => new double[] { y.Location.Latitude.Value, y.Location.Longitude.Value }).ToArray(), x.ToArray()); do { var structure = structures.First(); structures.Remove(structure); var area = new Area(); var minmax = new MinMaxCoords(); addStructureToArea(area, structure, minmax); var n = 0; FindNearbyStructuresRecursive(new double[] { structure.Location.Latitude.Value, structure.Location.Longitude.Value }, structures, tree, area, minmax, ref n, addStructureToArea); //var minLat = area.Min(y => y.Item2.Location.Latitude.Value); //var maxLat = area.Max(y => y.Item2.Location.Latitude.Value); //var minLng = area.Min(y => y.Item2.Location.Longitude.Value); //var maxLng = area.Max(y => y.Item2.Location.Longitude.Value); var dLat = (minmax.MaxY.Latitude.Value - minmax.MinY.Latitude.Value) / 2f; var dLng = (minmax.MaxX.Longitude.Value - minmax.MinX.Longitude.Value) / 2f; var avgLat = minmax.MinY.Latitude.Value + dLat; var avgLng = minmax.MinX.Longitude.Value + dLng; var dY = (minmax.MaxY.Y - minmax.MinY.Y) / 2f; var dX = (minmax.MaxX.X - minmax.MinX.X) / 2f; var dZ = (minmax.MaxZ.Z - minmax.MinZ.Z) / 2f; var avgY = minmax.MinY.Y + dY; var avgX = minmax.MinX.X + dX; var avgZ = minmax.MinZ.Z + dZ; var dTopoMapY = (minmax.MaxY.TopoMapY.Value - minmax.MinY.TopoMapY.Value) / 2f; var dTopoMapX = (minmax.MaxX.TopoMapX.Value - minmax.MinX.TopoMapX.Value) / 2f; var avgTopoMapY = minmax.MinY.TopoMapY.Value + dTopoMapY; var avgTopoMapX = minmax.MinX.TopoMapX.Value + dTopoMapX; var structureGroups = area.Structures.GroupBy(y => y.Item1).Select(y => new StructureViewModel { TypeId = y.Key, Count = y.Count() }).OrderByDescending(y => y.Count).ToList(); areas.Add(new StructureAreaViewModel { OwnerId = owner.Id, Structures = structureGroups, StructureCount = area.Structures.Count, Latitude = (float)Math.Round(avgLat, 2), Longitude = (float)Math.Round(avgLng, 2), Radius = (float)Math.Round(Math.Sqrt(dLat * dLat + dLng * dLng), 2), TopoMapX = (float)Math.Round(avgTopoMapX, 2), TopoMapY = (float)Math.Round(avgTopoMapY, 2), RadiusPx = (float)Math.Round(Math.Sqrt(dTopoMapX * dTopoMapX + dTopoMapY * dTopoMapY), 2), X = (float)Math.Round(avgX, 2), Y = (float)Math.Round(avgY, 2), Z = (float)Math.Round(avgZ, 2), RadiusUu = (float)Math.Round(Math.Sqrt(dX * dX + dY * dY), 2), TrashQuota = area.TrashTierCount / (float)area.Structures.Count }); } while (structures.Count > 0); owner.AreaCount = areas.Count; owner.StructureCount = areas.Sum(y => y.StructureCount); return(areas); }).ToArray(); result.Areas = structureAreas.OrderByDescending(x => x.Radius).ThenByDescending(x => x.StructureCount).ToList(); result.Owners = owners.Values.OrderBy(x => x.Id).ToList(); result.Types = types.Values.OrderBy(x => x.Id).ToList(); } return(result); }