- Added new `contacts` and `project_contacts` database tables for managing contacts. - Created API endpoints for CRUD operations on contacts and linking them to projects. - Developed UI components including `ContactForm` and `ProjectContactSelector`. - Integrated navigation and translations for Polish language support. - Documented usage, features, and future enhancements for the contacts system. feat: Introduce DOCX Template System for generating documents from templates - Enabled creation and management of DOCX templates with placeholders for project data. - Documented the process for creating templates, uploading, and generating documents. - Included detailed information on available variables and custom data fields. - Implemented troubleshooting guidelines for common issues related to template generation. feat: Add Radicale CardDAV Sync Integration for automatic contact synchronization - Implemented automatic syncing of contacts to a Radicale server on create/update/delete actions. - Documented setup instructions, including environment variable configuration and initial sync script. - Provided troubleshooting steps for common sync issues and error codes. feat: Develop Route Planning Feature with Optimization using OpenRouteService API - Integrated multi-point routing and automatic optimization for project locations. - Documented setup, usage, and technical implementation details for route planning. - Included performance considerations and troubleshooting for common routing issues. chore: Remove unnecessary files and scripts from the codebase - Deleted temporary, debug-related, and test-specific files that are not needed in production. - Reviewed and ensured core application code and essential documentation remain intact.
518 lines
16 KiB
Markdown
518 lines
16 KiB
Markdown
# Route Planning Feature with Optimization
|
|
|
|
This feature allows you to plan routes between multiple project locations using OpenRouteService API, with automatic optimization to find the fastest route regardless of point addition order.
|
|
|
|
## 🌟 Overview
|
|
|
|
The route planning system integrates with your project map to help optimize field visits. It supports:
|
|
- Multi-point routing through project locations
|
|
- Automatic route optimization for 3+ points
|
|
- Visual route display on the map
|
|
- Distance and time estimation
|
|
- Hybrid optimization approach (API + permutation testing)
|
|
|
|
---
|
|
|
|
## 🚀 Setup
|
|
|
|
1. **Get an API Key**:
|
|
- Visit [OpenRouteService](https://openrouteservice.org/)
|
|
- Sign up for a free account
|
|
- Generate an API key
|
|
|
|
2. **Configure Environment**:
|
|
- Copy `.env.example` to `.env.local`
|
|
- Add your API key: `NEXT_PUBLIC_ORS_API_KEY=your_actual_api_key`
|
|
|
|
3. **Install Dependencies**:
|
|
```bash
|
|
npm install @mapbox/polyline
|
|
```
|
|
|
|
4. **Restart Development Server**:
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
## How to Use
|
|
|
|
### Basic Routing (2 Points)
|
|
1. **Select Route Tool**: Click the route icon in the tool panel (looks like a path)
|
|
2. **Add Projects**: Click on project markers to add them to your route
|
|
3. **Calculate Route**: Click "Calculate Route" to get directions
|
|
4. **View Results**: See distance, duration, and route path on the map
|
|
|
|
### Optimized Routing (3+ Points)
|
|
1. **Select Route Tool**: Click the route icon in the tool panel
|
|
2. **Add Projects**: Click on project markers (order doesn't matter)
|
|
3. **Find Optimal Route**: Click "Find Optimal Route" - system automatically finds fastest path
|
|
4. **View Optimization Results**: See which route order was selected and performance stats
|
|
|
|
## Features
|
|
|
|
### Core Features
|
|
- **Multi-point routing**: Plan routes through multiple project locations
|
|
- **Visual route display**: Blue dashed line shows the calculated route
|
|
- **Route markers**: Green start marker, red end marker
|
|
- **Route information**: Distance and estimated travel time
|
|
- **Interactive management**: Add/remove projects from route
|
|
- **Map auto-fit**: Automatically adjusts map view to show entire route
|
|
|
|
### Optimization Features ✨
|
|
- **Hybrid Optimization**: Uses ORS Optimization API first, falls back to permutation testing
|
|
- **Smart Fallback**: Automatically switches to proven permutation method if ORS fails
|
|
- **Order Detection**: Clearly shows when route order was actually optimized vs unchanged
|
|
- **Large Point Support**: Can handle up to 50+ points with ORS API
|
|
- **Performance Monitoring**: Detailed logging of optimization approach and results
|
|
- **Real-time Progress**: Shows "Finding Optimal Route..." during calculation
|
|
|
|
## Technical Implementation
|
|
|
|
### Core Functions
|
|
|
|
#### `calculateRoute()`
|
|
Main function that handles both basic and optimized routing with hybrid approach:
|
|
|
|
```javascript
|
|
const calculateRoute = async () => {
|
|
// For 2 points: direct calculation
|
|
if (coordinates.length === 2) {
|
|
const routeData = await calculateRouteForCoordinates(coordinates);
|
|
setRouteData({...routeData, optimized: false});
|
|
return;
|
|
}
|
|
|
|
// For 3+ points: try ORS Optimization API first
|
|
let optimizationRequest = {
|
|
jobs: coordinates.map((coord, index) => ({
|
|
id: index,
|
|
location: coord,
|
|
service: 0
|
|
})),
|
|
vehicles: [{
|
|
id: 0,
|
|
profile: 'driving-car',
|
|
// No fixed start/end for true optimization
|
|
capacity: [coordinates.length]
|
|
}],
|
|
options: { g: true }
|
|
};
|
|
|
|
try {
|
|
const optimizationResponse = await fetch('https://api.openrouteservice.org/optimization', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': process.env.NEXT_PUBLIC_ORS_API_KEY,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(optimizationRequest)
|
|
});
|
|
const optimizationData = await optimizationResponse.json();
|
|
|
|
// Extract optimized order from ORS response
|
|
const optimizedCoordinates = extractOptimizedOrder(optimizationData, coordinates);
|
|
|
|
// Check if order actually changed
|
|
const orderChanged = detectOrderChange(coordinates, optimizedCoordinates);
|
|
|
|
if (orderChanged) {
|
|
// Use optimized order
|
|
const routeData = await calculateRouteForCoordinates(optimizedCoordinates);
|
|
setRouteData({...routeData, optimized: true, optimizationStats: {
|
|
method: 'ORS_Optimization_API',
|
|
totalJobs: coordinates.length,
|
|
duration: optimizationData.routes[0].duration,
|
|
distance: optimizationData.routes[0].distance
|
|
}});
|
|
} else {
|
|
// Fallback to permutation testing
|
|
console.log('ORS optimization did not change order, trying permutations...');
|
|
const bestRoute = await findOptimalRouteByPermutations(coordinates);
|
|
const routeData = await calculateRouteForCoordinates(bestRoute);
|
|
setRouteData({...routeData, optimized: true, optimizationStats: {
|
|
method: 'Permutation_Testing',
|
|
totalJobs: coordinates.length,
|
|
duration: routeData.summary.total_duration,
|
|
distance: routeData.summary.total_distance
|
|
}});
|
|
}
|
|
} catch (error) {
|
|
// Complete fallback to permutations
|
|
console.log('ORS optimization failed, using permutation fallback...');
|
|
const bestRoute = await findOptimalRouteByPermutations(coordinates);
|
|
const routeData = await calculateRouteForCoordinates(bestRoute);
|
|
setRouteData({...routeData, optimized: true, optimizationStats: {
|
|
method: 'Permutation_Testing',
|
|
totalJobs: coordinates.length,
|
|
duration: routeData.summary.total_duration,
|
|
distance: routeData.summary.total_distance
|
|
}});
|
|
}
|
|
};
|
|
```
|
|
|
|
#### `calculateRouteForCoordinates(coordinates)`
|
|
Handles individual OpenRouteService Directions API calls:
|
|
|
|
```javascript
|
|
const calculateRouteForCoordinates = async (coordinates) => {
|
|
const requestBody = {
|
|
coordinates: coordinates,
|
|
format: 'geojson',
|
|
instructions: true,
|
|
geometry_simplify: false,
|
|
continue_straight: false,
|
|
roundabout_exits: true,
|
|
attributes: ['avgspeed', 'percentage']
|
|
};
|
|
|
|
const response = await fetch('https://api.openrouteservice.org/v2/directions/driving-car', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': process.env.NEXT_PUBLIC_ORS_API_KEY,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(requestBody)
|
|
});
|
|
|
|
return await response.json();
|
|
};
|
|
```
|
|
|
|
### UI Components
|
|
|
|
#### Dynamic Button Text
|
|
```javascript
|
|
{routeProjects.length > 2 ? 'Find Optimal Route' : 'Calculate Route'}
|
|
```
|
|
|
|
#### Optimization Status Display
|
|
```javascript
|
|
{routeData.optimized && (
|
|
<div className="mb-2 p-2 bg-green-50 border border-green-200 rounded">
|
|
<div className="flex items-center gap-1 font-medium">
|
|
✅ Route Optimized
|
|
</div>
|
|
<div className="mt-1">
|
|
Tested {routeData.optimizationStats.totalPermutations} routes
|
|
</div>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Optimization Limits
|
|
- **Maximum Points**: Limited to 50 points (ORS can handle 100+ in some cases)
|
|
- **Algorithm**: Advanced TSP solver instead of brute-force permutations
|
|
- **API Calls**: Only 2 API calls (1 optimization + 1 detailed route)
|
|
- **Processing Time**: ~1-2 seconds for 50 points (much faster than permutation testing)
|
|
|
|
### Memory Usage
|
|
- Each route response contains detailed geometry data
|
|
- Large numbers of points can consume significant memory
|
|
- Automatic cleanup of unused route data
|
|
|
|
## API Integration
|
|
|
|
### OpenRouteService Optimization API
|
|
```javascript
|
|
{
|
|
jobs: [
|
|
{ id: 0, location: [lng, lat], service: 0 },
|
|
{ id: 1, location: [lng, lat], service: 0 }
|
|
],
|
|
vehicles: [{
|
|
id: 0,
|
|
profile: 'driving-car',
|
|
start: [lng, lat],
|
|
end: [lng, lat],
|
|
capacity: [point_count]
|
|
}],
|
|
options: { g: true }
|
|
}
|
|
```
|
|
|
|
### Directions API Parameters
|
|
```javascript
|
|
{
|
|
coordinates: [[lng, lat], [lng, lat], ...],
|
|
format: 'geojson',
|
|
instructions: true,
|
|
geometry_simplify: false,
|
|
continue_straight: false,
|
|
roundabout_exits: true,
|
|
attributes: ['avgspeed', 'percentage']
|
|
}
|
|
```
|
|
|
|
### Response Handling
|
|
- **Optimization API**: `data.routes[0].steps[]` for optimized order
|
|
- **Directions API**: `data.routes[0].summary` for route details
|
|
- **Fallback Path**: `data.features[0].properties.segments[0]`
|
|
- **Geometry**: Supports both encoded polylines and direct coordinates
|
|
- **Error Handling**: Graceful fallback for failed calculations
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### "Failed to calculate route"
|
|
- **Cause**: Invalid API key or network issues
|
|
- **Solution**: Verify `NEXT_PUBLIC_ORS_API_KEY` in `.env.local`
|
|
|
|
#### "Too many points for optimization"
|
|
- **Cause**: Selected more than 50 points
|
|
- **Solution**: Reduce to 50 or fewer points, or use manual routing
|
|
|
|
#### Optimization taking too long
|
|
- **Cause**: Large number of points or slow API responses
|
|
- **Solution**: Reduce points or wait for completion (much faster than before)
|
|
|
|
#### Optimization API unavailable
|
|
- **Cause**: ORS Optimization API temporarily unavailable
|
|
- **Solution**: Falls back to direct routing without optimization
|
|
|
|
#### Route order not optimized
|
|
- **Cause**: ORS Optimization API returned same order or failed
|
|
- **Solution**: System automatically falls back to permutation testing for guaranteed optimization
|
|
|
|
#### Optimization shows "Order unchanged"
|
|
- **Cause**: Points may already be in optimal order, or API returned original sequence
|
|
- **Solution**: Check browser console for detailed optimization logs
|
|
|
|
#### Permutation fallback activated
|
|
- **Cause**: ORS API unavailable or returned suboptimal results
|
|
- **Solution**: This is normal behavior - permutation testing ensures optimization
|
|
|
|
---
|
|
|
|
## 🔧 Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### 1. "API Key Missing" Error
|
|
**Symptom**: Route calculation fails with authentication error
|
|
|
|
**Solutions**:
|
|
- Check `.env.local` file has `NEXT_PUBLIC_ORS_API_KEY=your_key`
|
|
- Verify no extra spaces around the key
|
|
- Ensure development server was restarted after adding the key
|
|
- Confirm your OpenRouteService API key is active
|
|
|
|
```bash
|
|
# Verify environment variable
|
|
echo $env:NEXT_PUBLIC_ORS_API_KEY
|
|
# Should output your API key
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. Route Not Displaying on Map
|
|
**Symptom**: Calculation succeeds but no route visible
|
|
|
|
**Solutions**:
|
|
- Check browser console for coordinate transformation errors
|
|
- Verify all projects have valid coordinates in database
|
|
- Confirm map is zoomed to appropriate level
|
|
- Check if route layer is enabled in layer control
|
|
|
|
**Debug**:
|
|
```javascript
|
|
// Check route data in browser console
|
|
console.log('Route GeoJSON:', routeData.geojson);
|
|
console.log('Route bounds:', routeData.bounds);
|
|
```
|
|
|
|
---
|
|
|
|
#### 3. Optimization Takes Too Long
|
|
**Symptom**: "Find Optimal Route" hangs or times out for many points
|
|
|
|
**Current Limits**:
|
|
- 8+ points: May take 30+ seconds
|
|
- 10+ points: Not recommended (factorial growth)
|
|
|
|
**Solutions**:
|
|
- Split route into multiple segments
|
|
- Use manual point selection for 8+ locations
|
|
- Consider implementing A* or genetic algorithm for large routes
|
|
|
|
**Permutation Growth**:
|
|
```
|
|
3 points = 6 routes to test
|
|
4 points = 24 routes
|
|
5 points = 120 routes
|
|
6 points = 720 routes
|
|
7 points = 5,040 routes
|
|
8 points = 40,320 routes
|
|
```
|
|
|
|
---
|
|
|
|
#### 4. API Rate Limit Exceeded
|
|
**Symptom**: Error 429 or "Too many requests"
|
|
|
|
**Solutions**:
|
|
- OpenRouteService free tier: 40 requests/minute, 500/day
|
|
- Wait 1 minute and try again
|
|
- Consider upgrading to paid plan for higher limits
|
|
- Implement request queuing with delays
|
|
|
|
```javascript
|
|
// Add rate limiting check
|
|
if (routeProjects.length > 5) {
|
|
alert('Large route may hit rate limits. Consider breaking into segments.');
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### 5. Incorrect Route Order
|
|
**Symptom**: Optimization doesn't select expected fastest route
|
|
|
|
**Causes**:
|
|
- Road network topology (one-way streets, traffic restrictions)
|
|
- API routing preferences (avoid highways, ferries)
|
|
- Distance vs time optimization trade-offs
|
|
|
|
**Verification**:
|
|
```javascript
|
|
// Check all tested routes in console
|
|
routeData.optimizationStats.testedRoutes.forEach(route => {
|
|
console.log(`Route ${route.order}: ${route.distance}m in ${route.duration}s`);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
#### 6. Map Coordinate Transformation Errors
|
|
**Symptom**: "Failed to transform coordinates" in console
|
|
|
|
**Solutions**:
|
|
- Verify Proj4 definitions are loaded
|
|
- Check project coordinates are in valid EPSG:2180 format
|
|
- Confirm transformation libraries are properly initialized
|
|
|
|
```javascript
|
|
// Test coordinate transformation
|
|
import proj4 from 'proj4';
|
|
const wgs84 = proj4('EPSG:2180', 'EPSG:4326', [x, y]);
|
|
console.log('Transformed:', wgs84);
|
|
```
|
|
|
|
---
|
|
|
|
### Performance Tips
|
|
|
|
1. **Batch Route Calculations**: Group nearby projects before calculating routes
|
|
2. **Cache Routes**: Store frequently used routes in localStorage
|
|
3. **Limit Points**: Use max 7 points for real-time optimization
|
|
4. **Debounce Updates**: Wait for user to finish selecting points
|
|
5. **Progressive Loading**: Calculate partial routes while building full path
|
|
|
|
---
|
|
|
|
### API Limitations
|
|
|
|
| Tier | Requests/Minute | Requests/Day | Cost |
|
|
|------|----------------|--------------|------|
|
|
| Free | 40 | 500 | $0 |
|
|
| Starter | 300 | 10,000 | Contact ORS |
|
|
| Business | Custom | Custom | Contact ORS |
|
|
|
|
**Best Practices**:
|
|
- Avoid unnecessary recalculations
|
|
- Implement client-side caching
|
|
- Show loading states during API calls
|
|
- Handle errors gracefully with user feedback
|
|
|
|
---
|
|
|
|
### Quick Reference
|
|
|
|
**Enable Route Planning**:
|
|
```bash
|
|
# 1. Get API key from openrouteservice.org
|
|
# 2. Add to .env.local
|
|
NEXT_PUBLIC_ORS_API_KEY=your_key_here
|
|
# 3. Restart dev server
|
|
npm run dev
|
|
```
|
|
|
|
**Debug Mode**:
|
|
```javascript
|
|
// Enable in RoutePanel.js
|
|
const DEBUG = true;
|
|
// Logs all tested routes and optimization stats
|
|
```
|
|
|
|
**Performance Monitoring**:
|
|
```javascript
|
|
console.time('Route Optimization');
|
|
await optimizeRoute();
|
|
console.timeEnd('Route Optimization');
|
|
// Shows exact optimization duration
|
|
```
|
|
|
|
---
|
|
|
|
### Debug Information
|
|
Check browser console for detailed logs:
|
|
- Coordinate parsing details
|
|
- API request/response structures
|
|
- **Optimization approach used** (ORS API vs permutation fallback)
|
|
- **Order change detection** (whether optimization actually improved the route)
|
|
- Performance timing information
|
|
- **Original vs optimized coordinate sequences**
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## 📁 File Structure
|
|
|
|
```
|
|
src/app/projects/map/page.js # Main map page with routing logic
|
|
src/components/ui/LeafletMap.js # Map component with route rendering
|
|
src/components/ui/mapLayers.js # Map layer configurations
|
|
```
|
|
|
|
---
|
|
|
|
## 📦 Dependencies
|
|
|
|
- `@mapbox/polyline`: For decoding route geometry
|
|
- `leaflet`: Map rendering library
|
|
- `react-leaflet`: React integration for Leaflet
|
|
- `proj4`: Coordinate system transformations
|
|
- OpenRouteService API key (free tier available)
|
|
|
|
---
|
|
|
|
## 🚀 Future Enhancements
|
|
|
|
- **Advanced Vehicle Constraints**: Multiple vehicles, capacity limits, time windows
|
|
- **Route Preferences**: Allow users to prioritize distance vs time vs fuel efficiency
|
|
- **Real-time Traffic**: Integration with live traffic data
|
|
- **Route History**: Save and compare previously optimized routes
|
|
- **Mobile Optimization**: Optimize routes considering current location
|
|
- **Multi-stop Services**: Add service times at each location
|
|
- **Advanced Optimization**: Implement A* or genetic algorithms for 8+ points
|
|
- **Multi-Day Routes**: Break long routes into segments with overnight stops
|
|
- **Export Options**: Export routes to GPS devices or Google Maps
|
|
- **Cost Estimation**: Calculate fuel costs and travel expenses
|
|
|
|
---
|
|
|
|
## 📚 Additional Resources
|
|
|
|
- [OpenRouteService API Documentation](https://openrouteservice.org/dev/#/api-docs)
|
|
- [Directions API Reference](https://openrouteservice.org/dev/#/api-docs/v2/directions)
|
|
- [Polyline Encoding](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
|
|
- [Leaflet Routing Integration](https://www.liedman.net/leaflet-routing-machine/)
|
|
|
|
---
|
|
|
|
**Last Updated**: January 2025
|
|
**Maintainer**: Panel Development Team |