Skip to content

Commit 5411622

Browse files
committed
Added ability to inject tour groups
1 parent 4461960 commit 5411622

2 files changed

Lines changed: 176 additions & 40 deletions

File tree

Fritz.Charlie.Components/ChatterMapDirect.razor.cs

Lines changed: 136 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ public partial class ChatterMapDirect : ComponentBase, IAsyncDisposable
3737
private DotNetObjectReference<ChatterMapDirect>? dotNetObjectRef;
3838
private List<ClusterGroup> tourClusters = new();
3939

40+
// External tour grouping support - allows caller to provide AI-powered grouping
41+
[Parameter] public Func<IEnumerable<ViewerLocationEvent>, Task<IEnumerable<ExternalTourGroup>>>? OnTourGroupRequested { get; set; }
42+
43+
// Event to listen for external tour restart requests
44+
[Parameter] public EventCallback OnTourRestartRequested { get; set; }
45+
46+
// External tour groups when using OnTourGroupRequested
47+
private List<ExternalTourGroup> externalTourGroups = new();
48+
4049
// NEW: Aggregation structures
4150
private readonly Dictionary<(double lat, double lng), AggregateLocation> aggregatedMarkers = new();
4251
private readonly Dictionary<Guid, (double lat, double lng)> locationKeyIndex = new();
@@ -348,48 +357,90 @@ private async Task StartMapTour()
348357
locationsList = locationsList.Take(200).ToList();
349358
}
350359

351-
// Use the tour service to generate clusters
352-
tourClusters = TourService.GenerateClusters(locationsList);
360+
object[] tourStops;
353361

354-
// Log clustering results
355-
Console.WriteLine($"Generated {tourClusters.Count} clusters from {locationsList.Count} locations");
356-
foreach (var cluster in tourClusters)
362+
// Check if external tour grouping is available (e.g., TourGroupingService with AI region names)
363+
if (OnTourGroupRequested != null)
357364
{
358-
Console.WriteLine($" Cluster: {cluster.Count} locations, center ({cluster.CenterLatitude:F2}, {cluster.CenterLongitude:F2}), avg distance: {cluster.AverageDistanceFromCenter:F0}km");
359-
}
365+
Console.WriteLine($"Using external tour grouping for {locationsList.Count} locations");
366+
var externalGroups = await OnTourGroupRequested(locationsList);
367+
externalTourGroups = externalGroups.ToList();
360368

361-
// Use the tour service to arrange clusters
362-
var arrangedClusters = TourService.ArrangeClustersByDistance(tourClusters);
369+
Console.WriteLine($"External grouping returned {externalTourGroups.Count} groups");
370+
foreach (var group in externalTourGroups)
371+
{
372+
Console.WriteLine($" Group: {group.RegionName} - {group.LocationCount} locations, zoom {group.OptimalZoomLevel}");
373+
}
363374

364-
if (arrangedClusters.Count > 15)
365-
{
366-
arrangedClusters = arrangedClusters.Take(15).ToList();
367-
}
375+
if (externalTourGroups.Count > 15)
376+
{
377+
externalTourGroups = externalTourGroups.Take(15).ToList();
378+
}
368379

