Files
test/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataLabelApiController.java
2026-01-15 15:12:30 +09:00

502 lines
22 KiB
Java

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.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/label")
public class TrainingDataLabelApiController {
private final TrainingDataLabelService trainingDataLabelService;
@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<LabelingListDto>> findLabelingAssignedList(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "01022223333") String userId) {
TrainingDataLabelDto.searchReq searchReq = new TrainingDataLabelDto.searchReq(page, size, "");
return ApiResponseDto.ok(trainingDataLabelService.findLabelingAssignedList(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<LabelingGeometryInfo> findLabelingAssignedGeom(
@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.LabelFeatureRequest 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 = "선택한 작업의 변화탐지정보 및 실태조사결과를 조회합니다. 저장된 여러 개의 polygon을 조회할 수 있습니다.")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TrainingDataLabelDto.DetailRes.class),
examples = {
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "단일 polygon 조회",
description = "1개의 polygon이 저장된 경우 응답 예시",
value =
"""
{
"code": "OK",
"message": null,
"data": {
"assignmentUid": "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02",
"changeDetectionInfo": {
"mapSheetInfo": "NI52-3-13-1",
"detectionYear": "2023-2024",
"beforeClass": {
"classification": "waste",
"probability": 0.95
},
"afterClass": {
"classification": "land",
"probability": 0.98
},
"area": 1250.5,
"detectionAccuracy": 0.96,
"pnu": 1234567890
},
"inspectionResultInfo": {
"verificationResult": "완료",
"inappropriateReason": ""
},
"geom": {
"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"
}
},
"beforeCogUrl": "https://storage.example.com/cog/2023/NI52-3-13-1.tif",
"afterCogUrl": "https://storage.example.com/cog/2024/NI52-3-13-1.tif",
"mapBox": {
"type": "Polygon",
"coordinates": [[[126.65, 34.58], [126.67, 34.58], [126.67, 34.60], [126.65, 34.60], [126.65, 34.58]]]
},
"learnGeometries": [
{
"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 = "여러 polygon 조회",
description = "3개의 polygon이 저장된 경우 응답 예시",
value =
"""
{
"code": "OK",
"message": null,
"data": {
"assignmentUid": "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02",
"changeDetectionInfo": {
"mapSheetInfo": "NI52-3-13-1",
"detectionYear": "2023-2024",
"beforeClass": {
"classification": "waste",
"probability": 0.95
},
"afterClass": {
"classification": "land",
"probability": 0.98
},
"area": 1250.5,
"detectionAccuracy": 0.96,
"pnu": 1234567890
},
"inspectionResultInfo": {
"verificationResult": "완료",
"inappropriateReason": ""
},
"geom": {
"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"
}
},
"beforeCogUrl": "https://storage.example.com/cog/2023/NI52-3-13-1.tif",
"afterCogUrl": "https://storage.example.com/cog/2024/NI52-3-13-1.tif",
"mapBox": {
"type": "Polygon",
"coordinates": [[[126.65, 34.58], [126.67, 34.58], [126.67, 34.60], [126.65, 34.60], [126.65, 34.58]]]
},
"learnGeometries": [
{
"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.585], [126.664, 34.584], [126.666, 34.586], [126.665, 34.585]]]
},
"properties": {
"beforeClass": "forest",
"afterClass": "building"
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[126.660, 34.590], [126.659, 34.589], [126.661, 34.591], [126.660, 34.590]]]
},
"properties": {
"beforeClass": "grassland",
"afterClass": "concrete"
}
}
]
}
}
""")
})),
@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 = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
@RequestParam(defaultValue = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
java.util.UUID assignmentUid) {
return ApiResponseDto.ok(trainingDataLabelService.getDetail(assignmentUid));
}
@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("/default-page")
public ApiResponseDto<TrainingDataLabelDto.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 assignmentUid) {
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을 조회합니다. beforeYear와 afterYear 중 최소 하나는 필수입니다.")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(implementation = TrainingDataLabelDto.CogImageResponse.class))),
@ApiResponse(
responseCode = "400",
description = "년도 파라미터가 하나도 제공되지 않음",
content = @Content),
@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 = false, example = "2023")
@RequestParam(required = false)
Integer beforeYear,
@Parameter(description = "변화 후 년도", required = false, example = "2024")
@RequestParam(required = false)
Integer afterYear) {
return ApiResponseDto.ok(
trainingDataLabelService.getCogImageUrl(mapSheetNum, beforeYear, afterYear));
}
}