변화탐지 API 커밋 cog,count,yearlist

This commit is contained in:
2025-11-27 18:01:15 +09:00
parent 9d32c85fd0
commit 675d2f2ed1
11 changed files with 343 additions and 16 deletions

View File

@@ -5,13 +5,14 @@ import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto;
import com.kamco.cd.kamcoback.changedetection.service.ChangeDetectionService;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
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.tags.Tag;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@Tag(name = "변화탐지", description = "변화탐지 API")
@@ -34,8 +35,32 @@ public class ChangeDetectionApiController {
* PolygonData -> JsonNode 변환 예제
* @return
*/
@Hidden
@GetMapping("/json-data")
public ApiResponseDto<List<JsonNode>> getPolygonToJson(){
return ApiResponseDto.ok(changeDetectionService.getPolygonToJson());
}
@Operation(summary = "변화탐지 분류별 건수", description = "변화탐지 분류별 건수")
@GetMapping("/class-count/{id}")
public ApiResponseDto<List<ChangeDetectionDto.CountDto>> getChangeDetectionClassCount(
@Parameter(description = "변화탐지 년도(차수) /year-list 의 analUid", example = "1") @PathVariable Long id){
return ApiResponseDto.ok(changeDetectionService.getChangeDetectionClassCount(id));
}
@Operation(summary = "변화탐지 COG Url", description = "변화탐지 COG Url")
@GetMapping("/cog-url")
public ApiResponseDto<ChangeDetectionDto.CogUrlDto> getChangeDetectionCogUrl(
@Parameter(description = "이전 년도", example = "2023") @RequestParam Integer beforeYear,
@Parameter(description = "이후 년도", example = "2024") @RequestParam Integer afterYear,
@Parameter(description = "도엽번호(5k)", example = "36809010") @RequestParam String mapSheetNum){
ChangeDetectionDto.CogUrlReq req = new ChangeDetectionDto.CogUrlReq(beforeYear, afterYear, mapSheetNum);
return ApiResponseDto.ok(changeDetectionService.getChangeDetectionCogUrl(req));
}
@Operation(summary = "변화탐지 년도(차수) 목록", description = "변화탐지 년도(차수) 목록")
@GetMapping("/year-list")
public ApiResponseDto<List<ChangeDetectionDto.AnalYearList>> getChangeDetectionYearList(){
return ApiResponseDto.ok(changeDetectionService.getChangeDetectionYearList());
}
}

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.changedetection.dto;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -21,6 +22,52 @@ public class ChangeDetectionDto{
private Double centroidY;
}
@Schema(name = "CountDto", description = "분류별 탐지 건수")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class CountDto {
private String classCd; //영문코드
private String className; //한글명
private Long count; //건수
}
@Schema(name = "CogUrlReq", description = "COG Url Req")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class CogUrlReq {
private Integer beforeYear;
private Integer afterYear;
private String mapSheetNum;
}
@Schema(name = "CogUrlDto", description = "COG Url 정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class CogUrlDto {
private String beforeCogUrl;
private String afterCogUrl;
private JsonNode bbox;
}
@Schema(name = "AnalYearList", description = "년도(차수) 목록")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class AnalYearList {
private Long analUid;
private String analTitle;
private Integer beforeYear;
private Integer afterYear;
private String baseMapSheetNum;
}
@Schema(name = "PolygonGeometry", description = "폴리곤 리턴 객체")
@Getter
@Setter
@@ -47,16 +94,6 @@ public class ChangeDetectionDto{
private Double center_longitude; //폴리곤 중심 경도
}
@Schema(name = "CogURL", description = "COG URL")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class CogURL{
private String before_cog_url; //기준 COG URL
private String after_cog_url; //비교 COG URL
}
@Schema(name = "PolygonProperties", description = "폴리곤 정보")
@Getter
@Setter

View File

@@ -20,4 +20,16 @@ public class ChangeDetectionService {
public List<JsonNode> getPolygonToJson(){
return changeDetectionCoreService.getPolygonToJson();
}
public List<ChangeDetectionDto.CountDto> getChangeDetectionClassCount(Long id) {
return changeDetectionCoreService.getChangeDetectionClassCount(id);
}
public ChangeDetectionDto.CogUrlDto getChangeDetectionCogUrl(ChangeDetectionDto.CogUrlReq req) {
return changeDetectionCoreService.getChangeDetectionCogUrl(req);
}
public List<ChangeDetectionDto.AnalYearList> getChangeDetectionYearList() {
return changeDetectionCoreService.getChangeDetectionYearList();
}
}