369-
var tourStops = arrangedClusters.Select(cluster => new
380+
tourStops = externalTourGroups.Select(group => new
381+
{
382+
lat = Math.Round(group.CenterLatitude, 6),
383+
lng = Math.Round(group.CenterLongitude, 6),
384+
zoom = Math.Min(group.OptimalZoomLevel, MaxZoom),
385+
description = group.RegionName,
386+
locationCount = group.LocationCount,
387+
locations = group.Locations.Take(10).Select(loc => new
388+
{
389+
description = Truncate(loc.LocationDescription, 50),
390+
lat = Math.Round((double)loc.Latitude, 6),
391+
lng = Math.Round((double)loc.Longitude, 6),
392+
userType = loc.UserType,
393+
service = loc.Service == "YouTube" ? "YouTube" : "Twitch"
394+
}).ToArray()
395+
}).ToArray();
396+
397+
// Clear internal clusters since we're using external groups
398+
tourClusters.Clear();
399+
}
400+
else
370401
{
371-
lat = Math.Round(cluster.CenterLatitude, 6),
372-
lng = Math.Round(cluster.CenterLongitude, 6),
373-
zoom = TourService.DetermineZoomLevel(cluster, MaxZoom),
374-
description = TourService.GetLocationDescription(cluster),
375-
locationCount = cluster.Locations.Count,
376-
locations = cluster.Locations.Take(10).Select(loc => new
402+
// Use the built-in tour service to generate clusters
403+
tourClusters = TourService.GenerateClusters(locationsList);
404+
405+
// Log clustering results
406+
Console.WriteLine($"Generated {tourClusters.Count} clusters from {locationsList.Count} locations");
407+
foreach (var cluster in tourClusters)
377408
{
378-
description = Truncate(loc.LocationDescription, 50),
379-
lat = Math.Round((double)loc.Latitude, 6),
380-
lng = Math.Round((double)loc.Longitude, 6),
381-
userType = loc.UserType,
382-
service = loc.Service == "YouTube" ? "YouTube" : "Twitch"
383-
}).ToArray()
384-
}).ToArray();
409+
Console.WriteLine($" Cluster: {cluster.Count} locations, center ({cluster.CenterLatitude:F2}, {cluster.CenterLongitude:F2}), avg distance: {cluster.AverageDistanceFromCenter:F0}km");
410+
}
385411

386-
// Log tour stops with zoom levels
387-
Console.WriteLine($"Tour will visit {tourStops.Length} stops:");
388-
for (int i = 0; i < tourStops.Length; i++)
389-
{
390-
Console.WriteLine($" Stop {i + 1}: {tourStops[i].description} - {tourStops[i].locationCount} locations, zoom level {tourStops[i].zoom}");
412+
// Use the tour service to arrange clusters
413+
var arrangedClusters = TourService.ArrangeClustersByDistance(tourClusters);
414+
415+
if (arrangedClusters.Count > 15)
416+
{
417+
arrangedClusters = arrangedClusters.Take(15).ToList();
418+
}
419+
420+
tourStops = arrangedClusters.Select(cluster => new
421+
{
422+
lat = Math.Round(cluster.CenterLatitude, 6),
423+
lng = Math.Round(cluster.CenterLongitude, 6),
424+
zoom = TourService.DetermineZoomLevel(cluster, MaxZoom),
425+
description = TourService.GetLocationDescription(cluster),
426+
locationCount = cluster.Locations.Count,
427+
locations = cluster.Locations.Take(10).Select(loc => new
428+
{
429+
description = Truncate(loc.LocationDescription, 50),
430+
lat = Math.Round((double)loc.Latitude, 6),
431+
lng = Math.Round((double)loc.Longitude, 6),
432+
userType = loc.UserType,
433+
service = loc.Service == "YouTube" ? "YouTube" : "Twitch"
434+
}).ToArray()
435+
}).ToArray();
436+
437+
// Clear external groups since we're using internal clustering
438+
externalTourGroups.Clear();
391439
}
392440

