Vkladani mista na konkretni pozici

This commit is contained in:
Michal Kváček
2024-04-18 16:37:46 +02:00
parent de0b75163d
commit bc0b5e2f15
6 changed files with 132 additions and 64 deletions
+18 -16
View File
@@ -22,19 +22,22 @@ const props = defineProps({
pointsOfInterest: { pointsOfInterest: {
type: Array, type: Array,
required: true required: true
},
insertPosition: {
type: Number,
required: true
} }
}); });
const emits = defineEmits(["markers-changed"]); const emits = defineEmits(["markers-changed", "change-insert-position"]);
const {center} = useGeocode(); const {center} = useGeocode();
const {markerObject, googleMapsCustomMarkerObject, googleMapsMarkerObject, infoWindowObject, editableNameTypes} = useFlightPlanObjects(); const {markerObject, googleMapsMarkerObject, infoWindowObject, editableNameTypes} = useFlightPlanObjects();
const {getIcon, getMarkersFrom} = useFlightPlanUtils(); const {getMarkersFrom} = useFlightPlanUtils();
const infoWindow = ref(infoWindowObject({})); const infoWindow = ref(infoWindowObject({}));
const handleMapClick = ({clickCoordinates, airspaces, airport}) => { const handleMapClick = ({clickCoordinates, airspaces, airport}) => {
console.log(clickCoordinates, airspaces, airport);
infoWindow.value = infoWindowObject({active: true, airspaces, position: clickCoordinates, airport}) infoWindow.value = infoWindowObject({active: true, airspaces, position: clickCoordinates, airport})
} }
@@ -47,19 +50,23 @@ const handlePoiClick = (poi) => {
} }
const markers = ref([]); const markers = ref([]);
watch(() => props.flightPlan.track, (newVal) => { watch(() => props.flightPlan.track, (newVal) => {
markers.value = getMarkersFrom(props.flightPlan); markers.value = getMarkersFrom(props.flightPlan);
}, {immediate: true, deep: true}); }, {immediate: true, deep: true});
const track = computed(() => markers.value.map(m => ({lat: m.gpsLatitude, lng: m.gpsLongitude}))); const track = computed(() => markers.value.map(m => ({lat: m.gpsLatitude, lng: m.gpsLongitude})));
const addMarker = async (point) => { const addMarker = async (point) => {
if (!point) return; if (!point) return;
markers.value.push(markerObject({...point}));
const insertAt = Math.min(markers.value.length, props.insertPosition);
markers.value.splice(insertAt, 0, markerObject({...point}));
infoWindow.value = infoWindowObject({active: false});
emits("markers-changed", markers.value); emits("markers-changed", markers.value);
infoWindow.value = infoWindowObject({active: false}); emits("change-insert-position",insertAt + 1);
} }
const {searchPlaceName} = useGeocode(); const {searchPlaceName} = useGeocode();
@@ -74,12 +81,14 @@ const handleMarkerMove = async (idx, event) => {
</script> </script>
<template> <template>
vkladam na pozici {{ insertPosition }}
<MapWrapper <MapWrapper
style="width: 100%; height: 100%; min-height: 500px" style="width: 100%; height: 100%; min-height: 500px"
:center="center" :center="center"
:zoom="8" :zoom="8"
:airspaces-data="airspaceData" :airspaces-data="airspaceData"
:airports="airports" :airports="airports"
v-bind="$attrs"
@map-click="handleMapClick" @map-click="handleMapClick"
> >
<AirspaceInfoWindow <AirspaceInfoWindow
@@ -89,7 +98,7 @@ const handleMarkerMove = async (idx, event) => {
@add-to-route="addMarker" @add-to-route="addMarker"
/> />
<template v-for="poi in pointsOfInterest" :key="poi"> <template v-for="poi in pointsOfInterest" :key="poi.name">
<CustomMarker <CustomMarker
v-if="poi.gpsLatitude && poi.gpsLongitude" v-if="poi.gpsLatitude && poi.gpsLongitude"
:options="{position: {lat: poi.gpsLatitude, lng: poi.gpsLongitude}, anchorPoint: 'BOTTOM_CENTER'}" :options="{position: {lat: poi.gpsLatitude, lng: poi.gpsLongitude}, anchorPoint: 'BOTTOM_CENTER'}"
@@ -100,20 +109,13 @@ const handleMarkerMove = async (idx, event) => {
</template> </template>
<template v-for="(marker, i) in markers" :key="marker"> <template v-for="(marker, i) in markers" :key="marker.name">
<Marker <Marker
v-if="editableNameTypes.includes(marker.type)" v-if="editableNameTypes.includes(marker.type)"
:options="googleMapsMarkerObject(marker)" :options="googleMapsMarkerObject(marker)"
@click="handleMarkerClick(marker)" @click="handleMarkerClick(marker)"
@dragend="handleMarkerMove(i, $event.latLng)" @dragend="handleMarkerMove(i, $event.latLng)"
/> />
<!-- <CustomMarker-->
<!-- v-else-if="['poi', 'airport']marker.type !== 'poi'"-->
<!-- :options="googleMapsCustomMarkerObject(marker)"-->
<!-- @click="handleMarkerClick(marker)"-->
<!-- >-->
<!-- <q-icon :name="getIcon(marker.type)" style="font-size: 30px;color: #810000"/>-->
<!-- </CustomMarker>-->
</template> </template>
<Polyline :options="{path: track, geodesic: true, strokeColor: '#ff0202', strokeOpacity: 0.5, strokeWeight: 5}"/> <Polyline :options="{path: track, geodesic: true, strokeColor: '#ff0202', strokeOpacity: 0.5, strokeWeight: 5}"/>
</MapWrapper> </MapWrapper>
@@ -1,5 +1,5 @@
<script setup> <script setup>
import {computed} from "vue"; import {computed, ref} from "vue";
import {minutesToHumanReadable} from "src/composables/datetimeUtils"; import {minutesToHumanReadable} from "src/composables/datetimeUtils";
import {useFlightPlanUtils} from "src/composables/flightPlan"; import {useFlightPlanUtils} from "src/composables/flightPlan";
@@ -12,12 +12,15 @@ const props = defineProps({
type: Number, type: Number,
required: false, required: false,
default: 150 default: 150
},
insertPosition: {
type: Number,
required: true
} }
}); });
const emits = defineEmits(["open"]) const emits = defineEmits(["open", "change-insert-position"])
const {getIcon} = useFlightPlanUtils();
const getDuration = (distance, speed) => Math.round(distance / speed * 60) const getDuration = (distance, speed) => Math.round(distance / speed * 60)
@@ -31,30 +34,73 @@ const totalTime = computed(() => {
return props.flightPlan.track.map(t => getDuration(t.distance, props.plannedSpeed)).reduce((a, b) => a + b) return props.flightPlan.track.map(t => getDuration(t.distance, props.plannedSpeed)).reduce((a, b) => a + b)
}); });
</script> </script>
<template> <template>
<q-list bordered class="q-ml-md-md q-pt-md"> <q-list bordered class="q-ml-md-md q-pt-md">
<q-item>
<q-item-section>
<div>
<q-btn
@click="emits('change-insert-position', 0)"
icon="add"
:color="insertPosition === 0 ? 'primary' : 'grey'"
size="sm"
>
<template v-if="insertPosition === 0">Přidávám na začátek</template>
</q-btn>
</div>
</q-item-section>
</q-item>
<template v-for="(segment, i) in props.flightPlan.track" :key="segment"> <template v-for="(segment, i) in props.flightPlan.track" :key="segment">
<q-item dense> <q-item dense>
<q-item-section> <q-item-section>
<q-item-label class="text-h6"> <q-item-label>{{ segment.from.name }}</q-item-label>
<q-icon :name="getIcon(segment.from.type)"/> <q-item-label caption class="q-py-sm" v-if="segment.distance">
{{ segment.from.name }}
</q-item-label>
<q-item-section class="q-py-lg text-grey-8" v-if="segment.distance">
{{ segment.bearing }}°, {{ segment.bearing }}°,
{{ segment.distance }} km, {{ segment.distance }} km,
{{ minutesToHumanReadable(getDuration(segment.distance, props.plannedSpeed)) }} {{ minutesToHumanReadable(getDuration(segment.distance, props.plannedSpeed)) }}
</q-item-section> </q-item-label>
<q-item-label class="text-h6" v-if="segment.to && i === props.flightPlan.track.length - 1"> <q-item-label>
<q-icon :name="getIcon(segment.to.type)"/> <q-btn
@click="emits('change-insert-position', i+1)"
icon="add"
size="sm"
class="q-ml-lg q-mb-sm"
:round="insertPosition !== i+1"
:color="insertPosition === i+1 ? 'primary' : 'grey'"
>
<template v-if="insertPosition === i+1">Přidávám sem</template>
</q-btn>
</q-item-label>
</q-item-section>
</q-item>
<q-item dense v-if="segment.to && i === props.flightPlan.track.length - 1">
<q-item-section>
<q-item-label>
{{ segment.to.name }} {{ segment.to.name }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</template> </template>
<q-item>
<q-item-section>
<div>
<q-btn
@click="emits('change-insert-position', flightPlan.track.length + 1)"
icon="add"
size="sm"
:color="insertPosition === flightPlan.track.length + 1? 'primary' : 'grey'"
>
<template v-if="insertPosition === flightPlan.track.length + 1">Přidávám na konec</template>
</q-btn>
</div>
</q-item-section>
</q-item>
<template v-if="totalDistance"> <template v-if="totalDistance">
<q-item> <q-item>
<q-item-section> <q-item-section>
+40 -32
View File
@@ -17,14 +17,19 @@ const props = defineProps({
type: Array, type: Array,
required: false, required: false,
default: () => [] default: () => []
},
showLayerControls: {
type: Boolean,
required: false,
default: true
} }
}); });
const emits = defineEmits(["map-click"]); const emits = defineEmits(["map-click"]);
const key = process.env.GOOGLE_MAPS_API_KEY; const key = process.env.GOOGLE_MAPS_API_KEY;
const zoomLevel = ref(null);
const mapRef = ref(null); const mapRef = ref(null);
watch( watch(
() => mapRef.value?.ready, () => mapRef.value?.ready,
@@ -38,6 +43,12 @@ watch(
} }
mapRef.value.map.fitBounds(bounds); mapRef.value.map.fitBounds(bounds);
} }
zoomLevel.value = mapRef.value.map.getZoom();
mapRef.value.map.addListener("zoom_changed", () => {
console.log("zoom changed");
zoomLevel.value = mapRef.value.map.getZoom();
})
} }
); );
@@ -45,14 +56,14 @@ watch(
const displayAirspaceLayers = ref({ const displayAirspaceLayers = ref({
CTR: true, CTR: true,
TMA: true, TMA: true,
RMZ: true, RMZ: false,
ATZ: true, ATZ: true,
DANGER: true, DANGER: false,
PROHIBIT: true, PROHIBIT: true,
RESTRICT: true, RESTRICT: true,
TRA: true,
TRAGA: true, TRAGA: true,
AIRPORTS: true AIRPORTS: true,
FIELDSTRIP: false,
}); });
const airspaces = computed(() => { const airspaces = computed(() => {
@@ -66,7 +77,8 @@ const airspaces = computed(() => {
Q: "#FFA500", Q: "#FFA500",
PGZ: "#008000", PGZ: "#008000",
GSEC: "#008000", GSEC: "#008000",
ATZ: "#4e7cfc" ATZ: "#4e7cfc",
FIR: '#ffffff'
} }
return props.airspacesData return props.airspacesData
@@ -79,6 +91,7 @@ const airspaces = computed(() => {
if (airspace.type === 'Q') return displayAirspaceLayers.value.DANGER; if (airspace.type === 'Q') return displayAirspaceLayers.value.DANGER;
if (airspace.type === 'P') return displayAirspaceLayers.value.PROHIBIT; if (airspace.type === 'P') return displayAirspaceLayers.value.PROHIBIT;
if (airspace.type === 'R') return displayAirspaceLayers.value.RESTRICT; if (airspace.type === 'R') return displayAirspaceLayers.value.RESTRICT;
if (airspace.type === 'ATZ') return displayAirspaceLayers.value.ATZ;
return true; return true;
}) })
.map(airspace => { .map(airspace => {
@@ -103,9 +116,9 @@ const airspaces = computed(() => {
}); });
const airportsOnMap = computed(() => { const airportsOnMap = computed(() => {
if (!displayAirspaceLayers.value.AIRPORTS) return []; if (!displayAirspaceLayers.value.AIRPORTS && !displayAirspaceLayers.value.FIELDSTRIP) return [];
return props.airports.filter(a => a.gpsLatitude && a.gpsLongitude); return props.airports.filter(a => a.gpsLatitude && a.gpsLongitude).filter(a => a.icaoCode.length === 6 ? displayAirspaceLayers.value.FIELDSTRIP : displayAirspaceLayers.value.AIRPORTS);
}); });
const {isPointInPolygon, vectorDistanceToKm} = useVectorUtilities(); const {isPointInPolygon, vectorDistanceToKm} = useVectorUtilities();
@@ -137,8 +150,6 @@ const handleAirportClick = (airport) => {
const position = {lat: airport.gpsLatitude, lng: airport.gpsLongitude}; const position = {lat: airport.gpsLatitude, lng: airport.gpsLongitude};
emits("map-click", {clickCoordinates: position, airport}) emits("map-click", {clickCoordinates: position, airport})
} }
</script> </script>
<template> <template>
@@ -147,27 +158,13 @@ const handleAirportClick = (airport) => {
v-bind="$attrs" v-bind="$attrs"
:street-view-control="false" :street-view-control="false"
@click="handleMapClick" @click="handleMapClick"
map-type-id="terrain" map-type-id="roadmap"
:max-zoom="13" :max-zoom="13"
ref="mapRef" ref="mapRef"
> >
<CustomControl position="BOTTOM_CENTER" class="text-center"> <template v-for="airspace in airspaces" :key="airspace.name">
<q-card class="q-my-lg q-px-lg">
<q-checkbox v-model="displayAirspaceLayers.CTR" label="M/CTR"/>
<q-checkbox v-model="displayAirspaceLayers.TMA" label="TMA"/>
<q-checkbox v-model="displayAirspaceLayers.RMZ" label="RMZ"/>
<q-checkbox v-model="displayAirspaceLayers.ATZ" label="ATZ"/>
<q-checkbox v-model="displayAirspaceLayers.DANGER" label="Nebezpečné prostory"/>
<q-checkbox v-model="displayAirspaceLayers.PROHIBIT" label="Zakázané prostory"/>
<q-checkbox v-model="displayAirspaceLayers.RESTRICT" label="Omezené prostory (TRA)"/>
<q-checkbox v-model="displayAirspaceLayers.TRAGA" label="TRAGA"/>
<q-checkbox v-model="displayAirspaceLayers.AIRPORTS" label="Letiště"/>
</q-card>
</CustomControl>
<template v-for="airspace in airspaces" :key="airspace">
<Polygon :options="airspace" @click=handleMapClick v-if="airspace.path"/> <Polygon :options="airspace" @click=handleMapClick v-if="airspace.path"/>
<Circle :options="airspace" @click=handleMapClick v-if="airspace.center && airspace.radius"/> <Circle :options="airspace" @click=handleMapClick v-if="airspace.center && airspace.radius"/>
</template> </template>
<template v-for="airport in airportsOnMap" :key="airport.icaoCode"> <template v-for="airport in airportsOnMap" :key="airport.icaoCode">
@@ -175,15 +172,26 @@ const handleAirportClick = (airport) => {
:options="{position: {lat: airport.gpsLatitude, lng: airport.gpsLongitude}, anchorPoint: 'CENTER'}" :options="{position: {lat: airport.gpsLatitude, lng: airport.gpsLongitude}, anchorPoint: 'CENTER'}"
@click.stop="handleAirportClick(airport)" @click.stop="handleAirportClick(airport)"
> >
<div class="text-center"> <q-chip size="xs">{{ airport.icaoCode }}</q-chip>
<q-icon name="local_airport" style="font-size: 24px" class="text-primary"/>
<br>
{{ airport.icaoCode }}
</div>
</CustomMarker> </CustomMarker>
</template> </template>
<slot/> <slot/>
<CustomControl position="BOTTOM_CENTER" class="text-center" v-if="showLayerControls">
<q-card class="q-my-md q-mx-auto q-px-lg" style="max-width: 98%;">
<q-checkbox v-model="displayAirspaceLayers.CTR" label="M/CTR"/>
<q-checkbox v-model="displayAirspaceLayers.TMA" label="TMA"/>
<q-checkbox v-model="displayAirspaceLayers.RMZ" label="RMZ"/>
<q-checkbox v-model="displayAirspaceLayers.ATZ" label="ATZ"/>
<q-checkbox v-model="displayAirspaceLayers.DANGER" label="Nebezpečné prostory"/>
<q-checkbox v-model="displayAirspaceLayers.PROHIBIT" label="Zakázané prostory"/>
<q-checkbox v-model="displayAirspaceLayers.RESTRICT" label="TRA"/>
<q-checkbox v-model="displayAirspaceLayers.TRAGA" label="TRAGA"/>
<q-checkbox v-model="displayAirspaceLayers.AIRPORTS" label="Letiště"/>
<q-checkbox v-model="displayAirspaceLayers.FIELDSTRIP" label="SLZ plochy"/>
</q-card>
</CustomControl>
</GoogleMap> </GoogleMap>
</template> </template>
@@ -29,6 +29,7 @@ import PublishButton from "components/inputs/PublishButton.vue";
import CopyLinkButton from "components/inputs/CopyLinkButton.vue"; import CopyLinkButton from "components/inputs/CopyLinkButton.vue";
import {TOGGLE_FLIGHT_PUBLISH} from "src/graphql/mutations/flight"; import {TOGGLE_FLIGHT_PUBLISH} from "src/graphql/mutations/flight";
import {useAirspacesStore} from "stores/Airspaces"; import {useAirspacesStore} from "stores/Airspaces";
import {useAirportsStore} from "stores/Airports";
const flightId = parseInt(useRoute().params.id); const flightId = parseInt(useRoute().params.id);
const loggedUser = storeToRefs(useLoggedUserStore()).user; const loggedUser = storeToRefs(useLoggedUserStore()).user;
@@ -53,9 +54,11 @@ const handleDeleteAndRedirect = () => {
}); });
}; };
const {airspaces: airspacesData} = storeToRefs(useAirspacesStore()); const {airspaces} = storeToRefs(useAirspacesStore());
const {airports} = storeToRefs(useAirportsStore());
onMounted(() => { onMounted(() => {
useAirspacesStore().fetch(); useAirspacesStore().fetch();
useAirportsStore().fetch();
}); });
const togglePublicVisibility = (variables) => toggleVisibility({ const togglePublicVisibility = (variables) => toggleVisibility({
@@ -147,7 +150,8 @@ const togglePublicVisibility = (variables) => toggleVisibility({
<q-no-ssr v-else> <q-no-ssr v-else>
<FlightTrackMap <FlightTrackMap
:airspaces-data="airspacesData" :airspaces-data="airspaces"
:airports="airports"
:flight="flight" :flight="flight"
:photos="flight.photos" :photos="flight.photos"
/> />
@@ -77,7 +77,7 @@ const handleMarkersChanges = (markers) => {
}; };
const handleFlightPlanInfoSave = () => useFlightPlansStore().update(flightPlan.value.id, formData.value); const handleFlightPlanInfoSave = () => useFlightPlansStore().update(flightPlan.value.id, formData.value);
const insertAtPosition = ref(flightPlanMarkers.value.length);
</script> </script>
<template> <template>
@@ -98,16 +98,20 @@ const handleFlightPlanInfoSave = () => useFlightPlansStore().update(flightPlan.v
<div class="row"> <div class="row">
<div class="col" style="min-width: 1px"> <div class="col" style="min-width: 1px">
<FlightPlanMap <FlightPlanMap
:insert-position="insertAtPosition"
:airspace-data="airspacesData" :airspace-data="airspacesData"
:airports="airports" :airports="airports"
:points-of-interest="pointsOfInterest" :points-of-interest="pointsOfInterest"
:flight-plan="flightPlan" :flight-plan="flightPlan"
@change-insert-position="insertAtPosition = $event"
@markers-changed="handleMarkersChanges" @markers-changed="handleMarkersChanges"
/> />
</div> </div>
<div class="col-12 col-md-5 col-lg-3"> <div class="col-12 col-md-5 col-lg-3">
<FlightPlanMarkersList <FlightPlanMarkersList
:insert-position="insertAtPosition"
:flight-plan="flightPlan" :flight-plan="flightPlan"
@change-insert-position="insertAtPosition = $event"
@open="flightPlannerDialogOpen=true" @open="flightPlannerDialogOpen=true"
/> />
</div> </div>
@@ -77,7 +77,11 @@ useMeta(() => metaData({
<q-no-ssr> <q-no-ssr>
<q-inner-loading v-if="arePhotosLoading"/> <q-inner-loading v-if="arePhotosLoading"/>
<PhotoGallery :photos="flightPhotos" v-else-if="!arePhotosLoading && flightPhotos.length > 0"/> <PhotoGallery :photos="flightPhotos" v-else-if="!arePhotosLoading && flightPhotos.length > 0"/>
<FlightTrackMap :key="flight.id" :flight="flight" :photos="flightPhotos"/> <FlightTrackMap
:key="flight.id"
:flight="flight"
:showLayerControls="false"
:photos="flightPhotos"/>
</q-no-ssr> </q-no-ssr>
</q-card-section> </q-card-section>
</template> </template>