mirror of
https://github.com/dedicatedcode/reitti.git
synced 2026-01-08 00:53:53 -05:00
Documentation (#569)
This commit is contained in:
BIN
.github/screenshots/livemode.png
vendored
BIN
.github/screenshots/livemode.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 560 KiB After Width: | Height: | Size: 5.6 MiB |
BIN
.github/screenshots/main.png
vendored
BIN
.github/screenshots/main.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 6.9 MiB |
BIN
.github/screenshots/multiple-users.png
vendored
BIN
.github/screenshots/multiple-users.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 6.3 MiB |
26
README.md
26
README.md
@@ -38,7 +38,7 @@ Reitti is a comprehensive personal location tracking and analysis application th
|
||||
### Data Import & Integration
|
||||
- **Multiple Import Formats**: Support for GPX files, Google Takeout JSON, Google Timeline Exports and GeoJSON files
|
||||
- **Real-time Data Ingestion**: Live location updates via OwnTracks and GPSLogger mobile apps
|
||||
- **Batch Processing**: Efficient handling of large location datasets with queue-based processing
|
||||
- **Batch Processing**: Efficient handling of large location datasets with direct processing
|
||||
- **API Integration**: RESTful API for programmatic data access and ingestion
|
||||
|
||||
### Photo Management
|
||||
@@ -66,7 +66,7 @@ Reitti is a comprehensive personal location tracking and analysis application th
|
||||
### Privacy & Self-hosting
|
||||
- **Complete Data Control**: Your location data never leaves your server
|
||||
- **Self-hosted Solution**: Deploy on your own infrastructure
|
||||
- **Asynchronous Processing**: Handle large datasets efficiently with RabbitMQ-based processing
|
||||
- **Asynchronous Processing**: Handle large datasets efficiently with direct processing and RabbitMQ-based task scheduling
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -76,7 +76,7 @@ Reitti is a comprehensive personal location tracking and analysis application th
|
||||
- Maven 3.6 or higher
|
||||
- Docker and Docker Compose
|
||||
- PostgreSQL database with spatial extensions (PostGIS)
|
||||
- RabbitMQ for message processing
|
||||
- RabbitMQ
|
||||
- Redis for caching
|
||||
|
||||
### Quick Start with Docker
|
||||
@@ -186,7 +186,7 @@ docker run -p 8080:8080 \
|
||||
|
||||
The included `docker-compose.yml` provides a complete setup with:
|
||||
- PostgreSQL with PostGIS extensions
|
||||
- RabbitMQ for message processing
|
||||
- RabbitMQ for task scheduling
|
||||
- Redis for caching and session storage
|
||||
- Reitti application with proper networking
|
||||
- Persistent data volumes
|
||||
@@ -249,12 +249,7 @@ The included `docker-compose.yml` provides a complete setup with:
|
||||
- Real-time mobile app integration (OwnTracks, GPSLogger)
|
||||
- REST API endpoints
|
||||
|
||||
2. **Queue Processing**: Data is queued in RabbitMQ for asynchronous processing:
|
||||
- Raw location points are validated and stored
|
||||
- Processing jobs are distributed across workers
|
||||
- Queue status is monitored in real-time
|
||||
|
||||
3. **Analysis & Detection**: Processing workers analyze the data to:
|
||||
2. **Analysis & Detection**: The application directly processes the data to:
|
||||
- Detect significant places where you spend time
|
||||
- Identify trips between locations
|
||||
- Determine transport modes (walking, cycling, driving)
|
||||
@@ -265,7 +260,12 @@ The included `docker-compose.yml` provides a complete setup with:
|
||||
- Temporal indexing for timeline operations
|
||||
- User data isolation and security
|
||||
|
||||
5. **Visualization**: Web interface displays processed data as:
|
||||
3. **Task Scheduling**: RabbitMQ is used for scheduling background tasks:
|
||||
- Reverse geocoding requests
|
||||
- User notifications
|
||||
- Other asynchronous operations
|
||||
|
||||
4. **Visualization**: Web interface displays processed data as:
|
||||
- Interactive timeline with visits and trips
|
||||
- Map visualization with location markers
|
||||
- Photo integration showing images taken at locations
|
||||
@@ -450,13 +450,13 @@ To enable PKCE for the OIDC Client, you need to set `OIDC_AUTHENTICATION_METHOD`
|
||||
- **Backup Requirements:**
|
||||
- The PostGIS database needs to be backed up regularly. This database contains all user location data, analysis results, and other persistent information.
|
||||
- The storage path used by Reitti needs to be backed up regularly. This contains uploaded files.
|
||||
- **Stateless Services:** All other components (RabbitMQ, Redis, Photon, etc.) are stateless and do not store any important data. These can be redeployed or restarted without risk of data loss.
|
||||
- **Stateless Services:** All other components (RabbitMQ for task scheduling, Redis, Photon, etc.) are stateless and do not store any important data. These can be redeployed or restarted without risk of data loss.
|
||||
|
||||
**Recommended Backup Strategy:**
|
||||
- Use standard PostgreSQL backup tools (such as `pg_dump` or physical volume snapshots) to back up your database.
|
||||
- Back up the entire storage directory/volume used by Reitti for file storage.
|
||||
- Ensure backups are performed regularly and stored securely.
|
||||
- No backup is needed for RabbitMQ, Redis, Photon.
|
||||
- No backup is needed for RabbitMQ (task scheduling), Redis, Photon.
|
||||
|
||||
**Restore:**
|
||||
- In case of disaster recovery, restore both the PostGIS database and the storage path to recover all user data and history.
|
||||
|
||||
@@ -521,7 +521,7 @@ function updatePointsList() {
|
||||
html += `
|
||||
<div class="point-item" id="${pointId}" onclick="selectPoint(${trackIndex}, ${pointIndex})" style="border-left-color: ${speedColor}">
|
||||
<div class="point-content">
|
||||
<div class="point-coords">${point.lat.toFixed(5)}, ${point.lng.toFixed(5)}</div>
|
||||
<div class="point-coords">${point.lat.toFixed(4)}, ${point.lng.toFixed(4)}</div>
|
||||
<div class="point-time">${formatCompactTimestamp(point.timestamp)}</div>
|
||||
<div class="point-speed" style="color: ${speedColor}">${speedText}</div>
|
||||
</div>
|
||||
@@ -596,7 +596,20 @@ function formatTimestamp(timestamp) {
|
||||
}
|
||||
|
||||
function formatCompactTimestamp(timestamp) {
|
||||
return timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
const today = new Date();
|
||||
const pointDate = new Date(timestamp);
|
||||
|
||||
// Check if it's the same date as today
|
||||
const isToday = pointDate.toDateString() === today.toDateString();
|
||||
|
||||
if (isToday) {
|
||||
return pointDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
} else {
|
||||
// Include date for different days
|
||||
const dateStr = pointDate.toLocaleDateString([], { month: '2-digit', day: '2-digit' });
|
||||
const timeStr = pointDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
return `${dateStr} ${timeStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSpeedLegend() {
|
||||
@@ -1508,3 +1521,62 @@ function handleMarkerContextMenu(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Time shifting functions
|
||||
function shiftTrackTime(amount, unit) {
|
||||
if (tracks.length === 0 || currentTrackIndex >= tracks.length) {
|
||||
alert('No track selected to shift time.');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTrack = tracks[currentTrackIndex];
|
||||
if (currentTrack.points.length === 0) {
|
||||
alert('Current track has no points to shift.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate milliseconds to shift
|
||||
let shiftMs = 0;
|
||||
if (unit === 'hour') {
|
||||
shiftMs = amount * 60 * 60 * 1000; // hours to milliseconds
|
||||
} else if (unit === 'day') {
|
||||
shiftMs = amount * 24 * 60 * 60 * 1000; // days to milliseconds
|
||||
}
|
||||
|
||||
// Shift all points in the current track
|
||||
currentTrack.points.forEach(point => {
|
||||
point.timestamp = new Date(point.timestamp.getTime() + shiftMs);
|
||||
});
|
||||
|
||||
// Update track start time
|
||||
if (currentTrack.points.length > 0) {
|
||||
currentTrack.startTime = new Date(currentTrack.points[0].timestamp);
|
||||
}
|
||||
|
||||
// Update the datetime input to reflect the new time of the last point
|
||||
if (currentTrack.points.length > 0) {
|
||||
const lastPoint = currentTrack.points[currentTrack.points.length - 1];
|
||||
const timeInterval = parseInt(document.getElementById('timeInterval').value);
|
||||
const nextTimestamp = new Date(lastPoint.timestamp.getTime() + (timeInterval * 1000));
|
||||
|
||||
// Format for datetime-local input
|
||||
const year = nextTimestamp.getFullYear();
|
||||
const month = String(nextTimestamp.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(nextTimestamp.getDate()).padStart(2, '0');
|
||||
const hours = String(nextTimestamp.getHours()).padStart(2, '0');
|
||||
const minutes = String(nextTimestamp.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(nextTimestamp.getSeconds()).padStart(2, '0');
|
||||
|
||||
const datetimeString = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
||||
document.getElementById('startDateTime').value = datetimeString;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
updatePointsList();
|
||||
updateStatus();
|
||||
|
||||
// Show confirmation
|
||||
const unitText = unit === 'hour' ? 'hour' : 'day';
|
||||
const direction = amount > 0 ? 'forward' : 'backward';
|
||||
const absAmount = Math.abs(amount);
|
||||
}
|
||||
|
||||
|
||||
@@ -542,16 +542,16 @@
|
||||
}
|
||||
|
||||
.point-item {
|
||||
padding: 8px 20px;
|
||||
padding: 6px 20px;
|
||||
border-bottom: 1px solid rgba(237, 242, 247, 0.8);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
min-height: 28px;
|
||||
border-left: 4px solid #e2e8f0;
|
||||
}
|
||||
|
||||
@@ -568,7 +568,7 @@
|
||||
.point-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
}
|
||||
@@ -576,15 +576,17 @@
|
||||
.point-coords {
|
||||
color: #4a5568;
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.point-time {
|
||||
color: #718096;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.point-speed {
|
||||
font-weight: 500;
|
||||
min-width: 60px;
|
||||
min-width: 45px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -903,6 +905,21 @@
|
||||
<button onclick="newTrack()" class="control-button">➕ New Track</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-section">
|
||||
<div class="drawer-section-title">Time Adjustment</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Shift Current Track</label>
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
|
||||
<button onclick="shiftTrackTime(-1, 'hour')" class="control-button" style="flex: 1; font-size: 11px;">-1h</button>
|
||||
<button onclick="shiftTrackTime(1, 'hour')" class="control-button" style="flex: 1; font-size: 11px;">+1h</button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button onclick="shiftTrackTime(-1, 'day')" class="control-button" style="flex: 1; font-size: 11px;">-1d</button>
|
||||
<button onclick="shiftTrackTime(1, 'day')" class="control-button" style="flex: 1; font-size: 11px;">+1d</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -498,8 +498,8 @@ public class ReittiIntegrationService {
|
||||
(String) placeInfo.get("address"),
|
||||
(String) placeInfo.get("city"),
|
||||
(String) placeInfo.get("countryCode"),
|
||||
getDoubleValue(placeInfo, "latitudeCentroid"),
|
||||
getDoubleValue(placeInfo, "longitudeCentroid"),
|
||||
getDoubleValue(placeInfo, "lat"),
|
||||
getDoubleValue(placeInfo, "lng"),
|
||||
(String) placeInfo.get("type")
|
||||
);
|
||||
|
||||
@@ -515,7 +515,7 @@ public class ReittiIntegrationService {
|
||||
.toList();
|
||||
|
||||
// Parse summary data
|
||||
long totalDurationMs = getLongValue(placeData, "totalDurationSeconds") * 1000; // Convert to milliseconds
|
||||
long totalDurationMs = getLongValue(placeData, "totalDurationMs"); // Convert to milliseconds
|
||||
int visitCount = getIntValue(placeData, "visitCount");
|
||||
String color = "#3388ff"; // Default color, could be extracted from response if available
|
||||
|
||||
|
||||
Reference in New Issue
Block a user