From 3215b07e8ac8c198845c6a47172736ba361d9f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dhji=28=EC=A7=80=EB=8C=80=ED=95=9C=29?= Date: Wed, 22 Nov 2023 15:19:42 +0900 Subject: [PATCH] =?UTF-8?q?laanc=20validate=20=EC=9E=AC=EA=B5=AC=EC=84=B1(?= =?UTF-8?q?=EC=A0=81=EC=9A=A9x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bas/laanc/model/BasLaancValidatedRs.java | 81 ++---- .../bas/laanc/service/BasLaancService.java | 231 ++++++------------ .../com/palnet/comn/utils/AirspaceUtils.java | 2 +- 3 files changed, 103 insertions(+), 211 deletions(-) diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancValidatedRs.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancValidatedRs.java index fd6fd28b..6f3b0bd5 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancValidatedRs.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancValidatedRs.java @@ -1,14 +1,10 @@ package com.palnet.biz.api.bas.laanc.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.palnet.biz.api.external.model.PilotValidRs; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; - /** * packageName : com.palnet.biz.api.bas.laanc.model * fileName : BasLaancValidatedRs @@ -26,66 +22,39 @@ import java.util.List; @AllArgsConstructor public class BasLaancValidatedRs { +// // TS 연동 +// private String corpRegYn; // 사업자 여부 +// private List pilotValidRsList; // 자격여부 - private boolean isPilotQlfc; // 자격여부 - private boolean isArcrftInsurance; // 항공기보험여부 - + // 비행유형 여부 + private boolean isCommercial; // 사업자 - true, 비사업자 - false + // 관제권 private boolean isEvaluatedTargetArea; // 평가대상지역여부 - 공역과 겹칠때만 true - private boolean isFlightArea; // 비행가능여부 - 비행가능 true - + // 고도 + private boolean isElev; // 고도여부 - 관제권내 설정고도 이하, 권제권밖 150m 이하 true + // 기체중량 private boolean isArcrftWeight; // 항공기중량여부 - 25kg 이하 true + // 비행방식 + private boolean isFltMethod; // 비행방식 - 군집비행 false, 그외 true + // 비행시간 + private boolean isSpacialFlight; // 특별비행여부 - true (야간비행) + // 자격여부 + private boolean isPilotQlfc; // 자격여부 + // 기체보험여부 + private boolean isArcrftInsurance; // 기체보험여부 - private boolean isElev; // 고도여부 - 150m 이하 true - - private boolean isReport; // 신고 대상 - 비상업적이고 기체중량 2kg미만일 경우 - false / 그외 true - - // 추가.... - private boolean isCommercial; // 비행유형 사업자 - true / 비사업자 - false - private boolean isSpacialFlight; // 특별비행여부 - true - - // TS 연동 -// private String corpRegYn; // 사업자 여부 - private List pilotValidRsList; // 자격여부 - - // 활용안함. -// private boolean isArcrftDuplicated; // 기체 중복여부 -// private boolean isPlanAreaDuplicatd; // 비행계획서비행구역 중복여부 - + // 최종여부 public boolean isValid() { - if (isReport) { - return isPilotQlfc - && isArcrftInsurance -// && !isArcrftDuplicated // 기체 중복여부 -// && !isPlanAreaDuplicatd // 비행구역 중복여부 - && isFlightArea // 비행가능여부 - && isCheckingLance(); - } - return isFlightArea -// && !isPlanAreaDuplicatd -// && !isArcrftDuplicated - && isCheckingLance(); + return !isNonAprove() && !isNonAproveFlight(); } - - public boolean isFlight() { - return !isCheckingLance() || isValid(); + // 미승인 + public boolean isNonAprove() { + return isFltMethod && !isSpacialFlight && isElev && isPilotQlfc && isArcrftInsurance; } - - // LAANC 승인 대상 여부 - @JsonIgnore - public boolean isCheckingLance() { - return isEvaluatedTargetArea // 관제구역여부 - /* - 관제권 외 고도 150m초과에 대해서 승인 대상이나 담당자와의 협의가 필요하여 미승인 처리함 - || !isElev // 150m 초과 - - */ - || !isArcrftWeight; // 25kg 초과 + // 비행가능여부(미승인-날수있음) + public boolean isNonAproveFlight() { + return !isCommercial && !isEvaluatedTargetArea && isElev && isArcrftWeight; } - // 특별승인 대상 여부 - @JsonIgnore - public boolean isTargetSpacialFlight() { - return false; - } - } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/service/BasLaancService.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/service/BasLaancService.java index 27be6c83..73ec6216 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/service/BasLaancService.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/service/BasLaancService.java @@ -10,6 +10,9 @@ import java.util.List; import java.util.stream.Collectors; import com.palnet.biz.api.bas.laanc.model.*; +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetCoordRq; +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRs; +import com.palnet.biz.jpa.entity.type.FltMethod; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.springframework.stereotype.Service; @@ -87,6 +90,8 @@ public class BasLaancService { private final ComnSmsService comnSmsService; private final JwtTokenUtil jwtTokenUtil; private final AreaUtils areaUtils; + private final ComRiseSetQueryRepository comRiseSetQueryRepository; + // LAANC 검증 public BasLaancValidatedRs validationLaanc(BasLaancPlanRq rq) { @@ -94,63 +99,66 @@ public class BasLaancService { BasLaancValidatedRs rs = new BasLaancValidatedRs(); - // 조종사 자격 확인 - 무게가 2kg 초과이거나 상업적일 경우에만 진행 - // 상업 여부 - 상업(true) - boolean isCommercial = FltType.COMMERCIAL == rq.getFltType(); - rs.setCommercial(isCommercial); - - // 2kg 초과 기체신고번호 - List idntfNumList = rq.getArcrftList().stream().filter(arcrft -> arcrft.getArcrftWghtCd() != ArcrftWghtCd.W250G_LOE && arcrft.getArcrftWghtCd() != ArcrftWghtCd.W250G_W2KG).map(BasLaancArcrftModel::getIdntfNum).collect(Collectors.toList()); - - // 신고 여부 - 비상업적이고 기체중량 2kg이하일 경우 - false, 상업적이거나 기체중량 2kg초과일 경우 - true - boolean isReport = !idntfNumList.isEmpty() || isCommercial; - rs.setReport(isReport); - - - // 고도 150m 이하 - true - boolean isElev = rq.getAreaList().stream().anyMatch(area -> area.getFltElev() != null && Integer.parseInt(area.getFltElev()) <= 150); - rs.setElev(isElev); - - // 기체중량 25kg 이하 - boolean isArcrftWeight = rq.getArcrftList().stream().anyMatch(arcrft -> arcrft.getArcrftWghtCd() != ArcrftWghtCd.W25KG_GO && arcrft.getArcrftWghtCd() != ArcrftWghtCd.W25KG_GO_TEST); - rs.setArcrftWeight(isArcrftWeight); - // TODO start - 조종사 자격 및 기체보험 확인 - if (isReport) { - List pilotValidRqList = idntfNumList.stream().map(idntfNum -> { + // TS 자격 판별 - 조종사 자격증명, 기체 보험 + List idntfNumList = rq.getArcrftList().stream().filter(arcrft -> arcrft.getIdntfNum() != null && !arcrft.getIdntfNum().isBlank()).map(BasLaancArcrftModel::getIdntfNum).collect(Collectors.toList()); + Integer cstmrSno = jwtTokenUtil.getCstmrSnoByToken(); + AnctCstmrModel cstmrInfo = ptyCstmrQueryRepository.findByCstmrSno(cstmrSno); + String userCi = cstmrInfo.getIpinCi(); + List pilotValidRqList = new ArrayList<>(); + if (!idntfNumList.isEmpty()) { + pilotValidRqList = idntfNumList.stream().map(idntfNum -> { // TODO 기체보험 확인, 조종사 자격 확인 return PilotValidRq.builder() - .pilotci("조종사CI") + .pilotci(userCi) .declarationnum(idntfNum) .build(); }).collect(Collectors.toList()); + } else { + pilotValidRqList.add(PilotValidRq.builder() + .pilotci(userCi) + .build()); + } - List pilotValidRsList = tsService.getAccountValidate(pilotValidRqList); - if (pilotValidRsList.isEmpty()) { - rs.setPilotQlfc(false); - rs.setArcrftInsurance(false); - } else { - rs.setPilotValidRsList(pilotValidRsList); - rs.setPilotQlfc(pilotValidRsList.stream().allMatch(pilotValidRs -> "Y".equals(pilotValidRs.getPilotcredentialyn()))); - rs.setArcrftInsurance(pilotValidRsList.stream().allMatch(pilotValidRs -> "Y".equals(pilotValidRs.getArcrftinsuranceyn()))); - } + List pilotValidRsList = tsService.getAccountValidate(pilotValidRqList); + if (pilotValidRsList.isEmpty()) { + rs.setPilotQlfc(false); + rs.setArcrftInsurance(false); + } else { + rs.setPilotQlfc(pilotValidRsList.stream().allMatch(pilotValidRs -> "Y".equals(pilotValidRs.getPilotcredentialyn()))); + rs.setArcrftInsurance(pilotValidRsList.stream().allMatch(pilotValidRs -> "Y".equals(pilotValidRs.getArcrftinsuranceyn()))); } - // TODO end - 조종사 자격 및 기체보험 확인 + // 비행유형 판별 - 상업 - true + boolean isCommercial = FltType.COMMERCIAL == rq.getFltType(); + rs.setCommercial(isCommercial); + // 관제권 여부 판별 + BasLaancValidatedRs validationPlanDbRs = this.validationPlanAirspace(rq); + boolean isEvaluatedTargetArea = validationPlanDbRs.isEvaluatedTargetArea(); + rs.setEvaluatedTargetArea(isEvaluatedTargetArea); - /* 비행구역 및 기체 중복여부 확인 안하기로 함. - // 비행구역 중복여부, 기체 중복여부 - BasLaancValidatedRs validationPlanAirspaceRs = this.validationPlanAreaAndArcrft(rq); - rs.setPlanAreaDuplicatd(validationPlanAirspaceRs.isPlanAreaDuplicatd()); - rs.setArcrftDuplicated(validationPlanAirspaceRs.isArcrftDuplicated()); - */ + // 고도여부 - 관제권내 설정고도 이하, 권제권밖 150m 이하 true + if (isEvaluatedTargetArea) { + // 관제권 내부 - 설정고도 이하 + rs.setElev(validationPlanDbRs.isElev()); + } else { + // 관제권 외부 - 150m 이하 + boolean isElev = rq.getAreaList().stream().allMatch(area -> area.getFltElev() != null && Integer.parseInt(area.getFltElev()) <= 150); + rs.setElev(isElev); + } + // 기체중량 판별 - 25kg 이하 true + boolean isArcrftWeight = rq.getArcrftList().stream().allMatch(arcrft -> arcrft.getArcrftWghtCd() != ArcrftWghtCd.W25KG_GO && arcrft.getArcrftWghtCd() != ArcrftWghtCd.W25KG_GO_TEST); + rs.setArcrftWeight(isArcrftWeight); - // 판단구역 - 공역과 겹칠 경우, 비행가능여부 - BasLaancValidatedRs validationPlanDbRs = this.validationPlanAirspace(rq); - rs.setEvaluatedTargetArea(validationPlanDbRs.isEvaluatedTargetArea()); - rs.setFlightArea(validationPlanDbRs.isFlightArea()); + // 특별비행여부 판별 - 야간/주간 - 야간 true + boolean isSpcialFlight = this.validationPlanSpecialFlight(rq); + rs.setSpacialFlight(isSpcialFlight); + + // 비행방식 - 군집비행 false, 그외 true + boolean isFltMethod = rq.getAreaList().stream().allMatch(area -> FltMethod.CLUSTER_FLIGHT != area.getFltMethod()); + rs.setFltMethod(isFltMethod); return rs; @@ -369,113 +377,13 @@ public class BasLaancService { return rs; } - // LAANC 검증 - /* - private BasLaancValidatedRs validationPlanAreaAndArcrft(BasLaancPlanRq rq) { - - // 초기화 - BasLaancValidatedRs rs = BasLaancValidatedRs.builder() - .isPlanAreaDuplicatd(false) // 비행구역 중복여부 - .isArcrftDuplicated(false) // 기체 중복여부 - .build(); - - - // 비행계획서 - List fltPlanBasList = fltPlanBasRepository.findBySchFltStDtLessThanEqualAndSchFltEndDtGreaterThanEqualAndAprvlYnAndDelYn(rq.getSchFltEndDt(), rq.getSchFltStDt(), "Y", "N"); - if (fltPlanBasList != null && !fltPlanBasList.isEmpty()) { - - if (rq.getPlanSno() != null) { - // 동일한 비행계획서는 검증에서 제외 처리 - fltPlanBasList = fltPlanBasList.stream().filter(fltPlanBas -> !rq.getPlanSno().equals(fltPlanBas.getPlanSno())).collect(Collectors.toList()); - } - - // 비행계획서 planSno 모음 - List planSnoList = fltPlanBasList.stream().map(FltPlanBas::getPlanSno).collect(Collectors.toList()); - - // 지역 -// List fltPlanAreaList = fltPlanAreaRepository.findByPlanSnoIn(planSnoList); - - for (FltPlanBas fltPlanBas : fltPlanBasList) { - // 1. 구역조회 - List fltPlanAreaList = fltPlanAreaRepository.findByPlanSnoOrderByPlanAreaSnoAsc(fltPlanBas.getPlanSno()); - - // 2. 좌표 조회 -> 영역 포함 여부 확인 - for (FltPlanArea fltPlanArea : fltPlanAreaList) { - String effectiveFltElev = fltPlanArea.getFltElev(); - List fltPlanAreaCoordList = fltPlanAreaCoordRepository.findByPlanAreaSnoOrderByPlanAreaCoordSnoAsc(fltPlanArea.getPlanAreaSno()); - if (fltPlanAreaCoordList == null || fltPlanAreaCoordList.isEmpty()) continue; - - // 2-1 영역 좌표 - List effectiveCoordList = fltPlanAreaCoordList.stream().map(fltPlanAreaCoord -> new Coordinate(fltPlanAreaCoord.getLon(), fltPlanAreaCoord.getLat())).collect(Collectors.toList()); - List effectiveCoordBufferList = new ArrayList<>(); - // Query에서 조회한 좌표로 버퍼좌표 생성 - if ("LINE".equals(fltPlanArea.getAreaType())) { - List trans = areaUtils.transform(effectiveCoordList, "EPSG:4326", "EPSG:5181"); - List bufferList = areaUtils.buffer(trans, fltPlanArea.getBufferZone()); - effectiveCoordBufferList = areaUtils.transform(bufferList, "EPSG:5181", "EPSG:4326"); - } - if ("POLYGON".equals(fltPlanArea.getAreaType())) { - effectiveCoordBufferList.addAll(effectiveCoordList); - } - if ("CIRCLE".equals(fltPlanArea.getAreaType())) { - effectiveCoordBufferList = areaUtils.createCircle(effectiveCoordList.get(0), fltPlanArea.getBufferZone()); - } - - for (BasLaancAreaModel basLaancAreaModel : rq.getAreaList()) { - String targetFltElev = basLaancAreaModel.getFltElev(); - // TODO 추후 특정 고도 범위 확인 - boolean isEqualsFltElev = effectiveFltElev.equals(targetFltElev); - // rq로 들어온 좌표로 버퍼좌표 생성 - List targetCoords = basLaancAreaModel.getCoordList().stream().map(coord -> new Coordinate(coord.getLon(), coord.getLat())).collect(Collectors.toList()); - List targetBufferCoords = new ArrayList<>(); - if ("LINE".equals(basLaancAreaModel.getAreaType())) { - List trans = areaUtils.transform(targetCoords, "EPSG:4326", "EPSG:5181"); - List bufferList = areaUtils.buffer(trans, fltPlanArea.getBufferZone()); - targetBufferCoords = areaUtils.transform(bufferList, "EPSG:5181", "EPSG:4326"); - } else if ("POLYGON".equals(basLaancAreaModel.getAreaType())) { - targetBufferCoords.addAll(targetCoords); - } else if ("CIRCLE".equals(basLaancAreaModel.getAreaType())) { - targetBufferCoords = areaUtils.createCircle(targetCoords.get(0), fltPlanArea.getBufferZone()); - } - - // 검증 - Geometry targetGeometry = areaUtils.coordinateToGeometry(targetBufferCoords); - Geometry effectiveGeometry = areaUtils.coordinateToGeometry(effectiveCoordBufferList); - if (targetGeometry.intersects(effectiveGeometry) && isEqualsFltElev) { - rs.setPlanAreaDuplicatd(true); - } - } - } - } - - // 기체 중복 여부 확인 - List arcrftList = rq.getArcrftList(); - if (arcrftList != null && !arcrftList.isEmpty()) { - List fltPlanArcrftList = fltPlanArcrftRepository.findByPlanSnoIn(planSnoList); - if (fltPlanArcrftList != null && !fltPlanArcrftList.isEmpty()) { - boolean isDuplicatedArcrft = arcrftList.stream().anyMatch(arcrft -> - fltPlanArcrftList.stream().anyMatch(fltPlanArcrft -> - arcrft.getIdntfNum().equals(fltPlanArcrft.getIdntfNum()) - ) - ); - if (isDuplicatedArcrft) { - rs.setArcrftDuplicated(true); - } - } - } - - } - - return rs; - } - */ private BasLaancValidatedRs validationPlanAirspace(BasLaancPlanRq rq) { // 초기화 BasLaancValidatedRs rs = BasLaancValidatedRs.builder() .isEvaluatedTargetArea(false) // 판단구역 - 공역과 겹칠 경우 - .isFlightArea(false) // 비행가능여부 + .isElev(false) // 비행가능여부 .build(); // 공역 중복 확인 @@ -507,17 +415,9 @@ public class BasLaancService { boolean duplicatedAirspace = airspaceUtils.isDuplicatedAirspace(featureInfo); rs.setEvaluatedTargetArea(duplicatedAirspace); - // 비행 가능 지역 판단 -// if (duplicatedAirspace) { -// boolean validLaancAirspace = airspaceUtils.isValidLaancAirspace(featureInfo); -// rs.setFlightAreaYn(validLaancAirspace ? "Y" : "N"); -// } else { -// rs.setFlightAreaYn("N"); -// } - // 비행 가능 지역 판단 boolean validLaancAirspace = airspaceUtils.isValidLaancAirspace(featureInfo); - rs.setFlightArea(validLaancAirspace); + rs.setElev(validLaancAirspace); } return rs; @@ -571,6 +471,29 @@ public class BasLaancService { return true; } + private boolean validationPlanSpecialFlight(BasLaancPlanRq rq) { + + String[] stringStDt = InstantUtils.toDatetimeString(rq.getSchFltStDt()).split(" "); + String[] stringEndDt = InstantUtils.toDatetimeString(rq.getSchFltEndDt()).split(" "); + + ComnSunrisesetCoordRq comnSunrisesetCoordRq = new ComnSunrisesetCoordRq(stringStDt[0].replace("-", ""), stringEndDt[0].replace("-", ""), null, null); + + ComnSunrisesetRs comnSunrisesetRs = comRiseSetQueryRepository.findBySearchCoordDateTransform(comnSunrisesetCoordRq); + + LocalTime sunUp = this.convertStringToTime(comnSunrisesetRs.getCivilm(), "HHmmss"); + LocalTime sunDown = this.convertStringToTime(comnSunrisesetRs.getCivile(), "HHmmss"); + + LocalTime stringStTm = this.convertStringToTime(stringStDt[1].replace(":", ""), "HHmmss"); + LocalTime stringEndTm = this.convertStringToTime(stringEndDt[1].replace(":", ""), "HHmmss"); + + boolean stTmValid = this.isBetweenSunriseAndSunset(sunUp, sunDown, stringStTm); + boolean endTmValid = this.isBetweenSunriseAndSunset(sunUp, sunDown, stringEndTm); + + if (!stTmValid || !endTmValid) return false; + + return true; + } + /** * String to LocalTime - Instant는 시간정보만 담을 수 없음.. * diff --git a/pav-server/src/main/java/com/palnet/comn/utils/AirspaceUtils.java b/pav-server/src/main/java/com/palnet/comn/utils/AirspaceUtils.java index 06b7d540..66dbe156 100644 --- a/pav-server/src/main/java/com/palnet/comn/utils/AirspaceUtils.java +++ b/pav-server/src/main/java/com/palnet/comn/utils/AirspaceUtils.java @@ -100,7 +100,7 @@ public class AirspaceUtils { for (FeatureInfo featureInfo : this.airspaces) { Geometry airspaceGeometry = featureInfo.getGeometry(); int airspaceHighElev = featureInfo.getHighElev() != null ? featureInfo.getHighElev() : 0; - // 임시로 0~최대고도 기준으로 검증 + // 0~최대고도 기준으로 검증 if (airspaceHighElev == 0 || airspaceHighElev < targetHighElev) { if (airspaceGeometry.intersects(targetGeometry)) { return false;