Zakladni verze planovace
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 797 B |
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup>
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
markers: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const swapMarkers = (idx1, idx2) => {
|
||||||
|
const tmp = markers.value[idx1];
|
||||||
|
markers.value[idx1] = markers.value[idx2];
|
||||||
|
markers.value[idx2] = tmp;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
sem prijde ten modal, ve kterem se edituje cela trasa
|
||||||
|
|
||||||
|
</template>
|
||||||
@@ -13,7 +13,7 @@ const props = defineProps({
|
|||||||
<q-list bordered separator>
|
<q-list bordered separator>
|
||||||
<q-item
|
<q-item
|
||||||
clickable
|
clickable
|
||||||
:to="{name: 'flightPlan', params: {id: flightPlan.id }}"
|
:to="{name: 'editFlightPlan', params: {id: flightPlan.id }}"
|
||||||
v-ripple
|
v-ripple
|
||||||
v-for="flightPlan of items" :key="flightPlan">
|
v-for="flightPlan of items" :key="flightPlan">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const markersCenter = computed(() => {
|
||||||
|
if (markers.value.length === 0) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
gpsLatitude: markers.value.map(m => m.gpsLatitude).reduce((a, b) => a + b) / markers.value.length,
|
||||||
|
gpsLongitude: markers.value.map(m => m.gpsLongitude).reduce((a, b) => a + b) / markers.value.length,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const calcAngleFromCenter = (pointA, pointB) => {
|
||||||
|
if (!markersCenter.value) return 0;
|
||||||
|
|
||||||
|
const u = vector(markersCenter.value, pointA);
|
||||||
|
const v = vector(markersCenter.value, pointB);
|
||||||
|
|
||||||
|
return Math.acos(
|
||||||
|
(u[0] * v[0] + u[1] * v[1])
|
||||||
|
/
|
||||||
|
(vectorSize(u) * vectorSize(v))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetPosition = null;
|
||||||
|
let minAngleDiff = Infinity;
|
||||||
|
let mapPoints = 1;
|
||||||
|
|
||||||
|
const handleMapClickNefunkcni = (position) => {
|
||||||
|
const clickPoint = {gpsLatitude: position.lat, gpsLongitude: position.lng};
|
||||||
|
|
||||||
|
const clickAngle = calcAngleFromCenter(clickPoint, markers.value[0]);
|
||||||
|
|
||||||
|
for (let i = 1; i < markers.value.length - 1; i++) {
|
||||||
|
const marker = markers.value[i];
|
||||||
|
const angle = calcAngleFromCenter(marker, markers.value[0]);
|
||||||
|
|
||||||
|
const angleDiff = clickAngle - angle;
|
||||||
|
console.log("click", i, marker.name, angleDiff);
|
||||||
|
if (angleDiff < minAngleDiff) {
|
||||||
|
console.log(" update => ", minAngleDiff, " > ", Math.abs(angleDiff), marker.name, " -> ", markers.value[i + 1].name);
|
||||||
|
minAngleDiff = angleDiff
|
||||||
|
targetPosition = Math.max(1, angleDiff < 0 ? i - 1 : i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(marker.name, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("click", clickAngle);
|
||||||
|
console.log("insert at position", targetPosition);
|
||||||
|
console.log("---------------")
|
||||||
|
markers.value.splice(targetPosition, 0, {name: `Bod v mape ${mapPoints}`, type: "mapPoint", ...clickPoint});
|
||||||
|
mapPoints++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMapClickNefunkcni2 = (position) => {
|
||||||
|
const clickPoint = {gpsLatitude: position.lat, gpsLongitude: position.lng}
|
||||||
|
|
||||||
|
const markersToSort = markers.value.slice(1, -1);
|
||||||
|
markersToSort.push({name: `Bod v mape ${mapPoints}`, type: "mapPoint", ...clickPoint});
|
||||||
|
|
||||||
|
markersToSort.sort(
|
||||||
|
(a, b) => calcAngleFromCenter(a, markers.value[0]) - calcAngleFromCenter(b, markers.value[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const marker of markersToSort) {
|
||||||
|
console.log(marker.name, calcAngleFromCenter(marker, markers.value[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
markers.value = [markers.value[0], ...markersToSort, markers.value[markers.value.length - 1]];
|
||||||
|
mapPoints++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcTotalDistance = (points) => {
|
||||||
|
let distance = 0;
|
||||||
|
for (let i = 1; i < points.length; i++) {
|
||||||
|
distance += vectorDistance(points[i-1], points[i])
|
||||||
|
}
|
||||||
|
return distance
|
||||||
|
}
|
||||||
|
|
||||||
|
let minDistance = Infinity;
|
||||||
|
const handleMapClick = (position) => {
|
||||||
|
const clickPoint = {gpsLatitude: position.lat, gpsLongitude: position.lng};
|
||||||
|
const newMarker = {name: `Bod v mape ${mapPoints}`, type: "mapPoint", ...clickPoint}
|
||||||
|
markers.value.push(newMarker)
|
||||||
|
|
||||||
|
for (let i = 1; i < markers.value.length - 1; i++) {
|
||||||
|
console.log(markers.value[i], clickPoint);
|
||||||
|
const dist = vectorDistance(markers.value[i], clickPoint);
|
||||||
|
|
||||||
|
if (dist < minDistance) {
|
||||||
|
minDistance = dist;
|
||||||
|
targetPosition = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPosition = Math.min(markers.value.length - 1, Math.max(1, targetPosition));
|
||||||
|
markers.value.splice(targetPosition, 0, {name: `Bod v mape ${mapPoints}`, type: "mapPoint", ...clickPoint});
|
||||||
|
mapPoints++;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {Circle, GoogleMap, Marker, Polygon} from "vue3-google-map";
|
import {Circle, GoogleMap, CustomMarker, Polygon} from "vue3-google-map";
|
||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
|
import {useBasicVectorOperations, useVectorUtities} from "src/graphql/fragments/vectorUtilities";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
bounds: {
|
bounds: {
|
||||||
@@ -87,46 +88,56 @@ const atzs = computed(() => {
|
|||||||
|
|
||||||
// TODO: pridat letiste
|
// TODO: pridat letiste
|
||||||
|
|
||||||
function isPointInPolygon(clickPoint, polygonCoords) {
|
const {isPointInPolygon} = useVectorUtities();
|
||||||
var count = 0;
|
const {vectorDistance} = useBasicVectorOperations();
|
||||||
var x = clickPoint.lng, y = clickPoint.lat;
|
|
||||||
|
|
||||||
for (var i = 0; i < polygonCoords.length; i++) {
|
const handleLayerClick = (e) => {
|
||||||
var iNext = (i + 1) % polygonCoords.length;
|
const clickCoordinates = {lat: e.latLng.lat(), lng: e.latLng.lng()}
|
||||||
|
|
||||||
var xi = polygonCoords[i].lng, yi = polygonCoords[i].lat;
|
const airspacesUnderClick = []
|
||||||
var xiNext = polygonCoords[iNext].lng, yiNext = polygonCoords[iNext].lat;
|
|
||||||
|
|
||||||
var test = ((yi > y) !== (yiNext > y)) && (x < (xiNext - xi) * (y - yi) / (yiNext - yi) + xi);
|
for (const atz of atzs.value) {
|
||||||
if (test) count++;
|
const distance = vectorDistance(
|
||||||
|
{gpsLatitude: e.latLng.lat(), gpsLongitude: e.latLng.lng()},
|
||||||
|
{gpsLatitude: atz.center.lat, gpsLongitude: atz.center.lng},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distance < 0.079) { // tohle je nejaka bulharska konstanta, +- 5km od stredu letiste
|
||||||
|
airspacesUnderClick.push(atz);
|
||||||
}
|
}
|
||||||
|
|
||||||
return count % 2 === 1; // Lichý počet průsečíků = bod uvnitř
|
|
||||||
}
|
|
||||||
|
|
||||||
const showInfo = (e) => {
|
|
||||||
for (const airspace of airspaces.value) {
|
|
||||||
const clickCoords = {lat: e.latLng.lat(), lng: e.latLng.lng()}
|
|
||||||
|
|
||||||
if (airspace.path)
|
|
||||||
console.log(airspace.name, isPointInPolygon(clickCoords, airspace.path));
|
|
||||||
else {
|
|
||||||
// console.log(airspace);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const airspace of airspaces.value) {
|
||||||
|
if (airspace.path && isPointInPolygon(clickCoordinates, airspace.path)) {
|
||||||
|
airspacesUnderClick.push(airspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emits("map-click", {airspaces: airspacesUnderClick, clickCoordinates});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emits = defineEmits(["map-click"]);
|
||||||
|
|
||||||
|
const handleMapClick = (e) => {
|
||||||
|
const clickCoordinates = {lat: e.latLng.lat(), lng: e.latLng.lng()}
|
||||||
|
emits("map-click", {clickCoordinates});
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<GoogleMap :api-key="key" v-bind="$attrs" map-type-id="terrain" :max-zoom="13" ref="mapRef">
|
<GoogleMap :api-key="key" v-bind="$attrs" @click="handleMapClick" map-type-id="terrain" :max-zoom="13" ref="mapRef">
|
||||||
<slot/>
|
<slot/>
|
||||||
|
|
||||||
<template v-for="airspace in airspaces" :key="airspace">
|
<template v-for="airspace in airspaces" :key="airspace">
|
||||||
<Polygon :options="airspace" @click=showInfo v-if="airspace.path"/>
|
<Polygon :options="airspace" @click=handleLayerClick v-if="airspace.path"/>
|
||||||
<Circle :options="airspace" @click=showInfo v-if="airspace.center && airspace.radius"/>
|
<Circle :options="airspace" @click=handleLayerClick v-if="airspace.center && airspace.radius"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-for="atz in atzs" :key="atz">
|
||||||
|
<Circle :options="atz" @click=handleLayerClick />
|
||||||
|
<CustomMarker :options="{position: atz.center, anchorPoint: 'CENTER'}" @click="handleLayerClick">
|
||||||
|
<q-icon name="local_airport" style="font-size: 24px" class="text-primary"/>
|
||||||
|
</CustomMarker>
|
||||||
</template>
|
</template>
|
||||||
<Circle :options="atz" v-for="atz in atzs" @click=showInfo :key="atz"/>
|
|
||||||
</GoogleMap>
|
</GoogleMap>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export const useFlightPlanUtils = () => {
|
||||||
|
const getIcon = (type) => {
|
||||||
|
const icons = {
|
||||||
|
"poi": "pin_drop",
|
||||||
|
"geocode": "travel_explore",
|
||||||
|
"airport": "local_airport"
|
||||||
|
};
|
||||||
|
|
||||||
|
return icons[type] || "place"
|
||||||
|
}
|
||||||
|
|
||||||
|
return { getIcon }
|
||||||
|
}
|
||||||
@@ -1,20 +1,36 @@
|
|||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
export const FRAGMENT_FLIGHT_PLAN_MARKER = gql`
|
||||||
|
fragment flightPlanMarker on FlightPlanMarker {
|
||||||
|
name
|
||||||
|
position
|
||||||
|
type
|
||||||
|
gpsLatitude
|
||||||
|
gpsLongitude
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const FRAGMENT_FLIGHT_PLAN_FULL_INFO = gql`
|
export const FRAGMENT_FLIGHT_PLAN_FULL_INFO = gql`
|
||||||
fragment flightPlanFullInfo on FlightPlan {
|
fragment flightPlanFullInfo on FlightPlan {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
markers {
|
track {
|
||||||
name
|
bearing
|
||||||
position
|
distance
|
||||||
type
|
from {
|
||||||
gpsLatitude
|
...flightPlanMarker
|
||||||
gpsLongitude
|
}
|
||||||
|
to {
|
||||||
|
...flightPlanMarker
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_FLIGHT_PLAN_MARKER}
|
||||||
|
`;
|
||||||
|
|
||||||
export const FRAGMENT_FLIGHT_PLAN_LIST_INFO = gql`
|
export const FRAGMENT_FLIGHT_PLAN_LIST_INFO = gql`
|
||||||
fragment flightPlanListInfo on FlightPlanListInfo {
|
fragment flightPlanListInfo on FlightPlan {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
export const useBasicVectorOperations = () => {
|
||||||
|
const vectorSize = (a) => {
|
||||||
|
return Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
const vector = (pointA, pointB) => {
|
||||||
|
return [pointA.gpsLatitude - pointB.gpsLatitude, pointA.gpsLongitude - pointB.gpsLongitude];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vectorDistance = (pointA, pointB) => {
|
||||||
|
console.log("pointA", pointA, "pointB", pointB);
|
||||||
|
|
||||||
|
const u = [pointA.gpsLatitude, pointA.gpsLongitude];
|
||||||
|
const v = [pointB.gpsLatitude, pointB.gpsLongitude];
|
||||||
|
// const v = vector(pointB);
|
||||||
|
|
||||||
|
return Math.sqrt(
|
||||||
|
Math.pow(u[0] - v[0], 2) + Math.pow(u[1] - v[1], 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {vector, vectorSize, vectorDistance}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useVectorUtities = () => {
|
||||||
|
|
||||||
|
|
||||||
|
function isPointInPolygon(clickPoint, polygonCoords) {
|
||||||
|
let count = 0;
|
||||||
|
const x = clickPoint.lng, y = clickPoint.lat;
|
||||||
|
|
||||||
|
for (let i = 0; i < polygonCoords.length; i++) {
|
||||||
|
const iNext = (i + 1) % polygonCoords.length;
|
||||||
|
|
||||||
|
const xi = polygonCoords[i].lng, yi = polygonCoords[i].lat;
|
||||||
|
const xiNext = polygonCoords[iNext].lng, yiNext = polygonCoords[iNext].lat;
|
||||||
|
|
||||||
|
const test = ((yi > y) !== (yiNext > y)) && (x < (xiNext - xi) * (y - yi) / (yiNext - yi) + xi);
|
||||||
|
if (test) count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count % 2 === 1; // Lichý počet průsečíků = bod uvnitř
|
||||||
|
}
|
||||||
|
|
||||||
|
return {isPointInPolygon}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import gql from "graphql-tag";
|
|||||||
import {FRAGMENT_FLIGHT_PLAN_FULL_INFO} from "src/graphql/fragments/flightPlan";
|
import {FRAGMENT_FLIGHT_PLAN_FULL_INFO} from "src/graphql/fragments/flightPlan";
|
||||||
|
|
||||||
export const CREATE_FLIGHT_PLAN = gql`
|
export const CREATE_FLIGHT_PLAN = gql`
|
||||||
mutation createFlight_Plan($input: CreateFlightPlanInput!) {
|
mutation createFlightPlan($input: CreateFlightPlanInput!) {
|
||||||
createFlightPlan(input: $input) {
|
createFlightPlan(input: $input) {
|
||||||
...flightPlanFullInfo
|
...flightPlanFullInfo
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,8 @@ export const CREATE_FLIGHT_PLAN = gql`
|
|||||||
|
|
||||||
export const EDIT_FLIGHT_PLAN = gql`
|
export const EDIT_FLIGHT_PLAN = gql`
|
||||||
mutation editFlightPlan($id: Int!, $input: EditFlightPlanInput!) {
|
mutation editFlightPlan($id: Int!, $input: EditFlightPlanInput!) {
|
||||||
editFlight_Plan(id: $id, input: $input) {
|
editFlightPlan(id: $id, input: $input) {
|
||||||
...flight_planFullInfo
|
...flightPlanFullInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${FRAGMENT_FLIGHT_PLAN_FULL_INFO}
|
${FRAGMENT_FLIGHT_PLAN_FULL_INFO}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import {FRAGMENT_FLIGHT_PLAN_LIST_INFO} from "src/graphql/fragments/flightPlan";
|
import {FRAGMENT_FLIGHT_PLAN_FULL_INFO, FRAGMENT_FLIGHT_PLAN_LIST_INFO} from "src/graphql/fragments/flightPlan";
|
||||||
|
|
||||||
export const FLIGHT_PLANS_LIST = gql`
|
export const FLIGHT_PLANS_LIST = gql`
|
||||||
query flightPlansList {
|
query flightPlansList {
|
||||||
@@ -10,3 +10,13 @@ query flightPlansList {
|
|||||||
|
|
||||||
${FRAGMENT_FLIGHT_PLAN_LIST_INFO}
|
${FRAGMENT_FLIGHT_PLAN_LIST_INFO}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const FLIGHT_PLAN_DETAIL = gql`
|
||||||
|
query flightPlansDetail($id: Int!) {
|
||||||
|
flightPlan(id: $id) {
|
||||||
|
...flightPlanFullInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FRAGMENT_FLIGHT_PLAN_FULL_INFO}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import {useEventsStore} from "stores/Events";
|
|||||||
import FlightEditCopilotSkeleton from "components/flights/skeletons/FlightEditCopilotSkeleton.vue";
|
import FlightEditCopilotSkeleton from "components/flights/skeletons/FlightEditCopilotSkeleton.vue";
|
||||||
import {useBasicPhotoOperations} from "src/composables/photos/basicOperations";
|
import {useBasicPhotoOperations} from "src/composables/photos/basicOperations";
|
||||||
import {usePhotosStore} from "stores/Photos";
|
import {usePhotosStore} from "stores/Photos";
|
||||||
|
import {flightName} from "src/composables/flight";
|
||||||
|
import {useAppStore} from "stores/App";
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const $route = useRoute();
|
const $route = useRoute();
|
||||||
@@ -47,12 +49,10 @@ const {events} = storeToRefs(useEventsStore());
|
|||||||
const {photos} = storeToRefs(usePhotosStore());
|
const {photos} = storeToRefs(usePhotosStore());
|
||||||
const {flight, isQueryRunning, isRequestPending: isMutationRunning} = storeToRefs(useFlightsStore());
|
const {flight, isQueryRunning, isRequestPending: isMutationRunning} = storeToRefs(useFlightsStore());
|
||||||
|
|
||||||
|
|
||||||
const formStep = ref("basic");
|
const formStep = ref("basic");
|
||||||
watch(() => $route.params.step, (val) => formStep.value = val);
|
watch(() => $route.params.step, (val) => formStep.value = val);
|
||||||
|
|
||||||
import {flightName} from "src/composables/flight";
|
|
||||||
import {useAppStore} from "stores/App";
|
|
||||||
|
|
||||||
const flightNameFormatted = flightName(flight.value);
|
const flightNameFormatted = flightName(flight.value);
|
||||||
useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`);
|
useAppStore().setPageTitle(`Editace - ${flightNameFormatted}`);
|
||||||
useAppStore().setBreadcrumbs([
|
useAppStore().setBreadcrumbs([
|
||||||
|
|||||||
@@ -1,76 +1,402 @@
|
|||||||
|
<script>
|
||||||
|
import {useFlightPlansStore} from "stores/FlightPlans";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preFetch({currentRoute}) {
|
||||||
|
return useFlightPlansStore().fetchDetail(currentRoute.params.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onMounted} from "vue";
|
import {computed, onMounted, ref, watch} from "vue";
|
||||||
import {useAppStore} from "stores/App";
|
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||||
import CopilotEditSkeleton from "components/copilots/skeletons/CopilotEditSkeleton.vue";
|
|
||||||
import CopilotEditForm from "components/copilots/CopilotForm.vue";
|
|
||||||
import {useRoute, useRouter} from "vue-router";
|
|
||||||
import {useCopilotsStore} from "stores/Copilots";
|
|
||||||
import {successObject} from "src/composables/dialog";
|
|
||||||
import {useQuasar} from "quasar";
|
|
||||||
import {storeToRefs} from "pinia";
|
import {storeToRefs} from "pinia";
|
||||||
import PhotosEditorList from "components/photos/PhotosEditorList.vue";
|
import {Marker, CustomMarker, Polyline, InfoWindow} from "vue3-google-map";
|
||||||
import CommonEditSwitcher from "components/navigation/CommonEditSwitcher.vue";
|
import {useAirspacesStore} from "stores/Airspaces";
|
||||||
import PhotoGallerySkeleton from "components/photos/skeletons/PhotoGallerySkeleton.vue";
|
import MapWrapper from "components/map/MapWrapper.vue";
|
||||||
import BasicInfoBanner from "components/banners/BasicInfoBanner.vue";
|
import {useGeocode} from "src/composables/geocoding";
|
||||||
|
import {useAirportsStore} from "stores/Airports";
|
||||||
|
import {commonProps} from "src/composables/inputs";
|
||||||
|
import {minutesToHumanReadable} from "src/composables/datetimeUtils";
|
||||||
|
import {useAppStore} from "stores/App";
|
||||||
|
|
||||||
const $q = useQuasar();
|
|
||||||
const $router = useRouter();
|
|
||||||
const $route = useRoute();
|
|
||||||
|
|
||||||
const copilotId = parseInt($route.params.id);
|
const {flightPlan} = storeToRefs(useFlightPlansStore());
|
||||||
|
const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
|
||||||
|
const {airspaces: airspacesData, areAirspacesLoading: isQueryRunning} = storeToRefs(useAirspacesStore())
|
||||||
|
const {airports} = storeToRefs(useAirportsStore());
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
useCopilotsStore().fetchDetail(copilotId).then(() => {
|
useAirspacesStore().fetch();
|
||||||
useAppStore().setPageTitle(`${copilot.value.name} - editace`);
|
usePointsOfInterestStore().fetch({limit: 10000});
|
||||||
useAppStore().setBreadcrumbs([
|
useAirportsStore().fetch();
|
||||||
{to: {name: "copilots"}, label: "Kopiloti"},
|
|
||||||
{to: {name: "copilotDetail", params: {id: copilotId}}, label: copilot.value.name},
|
|
||||||
{label: "Editace"}
|
|
||||||
])
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSave = (formData) => {
|
|
||||||
useCopilotsStore().update(copilotId, formData).then(() => {
|
const emits = defineEmits(["update:gpsLatitude", "update:gpsLongitude", "notFound"]);
|
||||||
$q.notify(successObject("Kopilot úspěšně upraven"));
|
const {center, zoom, searchGeocode} = useGeocode(emits);
|
||||||
$router.push({name: "copilotDetail", params: {id: copilotId}});
|
zoom.value = 8;
|
||||||
});
|
|
||||||
|
|
||||||
|
const placesOptions = ref(null);
|
||||||
|
const allPlacesOptions = computed(() => {
|
||||||
|
return [
|
||||||
|
...pointsOfInterest.value.map(poi => ({...poi, type: "poi"})),
|
||||||
|
...airports.value.map(airport => ({
|
||||||
|
...airport,
|
||||||
|
name: `${airport.icaoCode} - ${airport.name}`,
|
||||||
|
type: "airport",
|
||||||
|
}))
|
||||||
|
].map(i => ({...i, disabled: !i.gpsLatitude || !i.gpsLongitude}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const placesFilter = (val, update, abort) => {
|
||||||
|
update(() => {
|
||||||
|
if (val && val !== '') {
|
||||||
|
placesOptions.value = allPlacesOptions.value.filter(p => p.name.includes(val));
|
||||||
|
if (placesOptions.value.length === 0) {
|
||||||
|
searchGeocode(val).then(results => {
|
||||||
|
if (!results) return;
|
||||||
|
|
||||||
|
placesOptions.value = results.map(result => ({
|
||||||
|
name: result.formatted_address,
|
||||||
|
type: "geocode",
|
||||||
|
id: null,
|
||||||
|
gpsLatitude: result.geometry.location.lat,
|
||||||
|
gpsLongitude: result.geometry.location.lng,
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
placesOptions.value = allPlacesOptions.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const {isQueryRunning, isMutationRunning, copilot} = storeToRefs(useCopilotsStore());
|
|
||||||
|
|
||||||
const formStep = computed(() => useRoute().params.step || "info");
|
const formData = ref({
|
||||||
const setTitlePhoto = (titlePhoto) => {
|
flightPlanName: flightPlan.value?.name || "",
|
||||||
useCopilotsStore().update(copilotId, {titlePhotoId: titlePhoto.id}).then(() => {
|
plannedSpeed: 150
|
||||||
$q.notify(successObject("Úvodní fotka nastavena"));
|
})
|
||||||
})
|
|
||||||
|
const place = ref("");
|
||||||
|
const prompt = ref(false);
|
||||||
|
const markers = ref([]);
|
||||||
|
markers.value = flightPlan.value.track.map(m => m.from);
|
||||||
|
if (flightPlan.value.track.length > 1) {
|
||||||
|
markers.value.push(flightPlan.value.track[flightPlan.value.track.length - 1].to);
|
||||||
|
}
|
||||||
|
const track = computed(() => markers.value.map(m => ({lat: m.gpsLatitude, lng: m.gpsLongitude})));
|
||||||
|
|
||||||
|
watch(() => place.value, (newVal) => {
|
||||||
|
addPoint(newVal);
|
||||||
|
place.value = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const markerObject = ({name, type, gpsLatitude, gpsLongitude}) => ({
|
||||||
|
name, type, gpsLatitude, gpsLongitude
|
||||||
|
});
|
||||||
|
|
||||||
|
const addPoint = async (point) => {
|
||||||
|
if (!point) return;
|
||||||
|
markers.value.push(markerObject({...point}));
|
||||||
|
}
|
||||||
|
const removeMarker = (idx) => {
|
||||||
|
markers.value.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
watch(() => markers.value, () => {
|
||||||
|
useFlightPlansStore().update(flightPlan.value.id, {
|
||||||
|
name: formData.value.flightPlanName,
|
||||||
|
isPublic: false,
|
||||||
|
markers: markers.value.map(m => markerObject({
|
||||||
|
name: m.name,
|
||||||
|
type: m.type,
|
||||||
|
gpsLatitude: m.gpsLatitude,
|
||||||
|
gpsLongitude: m.gpsLongitude,
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}, {deep: true});
|
||||||
|
|
||||||
|
const getIcon = (type) => {
|
||||||
|
const icons = {
|
||||||
|
"poi": "pin_drop",
|
||||||
|
"geocode": "travel_explore",
|
||||||
|
"airport": "local_airport"
|
||||||
|
};
|
||||||
|
|
||||||
|
return icons[type] || "place"
|
||||||
|
}
|
||||||
|
|
||||||
|
const swapMarkers = (idx1, idx2) => {
|
||||||
|
const tmp = markers.value[idx1];
|
||||||
|
markers.value[idx1] = markers.value[idx2];
|
||||||
|
markers.value[idx2] = tmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDuration = (distance, speed) => Math.round(distance / speed * 60)
|
||||||
|
|
||||||
|
const totalDistance = computed(() => {
|
||||||
|
if (!flightPlan.value.track.length) return null;
|
||||||
|
|
||||||
|
return Math.round(flightPlan.value.track.map(t => t.distance).reduce((a, b) => a + b));
|
||||||
|
});
|
||||||
|
const totalTime = computed(() => {
|
||||||
|
if (!flightPlan.value.track.length) return null;
|
||||||
|
|
||||||
|
return flightPlan.value.track.map(t => getDuration(t.distance, formData.value.plannedSpeed)).reduce((a, b) => a + b)
|
||||||
|
});
|
||||||
|
|
||||||
|
const infoWindowObject = ({active, position, marker, airspaces}) => {
|
||||||
|
if (!position) position = {};
|
||||||
|
if (!position.lat) position.lat = 0;
|
||||||
|
if (!position.lng) position.lng = 0;
|
||||||
|
|
||||||
|
if (marker) {
|
||||||
|
position.lat = marker.gpsLatitude;
|
||||||
|
position.lng = marker.gpsLongitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {active, position, airspaces, marker};
|
||||||
|
};
|
||||||
|
const infoWindow = ref(infoWindowObject({}));
|
||||||
|
|
||||||
|
const handleMapClick = ({clickCoordinates, airspaces}) => {
|
||||||
|
infoWindow.value = infoWindowObject({active: true, airspaces, position: clickCoordinates})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMarkerClick = (marker) => {
|
||||||
|
infoWindow.value = infoWindowObject({active: true, marker})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMarkerMove = (idx, event) => {
|
||||||
|
markers.value[idx].gpsLatitude = event.lat();
|
||||||
|
markers.value[idx].gpsLongitude = event.lng();
|
||||||
|
}
|
||||||
|
|
||||||
|
const editableNameTypes = ["geocode", "mapPoint"];
|
||||||
|
|
||||||
|
const flightPlanName = computed(() => flightPlan.value.name || "Nepojmenovaný plán");
|
||||||
|
useAppStore().setPageTitle(`${flightPlanName.value} - plánovnání`);
|
||||||
|
useAppStore().setBreadcrumbs([
|
||||||
|
{to: {name: "flightPlans"}, label: "Moje plány"},
|
||||||
|
{label: flightPlanName.value},
|
||||||
|
{label: "Editace"}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const googleMapsMarkerObject = (marker) => {
|
||||||
|
const anchorPoints = {
|
||||||
|
airport: "CENTER",
|
||||||
|
geocode: "BOTTOM_CENTER",
|
||||||
|
poi: "BOTTOM_CENTER",
|
||||||
|
mapPoint: "BOTTOM_CENTER",
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// label: marker.name,
|
||||||
|
anchorPoint: anchorPoints[marker.type],
|
||||||
|
draggable: editableNameTypes.includes(marker.type),
|
||||||
|
position: {lat: marker.gpsLatitude, lng: marker.gpsLongitude}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CommonEditSwitcher :route="{name: 'copilotEdit', params: {id: copilot.id}}"/>
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input v-model="formData.flightPlanName" label="Jméno letu" v-bind="commonProps"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-if="formStep === 'info'">
|
<div class="row">
|
||||||
<CopilotEditSkeleton v-if="isQueryRunning || !copilot"/>
|
<div class="col" style="min-width: 1px">
|
||||||
<CopilotEditForm
|
<MapWrapper
|
||||||
v-else
|
style="width: 100%; height: 100%; min-height: 500px"
|
||||||
:copilot="copilot"
|
:center="center"
|
||||||
:is-request-pending="isMutationRunning"
|
:zoom="zoom"
|
||||||
@save="handleSave"
|
:airspaces-data="airspacesData"
|
||||||
/>
|
@map-click="handleMapClick"
|
||||||
</template>
|
>
|
||||||
|
|
||||||
<template v-else-if="formStep === 'photo'">
|
<InfoWindow v-if="infoWindow.active" @close="infoWindow.active=false" :options="{position: infoWindow.position, title: 'titulek'}">
|
||||||
<PhotoGallerySkeleton v-if="isQueryRunning || !copilot" />
|
|
||||||
<BasicInfoBanner v-else-if="!isQueryRunning && copilot.photos.length === 0">
|
<div class="text-h5">{{ infoWindow.marker?.name || "Bod v mapě" }}</div>
|
||||||
U tohoto kopilota nemáš označenou žádnou fotku. Bez toho není možné zvolit úvodní fotografii.
|
|
||||||
</BasicInfoBanner>
|
<div v-if="infoWindow.marker">
|
||||||
<PhotosEditorList
|
<span class="text-h6">
|
||||||
v-else
|
<q-icon :name="getIcon(infoWindow.marker.type)"/>
|
||||||
:photos="copilot.photos"
|
{{ infoWindow.marker.name }}
|
||||||
:title-photo="copilot.titlePhoto"
|
</span>
|
||||||
:show-actions="false"
|
</div>
|
||||||
@set-title-photo="setTitlePhoto"
|
|
||||||
/>
|
<template v-if="infoWindow.airspaces?.length">
|
||||||
</template>
|
<span class="text-h6">Prostory</span>
|
||||||
|
<div v-for="airspace in infoWindow.airspaces" :key="airspace">
|
||||||
|
{{ airspace.name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<q-btn class="q-mt-lg" color="secondary" @click="addPoint(infoWindow.marker || {name: `Bod ${markers.length}`, type: 'mapPoint', gpsLatitude: infoWindow.position.lat, gpsLongitude: infoWindow.position.lng} )">Přidat bod do trasy</q-btn>
|
||||||
|
</InfoWindow>
|
||||||
|
|
||||||
|
<template v-for="(marker, i) in markers" :key="marker">
|
||||||
|
|
||||||
|
<Marker
|
||||||
|
v-if="editableNameTypes.includes(marker.type)"
|
||||||
|
:options="googleMapsMarkerObject(marker)"
|
||||||
|
@click="handleMarkerClick(marker)"
|
||||||
|
@dragend="handleMarkerMove(i, $event.latLng)"
|
||||||
|
/>
|
||||||
|
<CustomMarker
|
||||||
|
v-else
|
||||||
|
:options="googleMapsMarkerObject(marker)"
|
||||||
|
@click="handleMarkerClick(marker)"
|
||||||
|
>
|
||||||
|
<q-icon :name="getIcon(marker.type)" style="font-size: 30px;color: #810000"/>
|
||||||
|
</CustomMarker>
|
||||||
|
</template>
|
||||||
|
<Polyline :options="{path: track, geodesic: true, strokeColor: '#ff0202', strokeOpacity: 0.5, strokeWeight: 5}"/>
|
||||||
|
</MapWrapper>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-5 col-lg-3">
|
||||||
|
<q-list bordered class="q-ml-md-md q-pt-md">
|
||||||
|
<!-- <q-item>-->
|
||||||
|
<!-- <q-item-section>-->
|
||||||
|
<!-- <q-separator/>-->
|
||||||
|
<!-- </q-item-section>-->
|
||||||
|
<!-- </q-item>-->
|
||||||
|
|
||||||
|
<template v-for="(segment, i) in flightPlan.track" :key="segment">
|
||||||
|
<q-item dense>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label class="text-h6">
|
||||||
|
<q-icon :name="getIcon(segment.from.type)"/>
|
||||||
|
{{ segment.from.name }}
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-section class="q-py-lg text-grey-8">
|
||||||
|
{{ segment.bearing }}°,
|
||||||
|
{{ segment.distance }} km,
|
||||||
|
{{ minutesToHumanReadable(getDuration(segment.distance, formData.plannedSpeed)) }}
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-label class="text-h6" v-if="i === flightPlan.track.length - 1">
|
||||||
|
<q-icon :name="getIcon(segment.to.type)"/>
|
||||||
|
{{ segment.to.name }}
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="totalDistance">
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-separator/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
Celková vzdálenost: {{ totalDistance }} km
|
||||||
|
<br>
|
||||||
|
Plánovaná doba letu: {{ minutesToHumanReadable(totalTime) }}
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-btn @click="prompt=true" color="secondary">
|
||||||
|
|
||||||
|
{{ totalDistance ? 'Upravit trasu' : 'Sestavit trasu' }}
|
||||||
|
</q-btn>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input v-model="formData.plannedSpeed" label="Plánovaná rychlost" v-bind="commonProps">
|
||||||
|
<template #append>km/h</template>
|
||||||
|
</q-input>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-dialog full-width v-model="prompt">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6">Přidat otočný bod</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section class="q-pt-none">
|
||||||
|
<q-scroll-area style="height: 60vh;">
|
||||||
|
<q-list bordered separator class="full-width">
|
||||||
|
<q-item v-for="(marker, i) in markers" :key="marker">
|
||||||
|
|
||||||
|
<q-item-section side>
|
||||||
|
<q-icon :name="getIcon(marker.type)"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
|
||||||
|
<q-item-section side>
|
||||||
|
<div>
|
||||||
|
<q-btn text-color="primary" v-if="i > 0" size="16px" @click="swapMarkers(i, i-1)" flat dense round icon="arrow_upward"/>
|
||||||
|
<q-btn text-color="primary" v-if="i < markers.length - 1" size="16px" @click="swapMarkers(i, i+1)" flat dense round icon="arrow_downward"/>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<div class="cursor-pointer" v-if="editableNameTypes.includes(marker.type)">
|
||||||
|
{{ marker.name }}
|
||||||
|
<q-icon name="edit"/>
|
||||||
|
<q-popup-edit v-model="marker.name" v-slot="scope">
|
||||||
|
<q-input v-model="scope.value" autofocus @keyup.enter="scope.set">
|
||||||
|
<template #append>
|
||||||
|
<q-icon name="edit"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-popup-edit>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
{{ marker.name }}
|
||||||
|
</template>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side class="text-right">
|
||||||
|
<q-btn size="12px" @click="removeMarker(i)" flat dense round color="red" icon="delete"/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-scroll-area>
|
||||||
|
|
||||||
|
<q-select
|
||||||
|
v-bind="commonProps"
|
||||||
|
v-model="place"
|
||||||
|
label="Místo"
|
||||||
|
use-input
|
||||||
|
input-debounce="100"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
option-label="name"
|
||||||
|
option-disable="disabled"
|
||||||
|
:options="placesOptions"
|
||||||
|
@filter="placesFilter"
|
||||||
|
>
|
||||||
|
<template #no-option>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
Pis dal, najdeme neco na GoogleMaps!
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right" class="text-primary">
|
||||||
|
<q-btn flat label="Zavřít" v-close-popup/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,202 +1,21 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed, defineEmits, onMounted, ref, watch} from "vue";
|
import {onMounted} from "vue";
|
||||||
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
|
||||||
import {storeToRefs} from "pinia";
|
|
||||||
import {useQuasar} from "quasar";
|
|
||||||
import {commonProps} from "src/composables/inputs";
|
|
||||||
import {Marker, Polyline} from "vue3-google-map";
|
|
||||||
import {useAirspacesStore} from "stores/Airspaces";
|
|
||||||
import MapWrapper from "components/map/MapWrapper.vue";
|
|
||||||
import {useGeocode} from "src/composables/geocoding";
|
|
||||||
import {errorObject} from "src/composables/dialog";
|
|
||||||
import {useAirportsStore} from "stores/Airports";
|
|
||||||
import {useFlightPlansStore} from "stores/FlightPlans";
|
import {useFlightPlansStore} from "stores/FlightPlans";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const $router = useRouter();
|
||||||
|
|
||||||
const place = ref("");
|
onMounted(async () => {
|
||||||
|
await useFlightPlansStore().create({
|
||||||
const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
|
|
||||||
const {airspaces: airspacesData, areAirspacesLoading: isQueryRunning} = storeToRefs(useAirspacesStore())
|
|
||||||
|
|
||||||
const $q = useQuasar();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
useAirspacesStore().fetch();
|
|
||||||
|
|
||||||
usePointsOfInterestStore().fetch({limit: 10000});
|
|
||||||
useAirportsStore().fetch()
|
|
||||||
});
|
|
||||||
|
|
||||||
const markers = ref([]);
|
|
||||||
watch(() => markers.value, (newVal) => {
|
|
||||||
|
|
||||||
}, {deep: true});
|
|
||||||
|
|
||||||
const addPoint = async (point) => {
|
|
||||||
if (!point) return;
|
|
||||||
|
|
||||||
markers.value.push(point);
|
|
||||||
// {
|
|
||||||
// label: point.name,
|
|
||||||
// type: point.type,
|
|
||||||
//
|
|
||||||
// position: {
|
|
||||||
// lat: point.gpsLatitude,
|
|
||||||
// lng: point.gpsLongitude
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
const track = computed(() => {
|
|
||||||
return markers.value.map(m => ({lat: m.gpsLatitude, lng: m.gpsLongitude}));
|
|
||||||
})
|
|
||||||
|
|
||||||
const removeMarker = (idx) => {
|
|
||||||
markers.value.splice(idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const emits = defineEmits(["update:gpsLatitude", "update:gpsLongitude", "notFound"]);
|
|
||||||
const {center, zoom, searchGeocode} = useGeocode(emits);
|
|
||||||
zoom.value = 8;
|
|
||||||
|
|
||||||
const {airports} = storeToRefs(useAirportsStore());
|
|
||||||
|
|
||||||
const placesOptions = ref(null);
|
|
||||||
const allPlacesOptions = computed(() => {
|
|
||||||
return [
|
|
||||||
...pointsOfInterest.value.map(poi => ({...poi, type: "poi"})),
|
|
||||||
...airports.value.map(airport => ({
|
|
||||||
...airport,
|
|
||||||
name: `${airport.icaoCode} - ${airport.name}`,
|
|
||||||
type: "airport",
|
|
||||||
}))
|
|
||||||
].map(i => ({...i, disabled: !i.gpsLatitude || !i.gpsLongitude}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const placesFilter = (val, update, abort) => {
|
|
||||||
update(() => {
|
|
||||||
console.log(val);
|
|
||||||
|
|
||||||
if (val && val !== '') {
|
|
||||||
placesOptions.value = allPlacesOptions.value.filter(p => p.name.includes(val));
|
|
||||||
console.log(placesOptions.value);
|
|
||||||
if (placesOptions.value.length === 0) {
|
|
||||||
searchGeocode(val).then(results => {
|
|
||||||
placesOptions.value = results.map(result => ({
|
|
||||||
name: result.formatted_address,
|
|
||||||
type: "geocode",
|
|
||||||
id: null,
|
|
||||||
gpsLatitude: result.geometry.location.lat,
|
|
||||||
gpsLongitude: result.geometry.location.lng,
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
placesOptions.value = [];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => place.value, (newVal) => {
|
|
||||||
addPoint(newVal);
|
|
||||||
place.value = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const saveMarkers = () => {
|
|
||||||
useFlightPlansStore().create({
|
|
||||||
name: "",
|
name: "",
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
markers: markers.value.map(m => ({
|
markers: []
|
||||||
name: m.name,
|
}).then(fp => {
|
||||||
type: m.type,
|
return $router.replace({name: "editFlightPlan", params: {id: fp.id}});
|
||||||
gpsLatitude: m.gpsLatitude,
|
})
|
||||||
gpsLongitude: m.gpsLongitude,
|
})
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<q-spinner/>
|
||||||
<div class="col" style="min-width: 1px">
|
|
||||||
<MapWrapper style="width: 100%; height: 500px" :center="center" :zoom="zoom" :airspaces-data="airspacesData">
|
|
||||||
<template v-for="marker in markers" :key="marker">
|
|
||||||
<Marker :options="{label: marker.name, position: {lat: marker.gpsLatitude, lng: marker.gpsLongitude}}"/>
|
|
||||||
</template>
|
|
||||||
<Polyline :options="{path: track, geodesic: true, strokeColor: '#ff0000', strokeOpacity: 0.5, strokeWeight: 5}"/>
|
|
||||||
</MapWrapper>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-5 col-lg-3" v-if="markers.length">
|
|
||||||
<q-list bordered class="q-ml-md-md">
|
|
||||||
<template v-for="(marker, i) in markers" :key="marker">
|
|
||||||
<q-item>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>
|
|
||||||
{{ i + 1 }}. {{ marker.label }}
|
|
||||||
</q-item-label>
|
|
||||||
<q-item-section caption>
|
|
||||||
{{ marker.type }}
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-label caption>
|
|
||||||
<q-btn icon="add" color="green" flat dense round></q-btn>
|
|
||||||
</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<q-item-section middle side>
|
|
||||||
<div class="text-grey-8 q-gutter-xs">
|
|
||||||
<!-- <q-btn class="gt-xs" size="12px" flat dense round icon="delete" />-->
|
|
||||||
<!-- <q-btn class="gt-xs" size="12px" flat dense round icon="done" />-->
|
|
||||||
<!-- <q-btn size="12px" flat dense round icon="more_vert" />-->
|
|
||||||
|
|
||||||
<q-btn color="red" flat round dense icon="delete" @click="removeMarker(i)"/>
|
|
||||||
</div>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-select
|
|
||||||
v-model="place"
|
|
||||||
label="Místo"
|
|
||||||
use-input
|
|
||||||
input-debounce="100"
|
|
||||||
map-options
|
|
||||||
emit-value
|
|
||||||
option-label="name"
|
|
||||||
option-disable="disabled"
|
|
||||||
:options="placesOptions"
|
|
||||||
@filter="placesFilter"
|
|
||||||
>
|
|
||||||
<!-- <template #option="{opt}">-->
|
|
||||||
<!-- <q-item>-->
|
|
||||||
<!-- <q-item-section>-->
|
|
||||||
<!-- Option! {{ opt }}-->
|
|
||||||
<!-- </q-item-section>-->
|
|
||||||
<!-- </q-item>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
</q-select>
|
|
||||||
|
|
||||||
<q-btn @click="saveMarkers" color="primary">Save</q-btn>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <q-input type="text" v-bind="commonProps" v-model="place" label="Misto"/>-->
|
|
||||||
|
|
||||||
<q-list bordered v-if="false">
|
|
||||||
<q-item v-for="poi in pointsOfInterest" :key="poi">
|
|
||||||
<q-item-section>{{ poi.name }}</q-item-section>
|
|
||||||
<q-item-section v-if="poi.gpsLatitude">
|
|
||||||
<q-btn @click="addPoint(poi)">Přidat do trasy</q-btn>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item v-if="pointsOfInterest.length === 0 && place !== ''">
|
|
||||||
<q-btn @click="addPoint(null, place)">Najít {{ place }} na mapě</q-btn>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
|
|
||||||
<!-- <q-btn @click="searchPosition">Najit</q-btn>-->
|
|
||||||
|
|
||||||
<!-- <MapWithMarkers :markers="markers" />-->
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const routes = [
|
|||||||
|
|
||||||
{path: 'plany', name: "flightPlans", component: () => import('pages/profile/flightPlanner/FlightPlansPage.vue')},
|
{path: 'plany', name: "flightPlans", component: () => import('pages/profile/flightPlanner/FlightPlansPage.vue')},
|
||||||
{path: 'plany/novy', name: "newFlightPlan", component: () => import('pages/profile/flightPlanner/NewFlightPlanPage.vue')},
|
{path: 'plany/novy', name: "newFlightPlan", component: () => import('pages/profile/flightPlanner/NewFlightPlanPage.vue')},
|
||||||
{path: 'plany/:id', name: "flightPlan", component: () => import('pages/profile/flightPlanner/FlightPlanEditPage.vue')},
|
{path: 'plany/:id', name: "editFlightPlan", component: () => import('pages/profile/flightPlanner/FlightPlanEditPage.vue')},
|
||||||
|
|
||||||
{path: 'poi', name: "poi", component: () => import('pages/profile/poi/PointsOfInterestPage.vue')},
|
{path: 'poi', name: "poi", component: () => import('pages/profile/poi/PointsOfInterestPage.vue')},
|
||||||
{path: 'poi/novy', name: "addPOI", component: () => import('pages/profile/poi/NewPointOfInterestPage.vue')},
|
{path: 'poi/novy', name: "addPOI", component: () => import('pages/profile/poi/NewPointOfInterestPage.vue')},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
import {FLIGHT_PLANS_LIST} from "src/graphql/queries/flightPlan";
|
import {FLIGHT_PLAN_DETAIL, FLIGHT_PLANS_LIST} from "src/graphql/queries/flightPlan";
|
||||||
import {CREATE_FLIGHT_PLAN, EDIT_FLIGHT_PLAN} from "src/graphql/mutations/flightPlan";
|
import {CREATE_FLIGHT_PLAN, EDIT_FLIGHT_PLAN} from "src/graphql/mutations/flightPlan";
|
||||||
import {mutate, query, updateInItems} from "stores/helpers";
|
import {mutate, query, updateInItems} from "stores/helpers";
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
@@ -10,13 +10,13 @@ export const useFlightPlansStore = defineStore('FlightPlans', () => {
|
|||||||
const isMutationRunning = ref(false);
|
const isMutationRunning = ref(false);
|
||||||
const isQueryRunning = ref(false);
|
const isQueryRunning = ref(false);
|
||||||
|
|
||||||
// const fetchDetail = (id) => {
|
const fetchDetail = (id) => {
|
||||||
// return query({
|
return query({
|
||||||
// isQueryRunning,
|
isQueryRunning,
|
||||||
// query: FLIGHT_PLAN_DETAIL,
|
query: FLIGHT_PLAN_DETAIL,
|
||||||
// variables: {id},
|
variables: {id},
|
||||||
// }).then(item => flightPlan.value = item);
|
}).then(item => flightPlan.value = item);
|
||||||
// };
|
};
|
||||||
|
|
||||||
const fetch = () => {
|
const fetch = () => {
|
||||||
return query({
|
return query({
|
||||||
@@ -50,6 +50,6 @@ export const useFlightPlansStore = defineStore('FlightPlans', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
flightPlan, flightPlans, isMutationRunning, isQueryRunning,
|
flightPlan, flightPlans, isMutationRunning, isQueryRunning,
|
||||||
fetch, create, update
|
fetch, fetchDetail, create, update
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user