advanced
Geofence
Prioritaet
PostGIS

Prioritaet gestapelter Geofences

Die 6-stufige Prioritaetsleiter, die staedtische Policy-Zonen, Betreiberzonen und Systemstandards ordnet -- und wie PostGIS die strengste Regel an jedem Punkt zurueckgibt.

Levy Fleets TeamMay 18, 202612 min read

Prioritaet gestapelter Geofences

Wenn staedtische Policy-Geofences mit Betreiberzonen ueberlappen, muss eine gewinnen. Levy verwendet eine 6-stufige Prioritaetsleiter, materialisiert als Integer-priority-Spalte auf jeder Zone und beim Lesen ueber ein PostGIS-RPC aufgeloest, das alle Zonen an einem Punkt absteigend nach Prioritaet zurueckgibt.

Die 6-stufige Leiter

Hoehere Prioritaet gewinnt. Die erste nicht-null-Regel pro rule_type wird zur aktiven Regel am Punkt.

StufePrioritaetQuelleBeispiele
11000Stadt-Policy prohibited_areas + speed_limit"Kein Fahren in der Pearl Street", "5 km/h auf dem Platz"
2950Stadt-Policy parking_zones + equity_zones"Parken nur in markierten Korridoren", "Einsatz >= 20% in Zone X"
3700Betreiber-definierte No-Ride-ZonenIhre eigene No-Go-Zone um Privatgrund
4500Betreiber-definierte Slow-ZonenIhre eigene Fussgaengerzonen-Geschwindigkeitsbegrenzung
5300Betreiber-definierte ParkzonenIhre eigenen Parkstellplatz-Definitionen
6100SystemstandardsFallback Subaccount-Standards fuer Geschwindigkeit und Parken

Die numerischen Werte sind absichtlich 50-200 auseinander gesetzt, sodass Betreiber bei Bedarf eine individuelle Zwischenstufen-Zone einfuegen koennen (z.B. eine temporaere VIP-Event-Zone bei Prioritaet 800). Heute zeigt die UI keine eigene Prioritaetsbearbeitung -- jede Betreiberzone erhaelt ihren Stufenstandard --, aber die Spalte ermoeglicht es.

Der Konfliktloeser

src/lib/compliance/conflict-resolver.ts exportiert zwei Funktionen:

FunktionWas sie tut
resolveStack(zones)Nimmt eine Liste von Zonen an einem Punkt, gibt die aktive Regel pro rule_type zurueck (ein Tempolimit, ein No-Ride-Flag, eine Parkregel).
detectOperatorOverrides(operatorZones, policyGeofences)Gibt die Betreiberzonen zurueck, die derzeit von einer hoeher priorisierten Policy ueberdeckt werden.

resolveStack() ist die Funktion, die current_speed_limit_kph in MDS-Antworten und die GBFS-geofencing_zones.json-Prioritaetssortierung antreibt. Auch die Zone-Crossing-Engine ruft sie bei jeder GPS-Telemetrieaktualisierung waehrend einer aktiven Fahrt auf.

detectOperatorOverrides() treibt das Konflikt-Banner auf dem Compliance-Dashboard an.

Das zones_containing_point-RPC

Migration 20270601004000_07_zones_priority_source.sql liefert eine Postgres-Funktion:

CREATE FUNCTION zones_containing_point(
  p_subaccount_id uuid,
  p_lat double precision,
  p_lng double precision
)
RETURNS SETOF zones
LANGUAGE sql STABLE
AS $$
  SELECT *
  FROM zones
  WHERE subaccount_id = p_subaccount_id
    AND deleted_at IS NULL
    AND ST_Contains(geom, ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326))
  ORDER BY priority DESC, created_at ASC;
$$;

Die geom-Spalte wird via Trigger automatisch aus der geojson-Spalte gefuellt. Jede Zone, ob betreiber-authored oder von einer Policy gespiegelt, hat eine nicht-null geom, sodass die PostGIS-Abfrage die kanonische Antwort auf "welche Zonen enthalten diesen Punkt?" ist.

stackForPoint(point)

In JS umhuellt src/lib/compliance/zone-stack.ts das RPC und konsultiert zusaetzlich policy_geofences (das derzeit In-JS-pointInPolygon nutzt, weil die PostGIS-Spalte auf dieser Tabelle bis zur ST_GeomFromGeoJSON-Migration null ist). Die kombinierte Liste wird dann an resolveStack() uebergeben.

const stack = await stackForPoint({
  subaccountId,
  lat: vehicle.last_lat,
  lng: vehicle.last_lng,
});
// stack: [
//   { source: 'city', priority: 1000, rule_type: 'speed', rule_value: 8, ... },
//   { source: 'operator', priority: 500, rule_type: 'speed', rule_value: 10, ... },
//   { source: 'operator', priority: 300, rule_type: 'parking', ... },
// ]

const active = resolveStack(stack);
// active.speed.rule_value === 8 // Stadt gewinnt
// active.parking existiert // Betreiber-Parken gilt (Stadt hat hier keine Parkregel)

Konflikterkennung