View File

@@ -49,4 +49,25 @@ public class ChangeDetectionCoreService {
})
.collect(Collectors.toList());
}
public List<ChangeDetectionDto.CountDto> getChangeDetectionClassCount(Long id) {
return changeDetectionRepository.getChangeDetectionClassCount(id);
}
public ChangeDetectionDto.CogUrlDto getChangeDetectionCogUrl(ChangeDetectionDto.CogUrlReq req) {
ObjectMapper mapper = new ObjectMapper();
ChangeDetectionDto.CogUrlDto resultDto = changeDetectionRepository.getChangeDetectionCogUrl(req);
try {
JsonNode geomNode = mapper.readTree(resultDto.getBbox().toString());
resultDto.setBbox(geomNode);
} catch (Exception e) {
throw new RuntimeException("Failed to parse geom JSON", e);
}
return changeDetectionRepository.getChangeDetectionCogUrl(req);
}
public List<ChangeDetectionDto.AnalYearList> getChangeDetectionYearList() {
return changeDetectionRepository.getChangeDetectionYearList();
}
}

View File

@@ -0,0 +1,55 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import java.time.ZonedDateTime;
import java.util.UUID;
@Getter
@Setter
@Table(name = "imagery")
@Entity
public class ImageryEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "imagery_id_seq_gen")
@SequenceGenerator(name = "imagery_id_seq_gen",sequenceName = "imagery_id_seq",allocationSize = 1)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "uuid", columnDefinition = "uuid default gen_random_uuid()")
private UUID uuid;
@Column(name = "year")
private Integer year;
@Column(name = "scene_50k")
private String scene50k;
@Column(name = "scene_5k")
private String scene5k;
@Column(name = "scene_id_50k")
private Integer sceneId50k;
@Column(name = "scene_id_5k")
private Integer sceneId5k;
@ColumnDefault("now()")
@Column(name = "created_date", columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT now()")
private ZonedDateTime createdDate;
@Column(name = "middle_path")
private String middlePath;
@Column(name = "cog_middle_path")
private String cogMiddlePath;
@Column(name = "filename")
private String filename;
@Column(name = "cog_filename")
private String cogFilename;
}

View File

@@ -0,0 +1,33 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.locationtech.jts.geom.Geometry;
@Getter
@Setter
@Table(name = "tb_map_inkx_50k")
@Entity
public class MapInkx50kEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_50k_fid_seq_gen")
@SequenceGenerator(
name = "tb_map_inkx_50k_fid_seq_gen",
sequenceName = "tb_map_inkx_50k_fid_seq",
allocationSize = 1)
private Integer fid;
@Column(name = "mapidcd_no")
private String mapidcdNo;
@Column(name = "mapid_nm")
private String mapidNm;
@Column(name = "mapid_no")
private String mapidNo;
@Column(name = "geom")
private Geometry geom;
}

View File

@@ -0,0 +1,32 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.locationtech.jts.geom.Geometry;
@Getter
@Setter
@Table(name = "tb_map_inkx_5k")
@Entity
public class MapInkx5kEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_5k_fid_seq_gen")
@SequenceGenerator(
name = "tb_map_inkx_5k_fid_seq_gen",
sequenceName = "tb_map_inkx_5k_fid_seq",
allocationSize = 1)
private Integer fid;
@Column(name = "mapidcd_no")
private String mapidcdNo;
@Column(name = "mapid_nm")
private String mapidNm;
@Column(name = "geom")
private Geometry geom;
@Column(name = "fid_k50")
private Long fidK50;
}

View File

@@ -91,4 +91,7 @@ public class MapSheetAnalEntity {
@Column(name = "detecting_cnt")
private Long detectingCnt;
@Column(name = "base_map_sheet_num")
private String baseMapSheetNum;
}

View File

