diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiV2Controller.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiV2Controller.java new file mode 100644 index 00000000..8b01288e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiV2Controller.java @@ -0,0 +1,69 @@ +package com.kamco.cd.kamcoback.inference; + +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.service.InferenceResultService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "추론관리 분석결과", description = "추론관리 분석결과") +@RequiredArgsConstructor +@RestController +@RequestMapping({"/v2/demo/inf/res"}) +@Slf4j +public class InferenceResultApiV2Controller { + + private final InferenceResultService inferenceResultService; + + @Operation(summary = "추론관리 분석결과 상세 목록 V2", description = "추론관리 분석결과 상세 목록 geojson 데이터 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/geom/{id}") + public ApiResponseDto> listInferenceResultWithGeom( + @Parameter(description = "분석결과 id", example = "1") @PathVariable Long id, + @Parameter(description = "기준년도 분류", example = "land") @RequestParam(required = false) + String targetClass, + @Parameter(description = "비교년도 분류", example = "waste") @RequestParam(required = false) + String compareClass, + @Parameter(description = "5000:1 도협번호 37801011,37801012") @RequestParam(required = false) + List mapSheetNum, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size, + @Parameter(description = "정렬 조건 (형식: 필드명,방향)", example = "name,asc") + @RequestParam(required = false) + String sort) { + InferenceResultDto.SearchGeoReq searchGeoReq = + new InferenceResultDto.SearchGeoReq( + targetClass, compareClass, mapSheetNum, page, size, sort); + + Page geomList = + inferenceResultService.listInferenceResultWithGeom(id, searchGeoReq); + return ApiResponseDto.ok(geomList); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java index e093b8c3..e882da1c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java @@ -173,6 +173,50 @@ public class InferenceResultDto { } } + // 분석 상세 ROW + @Getter + @AllArgsConstructor + public static class DetailListEntity { + + private Clazz compare; + private Clazz target; + private MapSheet mapSheet; + private Coordinate center; + } + + // MAP NO + @Getter + @AllArgsConstructor + public static class MapSheet { + + private String number; + private String name; + } + + // classification info + @Getter + @AllArgsConstructor + public static class Clazz { + + private String code; + private Double score; + } + + // 좌표 정보 point + @Getter + public static class Coordinate { + + private Double lon; // 경도(Longitude) + private Double lat; // 위도(Latitude) + private String srid; // Spatial Reference ID의 약자로, 데이터베이스에서 좌표계를 식별하는 고유 번호 추후enum으로 + + public Coordinate(Double lon, Double lat) { + this.lon = lon; + this.lat = lat; + this.srid = "EPSG:4326"; + } + } + @Getter public static class Geom { diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java index 76490500..9a46072a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Detail; import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; +import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -59,6 +60,18 @@ public class InferenceResultService { return inferenceResultCoreService.getInferenceResultGeomList(id, searchGeoReq); } + /** + * 분석결과 상세 목록 + * + * @param searchReq + * @return + */ + public Page listInferenceResultWithGeom( + @NotNull Long id, InferenceResultDto.SearchGeoReq searchReq) { + + return inferenceResultCoreService.listInferenceResultWithGeom(id, searchReq); + } + /** * 분석결과 상제 정보 Summary, DashBoard * diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java index bbf1ea90..4b62d931 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java @@ -2,12 +2,16 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository; import jakarta.persistence.EntityNotFoundException; +import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -61,6 +65,27 @@ public class InferenceResultCoreService { return inferenceResultRepository.getInferenceGeomList(id, searchGeoReq); } + /** + * 분석결과 상세 목록 + * + * @param searchReq + * @return + */ + @Transactional(readOnly = true) + public Page listInferenceResultWithGeom( + @NotNull Long analyId, InferenceResultDto.SearchGeoReq searchReq) { + // 분석 ID 에 해당하는 dataids를 가져온다. + List dataIds = + inferenceResultRepository.listAnalyGeom(analyId).stream() + .mapToLong(MapSheetAnalDataEntity::getId) + .boxed() + .toList(); + // 해당데이터의 폴리곤데이터를 가져온다 + Page mapSheetAnalDataGeomEntities = + inferenceResultRepository.listInferenceResultWithGeom(dataIds, searchReq); + return mapSheetAnalDataGeomEntities.map(MapSheetAnalDataGeomEntity::toEntity); + } + /** * 추론된 5000:1 도엽 목록 * diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java index 4e16d734..fe95b6d1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java @@ -1,6 +1,14 @@ package com.kamco.cd.kamcoback.postgres.entity; -import jakarta.persistence.*; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; import org.locationtech.jts.geom.Geometry; @@ -10,6 +18,7 @@ import org.locationtech.jts.geom.Geometry; @Table(name = "tb_map_inkx_5k") @Entity public class MapInkx5kEntity { + @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_5k_fid_seq_gen") @SequenceGenerator( @@ -29,4 +38,8 @@ public class MapInkx5kEntity { @Column(name = "fid_k50") private Long fidK50; + + public InferenceResultDto.MapSheet toEntity() { + return new MapSheet(mapidcdNo, mapidNm); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataGeomEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataGeomEntity.java index 2eb3fd76..254f6c1e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataGeomEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalDataGeomEntity.java @@ -1,10 +1,15 @@ package com.kamco.cd.kamcoback.postgres.entity; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Clazz; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; import jakarta.validation.constraints.Size; @@ -51,6 +56,10 @@ public class MapSheetAnalDataGeomEntity { @Column(name = "map_sheet_num") private Long mapSheetNum; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "map_5k_id", referencedColumnName = "fid") + private MapInkx5kEntity map5k; + @Column(name = "compare_yyyy") private Integer compareYyyy; @@ -133,4 +142,19 @@ public class MapSheetAnalDataGeomEntity { @Column(name = "geom_center", columnDefinition = "geometry") private Geometry geomCenter; + + public InferenceResultDto.DetailListEntity toEntity() { + Clazz comparedClazz = new Clazz(classBeforeCd, classBeforeProb); + Clazz targetClazz = new Clazz(classAfterCd, classAfterProb); + InferenceResultDto.MapSheet mapSheet = map5k != null ? map5k.toEntity() : null; + + InferenceResultDto.Coordinate coordinate = null; + if (geomCenter != null) { + org.locationtech.jts.geom.Point point = (org.locationtech.jts.geom.Point) geomCenter; + coordinate = new InferenceResultDto.Coordinate(point.getX(), point.getY()); + } + + return new InferenceResultDto.DetailListEntity( + comparedClazz, targetClazz, mapSheet, coordinate); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java index 440af411..b2aeaf6d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java @@ -9,7 +9,9 @@ import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; import jakarta.validation.constraints.Size; import java.time.ZonedDateTime; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.ColumnDefault; @@ -17,6 +19,9 @@ import org.hibernate.annotations.ColumnDefault; @Setter @Entity @Table(name = "tb_map_sheet_anal") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +// TODO SETTER 제거 +// TODO 주석 public class MapSheetAnalEntity { @Id @@ -28,21 +33,24 @@ public class MapSheetAnalEntity { @Column(name = "anal_uid", nullable = false) private Long id; + // TODO UUID + // UK 추가 + @Column(name = "compare_yyyy") - private Integer compareYyyy; + private Integer compareYyyy; // 비교년도 @Column(name = "target_yyyy") - private Integer targetYyyy; + private Integer targetYyyy; // 기분년도 @Column(name = "model_uid") - private Long modelUid; + private Long modelUid; // 모델식별키 ? @Size(max = 100) @Column(name = "server_ids", length = 100) - private String serverIds; + private String serverIds; // 서버ID? @Column(name = "anal_map_sheet", length = Integer.MAX_VALUE) - private String analMapSheet; + private String analMapSheet; // 분석도엽? @Column(name = "anal_strt_dttm") private ZonedDateTime analStrtDttm; @@ -54,15 +62,15 @@ public class MapSheetAnalEntity { private Long analSec; @Column(name = "anal_pred_sec") - private Long analPredSec; + private Long analPredSec; // 예상소요초? @Size(max = 20) @Column(name = "anal_state", length = 20) - private String analState; + private String analState; // enum 으로 관리 @Size(max = 20) @Column(name = "gukyuin_used", length = 20) - private String gukyuinUsed; + private String gukyuinUsed; // Boolean으로 관리 @Column(name = "accuracy") private Double accuracy; @@ -71,17 +79,9 @@ public class MapSheetAnalEntity { @Column(name = "result_url") private String resultUrl; - @ColumnDefault("now()") - @Column(name = "created_dttm") - private ZonedDateTime createdDttm; - @Column(name = "created_uid") private Long createdUid; - @ColumnDefault("now()") - @Column(name = "updated_dttm") - private ZonedDateTime updatedDttm; - @Column(name = "updated_uid") private Long updatedUid; @@ -94,4 +94,13 @@ public class MapSheetAnalEntity { @Column(name = "base_map_sheet_num") private String baseMapSheetNum; + + // TODO CommonDateEntity ? + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @ColumnDefault("now()") + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java index 629d4ef9..7b9ac305 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java @@ -2,6 +2,10 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -16,7 +20,12 @@ public interface InferenceResultRepositoryCustom { Page getInferenceGeomList( Long id, InferenceResultDto.SearchGeoReq searchGeoReq); + Page listInferenceResultWithGeom( + List dataIds, SearchGeoReq searchReq); + List getSheets(Long id); List getDashboard(Long id); + + List listAnalyGeom(@NotNull Long id); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 8582414b..faef0038 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -3,6 +3,8 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity; @@ -10,17 +12,23 @@ import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalSttcEntity; import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity; import com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity; import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPQLQuery; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Repository; @Repository @@ -159,6 +167,56 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC .fetch(); } + @Override + public List listAnalyGeom(Long id) { + QMapSheetAnalDataEntity analy = QMapSheetAnalDataEntity.mapSheetAnalDataEntity; + return queryFactory.selectFrom(analy).where(analy.analUid.eq(id)).fetch(); + } + + /** + * 분석결과 상세 목록 + * + * @param searchReq + * @return + */ + @Override + public Page listInferenceResultWithGeom( + List ids, SearchGeoReq searchReq) { + + // 분석 차수 + QMapSheetAnalDataGeomEntity detectedEntity = + QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; + Pageable pageable = searchReq.toPageable(); + + // 검색조건 + JPAQuery query = + queryFactory + .selectFrom(detectedEntity) + .where( + detectedEntity.dataUid.in(ids), + eqTargetClass(detectedEntity, searchReq.getTargetClass()), + eqCompareClass(detectedEntity, searchReq.getCompareClass()), + containsMapSheetNum(detectedEntity, searchReq.getMapSheetNum())); + + // count + long total = query.fetchCount(); + + // Pageable에서 정렬 가져오기, 없으면 기본 정렬(createdDttm desc) 사용 + List> orders = getOrderSpecifiers(pageable.getSort()); + if (orders.isEmpty()) { + orders.add(detectedEntity.createdDttm.desc()); + } + + List content = + query + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(orders.toArray(new OrderSpecifier[0])) + .fetch(); + + return new PageImpl<>(content, pageable, total); + } + /** * 분석결과 상세 목록 * @@ -252,4 +310,61 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC .groupBy(mapSheetAnalDataEntity.mapSheetNum) .fetch(); } + + /** Pageable의 Sort를 QueryDSL OrderSpecifier로 변환 */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private List> getOrderSpecifiers(Sort sort) { + List> orders = new ArrayList<>(); + + if (sort.isSorted()) { + QMapSheetAnalDataGeomEntity entity = QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity; + + for (Sort.Order order : sort) { + Order direction = order.isAscending() ? Order.ASC : Order.DESC; + String property = order.getProperty(); + + // 유효한 필드만 처리 + switch (property) { + case "classBeforeCd" -> orders.add(new OrderSpecifier(direction, entity.classBeforeCd)); + case "classBeforeProb" -> + orders.add(new OrderSpecifier(direction, entity.classBeforeProb)); + case "classAfterCd" -> orders.add(new OrderSpecifier(direction, entity.classAfterCd)); + case "classAfterProb" -> orders.add(new OrderSpecifier(direction, entity.classAfterProb)); + case "mapSheetNum" -> orders.add(new OrderSpecifier(direction, entity.mapSheetNum)); + case "compareYyyy" -> orders.add(new OrderSpecifier(direction, entity.compareYyyy)); + case "targetYyyy" -> orders.add(new OrderSpecifier(direction, entity.targetYyyy)); + case "area" -> orders.add(new OrderSpecifier(direction, entity.area)); + case "createdDttm" -> orders.add(new OrderSpecifier(direction, entity.createdDttm)); + case "updatedDttm" -> orders.add(new OrderSpecifier(direction, entity.updatedDttm)); + // 유효하지 않은 필드는 무시 + default -> {} + } + } + } + + return orders; + } + + private BooleanExpression eqTargetClass( + QMapSheetAnalDataGeomEntity detectedEntity, String targetClass) { + return targetClass != null && !targetClass.isEmpty() + ? detectedEntity.classAfterCd.toLowerCase().eq(targetClass.toLowerCase()) + : null; + } + + private BooleanExpression eqCompareClass( + QMapSheetAnalDataGeomEntity detectedEntity, String compareClass) { + return compareClass != null && !compareClass.isEmpty() + ? detectedEntity.classBeforeCd.toLowerCase().eq(compareClass.toLowerCase()) + : null; + } + + private BooleanExpression containsMapSheetNum( + QMapSheetAnalDataGeomEntity detectedEntity, List mapSheet) { + if (mapSheet == null || mapSheet.isEmpty()) { + return null; + } + + return detectedEntity.mapSheetNum.in(mapSheet); + } }