detectOperatorOverrides() wird vom Betreiber-Dashboard aufgerufen, um das Hinweisbanner anzuzeigen. Es gibt fuer den gegebenen Subaccount alle Betreiberzonen zurueck, deren Geometrie eine aktive policy_geofences-Zeile hoeherer Prioritaet und gleichen rule_type schneidet.

Feld im ErgebnisBedeutung
operator_zoneDie ueberdeckte Zone
shadowing_ruleDie MDS-Policy-Regel, die sie ueberlagert
affected_areaDas Polygonschnittgebiet (fuer die Kartenvorschau)
rule_typeDer Regeltyp, in dem der Konflikt besteht (speed, no_ride, parking)

In der UI kann der Betreiber:

  • Die ueberdeckte Zone loeschen -- wenn sie jetzt redundant mit der Stadt-Policy ist.
  • Bearbeiten, um nebenher zu funktionieren -- wenn die Betreiberzone aus einem anderen Grund existiert (z.B. eine Slow-Zone, die die No-Go-Zone der Stadt umgibt und Kontext fuer Fahrer liefert).
  • Bestaetigen -- den Hinweis verwerfen ohne etwas zu aendern. Die Ueberdeckung bleibt; das Banner hoert nur auf, zu noergeln.

Lesen der Leiter in mobilen Clients

Die mobile Kunden-App liest GBFS 3.0 geofencing_zones.json (sortiert nach Prioritaet absteigend) und behandelt das erste passende Feature als aktive Regel. Das erwarten GBFS-Validatoren, und es passt zur Leiter-Semantik: die staedtische Regel kommt zuerst, weil sie die hoehere Prioritaet hat.

Wenn Ihr mobiler Client eigene Zonenaufloesung implementiert, gilt dieselbe Regel -- nach Prioritaet absteigend sortieren, das erste passende Feature pro rule_type nehmen.

Szenarien

Szenario 1: Stadt-Slow-Zone ueberlappt Betreiber-Slow-Zone

ZoneQuellePrioritaetTempolimit
Stadt "Innenstadt Fussgaengerzone"Stadt10008 km/h
Betreiber "Plaza"Betreiber50010 km/h

Ergebnis: 8 km/h der Stadt gewinnt im Ueberlappungsbereich. Ausserhalb der Stadtzone, aber innerhalb der Betreiberzone gilt 10 km/h. Die Betreiberzone erscheint im Konflikt-Banner als "ueberdeckt durch Stadt Innenstadt Fussgaengerzone".

Szenario 2: Stadt-No-Go innerhalb Betreiber-Parkzone

ZoneQuellePrioritaetTyp
Stadt "Baustelle"Stadt1000no_ride
Betreiber "Main Street Korridor"Betreiber300parking

Ergebnis: Die Regeln sind unterschiedlicher Typ, also gelten beide. Innerhalb der Baustelle darf das Fahrzeug nicht fahren (Stadt-no_ride gewinnt fuer no_ride-rule_type). Ausserhalb der Baustelle, aber innerhalb der Betreiberzone ist Parken erlaubt (Betreiber-Parken ist die einzige parking-Regel).

Szenario 3: Stadt-Policy laeuft ab

Wenn policy_geofences.is_active auf false flippt (weil active_until verstrichen ist), liefert das zones_containing_point-RPC diese Zeilen nicht mehr. Die Betreiberzone darunter kommt automatisch wieder an die Spitze der Leiter. Kein manuelles Aufraeumen; kein Audit-Log-Update; das Konflikt-Banner verschwindet beim naechsten Seitenaufruf.

Szenario 4: Zwei Stadt-Policies ueberlappen

Staedte veroeffentlichen manchmal zwei Policies mit ueberlappender Geometrie (z.B. eine dauerhafte Slow-Zone plus eine temporaere Veranstaltungs-No-Ride-Zone). Beide stehen bei Prioritaet 1000 (Stadtstufe), aber der Resolver sortiert Gleichstaende nach rule_type-Schaerfe: no_ride schlaegt speed, speed schlaegt parking. Die strengste Regel gilt.

Wenn derselbe rule_type bei gleicher Prioritaet kollidiert, sortieren wir nach start_date absteigend -- die zuletzt aktivierte Policy gewinnt. Das handhabt "Stadt hat ihr Slow-Zonen-Tempolimit von 10 auf 8 km/h aktualisiert" sauber.

Was Sie aendern koennen

KnopfStandardHinweise
Betreiberzonen-PrioritaetStufenstandard (700/500/300)Eigene Werte erfordern heute eine direkte DB-Aktualisierung.
Konflikt bestaetigenn/aDashboard-Aktion; die Ueberdeckung bleibt, aber das Banner hoert auf zu noergeln.
Ueberdeckte Zone loeschenn/aDashboard-Aktion; gespiegelte Stadt-Zonen sind nicht aus der Betreiber-UI loeschbar.

Stadt-Zonen koennen vom Betreiber nicht bearbeitet werden. Sie gehoeren dem Policy-Feed; der einzige Weg, sie zu aendern, ist die Stadt zu bitten, ihren Feed zu aktualisieren.

Was kommt als naechstes?