Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added popular airport rings #166

Draft
wants to merge 1 commit into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions src/components/map/airports/MapAirport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ import { sortControllersByPosition } from '~/composables/atc';
import MapAirportCounts from '~/components/map/airports/MapAirportCounts.vue';
import type { NavigraphAirportData } from '~/types/data/navigraph';
import { useMapStore } from '~/store/map';
import type { StoreOverlayAirport } from '~/store/map';
import { getCurrentThemeRgbColor, useScrollExists } from '~/composables';
import type { Coordinate } from 'ol/coordinate';
import type { AirportTraconFeature } from '~/components/map/airports/MapAirportsList.vue';
Expand Down Expand Up @@ -201,6 +202,9 @@ const airportsSource = inject<ShallowRef<VectorSource | null>>('airports-source'
const atcPopup = ref<{ $el: HTMLDivElement } | null>(null);
const approachPopup = ref<{ $el: HTMLDivElement } | null>(null);
const hoveredFacility = ref<boolean | number>(false);
const airportData = shallowRef<StoreOverlayAirport['data'] | null>(null);
const detailedAircraft = getAircraftForAirport(airportData as Ref<StoreOverlayAirport['data']>);
const hoveredController = ref<boolean>(false);
const facilityScroll = useScrollExists(computed(() => {
Expand Down Expand Up @@ -260,6 +264,8 @@ const localsFacilities = computed(() => {
let feature: Feature | null = null;
let hoverFeature: Feature | null = null;
let popularRingCircleFeature: Feature | null = null;
let popularRingLabelFeature: Feature | null = null;
interface ArrFeature {
id: string;
Expand All @@ -274,6 +280,7 @@ let runwaysFeatures: Feature[] = [];
const airportName = computed(() => (props.airport.isPseudo && props.airport.iata) ? props.airport.iata : props.airport.icao);
const hoveredFeature = computed(() => arrFeatures.value.find(x => x.id === props.hoveredId));
const showPopularRing = computed(() => !(store.mapSettings.visibility?.popularAirportRings ?? true));
function initAirport() {
if (!('lon' in props.airport) || isPseudoAirport.value) return;
Expand All @@ -297,6 +304,114 @@ function initAirport() {
airportsSource.value?.addFeature(feature);
}
const ringStyle = new Style({
stroke: new Stroke({
color: `rgba(${ radarColors.airportPopularRingRgb.join(',') }, 0.4)`,
width: 1,
}),
});
const popularRingLabelStyle = (icao: string) => {
return new Style({
text: new Text({
text: icao,
font: '10px Montserrat',
fill: new Fill({
color: `rgba(${ radarColors.airportPopularRingRgb.join(',') }, 0.8)`,
}),
offsetY: -5,
}),
})
}
function setPopularRing() {
clearPopularRing();
const minMovement: number = 8; // minimum required movements to show the ring
const maxMovement: number = 120; // the number of movements when the maximum radius is reached
const minRadius: number = 70000; // the radius at the minimum movements
const maxRadius: number = 1200000; // the radius at the maximum movements
if (isPrimaryAirport.value) return;
if (!showPopularRing.value) return;
const vatAirport = dataStore.vatsim.data.airports.value.find(x => x.icao === props.airport.icao);
if ((vatAirport?.aircraft?.arrivals?.length || 0) + (vatAirport?.aircraft?.groundDep?.length || 0) < minMovement) return; // We know for sure the airport has less than the minimum relevant movements, we don#t show the ring and skip the rest
if (!airportData.value) { // check if airportData is already initialized
// the airportData is not yet initialized, so we do it by setting the icao code. This automatically fills the detailedAircraft variable
// we trigger the initialization of the airportData here to make sure it is not initialized when the rings are disabled for performance reasons
airportData.value = {
icao: props.airport.icao,
};
}
// calculate movementCounter
const departureCounter: number = (detailedAircraft.value?.groundDep.length || 0) + (detailedAircraft.value?.prefiles.length || 0);
let arrivalCounter: number = 0;
if (detailedAircraft.value?.arrivals) {
for (let i = 0; i < detailedAircraft.value?.arrivals.length; i++) {
const arrival = detailedAircraft.value?.arrivals[i];
if (!arrival.eta) continue;
const currentDate = new Date() as Date;
const differenceInMs = arrival.eta.getTime() - currentDate.getTime();
const differenceInMinutes = differenceInMs / (1000 * 60);
if (differenceInMinutes > 0 && differenceInMinutes < 60) arrivalCounter++;
}
}
const movementCounter: number = departureCounter + arrivalCounter;
let radius: number = 0; // radius is in meter
if (movementCounter >= maxMovement) {
radius = maxRadius;
}
else if (movementCounter > minMovement) {
radius = minRadius + ((movementCounter - minMovement) * ((maxRadius - minRadius) / (maxMovement - minMovement)));
}
if (!radius) return; // If radius is 0, we don't need to draw the ring
if (!('lon' in props.airport)) return;
popularRingCircleFeature = new Feature({
geometry: new Circle([props.airport.lon, props.airport.lat], radius),
type: 'airportPopularRing',
icao: props.airport.icao,
});
popularRingCircleFeature.setStyle(ringStyle);
airportsSource.value?.addFeature(popularRingCircleFeature);
// Calculate the top position of the circle for the text
const geometry = popularRingCircleFeature.getGeometry();
const center = geometry?.getCenter();
const topPosition = [center[0], center[1] + radius];
// Create a new point feature for the text
popularRingLabelFeature = new Feature({
geometry: new Point(topPosition),
type: 'airportPopularLabel',
icao: props.airport.icao,
});
popularRingLabelFeature.setStyle(popularRingLabelStyle(props.airport.icao));
airportsSource.value?.addFeature(popularRingLabelFeature);
}
function clearPopularRing() {
if (popularRingCircleFeature) {
airportsSource.value?.removeFeature(popularRingCircleFeature);
popularRingCircleFeature.dispose();
popularRingCircleFeature = null;
}
if (popularRingLabelFeature) {
airportsSource.value?.removeFeature(popularRingLabelFeature);
popularRingLabelFeature.dispose();
popularRingLabelFeature = null;
}
}
watch(getAirportColor, () => {
if (!feature) return;
Expand Down Expand Up @@ -419,7 +534,18 @@ onMounted(async () => {
immediate: true,
});
watch(showPopularRing, (newValue, oldValue) => {
if (newValue) {
setPopularRing();
}
else {
clearPopularRing();
}
});
function initAndUpdateData(force = false) {
setPopularRing();
if (!props.arrAtc?.length || isPrimaryAirport.value || isHideAtcType('approach')) {
clearArrFeatures();
arrAtcLocal.value.clear();
Expand Down Expand Up @@ -589,6 +715,7 @@ onMounted(async () => {
deep: true,
});
if (isPrimaryAirport.value) {
const overlay = await mapStore.addAirportOverlay(props.airport.icao);
if (overlay) {
Expand All @@ -611,6 +738,7 @@ onBeforeUnmount(() => {
}
clearArrFeatures();
clearPopularRing();
gatesFeatures.forEach(feature => {
vectorSource.value?.removeFeature(feature);
Expand Down
29 changes: 29 additions & 0 deletions src/components/map/filters/settings/MapSettingsVisibility.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@
>
Runways
</common-toggle>
<common-toggle
:model-value="!!(store.mapSettings.visibility?.popularAirportRings ?? true)"
@update:modelValue="setUserMapSettings({ visibility: { popularAirportRings: $event } })"
>
<div class="tooltip">
Airport popular rings
<common-tooltip
width="150px"
>
<template #activator>
<div>
<question-icon width="14"/>
</div>
</template>
This will show rings around airport with more than 8 movements in the next hour. The size is decided based on the expected movements in the next hour.

</common-tooltip>
</div>
</common-toggle>
</div>
</template>
</div>
Expand All @@ -153,8 +172,18 @@ import CommonBlockTitle from '~/components/common/blocks/CommonBlockTitle.vue';
import { useStore } from '~/store';
import CommonSelect from '~/components/common/basic/CommonSelect.vue';
import CommonTabs from '~/components/common/basic/CommonTabs.vue';
import QuestionIcon from 'assets/icons/basic/question.svg?component';
import CommonTooltip from '~/components/common/basic/CommonTooltip.vue';
const store = useStore();
const tab = ref('modes');
</script>

<style scoped lang="scss">
.tooltip {
display: flex;
align-items: center;
gap: 5px;
}
</style>
3 changes: 3 additions & 0 deletions src/types/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ interface IUserLocalSettings {
layerVector?: boolean;
transparencySettings?: UserLayersTransparencySettings;
};
options?: {
airportPopularRings?: boolean; // Shows rings around popular airports
};
notamsSortBy?: NotamsSortBy;
};

Expand Down
1 change: 1 addition & 0 deletions src/utils/backend/map-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export interface IUserMapSettings {
pilots?: boolean;
gates?: boolean;
runways?: boolean;
popularAirportRings?: boolean;
};
heatmapLayer: boolean;
groundTraffic: {
Expand Down
1 change: 1 addition & 0 deletions src/utils/backend/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { PartialRecord } from '~/types';

export const colorsList = {
mapSectorBorder: '#2d2d30',
airportPopularRing: '#DA5225',

lightgray0: '#F7F7FA',
lightgray50: '#F2F2F7',
Expand Down