Merge branch 'develop' into feat/dev_251201

This commit is contained in:
2025-12-03 13:46:39 +09:00
19 changed files with 614 additions and 42 deletions

View File

@@ -137,6 +137,7 @@ public class ChangeDetectionDto {
private Integer afterYear; // 비교년도 private Integer afterYear; // 비교년도
private Double afterConfidence; // 비교 신뢰도(확률) private Double afterConfidence; // 비교 신뢰도(확률)
private String afterClass; private String afterClass;
private Double cdProb; // 탐지정확도
} }
@Schema(name = "PointFeature", description = "Geometry 리턴 객체") @Schema(name = "PointFeature", description = "Geometry 리턴 객체")
@@ -177,5 +178,6 @@ public class ChangeDetectionDto {
private Integer afterYear; // 비교년도 private Integer afterYear; // 비교년도
private Double afterConfidence; // 비교 신뢰도(확률) private Double afterConfidence; // 비교 신뢰도(확률)
private String afterClass; // 비교 분류 private String afterClass; // 비교 분류
private Double cdProb; // 탐지 정확도
} }
} }

View File

@@ -5,7 +5,6 @@ import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto;
import com.kamco.cd.kamcoback.postgres.core.ChangeDetectionCoreService; import com.kamco.cd.kamcoback.postgres.core.ChangeDetectionCoreService;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -35,7 +34,6 @@ public class ChangeDetectionService {
return changeDetectionCoreService.getChangeDetectionYearList(); return changeDetectionCoreService.getChangeDetectionYearList();
} }
@Cacheable(value = "changeDetectionPolygon", key = "#analUid + '_' + #mapSheetNum")
public ChangeDetectionDto.PolygonFeatureList getChangeDetectionPolygonList( public ChangeDetectionDto.PolygonFeatureList getChangeDetectionPolygonList(
Long analUid, String mapSheetNum) { Long analUid, String mapSheetNum) {

View File

@@ -2,7 +2,10 @@ package com.kamco.cd.kamcoback.code;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.code.service.CommonCodeService; import com.kamco.cd.kamcoback.code.service.CommonCodeService;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Clazzes;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@@ -10,6 +13,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -206,4 +211,16 @@ public class CommonCodeApiController {
String code) { String code) {
return ApiResponseDto.ok(commonCodeService.findByCode(code)); return ApiResponseDto.ok(commonCodeService.findByCode(code));
} }
@GetMapping("/clazz")
public ApiResponseDto<List<InferenceResultDto.Clazzes>> getClasses() {
List<Clazzes> list =
Arrays.stream(DetectionClassification.values())
.sorted(Comparator.comparingInt(DetectionClassification::getOrder))
.map(Clazzes::new)
.toList();
return ApiResponseDto.ok(list);
}
} }

View File

@@ -0,0 +1,46 @@
package com.kamco.cd.kamcoback.common.api;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet;
import com.kamco.cd.kamcoback.inference.dto.LearningModelResultDto.BatchProcessResponse;
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 java.util.List;
import lombok.RequiredArgsConstructor;
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.RestController;
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/api/scene")
public class SceneDemoApiController {
private final InferenceResultService inferenceResultService;
@Operation(summary = "추론된 도엽 목록", description = "추론된 도엽 목록 5000:1")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = BatchProcessResponse.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/sheets/train-set/{id}")
public ApiResponseDto<List<MapSheet>> listGetScenes5k(
@Parameter(description = "분석결과 id", example = "1") @PathVariable Long id) {
List<MapSheet> mapSheets = inferenceResultService.listGetScenes5k(id);
return ApiResponseDto.ok(mapSheets);
}
}

View File

