diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/controller/BasLaancController.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/controller/BasLaancController.java index 2804444..966ec1f 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/controller/BasLaancController.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/controller/BasLaancController.java @@ -1,8 +1,6 @@ package com.palnet.biz.api.bas.laanc.controller; -import com.palnet.biz.api.bas.laanc.model.BasLaancLastRs; -import com.palnet.biz.api.bas.laanc.model.BasLaancPlanRq; -import com.palnet.biz.api.bas.laanc.model.BasLaancValidatedRs; +import com.palnet.biz.api.bas.laanc.model.*; import com.palnet.biz.api.bas.laanc.service.BasLaancService; import com.palnet.biz.api.comn.response.BasicResponse; import com.palnet.biz.api.comn.response.ErrorResponse; @@ -21,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -96,4 +95,52 @@ public class BasLaancController { return ResponseEntity.ok().body(new SuccessResponse<>(rs)); } + // 허용고도 조회 + @PostMapping(value = "/valid/elev") + @ApiOperation(value = "비행구역 허용고도 조회") + @Tag(name = "LAANC", description = "LAANC 관련 API") + public ResponseEntity getAllowableElevation(@RequestBody List rq) { + List rs = null; + try { + rs = basLaancService.getAllowableElevation(rq); + } catch (CustomException e) { + Map resultMap = new HashMap(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse(resultMap)); + } catch (Exception e) { + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(rs)); + } + + // TS 연동 + @PostMapping(value = "/valid/ts/pilot") + @ApiOperation(value = "TS 연동 - 비행자격 조회(조종사 자격증명, 기체보험 여부)") + @Tag(name = "LAANC", description = "LAANC 관련 API") + public ResponseEntity getTsValid(@RequestBody List idntfNumList) { + BasLaancTsRs rs = null; + try { + rs = basLaancService.getTsValid(idntfNumList); + } catch (CustomException e) { + Map resultMap = new HashMap(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse(resultMap)); + } catch (Exception e) { + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(rs)); + } + } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancPlanRq.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancPlanRq.java index 82c9c81..311d5dd 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancPlanRq.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancPlanRq.java @@ -1,6 +1,7 @@ package com.palnet.biz.api.bas.laanc.model; import com.palnet.biz.api.acnt.cstmr.model.AnctCstmrTermsModel; +import com.palnet.biz.api.external.model.PilotValidRs; import com.palnet.biz.jpa.entity.type.FltPurpose; import com.palnet.biz.jpa.entity.type.FltType; import lombok.Data; @@ -26,8 +27,10 @@ public class BasLaancPlanRq { // private List pilotList; private List arcrftList; - // laanc 정보 - private BasLaancValidatedRs validatedRs; // 약관동의 private List terms; + // laanc 정보 +// private BasLaancValidatedRs validatedRs; + + private List pilotValidRsList; // 자격여부 } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancTsRq.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancTsRq.java new file mode 100644 index 0000000..e5368ad --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancTsRq.java @@ -0,0 +1,30 @@ +package com.palnet.biz.api.bas.laanc.model; + +import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.media.Schema; +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 : BasLaancTsRq + * author : dhji + * date : 2023-10-25(025) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-25(025) dhji 최초 생성 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BasLaancTsRq { + @ApiParam(name = "기체신고번호", collectionFormat = ",") + private List IdntfNumList; +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancTsRs.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancTsRs.java new file mode 100644 index 0000000..9a2c2e5 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancTsRs.java @@ -0,0 +1,29 @@ +package com.palnet.biz.api.bas.laanc.model; + +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 : BasLaancTsRs + * author : dhji + * date : 2023-10-25(025) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-25(025) dhji 최초 생성 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BasLaancTsRs { + private boolean isValid; + private List pilotValidRsList; +} 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 50be0f1..fd6fd28 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 @@ -10,8 +10,8 @@ import lombok.NoArgsConstructor; import java.util.List; /** - * packageName : com.palnet.biz.api.bas.flight.model - * fileName : BasFlightPlanErrorRS + * packageName : com.palnet.biz.api.bas.laanc.model + * fileName : BasLaancValidatedRs * author : dhji * date : 2023-09-19(019) * description : @@ -26,6 +26,7 @@ import java.util.List; @AllArgsConstructor public class BasLaancValidatedRs { + private boolean isPilotQlfc; // 자격여부 private boolean isArcrftInsurance; // 항공기보험여부 @@ -33,11 +34,17 @@ public class BasLaancValidatedRs { private boolean isFlightArea; // 비행가능여부 - 비행가능 true private boolean isArcrftWeight; // 항공기중량여부 - 25kg 이하 true + private boolean isElev; // 고도여부 - 150m 이하 true - private boolean isReport; // 신고 대상 - 상업적이면서 기체중량2kg 이상일경우 - true + private boolean isReport; // 신고 대상 - 비상업적이고 기체중량 2kg미만일 경우 - false / 그외 true - private String corpRegYn; // 사업자 여부 + // 추가.... + private boolean isCommercial; // 비행유형 사업자 - true / 비사업자 - false + private boolean isSpacialFlight; // 특별비행여부 - true + + // TS 연동 +// private String corpRegYn; // 사업자 여부 private List pilotValidRsList; // 자격여부 // 활용안함. @@ -53,21 +60,32 @@ public class BasLaancValidatedRs { && isFlightArea // 비행가능여부 && isCheckingLance(); } - return isCheckingLance() + return isFlightArea // && !isPlanAreaDuplicatd // && !isArcrftDuplicated - && isFlightArea; + && isCheckingLance(); } public boolean isFlight() { return !isCheckingLance() || isValid(); } + // LAANC 승인 대상 여부 @JsonIgnore public boolean isCheckingLance() { return isEvaluatedTargetArea // 관제구역여부 - || !isElev // 150m 초과 + /* + 관제권 외 고도 150m초과에 대해서 승인 대상이나 담당자와의 협의가 필요하여 미승인 처리함 + || !isElev // 150m 초과 - + */ || !isArcrftWeight; // 25kg 초과 } + + // 특별승인 대상 여부 + @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 d75315a..a708243 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,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import com.palnet.biz.api.bas.laanc.model.*; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.springframework.stereotype.Service; @@ -19,12 +20,6 @@ import org.springframework.util.StringUtils; import com.palnet.biz.api.acnt.cstmr.model.AnctCstmrModel; import com.palnet.biz.api.acnt.cstmr.model.AnctCstmrTermsModel; import com.palnet.biz.api.acnt.jwt.utils.JwtTokenUtil; -import com.palnet.biz.api.bas.laanc.model.BasLaancArcrftModel; -import com.palnet.biz.api.bas.laanc.model.BasLaancAreaCoordModel; -import com.palnet.biz.api.bas.laanc.model.BasLaancAreaModel; -import com.palnet.biz.api.bas.laanc.model.BasLaancLastRs; -import com.palnet.biz.api.bas.laanc.model.BasLaancPlanRq; -import com.palnet.biz.api.bas.laanc.model.BasLaancValidatedRs; import com.palnet.biz.api.comn.file.model.LaancPdfModel; import com.palnet.biz.api.comn.file.service.ComnFileService; import com.palnet.biz.api.comn.sms.model.ComnSmsLaancAprovModel; @@ -102,17 +97,30 @@ public class BasLaancService { // LAANC 검증 public BasLaancValidatedRs validationLaanc(BasLaancPlanRq rq) { if (!this.laancParamValid(rq)) throw new CustomException(ErrorCode.NON_VALID_PARAMETER); + 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 초과이거나 상업적일 경우 - true + + // 신고 여부 - 비상업적이고 기체중량 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 -> { @@ -136,12 +144,6 @@ public class BasLaancService { // TODO end - 조종사 자격 및 기체보험 확인 - // 고도 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); /* 비행구역 및 기체 중복여부 확인 안하기로 함. // 비행구역 중복여부, 기체 중복여부 @@ -163,10 +165,10 @@ public class BasLaancService { // 비행계획서 등록, 약관 등록, 공문 생성 @Transactional public BasLaancLastRs createFlightPlan(BasLaancPlanRq rq) { - if (rq == null || rq.getValidatedRs() == null) throw new CustomException(ErrorCode.NON_VALID_PARAMETER); +// if (rq == null || rq.getValidatedRs() == null) throw new CustomException(ErrorCode.NON_VALID_PARAMETER); if (!this.laancParamValid(rq)) throw new CustomException(ErrorCode.NON_VALID_PARAMETER); - BasLaancValidatedRs basLaancValidatedRs = rq.getValidatedRs(); +// BasLaancValidatedRs basLaancValidatedRs = rq.getValidatedRs(); // BasLaancValidatedRs validatedAirspaceRs = this.validationPlanAirspace(rq); @@ -178,7 +180,7 @@ public class BasLaancService { // BasLaancMapper.MAPPER.merge(basLaancValidatedRs, validatedRs); // LAANC가 검증된것들만 DB저장 - if (!basLaancValidatedRs.isValid()) throw new CustomException(ErrorCode.PLAN_LAANC_NOT_VALID); +// if (!basLaancValidatedRs.isValid()) throw new CustomException(ErrorCode.PLAN_LAANC_NOT_VALID); // 비행계획서 @@ -206,7 +208,12 @@ public class BasLaancService { } fltPlanBas.setGroupId(groupId); // 사업자 유무 - TS 데이터 - fltPlanBas.setCorpRegYn(rq.getValidatedRs().getCorpRegYn()); // 사업자유무 + String corpRegYn = "N"; + if (rq.getPilotValidRsList() != null && !rq.getPilotValidRsList().isEmpty()) { + boolean isCorpReg = rq.getPilotValidRsList().stream().anyMatch(pilotValidRs -> "Y".equals(pilotValidRs.getCorpregyn())); + corpRegYn = isCorpReg ? "Y" : "N"; + } + fltPlanBas.setCorpRegYn(corpRegYn); // 사업자유무 fltPlanBas.setServiceType("PAV-KAC"); FltPlanBas rBasEntity = fltPlanBasRepository.save(fltPlanBas); @@ -240,8 +247,8 @@ public class BasLaancService { Integer planAreaSno = rAreaEntity.getPlanAreaSno(); List laancAreaModelCoordList = laancAreaModel.getCoordList(); if (laancAreaModelCoordList != null && !laancAreaModelCoordList.isEmpty()) { - laancAreaModelCoordList.get(0).getLat(); - laancAreaModelCoordList.get(0).getLon(); +// double lat = laancAreaModelCoordList.get(0).getLat(); +// double lon = laancAreaModelCoordList.get(0).getLon(); for (BasLaancAreaCoordModel basLaancAreaCoordModel : laancAreaModelCoordList) { FltPlanAreaCoord coordEntity = BasLaancMapper.MAPPER.modelToPlanEntity(basLaancAreaCoordModel); coordEntity.setPlanAreaSno(planAreaSno); @@ -269,8 +276,17 @@ public class BasLaancService { arcrftEntity.setPlanSno(planSno); arcrftEntity.setCreateUserId(userId); arcrftEntity.setUpdateUserId(userId); + + String arcrftInsuranceYn = "N"; // 추가 필드 - arcrftEntity.setAcrftInsuranceYn(rq.getValidatedRs().isArcrftInsurance() ? "Y" : "N"); // 보험여부 + if (rq.getPilotValidRsList() != null && !rq.getPilotValidRsList().isEmpty() && basLaancArcrftModel.getIdntfNum() != null) { + PilotValidRs pilotValidRs = rq.getPilotValidRsList().stream().filter(pilotValid -> pilotValid.getRq().getDeclarationnum().equals(arcrftEntity.getIdntfNum())).findFirst().orElse(null); + if (pilotValidRs != null) { + String arcrftinsuranceyn = pilotValidRs.getArcrftinsuranceyn(); + arcrftInsuranceYn = "Y".equals(arcrftinsuranceyn) ? "Y" : "N"; + } + } + arcrftEntity.setAcrftInsuranceYn(arcrftInsuranceYn); // 보험여부 // arcrftEntity.setInsuranceExperiod(null); // 보헌 유효기간 // arcrftEntity.setCorporationNm(null); // 법인명 fltPlanArcrftRepository.save(arcrftEntity); @@ -458,7 +474,6 @@ public class BasLaancService { return rs; } - */ private BasLaancValidatedRs validationPlanAirspace(BasLaancPlanRq rq) { @@ -586,4 +601,60 @@ public class BasLaancService { timeToCheck.isBefore(sunset); } + // 허용고도 조회 + public List getAllowableElevation(List rq) { + AirspaceUtils airspaceUtils = AirspaceUtils.getInstance(); + List allowElevationList = new ArrayList<>(); + for (BasLaancAreaModel area : rq) { + + //rq로 들어온 좌표로 버퍼좌표 생성 + List targetCoord = new ArrayList<>(); + List targetCoordBufferList = new ArrayList<>(); + + for (BasLaancAreaCoordModel basLaancAreaCoordModel : area.getCoordList()) { + Coordinate coords = new Coordinate(basLaancAreaCoordModel.getLon(), basLaancAreaCoordModel.getLat()); + targetCoord.add(coords); + } + + if ("LINE".equals(area.getAreaType())) { + List trans = areaUtils.transform(targetCoord, "EPSG:4326", "EPSG:5181"); + List bufferList = areaUtils.buffer(trans, area.getBufferZone()); + targetCoordBufferList = areaUtils.transform(bufferList, "EPSG:5181", "EPSG:4326"); + } else if ("POLYGON".equals(area.getAreaType())) { + targetCoordBufferList.addAll(targetCoord); + } else if ("CIRCLE".equals(area.getAreaType())) { + targetCoordBufferList = areaUtils.createCircle(targetCoord.get(0), area.getBufferZone()); + } + + Geometry targetGeometry = airspaceUtils.createGeometryByCoordinate(targetCoordBufferList); +// Integer fltElev = area.getFltElev() != null ? Integer.parseInt(area.getFltElev()) : 0; +// AirspaceUtils.FeatureInfo featureInfo = new AirspaceUtils.FeatureInfo(null, null, 0, fltElev, rqGeometry); + Integer allowElevation = airspaceUtils.getAllowElevation(targetGeometry); + allowElevationList.add(allowElevation); + } + + return allowElevationList; + } + + public BasLaancTsRs getTsValid(List idntfNumList) { +// List idntfNumList = rq.getIdntfNumList(); + Integer cstmrSno = jwtTokenUtil.getCstmrSnoByToken(); + AnctCstmrModel cstmrInfo = ptyCstmrQueryRepository.findByCstmrSno(cstmrSno); + String userCi = cstmrInfo.getIpinCi(); + List pilotValidRqList = idntfNumList.stream().map(declarationnum -> { + // TODO 기체보험 확인, 조종사 자격 확인 + return PilotValidRq.builder() + .pilotci(userCi) + .declarationnum(declarationnum) + .build(); + }).collect(Collectors.toList()); + + List pilotValidRsList = tsService.callPilotValid(pilotValidRqList); + boolean isValid = pilotValidRsList.stream().noneMatch(pilotValidRs -> !"Y".equals(pilotValidRs.getPilotcredentialyn()) || !"Y".equals(pilotValidRs.getArcrftinsuranceyn())); + BasLaancTsRs rs = BasLaancTsRs.builder() + .isValid(isValid) + .pilotValidRsList(pilotValidRsList) + .build(); + return rs; + } } 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 d92d4d1..dd8e4db 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 @@ -119,11 +119,27 @@ public class AirspaceUtils { return true; } + // get allowable elevation + public Integer getAllowElevation(Geometry target) { + Integer allowElevation = null; + if (this.airspaces == null || this.airspaces.isEmpty()) return null; + // target과 겹치는 airspace 조회 + List duplicationAirspace = this.airspaces.stream().filter(featureInfo -> { + Geometry featureGeometry = featureInfo.getGeometry(); + return featureGeometry.intersects(target); + }).collect(Collectors.toList()); + // 중복된 airspace가 없을 경우 기본 허용고도 반환 150m(관제권x) + if(duplicationAirspace.isEmpty()) return 150; + allowElevation = duplicationAirspace.stream().map(FeatureInfo::getHighElev).min(Integer::compareTo).orElse(150); + return allowElevation; + } + // convert coordiate to geometry public Geometry createGeometryByCoordinate(List target) { return this.createGeometryByCoordinate(target, "Polygon"); } + // convert coordiate to geometry public Geometry createGeometryByCoordinate(List target, String type) { Geometry geometry = null; if ("Polygon".equals(type)) { diff --git a/pav-server/src/main/resources/air/elev2d/gimpo-airport-2d-elev.json b/pav-server/src/main/resources/air/elev2d/gimpo-airport-2d-elev.json index c698e7a..60686c2 100644 --- a/pav-server/src/main/resources/air/elev2d/gimpo-airport-2d-elev.json +++ b/pav-server/src/main/resources/air/elev2d/gimpo-airport-2d-elev.json @@ -3900,7 +3900,7 @@ "type": "0006", "use": true, "lowElev": "0", - "highElev": "40" + "highElev": "45" } }, { @@ -4458,7 +4458,7 @@ "type": "0006", "use": true, "lowElev": "0", - "highElev": "40" + "highElev": "45" } }, {