Planovac, zobrazeni vzdusnych prostoru
This commit is contained in:
@@ -110,6 +110,7 @@ const chartOptions = {
|
|||||||
:markers="pointsOfInterests"
|
:markers="pointsOfInterests"
|
||||||
:highlight-position="currentPositionMarker"
|
:highlight-position="currentPositionMarker"
|
||||||
:photos="mapPhotos"
|
:photos="mapPhotos"
|
||||||
|
v-bind="$attrs"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Line v-if="gpxTrack" :data="chartSpeedData" :options="chartOptions"/>
|
<Line v-if="gpxTrack" :data="chartSpeedData" :options="chartOptions"/>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {Marker} from "vue3-google-map";
|
import {Marker} from "vue3-google-map";
|
||||||
import {onMounted, ref} from "vue";
|
import {defineEmits, onMounted, ref} from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import MapWrapper from "components/map/MapWrapper.vue";
|
import MapWrapper from "components/map/MapWrapper.vue";
|
||||||
|
import {useGeocode} from "src/composables/geocoding";
|
||||||
const center = ref({
|
|
||||||
lat: 50.1314,
|
|
||||||
lng: 14.5256
|
|
||||||
});
|
|
||||||
const zoom = ref(10);
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
gpsLatitude: {
|
gpsLatitude: {
|
||||||
@@ -24,29 +19,13 @@ const props = defineProps({
|
|||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emits = defineEmits(["update:gpsLatitude", "update:gpsLongitude", "notFound"]);
|
const emits = defineEmits(["update:gpsLatitude", "update:gpsLongitude", "notFound"]);
|
||||||
const emitModels = (lat, lng) => {
|
|
||||||
emits("update:gpsLatitude", lat);
|
const {emitModels, setCoordinatesFromGeocode, center, zoom} = useGeocode(emits);
|
||||||
emits("update:gpsLongitude", lng);
|
|
||||||
}
|
|
||||||
const markerDropped = (position) => {
|
const markerDropped = (position) => {
|
||||||
emitModels(position.latLng.lat(), position.latLng.lng());
|
emitModels(position.latLng.lat(), position.latLng.lng());
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCoordinatesFromGeocode = (address) => {
|
|
||||||
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(address)}&key=${process.env.GOOGLE_MAPS_API_KEY}`).then(response => {
|
|
||||||
if (response.data.status !== 'OK') {
|
|
||||||
emits("notFound", props.label);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
center.value = response.data.results[0].geometry.location;
|
|
||||||
emitModels(center.value.lat, center.value.lng);
|
|
||||||
zoom.value = 13;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.gpsLatitude && props.gpsLongitude) {
|
if (props.gpsLatitude && props.gpsLongitude) {
|
||||||
center.value.lng = props.gpsLongitude;
|
center.value.lng = props.gpsLongitude;
|
||||||
@@ -61,7 +40,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MapWrapper style="width: 100%; height: 500px" :center="center" :zoom="zoom">
|
<MapWrapper style="width: 100%; height: 500px" :center="center" :zoom="zoom" v-bind="$attrs">
|
||||||
<Marker :options="{ position: center, title: label, draggable: true }" @dragend="markerDropped"/>
|
<Marker :options="{ position: center, title: label, draggable: true }" @dragend="markerDropped"/>
|
||||||
</MapWrapper>
|
</MapWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -85,16 +85,15 @@ const getColor = (i) => {
|
|||||||
</Marker>
|
</Marker>
|
||||||
|
|
||||||
<template v-if="gpxTrack && gpxTrack.length > 0">
|
<template v-if="gpxTrack && gpxTrack.length > 0">
|
||||||
<Polyline :options="{path: gpxTrack, geodesic: true, strokeColor: '#ff0000', strokeOpacity: 0.3, strokeWeight: 5}"/>
|
<Polyline :options="{path: gpxTrack, geodesic: true, strokeColor: getColor(0), strokeOpacity: 0.5, strokeWeight: 5}"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="tracks && tracks.length > 0">
|
<template v-if="tracks && tracks.length > 0">
|
||||||
<template v-for="(track, i) in tracks" :key="track">
|
<template v-for="(track, i) in tracks" :key="track">
|
||||||
<Polyline :options="{path: track, geodesic: false, strokeColor: getColor(i), strokeOpacity: 1, strokeWeight: 3}"/>
|
<Polyline :options="{path: track, geodesic: true, strokeColor: getColor(i), strokeOpacity: 0.5, strokeWeight: 5}"/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Marker v-if="highlightPosition" :options="{position: highlightPosition, icon: '/mapIcons/currentPosition32x32.png'}"/>
|
<Marker v-if="highlightPosition" :options="{position: highlightPosition, icon: '/mapIcons/currentPosition32x32.png'}"/>
|
||||||
|
|
||||||
</MapWrapper>
|
</MapWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,37 +1,132 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {GoogleMap} from "vue3-google-map";
|
import {Circle, GoogleMap, Marker, Polygon} from "vue3-google-map";
|
||||||
import {ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
bounds: {
|
bounds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false
|
required: false
|
||||||
}
|
},
|
||||||
|
airspacesData: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = process.env.GOOGLE_MAPS_API_KEY;
|
const key = process.env.GOOGLE_MAPS_API_KEY;
|
||||||
|
|
||||||
|
|
||||||
const mapRef = ref(null);
|
const mapRef = ref(null);
|
||||||
watch(
|
watch(
|
||||||
() => mapRef.value?.ready,
|
() => mapRef.value?.ready,
|
||||||
(ready) => {
|
(ready) => {
|
||||||
if (!props.bounds || !ready) return;
|
if (!ready) return;
|
||||||
const map = mapRef.value.map;
|
|
||||||
const api = mapRef.value.api;
|
|
||||||
|
|
||||||
const bounds = new api.LatLngBounds();
|
if (props.bounds) {
|
||||||
|
const bounds = new mapRef.value.api.LatLngBounds();
|
||||||
for (let i = 0; i < props.bounds.length; i++) {
|
for (let i = 0; i < props.bounds.length; i++) {
|
||||||
bounds.extend(props.bounds[i]);
|
bounds.extend(props.bounds[i]);
|
||||||
}
|
}
|
||||||
map.fitBounds(bounds);
|
mapRef.value.map.fitBounds(bounds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const airspaces = computed(() => {
|
||||||
|
const colorsMap = {
|
||||||
|
C: "#800080",
|
||||||
|
D: "#800080",
|
||||||
|
CTR: "#00ff00",
|
||||||
|
P: "#FF0000",
|
||||||
|
RMZ: "#FFdd00",
|
||||||
|
R: "#ff6a00",
|
||||||
|
Q: "#FFA500",
|
||||||
|
PGZ: "#008000",
|
||||||
|
GSEC: "#008000"
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.airspacesData
|
||||||
|
.filter(airspace => !!colorsMap[airspace.type])
|
||||||
|
.map(airspace => {
|
||||||
|
const data = {
|
||||||
|
name: airspace.name,
|
||||||
|
strokeColor: colorsMap[airspace.type],
|
||||||
|
strokeOpacity: 0.2,
|
||||||
|
strokeWeight: 3,
|
||||||
|
fillColor: colorsMap[airspace.type],
|
||||||
|
fillOpacity: 0.1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (airspace.mapBounds.length > 0) {
|
||||||
|
data['path'] = airspace.mapBounds;
|
||||||
|
} else {
|
||||||
|
data['center'] = airspace.mapCenter;
|
||||||
|
data['radius'] = airspace.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const atzs = computed(() => {
|
||||||
|
return props.airspacesData
|
||||||
|
.filter(a => a.type === 'ATZ')
|
||||||
|
.filter(a => !!a.mapCenter)
|
||||||
|
.map(airspace => ({
|
||||||
|
name: airspace.name,
|
||||||
|
strokeColor: "#4e7cfc",
|
||||||
|
strokeOpacity: 0.2,
|
||||||
|
strokeWeight: 1,
|
||||||
|
fillColor: "#4e7cfc",
|
||||||
|
fillOpacity: 0.1,
|
||||||
|
center: airspace.mapCenter,
|
||||||
|
radius: airspace.radius,
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: pridat letiste
|
||||||
|
|
||||||
|
function isPointInPolygon(clickPoint, polygonCoords) {
|
||||||
|
var count = 0;
|
||||||
|
var x = clickPoint.lng, y = clickPoint.lat;
|
||||||
|
|
||||||
|
for (var i = 0; i < polygonCoords.length; i++) {
|
||||||
|
var iNext = (i + 1) % polygonCoords.length;
|
||||||
|
|
||||||
|
var xi = polygonCoords[i].lng, yi = polygonCoords[i].lat;
|
||||||
|
var xiNext = polygonCoords[iNext].lng, yiNext = polygonCoords[iNext].lat;
|
||||||
|
|
||||||
|
var 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ř
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<GoogleMap :api-key="key" v-bind="$attrs" :max-zoom="13" ref="mapRef">
|
<GoogleMap :api-key="key" v-bind="$attrs" map-type-id="terrain" :max-zoom="13" ref="mapRef">
|
||||||
<slot/>
|
<slot/>
|
||||||
|
|
||||||
|
<template v-for="airspace in airspaces" :key="airspace">
|
||||||
|
<Polygon :options="airspace" @click=showInfo v-if="airspace.path"/>
|
||||||
|
<Circle :options="airspace" @click=showInfo v-if="airspace.center && airspace.radius"/>
|
||||||
|
</template>
|
||||||
|
<Circle :options="atz" v-for="atz in atzs" @click=showInfo :key="atz"/>
|
||||||
</GoogleMap>
|
</GoogleMap>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {ref} from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const useGeocode = (emits) => {
|
||||||
|
|
||||||
|
const center = ref({
|
||||||
|
lat: 50.1314,
|
||||||
|
lng: 14.5256
|
||||||
|
});
|
||||||
|
const zoom = ref(10);
|
||||||
|
|
||||||
|
const emitModels = (lat, lng) => {
|
||||||
|
emits("update:gpsLatitude", lat);
|
||||||
|
emits("update:gpsLongitude", lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchGeocode = (address) => {
|
||||||
|
return axios.get(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(address)}&key=${process.env.GOOGLE_MAPS_API_KEY}`).then(response => {
|
||||||
|
if (response.data.status !== 'OK') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data.results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCoordinatesFromGeocode = (address) => {
|
||||||
|
// TODO: odstranit a nahradit za searchGeocode! Vlastne cela tato composable by sla nahradit
|
||||||
|
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURI(address)}&key=${process.env.GOOGLE_MAPS_API_KEY}`).then(response => {
|
||||||
|
if (response.data.status !== 'OK') {
|
||||||
|
emits("notFound", address);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
center.value = response.data.results[0].geometry.location;
|
||||||
|
emitModels(center.value.lat, center.value.lng);
|
||||||
|
zoom.value = 13;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {emitModels, setCoordinatesFromGeocode, zoom, center, searchGeocode}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ fragment pointOfInterestListInfo on PointOfInterest {
|
|||||||
name
|
name
|
||||||
urlSlug
|
urlSlug
|
||||||
isPublic
|
isPublic
|
||||||
|
gpsLatitude
|
||||||
|
gpsLongitude
|
||||||
titlePhoto {
|
titlePhoto {
|
||||||
thumbnailUrl
|
thumbnailUrl
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export const AIRPORTS_LIST = gql`
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
icaoCode
|
icaoCode
|
||||||
|
gpsLatitude
|
||||||
|
gpsLongitude
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
// TODO: dodelat filtrovani podle typu a zobrazene zeme?
|
||||||
|
export const AIRSPACE_LIST = gql`
|
||||||
|
query AirspaceList {
|
||||||
|
airspaces {
|
||||||
|
name
|
||||||
|
type
|
||||||
|
mapCenter {
|
||||||
|
lat
|
||||||
|
lng
|
||||||
|
}
|
||||||
|
radius
|
||||||
|
mapBounds {
|
||||||
|
lat
|
||||||
|
lng
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -3,14 +3,14 @@ import {useFlightsStore} from "stores/Flights";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
preFetch({currentRoute}) {
|
preFetch({currentRoute}) {
|
||||||
useFlightsStore().fetchDetail(currentRoute.params.id);
|
return useFlightsStore().fetchDetail(currentRoute.params.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import {watch} from "vue";
|
import {onMounted, watch} from "vue";
|
||||||
import FlightDetailSkeleton from "components/flights/skeletons/FlightDetailSkeleton.vue";
|
import FlightDetailSkeleton from "components/flights/skeletons/FlightDetailSkeleton.vue";
|
||||||
import {flightName} from "src/composables/flight";
|
import {flightName} from "src/composables/flight";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
@@ -28,20 +28,18 @@ import {toggleVisibility} from "src/composables/form/formHandling";
|
|||||||
import PublishButton from "components/inputs/PublishButton.vue";
|
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";
|
||||||
|
|
||||||
const flightId = parseInt(useRoute().params.id);
|
const flightId = parseInt(useRoute().params.id);
|
||||||
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
const loggedUser = storeToRefs(useLoggedUserStore()).user;
|
||||||
|
|
||||||
const {isQueryRunning, flight} = storeToRefs(useFlightsStore());
|
const {isQueryRunning, flight} = storeToRefs(useFlightsStore());
|
||||||
watch(() => flight.value, (f) => {
|
const flightNameFormatted = flightName(flight.value);
|
||||||
if (!f) return;
|
|
||||||
const flightNameFormatted = flightName(f);
|
|
||||||
useAppStore().setPageTitle(flightNameFormatted);
|
useAppStore().setPageTitle(flightNameFormatted);
|
||||||
useAppStore().setBreadcrumbs([
|
useAppStore().setBreadcrumbs([
|
||||||
{to: {name: "myFlights"}, label: "Moje lety"},
|
{to: {name: "myFlights"}, label: "Moje lety"},
|
||||||
{label: flightNameFormatted}
|
{label: flightNameFormatted}
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const $router = useRouter();
|
const $router = useRouter();
|
||||||
@@ -53,7 +51,12 @@ const handleDeleteAndRedirect = () => {
|
|||||||
$q.notify(successObject(`Úspěšně smazáno`));
|
$q.notify(successObject(`Úspěšně smazáno`));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const {airspaces: airspacesData} = storeToRefs(useAirspacesStore());
|
||||||
|
onMounted(() => {
|
||||||
|
useAirspacesStore().fetch();
|
||||||
|
});
|
||||||
|
|
||||||
const togglePublicVisibility = (variables) => toggleVisibility({
|
const togglePublicVisibility = (variables) => toggleVisibility({
|
||||||
$q, store: useFlightsStore(), object: flight.value, mutation: TOGGLE_FLIGHT_PUBLISH, variables
|
$q, store: useFlightsStore(), object: flight.value, mutation: TOGGLE_FLIGHT_PUBLISH, variables
|
||||||
@@ -144,6 +147,7 @@ const togglePublicVisibility = (variables) => toggleVisibility({
|
|||||||
|
|
||||||
<q-no-ssr v-else>
|
<q-no-ssr v-else>
|
||||||
<FlightTrackMap
|
<FlightTrackMap
|
||||||
|
:airspaces-data="airspacesData"
|
||||||
:flight="flight"
|
:flight="flight"
|
||||||
:photos="flight.photos"
|
:photos="flight.photos"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,34 +1,189 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import MapWithMarkers from "components/map/MapWithMarkers.vue";
|
import {computed, defineEmits, onMounted, ref, watch} from "vue";
|
||||||
import {onMounted, ref, watch} from "vue";
|
|
||||||
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
import {usePointsOfInterestStore} from "stores/PointsOfInterest";
|
||||||
import ComboboxInput from "components/inputs/ComboboxInput.vue";
|
|
||||||
import {storeToRefs} from "pinia";
|
import {storeToRefs} from "pinia";
|
||||||
import {debounce} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
import {commonProps} from "src/composables/inputs";
|
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";
|
||||||
|
|
||||||
const markers = ref([]);
|
|
||||||
|
|
||||||
const place = ref("");
|
const place = ref("");
|
||||||
|
|
||||||
const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
|
const {pointsOfInterest} = storeToRefs(usePointsOfInterestStore());
|
||||||
// onMounted(() => {
|
const {airspaces: airspacesData, areAirspacesLoading: isQueryRunning} = storeToRefs(useAirspacesStore())
|
||||||
// usePointsOfInterestStore().fetch({});
|
|
||||||
// })
|
|
||||||
|
|
||||||
watch(() => place.value, (newVal) => {
|
const $q = useQuasar();
|
||||||
usePointsOfInterestStore().fetch({search: newVal});
|
|
||||||
|
onMounted(() => {
|
||||||
|
useAirspacesStore().fetch();
|
||||||
|
|
||||||
|
usePointsOfInterestStore().fetch({limit: 10000});
|
||||||
|
useAirportsStore().fetch()
|
||||||
|
});
|
||||||
|
|
||||||
|
const markers = ref([]);
|
||||||
|
watch(() => markers.value, (newVal) => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const addPoint = async (point) => {
|
||||||
|
markers.value.push({
|
||||||
|
label: point.name,
|
||||||
|
type: point.type,
|
||||||
|
position: {
|
||||||
|
lat: point.gpsLatitude,
|
||||||
|
lng: point.gpsLongitude
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const track = computed(() => {
|
||||||
|
return markers.value.map(m => m.position);
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchPosition = () => {
|
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 => ({
|
||||||
|
name: poi.name,
|
||||||
|
type: "poi",
|
||||||
|
id: poi.id,
|
||||||
|
gpsLatitude: poi.gpsLatitude,
|
||||||
|
gpsLongitude: poi.gpsLongitude
|
||||||
|
})),
|
||||||
|
...airports.value.map(airport => ({
|
||||||
|
name: `${airport.icaoCode} - ${airport.name}`,
|
||||||
|
type: "airport",
|
||||||
|
id: airport.id,
|
||||||
|
gpsLatitude: airport.gpsLatitude,
|
||||||
|
gpsLongitude: airport.gpsLongitude
|
||||||
|
}))
|
||||||
|
].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;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<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="marker"/>
|
||||||
|
</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>
|
||||||
|
|
||||||
<ComboboxInput :options="pointsOfInterest" v-bind="commonProps" :v-model="place" label="Misto" />
|
<q-item-section middle side>
|
||||||
<q-btn @click="searchPosition">Najit</q-btn>
|
<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" />-->
|
||||||
|
|
||||||
<MapWithMarkers :markers="markers" />
|
<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-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>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {AIRSPACE_LIST} from "src/graphql/queries/airspace";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {query} from "stores/helpers";
|
||||||
|
|
||||||
|
export const useAirspacesStore = defineStore('Airspaces', () => {
|
||||||
|
const airspaces = ref([])
|
||||||
|
const isQueryRunning = ref(false);
|
||||||
|
|
||||||
|
const fetch = () => {
|
||||||
|
return query({
|
||||||
|
isQueryRunning,
|
||||||
|
query: AIRSPACE_LIST,
|
||||||
|
}).then(items => airspaces.value = items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {airspaces, isQueryRunning, fetch};
|
||||||
|
})
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {FLIGHT_PLAN_DETAIL, FLIGHT_PLAN_PUBLIC_DETAIL, FLIGHT_PLAN_LIST} from "src/graphql/queries/flightPlans";
|
||||||
|
import {CREATE_FLIGHT_PLAN, EDIT_FLIGHT_PLAN} from "src/graphql/mutations/flightPlan";
|
||||||
|
import {mutate, query, updateInItems} from "stores/helpers";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
|
export const useFlightPlansStore = defineStore('FlightPlans', () => {
|
||||||
|
const flightPlan = ref(null);
|
||||||
|
const flightPlans = ref([]);
|
||||||
|
const isMutationRunning = ref(false);
|
||||||
|
const isQueryRunning = ref(false);
|
||||||
|
|
||||||
|
const fetchDetail = (id) => {
|
||||||
|
return query({
|
||||||
|
isQueryRunning,
|
||||||
|
query: FLIGHT_PLAN_DETAIL,
|
||||||
|
variables: {id},
|
||||||
|
}).then(item => flightPlan.value = item);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchPublicDetail = ({urlSlug, username, uploadToken, uploadFlightSlug}) => {
|
||||||
|
return query({
|
||||||
|
isQueryRunning,
|
||||||
|
query: FLIGHT_PLAN_PUBLIC_DETAIL,
|
||||||
|
variables: {urlSlug, pilotUsername: username, uploadToken, uploadFlightSlug},
|
||||||
|
}).then(item => {
|
||||||
|
flightPlan.value = item;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetch = () => {
|
||||||
|
return query({
|
||||||
|
isQueryRunning,
|
||||||
|
query: FLIGHT_PLANS_LIST,
|
||||||
|
}).then(items => flightPlans.value = items);
|
||||||
|
}
|
||||||
|
|
||||||
|
const create = (input) => {
|
||||||
|
return mutate({
|
||||||
|
isMutationRunning,
|
||||||
|
mutation: CREATE_FLIGHT_PLAN,
|
||||||
|
variables: {input},
|
||||||
|
}).then(item => {
|
||||||
|
flightPlans.value.push(item);
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = (id, input) => {
|
||||||
|
return mutate({
|
||||||
|
isMutationRunning,
|
||||||
|
mutation: EDIT_FLIGHT_PLAN,
|
||||||
|
variables: {id, input}
|
||||||
|
}).then(item => {
|
||||||
|
updateInItems(flightPlans.value, 'id', item);
|
||||||
|
flightPlan.value = item;
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flightPlan, flightPlans, isMutationRunning, isQueryRunning,
|
||||||
|
fetch, fetchDetail, create, update, fetchPublicDetail
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user