fix: 🐛 fixed weirdness in code and fixed bug that prevented point cloud of rendering properly

This commit is contained in:
steev 2025-01-15 22:15:53 +01:00
parent 9b46ac186d
commit 20ac0faa75
4 changed files with 140 additions and 314 deletions

View File

@ -36,6 +36,7 @@ export default defineComponent({
const mapDiv = ref<HTMLElement | null>(null); // ref to map container element const mapDiv = ref<HTMLElement | null>(null); // ref to map container element
const mapInstance = ref<any>(null); // reference for the Leaflet map const mapInstance = ref<any>(null); // reference for the Leaflet map
const track: Ref<Track> = ref({ id: 0, name: "N/A", driver: "N/A", vehicle: { name: "N/A", licenseplate: "N/A" }, distance: 0.0 }) const track: Ref<Track> = ref({ id: 0, name: "N/A", driver: "N/A", vehicle: { name: "N/A", licenseplate: "N/A" }, distance: 0.0 })
const multiple:Ref<boolean> = ref(props.multiple);
const initializeMap = () => { const initializeMap = () => {
if (mapDiv.value) { if (mapDiv.value) {
@ -56,7 +57,6 @@ export default defineComponent({
const clearMap = () => { const clearMap = () => {
if (mapInstance.value) { if (mapInstance.value) {
mapInstance.value.eachLayer((layer: any) => { mapInstance.value.eachLayer((layer: any) => {
// Nur Layer entfernen, die keine Basemap sind
if (!(layer instanceof L.TileLayer)) { if (!(layer instanceof L.TileLayer)) {
mapInstance.value.removeLayer(layer); mapInstance.value.removeLayer(layer);
} }
@ -94,32 +94,35 @@ export default defineComponent({
} }
} }
// Beobachte geoJsonData und füge sie der Karte hinzu, wenn sie sich ändert
watch(() => props.geoJsonData, (newData) => { watch(() => props.geoJsonData, (newData) => {
if (newData) { if (newData) {
console.log("Neue GeoJSON-Daten erhalten:", newData); console.log("loading GeoJSON:", newData);
if (mapInstance.value) { if (mapInstance.value) {
// Karte bereinigen if (!props.multiple){
clearMap(); clearMap();
}
// Neue GeoJSON-Daten hinzufügen
var geoJsonLayer = L.geoJSON(newData).addTo(mapInstance.value); var geoJsonLayer = L.geoJSON(newData).addTo(mapInstance.value);
// Metadaten des Tracks abrufen (z. B. für den ersten Track, falls ID bekannt) // pull meta data for for a single track
// function wont be called if multiple tracks are loaded
if (!props.multiple) {
getTrackMeta(props.track); getTrackMeta(props.track);
}
// Karte an die neuen GeoJSON-Daten anpassen // move camera view according to loaded track
const bounds = geoJsonLayer.getBounds(); const bounds = geoJsonLayer.getBounds();
mapInstance.value.fitBounds(bounds); mapInstance.value.fitBounds(bounds);
} else { } else {
console.error("Die Karte oder GeoJSON-Daten sind nicht verfügbar."); console.error("Map or GeoJSON data not available.");
} }
} }
}); });
return { return {
mapDiv, mapDiv,
multiple,
track track
}; };
}, },
@ -129,7 +132,7 @@ export default defineComponent({
<template> <template>
<div ref="mapDiv" style="width: 70vw; height: 75vh; overflow: hidden;"></div> <div ref="mapDiv" style="width: 70vw; height: 75vh; overflow: hidden;"></div>
<br> <br>
<div class="overflow-x-auto" style="width: 70vw;"> <div class="overflow-x-auto" style="width: 70vw;" v-if="!multiple">
<table class="table"> <table class="table">
<!-- head --> <!-- head -->
<thead> <thead>

View File

@ -1,122 +0,0 @@
<script lang="ts">
import { defineComponent, onMounted, PropType, ref, watch } from "vue";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
export default defineComponent({
name: "MapMultiple",
props: {
tracks: {
type: Array as PropType<number[]>, // Array von IDs
default: () => [],
required: true,
},
},
setup(props) {
const mapDiv = ref<HTMLElement | null>(null); // Ref für die Karten-Div
const mapInstance = ref<L.Map | null>(null); // Ref für die Leaflet-Karte
const geoJsonLayers = ref<L.LayerGroup | null>(null); // Ref für GeoJSON-Layer
// Initialisiert die Leaflet-Karte
const initializeMap = () => {
if (!mapDiv.value) {
console.error("mapDiv ist nicht verfügbar.");
return;
}
console.log("Initialisiere Karte...");
mapInstance.value = L.map(mapDiv.value, {
center: [51.4819, 7.2162], // Standardzentrum
zoom: 13,
});
// OpenStreetMap-Tiles hinzufügen
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {}).addTo(
mapInstance.value
);
// Initialisiere einen LayerGroup-Container für GeoJSON-Daten
geoJsonLayers.value = L.layerGroup().addTo(mapInstance.value);
console.log("Karte erfolgreich initialisiert.");
};
// Lädt GeoJSON-Daten für eine bestimmte Track-ID
const loadTrack = async (id: number) => {
console.log(`Lade Daten für Track ${id}...`);
try {
const response = await fetch(`http://localhost:5000/track?id=${id}`);
if (!response.ok) {
console.error(`Fehler beim Laden von Track ${id}:`, response.statusText);
return null;
}
const geoJson = await response.json();
console.log(`GeoJSON für Track ${id} erhalten:`, geoJson);
return geoJson;
} catch (error) {
console.error(`Fehler beim Laden von Track ${id}:`, error);
return null;
}
};
// Fügt GeoJSON-Daten zur Karte hinzu
const addGeoJsonToMap = (geoJson: any) => {
if (!geoJsonLayers.value) {
console.error("GeoJSON-Layer ist nicht initialisiert.");
return;
}
const layer = L.geoJSON(geoJson);
geoJsonLayers.value.addLayer(layer);
// Fit the map bounds to the newly added layer
if (mapInstance.value) {
mapInstance.value.fitBounds(layer.getBounds());
}
};
watch(
() => props.tracks,
async (newTracks) => {
console.log("Tracks geändert:", newTracks);
if (!mapInstance.value) {
console.error("Karte ist noch nicht initialisiert.");
return;
}
// Entferne alle vorhandenen GeoJSON-Layer
if (geoJsonLayers.value) {
geoJsonLayers.value.clearLayers();
}
for (const trackId of newTracks) {
const geoJson = await loadTrack(trackId);
if (geoJson) {
await addGeoJsonToMap(geoJson);
}
}
},
{ immediate: true }
);
onMounted(() => {
console.log("Komponente gemountet.");
initializeMap();
});
return {
mapDiv,
};
},
});
</script>
<template>
<div ref="mapDiv" id="mapDiv"></div>
</template>
<style scoped>
#mapDiv {
width: 100%;
height: 75vh;
}
</style>

View File

@ -35,14 +35,63 @@ export default defineComponent({
const tooltipRef: Ref<HTMLDivElement | null> = ref(null); const tooltipRef: Ref<HTMLDivElement | null> = ref(null);
const tooltipText = ref(""); const tooltipText = ref("");
const router = useRouter(); const router = useRouter();
const legend = ref<Record<number, string>>({}); const legend = ref<Record<string, string>>({});
const driverColors = ref<Record<number, string>>({}); const driverColors = ref<Record<string, string>>({});
var drivers:driver[] = [];
const points:Route[] = props.routes;
const close = () => { // handles sending webrequests to the backend
emit("close"); const getDrivers = async () => {
};
const headers: Headers = new Headers()
headers.set('Content-Type', 'application/json')
headers.set('Accept', 'application/json')
const request: RequestInfo = new Request("http://localhost:5000/driver", {
method: "GET",
headers: headers
})
var response = await fetch(request)
// make sure the request was successfull
if (response.ok) {
var jsonBody = await response.json()
// convert vehicles from json response to processable data
for (let i = 0; i < jsonBody.length; i++) {
let driver = jsonBody[i]
drivers.push({ id: driver["id"], name: driver["name"] })
const color = new THREE.Color(`hsl(${(i * 360) / drivers.length}, 70%, 50%)`);
driverColors.value[driver["id"]] = color.getStyle();
console.log(color.getStyle())
legend.value[driver["id"]] = driver["name"];
}
} else {
console.log(await response.text())
}
}
// assigns a driver to a point based on the points driver id
const assignDriverToPoint = (points:Route[]) => {
return points.reduce((acc, point) => {
const driverId = point.driver.id;
console.log(`driverid: ${driverId}`)
acc[driverId] = acc[driverId] || [];
acc[driverId].push(point);
return acc;
}, {} as Record<number, Route[]>);
}
// runs only when component has fully mounted
onMounted(async () => {
await getDrivers();
onMounted(() => {
if (!canvasRef.value) return; if (!canvasRef.value) return;
console.log(`routes: ${props.routes}`) console.log(`routes: ${props.routes}`)
@ -58,38 +107,6 @@ export default defineComponent({
const groupedPoints = assignDriverToPoint(points) const groupedPoints = assignDriverToPoint(points)
// Beispiel-Daten (Fahrer mit ID und Punkten)
const drivers = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
{ id: 4, name: "Dave" },
{ id: 5, name: "Eve" },
];
const points: Route[] = Array.from({ length: 100 }, (_, i) => ({
name: `Route ${i + 1}`,
id: i + 1,
time: new Date(Date.now() - i * 1000 * 60),
driver: drivers[i % drivers.length], // Fahrer zuweisen
}));
// Nach Fahrern gruppieren
const groupedPoints = points.reduce((acc, point) => {
const driverId = point.driver.id;
acc[driverId] = acc[driverId] || [];
acc[driverId].push(point);
return acc;
}, {} as Record<number, Route[]>);
// Farben für Fahrer generieren und der Legende zuweisen
drivers.forEach((driver, index) => {
const color = new THREE.Color(`hsl(${(index * 360) / drivers.length}, 70%, 50%)`);
driverColors.value[driver.id] = color.getStyle(); // Farbwert für die Legende
legend.value[driver.id] = driver.name; // Fahrernamen für die Legende
});
// Punktwolke erstellen
const geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
const vertices: number[] = []; const vertices: number[] = [];
const colors: number[] = []; const colors: number[] = [];
@ -102,20 +119,37 @@ export default defineComponent({
// Berechnungen für den Mittelpunkt der Punktwolke // Berechnungen für den Mittelpunkt der Punktwolke
let sumX = 0, sumY = 0, sumZ = 0; let sumX = 0, sumY = 0, sumZ = 0;
const getColorForDriver = (driverId: number) => {
const color = driverColors.value[driverId];
if (!color) {
console.error(`No color found for driver ID ${driverId}`);
return "#FFFFFF"; // Rückfallfarbe, wenn keine Farbe gefunden wurde
}
return color;
};
// complicated math things
Object.entries(groupedPoints).forEach(([driverIdStr, driverPoints], index) => { Object.entries(groupedPoints).forEach(([driverIdStr, driverPoints], index) => {
const driverId = parseInt(driverIdStr, 10); const driverId = parseInt(driverIdStr, 10);
const color = new THREE.Color(driverColors.value[driverId]); // Die Farbe aus `driverColors` console.log("Driver Colors:", driverColors.value[driverIdStr]);
// Berechnung des Abstands auf der X-Achse basierend auf der Fahrer-ID if (!driverColors.value[driverId]) {
console.error(`Missing color for Driver ID: ${driverId}`);
}
const color = new THREE.Color(driverColors.value[driverId]);
console.log(`Color for Driver ${driverId}:`, driverColors.value[driverId]);
// calculate offset base on driverid
offsetX += offsetXStep; offsetX += offsetXStep;
driverPoints.forEach(() => { driverPoints.forEach(() => {
// Zufällige Position innerhalb eines Bereichs // calculate random position in the area of a driver
const x = offsetX + (Math.random() - 0.5) * 2; const x = offsetX + (Math.random() - 0.5) * 2;
const y = (Math.random() - 0.5) * 6; const y = (Math.random() - 0.5) * 6;
const z = (Math.random() - 0.5) * 6; const z = (Math.random() - 0.5) * 6;
// Berechnungen für den Mittelpunkt // calculate center
sumX += x; sumX += x;
sumY += y; sumY += y;
sumZ += z; sumZ += z;
@ -130,7 +164,7 @@ export default defineComponent({
const centerY = sumY / totalPoints; const centerY = sumY / totalPoints;
const centerZ = sumZ / totalPoints; const centerZ = sumZ / totalPoints;
// Punktwolke verschieben, damit der Mittelpunkt im Ursprung ist // put cloud centered on the canvase
const offset = new THREE.Vector3(-centerX, -centerY, -centerZ); const offset = new THREE.Vector3(-centerX, -centerY, -centerZ);
for (let i = 0; i < vertices.length; i += 3) { for (let i = 0; i < vertices.length; i += 3) {
vertices[i] += offset.x; vertices[i] += offset.x;
@ -143,7 +177,6 @@ export default defineComponent({
const material = new THREE.PointsMaterial({ const material = new THREE.PointsMaterial({
vertexColors: true, vertexColors: true,
vertexColors: true, // Farben pro Punkt verwenden
size: 0.3, size: 0.3,
}); });
@ -196,30 +229,10 @@ export default defineComponent({
tooltipRef.value.style.top = `${tooltipY + 10}px`; // 10px Abstand zum Punkt tooltipRef.value.style.top = `${tooltipY + 10}px`; // 10px Abstand zum Punkt
tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`; tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`;
// 3D-Koordinaten des Punktes holen
const positionArray = pointCloud.geometry.attributes.position.array;
const x = positionArray[intersectIndex * 3];
const y = positionArray[intersectIndex * 3 + 1];
const z = positionArray[intersectIndex * 3 + 2];
// 3D-Koordinaten in 2D Bildschirmkoordinaten umrechnen
const screenPosition = new THREE.Vector3(x, y, z);
screenPosition.project(camera);
// Umrechnung von NDC (Normalized Device Coordinates) zu Pixeln
const tooltipX = (screenPosition.x * 0.5 + 0.5) * window.innerWidth;
const tooltipY = (screenPosition.y * -0.5 + 0.5) * window.innerHeight;
tooltipRef.value.style.left = `${tooltipX + 10}px`; // 10px Abstand zum Punkt
tooltipRef.value.style.top = `${tooltipY + 10}px`; // 10px Abstand zum Punkt
tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`;
} else { } else {
tooltipRef.value.style.display = "none"; tooltipRef.value.style.display = "none";
} }
} }
};
canvasRef.value.addEventListener("mousemove", onMouseMove); canvasRef.value.addEventListener("mousemove", onMouseMove);
@ -237,8 +250,6 @@ export default defineComponent({
tooltipText, tooltipText,
legend, legend,
driverColors, driverColors,
legend,
driverColors,
}; };
}, },
}); });
@ -261,16 +272,6 @@ export default defineComponent({
</li> </li>
</ul> </ul>
</div> </div>
<!-- Legende -->
<div class="legend absolute top-10 left-10 bg-base-200 p-4 rounded shadow-lg z-50">
<h3 class="font-bold mb-2">Fahrer Legende</h3>
<ul>
<li v-for="(driverName, driverId) in legend" :key="driverId" class="flex items-center mb-1">
<div class="w-4 h-4" :style="{ backgroundColor: driverColors[driverId] }"></div>
<span class="ml-2">{{ driverName }}</span>
</li>
</ul>
</div>
</div> </div>
</template> </template>
@ -323,40 +324,4 @@ export default defineComponent({
height: 15px; height: 15px;
margin-right: 10px; margin-right: 10px;
} }
.tooltip {
display: none;
position: absolute;
z-index: 50;
padding: 2px 8px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 4px;
pointer-events: none;
}
.legend {
width: auto;
max-width: 250px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
border-radius: 8px;
font-size: 14px;
}
.legend ul {
padding: 0;
list-style-type: none;
}
.legend li {
display: flex;
align-items: center;
}
.legend .color-box {
width: 15px;
height: 15px;
margin-right: 10px;
}
</style> </style>

View File

@ -5,7 +5,6 @@ import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css' import '@vuepic/vue-datepicker/dist/main.css'
import Map from '../components/map.vue'; import Map from '../components/map.vue';
import FileUpload from '../components/fileUpload.vue'; import FileUpload from '../components/fileUpload.vue';
import MapMultiple from '../components/mapMultiple.vue';
type DriverType = { type DriverType = {
id: number id: number
@ -38,7 +37,7 @@ type GeoJSON = {
export default defineComponent({ export default defineComponent({
name: 'map', name: 'map',
components: { PointCloud, VueDatePicker, Map, MapMultiple, FileUpload }, components: { PointCloud, VueDatePicker, Map, FileUpload },
setup(_, { emit }: SetupContext) { setup(_, { emit }: SetupContext) {
const showMap: Ref<boolean> = ref(false); const showMap: Ref<boolean> = ref(false);
const showCloud: Ref<boolean> = ref(false); const showCloud: Ref<boolean> = ref(false);
@ -50,9 +49,8 @@ export default defineComponent({
const endSearchDate = ref(); const endSearchDate = ref();
const mapData: Ref<GeoJSON | null> = ref(null); const mapData: Ref<GeoJSON | null> = ref(null);
const renderSearchOnMap: Ref<Boolean> = ref(false); const renderSearchOnMap: Ref<Boolean> = ref(false);
const multipleTracks:Ref<Boolean> = ref(false); const multipleTracks: Ref<Boolean> = ref(false);
const trackid: Ref<number> = ref(0) const trackid: Ref<number> = ref(0)
const trackids: Ref<number[]> = ref([])
const loadTrack = async (id: number) => { const loadTrack = async (id: number) => {
showMap.value = true; showMap.value = true;
@ -118,42 +116,15 @@ export default defineComponent({
showMap.value = false; showMap.value = false;
search.value = false; search.value = false;
showCloud.value = true;
showUpload.value = false; showUpload.value = false;
multipleTracks.value = false;
if (startSearchDate.value == null || endSearchDate.value == null) { if (startSearchDate.value == null || endSearchDate.value == null) {
alert("please give all required infos") alert("please give all required infos")
showCloud.value = false;
search.value = true;
return; return;
} }
if (renderSearchOnMap.value) {
const request: RequestInfo = new Request("http://localhost:5000/track?start=" + startSearchDate.value + "&end=" + endSearchDate.value, {
method: "GET",
headers: headers
})
try {
var response = await fetch(request);
if (response.ok) {
// Wenn die Antwort OK ist, die Daten verarbeiten
var resp = await response.json()
for (let i = 0; i < resp.length; i++) {
trackids.value.push(resp[i]["id"]);
}
showMap.value = true;
} else {
console.log(await response.text());
}
} catch (error) {
console.error("Fehler beim Laden der Track-Daten:", error);
}
showMap.value = true;
multipleTracks.value = true;
} else {
const request: RequestInfo = new Request("http://localhost:5000/track?start=" + startSearchDate.value + "&end=" + endSearchDate.value, { const request: RequestInfo = new Request("http://localhost:5000/track?start=" + startSearchDate.value + "&end=" + endSearchDate.value, {
method: "GET", method: "GET",
headers: headers headers: headers
@ -185,6 +156,17 @@ export default defineComponent({
} else { } else {
console.log(await response.text()) console.log(await response.text())
} }
if (renderSearchOnMap.value) {
multipleTracks.value = true;
showMap.value = true;
showCloud.value = false;
searchedTracks.value.forEach(async track => {
setTimeout(async () => {
await loadTrack(track.id);
}, 500)
});
} }
} }
@ -206,7 +188,6 @@ export default defineComponent({
showUpload, showUpload,
multipleTracks, multipleTracks,
trackid, trackid,
trackids
}; };
}, },
}); });
@ -226,7 +207,8 @@ export default defineComponent({
<li> <li>
<div class="divider"></div> <div class="divider"></div>
</li> </li>
<li v-for="track in tracks"> <a v-on:click="loadTrack(track.id)"> {{ track.name }} </a> </li> <li v-for="track in tracks"> <a v-on:click="loadTrack(track.id); multipleTracks = false;"> {{ track.name }} </a>
</li>
</ul> </ul>
</div> </div>
<div style="width: 70%; margin-left: 5%;"> <div style="width: 70%; margin-left: 5%;">
@ -248,11 +230,9 @@ export default defineComponent({
<button v-on:click="searchTracks" class="btn btn-success" <button v-on:click="searchTracks" class="btn btn-success"
style="margin-left: 5%; height:60px; width:120px;">Search</button> style="margin-left: 5%; height:60px; width:120px;">Search</button>
</div> </div>
<div v-if="!multipleTracks && showMap && !search && !showCloud"> <div v-if="showMap && !search && !showCloud">
<Map :track="trackid" :geoJsonData="mapData" style="width: 68vw; margin-left: 10%; border-radius: 10px; border: 1px solid #95a5a6;"></Map> <Map :track="trackid" :multiple="multipleTracks" :geoJsonData="mapData"
</div> style="width: 68vw; margin-left: 10%; border-radius: 10px; border: 1px solid #95a5a6;"></Map>
<div v-if="multipleTracks && showMap && !search && !showCloud">
<MapMultiple :tracks="trackids" style="width: 68vw; margin-left: 10%; border-radius: 10px; border: 1px solid #95a5a6;"></MapMultiple>
</div> </div>
</div> </div>
</template> </template>