217 lines
6.5 KiB
Vue
217 lines
6.5 KiB
Vue
<script setup>
|
|
import {Circle, GoogleMap, CustomControl, CustomMarker, Polygon} from "vue3-google-map";
|
|
import {computed, ref, watch} from "vue";
|
|
import {useBasicVectorOperations, useVectorUtilities} from "src/graphql/fragments/vectorUtilities";
|
|
|
|
const props = defineProps({
|
|
bounds: {
|
|
type: Array,
|
|
required: false
|
|
},
|
|
airspacesData: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => []
|
|
},
|
|
airports: {
|
|
type: Array,
|
|
required: false,
|
|
default: () => []
|
|
},
|
|
showLayerControls: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true
|
|
}
|
|
});
|
|
|
|
const emits = defineEmits(["map-click"]);
|
|
|
|
const key = process.env.GOOGLE_MAPS_API_KEY;
|
|
|
|
const zoomLevel = ref(null);
|
|
const mapRef = ref(null);
|
|
watch(
|
|
() => mapRef.value?.ready,
|
|
(ready) => {
|
|
if (!ready) return;
|
|
|
|
if (props.bounds) {
|
|
const bounds = new mapRef.value.api.LatLngBounds();
|
|
for (let i = 0; i < props.bounds.length; i++) {
|
|
bounds.extend(props.bounds[i]);
|
|
}
|
|
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();
|
|
})
|
|
}
|
|
);
|
|
|
|
|
|
const displayAirspaceLayers = ref({
|
|
CTR: true,
|
|
TMA: true,
|
|
RMZ: false,
|
|
ATZ: true,
|
|
PARA: false,
|
|
DANGER: false,
|
|
PROHIBIT: true,
|
|
RESTRICT: true,
|
|
TRAGA: true,
|
|
AIRPORTS: true,
|
|
FIELDSTRIP: false,
|
|
});
|
|
|
|
const airspaces = computed(() => {
|
|
const colorsMap = {
|
|
TMA: "#800080",
|
|
CTR: "#366e36",
|
|
PROHIBIT: "#FF0000",
|
|
RMZ: "#FFdd00",
|
|
RESTRICT: "#ff6a00",
|
|
DANGER: "#FFA500",
|
|
TRAGA: "#008000",
|
|
ATZ: "#4e7cfc",
|
|
FIR: '#ffffff',
|
|
PARA: "#000000"
|
|
}
|
|
|
|
return props.airspacesData
|
|
.filter(airspace => !!colorsMap[airspace.type])
|
|
.filter(airspace => displayAirspaceLayers.value[airspace.type])
|
|
.map(airspace => {
|
|
|
|
let fillOpacity = 0;
|
|
if (["PROHIBIT", "RESTRICT"].includes(airspace.type))
|
|
fillOpacity = 0.1;
|
|
if (highlightedAirspaces.value.includes(airspace.name))
|
|
fillOpacity = 0.2;
|
|
|
|
const data = {
|
|
name: airspace.name,
|
|
strokeColor: colorsMap[airspace.type],
|
|
fillColor: colorsMap[airspace.type],
|
|
strokeOpacity: 0.5,
|
|
strokeWeight: 1,
|
|
fillOpacity,
|
|
};
|
|
|
|
if (airspace.mapBounds.length > 0) {
|
|
data['path'] = airspace.mapBounds;
|
|
} else {
|
|
data['center'] = airspace.mapCenter;
|
|
data['radius'] = airspace.radius
|
|
}
|
|
|
|
return data
|
|
});
|
|
});
|
|
|
|
const airportsOnMap = computed(() => {
|
|
if (!displayAirspaceLayers.value.AIRPORTS && !displayAirspaceLayers.value.FIELDSTRIP) return [];
|
|
|
|
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 {vectorDistance} = useBasicVectorOperations();
|
|
|
|
const highlightedAirspaces = ref([]);
|
|
|
|
const getAirspacesAtCoordinates = (clickCoordinates) => {
|
|
const airspacesUnderClick = []
|
|
for (const airspace of props.airspacesData) {
|
|
if (airspace.mapBounds && isPointInPolygon(clickCoordinates, airspace.mapBounds)) {
|
|
airspacesUnderClick.push(airspace);
|
|
} else if (airspace.mapCenter && airspace.radius) {
|
|
const radDistance = vectorDistance(clickCoordinates, airspace.mapCenter);
|
|
const distanceKm = vectorDistanceToKm(radDistance)
|
|
|
|
if (distanceKm * 1000 <= airspace.radius) {
|
|
airspacesUnderClick.push(airspace)
|
|
}
|
|
}
|
|
}
|
|
return airspacesUnderClick;
|
|
}
|
|
const handleMapClick = (e) => {
|
|
const clickCoordinates = {lat: e.latLng.lat(), lng: e.latLng.lng()}
|
|
|
|
const airspacesUnderClick = getAirspacesAtCoordinates(clickCoordinates);
|
|
highlightedAirspaces.value = airspacesUnderClick.map(a => a.name);
|
|
|
|
emits("map-click", {airspaces: airspacesUnderClick, clickCoordinates});
|
|
}
|
|
|
|
const handleReportingPointClick = (airport, reportingPoint) => {
|
|
const position = {lat: reportingPoint.gpsLatitude, lng: reportingPoint.gpsLongitude};
|
|
emits("map-click", {clickCoordinates: position, airport, reportingPoint})
|
|
}
|
|
|
|
const handleAirportClick = (airport) => {
|
|
const position = {lat: airport.gpsLatitude, lng: airport.gpsLongitude};
|
|
emits("map-click", {clickCoordinates: position, airport})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<GoogleMap
|
|
:api-key="key"
|
|
v-bind="$attrs"
|
|
:street-view-control="false"
|
|
@click="handleMapClick"
|
|
map-type-id="roadmap"
|
|
:max-zoom="13"
|
|
ref="mapRef"
|
|
>
|
|
<template v-for="airspace in airspaces" :key="airspace.name">
|
|
<Polygon :options="airspace" @click="handleMapClick"/>
|
|
</template>
|
|
|
|
<slot/>
|
|
|
|
<template v-for="airport in airportsOnMap" :key="airport.icaoCode">
|
|
<CustomMarker
|
|
:options="{position: {lat: airport.gpsLatitude, lng: airport.gpsLongitude}, anchorPoint: 'CENTER'}"
|
|
@click.stop="handleAirportClick(airport)"
|
|
>
|
|
<q-chip size="xs">{{ airport.icaoCode }}</q-chip>
|
|
</CustomMarker>
|
|
|
|
<template v-if="displayAirspaceLayers.CTR">
|
|
<CustomMarker
|
|
v-for="reportingPoint in airport.reportingPoints" :key="reportingPoint.id"
|
|
:options="{position: {lat: reportingPoint.gpsLatitude, lng: reportingPoint.gpsLongitude, anchorPoint: 'CENTER'}}"
|
|
@click.stop="handleReportingPointClick(airport, reportingPoint)"
|
|
:title="`${airport.icaoCode}: ${reportingPoint.name}`"
|
|
>
|
|
<q-icon name="mdi-triangle"/>
|
|
</CustomMarker>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<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="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.PARA" label="PARA"/>
|
|
<q-checkbox v-model="displayAirspaceLayers.AIRPORTS" label="Letiště"/>
|
|
<q-checkbox v-model="displayAirspaceLayers.FIELDSTRIP" label="SLZ plochy"/>
|
|
</q-card>
|
|
</CustomControl>
|
|
|
|
</GoogleMap>
|
|
</template>
|