-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented service to compute weather categories
- Loading branch information
FrankMurmann
committed
Jan 22, 2025
1 parent
35a0d72
commit 263e2b8
Showing
2 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
metarParser-services/src/main/java/io/github/mivek/service/WeatherCategoryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package io.github.mivek.service; | ||
|
||
import io.github.mivek.enums.CloudQuantity; | ||
import io.github.mivek.model.Cloud; | ||
import io.github.mivek.model.Visibility; | ||
import io.github.mivek.model.WeatherCategory; | ||
import io.github.mivek.utils.Converter; | ||
|
||
import java.util.Arrays; | ||
import java.util.Comparator; | ||
import java.util.List; | ||
|
||
public final class WeatherCategoryService { | ||
|
||
/** Instance. **/ | ||
private static final WeatherCategoryService INSTANCE = new WeatherCategoryService(); | ||
|
||
/** | ||
* Private constructor. | ||
*/ | ||
private WeatherCategoryService() { } | ||
|
||
/** | ||
* @param weatherCategory type of weather category | ||
* @return the weather category. | ||
*/ | ||
public <T extends WeatherCategory> T computeWeatherCategory(final List<Cloud> clouds, final Visibility visibility, final Class<T> weatherCategory) { | ||
return (T) computeWeatherCategory(visibility, clouds, weatherCategory.getEnumConstants()); | ||
} | ||
|
||
/** | ||
* Returns a instance of the class. | ||
* | ||
* @return the instance of the class. | ||
*/ | ||
public static WeatherCategoryService getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
private WeatherCategory computeWeatherCategory(final Visibility visibility, final List<Cloud> clouds, final WeatherCategory[] categories) { | ||
final Double visibilityDistance = Converter.convertVisibilityToKM(visibility.getMainVisibility()); | ||
if (visibilityDistance == null) { | ||
return null; | ||
} | ||
|
||
final int ceiling = computeCeiling(clouds); | ||
|
||
return Arrays.stream(categories) | ||
.filter(cat -> cat.isCriteriaMet(visibilityDistance, ceiling)) | ||
.findFirst() | ||
.orElse(null); | ||
} | ||
|
||
private Integer computeCeiling(List<Cloud> clouds) { | ||
return clouds.stream() | ||
.sorted(Comparator.comparing(Cloud::getHeight)) | ||
.filter(c -> CloudQuantity.BKN.equals(c.getQuantity()) || CloudQuantity.OVC.equals(c.getQuantity())) | ||
.findFirst() | ||
.map(Cloud::getHeight) | ||
.orElse(Integer.MAX_VALUE); | ||
} | ||
} |
226 changes: 226 additions & 0 deletions
226
metarParser-services/src/test/java/io/github/mivek/service/WeatherCategoryServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
package io.github.mivek.service; | ||
|
||
import io.github.mivek.enums.CloudQuantity; | ||
import io.github.mivek.model.*; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
class WeatherCategoryServiceTest { | ||
|
||
@DisplayName("should compute FAA weather category") | ||
@ParameterizedTest | ||
@MethodSource | ||
void computeFAAWeatherCategory(final String mainVisibility, final Integer ceiling, final FAAWeatherCategory expected) { | ||
final Visibility visibility = new Visibility(); | ||
visibility.setMainVisibility(mainVisibility); | ||
final Cloud cloudLow = new Cloud(); | ||
cloudLow.setHeight(200); | ||
cloudLow.setQuantity(CloudQuantity.FEW); | ||
final Cloud cloudMid = new Cloud(); | ||
cloudMid.setHeight(ceiling); | ||
cloudMid.setQuantity(CloudQuantity.BKN); | ||
final Cloud cloudHigh = new Cloud(); | ||
cloudHigh.setHeight(10000); | ||
cloudHigh.setQuantity(CloudQuantity.SCT); | ||
|
||
final List<Cloud> clouds = new ArrayList<>(); | ||
clouds.add(cloudLow); | ||
clouds.add(cloudMid); | ||
clouds.add(cloudHigh); | ||
|
||
final FAAWeatherCategory result = WeatherCategoryService.getInstance().computeWeatherCategory(clouds, visibility, WeatherCategory.FAA); | ||
|
||
assertEquals(expected, result); | ||
} | ||
|
||
@DisplayName("should compute FAA weather category with no clouds at all") | ||
@ParameterizedTest | ||
@MethodSource | ||
void computeFAAWeatherCategoryWithNoClouds(final String mainVisibility, final FAAWeatherCategory expected) { | ||
final Visibility visibility = new Visibility(); | ||
visibility.setMainVisibility(mainVisibility); | ||
|
||
final FAAWeatherCategory result = WeatherCategoryService.getInstance().computeWeatherCategory(new ArrayList<>(), visibility, WeatherCategory.FAA); | ||
|
||
assertEquals(expected, result); | ||
} | ||
|
||
@DisplayName("should compute FAA weather category with scattered and few clouds only") | ||
@ParameterizedTest | ||
@MethodSource("computeFAAWeatherCategoryWithNoClouds") | ||
void computeFAAWeatherCategoryWithFewClouds(final String mainVisibility, final FAAWeatherCategory expected) { | ||
final Visibility visibility = new Visibility(); | ||
visibility.setMainVisibility(mainVisibility); | ||
final Cloud cloudLow = new Cloud(); | ||
cloudLow.setHeight(200); | ||
cloudLow.setQuantity(CloudQuantity.FEW); | ||
final Cloud cloudMid = new Cloud(); | ||
cloudMid.setHeight(3000); | ||
cloudMid.setQuantity(CloudQuantity.SCT); | ||
final Cloud cloudHigh = new Cloud(); | ||
cloudHigh.setHeight(10000); | ||
cloudHigh.setQuantity(CloudQuantity.FEW); | ||
|
||
final List<Cloud> clouds = new ArrayList<>(); | ||
clouds.add(cloudLow); | ||
clouds.add(cloudMid); | ||
clouds.add(cloudHigh); | ||
|
||
final FAAWeatherCategory result = WeatherCategoryService.getInstance().computeWeatherCategory(clouds, visibility, WeatherCategory.FAA); | ||
|
||
assertEquals(expected, result); | ||
} | ||
|
||
@DisplayName("should compute ICAO weather category") | ||
@ParameterizedTest | ||
@MethodSource | ||
void computeICAOWeatherCategory(final String mainVisibility, final Integer ceiling, final ICAOWeatherCategory expected) { | ||
final Visibility visibility = new Visibility(); | ||
visibility.setMainVisibility(mainVisibility); | ||
final Cloud cloudLow = new Cloud(); | ||
cloudLow.setHeight(200); | ||
cloudLow.setQuantity(CloudQuantity.FEW); | ||
final Cloud cloudMid = new Cloud(); | ||
cloudMid.setHeight(ceiling); | ||
cloudMid.setQuantity(CloudQuantity.BKN); | ||
final Cloud cloudHigh = new Cloud(); | ||
cloudHigh.setHeight(10000); | ||
cloudHigh.setQuantity(CloudQuantity.SCT); | ||
|
||
final List<Cloud> clouds = new ArrayList<>(); | ||
clouds.add(cloudLow); | ||
clouds.add(cloudMid); | ||
clouds.add(cloudHigh); | ||
|
||
final ICAOWeatherCategory result = WeatherCategoryService.getInstance().computeWeatherCategory(clouds, visibility, WeatherCategory.ICAO); | ||
|
||
assertEquals(expected, result); | ||
} | ||
|
||
@DisplayName("should compute GAFOR weather category") | ||
@ParameterizedTest | ||
@MethodSource | ||
void computeGAFORWeatherCategory(final String mainVisibility, final Integer ceiling, final GAFORWeatherCategory expected) { | ||
final Visibility visibility = new Visibility(); | ||
visibility.setMainVisibility(mainVisibility); | ||
final Cloud cloudLow = new Cloud(); | ||
cloudLow.setHeight(200); | ||
cloudLow.setQuantity(CloudQuantity.FEW); | ||
final Cloud cloudMid = new Cloud(); | ||
cloudMid.setHeight(ceiling); | ||
cloudMid.setQuantity(CloudQuantity.BKN); | ||
final Cloud cloudHigh = new Cloud(); | ||
cloudHigh.setHeight(10000); | ||
cloudHigh.setQuantity(CloudQuantity.SCT); | ||
|
||
final List<Cloud> clouds = new ArrayList<>(); | ||
clouds.add(cloudLow); | ||
clouds.add(cloudMid); | ||
clouds.add(cloudHigh); | ||
|
||
final GAFORWeatherCategory result = WeatherCategoryService.getInstance().computeWeatherCategory(clouds, visibility, WeatherCategory.GAFOR); | ||
|
||
assertEquals(expected, result); | ||
} | ||
|
||
@DisplayName("should compute military weather category") | ||
@ParameterizedTest | ||
@MethodSource | ||
void computeMilitaryWeatherCategory(final String mainVisibility, final Integer ceiling, final MilitaryWeatherCategory expected) { | ||
final Visibility visibility = new Visibility(); | ||
visibility.setMainVisibility(mainVisibility); | ||
final Cloud cloudLow = new Cloud(); | ||
cloudLow.setHeight(200); | ||
cloudLow.setQuantity(CloudQuantity.FEW); | ||
final Cloud cloudMid = new Cloud(); | ||
cloudMid.setHeight(ceiling); | ||
cloudMid.setQuantity(CloudQuantity.BKN); | ||
final Cloud cloudHigh = new Cloud(); | ||
cloudHigh.setHeight(10000); | ||
cloudHigh.setQuantity(CloudQuantity.SCT); | ||
|
||
final List<Cloud> clouds = new ArrayList<>(); | ||
clouds.add(cloudLow); | ||
clouds.add(cloudMid); | ||
clouds.add(cloudHigh); | ||
|
||
final MilitaryWeatherCategory result = WeatherCategoryService.getInstance().computeWeatherCategory(clouds, visibility, WeatherCategory.MILITARY); | ||
|
||
assertEquals(expected, result); | ||
} | ||
|
||
public static Stream<Arguments> computeFAAWeatherCategory() { | ||
return Stream.of( | ||
Arguments.of("notParsable", 5000, null), | ||
Arguments.of("500m", 5000, FAAWeatherCategory.LIFR), | ||
Arguments.of("10km", 300, FAAWeatherCategory.LIFR), | ||
Arguments.of("500m", 300, FAAWeatherCategory.LIFR), | ||
Arguments.of("2km", 5000, FAAWeatherCategory.IFR), | ||
Arguments.of("10km", 600, FAAWeatherCategory.IFR), | ||
Arguments.of("2km", 600, FAAWeatherCategory.IFR), | ||
Arguments.of("10SM", 1000, FAAWeatherCategory.MVFR), | ||
Arguments.of("4SM", 4000, FAAWeatherCategory.MVFR), | ||
Arguments.of("4SM", 1000, FAAWeatherCategory.MVFR), | ||
Arguments.of("10SM", 5000, FAAWeatherCategory.VFR)); | ||
} | ||
|
||
public static Stream<Arguments> computeFAAWeatherCategoryWithNoClouds() { | ||
return Stream.of( | ||
Arguments.of("notParsable", null), | ||
Arguments.of("500m", FAAWeatherCategory.LIFR), | ||
Arguments.of("10km", FAAWeatherCategory.VFR), | ||
Arguments.of("2km", FAAWeatherCategory.IFR), | ||
Arguments.of("10SM", FAAWeatherCategory.VFR), | ||
Arguments.of("4SM", FAAWeatherCategory.MVFR)); | ||
} | ||
|
||
public static Stream<Arguments> computeICAOWeatherCategory() { | ||
return Stream.of( | ||
Arguments.of("notParsable", 5000, null), | ||
Arguments.of("500m", 5000, ICAOWeatherCategory.IMC), | ||
Arguments.of("10km", 300, ICAOWeatherCategory.IMC), | ||
Arguments.of("500m", 300, ICAOWeatherCategory.IMC), | ||
Arguments.of("2km", 5000, ICAOWeatherCategory.IMC), | ||
Arguments.of("10km", 1100, ICAOWeatherCategory.IMC), | ||
Arguments.of("2km", 1100, ICAOWeatherCategory.IMC), | ||
Arguments.of("10SM", 2000, ICAOWeatherCategory.VMC), | ||
Arguments.of("4SM", 1000, ICAOWeatherCategory.IMC), | ||
Arguments.of("4SM", 4000, ICAOWeatherCategory.VMC), | ||
Arguments.of("10SM", 5000, ICAOWeatherCategory.VMC)); | ||
} | ||
|
||
public static Stream<Arguments> computeGAFORWeatherCategory() { | ||
return Stream.of( | ||
Arguments.of("notParsable", 5000, null), | ||
Arguments.of("500m", 300, GAFORWeatherCategory.X), | ||
Arguments.of("10km", 300, GAFORWeatherCategory.M2), | ||
Arguments.of("6km", 300, GAFORWeatherCategory.M5), | ||
Arguments.of("2km", 2500, GAFORWeatherCategory.M6), | ||
Arguments.of("2km", 1600, GAFORWeatherCategory.M7), | ||
Arguments.of("2km", 800, GAFORWeatherCategory.M8), | ||
Arguments.of("10SM", 1500, GAFORWeatherCategory.D1), | ||
Arguments.of("6km", 2500, GAFORWeatherCategory.D3), | ||
Arguments.of("6km", 1500, GAFORWeatherCategory.D4), | ||
Arguments.of("10SM", 3000, GAFORWeatherCategory.O), | ||
Arguments.of("10SM", 6000, GAFORWeatherCategory.C)); | ||
} | ||
|
||
public static Stream<Arguments> computeMilitaryWeatherCategory() { | ||
return Stream.of( | ||
Arguments.of("notParsable", 5000, null), | ||
Arguments.of("500m", 100, MilitaryWeatherCategory.RED), | ||
Arguments.of("1000m", 250, MilitaryWeatherCategory.AMB), | ||
Arguments.of("2000m", 500, MilitaryWeatherCategory.YLO), | ||
Arguments.of("4000m", 1300, MilitaryWeatherCategory.GRN), | ||
Arguments.of("6000m", 2300, MilitaryWeatherCategory.WHT), | ||
Arguments.of("10SM", 6000, MilitaryWeatherCategory.BLU)); | ||
} | ||
} |