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 class TripsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_trip, only: %i[show edit update destroy] before_action :set_trip, only: %i[show edit update destroy]
before_action :set_coordinates, only: %i[show edit]
def index def index
@trips = current_user.trips @trips = current_user.trips.order(created_at: :desc).page(params[:page])
end end
def show def show
@ -54,6 +55,14 @@ class TripsController < ApplicationController
@trip = current_user.trips.find(params[:id]) @trip = current_user.trips.find(params[:id])
end 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 def trip_params
params.require(:trip).permit(:name, :started_at, :ended_at, :notes, :field_notes) params.require(:trip).permit(:name, :started_at, :ended_at, :notes, :field_notes)
end 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'; import { fetchAndDisplayPhotos } from '../maps/helpers';
export default class extends Controller { export default class extends Controller {
static targets = ["container"] static targets = ["container", "startedAt", "endedAt"]
static values = { }
connect() { connect() {
this.coordinates = JSON.parse(this.element.dataset.coordinates) console.log("Trips controller connected")
this.apiKey = this.element.dataset.api_key this.coordinates = JSON.parse(this.containerTarget.dataset.coordinates)
this.userSettings = JSON.parse(this.element.dataset.user_settings) this.apiKey = this.containerTarget.dataset.api_key
this.timezone = this.element.dataset.timezone this.userSettings = JSON.parse(this.containerTarget.dataset.user_settings)
this.distanceUnit = this.element.dataset.distance_unit 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 // Initialize layer groups
this.markersLayer = L.layerGroup() this.markersLayer = L.layerGroup()
this.polylinesLayer = L.layerGroup() this.polylinesLayer = L.layerGroup()
this.photoMarkers = 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 // Initialize map
this.map = L.map(this.containerTarget).setView(center, 14) this.map = L.map(this.containerTarget).setView(center, zoom)
// Add base map layer // Add base map layer
osmMapLayer(this.map, "OpenStreetMap") osmMapLayer(this.map, "OpenStreetMap")
@ -111,7 +130,7 @@ export default class extends Controller {
marker.bindPopup(popupContent) marker.bindPopup(popupContent)
// Add to markers layer instead of directly to map // Add to markers layer instead of directly to map
this.markersLayer.addTo(this.map)
marker.addTo(this.markersLayer) marker.addTo(this.markersLayer)
}) })
} }
@ -135,19 +154,25 @@ export default class extends Controller {
this.map.fitBounds(bounds, { padding: [50, 50] }) this.map.fitBounds(bounds, { padding: [50, 50] })
} }
baseMaps() { // Add this new method to update coordinates and refresh the map
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap"; 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 { // Clear existing layers
OpenStreetMap: osmMapLayer(this.map, selectedLayerName), this.markersLayer.clearLayers()
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName), this.polylinesLayer.clearLayers()
OPNV: OPNVMapLayer(this.map, selectedLayerName), this.photoMarkers.clearLayers()
openTopo: openTopoMapLayer(this.map, selectedLayerName),
cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName), // Add new markers and route if coordinates exist
esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName), if (this.coordinates?.length > 0) {
esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName), this.addMarkers()
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName), this.addPolyline()
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName) this.fitMapToBounds()
}; }
} }
} }

View file

@ -50,7 +50,7 @@
data-user_settings=<%= current_user.settings.to_json %> data-user_settings=<%= current_user.settings.to_json %>
data-coordinates="<%= @coordinates %>" data-coordinates="<%= @coordinates %>"
data-timezone="<%= Rails.configuration.time_zone %>"> 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 id="fog" class="fog"></div>
</div> </div>
</div> </div>

View file

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

View file

@ -2,6 +2,11 @@
<div class="w-full"> <div class="w-full">
<div id="trips" class="min-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? %> <% if @trips.empty? %>
<div class="hero min-h-80 bg-base-200"> <div class="hero min-h-80 bg-base-200">
<div class="hero-content text-center"> <div class="hero-content text-center">

View file

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