@@ -6,24 +6,25 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum DetectionClassification { public enum DetectionClassification {
BUILDING("building", "빌딩"), BUILDING("building", "건물", 10),
CONTAINER("container", "컨테이너(창고·적재함)"), CONTAINER("container", "컨테이너", 20),
FIELD("field", "경작지 / 들판"), FIELD("field", "경작지", 30),
FOREST("forest", ""), FOREST("forest", "", 40),
GRASS("grass", "초지 / 잔디지역"), GRASS("grass", "초지", 50),
GREENHOUSE("greenhouse", "비닐하우스"), GREENHOUSE("greenhouse", "비닐하우스", 60),
LAND("land", ""), LAND("land", "일반토", 70),
ORCHARD("orchard", "과수원"), ORCHARD("orchard", "과수원", 80),
ROAD("road", "도로"), ROAD("road", "도로", 90),
STONE("stone", "암석 / 돌 지역"), STONE("stone", "모래/자갈", 100),
TANK("tank", "탱크(저유탱크 등 저장시설)"), TANK("tank", "탱크", 110),
TUMULUS("tumulus", "분(무덤)"), TUMULUS("tumulus", "분(무덤)", 120),
WASTE("waste", "폐기물 적치장 / 황폐지"), WASTE("waste", "폐기물", 130),
WATER("water", "수체(水域) / "), WATER("water", "", 140),
ETC("ETC", "기타"); // For 'etc' (miscellaneous/other) ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other)
private final String id; private final String id;
private final String desc; private final String desc;
private final int order;
/** /**
* Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not * Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not

View File

@@ -1,5 +1,11 @@
package com.kamco.cd.kamcoback.config; package com.kamco.cd.kamcoback.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.Duration; import java.time.Duration;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
@@ -38,6 +44,17 @@ public class RedisConfig {
return new LettuceConnectionFactory(redisConfig); return new LettuceConnectionFactory(redisConfig);
} }
@Bean
public ObjectMapper redisObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.findAndRegisterModules();
return objectMapper;
}
@Bean @Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisTemplate<String, Object> template = new RedisTemplate<>();
@@ -47,9 +64,11 @@ public class RedisConfig {
template.setKeySerializer(new StringRedisSerializer()); template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer());
// Value는 JSON으로 직렬화 // Value는 JSON으로 직렬화 (JavaTimeModule 포함)
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); GenericJackson2JsonRedisSerializer serializer =
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); new GenericJackson2JsonRedisSerializer(redisObjectMapper());
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet(); template.afterPropertiesSet();
return template; return template;
@@ -58,6 +77,25 @@ public class RedisConfig {
// 기본 레디스 캐시 세팅 // 기본 레디스 캐시 세팅
@Bean @Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
ObjectMapper cacheObjectMapper = new ObjectMapper();
cacheObjectMapper.registerModule(new JavaTimeModule());
cacheObjectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
cacheObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
cacheObjectMapper.findAndRegisterModules();
// 타입 정보 포함 - JAVA_LANG_OBJECT로 제한적으로 적용
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.kamco.cd.kamcoback")
.allowIfSubType("org.springframework.data.domain")
.allowIfSubType("java.util")
.allowIfSubType("java.time")
.build();
cacheObjectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer(cacheObjectMapper);
RedisCacheConfiguration config = RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig() RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 기본 TTL 1시간 .entryTtl(Duration.ofHours(1)) // 기본 TTL 1시간
@@ -65,8 +103,7 @@ public class RedisConfig {
RedisSerializationContext.SerializationPair.fromSerializer( RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer())) new StringRedisSerializer()))
.serializeValuesWith( .serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer( RedisSerializationContext.SerializationPair.fromSerializer(serializer));
new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build(); return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
} }

View File

@@ -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<Page<InferenceResultDto.DetailListEntity>> 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<Long> 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<InferenceResultDto.DetailListEntity> geomList =
inferenceResultService.listInferenceResultWithGeom(id, searchGeoReq);
return ApiResponseDto.ok(geomList);
}
}

View File

@@ -1,10 +1,13 @@
package com.kamco.cd.kamcoback.inference.dto; package com.kamco.cd.kamcoback.inference.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification; import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -173,6 +176,122 @@ public class InferenceResultDto {
} }
} }
// 분석 상세 ROW
@Getter
@AllArgsConstructor
public static class DetailListEntity {
private Uid code;
private Double detectionScore;
private Clazzes compare;
private Clazzes target;
private MapSheet mapSheet;
private Coordinate center;
@JsonFormatDttm private ZonedDateTime updatedDttm;
public DetailListEntity(
UUID uuid,
Double detectionScore,
Clazzes compare,
Clazzes target,
MapSheet mapSheet,
Coordinate center,
ZonedDateTime updatedDttm) {
this.code = new Uid(uuid);
this.detectionScore = detectionScore;
this.compare = compare;
this.target = target;
this.mapSheet = mapSheet;
this.center = center;
this.updatedDttm = updatedDttm;
}
}
@Getter
@AllArgsConstructor
public static class Uid {
private String shortCode;
private String code;
public Uid(UUID uuid) {
if (uuid != null) {
this.shortCode = uuid.toString().substring(0, 8).toUpperCase();
this.code = uuid.toString();
}
}
}
// MAP NO
@Getter
@AllArgsConstructor
public static class MapSheet {
private String number;
private String name;
}
// classification info
@Getter
public static class Clazz {
private String code;
private String name;
@JsonIgnore private Double score;
public Clazz(String code, Double score) {
this.code = code;
this.score = score;
this.name = DetectionClassification.fromString(code).getDesc();
}
public Clazz(String code) {
this.code = code;
this.name = DetectionClassification.fromString(code).getDesc();
}
}
// classification info
@Getter
public static class Clazzes {
private DetectionClassification code;
private String name;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Double score;
private Integer order;
public Clazzes(DetectionClassification classification, Double score) {
this.code = classification;
this.name = classification.getDesc();
this.order = classification.getOrder();
this.score = score;
}
public Clazzes(DetectionClassification classification) {
this.code = classification;
this.name = classification.getDesc();
this.order = classification.getOrder();
}
}
// 좌표 정보 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 @Getter
public static class Geom { public static class Geom {

View File

@@ -3,7 +3,9 @@ package com.kamco.cd.kamcoback.inference.service;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; 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.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Detail; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Detail;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -59,6 +61,18 @@ public class InferenceResultService {
return inferenceResultCoreService.getInferenceResultGeomList(id, searchGeoReq); return inferenceResultCoreService.getInferenceResultGeomList(id, searchGeoReq);
} }
/**
* 분석결과 상세 목록
*
* @param searchReq
* @return
*/
public Page<InferenceResultDto.DetailListEntity> listInferenceResultWithGeom(
@NotNull Long id, InferenceResultDto.SearchGeoReq searchReq) {
return inferenceResultCoreService.listInferenceResultWithGeom(id, searchReq);
}
/** /**
* 분석결과 상제 정보 Summary, DashBoard * 분석결과 상제 정보 Summary, DashBoard
* *
@@ -87,4 +101,8 @@ public class InferenceResultService {
public List<String> getSheets(Long id) { public List<String> getSheets(Long id) {
return inferenceResultCoreService.getSheets(id).stream().map(String::valueOf).toList(); return inferenceResultCoreService.getSheets(id).stream().map(String::valueOf).toList();
} }
public List<MapSheet> listGetScenes5k(Long id) {
return inferenceResultCoreService.listGetScenes5k(id);
}
} }

View File

@@ -2,18 +2,26 @@ package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; 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.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet;
import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity;
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 com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository;
import com.kamco.cd.kamcoback.postgres.repository.scene.MapInkx5kRepository;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class InferenceResultCoreService { public class InferenceResultCoreService {
private final InferenceResultRepository inferenceResultRepository; private final InferenceResultRepository inferenceResultRepository;
private final MapInkx5kRepository mapInkx5kRepository;
/** /**
* 추론관리 > 분석결과 목록 조회 * 추론관리 > 분석결과 목록 조회
@@ -61,6 +69,27 @@ public class InferenceResultCoreService {
return inferenceResultRepository.getInferenceGeomList(id, searchGeoReq); return inferenceResultRepository.getInferenceGeomList(id, searchGeoReq);
} }
/**
* 분석결과 상세 목록
*
* @param searchReq
* @return
*/
@Transactional(readOnly = true)
public Page<InferenceResultDto.DetailListEntity> listInferenceResultWithGeom(
@NotNull Long analyId, InferenceResultDto.SearchGeoReq searchReq) {
// 분석 ID 에 해당하는 dataids를 가져온다.
List<Long> dataIds =
inferenceResultRepository.listAnalyGeom(analyId).stream()
.mapToLong(MapSheetAnalDataEntity::getId)
.boxed()
.toList();
// 해당데이터의 폴리곤데이터를 가져온다
Page<MapSheetAnalDataGeomEntity> mapSheetAnalDataGeomEntities =
inferenceResultRepository.listInferenceResultWithGeom(dataIds, searchReq);
return mapSheetAnalDataGeomEntities.map(MapSheetAnalDataGeomEntity::toEntity);
}
/** /**
* 추론된 5000:1 도엽 목록 * 추론된 5000:1 도엽 목록
* *
@@ -70,4 +99,17 @@ public class InferenceResultCoreService {
public List<Long> getSheets(Long id) { public List<Long> getSheets(Long id) {
return inferenceResultRepository.getSheets(id); return inferenceResultRepository.getSheets(id);
} }
@Transactional(readOnly = true)
public List<MapSheet> listGetScenes5k(Long analyId) {
List<String> sceneCodes =
inferenceResultRepository.listAnalyGeom(analyId).stream()
.mapToLong(MapSheetAnalDataEntity::getMapSheetNum)
.mapToObj(String::valueOf)
.toList();
return mapInkx5kRepository.listGetScenes5k(sceneCodes).stream()
.map(MapInkx5kEntity::toEntity)
.toList();
}
} }

View File

@@ -1,6 +1,14 @@
package com.kamco.cd.kamcoback.postgres.entity; 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.Getter;
import lombok.Setter; import lombok.Setter;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
@@ -10,6 +18,7 @@ import org.locationtech.jts.geom.Geometry;
@Table(name = "tb_map_inkx_5k") @Table(name = "tb_map_inkx_5k")
@Entity @Entity
public class MapInkx5kEntity { public class MapInkx5kEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_5k_fid_seq_gen") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_5k_fid_seq_gen")
@SequenceGenerator( @SequenceGenerator(
@@ -29,4 +38,8 @@ public class MapInkx5kEntity {
@Column(name = "fid_k50") @Column(name = "fid_k50")
private Long fidK50; private Long fidK50;
public InferenceResultDto.MapSheet toEntity() {
return new MapSheet(mapidcdNo, mapidNm);
}
} }

View File

@@ -1,14 +1,21 @@
package com.kamco.cd.kamcoback.postgres.entity; package com.kamco.cd.kamcoback.postgres.entity;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Clazzes;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator; import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.ColumnDefault;
@@ -31,6 +38,9 @@ public class MapSheetAnalDataGeomEntity {
@Column(name = "geo_uid", nullable = false) @Column(name = "geo_uid", nullable = false)
private Long id; private Long id;
@Column(name = "uuid")
private UUID uuid;
@Column(name = "cd_prob") @Column(name = "cd_prob")
private Double cdProb; private Double cdProb;
@@ -51,6 +61,10 @@ public class MapSheetAnalDataGeomEntity {
@Column(name = "map_sheet_num") @Column(name = "map_sheet_num")
private Long mapSheetNum; private Long mapSheetNum;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "map_5k_id", referencedColumnName = "fid")
private MapInkx5kEntity map5k;
@Column(name = "compare_yyyy") @Column(name = "compare_yyyy")
private Integer compareYyyy; private Integer compareYyyy;
@@ -133,4 +147,21 @@ public class MapSheetAnalDataGeomEntity {
@Column(name = "geom_center", columnDefinition = "geometry") @Column(name = "geom_center", columnDefinition = "geometry")
private Geometry geomCenter; private Geometry geomCenter;
public InferenceResultDto.DetailListEntity toEntity() {
DetectionClassification classification = DetectionClassification.fromString(classBeforeCd);
Clazzes comparedClazz = new Clazzes(classification, classBeforeProb);
DetectionClassification classification1 = DetectionClassification.fromString(classAfterCd);
Clazzes targetClazz = new Clazzes(classification1, 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(
uuid, cdProb, comparedClazz, targetClazz, mapSheet, coordinate, createdDttm);
}
} }

View File

@@ -9,7 +9,9 @@ import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.ColumnDefault;
@@ -17,6 +19,9 @@ import org.hibernate.annotations.ColumnDefault;
@Setter @Setter
@Entity @Entity
@Table(name = "tb_map_sheet_anal") @Table(name = "tb_map_sheet_anal")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
// TODO SETTER 제거
// TODO 주석
public class MapSheetAnalEntity { public class MapSheetAnalEntity {
@Id @Id
@@ -28,21 +33,24 @@ public class MapSheetAnalEntity {
@Column(name = "anal_uid", nullable = false) @Column(name = "anal_uid", nullable = false)
private Long id; private Long id;
// TODO UUID
// UK 추가
@Column(name = "compare_yyyy") @Column(name = "compare_yyyy")
private Integer compareYyyy; private Integer compareYyyy; // 비교년도
@Column(name = "target_yyyy") @Column(name = "target_yyyy")
private Integer targetYyyy; private Integer targetYyyy; // 기분년도
@Column(name = "model_uid") @Column(name = "model_uid")
private Long modelUid; private Long modelUid; // 모델식별키 ?
@Size(max = 100) @Size(max = 100)
@Column(name = "server_ids", length = 100) @Column(name = "server_ids", length = 100)
private String serverIds; private String serverIds; // 서버ID?
@Column(name = "anal_map_sheet", length = Integer.MAX_VALUE) @Column(name = "anal_map_sheet", length = Integer.MAX_VALUE)
private String analMapSheet; private String analMapSheet; // 분석도엽?
@Column(name = "anal_strt_dttm") @Column(name = "anal_strt_dttm")
private ZonedDateTime analStrtDttm; private ZonedDateTime analStrtDttm;
@@ -54,15 +62,15 @@ public class MapSheetAnalEntity {
private Long analSec; private Long analSec;
@Column(name = "anal_pred_sec") @Column(name = "anal_pred_sec")
private Long analPredSec; private Long analPredSec; // 예상소요초?
@Size(max = 20) @Size(max = 20)
@Column(name = "anal_state", length = 20) @Column(name = "anal_state", length = 20)
private String analState; private String analState; // enum 으로 관리
@Size(max = 20) @Size(max = 20)
@Column(name = "gukyuin_used", length = 20) @Column(name = "gukyuin_used", length = 20)
private String gukyuinUsed; private String gukyuinUsed; // Boolean으로 관리
@Column(name = "accuracy") @Column(name = "accuracy")
private Double accuracy; private Double accuracy;
@@ -71,17 +79,9 @@ public class MapSheetAnalEntity {
@Column(name = "result_url") @Column(name = "result_url")
private String resultUrl; private String resultUrl;
@ColumnDefault("now()")
@Column(name = "created_dttm")
private ZonedDateTime createdDttm;
@Column(name = "created_uid") @Column(name = "created_uid")
private Long createdUid; private Long createdUid;
@ColumnDefault("now()")
@Column(name = "updated_dttm")
private ZonedDateTime updatedDttm;
@Column(name = "updated_uid") @Column(name = "updated_uid")
private Long updatedUid; private Long updatedUid;
@@ -94,4 +94,13 @@ public class MapSheetAnalEntity {
@Column(name = "base_map_sheet_num") @Column(name = "base_map_sheet_num")
private String baseMapSheetNum; private String baseMapSheetNum;
// TODO CommonDateEntity ?
@ColumnDefault("now()")
@Column(name = "created_dttm")
private ZonedDateTime createdDttm;
@ColumnDefault("now()")
@Column(name = "updated_dttm")
private ZonedDateTime updatedDttm;
} }

View File

@@ -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;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; 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.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -16,7 +20,12 @@ public interface InferenceResultRepositoryCustom {
Page<InferenceResultDto.Geom> getInferenceGeomList( Page<InferenceResultDto.Geom> getInferenceGeomList(
Long id, InferenceResultDto.SearchGeoReq searchGeoReq); Long id, InferenceResultDto.SearchGeoReq searchGeoReq);
Page<MapSheetAnalDataGeomEntity> listInferenceResultWithGeom(
List<Long> dataIds, SearchGeoReq searchReq);
List<Long> getSheets(Long id); List<Long> getSheets(Long id);
List<Dashboard> getDashboard(Long id); List<Dashboard> getDashboard(Long id);
List<MapSheetAnalDataEntity> listAnalyGeom(@NotNull Long id);
} }

View File

@@ -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;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq; 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.QMapSheetAnalDataEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity; 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.QModelMngEntity;
import com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity; import com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity;
import com.querydsl.core.BooleanBuilder; 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.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
@@ -80,7 +88,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
.where(builder) .where(builder)
.offset(pageable.getOffset()) .offset(pageable.getOffset())
.limit(pageable.getPageSize()) .limit(pageable.getPageSize())
.orderBy(mapSheetAnalEntity.createdDttm.desc()) .orderBy(mapSheetAnalEntity.id.desc())
.fetch(); .fetch();
long total = long total =
@@ -159,6 +167,56 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
.fetch(); .fetch();
} }
@Override
public List<MapSheetAnalDataEntity> listAnalyGeom(Long id) {
QMapSheetAnalDataEntity analy = QMapSheetAnalDataEntity.mapSheetAnalDataEntity;
return queryFactory.selectFrom(analy).where(analy.analUid.eq(id)).fetch();
}
/**
* 분석결과 상세 목록
*
* @param searchReq
* @return
*/
@Override
public Page<MapSheetAnalDataGeomEntity> listInferenceResultWithGeom(
List<Long> ids, SearchGeoReq searchReq) {
// 분석 차수
QMapSheetAnalDataGeomEntity detectedEntity =
QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity;
Pageable pageable = searchReq.toPageable();
// 검색조건
JPAQuery<MapSheetAnalDataGeomEntity> 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<OrderSpecifier<?>> orders = getOrderSpecifiers(pageable.getSort());
if (orders.isEmpty()) {
orders.add(detectedEntity.createdDttm.desc());
}
List<MapSheetAnalDataGeomEntity> 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) .groupBy(mapSheetAnalDataEntity.mapSheetNum)
.fetch(); .fetch();
} }
/** Pageable의 Sort를 QueryDSL OrderSpecifier로 변환 */
@SuppressWarnings({"unchecked", "rawtypes"})
private List<OrderSpecifier<?>> getOrderSpecifiers(Sort sort) {
List<OrderSpecifier<?>> 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<Long> mapSheet) {
if (mapSheet == null || mapSheet.isEmpty()) {
return null;
}
return detectedEntity.mapSheetNum.in(mapSheet);
}
} }

