Merge pull request '라벨링 툴 상세정보, 요약정보, 저장' (#187) from feat/infer_dev_260107 into develop
Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/187
This commit is contained in:
@@ -11,6 +11,10 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
|
||||
|
||||
public GeometryDeserializer() {
|
||||
super(Geometry.class);
|
||||
}
|
||||
|
||||
public GeometryDeserializer(Class<T> targetType) {
|
||||
super(targetType);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.repository.trainingdata.TrainingDataLabelRepository;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.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;
|
||||
|
||||
@@ -22,4 +27,52 @@ public class TrainingDataLabelCoreService {
|
||||
public LabelingGeometryInfo findLabelingAssignedGeom(String assignmentUid) {
|
||||
return trainingDataLabelRepository.findLabelingAssignedGeom(assignmentUid);
|
||||
}
|
||||
|
||||
public Long findLabelingAssignmentGeoUid(String assignmentUid) {
|
||||
return trainingDataLabelRepository.findLabelingAssignmentGeoUid(assignmentUid);
|
||||
}
|
||||
|
||||
public void updateLabelingStateAssignment(String assignmentUid, String status) {
|
||||
trainingDataLabelRepository.updateLabelingStateAssignment(assignmentUid, status);
|
||||
}
|
||||
|
||||
public void updateLabelingSkipState(Long inferenceGeomUid, String status) {
|
||||
trainingDataLabelRepository.updateLabelingSkipState(inferenceGeomUid, status);
|
||||
}
|
||||
|
||||
public void updateLabelingPolygonClass(
|
||||
Long inferenceGeomUid, Geometry geometry, Properties properties, String status) {
|
||||
trainingDataLabelRepository.updateLabelingPolygonClass(
|
||||
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 = trainingDataLabelRepository.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 assignmentUid 작업 배정 ID
|
||||
* @return 변화탐지정보 + 실태조사결과정보
|
||||
*/
|
||||
public DetailRes getDetail(UUID assignmentUid) {
|
||||
return trainingDataLabelRepository.getDetail(assignmentUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
|
||||
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.searchReq;
|
||||
import java.util.UUID;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
public interface TrainingDataLabelRepositoryCustom {
|
||||
@@ -10,4 +15,17 @@ public interface TrainingDataLabelRepositoryCustom {
|
||||
Page<LabelingListDto> findLabelingAssignedList(searchReq searchReq, String userId, String status);
|
||||
|
||||
LabelingGeometryInfo findLabelingAssignedGeom(String assignmentUid);
|
||||
|
||||
Long findLabelingAssignmentGeoUid(String assignmentUid);
|
||||
|
||||
void updateLabelingStateAssignment(String assignmentUid, String status);
|
||||
|
||||
void updateLabelingSkipState(Long inferenceGeomUid, String status);
|
||||
|
||||
void updateLabelingPolygonClass(
|
||||
Long inferenceGeomUid, Geometry geometry, Properties properties, String status);
|
||||
|
||||
SummaryRes getSummary(String userId);
|
||||
|
||||
DetailRes getDetail(UUID assignmentUid);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,13 @@ import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.l
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.searchReq;
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
@@ -16,10 +20,13 @@ 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 java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -132,6 +139,356 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long findLabelingAssignmentGeoUid(String assignmentUid) {
|
||||
return queryFactory
|
||||
.select(labelingAssignmentEntity.inferenceGeomUid)
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(assignmentUid)))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLabelingStateAssignment(String assignmentUid, String status) {
|
||||
queryFactory
|
||||
.update(labelingAssignmentEntity)
|
||||
.set(labelingAssignmentEntity.workState, status)
|
||||
.set(labelingAssignmentEntity.workStatDttm, ZonedDateTime.now())
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(assignmentUid)))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLabelingSkipState(Long inferenceGeomUid, String status) {
|
||||
queryFactory
|
||||
.update(mapSheetAnalDataInferenceGeomEntity)
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now())
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.labelState, status)
|
||||
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(inferenceGeomUid))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLabelingPolygonClass(
|
||||
Long mapSheetAnalDataInferenceGeomEntityUid,
|
||||
Geometry geometry,
|
||||
Properties properties,
|
||||
String status) {
|
||||
queryFactory
|
||||
.update(mapSheetAnalDataInferenceGeomEntity)
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.geom, geometry)
|
||||
.set(
|
||||
mapSheetAnalDataInferenceGeomEntity.classBeforeCd,
|
||||
properties.getBeforeClass().toLowerCase())
|
||||
.set(
|
||||
mapSheetAnalDataInferenceGeomEntity.classAfterCd,
|
||||
properties.getAfterClass().toLowerCase())
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now())
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.labelState, status)
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.geomCenter, geometry.getCentroid())
|
||||
.where(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(mapSheetAnalDataInferenceGeomEntityUid))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SummaryRes getSummary(String userId) {
|
||||
// 기본값 설정
|
||||
Long totalCnt = 0L;
|
||||
Long undoneCnt = 0L;
|
||||
Long todayCnt = 0L;
|
||||
|
||||
try {
|
||||
System.out.println("=== getSummary START ===");
|
||||
System.out.println("userId: " + userId);
|
||||
|
||||
// 1. 전체 배정 건수
|
||||
try {
|
||||
Long result =
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(labelingAssignmentEntity.workerUid.eq(userId))
|
||||
.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. 미작업 건수 (ASSIGNED 상태)
|
||||
try {
|
||||
Long result =
|
||||
queryFactory
|
||||
.select(labelingAssignmentEntity.count())
|
||||
.from(labelingAssignmentEntity)
|
||||
.where(
|
||||
labelingAssignmentEntity.workerUid.eq(userId),
|
||||
labelingAssignmentEntity.workState.eq("ASSIGNED"))
|
||||
.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.workerUid.eq(userId),
|
||||
labelingAssignmentEntity.workState.eq("DONE"),
|
||||
labelingAssignmentEntity.modifiedDate.isNotNull(),
|
||||
labelingAssignmentEntity.modifiedDate.goe(startOfToday),
|
||||
labelingAssignmentEntity.modifiedDate.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 ===");
|
||||
System.out.println(
|
||||
"Final result - totalCnt: "
|
||||
+ totalCnt
|
||||
+ ", undoneCnt: "
|
||||
+ undoneCnt
|
||||
+ ", todayCnt: "
|
||||
+ todayCnt);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 최상위 예외 처리
|
||||
System.err.println("=== getSummary OUTER ERROR ===");
|
||||
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 assignmentUid) {
|
||||
try {
|
||||
// 1. 작업 배정 정보 조회
|
||||
var assignment =
|
||||
queryFactory
|
||||
.selectFrom(labelingAssignmentEntity)
|
||||
.where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid))
|
||||
.fetchOne();
|
||||
|
||||
if (assignment == null) {
|
||||
throw new RuntimeException("Assignment not found: " + assignmentUid);
|
||||
}
|
||||
|
||||
// 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 =
|
||||
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ChangeDetectionInfo.builder()
|
||||
.mapSheetInfo(mapSheetEntity != null ? mapSheetEntity.getMapidNm() : "")
|
||||
.detectionYear(
|
||||
(mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy()
|
||||
: 0)
|
||||
+ "-"
|
||||
+ (mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy()
|
||||
: 0))
|
||||
.beforeClass(
|
||||
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ClassificationInfo
|
||||
.builder()
|
||||
.classification(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd()
|
||||
: "")
|
||||
.probability(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb() != null
|
||||
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb()
|
||||
: 0.0)
|
||||
.build())
|
||||
.afterClass(
|
||||
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.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 =
|
||||
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.InspectionResultInfo
|
||||
.builder()
|
||||
.verificationResult(convertInspectState(assignment.toDto().getInspectState()))
|
||||
.inappropriateReason("")
|
||||
// .memo(assignment.toDto().getInspectMemo() != null ?
|
||||
// assignment.toDto().getInspectMemo() : "")
|
||||
.build();
|
||||
|
||||
// 6. Geometry를 GeoJSON으로 변환
|
||||
com.fasterxml.jackson.databind.JsonNode geomJson = null;
|
||||
if (mapSheetAnalDataInferenceGeomEntityEntity.getGeom() != null) {
|
||||
try {
|
||||
String geomString =
|
||||
queryFactory
|
||||
.select(
|
||||
Expressions.stringTemplate(
|
||||
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geom))
|
||||
.from(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(
|
||||
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
|
||||
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
|
||||
.fetchOne();
|
||||
|
||||
if (geomString != null) {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper =
|
||||
new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
geomJson = mapper.readTree(geomString);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("GeoJSON parsing error: " + e.getMessage());
|
||||
// JSON 파싱 실패 시 null 유지
|
||||
}
|
||||
}
|
||||
|
||||
// 도엽 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) {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper =
|
||||
new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
mapBbox = mapper.readTree(bboxString);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("GeoJSON parsing error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return DetailRes.builder()
|
||||
.assignmentUid(assignmentUid)
|
||||
.changeDetectionInfo(changeDetectionInfo)
|
||||
.inspectionResultInfo(inspectionResultInfo)
|
||||
.geom(geomJson)
|
||||
.beforeCogUrl(beforeCogUrl)
|
||||
.afterCogUrl(afterCogUrl)
|
||||
.mapBox(mapBbox)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
System.err.println("getDetail Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to get detail for assignmentUid: " + assignmentUid, e);
|
||||
}
|
||||
}
|
||||
|
||||
private StringExpression makeCogUrl(NumberPath<Integer> year) {
|
||||
return new CaseBuilder()
|
||||
.when(imageryEntity.year.eq(year))
|
||||
@@ -149,4 +506,20 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
|
||||
String[] arrStatus = status.split(",");
|
||||
return labelingAssignmentEntity.workState.in(arrStatus);
|
||||
}
|
||||
|
||||
private String convertInspectState(String inspectState) {
|
||||
if (inspectState == null) {
|
||||
return "미확인";
|
||||
}
|
||||
switch (inspectState) {
|
||||
case "UNCONFIRM":
|
||||
return "미확인";
|
||||
case "EXCEPT":
|
||||
return "제외";
|
||||
case "COMPLETE":
|
||||
return "완료";
|
||||
default:
|
||||
return "미확인";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ 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.TrainingDataLabelDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.service.TrainingDataLabelService;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -15,6 +17,8 @@ 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;
|
||||
@@ -51,6 +55,7 @@ public class TrainingDataLabelApiController {
|
||||
trainingDataLabelService.findLabelingAssignedList(searchReq, userId, status));
|
||||
}
|
||||
|
||||
@Hidden
|
||||
@Operation(summary = "상세 Geometry 조회", description = "라벨 할당 상세 Geometry 조회")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@@ -69,4 +74,87 @@ public class TrainingDataLabelApiController {
|
||||
@RequestParam(defaultValue = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02") String assignmentUid) {
|
||||
return ApiResponseDto.ok(trainingDataLabelService.findLabelingAssignedGeom(assignmentUid));
|
||||
}
|
||||
|
||||
@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)
|
||||
})
|
||||
@PostMapping
|
||||
public ApiResponseDto<ResponseObj> saveLabelingFeature(
|
||||
@RequestBody TrainingDataLabelDto.GeoFeatureRequest request) {
|
||||
return ApiResponseDto.okObject(trainingDataLabelService.saveLabelingFeature(request));
|
||||
}
|
||||
|
||||
@Operation(summary = "작업 통계 조회", description = "라벨러의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "조회 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = TrainingDataLabelDto.SummaryRes.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/summary")
|
||||
public ApiResponseDto<TrainingDataLabelDto.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);
|
||||
TrainingDataLabelDto.SummaryRes result = trainingDataLabelService.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(
|
||||
TrainingDataLabelDto.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 = TrainingDataLabelDto.DetailRes.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
|
||||
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/detail")
|
||||
public ApiResponseDto<TrainingDataLabelDto.DetailRes> getDetail(
|
||||
@io.swagger.v3.oas.annotations.Parameter(
|
||||
description = "작업 배정 ID (UUID)",
|
||||
required = true,
|
||||
example = "93c56be8-0246-4b22-b976-2476549733cc")
|
||||
@RequestParam
|
||||
java.util.UUID assignmentUid) {
|
||||
return ApiResponseDto.ok(trainingDataLabelService.getDetail(assignmentUid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ 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.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
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;
|
||||
@@ -30,15 +34,6 @@ public class TrainingDataLabelDto {
|
||||
private String mapIdNm;
|
||||
private Long pnu;
|
||||
|
||||
// @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 LabelingListDto(
|
||||
UUID assignmentUid,
|
||||
Long inferenceGeomUid,
|
||||
@@ -99,6 +94,71 @@ public class TrainingDataLabelDto {
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "GeoFeatureRequest", description = "polygon 저장")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class GeoFeatureRequest {
|
||||
|
||||
@Schema(description = "assignmentUid", example = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
|
||||
private String assignmentUid;
|
||||
|
||||
@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(name = "searchReq", description = "검색 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -122,4 +182,115 @@ public class TrainingDataLabelDto {
|
||||
return PageRequest.of(page, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "DetailRes", description = "객체 상세 정보 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class DetailRes {
|
||||
|
||||
@Schema(description = "작업 배정 ID")
|
||||
private UUID assignmentUid;
|
||||
|
||||
@Schema(description = "변화탐지정보")
|
||||
private ChangeDetectionInfo changeDetectionInfo;
|
||||
|
||||
@Schema(description = "실태조사결과정보")
|
||||
private InspectionResultInfo inspectionResultInfo;
|
||||
|
||||
@Schema(description = "Geometry (GeoJSON)")
|
||||
private JsonNode geom;
|
||||
|
||||
@Schema(description = "변화 전 COG 이미지 URL")
|
||||
private String beforeCogUrl;
|
||||
|
||||
@Schema(description = "변화 후 COG 이미지 URL")
|
||||
private String afterCogUrl;
|
||||
|
||||
@Schema(description = "도엽 bbox")
|
||||
private JsonNode mapBox;
|
||||
}
|
||||
|
||||
@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 = "미작업 건수 (ASSIGNED 상태)", example = "7211")
|
||||
private Long undoneCnt;
|
||||
|
||||
@Schema(description = "오늘 완료 건수", example = "0")
|
||||
private Long todayCnt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
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.DetailRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
|
||||
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.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 TrainingDataLabelService {
|
||||
|
||||
@@ -24,4 +33,54 @@ public class TrainingDataLabelService {
|
||||
public LabelingGeometryInfo findLabelingAssignedGeom(String assignmentUid) {
|
||||
return trainingDataLabelCoreService.findLabelingAssignedGeom(assignmentUid);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ResponseObj saveLabelingFeature(GeoFeatureRequest request) {
|
||||
String status = "";
|
||||
String assignmentUid = request.getAssignmentUid();
|
||||
Long inferenceGeomUid =
|
||||
trainingDataLabelCoreService.findLabelingAssignmentGeoUid(assignmentUid);
|
||||
if (request.getGeometry() == null || request.getGeometry().isEmpty()) {
|
||||
// SKIP 상태만 업데이트
|
||||
status = "SKIP";
|
||||
trainingDataLabelCoreService.updateLabelingStateAssignment(assignmentUid, status);
|
||||
trainingDataLabelCoreService.updateLabelingSkipState(inferenceGeomUid, status);
|
||||
} else {
|
||||
status = "DONE";
|
||||
trainingDataLabelCoreService.updateLabelingStateAssignment(assignmentUid, status);
|
||||
trainingDataLabelCoreService.updateLabelingPolygonClass(
|
||||
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 = trainingDataLabelCoreService.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 assignmentUid 작업 배정 ID
|
||||
* @return 변화탐지정보 + 실태조사결과정보
|
||||
*/
|
||||
public DetailRes getDetail(UUID assignmentUid) {
|
||||
return trainingDataLabelCoreService.getDetail(assignmentUid);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user