441+
// Log tour stops with zoom levels
442+
Console.WriteLine($"Tour will visit {tourStops.Length} stops");
443+
393444
await mapModule.InvokeVoidAsync("startTour", (object)tourStops);
394445
IsTourActive = true;
395446
StateHasChanged();
@@ -434,16 +485,35 @@ public async Task OnTourStatusChanged(bool isActive, int currentIndex, int total
434485
{
435486
try
436487
{
437-
if (isActive && currentIndex > 0 && currentIndex <= tourClusters.Count)
488+
if (isActive && currentIndex > 0)
438489
{
439-
var currentCluster = tourClusters[currentIndex - 1];
440-
CurrentTourLocation = new ViewerLocationEvent(
441-
(decimal)Math.Round(currentCluster.CenterLatitude, 6),
442-
(decimal)Math.Round(currentCluster.CenterLongitude, 6),
443-
TourService.GetLocationDescription(currentCluster))
490+
// Check if using external tour groups or internal clusters
491+
if (externalTourGroups.Count > 0 && currentIndex <= externalTourGroups.Count)
492+
{
493+
var currentGroup = externalTourGroups[currentIndex - 1];
494+
CurrentTourLocation = new ViewerLocationEvent(
495+
(decimal)Math.Round(currentGroup.CenterLatitude, 6),
496+
(decimal)Math.Round(currentGroup.CenterLongitude, 6),
497+
currentGroup.RegionName)
498+
{
499+
Id = Guid.NewGuid()
500+
};
501+
}
502+
else if (tourClusters.Count > 0 && currentIndex <= tourClusters.Count)
503+
{
504+
var currentCluster = tourClusters[currentIndex - 1];
505+
CurrentTourLocation = new ViewerLocationEvent(
506+
(decimal)Math.Round(currentCluster.CenterLatitude, 6),
507+
(decimal)Math.Round(currentCluster.CenterLongitude, 6),
508+
TourService.GetLocationDescription(currentCluster))
509+
{
510+
Id = Guid.NewGuid()
511+
};
512+
}
513+
else
444514
{
445-
Id = Guid.NewGuid()
446-
};
515+
CurrentTourLocation = null;
516+
}
447517
}
448518
else
449519
{
@@ -777,6 +847,7 @@ public async ValueTask DisposeAsync()
777847
TourLocations.Clear();
778848
PendingLocationQueue.Clear();
779849
tourClusters.Clear();
850+
externalTourGroups.Clear();
780851
aggregatedMarkers.Clear();
781852
locationKeyIndex.Clear();
782853
userLocationIndex.Clear();
@@ -788,6 +859,31 @@ public async ValueTask DisposeAsync()
788859
}
789860
}
790861

862+
/// <summary>
863+
/// Restarts the tour by stopping any active tour, clearing caches, and starting fresh.
864+
/// Can be called externally to force a tour restart with new groupings.
865+
/// </summary>
866+
public async Task RestartTourAsync()
867+
{
868+
Console.WriteLine("🔄 RestartTourAsync called - restarting tour with fresh groupings");
869+
870+
// Stop current tour if active
871+
if (IsTourActive)
872+
{
873+
await StopMapTour();
874+
}
875+
876+
// Clear cached tour data
877+
tourClusters.Clear();
878+
externalTourGroups.Clear();
879+
880+
// Start new tour if we have locations
881+
if (TourLocations.Count > 0)
882+
{
883+
await StartMapTour();
884+
}
885+
}
886+
791887
/// <summary>
792888
/// Optional custom icon provider for marker icons.
793889
/// If not provided, default icon logic will be used.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Fritz.Charlie.Common;
2+
3+
namespace Fritz.Charlie.Components.Models;
4+
5+
/// <summary>
6+
/// Represents a tour group provided by an external service (like TourGroupingService with AI region names).
7+
/// This allows the ChatterMapDirect component to use custom grouping logic.
8+
/// </summary>
9+
public class ExternalTourGroup
10+
{
11+
/// <summary>
12+
/// The individual locations in this group.
13+
/// </summary>
14+
public List<ViewerLocationEvent> Locations { get; set; } = new();
15+
16+
/// <summary>
17+
/// The center latitude of the group.
18+
/// </summary>
19+
public double CenterLatitude { get; set; }
20+
21+
/// <summary>
22+
/// The center longitude of the group.
23+
/// </summary>
24+
public double CenterLongitude { get; set; }
25+
26+
/// <summary>
27+
/// The name for this region (e.g., "Iberian Peninsula", "Pacific Northwest").
28+
/// </summary>
29+
public string RegionName { get; set; } = string.Empty;
30+
31+
/// <summary>
32+
/// The optimal zoom level to view all pins in this group.
33+
/// </summary>
34+
public int OptimalZoomLevel { get; set; }
35+
36+
/// <summary>
37+
/// Number of locations in this group.
38+
/// </summary>
39+
public int LocationCount => Locations.Count;
40+
}

0 commit comments

Comments
 (0)