View File

@@ -126,7 +126,8 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataGeomEntity.classBeforeCd.toUpperCase(), mapSheetAnalDataGeomEntity.classBeforeCd.toUpperCase(),
mapSheetAnalDataGeomEntity.targetYyyy, mapSheetAnalDataGeomEntity.targetYyyy,
mapSheetAnalDataGeomEntity.classAfterProb, mapSheetAnalDataGeomEntity.classAfterProb,
mapSheetAnalDataGeomEntity.classAfterCd.toUpperCase())) mapSheetAnalDataGeomEntity.classAfterCd.toUpperCase(),
mapSheetAnalDataGeomEntity.cdProb))
.from(mapSheetAnalDataGeomEntity) .from(mapSheetAnalDataGeomEntity)
.innerJoin(mapSheetAnalDataEntity) .innerJoin(mapSheetAnalDataEntity)
.on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id)) .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id))
@@ -159,7 +160,8 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
data.getBeforeClass(), data.getBeforeClass(),
data.getAfterYear(), data.getAfterYear(),
data.getAfterConfidence(), data.getAfterConfidence(),
data.getAfterClass()); data.getAfterClass(),
data.getCdProb());
return new ChangeDetectionDto.PolygonFeature( return new ChangeDetectionDto.PolygonFeature(
data.getType(), jsonNode, properties); data.getType(), jsonNode, properties);

View File

@@ -0,0 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.scene;
import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MapInkx5kRepository
extends JpaRepository<MapInkx5kEntity, Long>, MapInkx5kRepositoryCustom {}

View File

@@ -0,0 +1,9 @@
package com.kamco.cd.kamcoback.postgres.repository.scene;
import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity;
import java.util.List;
public interface MapInkx5kRepositoryCustom {
List<MapInkx5kEntity> listGetScenes5k(List<String> codes);
}

View File

@@ -0,0 +1,28 @@
package com.kamco.cd.kamcoback.postgres.repository.scene;
import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
public class MapInkx5kRepositoryImpl extends QuerydslRepositorySupport
implements MapInkx5kRepositoryCustom {
private final JPAQueryFactory queryFactory;
public MapInkx5kRepositoryImpl(JPAQueryFactory queryFactory) {
super(MapInkx5kEntity.class);
this.queryFactory = queryFactory;
}
public List<MapInkx5kEntity> listGetScenes5k(List<String> codes) {
QMapInkx5kEntity map5k = QMapInkx5kEntity.mapInkx5kEntity;
return queryFactory
.selectFrom(map5k)
.where(map5k.mapidcdNo.in(codes))
.orderBy(map5k.mapidcdNo.asc())
.fetch();
}
}