@@ -47,6 +47,12 @@ public class MapSheetAnalSttcEntity {
@Column(name = "data_uid", nullable = false)
private Long dataUid;
@Column(name = "class_before_cd")
private String classBeforeCd;
@Column(name = "class_after_cd")
private String classAfterCd;
public InferenceResultDto.Dashboard toDto() {
return new InferenceResultDto.Dashboard(
id.getCompareYyyy(),

View File

@@ -1,5 +1,8 @@
package com.kamco.cd.kamcoback.postgres.repository.changedetection;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto;
import com.querydsl.core.Tuple;
import java.util.List;
public interface ChangeDetectionRepositoryCustom {
@@ -7,4 +10,10 @@ public interface ChangeDetectionRepositoryCustom {
String getPolygonToPoint();
List<String> findPolygonJson();
List<ChangeDetectionDto.CountDto> getChangeDetectionClassCount(Long id);
ChangeDetectionDto.CogUrlDto getChangeDetectionCogUrl(ChangeDetectionDto.CogUrlReq req);
List<ChangeDetectionDto.AnalYearList> getChangeDetectionYearList();
}

View File

@@ -1,13 +1,28 @@
package com.kamco.cd.kamcoback.postgres.repository.changedetection;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import java.util.List;
import java.util.Objects;
import static com.kamco.cd.kamcoback.postgres.entity.QImageryEntity.imageryEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataEntity.mapSheetAnalDataEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity;
public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
implements ChangeDetectionRepositoryCustom {
@@ -34,4 +49,83 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
.orderBy(mapSheetAnalDataGeomEntity.id.desc())
.fetch();
}
@Override
public List<ChangeDetectionDto.CountDto> getChangeDetectionClassCount(Long id) {
return queryFactory
.select(Projections.constructor(
ChangeDetectionDto.CountDto.class,
mapSheetAnalSttcEntity.classAfterCd.toUpperCase(),
mapSheetAnalSttcEntity.id.classAfterName,
mapSheetAnalSttcEntity.classAfterCnt.sum()
))
.from(mapSheetAnalEntity)
.innerJoin(mapSheetAnalDataEntity)
.on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id))
.innerJoin(mapSheetAnalSttcEntity)
.on(mapSheetAnalSttcEntity.dataUid.eq(mapSheetAnalDataEntity.id))
.where(mapSheetAnalEntity.id.eq(id))
.groupBy(mapSheetAnalSttcEntity.classAfterCd, mapSheetAnalSttcEntity.id.classAfterName)
.fetch();
}
@Override
public ChangeDetectionDto.CogUrlDto getChangeDetectionCogUrl(ChangeDetectionDto.CogUrlReq req) {
Tuple result = queryFactory
.select(
makeCogUrl(req.getBeforeYear()).max().as("beforeCogUrl"),
makeCogUrl(req.getAfterYear()).max().as("afterCogUrl"),
Expressions.stringTemplate("ST_AsGeoJSON({0})", mapInkx5kEntity.geom).as("bbox")
)
.from(imageryEntity)
.innerJoin(mapInkx5kEntity)
.on(imageryEntity.scene5k.eq(mapInkx5kEntity.mapidcdNo))
.where(
imageryEntity.year.eq(req.getBeforeYear()).or(imageryEntity.year.eq(req.getAfterYear())),
imageryEntity.scene5k.eq(req.getMapSheetNum())
)
.groupBy(mapInkx5kEntity.geom)
.fetchOne();
//Polygon -> JsonNode 로 변환
JsonNode geometryJson = changeGeometryJson(String.valueOf(Objects.requireNonNull(result).get(2, StringExpression.class)));
return new ChangeDetectionDto.CogUrlDto(result.get(0, String.class), result.get(1, String.class), geometryJson);
}
@Override
public List<ChangeDetectionDto.AnalYearList> getChangeDetectionYearList() {
return queryFactory
.select(
Projections.constructor(
ChangeDetectionDto.AnalYearList.class,
mapSheetAnalEntity.id,
mapSheetAnalEntity.analTitle,
mapSheetAnalEntity.compareYyyy.as("beforeYear"),
mapSheetAnalEntity.targetYyyy.as("afterYear"),
mapSheetAnalEntity.baseMapSheetNum
)
)
.from(mapSheetAnalEntity)
.orderBy(mapSheetAnalEntity.id.asc())
.fetch()
;
}
private StringExpression makeCogUrl(Integer year) {
return new CaseBuilder()
.when(imageryEntity.year.eq(year))
.then(Expressions.stringTemplate("{0} || {1}",imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
.otherwise("")
;
}
private JsonNode changeGeometryJson(String geometry){
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readTree(geometry);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}