diff --git a/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeoJsonDataService.java b/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeoJsonDataService.java index 918215ed..25da93b1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeoJsonDataService.java +++ b/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeoJsonDataService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnDataEntity; import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataRepository; +import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -29,13 +30,13 @@ public class GeoJsonDataService { @Transactional public List processGeoJsonFiles(Map geoJsonContents, String archiveFileName) { List savedIds = new ArrayList<>(); - + log.info("GeoJSON 파일 처리 시작: {} ({}개 파일)", archiveFileName, geoJsonContents.size()); - + for (Map.Entry entry : geoJsonContents.entrySet()) { String fileName = entry.getKey(); String geoJsonContent = entry.getValue(); - + try { Long savedId = processGeoJsonFile(fileName, geoJsonContent, archiveFileName); if (savedId != null) { @@ -47,10 +48,10 @@ public class GeoJsonDataService { // 개별 파일 처리 실패는 전체 처리를 중단시키지 않음 } } - - log.info("GeoJSON 파일 처리 완료: {} (성공: {}개, 전체: {}개)", + + log.info("GeoJSON 파일 처리 완료: {} (성공: {}개, 전체: {}개)", archiveFileName, savedIds.size(), geoJsonContents.size()); - + return savedIds; } @@ -62,22 +63,22 @@ public class GeoJsonDataService { // GeoJSON 파싱 및 검증 JsonNode geoJsonNode = objectMapper.readTree(geoJsonContent); validateGeoJsonStructure(geoJsonNode); - + // 파일이 이미 처리되었는지 확인 String dataPath = generateDataPath(archiveFileName, fileName); Optional existingData = mapSheetLearnDataRepository.findByDataPath(dataPath); - + if (existingData.isPresent()) { log.warn("이미 처리된 파일입니다: {}", dataPath); return existingData.get().getId(); } - + // 새 엔티티 생성 및 저장 MapSheetLearnDataEntity entity = createMapSheetLearnDataEntity(fileName, geoJsonContent, archiveFileName, geoJsonNode); MapSheetLearnDataEntity savedEntity = mapSheetLearnDataRepository.save(entity); - + return savedEntity.getId(); - + } catch (Exception e) { log.error("GeoJSON 파일 처리 중 오류 발생: {}", fileName, e); throw new RuntimeException("GeoJSON 파일 처리 실패: " + fileName, e); @@ -91,7 +92,7 @@ public class GeoJsonDataService { if (!geoJsonNode.has("type")) { throw new IllegalArgumentException("유효하지 않은 GeoJSON: 'type' 필드가 없습니다."); } - + String type = geoJsonNode.get("type").asText(); if (!"FeatureCollection".equals(type) && !"Feature".equals(type) && !"Geometry".equals(type)) { throw new IllegalArgumentException("지원하지 않는 GeoJSON type: " + type); @@ -103,18 +104,18 @@ public class GeoJsonDataService { */ private MapSheetLearnDataEntity createMapSheetLearnDataEntity( String fileName, String geoJsonContent, String archiveFileName, JsonNode geoJsonNode) { - + MapSheetLearnDataEntity entity = new MapSheetLearnDataEntity(); - + // 기본 정보 설정 entity.setDataName(fileName); entity.setDataPath(generateDataPath(archiveFileName, fileName)); entity.setDataType("GeoJSON"); entity.setDataTitle(extractTitle(fileName, geoJsonNode)); - + // CRS 정보 추출 및 설정 setCrsInformation(entity, geoJsonNode); - + // JSON 데이터 저장 try { @SuppressWarnings("unchecked") @@ -128,20 +129,20 @@ public class GeoJsonDataService { fallbackMap.put("parse_error", e.getMessage()); entity.setDataJson(fallbackMap); } - + // 연도 정보 추출 (파일명에서 추출 시도) setYearInformation(entity, fileName); - + // 상태 정보 설정 entity.setDataState("PROCESSED"); entity.setAnalState("PENDING"); - + // 시간 정보 설정 - Instant now = Instant.now(); + ZonedDateTime now = ZonedDateTime.now(); entity.setCreatedDttm(now); entity.setUpdatedDttm(now); entity.setDataStateDttm(now); - + return entity; } @@ -154,7 +155,7 @@ public class GeoJsonDataService { if (crsNode.has("type") && crsNode.has("properties")) { String crsType = crsNode.get("type").asText(); entity.setDataCrsType(crsType); - + JsonNode propertiesNode = crsNode.get("properties"); if (propertiesNode.has("name")) { String crsName = propertiesNode.get("name").asText(); @@ -207,13 +208,13 @@ public class GeoJsonDataService { return properties.get("name").asText(); } } - + // 파일명에서 확장자 제거하여 제목으로 사용 int lastDotIndex = fileName.lastIndexOf('.'); if (lastDotIndex > 0) { return fileName.substring(0, lastDotIndex); } - + return fileName; } @@ -231,14 +232,14 @@ public class GeoJsonDataService { if (geoJsonContents == null || geoJsonContents.isEmpty()) { return false; } - + // 최대 처리 가능한 파일 수 제한 (성능 고려) int maxFiles = 50; if (geoJsonContents.size() > maxFiles) { log.warn("처리 가능한 최대 파일 수를 초과했습니다: {} > {}", geoJsonContents.size(), maxFiles); return false; } - + return true; } -} \ No newline at end of file +} diff --git a/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeometryConversionService.java b/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeometryConversionService.java index 6e89ae50..057ea521 100644 --- a/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeometryConversionService.java +++ b/src/main/java/com/kamco/cd/kamcoback/geojson/service/GeometryConversionService.java @@ -6,6 +6,7 @@ import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnDataEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnDataGeomEntity; import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataGeomRepository; import com.kamco.cd.kamcoback.postgres.repository.MapSheetLearnDataRepository; +import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.locationtech.jts.geom.*; @@ -34,9 +35,9 @@ public class GeometryConversionService { @Transactional public List convertToGeometryData(List learnDataIds) { List processedIds = new ArrayList<>(); - + log.info("Geometry 변환 시작: {} 개의 학습 데이터", learnDataIds.size()); - + for (Long dataId : learnDataIds) { try { if (dataId != null) { @@ -54,7 +55,7 @@ public class GeometryConversionService { // 개별 변환 실패는 전체 처리를 중단시키지 않음 } } - + log.info("Geometry 변환 완료: {} 개 처리, {} 개의 geometry 생성", learnDataIds.size(), processedIds.size()); return processedIds; } @@ -64,22 +65,22 @@ public class GeometryConversionService { */ private List processLearnDataToGeometry(MapSheetLearnDataEntity learnData) { List geometryIds = new ArrayList<>(); - + try { // 기존 geometry 데이터 삭제 (재생성) mapSheetLearnDataGeomRepository.deleteByDataUid(learnData.getId()); - + // JSON 데이터에서 GeoJSON 추출 Map dataJson = learnData.getDataJson(); if (dataJson == null || dataJson.isEmpty()) { log.warn("JSON 데이터가 없습니다: {}", learnData.getId()); return geometryIds; } - + // JSON을 GeoJSON으로 파싱 String geoJsonString = objectMapper.writeValueAsString(dataJson); JsonNode geoJsonNode = objectMapper.readTree(geoJsonString); - + // GeoJSON 타입에 따라 처리 String type = geoJsonNode.get("type").asText(); switch (type) { @@ -106,12 +107,12 @@ public class GeometryConversionService { default: log.warn("지원하지 않는 GeoJSON type: {} (데이터 ID: {})", type, learnData.getId()); } - + } catch (Exception e) { log.error("Geometry 변환 실패: 학습 데이터 ID {}", learnData.getId(), e); throw new RuntimeException("Geometry 변환 실패", e); } - + return geometryIds; } @@ -120,12 +121,12 @@ public class GeometryConversionService { */ private List processFeatureCollection(JsonNode featureCollectionNode, MapSheetLearnDataEntity learnData) { List geometryIds = new ArrayList<>(); - + if (!featureCollectionNode.has("features")) { log.warn("FeatureCollection에 features 배열이 없습니다: {}", learnData.getId()); return geometryIds; } - + JsonNode featuresNode = featureCollectionNode.get("features"); if (featuresNode.isArray()) { for (JsonNode featureNode : featuresNode) { @@ -139,7 +140,7 @@ public class GeometryConversionService { } } } - + return geometryIds; } @@ -152,12 +153,12 @@ public class GeometryConversionService { log.warn("Feature에 geometry가 없습니다: {}", learnData.getId()); return null; } - + JsonNode geometryNode = featureNode.get("geometry"); JsonNode propertiesNode = featureNode.has("properties") ? featureNode.get("properties") : null; - + return createGeometryEntity(geometryNode, propertiesNode, learnData); - + } catch (Exception e) { log.error("Feature 처리 중 오류 (학습 데이터 ID: {})", learnData.getId(), e); return null; @@ -177,18 +178,18 @@ public class GeometryConversionService { private Long createGeometryEntity(JsonNode geometryNode, JsonNode propertiesNode, MapSheetLearnDataEntity learnData) { try { MapSheetLearnDataGeomEntity geometryEntity = new MapSheetLearnDataGeomEntity(); - + // 기본 정보 설정 geometryEntity.setDataUid(learnData.getId()); geometryEntity.setBeforeYyyy(learnData.getDataYyyy()); geometryEntity.setAfterYyyy(learnData.getCompareYyyy()); - + // Geometry 변환 및 설정 Geometry geometry = parseGeometryFromGeoJson(geometryNode); if (geometry != null) { geometryEntity.setGeom(geometry); geometryEntity.setGeoType(geometry.getGeometryType()); - + // 면적 계산 (Polygon인 경우) if (geometry instanceof Polygon || geometry.getGeometryType().contains("Polygon")) { double area = geometry.getArea(); @@ -198,21 +199,21 @@ public class GeometryConversionService { log.warn("Geometry 변환 실패: {}", geometryNode); return null; } - + // Properties에서 추가 정보 추출 if (propertiesNode != null) { extractPropertiesData(geometryEntity, propertiesNode, learnData); } - + // 시간 정보 설정 - Instant now = Instant.now(); + ZonedDateTime now = ZonedDateTime.now(); geometryEntity.setCreatedDttm(now); geometryEntity.setUpdatedDttm(now); - + // 저장 MapSheetLearnDataGeomEntity savedEntity = mapSheetLearnDataGeomRepository.save(geometryEntity); return savedEntity.getId(); - + } catch (Exception e) { log.error("GeometryEntity 생성 실패 (학습 데이터 ID: {})", learnData.getId(), e); return null; @@ -228,10 +229,10 @@ public class GeometryConversionService { log.warn("유효하지 않은 Geometry 형식: type 또는 coordinates가 없습니다."); return null; } - + String geometryType = geometryNode.get("type").asText(); JsonNode coordinatesNode = geometryNode.get("coordinates"); - + switch (geometryType.toLowerCase()) { case "point": return createPoint(coordinatesNode); @@ -249,7 +250,7 @@ public class GeometryConversionService { log.warn("지원하지 않는 Geometry 타입: {}", geometryType); return null; } - + } catch (Exception e) { log.error("Geometry 파싱 실패", e); return null; @@ -275,7 +276,7 @@ public class GeometryConversionService { private Polygon createPolygon(JsonNode coordinatesNode) { if (coordinatesNode.size() == 0) return null; - + // Exterior ring JsonNode exteriorRing = coordinatesNode.get(0); List coords = new ArrayList<>(); @@ -284,16 +285,16 @@ public class GeometryConversionService { coords.add(new Coordinate(coordNode.get(0).asDouble(), coordNode.get(1).asDouble())); } } - + if (coords.size() < 3) return null; - + // Close ring if not already closed if (!coords.get(0).equals2D(coords.get(coords.size() - 1))) { coords.add(new Coordinate(coords.get(0))); } - + LinearRing shell = geometryFactory.createLinearRing(coords.toArray(new Coordinate[0])); - + // Interior rings (holes) LinearRing[] holes = new LinearRing[coordinatesNode.size() - 1]; for (int i = 1; i < coordinatesNode.size(); i++) { @@ -311,7 +312,7 @@ public class GeometryConversionService { holes[i - 1] = geometryFactory.createLinearRing(holeCoords.toArray(new Coordinate[0])); } } - + return geometryFactory.createPolygon(shell, holes); } @@ -361,7 +362,7 @@ public class GeometryConversionService { log.debug("cd_prob 파싱 실패", e); } } - + // Before class 정보 if (propertiesNode.has("class_before_name")) { geometryEntity.setClassBeforeName(propertiesNode.get("class_before_name").asText()); @@ -374,7 +375,7 @@ public class GeometryConversionService { log.debug("class_before_prob 파싱 실패", e); } } - + // After class 정보 if (propertiesNode.has("class_after_name")) { geometryEntity.setClassAfterName(propertiesNode.get("class_after_name").asText()); @@ -387,7 +388,7 @@ public class GeometryConversionService { log.debug("class_after_prob 파싱 실패", e); } } - + // 도엽 번호 if (propertiesNode.has("map_sheet_num")) { try { @@ -397,7 +398,7 @@ public class GeometryConversionService { log.debug("map_sheet_num 파싱 실패", e); } } - + // 면적 (properties에서 제공되는 경우) if (propertiesNode.has("area")) { try { @@ -416,24 +417,24 @@ public class GeometryConversionService { public List processUnprocessedLearnData() { // 분석 상태가 PENDING인 학습 데이터 조회 List unprocessedData = mapSheetLearnDataRepository.findByAnalState("PENDING"); - + if (unprocessedData.isEmpty()) { log.debug("처리할 미완료 학습 데이터가 없습니다."); return new ArrayList<>(); } - + log.info("미처리 학습 데이터 {}개에 대해 geometry 변환을 수행합니다.", unprocessedData.size()); - + List processedIds = new ArrayList<>(); for (MapSheetLearnDataEntity data : unprocessedData) { try { List geometryIds = processLearnDataToGeometry(data); processedIds.addAll(geometryIds); - + // 처리 완료 상태로 업데이트 data.setAnalState("COMPLETED"); mapSheetLearnDataRepository.save(data); - + } catch (Exception e) { log.error("미처리 학습 데이터 처리 실패: {}", data.getId(), e); // 실패한 경우 ERROR 상태로 설정 @@ -441,7 +442,7 @@ public class GeometryConversionService { mapSheetLearnDataRepository.save(data); } } - + return processedIds; } -} \ No newline at end of file +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetLearnDataEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetLearnDataEntity.java new file mode 100644 index 00000000..c30d41da --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetLearnDataEntity.java @@ -0,0 +1,108 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Getter +@Setter +@Entity +@Table(name = "tb_map_sheet_learn_data") +public class MapSheetLearnDataEntity { + + @Id + @Column(name = "data_uid", nullable = false) + private Long id; + + @Column(name = "anal_end_dttm") + private ZonedDateTime analEndDttm; + + @Size(max = 255) + @Column(name = "anal_map_sheet") + private String analMapSheet; + + @Column(name = "anal_sec") + private Long analSec; + + @Size(max = 20) + @Column(name = "anal_state", length = 20) + private String analState; + + @Column(name = "anal_strt_dttm") + private ZonedDateTime analStrtDttm; + + @Column(name = "compare_yyyy") + private Integer compareYyyy; + + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @Column(name = "created_uid") + private Long createdUid; + + @Size(max = 128) + @Column(name = "data_crs_type", length = 128) + private String dataCrsType; + + @Size(max = 255) + @Column(name = "data_crs_type_name") + private String dataCrsTypeName; + + @Column(name = "data_json") + @JdbcTypeCode(SqlTypes.JSON) + private Map dataJson; + + @Size(max = 128) + @Column(name = "data_name", length = 128) + private String dataName; + + @Size(max = 255) + @Column(name = "data_path") + private String dataPath; + + @Size(max = 20) + @Column(name = "data_state", length = 20) + private String dataState; + + @ColumnDefault("now()") + @Column(name = "data_state_dttm") + private ZonedDateTime dataStateDttm; + + @Size(max = 255) + @Column(name = "data_title") + private String dataTitle; + + @Size(max = 128) + @Column(name = "data_type", length = 128) + private String dataType; + + @Column(name = "data_yyyy") + private Integer dataYyyy; + + @Size(max = 20) + @Column(name = "gukuin_used", length = 20) + private String gukuinUsed; + + @Column(name = "gukuin_used_dttm") + private ZonedDateTime gukuinUsedDttm; + + @ColumnDefault("now()") + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Column(name = "updated_uid") + private Long updatedUid; + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetLearnDataGeomEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetLearnDataGeomEntity.java new file mode 100644 index 00000000..798bfef8 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetLearnDataGeomEntity.java @@ -0,0 +1,79 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.locationtech.jts.geom.Geometry; + +@Getter +@Setter +@Entity +@Table(name = "tb_map_sheet_learn_data_geom") +public class MapSheetLearnDataGeomEntity { + + @Id + @Column(name = "geo_uid", nullable = false) + private Long id; + + @Column(name = "after_yyyy") + private Integer afterYyyy; + + @Column(name = "area") + private Double area; + + @Column(name = "before_yyyy") + private Integer beforeYyyy; + + @Column(name = "cd_prob") + private Double cdProb; + + @Size(max = 100) + @Column(name = "class_after_name", length = 100) + private String classAfterName; + + @Column(name = "class_after_prob") + private Double classAfterProb; + + @Size(max = 100) + @Column(name = "class_before_name", length = 100) + private String classBeforeName; + + @Column(name = "class_before_prob") + private Double classBeforeProb; + + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @Column(name = "created_uid") + private Long createdUid; + + private Long dataUid; + + @Size(max = 100) + @Column(name = "geo_type", length = 100) + private String geoType; + + @Column(name = "geom") + private Geometry geom; + + @Column(name = "map_sheet_num") + private Long mapSheetNum; + + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Column(name = "updated_uid") + private Long updatedUid; + +}