package com.interplug.qcast.biz.pwrGnrSimulation; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.interplug.qcast.biz.pwrGnrSimulation.dto.*; import com.interplug.qcast.config.Exception.ErrorCode; import com.interplug.qcast.config.Exception.QcastException; import com.interplug.qcast.config.message.Messages; import com.interplug.qcast.util.InterfaceQsp; import java.awt.*; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor public class PwrGnrSimService { private final InterfaceQsp interfaceQsp; @Autowired Messages message; @Value("${qsp.url}") private String qspUrl; @Value("${qsp.simulation-guide-info-url}") private String qspSimulationGuideInfUrl; private final PwrGnrSimMapper pwrGnrSimMapper; static String[][] module_data = readValuesFromFileString2("template/pwrGnrSimulation/module_data.txt"); static String[][] pcs_data = readValuesFromFileString2("template/pwrGnrSimulation/pcs_data.txt"); double[] snow = readValuesFromFile("template/pwrGnrSimulation/snow.txt"); // cosθz double[][] cosThetaZ = readValuesFromFile2("template/pwrGnrSimulation/cosThetaZ.txt"); // hinode_nichibotsu double[][] hinode_nichibotsu = readValuesFromFile2("template/pwrGnrSimulation/hinode_nichibotsu.txt"); double[] ion = readValuesFromFile("template/pwrGnrSimulation/ion.txt"); // 시간 각도 double[][] jikakudo = readValuesFromFile2("template/pwrGnrSimulation/jikakudo.txt"); double[] jimenhansharitsu = readValuesFromFile("template/pwrGnrSimulation/jimenhansharitsu.txt"); // 균시차 // double[] kinjisa = readValuesFromFile("template/pwrGnrSimulation/kinjisa.txt"); // 위도 경도[rad] double[][] latlng = readValuesFromFile2("template/pwrGnrSimulation/latlng.txt"); double[][] rd = readValuesFromFile2("template/pwrGnrSimulation/rd.txt"); double[][] rdx = readValuesFromFile2("template/pwrGnrSimulation/rdx.txt"); // double[][] rt = readValuesFromFile2("template/pwrGnrSimulation/rt.txt"); double[][] rtx = readValuesFromFile2("template/pwrGnrSimulation/rtx.txt"); // 산란 일사 double[] sanrannissha = readValuesFromFile("template/pwrGnrSimulation/sanrannissha.txt"); // 수평면 일사 double[] suiheimennissha = readValuesFromFile("template/pwrGnrSimulation/suiheimennissha.txt"); // 태양 적위 double[] taiyousekii = readValuesFromFile("template/pwrGnrSimulation/taiyousekii.txt"); static double[][] peakcut_by_pcs_data = readValuesFromFile2("template/pwrGnrSimulation/peakcut_by_pcs_data.txt"); static double[][] tashounen_data_deg0 = readValuesFromFile2("template/pwrGnrSimulation/tashounen_data_deg0.txt"); static double[][] tashounen_data_deg90 = readValuesFromFile2("template/pwrGnrSimulation/tashounen_data_deg90.txt"); static double[][] amp_peakcut_data_deg0 = readValuesFromFile2("template/pwrGnrSimulation/amp_peakcut_data_deg0.txt"); static double[][] amp_peakcut_data_deg90 = readValuesFromFile2("template/pwrGnrSimulation/amp_peakcut_data_deg90.txt"); /** * 발전시뮬레이션 계산로직 * * @param pwrGnrSimRequest * @return * @throws Exception */ public PwrGnrSimResponse selectPwrGnrSimulation(PwrGnrSimRequest pwrGnrSimRequest) throws Exception { // 데이터가 없어서 오류가 발생한 경우, 빈값으로 리턴 PwrGnrSimResponse exceptionRes = new PwrGnrSimResponse(); int[] exceptionData = new int[13]; exceptionRes.setFrcPwrGnrList(exceptionData); // 견적서 정보를 조회한다. PwrGnrSimPlanResponse planInfo = pwrGnrSimMapper.selectPlanInfo(pwrGnrSimRequest); // 지역이 없으면 계산 불가능 if (planInfo == null || planInfo.getAreaId() == 0) { log.error("지역값이 없음."); return exceptionRes; } // 견적서의 지붕재와 아이템 정보 조회한다. List roofItemList = pwrGnrSimMapper.selectRoofItemList(pwrGnrSimRequest); // 지붕재 또는 지붕재에 아이템이 없음. if (roofItemList == null || roofItemList.size() == 0) { log.error("지붕재 또는 지붕재에 아이템이 없음."); return exceptionRes; } List moduleList = roofItemList.stream() .filter(item -> "MODULE_".equals(item.getItemGroup())) // itemGroup이 MODULE_인 항목만 필터링 .collect(Collectors.groupingBy(PwrGnrSimRoofResponse::getRoofNo)) // roofNo별로 그룹화 .values() .stream() .flatMap(List::stream) // 그룹화된 리스트를 다시 평탄화 .collect(Collectors.toList()); // 모듈 아이템이 없음. if (moduleList == null || moduleList.size() == 0) { log.error("모듈 아이템이 없음."); return exceptionRes; } List pcsList = roofItemList.stream() .filter(item -> "PC_".equals(item.getItemGroup())) // itemGroup이 PC_인 항목만 필터링 .collect( Collectors.groupingBy( PwrGnrSimRoofResponse::getItemId, Collectors.reducing( (item1, item2) -> { // item1의 amount에 item2의 amount를 더한 새로운 객체 생성 item1.setAmount( Integer.parseInt(item1.getAmount()) + Integer.parseInt(item2.getAmount()) + ""); return item1; }))) .values() .stream() .flatMap(Optional::stream) // Optional을 평탄화 .collect(Collectors.toList()); // pcs 아이템이 없음. if (pcsList == null || pcsList.size() == 0) { log.error("PCS 아이템이 없음."); return exceptionRes; } List roofList = roofItemList.stream() .collect( Collectors.toMap( PwrGnrSimRoofResponse::getRoofNo, // key는 roofNo로 Function.identity(), // value는 객체 자체로 (existing, replacement) -> existing // 중복 시 기존 객체 유지 )) .values() .stream() .collect(Collectors.toList()); int roofLength = roofList.size(); // Set의 크기 = 고유 roofNo 개수 // 견적서 정보 기준으로 발전시뮬레이션 계산을 진행한다. PwrGnrSimRequest pwrGnrSimReq = new PwrGnrSimRequest(); pwrGnrSimReq.setSimulationPointNumber(planInfo.getAreaId() - 1); double[] dKoubai = new double[roofLength]; double[] dHoui = new double[roofLength]; int[] dModuleInput1 = new int[roofLength]; int[] dModuleInput2 = new int[roofLength]; int[] dModuleInput3 = new int[roofLength]; // pcs 정보 int k = 0; for (PwrGnrSimRoofResponse p : pcsList) { if (k == 0) { pwrGnrSimReq.setPcs1(p.getItemId()); pwrGnrSimReq.setPcsInput1(Integer.parseInt(p.getAmount())); } else if (k == 1) { pwrGnrSimReq.setPcs2(p.getItemId()); pwrGnrSimReq.setPcsInput2(Integer.parseInt(p.getAmount())); } else { pwrGnrSimReq.setPcs3(p.getItemId()); pwrGnrSimReq.setPcsInput3(Integer.parseInt(p.getAmount())); } } // module 정보 k = 0; for (PwrGnrSimRoofResponse m : moduleList) { if (k == 0) { pwrGnrSimReq.setModule1(m.getItemId()); } else if (k == 1) { pwrGnrSimReq.setModule2(m.getItemId()); } else { pwrGnrSimReq.setModule3(m.getItemId()); } k++; } // 전체 시스템 용량 double dSpecification = 0.0; int i = 0; for (PwrGnrSimRoofResponse data : roofList) { if (data.getClassType() == 0) { // 경사 dKoubai[i] = (2 * Math.PI) * ((Math.atan(Double.parseDouble(data.getSlopeAngle()) / 10) * 180 / Math.PI) / 360); } else { // 각도 dKoubai[i] = (2 * Math.PI) * (Double.parseDouble(data.getSlopeAngle()) / 360); } dHoui[i] = Math.abs((2 * Math.PI) * (Double.parseDouble(data.getAzimuth()) / 360)); // 지붕별 모듈정보 셋팅 int j = 0; for (PwrGnrSimRoofResponse m : moduleList) { if (data.getRoofNo().equals(m.getRoofNo())) { dSpecification += Double.parseDouble(m.getSpecification()); if (j == 0) { dModuleInput1[i] = Integer.parseInt(m.getAmount()); } else if (j == 1) { dModuleInput2[i] = Integer.parseInt(m.getAmount()); } else { dModuleInput3[i] = Integer.parseInt(m.getAmount()); } j++; } } i++; } pwrGnrSimReq.setKoubai(dKoubai); pwrGnrSimReq.setHoui(dHoui); pwrGnrSimReq.setModuleInput1(dModuleInput1); pwrGnrSimReq.setModuleInput2(dModuleInput2); pwrGnrSimReq.setModuleInput3(dModuleInput3); // 발전시뮬레이션 결과와 견적서 정보를 return 한다. PwrGnrSimResponse pwrGnrSimRes = this.calcResults(pwrGnrSimReq, roofLength); pwrGnrSimRes.setObjectNo(planInfo.getObjectNo()); pwrGnrSimRes.setPlanNo(planInfo.getPlanNo()); pwrGnrSimRes.setDrawingEstimateCreateDate(planInfo.getDrawingEstimateCreateDate()); pwrGnrSimRes.setCapacity(String.valueOf(dSpecification)); pwrGnrSimRes.setAnlFrcsGnrt(pwrGnrSimRes.getFrcPwrGnrList()[12]); pwrGnrSimRes.setPrefName(planInfo.getPrefName()); pwrGnrSimRes.setAreaName(planInfo.getAreaName()); pwrGnrSimRes.setSnowfall(planInfo.getSnowfall()); pwrGnrSimRes.setStandardWindSpeedId(planInfo.getStandardWindSpeedId()); pwrGnrSimRes.setRoofModuleList(moduleList); pwrGnrSimRes.setPcsList(pcsList); return pwrGnrSimRes; } public PwrGnrSimResponse calcResults(PwrGnrSimRequest pwrGnrSimReq, int roofLength) throws Exception { int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 각 월별 일수 선언 double module_system_loss = 0; double ondo_keisu = 0; for (int i = 0; i < module_data.length; i++) { // 모듈 시스템 손실과 모듈의 온도 계수 가져오기 if (pwrGnrSimReq.getModule1().equals(module_data[i][0])) { module_system_loss = Double.valueOf(module_data[i][6]); ondo_keisu = Double.valueOf(module_data[i][7]); } } // 각 월별 온도 손실 계산 double[] ondo_sonshitsu = new double[12]; ondo_sonshitsu[0] = 1 - 0.1 * ondo_keisu / 0.5; ondo_sonshitsu[1] = 1 - 0.1 * ondo_keisu / 0.5; ondo_sonshitsu[2] = 1 - 0.1 * ondo_keisu / 0.5; ondo_sonshitsu[3] = 1 - 0.15 * ondo_keisu / 0.5; ondo_sonshitsu[4] = 1 - 0.15 * ondo_keisu / 0.5; ondo_sonshitsu[5] = 1 - 0.2 * ondo_keisu / 0.5; ondo_sonshitsu[6] = 1 - 0.2 * ondo_keisu / 0.5; ondo_sonshitsu[7] = 1 - 0.2 * ondo_keisu / 0.5; ondo_sonshitsu[8] = 1 - 0.2 * ondo_keisu / 0.5; ondo_sonshitsu[9] = 1 - 0.15 * ondo_keisu / 0.5; ondo_sonshitsu[10] = 1 - 0.15 * ondo_keisu / 0.5; ondo_sonshitsu[11] = 1 - 0.1 * ondo_keisu / 0.5; // PCS1 선택 정보 double pcs1_henkankouritsu = 0; double pcs1_youryou = 0; // double pcs1_daidenryu = 0; // double pcs1_sekisairitsu = 0; // PCS2 선택 정보 double pcs2_henkankouritsu = 1; double pcs2_youryou = 0; // PCS3 선택 정보 double pcs3_henkankouritsu = 1; double pcs3_youryou = 0; for (int i = 0; i < pcs_data.length; i++) { // pcs1 변환 효율과 용량, 대전류 대응, 허용 적재율 가져오기 if (pwrGnrSimReq.getPcs1().equals(pcs_data[i][0])) { pcs1_henkankouritsu = Double.parseDouble(pcs_data[i][3]) / 100; pcs1_youryou = Double.parseDouble(pcs_data[i][4]); // pcs1_daidenryu = Double.parseDouble(pcs_data[i][9]); // pcs1_sekisairitsu = Double.parseDouble(pcs_data[i][5]); break; } } for (int i = 0; i < pcs_data.length; i++) { // pcs2 변환 효율과 용량 가져오기 if (pwrGnrSimReq.getPcs2() != null && pwrGnrSimReq.getPcs2().equals(pcs_data[i][0])) { pcs2_henkankouritsu = Double.parseDouble(pcs_data[i][3]) / 100; pcs2_youryou = Double.parseDouble(pcs_data[i][4]); break; } else { pcs2_henkankouritsu = 1; pcs2_youryou = 0; } } for (int i = 0; i < pcs_data.length; i++) { // pcs3 변환 효율과 용량 가져오기 if (pwrGnrSimReq.getPcs3() != null && pwrGnrSimReq.getPcs3().equals(pcs_data[i][0])) { pcs3_henkankouritsu = Double.parseDouble(pcs_data[i][3]) / 100; pcs3_youryou = Double.parseDouble(pcs_data[i][4]); break; } else { pcs3_henkankouritsu = 1; pcs3_youryou = 0; } } // pcs1과 pcs2, pcs3의 실효 변환 효율 중 낮은 값을 사용 double pcs_henkankouritsu = Math.min(pcs1_henkankouritsu, Math.min(pcs3_henkankouritsu, pcs2_henkankouritsu)); // pcs 총 용량 계산 double pcs_youryou_total = (pcs1_youryou * Double.valueOf(pwrGnrSimReq.getPcsInput1())) + (pcs2_youryou * Double.valueOf(pwrGnrSimReq.getPcsInput2())) + (pcs3_youryou * Double.valueOf(pwrGnrSimReq.getPcsInput3())); double module1_watt = 0, module1_watt_flash = 0 /*, module1_amp = 0*/; double module2_watt = 0, module2_watt_flash = 0; double module3_watt = 0, module3_watt_flash = 0; // 모듈 1의 출력 획득 for (int i = 0; i < module_data.length; i++) { if (pwrGnrSimReq.getModule1() != null && pwrGnrSimReq.getModule1().equals(module_data[i][0])) { module1_watt = Double.parseDouble(module_data[i][2]); module1_watt_flash = module1_watt + 2; // 모듈 1 출력 + 2w (플래시) // module1_amp = Double.parseDouble(module_data[i][9]); break; } } // 모듈 2의 출력 획득 for (int i = 0; i < module_data.length; i++) { if (pwrGnrSimReq.getModule2() != null && pwrGnrSimReq.getModule2().equals(module_data[i][0])) { module2_watt = Double.parseDouble(module_data[i][2]); module2_watt_flash = module2_watt + 2; // 모듈 2 출력 + 2w (플래시) break; } else { module2_watt = 0; module2_watt_flash = 0; } } // 모듈 3의 출력 획득 for (int i = 0; i < module_data.length; i++) { if (pwrGnrSimReq.getModule3() != null && pwrGnrSimReq.getModule3().equals(module_data[i][0])) { module3_watt = Double.parseDouble(module_data[i][2]); module3_watt_flash = module3_watt + 2; // 모듈 3 출력 + 2w (플래시) break; } else { module3_watt = 0; module3_watt_flash = 0; } } int module1_number_total = 0; int module2_number_total = 0; int module3_number_total = 0; for (int i = 0; i < roofLength; i++) { if (!Double.isNaN(pwrGnrSimReq.getKoubai()[i]) && !Double.isNaN(pwrGnrSimReq.getHoui()[i])) { // 설치면 기울기와 방위가 입력되어 있으면 모듈의 매수를 취득 module1_number_total += pwrGnrSimReq.getModuleInput1()[i]; module2_number_total += pwrGnrSimReq.getModuleInput2()[i]; module3_number_total += pwrGnrSimReq.getModuleInput3()[i]; } } // 모듈 1, 2, 3 총 용량 계산 double module1_youryou_total = module1_watt * module1_number_total; double module2_youryou_total = module2_watt * module2_number_total; double module3_youryou_total = module3_watt * module3_number_total; // 모듈 총 용량 계산 double module_youryou_total = module1_youryou_total + module2_youryou_total + module3_youryou_total; // 적재율 [실수] 계산 double sekisairitsu = module_youryou_total / (pcs_youryou_total * 1000); // 피크 컷 및 발전량 배열 선언 int[] hatsudenryou_all = new int[13]; // 피크 컷 없음 발전량(적설 고려 없음) // double[] hatsudenryou_all_snow = new double[12]; // 피크 컷 없음 발전량(적설 고려 있음) // double[] hatsudenryou_peakcut_all = new double[12]; // 피크 컷 있음 발전량(적설 고려 없음) // double[] hatsudenryou_peakcut_all_snow = new double[12]; // 피크 컷 있음 발전량(적설 고려 있음) if (sekisairitsu <= 3) { // 설치면 피크 컷 및 발전량 배열 선언 double[][] hatsudenryou = new double[roofLength][12]; // 전체 설치면의 피크 컷 없음 발전량 double[][] hatsudenryou_peakcut = new double[roofLength][12]; // 전체 설치면의 피크 컷 있음 발전량 int simulationPointNumber = pwrGnrSimReq.getSimulationPointNumber(); // 우기일 사량 관측 지점의 경우, 계산을 하지 않는다 , 이리 오모테 섬, 이시가키 섬, 오하라, 하 테루 간] if (simulationPointNumber != 361 || simulationPointNumber != 362 || simulationPointNumber != 363 || simulationPointNumber != 364 || simulationPointNumber != 365 || simulationPointNumber != 827 || simulationPointNumber != 828 || simulationPointNumber != 829 || simulationPointNumber != 830 || simulationPointNumber != 831 || simulationPointNumber != 832 || simulationPointNumber != 833 || simulationPointNumber != 834 || simulationPointNumber != 835 || simulationPointNumber != 836 || simulationPointNumber != 837) { for (int i = 0; i < roofLength; i++) { // 설치면 발전량 계산 if (!Double.isNaN(pwrGnrSimReq.getKoubai()[i]) && !Double.isNaN(pwrGnrSimReq.getHoui()[i])) { // 설치면의 사면 일사량 계산 double[] shamen_nissha = this.shamenNissharyou( pwrGnrSimReq.getKoubai()[i], pwrGnrSimReq.getHoui()[i], simulationPointNumber); double[] peakcut = this.peakcut( simulationPointNumber, pwrGnrSimReq.getKoubai()[i], pwrGnrSimReq.getHoui()[i], sekisairitsu, pwrGnrSimReq.getModule1(), pwrGnrSimReq.getPcs1(), pcs_henkankouritsu); for (int j = 0; j < 12; j++) { // 피크 컷 없음 발전량 계산 // shamen_nissha 조금 다름 hatsudenryou[i][j] = shamen_nissha[j] * days[j] * ondo_sonshitsu[j] * pcs_henkankouritsu * ((module1_watt_flash * pwrGnrSimReq.getModuleInput1()[i]) + (module2_watt_flash * pwrGnrSimReq.getModuleInput2()[i]) + (module3_watt_flash * pwrGnrSimReq.getModuleInput3()[i])) / 1000 * module_system_loss * peakcut[0]; // 피크 컷 있음 발전량 계산 hatsudenryou_peakcut[i][j] = shamen_nissha[j] * days[j] * ondo_sonshitsu[j] * pcs_henkankouritsu * ((module1_watt_flash * pwrGnrSimReq.getModuleInput1()[i]) + (module2_watt_flash * pwrGnrSimReq.getModuleInput2()[i]) + (module3_watt_flash * pwrGnrSimReq.getModuleInput3()[i])) / 1000 * module_system_loss * peakcut[1]; } } } // 설치면 1~4의 합계 발전량 for (int j = 0; j < 12; j++) { for (int i = 0; i < roofLength; i++) { hatsudenryou_all[j] += (int) Math.round(hatsudenryou[i][j]); // hatsudenryou_peakcut_all[j] += hatsudenryou_peakcut[i][j]; } // hatsudenryou_all_snow[j] = // hatsudenryou_all[j] * (1 - (snow[(simulationPointNumber * 12) + j])); // hatsudenryou_peakcut_all_snow[j] = // hatsudenryou_peakcut_all[j] * (1 - (snow[(simulationPointNumber * 12) + // j])); } } // 마지막에 총합계 추가 hatsudenryou_all[12] = Arrays.stream(hatsudenryou_all).sum(); } // 결과 확인용 System.err.println("hatsudenryou_all 결과 ::: " + Arrays.toString(hatsudenryou_all)); // System.err.println("hatsudenryou_all_snow 결과 ::: " + // Arrays.toString(hatsudenryou_all_snow)); // System.err.println( // "hatsudenryou_peakcut_all 결과 ::: " + Arrays.toString(hatsudenryou_peakcut_all)); // System.err.println( // "hatsudenryou_peakcut_all_snow 결과 ::: " + // Arrays.toString(hatsudenryou_peakcut_all_snow)); PwrGnrSimResponse pwrGnrSimRes = new PwrGnrSimResponse(); // pwrGnrSimRes.setSekisairitsu(sekisairitsu); pwrGnrSimRes.setFrcPwrGnrList(hatsudenryou_all); // pwrGnrSimRes.setHatsudenryouAllSnow(hatsudenryou_all_snow); // pwrGnrSimRes.setHatsudenryouPeakcutAll(hatsudenryou_peakcut_all); // pwrGnrSimRes.setHatsudenryouPeakcutAllSnow(hatsudenryou_peakcut_all_snow); return pwrGnrSimRes; } // 설치면의 사면 일사량 계산 public static double[] peakcut( int simulationPointNumber, double koubai, double houi, double sekisairitsu, String moduleName, String pcsName, double pcsHenkankouritsu) { // koubai[rad]을 [도]로 변환 _아래 계산식도 변경된 값으로 계산됨. koubai = (koubai / (2 * Math.PI)) * 360; // 방위각 0도 설치각에서의 전력 피크 컷 double wattPeakcutAboutDeg0 = computePowerPeakCutDeg0(simulationPointNumber, sekisairitsu, koubai); // 방위각 90도 설치각에서의 전력 피크 컷 double watt_peakcut_about_deg90 = computePowerPeakCutDeg90(simulationPointNumber, sekisairitsu, koubai); double matrix_multiply1 = (1 * watt_peakcut_about_deg90) + (-1 * watt_peakcut_about_deg90); double matrix_multiply2 = (-1 * watt_peakcut_about_deg90) + (2 * watt_peakcut_about_deg90); // houi[rad]을 [도]로 변환 houi = (houi / (2 * Math.PI)) * 360; // 電力ピークカット double watt_peakcut_result = ((matrix_multiply1 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply2) < 0.1) ? 0.1 : (matrix_multiply1 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply2); //////////// // pcs1과 모듈 1의 전류차 구하기 용도 double module_amp = 0; double pcs_amp = 0; boolean pcsAmpFound = false; for (int j = 0; j < module_data.length; j++) { if (moduleName.equals(module_data[j][0])) { module_amp = Double.valueOf(module_data[j][9]); break; } } for (int j = 0; j < pcs_data.length; j++) { if (pcsName.equals(pcs_data[j][0])) { pcs_amp = Double.valueOf(pcs_data[j][8]); pcsAmpFound = true; break; } } // pcs1과 모듈 1의 전류차(pcs의 입력 전류 데이터가 없는 경우는 0) double amp_peakcut_result = 1; // 디폴트 : pcs1이 없거나 pcs1과모듈1dml wjsfbckrk 0 보다 작은경우 if (pcsAmpFound) { // pcs1과 모듈 1의 전류차(pcs의 입력 전류 데이터가 없는 경우는 0) double amp_delta = module_amp - pcs_amp; if (amp_delta >= 0) { double amp_peakcut_about_deg0 = computeAmpPeakCutDeg0( simulationPointNumber, sekisairitsu, koubai, pcsAmpFound, amp_delta); double amp_peakcut_about_deg90 = computeAmpPeakCutDeg90(simulationPointNumber, sekisairitsu, koubai, amp_delta); double matrix_multiply3 = (1 * amp_peakcut_about_deg0) + (-1 * amp_peakcut_about_deg90); double matrix_multiply4 = (-1 * amp_peakcut_about_deg0) + (2 * amp_peakcut_about_deg90); // 전류 피크 컷 amp_peakcut_result = 1 - (((matrix_multiply3 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply4) < 0.001) ? 0 : (matrix_multiply3 * (Math.cos(houi * Math.PI / 180) + 1) + matrix_multiply4)); } } ////////////// // 전력 피크 컷과 전류 피크 컷의 최소값 계산 if (sekisairitsu >= 1.25) { sekisairitsu *= 100; int sekisairitsuCeil = (int) Math.ceil(sekisairitsu / 5.0) * 5; double pcsEfficiency = (pcsHenkankouritsu * 1000 <= 985) ? Math.ceil(pcsHenkankouritsu * 1000 / 5) * 5 : 985; int i = 1, j = 1; while (i < peakcut_by_pcs_data.length && peakcut_by_pcs_data[i][0] != sekisairitsuCeil) i++; while (j < peakcut_by_pcs_data[0].length && peakcut_by_pcs_data[0][j] != pcsEfficiency) j++; double peakcutByPcs = peakcut_by_pcs_data[i][j]; double peakcutByPcsKijun = peakcut_by_pcs_data[i][10]; double peakcutByPcsResult = peakcutByPcsKijun - peakcutByPcs; watt_peakcut_result = ((100 - wattPeakcutAboutDeg0 + peakcutByPcsResult) / 100 < 1) ? ((100 - wattPeakcutAboutDeg0 + peakcutByPcsResult) / 100) : 1.0; } else { watt_peakcut_result = 1.0; } // 피크컷이 없는 경우, 발전량에 곱하는 피크컷 계수 double[] peakcutResult = new double[2]; peakcutResult[0] = amp_peakcut_result; peakcutResult[1] = Math.min(watt_peakcut_result, amp_peakcut_result); return peakcutResult; } /** 0도 방위 전력 피크 컷을 계산하는 방법 */ private static double computePowerPeakCutDeg0( int simulationPointNumber, double sekisairitsu, double koubai) { // 0度近似曲線係数 double[] x = {1, 5, 15, 20, 30, 40}; double[] y = new double[6]; double x1, x2, y1, y2; int m1; double[] z1 = new double[4]; // 적재율과 다조년 데이터(방위 0도)에서 y를 대입 for (int i = 0; i < 6; i++) { int idx = i * 6; y[i] = tashounen_data_deg0[simulationPointNumber][idx] * Math.pow(sekisairitsu, 5) + tashounen_data_deg0[simulationPointNumber][idx + 1] * Math.pow(sekisairitsu, 4) + tashounen_data_deg0[simulationPointNumber][idx + 2] * Math.pow(sekisairitsu, 3) + tashounen_data_deg0[simulationPointNumber][idx + 3] * Math.pow(sekisairitsu, 2) + tashounen_data_deg0[simulationPointNumber][idx + 4] * sekisairitsu + tashounen_data_deg0[simulationPointNumber][idx + 5]; } // 3차 최소 제곱법 double[][] w = new double[4][5]; double[][] A = new double[6][4]; for (int i = 0; i < 6; i++) { A[i][2] = x[i]; A[i][3] = 1.0; x1 = A[i][2]; x2 = x1; for (int j = 1; j >= 0; j--) { x2 *= x1; A[i][j] = x2; } } for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { w[i][j] = 0.0; for (int k = 0; k < 6; k++) { w[i][j] += A[k][i] * A[k][j]; } } } for (int i = 0; i < 4; i++) { w[i][4] = 0.0; for (int j = 0; j < 6; j++) { w[i][4] += A[j][i] * y[j]; } } for (int i = 0; i < 4; i++) { y1 = 0.0; m1 = i + 1; int m2Index = 0; for (int j = i; j < 4; j++) { y2 = Math.abs(w[j][i]); if (y1 < y2) { y1 = y2; m2Index = j; } } for (int j = i; j < 5; j++) { double temp = w[i][j]; w[i][j] = w[m2Index][j]; w[m2Index][j] = temp; } y1 = 1.0 / w[i][i]; for (int j = m1; j < 5; j++) { w[i][j] *= y1; } for (int j = 0; j < 4; j++) { if (j != i) { for (int k = m1; k < 5; k++) { w[j][k] -= w[j][i] * w[i][k]; } } } } // z1에 3차, 2차, 1차 계수와 절편을 대입 for (int i = 0; i < 4; i++) { z1[i] = w[i][4]; } // 방위각 90도 설치각에서의 전력 피크 컷 return (z1[0] * Math.pow(koubai, 3)) + (z1[1] * Math.pow(koubai, 2)) + (z1[2] * koubai) + z1[3]; } /** 전류 피크 컷(5차) 90도 근사 곡선 계수 */ private static double computeAmpPeakCutDeg90( int simulationPointNumber, double sekisairitsu, double koubai, double amp_delta) { // 초기 설정 double[] x = {5, 15, 20, 30, 40, 50}; double[] z2 = new double[6]; double[] y = new double[6]; // 데이터 초기화 - y 값 계산 (amp_delta의 다항식 계산) for (int i = 0; i < 6; i++) { int idx = i * 5; y[i] = amp_peakcut_data_deg90[simulationPointNumber][idx] * Math.pow(amp_delta, 4) + amp_peakcut_data_deg90[simulationPointNumber][idx + 1] * Math.pow(amp_delta, 3) + amp_peakcut_data_deg90[simulationPointNumber][idx + 2] * Math.pow(amp_delta, 2) + amp_peakcut_data_deg90[simulationPointNumber][idx + 3] * amp_delta + amp_peakcut_data_deg90[simulationPointNumber][idx + 4]; } // 최소 제곱법을 위한 행렬 A와 w 초기화 double[][] w = new double[6][7]; double[][] A = new double[6][6]; // A 행렬 설정 (x 값을 사용하여 다항식 계수 생성) for (int i1 = 0; i1 < 6; i1++) { A[i1][4] = x[i1]; A[i1][5] = 1.0; double x1 = A[i1][4]; double x2 = x1; for (int i2 = 3; i2 >= 0; i2--) { x2 *= x1; A[i1][i2] = x2; } } // w 행렬 초기화 for (int i1 = 0; i1 < 6; i1++) { for (int i2 = 0; i2 < 6; i2++) { w[i1][i2] = 0.0; for (int i3 = 0; i3 < 6; i3++) { w[i1][i2] += A[i3][i1] * A[i3][i2]; } } } // w의 마지막 열 값 설정 for (int i1 = 0; i1 < 6; i1++) { w[i1][6] = 0.0; for (int i2 = 0; i2 < 6; i2++) { w[i1][6] += A[i2][i1] * y[i2]; } } // 가우스 소거법을 통해 연립방정식 해 구하기 for (int i1 = 0; i1 < 6; i1++) { double y1 = 0.0; int m1 = i1 + 1; int m2 = 0; for (int i2 = i1; i2 < 6; i2++) { double y2 = Math.abs(w[i2][i1]); if (y1 < y2) { y1 = y2; m2 = i2; } } for (int i2 = i1; i2 < 7; i2++) { y1 = w[i1][i2]; w[i1][i2] = w[m2][i2]; w[m2][i2] = y1; } y1 = 1.0 / w[i1][i1]; for (int i2 = m1; i2 < 7; i2++) { w[i1][i2] *= y1; } for (int i2 = 0; i2 < 6; i2++) { if (i2 != i1) { for (int i3 = m1; i3 < 7; i3++) { w[i2][i3] -= w[i2][i1] * w[i1][i3]; } } } } // z2에 계수 저장 for (int i1 = 0; i1 < 6; i1++) { z2[i1] = w[i1][6]; } return z2[0] * Math.pow(koubai, 5) + z2[1] * Math.pow(koubai, 4) + z2[2] * Math.pow(koubai, 3) + z2[3] * Math.pow(koubai, 2) + z2[4] * koubai + z2[5]; } /** 전류 피크 컷(5차) 0도 근사 곡선 계수 */ private static double computeAmpPeakCutDeg0( int simulationPointNumber, double sekisairitsu, double koubai, boolean pcsAmpFound, double amp_delta) { double[] x = {5, 15, 20, 30, 40, 50}; double[] z2 = new double[6]; double[] y = new double[6]; for (int i = 0; i < 6; i++) { int idx = i * 5; y[i] = amp_peakcut_data_deg0[simulationPointNumber][idx] * Math.pow(amp_delta, 4) + amp_peakcut_data_deg0[simulationPointNumber][idx + 1] * Math.pow(amp_delta, 3) + amp_peakcut_data_deg0[simulationPointNumber][idx + 2] * Math.pow(amp_delta, 2) + amp_peakcut_data_deg0[simulationPointNumber][idx + 3] * amp_delta + amp_peakcut_data_deg0[simulationPointNumber][idx + 4]; } double[][] w = new double[6][7]; double[][] A = new double[6][6]; for (int i1 = 0; i1 < 6; i1++) { A[i1][4] = x[i1]; A[i1][5] = 1.0; double x1 = A[i1][4]; double x2 = x1; for (int i2 = 3; i2 >= 0; i2--) { x2 *= x1; A[i1][i2] = x2; } } for (int i1 = 0; i1 < 6; i1++) { for (int i2 = 0; i2 < 6; i2++) { w[i1][i2] = 0.0; for (int i3 = 0; i3 < 6; i3++) { w[i1][i2] += A[i3][i1] * A[i3][i2]; } } } for (int i1 = 0; i1 < 6; i1++) { w[i1][6] = 0.0; for (int i2 = 0; i2 < 6; i2++) { w[i1][6] += A[i2][i1] * y[i2]; } } for (int i1 = 0; i1 < 6; i1++) { double y1 = 0.0; int m1 = i1 + 1; int m2 = 0; for (int i2 = i1; i2 < 6; i2++) { double y2 = Math.abs(w[i2][i1]); if (y1 < y2) { y1 = y2; m2 = i2; } } for (int i2 = i1; i2 < 7; i2++) { y1 = w[i1][i2]; w[i1][i2] = w[m2][i2]; w[m2][i2] = y1; } y1 = 1.0 / w[i1][i1]; for (int i2 = m1; i2 < 7; i2++) { w[i1][i2] *= y1; } for (int i2 = 0; i2 < 6; i2++) { if (i2 != i1) { for (int i3 = m1; i3 < 7; i3++) { w[i2][i3] -= w[i2][i1] * w[i1][i3]; } } } } for (int i1 = 0; i1 < 6; i1++) { z2[i1] = w[i1][6]; } return z2[0] * Math.pow(koubai, 5) + z2[1] * Math.pow(koubai, 4) + z2[2] * Math.pow(koubai, 3) + z2[3] * Math.pow(koubai, 2) + z2[4] * koubai + z2[5]; } /** 0도 방위 전력 피크 컷을 계산하는 방법 */ private static double computePowerPeakCutDeg90( int simulationPointNumber, double sekisairitsu, double koubai) { // 데이터 초기화 double[] x = {1, 5, 15, 20, 30, 40}; // y 배열 초기화 및 값 할당 double[] y = new double[6]; for (int i = 0; i < 6; i++) { int idx = i * 6; y[i] = tashounen_data_deg90[simulationPointNumber][idx] * Math.pow(sekisairitsu, 5) + tashounen_data_deg90[simulationPointNumber][idx + 1] * Math.pow(sekisairitsu, 4) + tashounen_data_deg90[simulationPointNumber][idx + 2] * Math.pow(sekisairitsu, 3) + tashounen_data_deg90[simulationPointNumber][idx + 3] * Math.pow(sekisairitsu, 2) + tashounen_data_deg90[simulationPointNumber][idx + 4] * sekisairitsu + tashounen_data_deg90[simulationPointNumber][idx + 5]; } // w 행렬 초기화 double[][] w = new double[4][5]; // A 행렬 초기화 double[][] A = new double[6][4]; for (int i = 0; i < 6; i++) { A[i][2] = x[i]; A[i][3] = 1.0; double x1 = A[i][2]; double x2 = x1; for (int j = 1; j >= 0; j--) { x2 *= x1; A[i][j] = x2; } } // w[i1][i2] 계산 for (int i1 = 0; i1 < 4; i1++) { for (int i2 = 0; i2 < 4; i2++) { w[i1][i2] = 0.0; for (int i3 = 0; i3 < 6; i3++) { w[i1][i2] += A[i3][i1] * A[i3][i2]; } } } // w[i1][4] 계산 for (int i1 = 0; i1 < 4; i1++) { w[i1][4] = 0.0; for (int i2 = 0; i2 < 6; i2++) { w[i1][4] += A[i2][i1] * y[i2]; } } // 가우스 소거법 적용 for (int i1 = 0; i1 < 4; i1++) { double y1 = 0.0; int m1 = i1 + 1; int m2 = 0; for (int i2 = i1; i2 < 4; i2++) { double y2 = Math.abs(w[i2][i1]); if (y1 < y2) { y1 = y2; m2 = i2; } } for (int i2 = i1; i2 < 5; i2++) { y1 = w[i1][i2]; w[i1][i2] = w[m2][i2]; w[m2][i2] = y1; } y1 = 1.0 / w[i1][i1]; for (int i2 = m1; i2 < 5; i2++) { w[i1][i2] *= y1; } for (int i2 = 0; i2 < 4; i2++) { if (i2 != i1) { for (int i3 = m1; i3 < 5; i3++) { w[i2][i3] -= w[i2][i1] * w[i1][i3]; } } } } // 결과 z1 배열에 저장 double[] z1 = new double[4]; for (int i1 = 0; i1 < 4; i1++) { z1[i1] = w[i1][4]; } // 출력 결과 확인 // System.out.println("Fitted coefficients:"); // for (double coef : z1) { // System.out.println(coef); // } return (z1[0] * Math.pow(koubai, 3)) + (z1[1] * Math.pow(koubai, 2)) + (z1[2] * koubai) + z1[3]; } // 설치면의 사면 일사량 계산 public double[] shamenNissharyou(double koubai, double houi, int simulationPointNumber) { double[] shamenNissha = new double[12]; // 경사면 일사량 대입을 위한 빈 배열 변수 생성 int i = simulationPointNumber * 12; // 일사량 관측 지점의 행 번호로부터 데이터베이스의 일치하는 행을 산출 // (例:波照間は836行目→データベースでは836×12=10032行目から10044行目までが1月~12月) if (koubai == 0) { // 경사가 0이면 경사 일사량 = 수평면 일사 for (int m = 0; m < 12; m++) { shamenNissha[m] = suiheimennissha[i]; i++; } } else { // 기울기가 0이 아니면 경사 일사량 계산 // 경사면 일사량 (직접, 산란, 지면 반사)의 도출을위한 빈 배열 변수 생성 double[][] cosTheta = new double[12][26]; double[][] rb = new double[12][26]; double[][] io = new double[12][26]; double[][] I = new double[12][26]; double[][] id = new double[12][26]; double[][] Isby = new double[12][26]; double[][] Ibby = new double[12][26]; double[][] Irby = new double[12][26]; // 시간 double[] timeParam = { 100, 100, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5 }; double IsbyMath = 0; double[] IsbyTotal = new double[12]; double IbbyMath = 0; double[] IbbyTotal = new double[12]; double IrbyMath = 0; double[] IrbyTotal = new double[12]; i = simulationPointNumber * 12; // Reset i // System.err.println( " i >>>>>>>>> " + i); // 매월 각 시간의 각 파라미터 계산 for (int m = 0; m < 12; m++) { // System.err.println( " " ); // System.err.println( " m >>>>>>>>> " + m); for (int j = 0; j < 26; j++) { // System.err.println( " i >>>>>>>>> " + i); // System.err.println( " j >>>>>>>>> " + j); // System.err.println( " latlng[i] >>>>>>>>> " + latlng[i]); // System.err.println( " latlng[i][0] >>>>>>>>> " + latlng[i][0]); // System.err.println( " taiyousekii[i] >>>>>>>>> " + taiyousekii[i]); // System.err.println( " jikakudo[i][j] >>>>>>>>> " + jikakudo[i][j]); // System.err.println( " koubai>>>>>>>>> " + koubai); // System.err.println( " houi>>>>>>>>> " + houi); // System.err.println( " 결과>>>>>>>>> " + Math.max(0, (Math.sin(latlng[i][0]) * // Math.cos(koubai) - Math.cos(latlng[i][0]) * Math.sin(koubai) * Math.cos(houi)) * // Math.sin(taiyousekii[i]) // + (Math.cos(latlng[i][0]) * Math.cos(koubai) + Math.sin(latlng[i][0]) * // Math.sin(koubai) * Math.cos(houi)) * Math.cos(taiyousekii[i]) * // Math.cos(jikakudo[i][j]) // + Math.cos(taiyousekii[i]) * Math.sin(koubai) * Math.sin(houi) * // Math.sin(jikakudo[i][j]))); // System.err.println( " " ); cosTheta[m][j] = Math.max( 0, (Math.sin(latlng[i][0]) * Math.cos(koubai) - Math.cos(latlng[i][0]) * Math.sin(koubai) * Math.cos(houi)) * Math.sin(taiyousekii[i]) + (Math.cos(latlng[i][0]) * Math.cos(koubai) + Math.sin(latlng[i][0]) * Math.sin(koubai) * Math.cos(houi)) * Math.cos(taiyousekii[i]) * Math.cos(jikakudo[i][j]) + Math.cos(taiyousekii[i]) * Math.sin(koubai) * Math.sin(houi) * Math.sin(jikakudo[i][j])); rb[m][j] = (cosThetaZ[i][j] == 0) ? 0 : cosTheta[m][j] / cosThetaZ[i][j]; io[m][j] = ion[i] * cosThetaZ[i][j]; I[m][j] = suiheimennissha[i] * rtx[i][j]; id[m][j] = Math.min(sanrannissha[i] * rdx[i][j], I[m][j]); Isby[m][j] = Math.max( 0, (io[m][j] == 0) ? 0 : id[m][j] * (((I[m][j] - id[m][j]) / io[m][j]) * rb[m][j] + (1 - (I[m][j] - id[m][j]) / io[m][j]) * (1 + Math.cos(koubai)) / 2)); Ibby[m][j] = Math.max(0, (I[m][j] - id[m][j]) * rb[m][j]); Irby[m][j] = I[m][j] * jimenhansharitsu[i] * ((1 - Math.cos(koubai)) / 2); } i++; } // Reset i i = simulationPointNumber * 12; // 직접, 산란 계산 for (int m = 0; m < 12; m++) { for (int j = 0; j < 26; j++) { if (timeParam[j] > (Math.ceil(hinode_nichibotsu[i][0] * 2.0) / 2) && timeParam[j] < (Math.floor(hinode_nichibotsu[i][1] * 2.0) / 2)) { IsbyMath += Isby[m][j]; IbbyMath += Ibby[m][j]; } } IsbyMath += ((((Math.ceil(hinode_nichibotsu[i][0]) - 0.5) < hinode_nichibotsu[i][0]) ? 0 : Isby[m][0]) + (((Math.floor(hinode_nichibotsu[i][1]) + 0.5) > hinode_nichibotsu[i][1]) ? 0 : Isby[m][1])); IsbyTotal[m] = IsbyMath; IbbyMath += (((Math.ceil(hinode_nichibotsu[i][0]) - 0.5) < hinode_nichibotsu[i][0]) ? 0 : Ibby[m][0]) + (((Math.floor(hinode_nichibotsu[i][1]) + 0.5) > hinode_nichibotsu[i][1]) ? 0 : Ibby[m][1]); IbbyTotal[m] = IbbyMath; IsbyMath = 0; IbbyMath = 0; i++; } // Reset i i = simulationPointNumber * 12; // 지면 반사 계산 for (int m = 0; m < 12; m++) { for (int j = 0; j < 26; j++) { // 배열 범위에 데이터가 없어서 추가했음. if ((j + 2 < rd[i].length) && (rd[i][j + 2] > ((jikakudo[i][0] >= 0) ? Math.ceil(jikakudo[i][0] * 2.0) / 2 : Math.floor(jikakudo[i][0] * 2.0) / 2)) && (rd[i][j + 2] < ((jikakudo[i][1] >= 0) ? Math.floor(jikakudo[i][1] * 2.0) / 2 : Math.ceil(jikakudo[i][1] * 2.0) / 2))) { IrbyMath += Irby[m][j + 2]; } } IrbyMath += (((jikakudo[i][0] >= 0) ? Math.ceil(jikakudo[i][0]) - 0.5 : Math.floor(jikakudo[i][0]) - 0.5) < jikakudo[i][0] ? 0 : Irby[m][0]) + (((jikakudo[i][1] >= 0) ? Math.floor(jikakudo[i][1]) + 0.5 : Math.ceil(jikakudo[i][1]) + 0.5) > jikakudo[i][1] ? 0 : Irby[m][1]); IrbyTotal[m] = IrbyMath; IrbyMath = 0; i++; } log.error(Arrays.toString(IsbyTotal)); // 직달, 산란, 지면 반사를 매월 합계 for (int m = 0; m < 12; m++) { shamenNissha[m] = IsbyTotal[m] + IbbyTotal[m] + IrbyTotal[m]; } } return shamenNissha; } public static double[] readValuesFromFile(String fileName) { List values = new ArrayList<>(); try (InputStream inputStream = PwrGnrSimService.class.getClassLoader().getResourceAsStream(fileName); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { // 줄을 , 기준으로 분리하여 배열에 추가 String[] parts = line.split(","); for (String part : parts) { values.add(Double.parseDouble(part.trim())); } } } catch (IOException e) { e.printStackTrace(); } // List를 배열로 변환하여 반환 return values.stream().mapToDouble(Double::doubleValue).toArray(); } public static String[][] readValuesFromFileString2(String fileName) { List values = new ArrayList<>(); StringBuilder sb = new StringBuilder(); try (InputStream inputStream = PwrGnrSimService.class.getClassLoader().getResourceAsStream(fileName); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } String content = sb.toString(); content = content.substring(1, content.length() - 1); // 시작과 끝의 중괄호 제거 String[] arrays = content.split("\\}\\s*,\\s*\\{"); for (String innerArray : arrays) { innerArray = innerArray.replaceAll("[{}]", "").trim(); String[] elements = innerArray.split(","); String[] row = new String[elements.length]; for (int i = 0; i < elements.length; i++) { String trimmedValue = elements[i].trim(); trimmedValue = trimmedValue.replace("\"", ""); row[i] = trimmedValue.isEmpty() ? "" : trimmedValue; } values.add(row); } return values.toArray(new String[0][]); } public static double[][] readValuesFromFile2(String fileName) { List values = new ArrayList<>(); StringBuilder sb = new StringBuilder(); try (InputStream inputStream = PwrGnrSimService.class.getClassLoader().getResourceAsStream(fileName); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } String content = sb.toString(); content = content.substring(1, content.length() - 1); // 시작과 끝의 중괄호 제거 String[] arrays = content.split("\\},\\{"); for (String innerArray : arrays) { innerArray = innerArray.replaceAll("[{}]", "").trim(); String[] elements = innerArray.split(","); // 각 요소를 쉼표로 분리 double[] row = new double[elements.length]; for (int i = 0; i < elements.length; i++) { String trimmedValue = elements[i].trim(); if (trimmedValue.isEmpty()) { row[i] = 0.0; // 빈 문자열인 경우 기본값 설정 } else { row[i] = Double.parseDouble(trimmedValue); } } values.add(row); } // List을 double[][]로 변환하여 반환 return values.toArray(new double[0][]); } public PwrGnrSimGuideResponse selectPwrGnrSimulationGuideInfo() throws Exception { PwrGnrSimGuideResponse guideResponse = new PwrGnrSimGuideResponse(); String strResponse = interfaceQsp.callApi(HttpMethod.GET, qspUrl + qspSimulationGuideInfUrl, null); if (!"".equals(strResponse)) { ObjectMapper om = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); guideResponse = om.readValue(strResponse, PwrGnrSimGuideResponse.class); } return guideResponse; } public static Document pwrGnrSimPdfHtml(Document doc, PwrGnrSimResponse data) throws IOException, QcastException { Element elm; // 상단 요약정보 elm = doc.getElementById("objectNo"); elm.text( StringUtils.defaultString(data.getObjectNo() + " (Plan No : " + data.getPlanNo() + ")")); elm = doc.getElementById("drawingEstimateCreateDate"); elm.text(StringUtils.defaultString(data.getDrawingEstimateCreateDate())); elm = doc.getElementById("prefName"); elm.text(StringUtils.defaultString(data.getPrefName())); elm = doc.getElementById("areaName"); elm.text(StringUtils.defaultString(data.getAreaName())); elm = doc.getElementById("capacity"); elm.text(StringUtils.defaultString(data.getCapacity())); elm = doc.getElementById("anlFrcsGnrt"); elm.text(StringUtils.defaultString(String.valueOf(data.getAnlFrcsGnrt()))); elm = doc.getElementById("snowfall"); elm.text(StringUtils.defaultString(data.getSnowfall())); elm = doc.getElementById("standardWindSpeedId"); elm.text(StringUtils.defaultString(data.getStandardWindSpeedId())); if (data.getFrcPwrGnrList() != null && data.getFrcPwrGnrList().length > 0) { int[] onlyData = Arrays.copyOfRange(data.getFrcPwrGnrList(), 0, data.getFrcPwrGnrList().length - 1); int referenceValue = 300; // table 높이 int orgMaxValue = Arrays.stream(onlyData) .max() .orElseThrow(() -> new QcastException(ErrorCode.INTERNAL_SERVER_ERROR, "배열이 없음")); int maxValue = roundUpAuto(orgMaxValue / 8); // chart y축 StringBuilder sb = new StringBuilder(); for (int i = 8; i >= 1; i--) { sb.append(""); sb.append("") .append(maxValue * i) .append(""); sb.append(""); } sb.append(""); sb.append("0"); elm = doc.getElementById("htmlY"); elm.append(sb.toString()); // chart 데이터 String[] color = { "#B5D4F5", "#FFE899", "#FBC3AB", "#D1D1D1", "#FFE899", "#B5D0F0", "#C1E4B8", "#A3C9EE", "#F7BBA2", "#BEBEBE", "#A8BBE0", "#B5D7A3" }; StringBuilder sb2 = new StringBuilder(); for (int i = 0; i < onlyData.length; i++) { double scaledValues = ((double) onlyData[i] / (maxValue * 8)) * referenceValue; sb2.append(""); sb2.append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("") .append("
"); sb2.append( "
"); sb2.append(""); } elm = doc.getElementById("htmlX"); elm.append(sb2.toString()); } // 예측발전량 if (data.getFrcPwrGnrList() != null && data.getFrcPwrGnrList().length > 0) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 13; i++) { sb.append( "" + StringUtils.defaultString(String.valueOf(data.getFrcPwrGnrList()[i])) + ""); } elm = doc.getElementById("frcPwrGnrList_detail"); elm.append(sb.toString()); } // 모듈 list if (data.getRoofModuleList() != null && data.getRoofModuleList().size() > 0) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < data.getRoofModuleList().size(); i++) { PwrGnrSimRoofResponse listItem = data.getRoofModuleList().get(i); sb.append(""); sb.append("" + StringUtils.defaultString(listItem.getRoofSurface()) + ""); sb.append("" + Optional.ofNullable(listItem.getSlopeAngle()).orElse("") + ""); sb.append("" + StringUtils.defaultString(listItem.getAzimuth()) + ""); sb.append("" + StringUtils.defaultString(listItem.getItemNo()) + ""); sb.append("" + StringUtils.defaultString(listItem.getAmount()) + ""); sb.append(""); } elm = doc.getElementById("roofModuleList_detail"); elm.append(sb.toString()); } // pcs list if (data.getPcsList() != null && data.getPcsList().size() > 0) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < data.getPcsList().size(); i++) { PwrGnrSimRoofResponse listItem = data.getPcsList().get(i); sb.append(""); sb.append("" + StringUtils.defaultString(listItem.getItemNo()) + ""); sb.append("" + StringUtils.defaultString(listItem.getAmount()) + ""); sb.append(""); } elm = doc.getElementById("pcsList_detail"); elm.append(sb.toString()); } return doc; } public static int roundUpAuto(double value) { int length = (int) Math.log10(value) + 1; // 숫자의 자리수를 계산 int place = (int) Math.pow(10, length - 1); // 자리수에 따른 올림 기준(10, 100, 1000 등) return (int) Math.ceil(value / place) * place; } }