diff --git a/package.json b/package.json index 40a6a25..6287851 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,13 @@ "@vue/apollo-option": "^4.0.0-beta.5", "apollo-upload-client": "^17.0.0", "axios": "^1.4.0", + "chart.js": "^4.4.0", "graphql": "16.6.0", "graphql-tag": "2.12.6", "pinia": "^2.0.33", "quasar": "^2.12.0", "vue": "^3.2.47", + "vue-chartjs": "^5.2.0", "vue-inner-image-zoom": "^2.0.0", "vue-router": "^4.1.6", "vue3-google-map": "^0.15.0" diff --git a/public/mapIcons/currentPosition.png b/public/mapIcons/currentPosition.png new file mode 100644 index 0000000..1f29525 Binary files /dev/null and b/public/mapIcons/currentPosition.png differ diff --git a/public/mapIcons/currentPosition32x32.png b/public/mapIcons/currentPosition32x32.png new file mode 100644 index 0000000..2dac397 Binary files /dev/null and b/public/mapIcons/currentPosition32x32.png differ diff --git a/quasar.config.js b/quasar.config.js index b148bc9..ad05f7d 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -31,6 +31,7 @@ module.exports = configure(function (ctx) { boot: [ "axios", "vueInnerImageZoom", + "chartJs", ], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css diff --git a/src/boot/chartJs.js b/src/boot/chartJs.js new file mode 100644 index 0000000..41daa61 --- /dev/null +++ b/src/boot/chartJs.js @@ -0,0 +1,6 @@ +import {boot} from "quasar/wrappers"; +import {BarElement, CategoryScale, Chart as ChartJS, Filler, Legend, LinearScale, LineElement, PointElement, Title, Tooltip} from "chart.js"; + +export default boot(({app}) => { + ChartJS.register(Title, Tooltip, Legend, PointElement, LineElement, Filler, BarElement, CategoryScale, LinearScale) +}) diff --git a/src/components/flights/FlightTrackMap.vue b/src/components/flights/FlightTrackMap.vue index c3c581c..d091aa3 100644 --- a/src/components/flights/FlightTrackMap.vue +++ b/src/components/flights/FlightTrackMap.vue @@ -2,6 +2,7 @@ import {flight} from "src/composables/objects/flight"; import MapWithMarkers from "components/map/MapWithMarkers.vue"; import {computed, ref} from "vue"; +import {Line} from "vue-chartjs" const props = defineProps({ flight: { @@ -11,24 +12,22 @@ const props = defineProps({ }) -const trackWithGPS = computed(() => { +const mapPhotos = computed(() => flight.value.photos.filter(photo => photo.gpsLatitude && photo.gpsLongitude)); +const poisWithGPS = computed(() => { return flight.value.track.filter(poi => poi.pointOfInterest.gpsLatitude && poi.pointOfInterest.gpsLongitude) }); -const slider = ref(0); - - const pointsOfInterests = computed(() => { const path = [{ label: flight.value.takeoffAirport.name, position: { - lat: flight.value.takeoffAirport.gpsLatitude + (slider.value / 1000), - lng: flight.value.takeoffAirport.gpsLongitude + (slider.value / 1000) + lat: flight.value.takeoffAirport.gpsLatitude, + lng: flight.value.takeoffAirport.gpsLongitude } }]; - for (const poi of trackWithGPS.value) { + for (const poi of poisWithGPS.value) { path.push({ label: poi.pointOfInterest.name, position: {lat: poi.pointOfInterest.gpsLatitude, lng: poi.pointOfInterest.gpsLongitude} @@ -47,33 +46,93 @@ const pointsOfInterests = computed(() => { }); -const mapPhotos = computed(() => { - return flight.value.photos.filter(photo => photo.gpsLatitude && photo.gpsLongitude) -}); +const gpxTrack = flight.value.gpxTrack; const displayMap = computed(() => { return ( - !!flight.value.gpxTrackUrl || + !!gpxTrack || mapPhotos.value.length > 0 || - trackWithGPS.value.length > 0 || + poisWithGPS.value.length > 0 || flight.value.landingAirport.id !== flight.value.takeoffAirport.id ) }); +let chartSpeedData, chartAltitudeData = null; +if (gpxTrack) { + const chartLabels = gpxTrack.time.map(t => { + const datetime = new Date(t); + return datetime.getHours() + ":" + datetime.getMinutes(); + }); + chartSpeedData = { + labels: chartLabels, + datasets: [{ + backgroundColor: "#38bd3e", + borderColor: "#38bd3e", + label: "Rychlost (km/h)", + data: gpxTrack.speed, + pointRadius: 1, + }] + }; + + chartAltitudeData = { + labels: chartLabels, + datasets: [{ + backgroundColor: "#9ae3ff", + borderColor: "#9ae3ff", + label: "Nadmořská výška letu (m)", + data: gpxTrack.altitude, + pointRadius: 1, + }] + }; + + if (gpxTrack.terrainElevation.length > 0) { + chartAltitudeData.datasets.push( + { + backgroundColor: "rgba(148,94,40,0.7)", + borderColor: "#945e28", + label: "Nadmořská výška země (m)", + data: gpxTrack.terrainElevation, + fill: true, + pointRadius: 1, + }); + } +} + + +const currentPositionMarker = ref(null); +const drawCurrentPositionMarker = (e) => { + const res = e.chart.getElementsAtEventForMode(e, 'nearest', {intersect: false},); + if (res.length === 0) { + currentPositionMarker.value = null; + return; + } + const idx = res[0].index; + currentPositionMarker.value = flight.value.gpxTrack.coordinates[idx]; +} + +const chartOptions = { + plugins: { + tooltip: { + mode: 'index', + intersect: false, + } + }, + onClick: drawCurrentPositionMarker, + onHover: drawCurrentPositionMarker, +} diff --git a/src/components/map/MapWithMarkers.vue b/src/components/map/MapWithMarkers.vue index c9783ce..bd1a431 100644 --- a/src/components/map/MapWithMarkers.vue +++ b/src/components/map/MapWithMarkers.vue @@ -6,8 +6,8 @@ import {axios} from "boot/axios"; const props = defineProps({ - gpxTrackUrl: { - type: String, + gpxTrack: { + type: Object, required: false, }, markers: { @@ -15,6 +15,10 @@ const props = defineProps({ required: false, default: () => [] }, + highlightPosition: { + type: Object, + required: false, + }, photos: { type: Array, required: false, @@ -23,35 +27,8 @@ const props = defineProps({ }) const track = ref([]); -const gpxLoading = ref(!!props.gpxTrackUrl); const speed = ref([]); const elevation = ref([]); -onMounted(() => { - if (!props.gpxTrackUrl) { - return; - } - gpxLoading.value = true; - axios.get(props.gpxTrackUrl) - .then(({data}) => (new window.DOMParser()).parseFromString(data, "text/xml")) - .then(doc => { - const points = []; - const nodes = [...doc.getElementsByTagName('trkpt')]; - nodes.forEach(node => { - const lat = parseFloat(node.getAttribute("lat")); - const lng = parseFloat(node.getAttribute("lon")); - - console.log(node.childNodes); - - points.push({lat, lng}); - // speed.value.push(node.getElementsByTagName("extensions").getElementsByTagName("speed")); - // elevation.value.push(node.getElementsByTagName("ele").innerText); - }) - - track.value = points; // puvodne se bounds bralo z tohodle - }) - .finally(() => gpxLoading.value = false); -}); - const bounds = computed(() => { const photosTransformed = props.photos.map(p => ({position: {lat: p.gpsLatitude, lng: p.gpsLongitude}})); @@ -61,22 +38,19 @@ const bounds = computed(() => {