Skip to content

Custom Geofences

Users can draw custom polygon geofences on the "My Geofences" page for precise notification zones (e.g., park boundaries) instead of distance-from-center circles.

How it works

PoracleWeb acts as the single geofence source for PoracleJS. Instead of PoracleJS connecting to Koji directly, PoracleWeb fetches admin geofences from Koji, resolves group names from the Koji parent chain, merges them with user-drawn geofences from its own database, and serves everything via one endpoint. No custom code is needed in PoracleJS or Koji — standard upstream versions work.

  1. User draws a polygon on the map, saved to the PoracleWeb database
  2. PoracleWeb serves a unified geofence feed via GET /api/geofence-feed — admin geofences from Koji (cached 5 minutes) plus user geofences from the local DB
  3. PoracleJS loads all geofences from a single PoracleWeb URL (no direct Koji connection needed)
  4. User geofences have displayInMatches: false — names are hidden from all DMs for privacy
  5. Admin geofences have displayInMatches: true and group populated from Koji parent hierarchy
  6. Users can submit geofences for admin review, which creates a Discord forum post with a static map
  7. Admins approve, and the geofence is promoted to Koji as a public area visible to all users
  8. If Koji is unreachable, user geofences are still served (graceful degradation)
  9. If PoracleWeb itself is down, PoracleJS falls back to its built-in .cache/ directory

Component diagram

graph LR
    KojiServer[(Koji Server
Public areas)] -->|admin geofences
cached 5 min| PoracleWeb subgraph PoracleWeb Feed[GeofenceFeedController
GET /api/geofence-feed
Unified proxy] DB[(poracle_web DB
user geofences)] end PoracleWeb -->|single URL
admin + user geofences| Poracle[PoracleJS
geofence.path] Poracle -.->|failover| Cache[PoracleJS .cache/]

Detailed internal flow

graph TB
    subgraph PoracleWeb
        UI1[My Geofences Page
Draw / Name / Submit] UI2[Geofence Mgmt
Approve / Reject / Delete] Feed[GeofenceFeedController
GET /api/geofence-feed
Unified proxy] Svc[UserGeofenceService
Create / Delete / Submit / Approve] Koji[KojiService
Fetch admin geofences + approve] Discord[DiscordNotificationService
Forum posts + maps] end DB[(poracle_web DB
user_geofences)] KojiServer[(Koji Server
Public areas)] DiscordForum[(Discord Forum
Threads + Tags)] Poracle[PoracleJS
Single URL to PoracleWeb] UI1 --> Svc UI2 --> Svc Svc --> DB Svc --> Koji Svc --> Discord Feed --> DB Feed --> Koji Koji --> KojiServer Discord --> DiscordForum Feed -.->|admin + user geofences| Poracle

Geofence lifecycle

stateDiagram-v2
    [*] --> Create : User draws polygon
    Create --> Active : Save to DB + add to area + reload Poracle

    Active --> Active : Alerts work via feed endpoint
    Active --> Submitted : User clicks Submit for Review

    Submitted --> PendingReview : Discord forum post created with map
    PendingReview --> PendingReview : Still works privately

    PendingReview --> Approved : Admin approves
    PendingReview --> Rejected : Admin rejects

    Approved --> [*] : Push to Koji as public area\nLock Discord thread
    Rejected --> Active : Stays private with review notes\nLock Discord thread

    note right of Active : Private — only owner\ngets alerts.\nName hidden from\nall DMs.
    note right of Approved : Public — all users\ncan select it on\nthe Areas page.

Admin geofence management

Admins can view and manage all user-created geofences from the User Geofences page in the Admin sidebar (/admin/geofence-submissions).

View modes

The page supports three view modes, toggled via the toolbar:

  • Card view (default) — Map thumbnail cards grouped by region in collapsible expansion panels. Each card shows the geofence polygon, owner with avatar, status chip, metadata, and action buttons. Map thumbnails are lazy-loaded via IntersectionObserver and preserved across view switches.
  • List view — Compact table grouped by region in collapsible expansion panels. Columns: Name, Status, Owner (with avatar), Region, Points, Created, Actions.
  • Table view — Flat ungrouped table showing all geofences with sortable columns. Columns: Name, Status, Owner, Region, Points, Created, Submitted, Reviewed By, Actions. Click column headers to sort ascending/descending.

