feat(monitor): aggregate nationwide fleet by province
This commit is contained in:
@@ -25,13 +25,14 @@ type MonitorSummary struct {
|
||||
}
|
||||
|
||||
type MonitorMapResponse struct {
|
||||
Mode string `json:"mode"`
|
||||
Zoom int `json:"zoom"`
|
||||
Total int `json:"total"`
|
||||
Truncated bool `json:"truncated"`
|
||||
Points []MonitorMapPoint `json:"points"`
|
||||
Clusters []MonitorMapCluster `json:"clusters"`
|
||||
AsOf string `json:"asOf"`
|
||||
Mode string `json:"mode"`
|
||||
Zoom int `json:"zoom"`
|
||||
Total int `json:"total"`
|
||||
Truncated bool `json:"truncated"`
|
||||
Points []MonitorMapPoint `json:"points"`
|
||||
Clusters []MonitorMapCluster `json:"clusters"`
|
||||
ProvincePoints []MonitorMapProvincePoint `json:"provincePoints"`
|
||||
AsOf string `json:"asOf"`
|
||||
}
|
||||
|
||||
type MonitorWorkspaceResponse struct {
|
||||
@@ -69,6 +70,16 @@ type MonitorMapCluster struct {
|
||||
Unknown int `json:"unknown"`
|
||||
}
|
||||
|
||||
// MonitorMapProvincePoint is deliberately compact and identity-free. At the
|
||||
// nationwide zoom the browser groups these already-scoped coordinates with
|
||||
// AMap's cached administrative boundary node, while Clusters remain available
|
||||
// as a no-blank-map fallback if the boundary resource cannot be loaded.
|
||||
type MonitorMapProvincePoint struct {
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type TrackPlaybackResponse struct {
|
||||
VIN string `json:"vin"`
|
||||
Plate string `json:"plate"`
|
||||
|
||||
@@ -373,6 +373,7 @@ const (
|
||||
monitorVehicleLimit = 10000
|
||||
monitorPointLimit = 2000
|
||||
monitorPointZoom = 11
|
||||
monitorProvinceMaxZoom = 5
|
||||
monitorClusterGridPixels = 64
|
||||
vehicleSearchKeywordLimit = 100
|
||||
)
|
||||
@@ -606,17 +607,19 @@ func buildMonitorMapResponse(vehicles Page[VehicleRealtimeRow], query url.Values
|
||||
if zoom > 20 {
|
||||
zoom = 20
|
||||
}
|
||||
provinceMode := zoom <= monitorProvinceMaxZoom
|
||||
bounds, hasBounds, err := parseMonitorBounds(query.Get("bounds"))
|
||||
if err != nil {
|
||||
return MonitorMapResponse{}, err
|
||||
}
|
||||
result := MonitorMapResponse{
|
||||
Mode: "clusters",
|
||||
Zoom: zoom,
|
||||
Truncated: vehicles.Total > len(vehicles.Items),
|
||||
Points: []MonitorMapPoint{},
|
||||
Clusters: []MonitorMapCluster{},
|
||||
AsOf: time.Now().UTC().Format(time.RFC3339),
|
||||
Mode: "clusters",
|
||||
Zoom: zoom,
|
||||
Truncated: vehicles.Total > len(vehicles.Items),
|
||||
Points: []MonitorMapPoint{},
|
||||
Clusters: []MonitorMapCluster{},
|
||||
ProvincePoints: []MonitorMapProvincePoint{},
|
||||
AsOf: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
points := make([]MonitorMapPoint, 0, len(vehicles.Items))
|
||||
for _, vehicle := range vehicles.Items {
|
||||
@@ -627,7 +630,17 @@ func buildMonitorMapResponse(vehicles Page[VehicleRealtimeRow], query url.Values
|
||||
continue
|
||||
}
|
||||
result.Total++
|
||||
if hasBounds && !bounds.contains(vehicle.Longitude, vehicle.Latitude) {
|
||||
status := monitorVehicleStatus(vehicle)
|
||||
if provinceMode {
|
||||
result.ProvincePoints = append(result.ProvincePoints, MonitorMapProvincePoint{
|
||||
Longitude: vehicle.Longitude,
|
||||
Latitude: vehicle.Latitude,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
// Nationwide province counts describe the complete filtered fleet, not
|
||||
// only the current slippy-map rectangle. Bounds resume after drill-down.
|
||||
if hasBounds && !provinceMode && !bounds.contains(vehicle.Longitude, vehicle.Latitude) {
|
||||
continue
|
||||
}
|
||||
points = append(points, MonitorMapPoint{
|
||||
@@ -637,7 +650,7 @@ func buildMonitorMapResponse(vehicles Page[VehicleRealtimeRow], query url.Values
|
||||
ReportIntervalMs: vehicle.ReportIntervalMs,
|
||||
LocationSource: vehicle.LocationSource,
|
||||
LocationConflict: vehicle.LocationConflict,
|
||||
Status: monitorVehicleStatus(vehicle),
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
if zoom >= monitorPointZoom && len(points) <= monitorPointLimit {
|
||||
@@ -708,6 +721,9 @@ func buildMonitorMapResponse(vehicles Page[VehicleRealtimeRow], query url.Values
|
||||
result.Mode = "points"
|
||||
}
|
||||
}
|
||||
if provinceMode {
|
||||
result.Mode = "provinces"
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -541,6 +541,33 @@ func TestMonitorMapUsesMeaningfulClustersAndReleasesPointsAtDetailZoom(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorMapNationwideReturnsScopedProvinceSeedsAndGridFallback(t *testing.T) {
|
||||
vehicles := Page[VehicleRealtimeRow]{Items: []VehicleRealtimeRow{
|
||||
{VIN: "GUANGDONG-1", Online: true, LocationAvailable: true, Longitude: 113.260, Latitude: 23.130},
|
||||
{VIN: "GUANGDONG-2", Online: false, LocationAvailable: true, Longitude: 113.265, Latitude: 23.135, LastSeen: "2026-07-15T08:00:00+08:00"},
|
||||
{VIN: "OUTSIDE-STATUS", Online: true, LocationAvailable: true, Longitude: 121.47, Latitude: 31.23, SpeedKmh: 20},
|
||||
{VIN: "NO-LOCATION", Online: true},
|
||||
}, Total: 4, Limit: 4}
|
||||
|
||||
result, err := buildMonitorMapResponse(vehicles, url.Values{
|
||||
"zoom": {"5"},
|
||||
"status": {"offline"},
|
||||
"bounds": {"113,22,114,24"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Mode != "provinces" || result.Total != 1 || len(result.ProvincePoints) != 1 {
|
||||
t.Fatalf("nationwide response must expose only filtered, located province seeds: %+v", result)
|
||||
}
|
||||
if result.ProvincePoints[0].Status != "offline" || result.ProvincePoints[0].Longitude != 113.265 {
|
||||
t.Fatalf("unexpected province seed: %+v", result.ProvincePoints[0])
|
||||
}
|
||||
if len(result.Points)+len(result.Clusters) == 0 {
|
||||
t.Fatal("nationwide response must retain a grid fallback when AMap district data is unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonitorMapTenThousandVehicles(b *testing.B) {
|
||||
vehicles := syntheticMonitorVehicles(10_000)
|
||||
query := url.Values{"zoom": {"5"}}
|
||||
|
||||
Reference in New Issue
Block a user