fleet_hazard 0.5.1 copy "fleet_hazard: ^0.5.1" to clipboard
fleet_hazard: ^0.5.1 copied to clipboard

Geographic clustering of scattered vehicle reports into map-ready hazard zones with severity aggregation. Pure Dart, pluggable.

fleet_hazard #

pub package CI License: BSD-3-Clause

Turn scattered vehicle reports into map-ready hazard zones. Clusters individual snow/ice reports by geography and severity — pure Dart, no Flutter dependency.

Use fleet_hazard when you need to aggregate telemetry from multiple vehicles into hazard zones that can be rendered on any map widget.

Features #

  • FleetReport model with road condition, timestamp, confidence, and position — the input atom, carrying a vehicleId.
  • HazardZone cluster model with severity, vehicle count, and average confidence. The zone retains anonymized ZoneObservations — it drops the vehicleId, so the aggregate is genuinely anonymized and not a re-identifiable per-vehicle trail.
  • HazardAggregator pure-Dart clustering algorithm for snowy and icy reports.
  • FleetProvider abstract interface so apps can swap simulated, local, or remote telemetry backends.
  • Pure Dart package with no Flutter dependency.

Install #

dependencies:
  fleet_hazard: ^0.5.0

Quick Start #

import 'package:fleet_hazard/fleet_hazard.dart';
import 'package:latlong2/latlong.dart';

final reports = [
  FleetReport(
    vehicleId: 'V-001',
    position: const LatLng(35.050, 137.250),
    timestamp: DateTime.now(),
    condition: RoadCondition.snowy,
  ),
  FleetReport(
    vehicleId: 'V-002',
    position: const LatLng(35.052, 137.252),
    timestamp: DateTime.now(),
    condition: RoadCondition.icy,
  ),
];

final zones = HazardAggregator.aggregate(reports);

for (final zone in zones) {
  print('${zone.severity.name}: ${zone.vehicleCount} vehicles');
}

Integration Pattern #

fleet_hazard usually lives between a telemetry stream and a map overlay. Collect reports from your provider, aggregate them into zones, then render the zone summaries in whatever map widget or overlay layer your app uses.

import 'dart:async';

import 'package:fleet_hazard/fleet_hazard.dart';
import 'package:flutter/material.dart';

class HazardSummaryList extends StatefulWidget {
  const HazardSummaryList({
    super.key,
    required this.provider,
  });

  final FleetProvider provider;

  @override
  State<HazardSummaryList> createState() => _HazardSummaryListState();
}

class _HazardSummaryListState extends State<HazardSummaryList> {
  final reports = <FleetReport>[];
  StreamSubscription<FleetReport>? subscription;

  @override
  void initState() {
    super.initState();
    subscription = widget.provider.reports.listen((report) {
      setState(() {
        reports.add(report);
      });
    });
    widget.provider.startListening();
  }

  @override
  void dispose() {
    subscription?.cancel();
    widget.provider.stopListening();
    widget.provider.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final zones = HazardAggregator.aggregate(reports);

    return ListView(
      shrinkWrap: true,
      children: [
        for (final zone in zones)
          ListTile(
            title: Text('${zone.severity.name} zone'),
            subtitle: Text(
              '${zone.vehicleCount} vehicles • '
              '${zone.radiusMeters.toStringAsFixed(0)}m radius',
            ),
          ),
      ],
    );
  }
}

In a full navigation UI, this same zones list is what you would pass to a map overlay layer so the driver sees clustered snowy and icy segments, not raw per- vehicle noise.

An empty result is not an all-clear #

HazardAggregator.aggregate() returns an empty list in three different situations, and it cannot tell you which one you are in:

  • nobody has reported this road;
  • the fleet drove it and found it clear;
  • the fleet drove it and every sensor returned RoadCondition.unknown.

If you render an empty list as a clear road, you will show a driver a clear road on stretches where nothing is known at all. Use aggregateWithProvenance() when a person is going to act on the answer:

// oracle:placeholders reports
final result = HazardAggregator.aggregateWithProvenance(
  reports,
  maxAge: const Duration(minutes: 30),
);

if (result.isSilent) {
  // No fresh reports at all. Say so — do not render a clear road.
} else if (result.isAllUnknown) {
  // Vehicles drove it; none could read it. Also not a clear road.
} else if (result.isMeasuredClear) {
  // Vehicles drove it and found it fine. This you may render as clear.
} else {
  for (final zone in result.zones) {
    // Hazard zones, same clustering as aggregate().
  }
}

maxAge is what makes a claim like "12 vehicles reported in the last 30 minutes" true: reports older than it are excluded from clustering and counted in result.staleCount. Omit maxAge and no age filter is applied at all — a report from last week counts the same as one from a minute ago.

result.freshestReportAt is null when nothing was reported. null means NOT KNOWN — do not coalesce it into a time or render it as freshness.

Implement a provider #

import 'dart:async';

class MyFleetProvider implements FleetProvider {
  final _controller = StreamController<FleetReport>.broadcast();

  @override
  Stream<FleetReport> get reports => _controller.stream;

  @override
  Future<void> startListening() async {
    // Connect to your telemetry source, and add each report:
    //   _controller.add(report);
  }

  @override
  Future<void> stopListening() async {
    // Stop updates
  }

  @override
  void dispose() {
    _controller.close();
  }
}

API Overview #

Type Purpose
FleetReport Individual vehicle report (the input atom) carrying vehicleId, position, road condition, confidence, and timestamp.
ZoneObservation Anonymized observation retained inside a zone — position, condition, timestamp, confidence; no vehicleId.
HazardZone Clustered geographic hazard summary with severity, a precomputed vehicleCount, and confidence rollups over anonymized observations.
HazardAggregator Pure Dart clustering algorithm that converts reports into hazard zones. aggregate() returns a bare List<HazardZone>; aggregateWithProvenance() returns a FleetAggregate and enforces maxAge.
FleetAggregate Aggregation result that keeps absence distinct from a measured all-clear: zones, reportCount, unknownCount, staleCount, freshestReportAt, plus isSilent / isMeasuredClear / isAllUnknown.
FleetProvider Stream-based interface for simulated, local, or remote fleet telemetry sources.
RoadCondition Canonical hazard labels such as dry, snowy, and icy.

Works With #

Package How
driving_weather Weather conditions correlate with fleet-reported hazards
map_viewport_bloc Hazard zones render at Z3 in the viewport layer stack
driving_consent Fleet data sharing requires explicit consent

See Also #

Part of SNGNav — 11 packages for offline-first navigation on Flutter.

License #

BSD-3-Clause — see LICENSE.

0
likes
150
points
480
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Geographic clustering of scattered vehicle reports into map-ready hazard zones with severity aggregation. Pure Dart, pluggable.

Repository (GitHub)
View/report issues
Contributing

Topics

#fleet #hazard #automotive #navigation #winter-driving

License

BSD-3-Clause (license)

Dependencies

equatable, latlong2

More

Packages that depend on fleet_hazard