Implement rendering the route when the dates if the trip are changed

This commit is contained in:
Eugene Burmakin 2024-11-28 13:20:03 +01:00
parent 2cfc485f12
commit e8842a9476
8 changed files with 189 additions and 47 deletions

File diff suppressed because one or more lines are too long

View file

@ -3,9 +3,10 @@
class TripsController < ApplicationController
before_action :authenticate_user!
before_action :set_trip, only: %i[show edit update destroy]
before_action :set_coordinates, only: %i[show edit]
def index
@trips = current_user.trips
@trips = current_user.trips.order(created_at: :desc).page(params[:page])
end
def show
@ -54,6 +55,14 @@ class TripsController < ApplicationController
@trip = current_user.trips.find(params[:id])
end
def set_coordinates
@coordinates = @trip.points.pluck(
:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id,
:country
).map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7.to_s, _8.to_s] }
end
def trip_params
params.require(:trip).permit(:name, :started_at, :ended_at, :notes, :field_notes)
end

View file

@ -0,0 +1,69 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["startedAt", "endedAt", "apiKey"]
static values = { tripsId: String }
connect() {
console.log("Datetime controller connected")
this.debounceTimer = null;
}
async updateCoordinates(event) {
// Clear any existing timeout
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
// Set new timeout
this.debounceTimer = setTimeout(async () => {
const startedAt = this.startedAtTarget.value
const endedAt = this.endedAtTarget.value
const apiKey = this.apiKeyTarget.value
if (startedAt && endedAt) {
try {
const params = new URLSearchParams({
start_at: startedAt,
end_at: endedAt,
api_key: apiKey,
slim: true
})
let allPoints = [];
let currentPage = 1;
const perPage = 1000;
do {
const paginatedParams = `${params}&page=${currentPage}&per_page=${perPage}`;
const response = await fetch(`/api/v1/points?${paginatedParams}`);
const data = await response.json();
allPoints = [...allPoints, ...data];
const totalPages = parseInt(response.headers.get('X-Total-Pages'));
currentPage++;
if (!totalPages || currentPage > totalPages) {
break;
}
} while (true);
const event = new CustomEvent('coordinates-updated', {
detail: { coordinates: allPoints },
bubbles: true,
composed: true
})
const tripsElement = document.querySelector('[data-controller="trips"]')
if (tripsElement) {
tripsElement.dispatchEvent(event)
} else {
console.error('Trips controller element not found')
}
} catch (error) {
console.error('Error:', error)
}
}
}, 500);
}
}

View file

@ -13,24 +13,43 @@ import { esriWorldGrayCanvasMapLayer } from "../maps/layers"
import { fetchAndDisplayPhotos } from '../maps/helpers';
export default class extends Controller {
static targets = ["container"]
static targets = ["container", "startedAt", "endedAt"]
static values = { }
connect() {
this.coordinates = JSON.parse(this.element.dataset.coordinates)
this.apiKey = this.element.dataset.api_key
this.userSettings = JSON.parse(this.element.dataset.user_settings)
this.timezone = this.element.dataset.timezone
this.distanceUnit = this.element.dataset.distance_unit
console.log("Trips controller connected")
this.coordinates = JSON.parse(this.containerTarget.dataset.coordinates)
this.apiKey = this.containerTarget.dataset.api_key
this.userSettings = JSON.parse(this.containerTarget.dataset.user_settings)
this.timezone = this.containerTarget.dataset.timezone
this.distanceUnit = this.containerTarget.dataset.distance_unit
// Initialize map and layers
this.initializeMap()
// Add event listener for coordinates updates
this.element.addEventListener('coordinates-updated', (event) => {
console.log("Coordinates updated:", event.detail.coordinates)
this.updateMapWithCoordinates(event.detail.coordinates)
})
}
// Move map initialization to separate method
initializeMap() {
// Initialize layer groups
this.markersLayer = L.layerGroup()
this.polylinesLayer = L.layerGroup()
this.photoMarkers = L.layerGroup()
const center = [this.coordinates[0][0], this.coordinates[0][1]]
// Set default center and zoom for world view
const hasValidCoordinates = this.coordinates && Array.isArray(this.coordinates) && this.coordinates.length > 0
const center = hasValidCoordinates
? [this.coordinates[0][0], this.coordinates[0][1]]
: [20, 0] // Roughly centers the world map
const zoom = hasValidCoordinates ? 14 : 2
// Initialize map
this.map = L.map(this.containerTarget).setView(center, 14)
this.map = L.map(this.containerTarget).setView(center, zoom)
// Add base map layer
osmMapLayer(this.map, "OpenStreetMap")
@ -111,7 +130,7 @@ export default class extends Controller {
marker.bindPopup(popupContent)
// Add to markers layer instead of directly to map
this.markersLayer.addTo(this.map)
marker.addTo(this.markersLayer)
})
}
@ -135,19 +154,25 @@ export default class extends Controller {
this.map.fitBounds(bounds, { padding: [50, 50] })
}
baseMaps() {
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
// Add this new method to update coordinates and refresh the map
updateMapWithCoordinates(newCoordinates) {
// Transform the coordinates to match the expected format
this.coordinates = newCoordinates.map(point => [
parseFloat(point.latitude),
parseFloat(point.longitude),
(point.timestamp).toString()
])
return {
OpenStreetMap: osmMapLayer(this.map, selectedLayerName),
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName),
OPNV: OPNVMapLayer(this.map, selectedLayerName),
openTopo: openTopoMapLayer(this.map, selectedLayerName),
cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName),
esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName),
esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName),
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName),
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName)
};
// Clear existing layers
this.markersLayer.clearLayers()
this.polylinesLayer.clearLayers()
this.photoMarkers.clearLayers()
// Add new markers and route if coordinates exist
if (this.coordinates?.length > 0) {
this.addMarkers()
this.addPolyline()
this.fitMapToBounds()
}
}
}