Features

  • Region grouping — Card and list views group geofences by their groupName (region). Each group has a collapsible mat-expansion-panel with the region name and a geofence count badge. Regions are sorted alphabetically, with "No Region" last.
  • Sortable columns — Table view supports sorting by name, status, owner, region, points, created, and submitted date. Click a column header to sort; click again to reverse direction. Sorting also applies to the card and list views.
  • Owner display names and avatars — Resolves Discord/Telegram usernames from the Poracle humans table instead of showing raw user IDs. Circular avatars (24px) are displayed next to owner names. Fallback: generic person icon when no avatar is available.
  • Reviewer display names and avatars — The reviewedBy field is resolved to the reviewer's Discord username and avatar via the same batch human lookup. Reviewer avatars (16px) appear in card metadata and the table's Reviewed By column.
  • Map thumbnails — Each geofence card shows a non-interactive Leaflet map preview with the polygon rendered in its status color. Thumbnails are lazy-loaded via IntersectionObserver for performance.
  • Detail dialog — Click a card's map thumbnail or View button to open an interactive Leaflet map dialog with:
    • Full summary panel (name, owner, group, status, point count, area in km²/m², dates, review notes)
    • Interactive pan/zoom map with the polygon auto-fitted to bounds
    • Reference geofences from Poracle areas shown as dashed colored outlines (same palette as the Areas page) with name tooltips on hover
  • Point count and area — Each geofence shows its vertex count and computed area (m² for areas under 1 km², km² otherwise) using the spherical excess formula
  • Status filtering — Filter tabs for All, Pending, Active, Approved, and Rejected with counts. Filters apply across all view modes.
  • Skeleton loading — Animated skeleton cards with map placeholders during data fetch

Owner and reviewer resolution

Owner and reviewer names are resolved via a single batch lookup against the Poracle humans table. Distinct owner IDs and reviewer IDs are merged and fetched in one pass for efficiency. Avatars are served from AvatarCacheService with Discord CDN default fallback. The UserGeofence model exposes ownerName, ownerAvatarUrl, reviewedByName, and reviewedByAvatarUrl as enriched (non-mapped) properties set by UserGeofenceService.GetAllWithDetailsAsync() and AdminGeofenceController.GetAll.

Geofence statuses

Status Description
active Private, user-only. Alerts work via the feed endpoint.
pending_review Submitted for admin review. Discord forum post created. Still works privately.
approved Promoted to Koji as a public area. Visible to all users.
rejected Remains private with review notes. User can continue using it.

Limits

  • Maximum 10 custom geofences per user
  • Polygons limited to 500 points

Naming rules

  • Geofence names (kojiName field) are always lowercase because Poracle does case-sensitive area matching
  • Names are auto-generated from the user-provided display name (lowercased)
  • Collisions are resolved by appending a numeric suffix

Caching

  • Admin geofences from Koji are cached in memory for 5 minutes (IMemoryCache)
  • Cache is invalidated when a geofence is approved/promoted to Koji
  • User geofences are served directly from the database (no caching)

Failover

Failure Behavior
Koji unreachable Feed endpoint logs the error, still serves user geofences from DB
PoracleWeb down PoracleJS falls back to its built-in .cache/ directory

Setup

1. Create the PoracleWeb database

A separate MySQL/MariaDB database for app-owned data:

CREATE DATABASE poracle_web;

The user_geofences table is created automatically on first run.

2. Configure the Koji connection

Set the following in your environment or appsettings.json:

  • Koji:ApiAddress — Koji server URL (e.g., http://localhost:8080)
  • Koji:BearerToken — Koji API bearer token
  • Koji:ProjectId — Koji project ID for promoted geofences
  • Koji:ProjectName — Koji project name, used to fetch from /geofence/poracle/{name}

3. Point PoracleJS to PoracleWeb

Set geofence.path in PoracleJS config to a single PoracleWeb URL:

"geofence": {
  "path": "http://poracleweb-host:8082/api/geofence-feed"
}

Remove kojiOptions.bearerToken from the PoracleJS geofence config if present (it is harmless if left, but no longer needed).

4. Remove group_map.json

Remove group_map.json from PoracleJS if it exists — group names are now resolved automatically from the Koji parent chain by PoracleWeb.

5. Restart PoracleJS

pm2 restart all

6. Discord forum channel (optional)

For geofence submission discussions:

  1. Set Discord:GeofenceForumChannelId to your forum channel ID
  2. Give the bot View Channel, Send Messages in Threads, and Manage Threads permissions
  3. Forum tags (Pending/Approved/Rejected) are auto-created if the bot has Manage Channels permission, or create them manually

PoracleJS failover

PoracleJS's built-in .cache/ directory automatically caches geofence data. If PoracleWeb is temporarily unavailable, PoracleJS falls back to its last cached copy.