Rviewer add
This commit is contained in:
@@ -90,7 +90,8 @@ public class SecurityConfig {
|
||||
"/api/user/**",
|
||||
"/api/my/menus",
|
||||
"/api/common-code/**",
|
||||
"/api/training-data/label/**")
|
||||
"/api/training-data/label/**",
|
||||
"/api/training-data/review/**")
|
||||
.authenticated()
|
||||
.anyRequest()
|
||||
.access(menuAuthorizationManager)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.repository.trainingdata.TrainingDataLabelRepository;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
|
||||
@@ -79,4 +80,13 @@ public class TrainingDataLabelCoreService {
|
||||
public DefaultPaging getDefaultPagingNumber(String userId, Integer size, String assignmentUid) {
|
||||
return trainingDataLabelRepository.getDefaultPagingNumber(userId, size, assignmentUid);
|
||||
}
|
||||
|
||||
public void saveNewPolygon(TrainingDataLabelDto.NewPolygonRequest request) {
|
||||
trainingDataLabelRepository.saveNewPolygon(request);
|
||||
}
|
||||
|
||||
public TrainingDataLabelDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear) {
|
||||
return trainingDataLabelRepository.getCogImageUrl(mapSheetNum, beforeYear, afterYear);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.repository.trainingdata.TrainingDataReviewRepository;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.GeoFeatureRequest.Properties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.searchReq;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TrainingDataReviewCoreService {
|
||||
|
||||
private final TrainingDataReviewRepository trainingDataReviewRepository;
|
||||
|
||||
public Page<ReviewListDto> findReviewAssignedList(searchReq searchReq, String userId) {
|
||||
return trainingDataReviewRepository.findReviewAssignedList(searchReq, userId);
|
||||
}
|
||||
|
||||
public ReviewGeometryInfo findReviewAssignedGeom(String operatorUid) {
|
||||
return trainingDataReviewRepository.findReviewAssignedGeom(operatorUid);
|
||||
}
|
||||
|
||||
public Long findReviewOperatorGeoUid(String operatorUid) {
|
||||
return trainingDataReviewRepository.findReviewOperatorGeoUid(operatorUid);
|
||||
}
|
||||
|
||||
public void updateReviewStateOperator(String operatorUid, String status, String memo) {
|
||||
trainingDataReviewRepository.updateReviewStateOperator(operatorUid, status, memo);
|
||||
}
|
||||
|
||||
public void updateReviewExceptState(Long inferenceGeomUid, String status) {
|
||||
trainingDataReviewRepository.updateReviewExceptState(inferenceGeomUid, status);
|
||||
}
|
||||
|
||||
public void updateReviewPolygonClass(
|
||||
Long inferenceGeomUid, Geometry geometry, Properties properties, String status) {
|
||||
trainingDataReviewRepository.updateReviewPolygonClass(
|
||||
inferenceGeomUid, geometry, properties, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 검수자별 작업 통계 조회
|
||||
*
|
||||
* @param userId 검수자 사번
|
||||
* @return 전체/미작업/Today 건수
|
||||
*/
|
||||
public SummaryRes getSummary(String userId) {
|
||||
try {
|
||||
System.out.println("[CoreService] getSummary called with userId: " + userId);
|
||||
SummaryRes result = trainingDataReviewRepository.getSummary(userId);
|
||||
System.out.println("[CoreService] getSummary result: " + result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
System.err.println("[CoreService] getSummary ERROR: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// 예외 발생 시에도 빈 통계 반환
|
||||
return SummaryRes.builder().totalCnt(0L).undoneCnt(0L).todayCnt(0L).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검수 작업 상세 정보 조회
|
||||
*
|
||||
* @param operatorUid 검수 작업 ID
|
||||
* @return 변화탐지정보 + 실태조사결과정보
|
||||
*/
|
||||
public DetailRes getDetail(UUID operatorUid) {
|
||||
return trainingDataReviewRepository.getDetail(operatorUid);
|
||||
}
|
||||
|
||||
public DefaultPaging getDefaultPagingNumber(String userId, Integer size, String operatorUid) {
|
||||
return trainingDataReviewRepository.getDefaultPagingNumber(userId, size, operatorUid);
|
||||
}
|
||||
|
||||
public void saveNewPolygon(TrainingDataReviewDto.NewPolygonRequest request) {
|
||||
trainingDataReviewRepository.saveNewPolygon(request);
|
||||
}
|
||||
|
||||
public TrainingDataReviewDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear) {
|
||||
return trainingDataReviewRepository.getCogImageUrl(mapSheetNum, beforeYear, afterYear);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
|
||||
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
|
||||
@@ -31,4 +32,9 @@ public interface TrainingDataLabelRepositoryCustom {
|
||||
DetailRes getDetail(UUID assignmentUid);
|
||||
|
||||
DefaultPaging getDefaultPagingNumber(String userId, Integer size, String assignmentUid);
|
||||
|
||||
void saveNewPolygon(TrainingDataLabelDto.NewPolygonRequest request);
|
||||
|
||||
TrainingDataLabelDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ChangeDetectionInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ClassificationInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
|
||||
@@ -519,8 +520,8 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 라벨링 저장한 Geometry를 GeoJSON으로 변환
|
||||
LearnDataGeometry learnData =
|
||||
// 7. 라벨링 저장한 Geometry들을 GeoJSON으로 변환 (여러 개 가능)
|
||||
List<LearnDataGeometry> learnDataList =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
@@ -536,7 +537,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
|
||||
.where(
|
||||
mapSheetLearnDataGeomEntity.geoUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
|
||||
.fetchOne();
|
||||
.fetch(); // fetchOne() -> fetch()로 변경
|
||||
|
||||
return DetailRes.builder()
|
||||
.assignmentUid(assignmentUid)
|
||||
@@ -546,7 +547,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
|
||||
.beforeCogUrl(beforeCogUrl)
|
||||
.afterCogUrl(afterCogUrl)
|
||||
.mapBox(mapBbox)
|
||||
.learnGeometry(learnData)
|
||||
.learnGeometries(learnDataList) // learnGeometry -> learnGeometries
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -624,6 +625,165 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
|
||||
return DefaultPaging.builder().page(page).assignmentUid(firstAssignedUid).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewPolygon(TrainingDataLabelDto.NewPolygonRequest request) {
|
||||
try {
|
||||
if (request.getFeatures() == null || request.getFeatures().isEmpty()) {
|
||||
throw new RuntimeException("No polygons to save");
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"Saving "
|
||||
+ request.getFeatures().size()
|
||||
+ " new polygon(s) for mapSheetNum: "
|
||||
+ request.getMapSheetNum());
|
||||
|
||||
int savedCount = 0;
|
||||
for (TrainingDataLabelDto.NewPolygonRequest.PolygonFeature feature : request.getFeatures()) {
|
||||
try {
|
||||
// 1. map_sheet_anal_data_inference_geom 테이블에 새 polygon 삽입
|
||||
queryFactory
|
||||
.insert(mapSheetAnalDataInferenceGeomEntity)
|
||||
.columns(
|
||||
mapSheetAnalDataInferenceGeomEntity.geom,
|
||||
mapSheetAnalDataInferenceGeomEntity.compareYyyy,
|
||||
mapSheetAnalDataInferenceGeomEntity.targetYyyy,
|
||||
mapSheetAnalDataInferenceGeomEntity.classBeforeCd,
|
||||
mapSheetAnalDataInferenceGeomEntity.classAfterCd,
|
||||
mapSheetAnalDataInferenceGeomEntity.createdDttm,
|
||||
mapSheetAnalDataInferenceGeomEntity.updatedDttm,
|
||||
mapSheetAnalDataInferenceGeomEntity.labelState)
|
||||
.values(
|
||||
feature.getGeometry(),
|
||||
request.getCompareYyyy(),
|
||||
request.getTargetYyyy(),
|
||||
feature.getProperties().getBeforeClass().toLowerCase(),
|
||||
feature.getProperties().getAfterClass().toLowerCase(),
|
||||
ZonedDateTime.now(),
|
||||
ZonedDateTime.now(),
|
||||
"DONE")
|
||||
.execute();
|
||||
|
||||
// 2. 생성된 geoUid 조회
|
||||
Long geoUid =
|
||||
queryFactory
|
||||
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
|
||||
.from(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(
|
||||
mapSheetAnalDataInferenceGeomEntity.geom.eq(feature.getGeometry()),
|
||||
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(request.getCompareYyyy()),
|
||||
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(request.getTargetYyyy()))
|
||||
.orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.desc())
|
||||
.fetchFirst();
|
||||
|
||||
if (geoUid == null) {
|
||||
System.err.println("Failed to get geo_uid for polygon #" + (savedCount + 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. learn_data_geom 테이블에도 삽입
|
||||
queryFactory
|
||||
.insert(mapSheetLearnDataGeomEntity)
|
||||
.columns(
|
||||
mapSheetLearnDataGeomEntity.geoUid,
|
||||
mapSheetLearnDataGeomEntity.afterYyyy,
|
||||
mapSheetLearnDataGeomEntity.beforeYyyy,
|
||||
mapSheetLearnDataGeomEntity.classAfterCd,
|
||||
mapSheetLearnDataGeomEntity.classBeforeCd,
|
||||
mapSheetLearnDataGeomEntity.geom,
|
||||
mapSheetLearnDataGeomEntity.createdDate,
|
||||
mapSheetLearnDataGeomEntity.modifiedDate)
|
||||
.values(
|
||||
geoUid,
|
||||
request.getTargetYyyy(),
|
||||
request.getCompareYyyy(),
|
||||
feature.getProperties().getAfterClass().toLowerCase(),
|
||||
feature.getProperties().getBeforeClass().toLowerCase(),
|
||||
feature.getGeometry(),
|
||||
ZonedDateTime.now(),
|
||||
ZonedDateTime.now())
|
||||
.execute();
|
||||
|
||||
savedCount++;
|
||||
System.out.println(
|
||||
"Successfully saved polygon #" + savedCount + " with geo_uid: " + geoUid);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error saving polygon #" + (savedCount + 1) + ": " + e.getMessage());
|
||||
// 개별 polygon 저장 실패해도 계속 진행
|
||||
}
|
||||
}
|
||||
|
||||
if (savedCount == 0) {
|
||||
throw new RuntimeException("Failed to save any polygons");
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"Successfully saved " + savedCount + "/" + request.getFeatures().size() + " polygon(s)");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("saveNewPolygon Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to save new polygons", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrainingDataLabelDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear) {
|
||||
try {
|
||||
// beforeYear COG URL 조회
|
||||
String beforeCogUrl =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.from(imageryEntity)
|
||||
.where(imageryEntity.scene5k.eq(mapSheetNum), imageryEntity.year.eq(beforeYear))
|
||||
.fetchFirst();
|
||||
|
||||
// afterYear COG URL 조회
|
||||
String afterCogUrl =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.from(imageryEntity)
|
||||
.where(imageryEntity.scene5k.eq(mapSheetNum), imageryEntity.year.eq(afterYear))
|
||||
.fetchFirst();
|
||||
|
||||
if (beforeCogUrl == null && afterCogUrl == null) {
|
||||
throw new RuntimeException(
|
||||
"COG images not found for mapSheetNum: "
|
||||
+ mapSheetNum
|
||||
+ ", years: "
|
||||
+ beforeYear
|
||||
+ ", "
|
||||
+ afterYear);
|
||||
}
|
||||
|
||||
return TrainingDataLabelDto.CogImageResponse.builder()
|
||||
.beforeCogUrl(beforeCogUrl != null ? beforeCogUrl : "")
|
||||
.afterCogUrl(afterCogUrl != null ? afterCogUrl : "")
|
||||
.beforeYear(beforeYear)
|
||||
.afterYear(afterYear)
|
||||
.mapSheetNum(mapSheetNum)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("getCogImageUrl Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(
|
||||
"Failed to get COG image URLs for mapSheetNum: "
|
||||
+ mapSheetNum
|
||||
+ ", years: "
|
||||
+ beforeYear
|
||||
+ ", "
|
||||
+ afterYear,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private StringExpression makeCogUrl(NumberPath<Integer> year) {
|
||||
return new CaseBuilder()
|
||||
.when(imageryEntity.year.eq(year))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface TrainingDataReviewRepository
|
||||
extends JpaRepository<LabelingAssignmentEntity, Long>, TrainingDataReviewRepositoryCustom {}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
|
||||
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.GeoFeatureRequest.Properties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.searchReq;
|
||||
import java.util.UUID;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface TrainingDataReviewRepositoryCustom {
|
||||
|
||||
Page<ReviewListDto> findReviewAssignedList(searchReq searchReq, String userId);
|
||||
|
||||
ReviewGeometryInfo findReviewAssignedGeom(String operatorUid);
|
||||
|
||||
Long findReviewOperatorGeoUid(String operatorUid);
|
||||
|
||||
void updateReviewStateOperator(String operatorUid, String status, String memo);
|
||||
|
||||
void updateReviewExceptState(Long inferenceGeomUid, String status);
|
||||
|
||||
void updateReviewPolygonClass(
|
||||
Long inferenceGeomUid, Geometry geometry, Properties properties, String status);
|
||||
|
||||
SummaryRes getSummary(String userId);
|
||||
|
||||
DetailRes getDetail(UUID operatorUid);
|
||||
|
||||
DefaultPaging getDefaultPagingNumber(String userId, Integer size, String operatorUid);
|
||||
|
||||
void saveNewPolygon(TrainingDataReviewDto.NewPolygonRequest request);
|
||||
|
||||
TrainingDataReviewDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear);
|
||||
}
|
||||
@@ -0,0 +1,842 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
|
||||
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QImageryEntity.imageryEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ChangeDetectionInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ClassificationInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.GeoFeatureRequest.Properties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.InferenceDataGeometry;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.InferenceDataGeometry.InferenceProperties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.InspectionResultInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.LearnDataGeometry;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.LearnDataGeometry.LearnProperties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.searchReq;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.core.types.dsl.CaseBuilder;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberPath;
|
||||
import com.querydsl.core.types.dsl.StringExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||
|
||||
@Slf4j
|
||||
public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements TrainingDataReviewRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
|
||||
public TrainingDataReviewRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||
super(LabelingAssignmentEntity.class);
|
||||
this.queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ReviewListDto> findReviewAssignedList(searchReq searchReq, String userId) {
|
||||
|
||||
// 완료된 검수는 오늘만, 나머지는 전체 조회
|
||||
LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul"));
|
||||
ZonedDateTime start = today.atStartOfDay(ZoneId.of("Asia/Seoul"));
|
||||
ZonedDateTime end = start.plusDays(1);
|
||||
|
||||
BooleanExpression doneToday =
|
||||
labelingAssignmentEntity
|
||||
.inspectState
|
||||
.eq(InspectState.COMPLETE.getId())
|
||||
.and(labelingAssignmentEntity.inspectStatDttm.goe(start))
|
||||
.and(labelingAssignmentEntity.inspectStatDttm.lt(end));
|
||||
|
||||
BooleanExpression unconfirmOrExcept =
|
||||
labelingAssignmentEntity.inspectState.in(
|
||||
InspectState.EXCEPT.getId(), InspectState.UNCONFIRM.getId());
|
||||
|
||||
BooleanExpression dayStateCondition = doneToday.or(unconfirmOrExcept);
|
||||
|
||||
Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize());
|
||||
List<ReviewListDto> list =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
ReviewListDto.class,
|
||||
labelingAssignmentEntity.assignmentUid,
|
||||
labelingAssignmentEntity.inferenceGeomUid,
|
||||
labelingAssignmentEntity.inspectorUid,
|
||||
labelingAssignmentEntity.inspectState,
|
||||
labelingAssignmentEntity.assignGroupId,
|
||||
mapInkx5kEntity.mapidNm,
|
||||
mapSheetAnalDataInferenceGeomEntity.pnu))
|
||||
.from(labelingAssignmentEntity)
|
||||
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
|
||||
.on(
|
||||
labelingAssignmentEntity.inferenceGeomUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid))
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(labelingAssignmentEntity.assignGroupId.eq(mapInkx5kEntity.mapidcdNo))
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
dayStateCondition,
|
||||
labelingAssignmentEntity.workState.eq("DONE")) // 라벨링 완료된 것만 검수 대상
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.orderBy(
|
||||
labelingAssignmentEntity.createdDate.asc(),
|
||||
labelingAssignmentEntity.inferenceGeomUid.asc())
|
||||
.fetch();
|
||||
|
||||
Long count =
|
||||
Optional.ofNullable(
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.assignmentUid.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
|
||||
.on(
|
||||
labelingAssignmentEntity.inferenceGeomUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid))
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(labelingAssignmentEntity.assignGroupId.eq(mapInkx5kEntity.mapidcdNo))
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
dayStateCondition,
|
||||
labelingAssignmentEntity.workState.eq("DONE"))
|
||||
.fetchOne())
|
||||
.orElse(0L);
|
||||
|
||||
return new PageImpl<>(list, pageable, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewGeometryInfo findReviewAssignedGeom(String operatorUid) {
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
ReviewGeometryInfo.class,
|
||||
labelingAssignmentEntity.assignmentUid,
|
||||
labelingAssignmentEntity.inferenceGeomUid,
|
||||
Expressions.stringTemplate(
|
||||
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geom),
|
||||
makeCogUrl(mapSheetAnalDataInferenceGeomEntity.compareYyyy)
|
||||
.max()
|
||||
.as("beforeCogUrl"),
|
||||
makeCogUrl(mapSheetAnalDataInferenceGeomEntity.targetYyyy).max().as("afterCogUrl"),
|
||||
Expressions.stringTemplate("ST_AsGeoJSON({0})", mapInkx5kEntity.geom)))
|
||||
.from(labelingAssignmentEntity)
|
||||
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
|
||||
.on(
|
||||
labelingAssignmentEntity.inferenceGeomUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid))
|
||||
.innerJoin(mapInkx5kEntity)
|
||||
.on(labelingAssignmentEntity.assignGroupId.eq(mapInkx5kEntity.mapidcdNo))
|
||||
.leftJoin(imageryEntity)
|
||||
.on(
|
||||
imageryEntity
|
||||
.scene5k
|
||||
.eq(labelingAssignmentEntity.assignGroupId)
|
||||
.and(
|
||||
imageryEntity
|
||||
.year
|
||||
.eq(mapSheetAnalDataInferenceGeomEntity.compareYyyy)
|
||||
.or(imageryEntity.year.eq(mapSheetAnalDataInferenceGeomEntity.targetYyyy))))
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(operatorUid)))
|
||||
.groupBy(
|
||||
labelingAssignmentEntity.assignmentUid,
|
||||
labelingAssignmentEntity.inferenceGeomUid,
|
||||
labelingAssignmentEntity.inspectorUid,
|
||||
labelingAssignmentEntity.inspectState,
|
||||
labelingAssignmentEntity.assignGroupId,
|
||||
mapInkx5kEntity.mapidNm,
|
||||
mapSheetAnalDataInferenceGeomEntity.pnu,
|
||||
mapSheetAnalDataInferenceGeomEntity.geom,
|
||||
mapInkx5kEntity.geom)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long findReviewOperatorGeoUid(String operatorUid) {
|
||||
return queryFactory
|
||||
.select(labelingAssignmentEntity.inferenceGeomUid)
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(operatorUid)))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReviewStateOperator(String operatorUid, String status, String memo) {
|
||||
queryFactory
|
||||
.update(labelingAssignmentEntity)
|
||||
.set(labelingAssignmentEntity.inspectState, status)
|
||||
.set(labelingAssignmentEntity.inspectStatDttm, ZonedDateTime.now())
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(operatorUid)))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReviewExceptState(Long inferenceGeomUid, String status) {
|
||||
// 검수 제외 시 assignment 테이블만 업데이트
|
||||
// (inference_geom 테이블에는 inspect_state 컬럼이 없음)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReviewPolygonClass(
|
||||
Long inferenceGeomUid, Geometry geometry, Properties properties, String status) {
|
||||
// inference_geom 테이블 정보 가져오기
|
||||
MapSheetAnalDataInferenceGeomEntity entity =
|
||||
queryFactory
|
||||
.selectFrom(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(inferenceGeomUid))
|
||||
.fetchOne();
|
||||
|
||||
if (Objects.isNull(entity)) {
|
||||
throw new EntityNotFoundException(
|
||||
"MapSheetAnalDataInferenceGeomEntity not found for inferenceGeomUid: "
|
||||
+ inferenceGeomUid);
|
||||
}
|
||||
|
||||
// learn_data_geom 업데이트 또는 insert (검수 완료된 폴리곤 수정)
|
||||
Long existingLearnGeomUid =
|
||||
queryFactory
|
||||
.select(mapSheetLearnDataGeomEntity.geoUid)
|
||||
.from(mapSheetLearnDataGeomEntity)
|
||||
.where(mapSheetLearnDataGeomEntity.geoUid.eq(inferenceGeomUid))
|
||||
.fetchOne();
|
||||
|
||||
if (existingLearnGeomUid != null) {
|
||||
// 기존 데이터 업데이트
|
||||
queryFactory
|
||||
.update(mapSheetLearnDataGeomEntity)
|
||||
.set(mapSheetLearnDataGeomEntity.classAfterCd, properties.getAfterClass().toLowerCase())
|
||||
.set(mapSheetLearnDataGeomEntity.classBeforeCd, properties.getBeforeClass().toLowerCase())
|
||||
.set(mapSheetLearnDataGeomEntity.geom, geometry)
|
||||
.set(mapSheetLearnDataGeomEntity.modifiedDate, ZonedDateTime.now())
|
||||
.where(mapSheetLearnDataGeomEntity.geoUid.eq(inferenceGeomUid))
|
||||
.execute();
|
||||
} else {
|
||||
// 새로운 데이터 insert
|
||||
queryFactory
|
||||
.insert(mapSheetLearnDataGeomEntity)
|
||||
.columns(
|
||||
mapSheetLearnDataGeomEntity.geoUid,
|
||||
mapSheetLearnDataGeomEntity.afterYyyy,
|
||||
mapSheetLearnDataGeomEntity.beforeYyyy,
|
||||
mapSheetLearnDataGeomEntity.classAfterCd,
|
||||
mapSheetLearnDataGeomEntity.classBeforeCd,
|
||||
mapSheetLearnDataGeomEntity.geom,
|
||||
mapSheetLearnDataGeomEntity.createdDate,
|
||||
mapSheetLearnDataGeomEntity.modifiedDate)
|
||||
.values(
|
||||
inferenceGeomUid,
|
||||
entity.getTargetYyyy(),
|
||||
entity.getCompareYyyy(),
|
||||
properties.getAfterClass().toLowerCase(),
|
||||
properties.getBeforeClass().toLowerCase(),
|
||||
geometry,
|
||||
ZonedDateTime.now(),
|
||||
ZonedDateTime.now())
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SummaryRes getSummary(String userId) {
|
||||
// 기본값 설정
|
||||
Long totalCnt = 0L;
|
||||
Long undoneCnt = 0L;
|
||||
Long todayCnt = 0L;
|
||||
|
||||
try {
|
||||
System.out.println("=== getSummary START (Review) ===");
|
||||
System.out.println("userId: " + userId);
|
||||
|
||||
// 1. 전체 배정 건수 (라벨링 완료된 것만)
|
||||
try {
|
||||
Long result =
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
labelingAssignmentEntity.workState.eq("DONE"))
|
||||
.fetchOne();
|
||||
|
||||
totalCnt = (result != null) ? result : 0L;
|
||||
System.out.println("totalCnt: " + totalCnt);
|
||||
} catch (Exception e) {
|
||||
System.err.println(
|
||||
"totalCnt query error: " + e.getClass().getName() + " - " + e.getMessage());
|
||||
if (e.getCause() != null) {
|
||||
System.err.println("Caused by: " + e.getCause().getMessage());
|
||||
}
|
||||
totalCnt = 0L;
|
||||
}
|
||||
|
||||
// 2. 미작업 건수 (UNCONFIRM 상태)
|
||||
try {
|
||||
Long result =
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
labelingAssignmentEntity.workState.eq("DONE"),
|
||||
labelingAssignmentEntity.inspectState.eq("UNCONFIRM"))
|
||||
.fetchOne();
|
||||
|
||||
undoneCnt = (result != null) ? result : 0L;
|
||||
System.out.println("undoneCnt: " + undoneCnt);
|
||||
} catch (Exception e) {
|
||||
System.err.println(
|
||||
"undoneCnt query error: " + e.getClass().getName() + " - " + e.getMessage());
|
||||
if (e.getCause() != null) {
|
||||
System.err.println("Caused by: " + e.getCause().getMessage());
|
||||
}
|
||||
undoneCnt = 0L;
|
||||
}
|
||||
|
||||
// 3. 오늘 완료 건수
|
||||
try {
|
||||
// 오늘 날짜의 시작과 끝 시간 계산
|
||||
ZonedDateTime startOfToday = LocalDate.now().atStartOfDay(java.time.ZoneId.systemDefault());
|
||||
ZonedDateTime endOfToday = startOfToday.plusDays(1);
|
||||
System.out.println("startOfToday: " + startOfToday);
|
||||
System.out.println("endOfToday: " + endOfToday);
|
||||
|
||||
Long result =
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
labelingAssignmentEntity.inspectState.eq("COMPLETE"),
|
||||
labelingAssignmentEntity.inspectStatDttm.isNotNull(),
|
||||
labelingAssignmentEntity.inspectStatDttm.goe(startOfToday),
|
||||
labelingAssignmentEntity.inspectStatDttm.lt(endOfToday))
|
||||
.fetchOne();
|
||||
|
||||
todayCnt = (result != null) ? result : 0L;
|
||||
System.out.println("todayCnt: " + todayCnt);
|
||||
} catch (Exception e) {
|
||||
System.err.println(
|
||||
"todayCnt query error: " + e.getClass().getName() + " - " + e.getMessage());
|
||||
if (e.getCause() != null) {
|
||||
System.err.println("Caused by: " + e.getCause().getMessage());
|
||||
}
|
||||
todayCnt = 0L;
|
||||
}
|
||||
|
||||
System.out.println("=== getSummary END (Review) ===");
|
||||
System.out.println(
|
||||
"Final result - totalCnt: "
|
||||
+ totalCnt
|
||||
+ ", undoneCnt: "
|
||||
+ undoneCnt
|
||||
+ ", todayCnt: "
|
||||
+ todayCnt);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 최상위 예외 처리
|
||||
System.err.println("=== getSummary OUTER ERROR (Review) ===");
|
||||
System.err.println("Error: " + e.getClass().getName() + " - " + e.getMessage());
|
||||
if (e.getCause() != null) {
|
||||
System.err.println("Caused by: " + e.getCause().getMessage());
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 항상 정상 응답 반환 (예외를 throw하지 않음)
|
||||
return SummaryRes.builder().totalCnt(totalCnt).undoneCnt(undoneCnt).todayCnt(todayCnt).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetailRes getDetail(UUID operatorUid) {
|
||||
try {
|
||||
// 1. 작업 배정 정보 조회
|
||||
var assignment =
|
||||
queryFactory
|
||||
.selectFrom(labelingAssignmentEntity)
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(operatorUid))
|
||||
.fetchOne();
|
||||
|
||||
if (assignment == null) {
|
||||
throw new RuntimeException("Assignment not found: " + operatorUid);
|
||||
}
|
||||
|
||||
// 2. 추론 결과 정보 조회
|
||||
var mapSheetAnalDataInferenceGeomEntityEntity =
|
||||
queryFactory
|
||||
.selectFrom(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
|
||||
assignment.toDto().getInferenceGeomUid()))
|
||||
.fetchOne();
|
||||
|
||||
if (mapSheetAnalDataInferenceGeomEntityEntity == null) {
|
||||
throw new RuntimeException(
|
||||
"Inference geometry not found: " + assignment.toDto().getInferenceGeomUid());
|
||||
}
|
||||
|
||||
// 3. 도엽 정보 조회
|
||||
var mapSheetEntity =
|
||||
queryFactory
|
||||
.selectFrom(mapInkx5kEntity)
|
||||
.where(mapInkx5kEntity.mapidcdNo.eq(assignment.toDto().getAssignGroupId()))
|
||||
.fetchOne();
|
||||
|
||||
// 4. COG URL 조회 - imagery만 사용
|
||||
String beforeCogUrl = "";
|
||||
String afterCogUrl = "";
|
||||
|
||||
try {
|
||||
var beforeImagery =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.from(imageryEntity)
|
||||
.where(
|
||||
imageryEntity.scene5k.eq(assignment.toDto().getAssignGroupId()),
|
||||
imageryEntity.year.eq(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy()))
|
||||
.fetchFirst();
|
||||
beforeCogUrl = beforeImagery != null ? beforeImagery : "";
|
||||
|
||||
var afterImagery =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.from(imageryEntity)
|
||||
.where(
|
||||
imageryEntity.scene5k.eq(assignment.toDto().getAssignGroupId()),
|
||||
imageryEntity.year.eq(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy()))
|
||||
.fetchFirst();
|
||||
afterCogUrl = afterImagery != null ? afterImagery : "";
|
||||
} catch (Exception e) {
|
||||
System.err.println("COG URL retrieval error: " + e.getMessage());
|
||||
// COG URL 조회 실패 시 빈 문자열 유지
|
||||
}
|
||||
|
||||
// 5. DTO 생성
|
||||
var changeDetectionInfo =
|
||||
ChangeDetectionInfo.builder()
|
||||
.mapSheetInfo(mapSheetEntity != null ? mapSheetEntity.getMapidNm() : "")
|
||||
.detectionYear(
|
||||
(mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy()
|
||||
: 0)
|
||||
+ "-"
|
||||
+ (mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy()
|
||||
: 0))
|
||||
.beforeClass(
|
||||
ClassificationInfo.builder()
|
||||
.classification(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd()
|
||||
: "")
|
||||
.probability(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb()
|
||||
: 0.0)
|
||||
.build())
|
||||
.afterClass(
|
||||
ClassificationInfo.builder()
|
||||
.classification(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()
|
||||
: "")
|
||||
.probability(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb()
|
||||
: 0.0)
|
||||
.build())
|
||||
.area(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getArea() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getArea()
|
||||
: 0.0)
|
||||
.detectionAccuracy(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getCdProb() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getCdProb()
|
||||
: 0.0)
|
||||
.pnu(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getPnu()
|
||||
: 0L)
|
||||
.build();
|
||||
|
||||
var inspectionResultInfo =
|
||||
InspectionResultInfo.builder()
|
||||
.verificationResult(convertInspectState(assignment.toDto().getInspectState()))
|
||||
.inappropriateReason("")
|
||||
.memo("")
|
||||
.build();
|
||||
|
||||
// 6. Geometry를 GeoJSON으로 변환
|
||||
InferenceDataGeometry inferData =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
InferenceDataGeometry.class,
|
||||
Expressions.stringTemplate("{0}", "Feature"),
|
||||
Expressions.stringTemplate(
|
||||
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geom),
|
||||
Projections.constructor(
|
||||
InferenceProperties.class,
|
||||
mapSheetAnalDataInferenceGeomEntity.classBeforeCd,
|
||||
mapSheetAnalDataInferenceGeomEntity.classAfterCd)))
|
||||
.from(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
|
||||
.fetchOne();
|
||||
|
||||
// 도엽 bbox json으로 가져오기
|
||||
JsonNode mapBbox = null;
|
||||
if (mapSheetEntity.getGeom() != null) {
|
||||
try {
|
||||
String bboxString =
|
||||
queryFactory
|
||||
.select(Expressions.stringTemplate("ST_AsGeoJSON({0})", mapInkx5kEntity.geom))
|
||||
.from(mapInkx5kEntity)
|
||||
.where(mapInkx5kEntity.mapidcdNo.eq(assignment.toDto().getAssignGroupId()))
|
||||
.fetchOne();
|
||||
if (bboxString != null) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapBbox = mapper.readTree(bboxString);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("GeoJSON parsing error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 라벨링 저장한 Geometry들을 GeoJSON으로 변환 (여러 개 가능)
|
||||
List<LearnDataGeometry> learnDataList =
|
||||
queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
LearnDataGeometry.class,
|
||||
Expressions.stringTemplate("{0}", "Feature"),
|
||||
Expressions.stringTemplate(
|
||||
"ST_AsGeoJSON({0})", mapSheetLearnDataGeomEntity.geom),
|
||||
Projections.constructor(
|
||||
LearnProperties.class,
|
||||
mapSheetLearnDataGeomEntity.classBeforeCd,
|
||||
mapSheetLearnDataGeomEntity.classAfterCd)))
|
||||
.from(mapSheetLearnDataGeomEntity)
|
||||
.where(
|
||||
mapSheetLearnDataGeomEntity.geoUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
|
||||
.fetch(); // fetchOne() -> fetch()로 변경
|
||||
|
||||
return DetailRes.builder()
|
||||
.operatorUid(operatorUid)
|
||||
.changeDetectionInfo(changeDetectionInfo)
|
||||
.inspectionResultInfo(inspectionResultInfo)
|
||||
.geom(inferData)
|
||||
.beforeCogUrl(beforeCogUrl)
|
||||
.afterCogUrl(afterCogUrl)
|
||||
.mapBox(mapBbox)
|
||||
.learnGeometries(learnDataList) // learnGeometry -> learnGeometries
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("getDetail Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to get detail for operatorUid: " + operatorUid, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultPaging getDefaultPagingNumber(String userId, Integer size, String operatorUid) {
|
||||
|
||||
ZoneId KST = ZoneId.of("Asia/Seoul");
|
||||
ZonedDateTime todayStart = ZonedDateTime.now(KST).toLocalDate().atStartOfDay(KST);
|
||||
ZonedDateTime todayEnd = todayStart.plusDays(1);
|
||||
|
||||
BooleanExpression doneToday =
|
||||
labelingAssignmentEntity
|
||||
.inspectState
|
||||
.eq(InspectState.COMPLETE.getId())
|
||||
.and(labelingAssignmentEntity.inspectStatDttm.goe(todayStart))
|
||||
.and(labelingAssignmentEntity.inspectStatDttm.lt(todayEnd));
|
||||
|
||||
BooleanExpression unconfirmOrExcept =
|
||||
labelingAssignmentEntity.inspectState.in(
|
||||
InspectState.EXCEPT.getId(), InspectState.UNCONFIRM.getId());
|
||||
|
||||
BooleanExpression stateCondition = doneToday.or(unconfirmOrExcept);
|
||||
|
||||
Tuple firstAssigned =
|
||||
queryFactory
|
||||
.select(
|
||||
labelingAssignmentEntity.assignmentUid,
|
||||
labelingAssignmentEntity.createdDate,
|
||||
labelingAssignmentEntity.inferenceGeomUid)
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
labelingAssignmentEntity.workState.eq("DONE"),
|
||||
stateCondition,
|
||||
operatorUid == null
|
||||
? labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId())
|
||||
: labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(operatorUid)))
|
||||
.orderBy(
|
||||
labelingAssignmentEntity.createdDate.asc(),
|
||||
labelingAssignmentEntity.inferenceGeomUid.asc())
|
||||
.limit(1)
|
||||
.fetchOne();
|
||||
|
||||
if (firstAssigned == null) {
|
||||
return DefaultPaging.builder().page(0).operatorUid(null).build();
|
||||
}
|
||||
|
||||
UUID firstAssignedUid = firstAssigned.get(labelingAssignmentEntity.assignmentUid);
|
||||
ZonedDateTime createdDttm = firstAssigned.get(labelingAssignmentEntity.createdDate);
|
||||
Long inferenceGeomUid = firstAssigned.get(labelingAssignmentEntity.inferenceGeomUid);
|
||||
|
||||
BooleanExpression beforeCondition =
|
||||
labelingAssignmentEntity
|
||||
.createdDate
|
||||
.lt(createdDttm)
|
||||
.or(
|
||||
labelingAssignmentEntity
|
||||
.createdDate
|
||||
.eq(createdDttm)
|
||||
.and(labelingAssignmentEntity.inferenceGeomUid.lt(inferenceGeomUid)));
|
||||
|
||||
Long beforeCnt =
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(
|
||||
labelingAssignmentEntity.inspectorUid.eq(userId),
|
||||
labelingAssignmentEntity.workState.eq("DONE"),
|
||||
beforeCondition.and(stateCondition))
|
||||
.fetchOne();
|
||||
|
||||
int page = (int) (beforeCnt / size); // 기본 사이즈 20
|
||||
return DefaultPaging.builder().page(page).operatorUid(firstAssignedUid).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewPolygon(TrainingDataReviewDto.NewPolygonRequest request) {
|
||||
try {
|
||||
if (request.getFeatures() == null || request.getFeatures().isEmpty()) {
|
||||
throw new RuntimeException("No polygons to save");
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"Saving "
|
||||
+ request.getFeatures().size()
|
||||
+ " new polygon(s) for mapSheetNum: "
|
||||
+ request.getMapSheetNum());
|
||||
|
||||
int savedCount = 0;
|
||||
for (TrainingDataReviewDto.NewPolygonRequest.PolygonFeature feature : request.getFeatures()) {
|
||||
try {
|
||||
// 1. map_sheet_anal_data_inference_geom 테이블에 새 polygon 삽입
|
||||
queryFactory
|
||||
.insert(mapSheetAnalDataInferenceGeomEntity)
|
||||
.columns(
|
||||
mapSheetAnalDataInferenceGeomEntity.geom,
|
||||
mapSheetAnalDataInferenceGeomEntity.compareYyyy,
|
||||
mapSheetAnalDataInferenceGeomEntity.targetYyyy,
|
||||
mapSheetAnalDataInferenceGeomEntity.classBeforeCd,
|
||||
mapSheetAnalDataInferenceGeomEntity.classAfterCd,
|
||||
mapSheetAnalDataInferenceGeomEntity.createdDttm,
|
||||
mapSheetAnalDataInferenceGeomEntity.updatedDttm,
|
||||
mapSheetAnalDataInferenceGeomEntity.labelState)
|
||||
.values(
|
||||
feature.getGeometry(),
|
||||
request.getCompareYyyy(),
|
||||
request.getTargetYyyy(),
|
||||
feature.getProperties().getBeforeClass().toLowerCase(),
|
||||
feature.getProperties().getAfterClass().toLowerCase(),
|
||||
ZonedDateTime.now(),
|
||||
ZonedDateTime.now(),
|
||||
"DONE")
|
||||
.execute();
|
||||
|
||||
// 2. 생성된 geoUid 조회
|
||||
Long geoUid =
|
||||
queryFactory
|
||||
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
|
||||
.from(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(
|
||||
mapSheetAnalDataInferenceGeomEntity.geom.eq(feature.getGeometry()),
|
||||
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(request.getCompareYyyy()),
|
||||
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(request.getTargetYyyy()))
|
||||
.orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.desc())
|
||||
.fetchFirst();
|
||||
|
||||
if (geoUid == null) {
|
||||
System.err.println("Failed to get geo_uid for polygon #" + (savedCount + 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. learn_data_geom 테이블에도 삽입
|
||||
queryFactory
|
||||
.insert(mapSheetLearnDataGeomEntity)
|
||||
.columns(
|
||||
mapSheetLearnDataGeomEntity.geoUid,
|
||||
mapSheetLearnDataGeomEntity.afterYyyy,
|
||||
mapSheetLearnDataGeomEntity.beforeYyyy,
|
||||
mapSheetLearnDataGeomEntity.classAfterCd,
|
||||
mapSheetLearnDataGeomEntity.classBeforeCd,
|
||||
mapSheetLearnDataGeomEntity.geom,
|
||||
mapSheetLearnDataGeomEntity.createdDate,
|
||||
mapSheetLearnDataGeomEntity.modifiedDate)
|
||||
.values(
|
||||
geoUid,
|
||||
request.getTargetYyyy(),
|
||||
request.getCompareYyyy(),
|
||||
feature.getProperties().getAfterClass().toLowerCase(),
|
||||
feature.getProperties().getBeforeClass().toLowerCase(),
|
||||
feature.getGeometry(),
|
||||
ZonedDateTime.now(),
|
||||
ZonedDateTime.now())
|
||||
.execute();
|
||||
|
||||
savedCount++;
|
||||
System.out.println(
|
||||
"Successfully saved polygon #" + savedCount + " with geo_uid: " + geoUid);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error saving polygon #" + (savedCount + 1) + ": " + e.getMessage());
|
||||
// 개별 polygon 저장 실패해도 계속 진행
|
||||
}
|
||||
}
|
||||
|
||||
if (savedCount == 0) {
|
||||
throw new RuntimeException("Failed to save any polygons");
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"Successfully saved " + savedCount + "/" + request.getFeatures().size() + " polygon(s)");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("saveNewPolygon Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to save new polygons", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrainingDataReviewDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear) {
|
||||
try {
|
||||
// beforeYear COG URL 조회
|
||||
String beforeCogUrl =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.from(imageryEntity)
|
||||
.where(imageryEntity.scene5k.eq(mapSheetNum), imageryEntity.year.eq(beforeYear))
|
||||
.fetchFirst();
|
||||
|
||||
// afterYear COG URL 조회
|
||||
String afterCogUrl =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.from(imageryEntity)
|
||||
.where(imageryEntity.scene5k.eq(mapSheetNum), imageryEntity.year.eq(afterYear))
|
||||
.fetchFirst();
|
||||
|
||||
if (beforeCogUrl == null && afterCogUrl == null) {
|
||||
throw new RuntimeException(
|
||||
"COG images not found for mapSheetNum: "
|
||||
+ mapSheetNum
|
||||
+ ", years: "
|
||||
+ beforeYear
|
||||
+ ", "
|
||||
+ afterYear);
|
||||
}
|
||||
|
||||
return TrainingDataReviewDto.CogImageResponse.builder()
|
||||
.beforeCogUrl(beforeCogUrl != null ? beforeCogUrl : "")
|
||||
.afterCogUrl(afterCogUrl != null ? afterCogUrl : "")
|
||||
.beforeYear(beforeYear)
|
||||
.afterYear(afterYear)
|
||||
.mapSheetNum(mapSheetNum)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("getCogImageUrl Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(
|
||||
"Failed to get COG image URLs for mapSheetNum: "
|
||||
+ mapSheetNum
|
||||
+ ", years: "
|
||||
+ beforeYear
|
||||
+ ", "
|
||||
+ afterYear,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private StringExpression makeCogUrl(NumberPath<Integer> year) {
|
||||
return new CaseBuilder()
|
||||
.when(imageryEntity.year.eq(year))
|
||||
.then(
|
||||
Expressions.stringTemplate(
|
||||
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
|
||||
.otherwise("");
|
||||
}
|
||||
|
||||
private BooleanExpression statusInInspectState(String status) {
|
||||
if (Objects.isNull(status)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] arrStatus = status.split(",");
|
||||
return labelingAssignmentEntity.inspectState.in(arrStatus);
|
||||
}
|
||||
|
||||
private String convertInspectState(String inspectState) {
|
||||
if (inspectState == null) {
|
||||
return "미확인";
|
||||
}
|
||||
switch (inspectState) {
|
||||
case "UNCONFIRM":
|
||||
return "미확인";
|
||||
case "EXCEPT":
|
||||
return "제외";
|
||||
case "COMPLETE":
|
||||
return "완료";
|
||||
default:
|
||||
return "미확인";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,4 +182,163 @@ public class TrainingDataLabelApiController {
|
||||
return ApiResponseDto.ok(
|
||||
trainingDataLabelService.getDefaultPagingNumber(userId, size, assignmentUid));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "새로운 polygon(들) 추가 저장",
|
||||
description = "탐지결과 외 새로운 polygon을 추가로 저장합니다. 단일 또는 여러 개를 저장할 수 있습니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "저장 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = ResponseObj.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/new-polygon")
|
||||
public ApiResponseDto<ResponseObj> saveNewPolygon(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "새로운 polygon 저장 요청",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema =
|
||||
@Schema(implementation = TrainingDataLabelDto.NewPolygonRequest.class),
|
||||
examples = {
|
||||
@io.swagger.v3.oas.annotations.media.ExampleObject(
|
||||
name = "1개 polygon 저장",
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"assignmentUid": "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02",
|
||||
"analUid": 53,
|
||||
"mapSheetNum": "35905086",
|
||||
"compareYyyy": 2023,
|
||||
"targetYyyy": 2024,
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.663, 34.588],
|
||||
[126.662, 34.587],
|
||||
[126.664, 34.589],
|
||||
[126.663, 34.588]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "WASTE",
|
||||
"afterClass": "LAND"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""),
|
||||
@io.swagger.v3.oas.annotations.media.ExampleObject(
|
||||
name = "3개 polygon 저장",
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"assignmentUid": "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02",
|
||||
"analUid": 53,
|
||||
"mapSheetNum": "35905086",
|
||||
"compareYyyy": 2023,
|
||||
"targetYyyy": 2024,
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.663, 34.588],
|
||||
[126.662, 34.587],
|
||||
[126.664, 34.589],
|
||||
[126.663, 34.588]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "WASTE",
|
||||
"afterClass": "LAND"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.665, 34.590],
|
||||
[126.664, 34.589],
|
||||
[126.666, 34.591],
|
||||
[126.665, 34.590]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "FOREST",
|
||||
"afterClass": "BUILDING"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.667, 34.592],
|
||||
[126.666, 34.591],
|
||||
[126.668, 34.593],
|
||||
[126.667, 34.592]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "FARMLAND",
|
||||
"afterClass": "SOLAR_PANEL"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
}))
|
||||
@RequestBody
|
||||
TrainingDataLabelDto.NewPolygonRequest request) {
|
||||
return ApiResponseDto.okObject(trainingDataLabelService.saveNewPolygon(request));
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "COG 이미지 URL 조회", description = "변화 전/후 COG 이미지 URL을 함께 조회합니다")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema =
|
||||
@Schema(implementation = TrainingDataLabelDto.CogImageResponse.class))),
|
||||
@ApiResponse(responseCode = "404", description = "이미지를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/cog-image")
|
||||
public ApiResponseDto<TrainingDataLabelDto.CogImageResponse> getCogImageUrl(
|
||||
@Parameter(description = "도엽번호", required = true, example = "35905086") @RequestParam
|
||||
String mapSheetNum,
|
||||
@Parameter(description = "변화 전 년도", required = true, example = "2023") @RequestParam
|
||||
Integer beforeYear,
|
||||
@Parameter(description = "변화 후 년도", required = true, example = "2024") @RequestParam
|
||||
Integer afterYear) {
|
||||
return ApiResponseDto.ok(
|
||||
trainingDataLabelService.getCogImageUrl(mapSheetNum, beforeYear, afterYear));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
package com.kamco.cd.kamcoback.trainingdata;
|
||||
|
||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.service.TrainingDataReviewService;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "라벨링 툴 > 검수자", description = "라벨링 툴 > 검수자 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/training-data/review")
|
||||
public class TrainingDataReviewApiController {
|
||||
|
||||
private final TrainingDataReviewService trainingDataReviewService;
|
||||
|
||||
@Operation(summary = "목록 조회", description = "검수 할당 목록 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping
|
||||
public ApiResponseDto<Page<ReviewListDto>> findReviewAssignedList(
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(defaultValue = "01022223333") String userId) {
|
||||
TrainingDataReviewDto.searchReq searchReq = new TrainingDataReviewDto.searchReq(page, size, "");
|
||||
return ApiResponseDto.ok(trainingDataReviewService.findReviewAssignedList(searchReq, userId));
|
||||
}
|
||||
|
||||
@Hidden
|
||||
@Operation(summary = "상세 Geometry 조회", description = "검수 할당 상세 Geometry 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/geom-info")
|
||||
public ApiResponseDto<ReviewGeometryInfo> findReviewAssignedGeom(
|
||||
@RequestParam(defaultValue = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02") String operatorUid) {
|
||||
return ApiResponseDto.ok(trainingDataReviewService.findReviewAssignedGeom(operatorUid));
|
||||
}
|
||||
|
||||
@Operation(summary = "검수 결과 저장", description = "검수 결과 저장")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
|
||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping
|
||||
public ApiResponseDto<ResponseObj> saveReviewFeature(
|
||||
@RequestBody TrainingDataReviewDto.GeoFeatureRequest request) {
|
||||
return ApiResponseDto.okObject(trainingDataReviewService.saveReviewFeature(request));
|
||||
}
|
||||
|
||||
@Operation(summary = "작업 통계 조회", description = "검수자의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = TrainingDataReviewDto.SummaryRes.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/summary")
|
||||
public ApiResponseDto<TrainingDataReviewDto.SummaryRes> getSummary(
|
||||
@io.swagger.v3.oas.annotations.Parameter(
|
||||
description = "검수자 사번",
|
||||
required = true,
|
||||
example = "01022223333")
|
||||
@RequestParam
|
||||
String userId) {
|
||||
try {
|
||||
System.out.println("[Controller] getSummary called with userId: " + userId);
|
||||
TrainingDataReviewDto.SummaryRes result = trainingDataReviewService.getSummary(userId);
|
||||
System.out.println("[Controller] getSummary result: " + result);
|
||||
return ApiResponseDto.ok(result);
|
||||
} catch (Exception e) {
|
||||
System.err.println("[Controller] getSummary ERROR: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// 예외 발생 시에도 빈 통계 반환
|
||||
return ApiResponseDto.ok(
|
||||
TrainingDataReviewDto.SummaryRes.builder()
|
||||
.totalCnt(0L)
|
||||
.undoneCnt(0L)
|
||||
.todayCnt(0L)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "변화탐지정보 및 실태조사결과 조회", description = "선택한 작업의 변화탐지정보 및 실태조사결과를 조회합니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = TrainingDataReviewDto.DetailRes.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/detail")
|
||||
public ApiResponseDto<TrainingDataReviewDto.DetailRes> getDetail(
|
||||
@io.swagger.v3.oas.annotations.Parameter(
|
||||
description = "검수 작업 ID (UUID)",
|
||||
required = true,
|
||||
example = "93c56be8-0246-4b22-b976-2476549733cc")
|
||||
@RequestParam
|
||||
java.util.UUID operatorUid) {
|
||||
return ApiResponseDto.ok(trainingDataReviewService.getDetail(operatorUid));
|
||||
}
|
||||
|
||||
@Operation(summary = "검수자 기본 page number 제공", description = "검수자 기본 page number 제공")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = TrainingDataReviewDto.DetailRes.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/default-page")
|
||||
public ApiResponseDto<TrainingDataReviewDto.DefaultPaging> getDefaultPagingNumber(
|
||||
@Parameter(description = "사번", example = "01022223333") @RequestParam String userId,
|
||||
@Parameter(description = "페이징 사이즈", example = "20") @RequestParam(defaultValue = "20")
|
||||
Integer size,
|
||||
@Parameter(description = "개별 UUID", example = "79bcdbbe-6ed4-4caa-b4a4-22f3cf2f9d25")
|
||||
@RequestParam(required = false)
|
||||
String operatorUid) {
|
||||
return ApiResponseDto.ok(
|
||||
trainingDataReviewService.getDefaultPagingNumber(userId, size, operatorUid));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "새로운 polygon(들) 추가 저장",
|
||||
description = "탐지결과 외 새로운 polygon을 추가로 저장합니다. 단일 또는 여러 개를 저장할 수 있습니다.")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "저장 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = ResponseObj.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/new-polygon")
|
||||
public ApiResponseDto<ResponseObj> saveNewPolygon(
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "새로운 polygon 저장 요청",
|
||||
required = true,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema =
|
||||
@Schema(implementation = TrainingDataReviewDto.NewPolygonRequest.class),
|
||||
examples = {
|
||||
@io.swagger.v3.oas.annotations.media.ExampleObject(
|
||||
name = "1개 polygon 저장",
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"operatorUid": "93c56be8-0246-4b22-b976-2476549733cc",
|
||||
"analUid": 53,
|
||||
"mapSheetNum": "35905086",
|
||||
"compareYyyy": 2023,
|
||||
"targetYyyy": 2024,
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.663, 34.588],
|
||||
[126.662, 34.587],
|
||||
[126.664, 34.589],
|
||||
[126.663, 34.588]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "WASTE",
|
||||
"afterClass": "LAND"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""),
|
||||
@io.swagger.v3.oas.annotations.media.ExampleObject(
|
||||
name = "3개 polygon 저장",
|
||||
value =
|
||||
"""
|
||||
{
|
||||
"operatorUid": "93c56be8-0246-4b22-b976-2476549733cc",
|
||||
"analUid": 53,
|
||||
"mapSheetNum": "35905086",
|
||||
"compareYyyy": 2023,
|
||||
"targetYyyy": 2024,
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.663, 34.588],
|
||||
[126.662, 34.587],
|
||||
[126.664, 34.589],
|
||||
[126.663, 34.588]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "WASTE",
|
||||
"afterClass": "LAND"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.665, 34.590],
|
||||
[126.664, 34.589],
|
||||
[126.666, 34.591],
|
||||
[126.665, 34.590]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "FOREST",
|
||||
"afterClass": "BUILDING"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[126.667, 34.592],
|
||||
[126.666, 34.591],
|
||||
[126.668, 34.593],
|
||||
[126.667, 34.592]
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"beforeClass": "FARMLAND",
|
||||
"afterClass": "SOLAR_PANEL"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
}))
|
||||
@RequestBody
|
||||
TrainingDataReviewDto.NewPolygonRequest request) {
|
||||
return ApiResponseDto.okObject(trainingDataReviewService.saveNewPolygon(request));
|
||||
}
|
||||
|
||||
@Operation(summary = "COG 이미지 URL 조회", description = "변화 전/후 COG 이미지 URL을 함께 조회합니다")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema =
|
||||
@Schema(implementation = TrainingDataReviewDto.CogImageResponse.class))),
|
||||
@ApiResponse(responseCode = "404", description = "이미지를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/cog-image")
|
||||
public ApiResponseDto<TrainingDataReviewDto.CogImageResponse> getCogImageUrl(
|
||||
@Parameter(description = "도엽번호", required = true, example = "35905086") @RequestParam
|
||||
String mapSheetNum,
|
||||
@Parameter(description = "변화 전 년도", required = true, example = "2023") @RequestParam
|
||||
Integer beforeYear,
|
||||
@Parameter(description = "변화 후 년도", required = true, example = "2024") @RequestParam
|
||||
Integer afterYear) {
|
||||
return ApiResponseDto.ok(
|
||||
trainingDataReviewService.getCogImageUrl(mapSheetNum, beforeYear, afterYear));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -298,8 +299,8 @@ public class TrainingDataLabelDto {
|
||||
@Schema(description = "도엽 bbox")
|
||||
private JsonNode mapBox;
|
||||
|
||||
@Schema(description = "라벨링 툴에서 그린 폴리곤")
|
||||
private LearnDataGeometry learnGeometry;
|
||||
@Schema(description = "라벨링 툴에서 그린 폴리곤들 (여러 개 가능)")
|
||||
private List<LearnDataGeometry> learnGeometries;
|
||||
}
|
||||
|
||||
@Schema(name = "ChangeDetectionInfo", description = "변화탐지정보")
|
||||
@@ -394,4 +395,133 @@ public class TrainingDataLabelDto {
|
||||
private int page;
|
||||
private UUID assignmentUid;
|
||||
}
|
||||
|
||||
@Schema(name = "NewPolygonRequest", description = "새로운 polygon(들) 추가 저장")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class NewPolygonRequest {
|
||||
|
||||
@Schema(description = "assignmentUid", example = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
|
||||
private String assignmentUid;
|
||||
|
||||
@Schema(description = "anal_uid", example = "1")
|
||||
private Long analUid;
|
||||
|
||||
@Schema(description = "map_sheet_num (도엽번호)", example = "NI52-3-13-1")
|
||||
private String mapSheetNum;
|
||||
|
||||
@Schema(description = "compare_yyyy (변화 전 년도)", example = "2022")
|
||||
private Integer compareYyyy;
|
||||
|
||||
@Schema(description = "target_yyyy (변화 후 년도)", example = "2023")
|
||||
private Integer targetYyyy;
|
||||
|
||||
@Schema(description = "새로 그린 polygon 리스트")
|
||||
private List<PolygonFeature> features;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PolygonFeature {
|
||||
|
||||
@Schema(description = "type", example = "Feature")
|
||||
private String type;
|
||||
|
||||
@JsonDeserialize(using = GeometryDeserializer.class)
|
||||
@Schema(
|
||||
description = "polygon geometry",
|
||||
example =
|
||||
"""
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
126.66292461969202,
|
||||
34.58785236216609
|
||||
],
|
||||
[
|
||||
126.66263801099049,
|
||||
34.58740117447532
|
||||
],
|
||||
[
|
||||
126.66293668521236,
|
||||
34.5873904146878
|
||||
],
|
||||
[
|
||||
126.66312820122245,
|
||||
34.587841464427825
|
||||
],
|
||||
[
|
||||
126.66289124481979,
|
||||
34.58786048381633
|
||||
],
|
||||
[
|
||||
126.66292461969202,
|
||||
34.58785236216609
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
""")
|
||||
private Geometry geometry;
|
||||
|
||||
@Schema(description = "polygon properties")
|
||||
private PolygonProperties properties;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PolygonProperties {
|
||||
|
||||
@Schema(description = "beforeClass", example = "WASTE")
|
||||
private String beforeClass;
|
||||
|
||||
@Schema(description = "afterClass", example = "LAND")
|
||||
private String afterClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "CogImageRequest", description = "COG 이미지 조회 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CogImageRequest {
|
||||
|
||||
@Schema(description = "map_sheet_num (도엽번호)", example = "NI52-3-13-1", required = true)
|
||||
private String mapSheetNum;
|
||||
|
||||
@Schema(description = "year (년도)", example = "2022", required = true)
|
||||
private Integer year;
|
||||
}
|
||||
|
||||
@Schema(name = "CogImageResponse", description = "COG 이미지 URL 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CogImageResponse {
|
||||
|
||||
@Schema(description = "변화 전 COG 이미지 URL")
|
||||
private String beforeCogUrl;
|
||||
|
||||
@Schema(description = "변화 후 COG 이미지 URL")
|
||||
private String afterCogUrl;
|
||||
|
||||
@Schema(description = "변화 전 년도")
|
||||
private Integer beforeYear;
|
||||
|
||||
@Schema(description = "변화 후 년도")
|
||||
private Integer afterYear;
|
||||
|
||||
@Schema(description = "도엽번호")
|
||||
private String mapSheetNum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
package com.kamco.cd.kamcoback.trainingdata.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
public class TrainingDataReviewDto {
|
||||
|
||||
@Schema(name = "ReviewListDto", description = "ReviewListDto")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class ReviewListDto {
|
||||
|
||||
private UUID operatorUid;
|
||||
private Long inferenceGeomUid;
|
||||
private String inspectorUid;
|
||||
private String inspectState;
|
||||
private String mapSheetNum;
|
||||
private String mapIdNm;
|
||||
private Long pnu;
|
||||
|
||||
public ReviewListDto(
|
||||
UUID operatorUid,
|
||||
Long inferenceGeomUid,
|
||||
String inspectorUid,
|
||||
String inspectState,
|
||||
String mapSheetNum,
|
||||
String mapIdNm,
|
||||
Long pnu) {
|
||||
this.operatorUid = operatorUid;
|
||||
this.inferenceGeomUid = inferenceGeomUid;
|
||||
this.inspectorUid = inspectorUid;
|
||||
this.inspectState = inspectState;
|
||||
this.mapSheetNum = mapSheetNum;
|
||||
this.mapIdNm = mapIdNm;
|
||||
this.pnu = pnu;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "ReviewGeometryInfo", description = "ReviewGeometryInfo")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class ReviewGeometryInfo {
|
||||
|
||||
private UUID operatorUid;
|
||||
private Long inferenceGeomUid;
|
||||
@JsonIgnore private String geomData; // json string
|
||||
private JsonNode geom;
|
||||
private String beforeCogUrl;
|
||||
private String afterCogUrl;
|
||||
@JsonIgnore private String mapBboxString; // json string
|
||||
private JsonNode mapBbox;
|
||||
|
||||
public ReviewGeometryInfo(
|
||||
UUID operatorUid,
|
||||
Long inferenceGeomUid,
|
||||
String geomData,
|
||||
String beforeCogUrl,
|
||||
String afterCogUrl,
|
||||
String mapBboxString) {
|
||||
this.operatorUid = operatorUid;
|
||||
this.inferenceGeomUid = inferenceGeomUid;
|
||||
this.beforeCogUrl = beforeCogUrl;
|
||||
this.afterCogUrl = afterCogUrl;
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode geomJson;
|
||||
JsonNode mapBboxJson;
|
||||
try {
|
||||
geomJson = mapper.readTree(geomData);
|
||||
mapBboxJson = mapper.readTree(mapBboxString);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
this.geom = geomJson;
|
||||
this.mapBbox = mapBboxJson;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "GeoFeatureRequest", description = "검수 결과 저장")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class GeoFeatureRequest {
|
||||
|
||||
@Schema(description = "operatorUid", example = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
|
||||
private String operatorUid;
|
||||
|
||||
@Schema(description = "type", example = "Feature")
|
||||
private String type;
|
||||
|
||||
@JsonDeserialize(using = GeometryDeserializer.class)
|
||||
@Schema(
|
||||
description = "검수 결과 polygon",
|
||||
example =
|
||||
"""
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
126.66292461969202,
|
||||
34.58785236216609
|
||||
],
|
||||
[
|
||||
126.66263801099049,
|
||||
34.58740117447532
|
||||
],
|
||||
[
|
||||
126.66293668521236,
|
||||
34.5873904146878
|
||||
],
|
||||
[
|
||||
126.66312820122245,
|
||||
34.587841464427825
|
||||
],
|
||||
[
|
||||
126.66289124481979,
|
||||
34.58786048381633
|
||||
],
|
||||
[
|
||||
126.66292461969202,
|
||||
34.58785236216609
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
""")
|
||||
private Geometry geometry;
|
||||
|
||||
private Properties properties;
|
||||
|
||||
@Getter
|
||||
public static class Properties {
|
||||
|
||||
@Schema(description = "beforeClass", example = "WASTE")
|
||||
private String beforeClass;
|
||||
|
||||
@Schema(description = "afterClass", example = "LAND")
|
||||
private String afterClass;
|
||||
|
||||
@Schema(description = "inspectState", example = "COMPLETE")
|
||||
private String inspectState;
|
||||
|
||||
@Schema(description = "inspectMemo", example = "검수 완료")
|
||||
private String inspectMemo;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "InferenceDataGeometry", description = "InferenceDataGeometry")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class InferenceDataGeometry {
|
||||
|
||||
private String type;
|
||||
@JsonIgnore private String learnGeomString;
|
||||
private JsonNode geometry;
|
||||
private InferenceProperties properties;
|
||||
|
||||
public InferenceDataGeometry(
|
||||
String type, String learnGeomString, InferenceProperties properties) {
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode inferenceJson;
|
||||
try {
|
||||
inferenceJson = mapper.readTree(learnGeomString);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.geometry = inferenceJson;
|
||||
|
||||
if (inferenceJson.isObject()) {
|
||||
((ObjectNode) inferenceJson).remove("crs");
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class InferenceProperties {
|
||||
|
||||
@Schema(description = "beforeClass", example = "WASTE")
|
||||
private String beforeClass;
|
||||
|
||||
@Schema(description = "afterClass", example = "LAND")
|
||||
private String afterClass;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "LearnDataGeometry", description = "LearnDataGeometry")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class LearnDataGeometry {
|
||||
|
||||
private String type;
|
||||
@JsonIgnore private String learnGeomString;
|
||||
private JsonNode geometry;
|
||||
private LearnProperties properties;
|
||||
|
||||
public LearnDataGeometry(String type, String learnGeomString, LearnProperties properties) {
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode learnJson;
|
||||
try {
|
||||
learnJson = mapper.readTree(learnGeomString);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.geometry = learnJson;
|
||||
|
||||
if (learnJson.isObject()) {
|
||||
((ObjectNode) learnJson).remove("crs");
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class LearnProperties {
|
||||
|
||||
@Schema(description = "beforeClass", example = "WASTE")
|
||||
private String beforeClass;
|
||||
|
||||
@Schema(description = "afterClass", example = "LAND")
|
||||
private String afterClass;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "searchReq", description = "검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class searchReq {
|
||||
|
||||
// 페이징 파라미터
|
||||
private int page = 0;
|
||||
private int size = 20;
|
||||
private String sort;
|
||||
|
||||
public Pageable toPageable() {
|
||||
if (sort != null && !sort.isEmpty()) {
|
||||
String[] sortParams = sort.split(",");
|
||||
String property = sortParams[0];
|
||||
Sort.Direction direction =
|
||||
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
|
||||
return PageRequest.of(page, size, Sort.by(direction, property));
|
||||
}
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "DetailRes", description = "객체 상세 정보 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class DetailRes {
|
||||
|
||||
@Schema(description = "검수 작업 ID")
|
||||
private UUID operatorUid;
|
||||
|
||||
@Schema(description = "변화탐지정보")
|
||||
private ChangeDetectionInfo changeDetectionInfo;
|
||||
|
||||
@Schema(description = "실태조사결과정보")
|
||||
private InspectionResultInfo inspectionResultInfo;
|
||||
|
||||
@Schema(description = "Geometry (GeoJSON)")
|
||||
private InferenceDataGeometry geom;
|
||||
|
||||
@Schema(description = "변화 전 COG 이미지 URL")
|
||||
private String beforeCogUrl;
|
||||
|
||||
@Schema(description = "변화 후 COG 이미지 URL")
|
||||
private String afterCogUrl;
|
||||
|
||||
@Schema(description = "도엽 bbox")
|
||||
private JsonNode mapBox;
|
||||
|
||||
@Schema(description = "검수 시 추가/수정한 폴리곤들 (여러 개 가능)")
|
||||
private List<LearnDataGeometry> learnGeometries;
|
||||
}
|
||||
|
||||
@Schema(name = "ChangeDetectionInfo", description = "변화탐지정보")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ChangeDetectionInfo {
|
||||
|
||||
@Schema(description = "도엽번호정보", example = "남해")
|
||||
private String mapSheetInfo;
|
||||
|
||||
@Schema(description = "변화탐지연도", example = "2022-2023")
|
||||
private String detectionYear;
|
||||
|
||||
@Schema(description = "변화 전 분류 정보")
|
||||
private ClassificationInfo beforeClass;
|
||||
|
||||
@Schema(description = "변화 후 분류 정보")
|
||||
private ClassificationInfo afterClass;
|
||||
|
||||
@Schema(description = "면적 (㎡)", example = "179.52")
|
||||
private Double area;
|
||||
|
||||
@Schema(description = "탐지정확도 (%)", example = "84.8")
|
||||
private Double detectionAccuracy;
|
||||
|
||||
@Schema(description = "PNU (필지고유번호)", example = "36221202306020")
|
||||
private Long pnu;
|
||||
}
|
||||
|
||||
@Schema(name = "ClassificationInfo", description = "분류정보")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ClassificationInfo {
|
||||
|
||||
@Schema(description = "분류", example = "일반토지")
|
||||
private String classification;
|
||||
|
||||
@Schema(description = "확률", example = "80.0")
|
||||
private Double probability;
|
||||
}
|
||||
|
||||
@Schema(name = "InspectionResultInfo", description = "실태조사결과정보")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class InspectionResultInfo {
|
||||
|
||||
@Schema(description = "검증결과 (미확인/제외/완료)", example = "미확인")
|
||||
private String verificationResult;
|
||||
|
||||
@Schema(description = "부적합사유")
|
||||
private String inappropriateReason;
|
||||
|
||||
@Schema(description = "메모")
|
||||
private String memo;
|
||||
}
|
||||
|
||||
@Schema(name = "SummaryRes", description = "작업 통계 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SummaryRes {
|
||||
|
||||
@Schema(description = "전체 배정 건수", example = "8901")
|
||||
private Long totalCnt;
|
||||
|
||||
@Schema(description = "미작업 건수 (UNCONFIRM 상태)", example = "7211")
|
||||
private Long undoneCnt;
|
||||
|
||||
@Schema(description = "오늘 완료 건수", example = "0")
|
||||
private Long todayCnt;
|
||||
}
|
||||
|
||||
@Schema(name = "DefaultPaging", description = "페이징 기본 number, uuid 전달")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class DefaultPaging {
|
||||
|
||||
private int page;
|
||||
private UUID operatorUid;
|
||||
}
|
||||
|
||||
@Schema(name = "NewPolygonRequest", description = "새로운 polygon(들) 추가 저장")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class NewPolygonRequest {
|
||||
|
||||
@Schema(description = "operatorUid", example = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
|
||||
private String operatorUid;
|
||||
|
||||
@Schema(description = "anal_uid", example = "53")
|
||||
private Long analUid;
|
||||
|
||||
@Schema(description = "map_sheet_num (도엽번호)", example = "35905086")
|
||||
private String mapSheetNum;
|
||||
|
||||
@Schema(description = "compare_yyyy (변화 전 년도)", example = "2023")
|
||||
private Integer compareYyyy;
|
||||
|
||||
@Schema(description = "target_yyyy (변화 후 년도)", example = "2024")
|
||||
private Integer targetYyyy;
|
||||
|
||||
@Schema(description = "새로 그린 polygon 리스트")
|
||||
private List<PolygonFeature> features;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PolygonFeature {
|
||||
|
||||
@Schema(description = "type", example = "Feature")
|
||||
private String type;
|
||||
|
||||
@JsonDeserialize(using = GeometryDeserializer.class)
|
||||
@Schema(
|
||||
description = "polygon geometry",
|
||||
example =
|
||||
"""
|
||||
{
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
126.66292461969202,
|
||||
34.58785236216609
|
||||
],
|
||||
[
|
||||
126.66263801099049,
|
||||
34.58740117447532
|
||||
],
|
||||
[
|
||||
126.66293668521236,
|
||||
34.5873904146878
|
||||
],
|
||||
[
|
||||
126.66312820122245,
|
||||
34.587841464427825
|
||||
],
|
||||
[
|
||||
126.66289124481979,
|
||||
34.58786048381633
|
||||
],
|
||||
[
|
||||
126.66292461969202,
|
||||
34.58785236216609
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
""")
|
||||
private Geometry geometry;
|
||||
|
||||
@Schema(description = "polygon properties")
|
||||
private PolygonProperties properties;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PolygonProperties {
|
||||
|
||||
@Schema(description = "beforeClass", example = "WASTE")
|
||||
private String beforeClass;
|
||||
|
||||
@Schema(description = "afterClass", example = "LAND")
|
||||
private String afterClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "CogImageRequest", description = "COG 이미지 조회 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CogImageRequest {
|
||||
|
||||
@Schema(description = "map_sheet_num (도엽번호)", example = "NI52-3-13-1", required = true)
|
||||
private String mapSheetNum;
|
||||
|
||||
@Schema(description = "year (년도)", example = "2022", required = true)
|
||||
private Integer year;
|
||||
}
|
||||
|
||||
@Schema(name = "CogImageResponse", description = "COG 이미지 URL 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CogImageResponse {
|
||||
|
||||
@Schema(description = "변화 전 COG 이미지 URL")
|
||||
private String beforeCogUrl;
|
||||
|
||||
@Schema(description = "변화 후 COG 이미지 URL")
|
||||
private String afterCogUrl;
|
||||
|
||||
@Schema(description = "변화 전 년도")
|
||||
private Integer beforeYear;
|
||||
|
||||
@Schema(description = "변화 후 년도")
|
||||
private Integer afterYear;
|
||||
|
||||
@Schema(description = "도엽번호")
|
||||
private String mapSheetNum;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.trainingdata.service;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
|
||||
import com.kamco.cd.kamcoback.postgres.core.TrainingDataLabelCoreService;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest;
|
||||
@@ -87,4 +88,30 @@ public class TrainingDataLabelService {
|
||||
public DefaultPaging getDefaultPagingNumber(String userId, Integer size, String assignmentUid) {
|
||||
return trainingDataLabelCoreService.getDefaultPagingNumber(userId, size, assignmentUid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 새로운 polygon(들) 추가 저장
|
||||
*
|
||||
* @param request 새 polygon 정보 (여러 개 가능)
|
||||
* @return 응답 메시지
|
||||
*/
|
||||
@Transactional
|
||||
public ResponseObj saveNewPolygon(TrainingDataLabelDto.NewPolygonRequest request) {
|
||||
trainingDataLabelCoreService.saveNewPolygon(request);
|
||||
int count = request.getFeatures() != null ? request.getFeatures().size() : 0;
|
||||
return new ResponseObj(ApiResponseCode.OK, count + "개의 polygon이 저장되었습니다.");
|
||||
}
|
||||
|
||||
/**
|
||||
* COG 이미지 URL 조회 (변화 전/후)
|
||||
*
|
||||
* @param mapSheetNum 도엽번호
|
||||
* @param beforeYear 변화 전 년도
|
||||
* @param afterYear 변화 후 년도
|
||||
* @return 변화 전/후 COG 이미지 URL
|
||||
*/
|
||||
public TrainingDataLabelDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear) {
|
||||
return trainingDataLabelCoreService.getCogImageUrl(mapSheetNum, beforeYear, afterYear);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.kamco.cd.kamcoback.trainingdata.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
|
||||
import com.kamco.cd.kamcoback.postgres.core.TrainingDataReviewCoreService;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DefaultPaging;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.GeoFeatureRequest;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.searchReq;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TrainingDataReviewService {
|
||||
|
||||
private final TrainingDataReviewCoreService trainingDataReviewCoreService;
|
||||
|
||||
public TrainingDataReviewService(TrainingDataReviewCoreService trainingDataReviewCoreService) {
|
||||
this.trainingDataReviewCoreService = trainingDataReviewCoreService;
|
||||
}
|
||||
|
||||
public Page<ReviewListDto> findReviewAssignedList(searchReq searchReq, String userId) {
|
||||
return trainingDataReviewCoreService.findReviewAssignedList(searchReq, userId);
|
||||
}
|
||||
|
||||
public ReviewGeometryInfo findReviewAssignedGeom(String operatorUid) {
|
||||
return trainingDataReviewCoreService.findReviewAssignedGeom(operatorUid);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ResponseObj saveReviewFeature(GeoFeatureRequest request) {
|
||||
String status = "";
|
||||
String operatorUid = request.getOperatorUid();
|
||||
Long inferenceGeomUid = trainingDataReviewCoreService.findReviewOperatorGeoUid(operatorUid);
|
||||
|
||||
if (request.getGeometry() == null || request.getGeometry().isEmpty()) {
|
||||
// EXCEPT 상태만 업데이트
|
||||
status = "EXCEPT";
|
||||
trainingDataReviewCoreService.updateReviewStateOperator(
|
||||
operatorUid, status, request.getProperties().getInspectMemo());
|
||||
trainingDataReviewCoreService.updateReviewExceptState(inferenceGeomUid, status);
|
||||
} else {
|
||||
status = "COMPLETE";
|
||||
trainingDataReviewCoreService.updateReviewStateOperator(
|
||||
operatorUid, status, request.getProperties().getInspectMemo());
|
||||
trainingDataReviewCoreService.updateReviewPolygonClass(
|
||||
inferenceGeomUid, request.getGeometry(), request.getProperties(), status);
|
||||
}
|
||||
return new ResponseObj(ApiResponseCode.OK, "저장되었습니다.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 검수자별 작업 통계 조회
|
||||
*
|
||||
* @param userId 검수자 사번
|
||||
* @return 전체/미작업/Today 건수
|
||||
*/
|
||||
public SummaryRes getSummary(String userId) {
|
||||
try {
|
||||
System.out.println("[Service] getSummary called with userId: " + userId);
|
||||
SummaryRes result = trainingDataReviewCoreService.getSummary(userId);
|
||||
System.out.println("[Service] getSummary result: " + result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
System.err.println("[Service] getSummary ERROR: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// 예외 발생 시에도 빈 통계 반환
|
||||
return SummaryRes.builder().totalCnt(0L).undoneCnt(0L).todayCnt(0L).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검수 작업 상세 정보 조회
|
||||
*
|
||||
* @param operatorUid 검수 작업 ID
|
||||
* @return 변화탐지정보 + 실태조사결과정보
|
||||
*/
|
||||
public DetailRes getDetail(UUID operatorUid) {
|
||||
return trainingDataReviewCoreService.getDetail(operatorUid);
|
||||
}
|
||||
|
||||
public DefaultPaging getDefaultPagingNumber(String userId, Integer size, String operatorUid) {
|
||||
return trainingDataReviewCoreService.getDefaultPagingNumber(userId, size, operatorUid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 새로운 polygon(들) 추가 저장
|
||||
*
|
||||
* @param request 새 polygon 정보 (여러 개 가능)
|
||||
* @return 응답 메시지
|
||||
*/
|
||||
@Transactional
|
||||
public ResponseObj saveNewPolygon(TrainingDataReviewDto.NewPolygonRequest request) {
|
||||
trainingDataReviewCoreService.saveNewPolygon(request);
|
||||
int count = request.getFeatures() != null ? request.getFeatures().size() : 0;
|
||||
return new ResponseObj(ApiResponseCode.OK, count + "개의 polygon이 저장되었습니다.");
|
||||
}
|
||||
|
||||
/**
|
||||
* COG 이미지 URL 조회 (변화 전/후)
|
||||
*
|
||||
* @param mapSheetNum 도엽번호
|
||||
* @param beforeYear 변화 전 년도
|
||||
* @param afterYear 변화 후 년도
|
||||
* @return 변화 전/후 COG 이미지 URL
|
||||
*/
|
||||
public TrainingDataReviewDto.CogImageResponse getCogImageUrl(
|
||||
String mapSheetNum, Integer beforeYear, Integer afterYear) {
|
||||
return trainingDataReviewCoreService.getCogImageUrl(mapSheetNum, beforeYear, afterYear);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ spring:
|
||||
jpa:
|
||||
show-sql: false
|
||||
hibernate:
|
||||
ddl-auto: validate # 로컬만 완화(시킬려면 update으로 변경)
|
||||
ddl-auto: update # 로컬만 완화(시킬려면 update으로 변경)
|
||||
properties:
|
||||
hibernate:
|
||||
default_batch_fetch_size: 100 # ✅ 성능 - N+1 쿼리 방지
|
||||
|
||||
10540
src/main/resources/db/migration/dump-kamco_cds-202601132249.sql
Normal file
10540
src/main/resources/db/migration/dump-kamco_cds-202601132249.sql
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user