View file

@ -50,7 +50,7 @@
data-user_settings=<%= current_user.settings.to_json %>
data-coordinates="<%= @coordinates %>"
data-timezone="<%= Rails.configuration.time_zone %>">
<div data-maps-target="container" class="h-[25rem] w-full min-h-screen">
<div data-maps-target="container" class="h-[25rem] rounded-lg w-full min-h-screen">
<div id="fog" class="fog"></div>
</div>
</div>

View file

@ -11,29 +11,63 @@
</div>
<% end %>
<div class="flex flex-col lg:flex-row gap-4 mt-4">
<div class="form-control w-full lg:w-1/2">
<%= form.label :name %>
<%= form.text_field :name, class: 'input input-bordered' %>
</div>
<div class="flex flex-col lg:flex-row lg:w-1/2 gap-4">
<div class="form-control w-full lg:w-1/2">
<%= form.label :started_at %>
<%= form.datetime_field :started_at, include_seconds: false, class: 'input input-bordered', value: trip.started_at %>
</div>
<div class="form-control w-full lg:w-1/2">
<%= form.label :ended_at %>
<%= form.datetime_field :ended_at, include_seconds: false, class: 'input input-bordered', value: trip.ended_at %>
<div class="flex flex-col lg:flex-row gap-4 my-4" data-controller="trips">
<div class="w-full lg:w-1/2">
<div
id='map trips-container'
class="w-full h-full rounded-lg"
data-trips-target="container"
data-distance_unit="<%= DISTANCE_UNIT %>"
data-api_key="<%= current_user.api_key %>"
data-user_settings="<%= current_user.settings.to_json %>"
data-coordinates="<%= [].to_json %>"
data-timezone="<%= Rails.configuration.time_zone %>">
</div>
</div>
</div>
<div class="form-control w-full mt-4 mb-4">
<%= form.label :field_notes %>
<%= form.rich_text_area :field_notes %>
</div>
<div
class="w-full lg:w-1/2 space-y-4"
data-controller="datetime">
<div class="inline mb-4">
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
<div class="form-control">
<%= form.label :name %>
<%= form.text_field :name, class: 'input input-bordered w-full' %>
</div>
<div class="flex flex-col sm:flex-row gap-4">
<div class="form-control w-full">
<input type="hidden" data-datetime-target="apiKey" value="<%= current_user.api_key %>">
<%= form.label :started_at %>
<%= form.datetime_field :started_at,
include_seconds: false,
class: 'input input-bordered w-full',
value: trip.started_at,
data: {
datetime_target: "startedAt",
action: "change->datetime#updateCoordinates"
} %>
</div>
<div class="form-control w-full">
<%= form.label :ended_at %>
<%= form.datetime_field :ended_at,
include_seconds: false,
class: 'input input-bordered w-full',
value: trip.ended_at,
data: {
datetime_target: "endedAt",
action: "change->datetime#updateCoordinates"
} %>
</div>
</div>
<div class="form-control">
<%= form.label :field_notes %>
<%= form.rich_text_area :field_notes %>
</div>
<div>
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
</div>
</div>
</div>
<% end %>

View file

@ -2,6 +2,11 @@
<div class="w-full">
<div id="trips" class="min-w-full">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold">Trips</h1>
<%= link_to 'New trip', new_trip_path, class: 'btn btn-primary' %>
</div>
<% if @trips.empty? %>
<div class="hero min-h-80 bg-base-200">
<div class="hero-content text-center">

View file

@ -20,7 +20,7 @@
<div class="w-full">
<div
id='map'
class="w-full h-full"
class="w-full h-full rounded-lg"
data-controller="trips"
data-trips-target="container"
data-distance_unit="<%= DISTANCE_UNIT %>"