Files
app/src/components/map/MapWrapper.vue
T

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>