A comprehensive web application for defining geofence boundaries and managing location-based triggers for Dartmouth campus structures. This tool allows users to create accurate geofences by combining map-drawn boundaries with real-world GPS walking points, and then configure automated triggers for entry/exit events.
# Clone the repository
git clone https://github.com/alejo742/geofencing.git
cd geofencing
# Install dependencies
npm install
# Run the development server
npm run devOpen http://localhost:3000 with your browser to access the application.
For best results, use this tool on a mobile device with GPS capabilities. The app works in most modern browsers, but we recommend:
- Chrome/Safari on iOS
- Chrome on Android
The Evergreen Geofencing Tool consists of two main sections:
- Geofencing - Define and refine structure boundaries
- Triggers - Configure location-based notifications and flows
Use the navigation bar to switch between these sections.
- Click the "New Structure" button in the sidebar
- Enter the name of the building or area (e.g., "Baker Library")
- Enter a unique code/identifier (e.g., "BAKER")
- Select the structure type (academic, residential, dining, etc.)
- Optionally select a parent structure to create a hierarchy
- Click "Create" to start defining the structure
The app supports parent-child relationships between structures:
- When creating a new structure, you can select an existing structure as its parent
- This creates a hierarchy (e.g., "Baker Library" → "Reading Room" → "Study Carrel 1")
- Useful for complex buildings with multiple zones or nested areas
- Click the "Hierarchy" button in the header to open the hierarchy manager
- View the complete structure tree with indentation showing relationships
- Edit parent-child relationships by selecting a structure and choosing a new parent
- The system prevents circular dependencies (a structure cannot be its own ancestor)
The app allows you to define boundaries in two ways simultaneously:
- Click directly on the map to place points
- Points will connect automatically to form a polygon
- Drag any point to reposition it
- Click a point to select/delete it
- Walk to a corner or boundary point of the building
- Enable "GPS Mode" using the toggle
- Click "Add Walk Point" to drop a point at your current location
- Repeat for each corner of the building
- The app shows your current GPS accuracy
Once you have both map-drawn and GPS-walked points:
- A "trigger band" is automatically generated between the two boundaries
- Use the thickness slider to adjust how wide this band should be
- The trigger band represents the area where location-based events will fire
Click "Triggers" in the navigation bar to access the triggers management system.
- Click "Create Trigger" on the triggers page
- Select a Structure: Search and select the campus structure for your trigger
- Choose Trigger Type:
- On Enter: Fires when user enters the structure boundary
- On Exit: Fires when user leaves the structure boundary
- Configure Notification:
- Title: Main notification text (3-50 characters)
- Body: Additional notification details (3-100 characters)
- Set Flow ID: Dialogflow CX flow identifier that will be triggered
- Click "Create Trigger" to save
- Duplicate Prevention: Each structure can have only one "enter" trigger and one "exit" trigger
- Real-time Validation: The form prevents creating duplicate triggers and shows which trigger types already exist
- Character Limits: Enforced to ensure notifications display properly on mobile devices
- Flow ID Format: Must be alphanumeric with underscores and hyphens only
- Search: Filter triggers by title, structure name, or flow ID
- Statistics: View quick stats including total triggers, active triggers, and structures with triggers
- Status Toggle: Activate/deactivate triggers without deleting them
- Bulk Actions: Export/import trigger configurations
- Click "Edit" on any trigger to modify its configuration
- All validation rules apply to edits
- Changes are saved immediately
- Click "Delete" to remove a trigger permanently
- Confirmation is required before deletion
- Click "Export Triggers" to download a JSON file containing all trigger configurations
- Export includes metadata like creation dates and statistics
- Use for backup, sharing with team members, or deploying to production
- Click "Import Triggers" and select a previously exported JSON file
- Existing triggers with matching IDs will be updated
- New triggers will be created
- Import validation ensures data integrity
The exported geofence data contains structure definitions with:
{
"version": "2.0",
"structures": [
{
"code": "BAKER",
"name": "Baker Library",
"description": "Main campus library with study spaces",
"type": "academic",
"parentId": null,
"mapPoints": [{"lat": 43.7056, "lng": -72.2943}, ...],
"walkPoints": [{"lat": 43.7055, "lng": -72.2944}, ...],
"triggerBand": {
"points": [...],
"thickness": 5
},
"lastModified": "2025-08-23T16:30:22Z"
}
]
}The exported triggers data contains trigger configurations:
{
"version": "1.0",
"triggers": [
{
"id": "trigger-123",
"structureCode": "BAKER",
"triggerType": "enter",
"notificationConfig": {
"title": "Welcome to Baker Library!",
"body": "Tap to see study spaces and current hours"
},
"flowId": "library_main_flow",
"isActive": true,
"createdAt": "2025-08-23T15:30:00Z",
"updatedAt": "2025-08-23T15:30:00Z"
}
],
"metadata": {
"exportedAt": "2025-08-23T16:45:00Z",
"totalTriggers": 1
}
}The exported trigger data is designed to work seamlessly with the Flutter geofencing implementation. You just have to parse the JSON and create triggers using the provided structure codes.
// Import trigger configuration
final Map<String, List<GeofencingTrigger>> structureTriggers = {};
// Group triggers by structure code
for (final trigger in triggerData.triggers) {
if (!structureTriggers.containsKey(trigger.structureCode)) {
structureTriggers[trigger.structureCode] = [];
}
structureTriggers[trigger.structureCode]!.add(trigger);
}
// Set up geofencing triggers for each structure
for (final entry in structureTriggers.entries) {
final structureCode = entry.key;
final triggers = entry.value;
// Find enter and exit triggers
final enterTrigger = triggers.firstWhere(
(t) => t.triggerType == 'enter',
orElse: () => null,
);
final exitTrigger = triggers.firstWhere(
(t) => t.triggerType == 'exit',
orElse: () => null,
);
EvergreenGeofencing.setStructureTriggers(
structureCode,
onEnterTrigger: enterTrigger != null ? GeofencingTrigger(
callback: (code, name, location) async {
// Show notification if configured
if (enterTrigger.isActive) {
await NotificationService.show(
title: enterTrigger.notificationConfig.title,
body: enterTrigger.notificationConfig.body,
);
// Trigger Dialogflow
await Dialogflow.triggerFlow(enterTrigger.flowId);
}
},
notificationConfig: NotificationConfig(
title: enterTrigger.notificationConfig.title,
body: enterTrigger.notificationConfig.body,
),
) : null,
onExitTrigger: exitTrigger != null ? GeofencingTrigger(
callback: (code, name, location) async {
// Show notification if configured
if (exitTrigger.isActive) {
await NotificationService.show(
title: exitTrigger.notificationConfig.title,
body: exitTrigger.notificationConfig.body,
);
// Trigger Dialogflow
await Dialogflow.triggerFlow(exitTrigger.flowId);
}
},
notificationConfig: NotificationConfig(
title: exitTrigger.notificationConfig.title,
body: exitTrigger.notificationConfig.body,
),
) : null,
);
}If you prefer to handle each trigger individually:
// Assuming we're already looping through the correct triggers
for (final trigger in triggers) {
if (!trigger.isActive) continue; // Skip inactive triggers
final geofencingTrigger = GeofencingTrigger(
callback: (code, name, location) async {
// Show notification and trigger flow
await NotificationService.show(
title: trigger.notificationConfig.title,
body: trigger.notificationConfig.body,
);
await Dialogflow.triggerFlow(trigger.flowId); // assuming there's a Dialogflow service
},
notificationConfig: NotificationConfig(
title: trigger.notificationConfig.title,
body: trigger.notificationConfig.body,
),
);
// Use ternary operator to determine trigger type
trigger.triggerType == 'enter'
? EvergreenGeofencing.setStructureTriggers(
trigger.structureCode,
onEnterTrigger: geofencingTrigger,
)
: EvergreenGeofencing.setStructureTriggers(
trigger.structureCode,
onExitTrigger: geofencingTrigger,
);
}- Geofencing Tab: Create and manage structure boundaries
- Triggers Tab: Configure location-based notifications and flows
- Back Button: Navigate back to previous screens when creating/editing
- Hierarchy Manager: Access advanced structure relationship management
This project uses Next.js with the App Router and TypeScript. For more information about Next.js, check out: