feat/infer_dev_260107 #1

Merged
teddy merged 43 commits from feat/infer_dev_260107 into develop 2026-01-29 10:31:31 +09:00
55 changed files with 2948 additions and 241 deletions

View File

@@ -12,7 +12,8 @@ public enum FileUploadStatus implements EnumType {
INIT("초기화"),
UPLOADING("업로드중"),
DONE("업로드완료"),
MERGED("병합완료");
MERGED("병합완료"),
MERGE_FAIL("병합 실패");
private final String desc;

View File

@@ -0,0 +1,37 @@
package com.kamco.cd.kamcoback.common.enums;
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Getter;
@CodeExpose
@Getter
@AllArgsConstructor
public enum LayerType implements EnumType {
TILE("배경지도"),
GEOJSON("객체데이터"),
WMTS("타일레이어"),
WMS("지적도");
private final String desc;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return desc;
}
public static Optional<LayerType> from(String type) {
try {
return Optional.of(LayerType.valueOf(type));
} catch (Exception e) {
return Optional.empty();
}
}
}

View File

@@ -4,10 +4,12 @@ import com.kamco.cd.kamcoback.auth.CustomUserDetails;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.exception.DuplicateFileException;
import com.kamco.cd.kamcoback.common.exception.ValidationException;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.log.dto.ErrorLogDto;
import com.kamco.cd.kamcoback.log.dto.EventType;
import com.kamco.cd.kamcoback.members.exception.MemberException;
import com.kamco.cd.kamcoback.postgres.entity.ErrorLogEntity;
import com.kamco.cd.kamcoback.postgres.repository.log.ErrorLogRepository;
@@ -466,10 +468,14 @@ public class GlobalExceptionHandler {
.collect(Collectors.joining("\n"))
.substring(0, 255);
String actionType = HeaderUtil.get(request, "kamco-action-type");
ErrorLogEntity errorLogEntity =
new ErrorLogEntity(
request.getRequestURI(),
ApiLogFunction.getEventType(request),
actionType == null
? ApiLogFunction.getEventType(request)
: EventType.fromName(actionType),
logErrorLevel,
String.valueOf(httpStatus.value()),
errorCode.getText(),

View File

@@ -309,6 +309,15 @@ public class InferenceResultDto {
@Schema(description = "모델3 분석 대기")
private Integer m3PendingJobs;
@Schema(description = "모델1 분석 진행중")
private Integer m1RunningJobs;
@Schema(description = "모델2 분석 진행중")
private Integer m2RunningJobs;
@Schema(description = "모델3 분석 진행중")
private Integer m3RunningJobs;
@Schema(description = "모델1 분석 완료")
private Integer m1CompletedJobs;
@@ -386,11 +395,18 @@ public class InferenceResultDto {
@Schema(description = "모델3 버전")
private String modelVer3;
@Schema(description = "탑지 도엽 수")
@JsonIgnore
private Long totalJobs;
public InferenceStatusDetailDto(
Long detectingCnt,
Integer m1PendingJobs,
Integer m2PendingJobs,
Integer m3PendingJobs,
Integer m1RunningJobs,
Integer m2RunningJobs,
Integer m3RunningJobs,
Integer m1CompletedJobs,
Integer m2CompletedJobs,
Integer m3CompletedJobs,
@@ -413,11 +429,15 @@ public class InferenceResultDto {
String mapSheetScope,
String modelVer1,
String modelVer2,
String modelVer3) {
String modelVer3,
Long totalJobs) {
this.detectingCnt = detectingCnt;
this.m1PendingJobs = m1PendingJobs;
this.m2PendingJobs = m2PendingJobs;
this.m3PendingJobs = m3PendingJobs;
this.m1RunningJobs = m1RunningJobs;
this.m2RunningJobs = m2RunningJobs;
this.m3RunningJobs = m3RunningJobs;
this.m1CompletedJobs = m1CompletedJobs;
this.m2CompletedJobs = m2CompletedJobs;
this.m3CompletedJobs = m3CompletedJobs;
@@ -441,12 +461,13 @@ public class InferenceResultDto {
this.modelVer1 = modelVer1;
this.modelVer2 = modelVer2;
this.modelVer3 = modelVer3;
this.totalJobs = totalJobs;
}
@Schema(description = "진행률")
@JsonProperty("progress")
private int getProgress() {
long tiles = this.detectingCnt; // 도엽수
long tiles = this.totalJobs == null ? 0L : this.totalJobs; // 도엽수
int models = 3; // 모델 개수
int completed =
this.m1CompletedJobs

View File

@@ -0,0 +1,6 @@
package com.kamco.cd.kamcoback.inference.service;
import org.springframework.stereotype.Service;
@Service
public class InferenceStatusService {}

View File

@@ -205,6 +205,9 @@ public class LabelWorkDto {
@Schema(description = "1일전처리개수")
private Long day1AgoDoneCnt;
@Schema(description = "계정 상태")
private String memberStatus;
public Long getRemainCnt() {
return this.assignedCnt - this.doneCnt;
}

View File

@@ -0,0 +1,190 @@
package com.kamco.cd.kamcoback.layer;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.SearchReq;
import com.kamco.cd.kamcoback.layer.service.LayerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
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/layer")
public class LayerApiController {
private final LayerService layerService;
@Operation(summary = "지도 레이어 관리 목록", description = "지도 레이어 관리 목록 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = LayerDto.Basic.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/list")
public ApiResponseDto<List<LayerDto.Basic>> getLayers(
@RequestParam(required = false) String tag) {
LayerDto.SearchReq searchReq = new SearchReq();
searchReq.setTag(tag);
List<LayerDto.Basic> layers = layerService.getLayers(searchReq);
return ApiResponseDto.ok(layers);
}
/**
* 레이어 등록
*
* @param layerType
* @param dto
* @return
*/
@Operation(summary = "레이어 등록", description = "레이어 등록 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = UUID.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/save/{layerType}")
public ApiResponseDto<UUID> save(
@PathVariable String layerType, @RequestBody LayerDto.AddReq dto) {
return ApiResponseDto.ok(layerService.saveLayers(layerType, dto));
}
@PutMapping("/order")
public ApiResponseDto<Void> updateOrder(@RequestBody List<OrderReq> dto) {
layerService.orderUpdate(dto);
return ApiResponseDto.ok(null);
}
@Operation(summary = "상세 조회", description = "상세 조회 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = LayerDto.Detail.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/detail/{uuid}")
public ApiResponseDto<LayerDto.Detail> getDetail(@PathVariable UUID uuid) {
return ApiResponseDto.ok(layerService.getDetail(uuid));
}
/**
* 레이어 삭제
*
* @param uuid
* @return
*/
@Operation(summary = "레이어 삭제", description = "레이어 삭제 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "삭제 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@DeleteMapping("/delete/{uuid}")
public ApiResponseDto<Void> delete(@PathVariable UUID uuid) {
layerService.delete(uuid);
return ApiResponseDto.ok(null);
}
/**
* 레이어 수정
*
* @param uuid
* @return
*/
@Operation(summary = "레이어 수정", description = "레이어 수정 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "수정 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/update/{uuid}")
public ApiResponseDto<Void> update(@PathVariable UUID uuid, @RequestBody LayerDto.Detail dto) {
layerService.update(uuid, dto);
return ApiResponseDto.ok(null);
}
@Operation(summary = "wmts tile 조회", description = "wmts tile 조회 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = String.class)))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/wmts/tile")
public ApiResponseDto<List<String>> getWmtsTile() {
return ApiResponseDto.ok(layerService.getWmtsTile());
}
@Operation(summary = "wms tile 조회", description = "wms tile 조회 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = String.class)))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/wms/tile")
public ApiResponseDto<List<String>> getWmsTile() {
return ApiResponseDto.ok(layerService.getWmsTitle());
}
}

View File

@@ -0,0 +1,18 @@
package com.kamco.cd.kamcoback.layer.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class GeoJsonDto {
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class GeoJsonAddReqDto {
private String url;
private String description;
private String tag;
}
}

View File

@@ -0,0 +1,192 @@
package com.kamco.cd.kamcoback.layer.dto;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class LayerDto {
@Getter
@Setter
@AllArgsConstructor
@Schema(name = "LayerBasic")
public static class Basic {
@Schema(description = "uuid")
private UUID uuid;
@Schema(example = "WMTS", description = "유형 (TILE/GEOJSON/WMTS/WMS)")
private String layerType;
@Schema(description = "설명")
private String description;
@Schema(description = "태그")
private String tag;
@Schema(description = "순서")
private Long order;
@Schema(description = "변화지도 여부")
private Boolean isChangeMap;
@Schema(description = "라벨링지도 여부")
private Boolean isLabelingMap;
@JsonFormatDttm
@Schema(description = "등록일시")
private ZonedDateTime createdDttm;
}
@Getter
@Setter
@AllArgsConstructor
@Schema(name = "LayerDetail")
public static class Detail {
@Schema(description = "uuid")
private UUID uuid;
@Schema(description = "유형 (TILE/GEOJSON/WMTS/WMS)")
private String layerType;
@Schema(description = "title")
private String title;
@Schema(description = "설명")
private String description;
@Schema(description = "태그")
private String tag;
@Schema(description = "순서")
private Long order;
@Schema(description = "변화지도 여부")
private Boolean isChangeMap;
@Schema(description = "라벨링지도 여부")
private Boolean isLabelingMap;
@Schema(description = "url")
private String url;
@Schema(description = "좌측상단 경도", example = "126.0")
private BigDecimal minLon;
@Schema(description = "좌측상단 위도", example = "34.0")
private BigDecimal minLat;
@Schema(description = "우측하단 경도", example = "130.0")
private BigDecimal maxLon;
@Schema(description = "우측하단 위도", example = "38.5")
private BigDecimal maxLat;
@Schema(description = "zoom min", example = "5")
private Short min;
@Schema(description = "zoom max", example = "18")
private Short max;
@JsonFormatDttm
@Schema(description = "등록일시")
private ZonedDateTime createdDttm;
}
@Getter
@Setter
@AllArgsConstructor
@Schema(name = "LayerAddReq")
public static class AddReq {
@Schema(description = "title")
private String title;
@Schema(description = "설명")
private String description;
@Schema(description = "태그")
private String tag;
@Schema(description = "url")
private String url;
@Schema(description = "좌측상단 경도", example = "126.0")
private BigDecimal minLon;
@Schema(description = "좌측상단 위도", example = "34.0")
private BigDecimal minLat;
@Schema(description = "우측하단 경도", example = "130.0")
private BigDecimal maxLon;
@Schema(description = "우측하단 위도", example = "38.5")
private BigDecimal maxLat;
@Schema(description = "zoom min", example = "5")
private Short min;
@Schema(description = "zoom max", example = "18")
private Short max;
}
@Getter
@Setter
@AllArgsConstructor
@Schema(name = "LayerOrderReq")
public static class OrderReq {
@Schema(description = "uuid")
private UUID uuid;
@Schema(description = "레이어 순서")
private Long order;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class SearchReq {
private String tag;
private String layerType;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class TileAddReqDto {
@Schema(description = "설명", example = "배경지도 입니다.")
private String description;
@Schema(description = "url", example = "http://test.gs.dabeeo.com/tile/{z}/{x}/{y}.png")
private String url;
@Schema(description = "태그", example = "변화지도")
private String tag;
@Schema(description = "좌측상단 경도", example = "126.0")
private BigDecimal minLon;
@Schema(description = "좌측상단 위도", example = "34.0")
private BigDecimal minLat;
@Schema(description = "우측하단 경도", example = "130.0")
private BigDecimal maxLon;
@Schema(description = "우측하단 위도", example = "38.5")
private BigDecimal maxLat;
@Schema(description = "zoom min", example = "5")
private Short min;
@Schema(description = "zoom max", example = "18")
private Short max;
}
}

View File

@@ -0,0 +1,30 @@
package com.kamco.cd.kamcoback.layer.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class WmsDto {
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class WmsAddReqDto {
private String title;
private String description;
private String tag;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class WmsAddDto {
private WmsLayerInfo wmsLayerInfo;
private String title;
private String description;
private String tag;
}
}

View File

@@ -0,0 +1,172 @@
package com.kamco.cd.kamcoback.layer.dto;
import java.util.ArrayList;
import java.util.List;
/** WMS 레이어 정보를 담는 DTO 클래스 */
public class WmsLayerInfo {
private String name;
private String title;
private String abstractText;
private List<String> keywords;
private BoundingBox boundingBox;
private List<String> crs; // 지원하는 좌표계 목록
@Override
public String toString() {
return "WmsLayerInfo{"
+ "name='"
+ name
+ '\''
+ ", title='"
+ title
+ '\''
+ ", abstractText='"
+ abstractText
+ '\''
+ ", keywords="
+ keywords
+ ", boundingBox="
+ boundingBox
+ ", crs="
+ crs
+ '}';
}
public WmsLayerInfo() {
this.keywords = new ArrayList<>();
this.crs = new ArrayList<>();
}
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAbstractText() {
return abstractText;
}
public void setAbstractText(String abstractText) {
this.abstractText = abstractText;
}
public List<String> getKeywords() {
return keywords;
}
public void setKeywords(List<String> keywords) {
this.keywords = keywords;
}
public void addKeyword(String keyword) {
this.keywords.add(keyword);
}
public BoundingBox getBoundingBox() {
return boundingBox;
}
public void setBoundingBox(BoundingBox boundingBox) {
this.boundingBox = boundingBox;
}
public List<String> getCrs() {
return crs;
}
public void setCrs(List<String> crs) {
this.crs = crs;
}
public void addCrs(String crsValue) {
this.crs.add(crsValue);
}
/** BoundingBox 정보를 담는 내부 클래스 */
public static class BoundingBox {
private String crs;
private double minX;
private double minY;
private double maxX;
private double maxY;
public BoundingBox(String crs, double minX, double minY, double maxX, double maxY) {
this.crs = crs;
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
// Getters and Setters
public String getCrs() {
return crs;
}
public void setCrs(String crs) {
this.crs = crs;
}
public double getMinX() {
return minX;
}
public void setMinX(double minX) {
this.minX = minX;
}
public double getMinY() {
return minY;
}
public void setMinY(double minY) {
this.minY = minY;
}
public double getMaxX() {
return maxX;
}
public void setMaxX(double maxX) {
this.maxX = maxX;
}
public double getMaxY() {
return maxY;
}
public void setMaxY(double maxY) {
this.maxY = maxY;
}
@Override
public String toString() {
return "BoundingBox{"
+ "crs='"
+ crs
+ '\''
+ ", minX="
+ minX
+ ", minY="
+ minY
+ ", maxX="
+ maxX
+ ", maxY="
+ maxY
+ '}';
}
}
}

View File

@@ -0,0 +1,30 @@
package com.kamco.cd.kamcoback.layer.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class WmtsDto {
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class WmtsAddReqDto {
private String title;
private String description;
private String tag;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class WmtsAddDto {
private WmtsLayerInfo wmtsLayerInfo;
private String title;
private String description;
private String tag;
}
}

View File

@@ -0,0 +1,276 @@
package com.kamco.cd.kamcoback.layer.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
/** WMTS 레이어 정보를 담는 DTO 클래스 */
public class WmtsLayerInfo {
public String identifier;
public String title;
public String abstractText;
public List<String> keywords = new ArrayList<>();
public BoundingBox boundingBox;
public List<String> formats = new ArrayList<>();
public List<String> tileMatrixSetLinks = new ArrayList<>();
public List<ResourceUrl> resourceUrls = new ArrayList<>();
public List<Style> styles = new ArrayList<>();
public void setTitle(String title) {
this.title = title;
}
public void setAbstractText(String abstractText) {
this.abstractText = abstractText;
}
public void setBoundingBox(BoundingBox boundingBox) {
this.boundingBox = boundingBox;
}
@Override
public String toString() {
return "WmtsLayerInfo{"
+ "identifier='"
+ identifier
+ '\''
+ ", title='"
+ title
+ '\''
+ ", abstractText='"
+ abstractText
+ '\''
+ ", keywords="
+ keywords
+ ", boundingBox="
+ boundingBox
+ ", formats="
+ formats
+ ", tileMatrixSetLinks="
+ tileMatrixSetLinks
+ ", resourceUrls="
+ resourceUrls
+ ", styles="
+ styles
+ '}';
}
public void addKeyword(String keywowrd) {
this.keywords.add(keywowrd);
}
public void addFormat(String format) {
this.formats.add(format);
}
public void addTileMatrixSetLink(String tileMatrixSetLink) {
this.tileMatrixSetLinks.add(tileMatrixSetLink);
}
public void addResourceUrl(ResourceUrl resourceUrl) {
this.resourceUrls.add(resourceUrl);
}
public void addStyle(Style style) {
this.styles.add(style);
}
/** BoundingBox 정보를 담는 내부 클래스 */
public static class BoundingBox {
public String crs;
public double lowerCornerX;
public double lowerCornerY;
public double upperCornerX;
public double upperCornerY;
public BoundingBox() {}
public BoundingBox(
String crs,
double lowerCornerX,
double lowerCornerY,
double upperCornerX,
double upperCornerY) {
this.crs = crs;
this.lowerCornerX = lowerCornerX;
this.lowerCornerY = lowerCornerY;
this.upperCornerX = upperCornerX;
this.upperCornerY = upperCornerY;
}
// Getters and Setters
public String getCrs() {
return crs;
}
public void setCrs(String crs) {
this.crs = crs;
}
public double getLowerCornerX() {
return lowerCornerX;
}
public void setLowerCornerX(double lowerCornerX) {
this.lowerCornerX = lowerCornerX;
}
public double getLowerCornerY() {
return lowerCornerY;
}
public void setLowerCornerY(double lowerCornerY) {
this.lowerCornerY = lowerCornerY;
}
public double getUpperCornerX() {
return upperCornerX;
}
public void setUpperCornerX(double upperCornerX) {
this.upperCornerX = upperCornerX;
}
public double getUpperCornerY() {
return upperCornerY;
}
public void setUpperCornerY(double upperCornerY) {
this.upperCornerY = upperCornerY;
}
@Override
public String toString() {
return "BoundingBox{"
+ "crs='"
+ crs
+ '\''
+ ", lowerCorner=["
+ lowerCornerX
+ ", "
+ lowerCornerY
+ ']'
+ ", upperCorner=["
+ upperCornerX
+ ", "
+ upperCornerY
+ ']'
+ '}';
}
}
/** ResourceURL 정보를 담는 내부 클래스 (타일 URL 템플릿) */
public static class ResourceUrl {
private String format;
private String resourceType;
private String template;
public ResourceUrl() {}
public ResourceUrl(String format, String resourceType, String template) {
this.format = format;
this.resourceType = resourceType;
this.template = template;
}
// Getters and Setters
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
@Override
public String toString() {
return "ResourceUrl{"
+ "format='"
+ format
+ '\''
+ ", resourceType='"
+ resourceType
+ '\''
+ ", template='"
+ template
+ '\''
+ '}';
}
}
/** Style 정보를 담는 내부 클래스 */
public static class Style {
private String identifier;
private String title;
@JsonProperty("default")
private boolean isDefault;
public Style() {}
public Style(String identifier, String title, boolean isDefault) {
this.identifier = identifier;
this.title = title;
this.isDefault = isDefault;
}
// Getters and Setters
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isDefault() {
return isDefault;
}
public void setDefault(boolean isDefault) {
this.isDefault = isDefault;
}
@Override
public String toString() {
return "Style{"
+ "identifier='"
+ identifier
+ '\''
+ ", title='"
+ title
+ '\''
+ ", isDefault="
+ isDefault
+ '}';
}
}
}

View File

@@ -0,0 +1,3 @@
package com.kamco.cd.kamcoback.layer.service;
public class GeojsonService {}

View File

@@ -0,0 +1,162 @@
package com.kamco.cd.kamcoback.layer.service;
import com.kamco.cd.kamcoback.common.enums.LayerType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.Basic;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq;
import com.kamco.cd.kamcoback.layer.dto.WmsDto.WmsAddDto;
import com.kamco.cd.kamcoback.layer.dto.WmsDto.WmsAddReqDto;
import com.kamco.cd.kamcoback.layer.dto.WmsLayerInfo;
import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddDto;
import com.kamco.cd.kamcoback.layer.dto.WmtsLayerInfo;
import com.kamco.cd.kamcoback.postgres.core.MapLayerCoreService;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class LayerService {
private final MapLayerCoreService mapLayerCoreService;
private final WmtsService wmtsService;
private final WmsService wmsService;
/**
* 지도 레이어 관리 목록
*
* @return
*/
public List<Basic> getLayers(LayerDto.SearchReq searchReq) {
return mapLayerCoreService.getLayers(searchReq);
}
/**
* 레이어 저장
*
* @param type
* @param dto
* @return
*/
@Transactional
public UUID saveLayers(String type, LayerDto.AddReq dto) {
LayerType layerType =
LayerType.from(type)
.orElseThrow(() -> new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST));
switch (layerType) {
case TILE -> {
return mapLayerCoreService.saveTile(dto);
}
case GEOJSON -> {
mapLayerCoreService.saveGeoJson(dto);
}
case WMTS -> {
WmtsLayerInfo info = wmtsService.getDetail(dto.getTitle());
WmtsAddDto addDto = new WmtsAddDto();
addDto.setWmtsLayerInfo(info);
addDto.setDescription(dto.getDescription());
addDto.setTitle(dto.getTitle());
addDto.setTag(dto.getTag());
return mapLayerCoreService.saveWmts(addDto);
}
case WMS -> {
WmsLayerInfo info = wmsService.getDetail(dto.getTitle());
WmsAddDto addDto = new WmsAddDto();
addDto.setWmsLayerInfo(info);
addDto.setDescription(dto.getDescription());
addDto.setTitle(dto.getTitle());
addDto.setTag(dto.getTag());
return mapLayerCoreService.saveWms(addDto);
}
default -> throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
return null;
}
/**
* 순서 수정
*
* @param dtoList
*/
@Transactional
public void orderUpdate(List<OrderReq> dtoList) {
mapLayerCoreService.orderUpdate(dtoList);
}
/**
* 지도 레이어 관리 상세
*
* @param uuid
* @return
*/
public LayerDto.Detail getDetail(UUID uuid) {
return mapLayerCoreService.getLayers(uuid);
}
/**
* 삭제
*
* @param uuid
*/
@Transactional
public void delete(UUID uuid) {
mapLayerCoreService.delete(uuid);
}
/**
* 수정
*
* @param uuid
*/
@Transactional
public void update(UUID uuid, LayerDto.Detail dto) {
mapLayerCoreService.update(uuid, dto);
}
/**
* wmts tile 조회
*
* @return List<String>
*/
public List<String> getWmtsTile() {
return wmtsService.getTile();
}
/**
* wms title 조회
*
* @return
*/
public List<String> getWmsTitle() {
return wmsService.getTile();
}
/**
* wms 저장
*
* @param dto
* @return
*/
@Transactional
public UUID saveWms(WmsAddReqDto dto) {
// 선택한 tile 상세정보 조회
WmsLayerInfo info = wmsService.getDetail(dto.getTitle());
WmsAddDto addDto = new WmsAddDto();
addDto.setWmsLayerInfo(info);
addDto.setDescription(dto.getDescription());
addDto.setTitle(dto.getTitle());
addDto.setTag(dto.getTag());
return mapLayerCoreService.saveWms(addDto);
}
}

View File

@@ -0,0 +1,254 @@
package com.kamco.cd.kamcoback.layer.service;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.layer.dto.WmsLayerInfo;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class WmsService {
@Value("${layer.geoserver-url}")
private String geoserverUrl;
@Value("${layer.workspace}")
private String workspace;
public List<String> getTile() {
List<WmsLayerInfo> layers;
try {
layers = getAllLayers(geoserverUrl, workspace);
} catch (IOException | ParserConfigurationException | SAXException e) {
throw new CustomApiException("INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR);
}
List<String> titles = new ArrayList<>();
for (WmsLayerInfo layer : layers) {
if (StringUtils.hasText(layer.getTitle())) {
titles.add(layer.getTitle());
}
}
return titles;
}
public WmsLayerInfo getDetail(String title) {
try {
// 특정 title로 레이어 찾기
WmsLayerInfo layerInfo = getLayerByTitle(geoserverUrl, workspace, title);
if (layerInfo != null) {
return layerInfo;
} else {
throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND);
}
} catch (Exception e) {
throw new CustomApiException("INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
public List<WmsLayerInfo> getAllLayers(String baseUrl, String workspace)
throws IOException, ParserConfigurationException, SAXException {
String capabilitiesUrl = buildGetCapabilitiesUrl(baseUrl, workspace);
Document doc = fetchAndParseXml(capabilitiesUrl);
List<WmsLayerInfo> layers = new ArrayList<>();
NodeList layerNodes = doc.getElementsByTagName("Layer");
for (int i = 0; i < layerNodes.getLength(); i++) {
Element layerElement = (Element) layerNodes.item(i);
// Name이 있는 레이어만 추가 (실제 레이어, 그룹 레이어 제외)
String name = getElementTextContent(layerElement, "Name");
if (name != null && !name.isEmpty()) {
layers.add(extractLayerInfo(layerElement));
}
}
return layers;
}
/** GetCapabilities URL 생성 */
private String buildGetCapabilitiesUrl(String baseUrl, String workspace) {
// URL 끝의 슬래시 처리
String cleanBaseUrl =
baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
return String.format(
"%s/geoserver/%s/wms?service=WMS&request=GetCapabilities", cleanBaseUrl, workspace);
}
/** URL에서 XML을 가져와 Document 객체로 파싱 */
private Document fetchAndParseXml(String urlString)
throws IOException, ParserConfigurationException, SAXException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000); // 10초 타임아웃
conn.setReadTimeout(10000);
try (InputStream inputStream = conn.getInputStream()) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(inputStream);
} finally {
conn.disconnect();
}
}
/** Element에서 특정 태그명의 텍스트 내용 추출 */
private String getElementTextContent(Element parent, String tagName) {
NodeList nodeList = parent.getElementsByTagName(tagName);
if (nodeList.getLength() > 0) {
return nodeList.item(0).getTextContent();
}
return null;
}
/** Layer Element에서 WmsLayerInfo 객체 생성 */
private WmsLayerInfo extractLayerInfo(Element layerElement) {
WmsLayerInfo layerInfo = new WmsLayerInfo();
// Name
layerInfo.setName(getElementTextContent(layerElement, "Name"));
// Title
layerInfo.setTitle(getElementTextContent(layerElement, "Title"));
// Abstract
layerInfo.setAbstractText(getElementTextContent(layerElement, "Abstract"));
// Keywords
NodeList keywordNodes = layerElement.getElementsByTagName("Keyword");
for (int i = 0; i < keywordNodes.getLength(); i++) {
String keyword = keywordNodes.item(i).getTextContent();
if (keyword != null && !keyword.trim().isEmpty()) {
layerInfo.addKeyword(keyword.trim());
}
}
// CRS (좌표계)
NodeList crsNodes = layerElement.getElementsByTagName("CRS");
if (crsNodes.getLength() == 0) {
// WMS 1.1.1의 경우 SRS 사용
crsNodes = layerElement.getElementsByTagName("SRS");
}
for (int i = 0; i < crsNodes.getLength(); i++) {
String crs = crsNodes.item(i).getTextContent();
if (crs != null && !crs.trim().isEmpty()) {
layerInfo.addCrs(crs.trim());
}
}
// BoundingBox
NodeList bboxNodes = layerElement.getElementsByTagName("BoundingBox");
if (bboxNodes.getLength() > 0) {
Element bboxElement = (Element) bboxNodes.item(0);
String crs = bboxElement.getAttribute("CRS");
if (crs == null || crs.isEmpty()) {
crs = bboxElement.getAttribute("SRS"); // WMS 1.1.1 호환
}
try {
double minX = Double.parseDouble(bboxElement.getAttribute("minx"));
double minY = Double.parseDouble(bboxElement.getAttribute("miny"));
double maxX = Double.parseDouble(bboxElement.getAttribute("maxx"));
double maxY = Double.parseDouble(bboxElement.getAttribute("maxy"));
WmsLayerInfo.BoundingBox bbox = new WmsLayerInfo.BoundingBox(crs, minX, minY, maxX, maxY);
layerInfo.setBoundingBox(bbox);
} catch (NumberFormatException e) {
System.err.println("BoundingBox 파싱 오류: " + e.getMessage());
}
}
// EX_GeographicBoundingBox도 확인 (전역 범위)
if (layerInfo.getBoundingBox() == null) {
NodeList geoBboxNodes = layerElement.getElementsByTagName("EX_GeographicBoundingBox");
if (geoBboxNodes.getLength() > 0) {
Element geoBboxElement = (Element) geoBboxNodes.item(0);
try {
double westBound =
Double.parseDouble(getElementTextContent(geoBboxElement, "westBoundLongitude"));
double eastBound =
Double.parseDouble(getElementTextContent(geoBboxElement, "eastBoundLongitude"));
double southBound =
Double.parseDouble(getElementTextContent(geoBboxElement, "southBoundLatitude"));
double northBound =
Double.parseDouble(getElementTextContent(geoBboxElement, "northBoundLatitude"));
WmsLayerInfo.BoundingBox bbox =
new WmsLayerInfo.BoundingBox(
"EPSG:4326", westBound, southBound, eastBound, northBound);
layerInfo.setBoundingBox(bbox);
} catch (NumberFormatException e) {
System.err.println("GeographicBoundingBox 파싱 오류: " + e.getMessage());
}
}
}
return layerInfo;
}
/**
* GetCapabilities를 호출하고 title로 레이어 정보를 찾아 반환
*
* @param baseUrl GeoServer 기본 URL (예: http://localhost:8080)
* @param workspace 워크스페이스 이름
* @param targetTitle 찾고자 하는 레이어의 title
* @return WmsLayerInfo 객체, 찾지 못하면 null
* @throws Exception 네트워크 또는 파싱 오류 시
*/
public WmsLayerInfo getLayerByTitle(String baseUrl, String workspace, String targetTitle)
throws Exception {
// GetCapabilities URL 구성
String capabilitiesUrl = buildGetCapabilitiesUrl(baseUrl, workspace);
// GetCapabilities 요청 및 XML 파싱
Document doc = fetchAndParseXml(capabilitiesUrl);
// title로 레이어 찾기
return findLayerByTitle(doc, targetTitle);
}
/** XML Document에서 title로 레이어를 찾아 WmsLayerInfo 객체로 변환 */
private WmsLayerInfo findLayerByTitle(Document doc, String targetTitle) {
// Layer 요소들 찾기
NodeList layerNodes = doc.getElementsByTagName("Layer");
for (int i = 0; i < layerNodes.getLength(); i++) {
Element layerElement = (Element) layerNodes.item(i);
// Title 찾기
String title = getElementTextContent(layerElement, "Title");
// Title이 일치하면 레이어 정보 추출
if (title != null && title.equals(targetTitle)) {
return extractLayerInfo(layerElement);
}
}
return null; // 찾지 못한 경우
}
}

View File

@@ -0,0 +1,282 @@
package com.kamco.cd.kamcoback.layer.service;
import com.kamco.cd.kamcoback.layer.dto.WmtsLayerInfo;
import com.kamco.cd.kamcoback.postgres.core.MapLayerCoreService;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@Service
@Log4j2
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class WmtsService {
private final MapLayerCoreService mapLayerCoreService;
@Value("${layer.geoserver-url}")
private String geoserverUrl;
@Value("${layer.workspace}")
private String workspace;
private static final String WMTS_GEOSERVER_URL = "/geoserver/";
private static final String WMTS_CAPABILITIES_URL = "/gwc/service/wmts?REQUEST=GetCapabilities";
public List<String> getTile() {
List<WmtsLayerInfo> layers = getAllLayers(geoserverUrl, workspace);
List<String> titles = new ArrayList<>();
for (WmtsLayerInfo layer : layers) {
titles.add(layer.title);
}
return titles;
}
public WmtsLayerInfo getDetail(String tile) {
return getLayerInfoByTitle(geoserverUrl, workspace, tile);
}
private List<WmtsLayerInfo> getAllLayers(String geoserverUrl, String workspace) {
List<WmtsLayerInfo> layers = new ArrayList<>();
try {
// 1. XML 문서 로드 및 파싱
String capabilitiesUrl =
geoserverUrl + WMTS_GEOSERVER_URL + workspace + WMTS_CAPABILITIES_URL;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new URL(capabilitiesUrl).openStream());
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
// 2. 모든 Layer 노드 검색
String expression = "//*[local-name()='Layer']";
NodeList layerNodes =
(NodeList) xpath.compile(expression).evaluate(doc, XPathConstants.NODESET);
// 3. 모든 레이어를 파싱하여 리스트에 추가
for (int i = 0; i < layerNodes.getLength(); i++) {
Node layerNode = layerNodes.item(i);
String title = getChildValue(layerNode, "Title");
if (title != null && !title.trim().isEmpty()) {
WmtsLayerInfo layerInfo = parseLayerNode(layerNode, title);
layers.add(layerInfo);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("WMTS 정보 조회 중 오류 발생: " + e.getMessage());
}
return layers;
}
// 특정 노드 아래의 자식 태그 값 추출 (예: <Title>값)
private String getChildValue(Node parent, String childName) {
Node child = findChildNode(parent, childName);
return (child != null) ? child.getTextContent() : null;
}
// 이름으로 자식 노드 찾기 (Local Name 기준)
private Node findChildNode(Node parent, String localName) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
// 네임스페이스 접두사(ows:, wmts:)를 무시하고 태그 이름 확인
if (node.getNodeName().endsWith(":" + localName) || node.getNodeName().equals(localName)) {
return node;
}
}
return null;
}
// 레이어 노드를 Java 객체로 변환
private WmtsLayerInfo parseLayerNode(Node layerNode, String title) {
WmtsLayerInfo info = new WmtsLayerInfo();
info.title = title;
info.identifier = getChildValue(layerNode, "Identifier");
info.abstractText = getChildValue(layerNode, "Abstract");
// Keywords 파싱
// 구조: <ows:Keywords><ows:Keyword>...</ows:Keyword></ows:Keywords>
info.keywords = getChildValues(layerNode, "Keywords", "Keyword");
// BoundingBox 파싱 (WGS84BoundingBox 기준)
info.boundingBox = parseBoundingBox(layerNode);
// Formats 파싱
info.formats = getChildValuesDirect(layerNode, "Format");
// TileMatrixSetLink 파싱
// 구조: <TileMatrixSetLink><TileMatrixSet>...</TileMatrixSet></TileMatrixSetLink>
info.tileMatrixSetLinks = getChildValues(layerNode, "TileMatrixSetLink", "TileMatrixSet");
// ResourceURL 파싱
info.resourceUrls = parseResourceUrls(layerNode);
// Styles 파싱
info.styles = parseStyles(layerNode);
return info;
}
// 특정 노드 아래의 반복되는 자식 구조 값 추출 (예: Keywords -> Keyword)
private List<String> getChildValues(Node parent, String wrapperName, String childName) {
List<String> results = new ArrayList<>();
Node wrapper = findChildNode(parent, wrapperName);
if (wrapper != null) {
NodeList children = wrapper.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith(childName)) {
results.add(node.getTextContent());
}
}
}
return results;
}
private WmtsLayerInfo.BoundingBox parseBoundingBox(Node layerNode) {
// 보통 <ows:WGS84BoundingBox>를 찾음
Node bboxNode = findChildNode(layerNode, "WGS84BoundingBox");
if (bboxNode == null) bboxNode = findChildNode(layerNode, "BoundingBox");
if (bboxNode != null) {
WmtsLayerInfo.BoundingBox bbox = new WmtsLayerInfo.BoundingBox();
bbox.crs = getAttributeValue(bboxNode, "crs"); // WGS84는 보통 CRS 속성이 없을 수 있음(Default EPSG:4326)
String lowerCorner = getChildValue(bboxNode, "LowerCorner");
String upperCorner = getChildValue(bboxNode, "UpperCorner");
if (lowerCorner != null) {
String[] coords = lowerCorner.split(" ");
bbox.lowerCornerX = Double.parseDouble(coords[0]);
bbox.lowerCornerY = Double.parseDouble(coords[1]);
}
if (upperCorner != null) {
String[] coords = upperCorner.split(" ");
bbox.upperCornerX = Double.parseDouble(coords[0]);
bbox.upperCornerY = Double.parseDouble(coords[1]);
}
return bbox;
}
return null;
}
private String getAttributeValue(Node node, String attrName) {
if (node.hasAttributes()) {
Node attr = node.getAttributes().getNamedItem(attrName);
if (attr != null) return attr.getNodeValue();
}
return null;
}
// Wrapper 없이 바로 반복되는 값 추출 (예: Format)
private List<String> getChildValuesDirect(Node parent, String childName) {
List<String> results = new ArrayList<>();
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith(childName)) {
results.add(node.getTextContent());
}
}
return results;
}
private List<WmtsLayerInfo.ResourceUrl> parseResourceUrls(Node layerNode) {
List<WmtsLayerInfo.ResourceUrl> list = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().contains("ResourceURL")) { // local-name check simplification
WmtsLayerInfo.ResourceUrl url = new WmtsLayerInfo.ResourceUrl();
url.setFormat(getAttributeValue(node, "format"));
url.setResourceType(getAttributeValue(node, "resourceType"));
url.setTemplate(getAttributeValue(node, "template"));
list.add(url);
}
}
return list;
}
private List<WmtsLayerInfo.Style> parseStyles(Node layerNode) {
List<WmtsLayerInfo.Style> styles = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith("Style")) {
WmtsLayerInfo.Style style = new WmtsLayerInfo.Style();
style.setDefault(Boolean.parseBoolean(getAttributeValue(node, "isDefault")));
style.setIdentifier(getChildValue(node, "Identifier"));
style.setTitle(getChildValue(node, "Title"));
styles.add(style);
}
}
return styles;
}
/**
* WMTS Capabilities URL에서 특정 타이틀의 레이어 정보를 가져옵니다. // * @param capabilitiesUrl 예:
* http://localhost:8080/geoserver/gwc/service/wmts?REQUEST=GetCapabilities
*
* @param geoserverUrl 예: http://localhost:8080
* @param targetTitle 찾고자 하는 레이어의 Title (예: "My Maps")
*/
public WmtsLayerInfo getLayerInfoByTitle(
String geoserverUrl, String workspace, String targetTitle) {
try {
// 1. XML 문서 로드 및 파싱
String capabilitiesUrl =
geoserverUrl + WMTS_GEOSERVER_URL + workspace + WMTS_CAPABILITIES_URL;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // 네임스페이스 인식
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new URL(capabilitiesUrl).openStream());
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
// 2. 모든 Layer 노드 검색 (네임스페이스 무시하고 local-name으로 검색)
// GeoServer WMTS에서 Layer는 <Contents> -> <Layer> 구조임
String expression = "//*[local-name()='Layer']";
NodeList layerNodes =
(NodeList) xpath.compile(expression).evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < layerNodes.getLength(); i++) {
Node layerNode = layerNodes.item(i);
// 3. Title 확인
String title = getChildValue(layerNode, "Title");
// 타이틀이 일치하면 객체 매핑 시작
if (title != null && title.trim().equals(targetTitle)) {
return parseLayerNode(layerNode, title);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("WMTS 정보 조회 중 오류 발생: " + e.getMessage());
}
return null; // 찾지 못한 경우
}
}

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.log.dto;
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
@@ -13,6 +14,30 @@ import org.springframework.data.domain.Sort;
public class ErrorLogDto {
@CodeExpose
@Getter
public enum LogErrorLevel implements EnumType {
WARNING("Warning"),
ERROR("Error"),
CRITICAL("Critical");
private final String desc;
LogErrorLevel(String desc) {
this.desc = desc;
}
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return desc;
}
}
@Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보")
@Getter
@Setter
@@ -76,26 +101,4 @@ public class ErrorLogDto {
return PageRequest.of(page, size);
}
}
public enum LogErrorLevel implements EnumType {
WARNING("Warning"),
ERROR("Error"),
CRITICAL("Critical");
private final String desc;
LogErrorLevel(String desc) {
this.desc = desc;
}
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return desc;
}
}
}

View File

@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
@@ -204,12 +205,12 @@ public class MapSheetMngApiController {
@PostMapping(value = "/upload-pair", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponseDto<MapSheetMngDto.DmlReturn> uploadPair(
@RequestPart("tfw") MultipartFile tfwFile,
@RequestPart("tif") MultipartFile tifFile,
@RequestParam(value = "hstUid", required = false) Long hstUid,
@RequestParam(value = "overwrite", required = false) boolean overwrite) {
@RequestParam(value = "tifFileName") String tifFile,
@RequestParam(value = "tifFileSize") Long tifFileSize,
@RequestParam(value = "hstUid", required = false) Long hstUid) {
return ApiResponseDto.createOK(
mapSheetMngService.uploadPair(tfwFile, tifFile, hstUid, overwrite));
mapSheetMngService.uploadPair(tfwFile, tifFile, hstUid, tifFileSize));
}
@Operation(summary = "영상관리 > 파일조회", description = "영상관리 > 파일조회")
@@ -322,6 +323,7 @@ public class MapSheetMngApiController {
@PostMapping(value = "/file-chunk-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponseDto<ModelUploadResDto> fileChunkUpload(
@RequestParam("hstUid") Long hstUid,
@RequestParam("uuid") UUID uuid,
@RequestParam("fileName") String fileName,
@RequestParam("fileSize") long fileSize,
@RequestParam("chunkIndex") Integer chunkIndex,
@@ -338,6 +340,7 @@ public class MapSheetMngApiController {
upAddReqDto.setChunkIndex(chunkIndex);
upAddReqDto.setChunkTotalIndex(chunkTotalIndex);
upAddReqDto.setUploadDivi(uploadDivi);
upAddReqDto.setUuid(uuid);
upAddReqDto.setFinalPath(syncRootDir);
upAddReqDto.setTempPath(syncRootTmpDir);

View File

@@ -258,6 +258,7 @@ public class MapSheetMngDto {
private String mapSheetPath;
private UUID uuid;
private String uploadId;
// private List<MngFIleDto> fileArray;
@@ -278,7 +279,8 @@ public class MapSheetMngDto {
String errorCheckTfwFileName,
String errorCheckTifFileName,
String mapSheetPath,
UUID uuid) {
UUID uuid,
String uploadId) {
this.hstUid = hstUid;
this.mngYyyy = mngYyyy;
this.mapSheetNum = mapSheetNum;
@@ -298,6 +300,7 @@ public class MapSheetMngDto {
this.errorCheckTifFileName = errorCheckTifFileName;
this.mapSheetPath = mapSheetPath;
this.uuid = uuid;
this.uploadId = uploadId;
}
private String getSyncStateName(String enumId) {

View File

@@ -30,12 +30,14 @@ import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@@ -107,7 +109,7 @@ public class MapSheetMngService {
@Transactional
public DmlReturn uploadPair(
MultipartFile tfwFile, MultipartFile tifFile, Long hstUid, Boolean overwrite) {
MultipartFile tfwFile, String tifFile, Long hstUid, Long tifFileSize) {
String rootPath = syncRootDir;
String tmpPath = syncTmpDir;
@@ -134,7 +136,7 @@ public class MapSheetMngService {
MngDto mngDto = mapSheetMngCoreService.findMapSheetMng(errDto.getMngYyyy());
String targetYearDir = mngDto.getMngPath();
// 중복체크 -> 도엽/uuid 경로에 업로드 할 거라 overwrite 되지 않음
// 중복체크 -> 도엽50k/uuid 경로에 업로드 할 거라 overwrite 되지 않음
// if (!overwrite) {
// dmlReturn =
// this.duplicateFile(
@@ -146,7 +148,7 @@ public class MapSheetMngService {
// 멀티파트 파일 tmp폴더 저장(파일형식 체크를 위해)
String tfwTmpPath = tmpPath + tfwFile.getOriginalFilename();
String tifTmpPath = tmpPath + tifFile.getOriginalFilename();
// String tifTmpPath = tmpPath + tifFile;
if (!FIleChecker.multipartSaveTo(tfwFile, tfwTmpPath)) {
return new DmlReturn("fail", "UPLOAD ERROR");
@@ -166,7 +168,7 @@ public class MapSheetMngService {
List<MngFilesDto> mngFiles = mapSheetMngCoreService.findByHstUidMapSheetFileList(hstUid);
String uploadPath = "";
for (MngFilesDto dto : mngFiles) {
uploadPath = dto.getFilePath() + "/" + errDto.getUuid();
uploadPath = dto.getFilePath();
break;
}
@@ -180,12 +182,26 @@ public class MapSheetMngService {
MngFilesDto filesDto =
mapSheetMngCoreService.findYyyyToMapSheetFilePathRefer(errDto.getMngYyyy());
String referPath = filesDto.getFilePath();
uploadPath =
Paths.get(referPath).getParent().toString()
+ "/"
+ errDto.getRefMapSheetNum()
+ "/"
+ errDto.getUuid();
Path path = Paths.get(referPath);
boolean isFiveDigitNumber =
path.getFileName() != null && path.getFileName().toString().matches("\\d{5}");
log.info("isFiveDigitNumber : " + isFiveDigitNumber);
if (isFiveDigitNumber) {
uploadPath =
Paths.get(referPath).getParent().toString()
+ "/"
+ errDto.getRefMapSheetNum()
+ "/"
+ errDto.getUuid();
} else {
uploadPath =
Paths.get(referPath).getParent().getParent().toString()
+ "/"
+ errDto.getRefMapSheetNum()
+ "/"
+ errDto.getUuid();
}
}
// 업로드 경로 확인(없으면 생성)
@@ -194,8 +210,10 @@ public class MapSheetMngService {
}
tfwTargetPath = Paths.get(uploadPath).resolve(tfwFile.getOriginalFilename());
tifTargetPath = Paths.get(uploadPath).resolve(tifFile.getOriginalFilename());
tifTargetPath = Paths.get(uploadPath).resolve(tifFile);
log.info("tfwTargetPath : " + tfwTargetPath.toString());
log.info("tifTargetPath : " + tifTargetPath.toString());
if (!Files.exists(tifTargetPath)) {
return new DmlReturn("fail", "TIF 파일이 정상적으로 업로드 되지 않았습니다. 확인해주세요.");
}
@@ -213,7 +231,7 @@ public class MapSheetMngService {
updReqSyncCheckState.setHstUid(hstUid);
updReqSyncCheckState.setFilePath(uploadPath);
updReqSyncCheckState.setSyncCheckTfwFileName(tfwFile.getOriginalFilename());
updReqSyncCheckState.setSyncCheckTifFileName(tifFile.getOriginalFilename());
updReqSyncCheckState.setSyncCheckTifFileName(tifFile);
updReqSyncCheckState.setSyncCheckState("DONE");
mapSheetMngCoreService.updateMapSheetMngHstSyncCheckState(updReqSyncCheckState);
// 파일정보 업데이트
@@ -233,11 +251,14 @@ public class MapSheetMngService {
mapSheetMngCoreService.mngFileSave(addReq);
addReq.setFileName(tifFile.getOriginalFilename());
addReq.setFileName(tifFile);
addReq.setFileExt("tif");
addReq.setFileSize(tifFile.getSize());
addReq.setFileSize(tifFileSize);
mapSheetMngCoreService.mngFileSave(addReq);
// 사용할 수 있는 이전 년도 도엽 테이블 저장
mapSheetMngCoreService.saveSheetMngYear();
return new DmlReturn("success", "파일 업로드 완료되었습니다.");
}
@@ -279,14 +300,12 @@ public class MapSheetMngService {
return new DmlReturn("success", fileUids.size() + "개 파일이 사용설정되었습니다.");
}
public DmlReturn validationFile(MultipartFile tfwFile, MultipartFile tifFile) {
public DmlReturn validationFile(MultipartFile tfwFile, String tifFile) {
if (!FIleChecker.validationMultipart(tfwFile)) {
return new DmlReturn("fail", "TFW SIZE 오류");
} else if (!FIleChecker.validationMultipart(tifFile)) {
return new DmlReturn("fail", "TFW SIZE 오류");
} else if (!FIleChecker.checkExtensions(tfwFile.getOriginalFilename(), "tfw")) {
return new DmlReturn("fail", "TFW FILENAME ERROR");
} else if (!FIleChecker.checkExtensions(tifFile.getOriginalFilename(), "tif")) {
} else if (!FIleChecker.checkExtensions(tifFile, "tif")) {
return new DmlReturn("fail", "TIF FILENAME ERROR");
}
@@ -390,10 +409,21 @@ public class MapSheetMngService {
MngFilesDto filesDto =
mapSheetMngCoreService.findYyyyToMapSheetFilePathRefer(errDto.getMngYyyy());
String referPath = filesDto.getFilePath();
uploadPath = Paths.get(referPath).getParent().toString() + "/" + errDto.getRefMapSheetNum();
Path path = Paths.get(referPath);
boolean isFiveDigitNumber =
path.getFileName() != null && path.getFileName().toString().matches("\\d{5}");
log.info("isFiveDigitNumber : " + isFiveDigitNumber);
if (isFiveDigitNumber) {
uploadPath = Paths.get(referPath).getParent().toString() + "/" + errDto.getRefMapSheetNum();
} else {
uploadPath =
Paths.get(referPath).getParent().getParent().toString()
+ "/"
+ errDto.getRefMapSheetNum();
}
}
upAddReqDto.setUuid(errDto.getUuid());
upAddReqDto.setFinalPath(uploadPath + "/");
upAddReqDto.setTempPath(upAddReqDto.getTempPath() + "/");
@@ -402,7 +432,7 @@ public class MapSheetMngService {
ModelUploadResDto modelUploadResDto = new ModelUploadResDto();
// 병합 다 했는데 gdalinfo 가 fail 이면 삭제?
if (upRes.getRes().equals("MERGED")) {
if (upAddReqDto.getStatus().equals("MERGED")) {
if (!FIleChecker.cmmndGdalInfo(upRes.getFilePath() + "/" + upRes.getFileName())) {
Path filePath = Paths.get(upRes.getFilePath());
Files.deleteIfExists(filePath);
@@ -410,6 +440,10 @@ public class MapSheetMngService {
modelUploadResDto.setResMsg("TIF TYPE ERROR");
return modelUploadResDto;
}
// upload_id, uuid 를 update
mapSheetMngCoreService.updateMapSheetMngHstUploadId(
hstUid, upAddReqDto.getUuid(), upRes.getUploadId());
}
modelUploadResDto.setRes(upRes.getRes());

View File

@@ -228,9 +228,11 @@ public class ModelMngDto {
private String clsModelFileName;
private int chunkIndex;
private int chunkTotalIndex;
@JsonIgnore private String jsonPath;
@JsonIgnore private String jsonFileName;
public double getUploadRate() {
if (this.chunkTotalIndex == 0) {
if (chunkIndex < 0 || chunkTotalIndex < 0) {
return 0.0;
}
return (double) (this.chunkIndex + 1) / (this.chunkTotalIndex + 1) * 100.0;

View File

@@ -15,16 +15,19 @@ import com.kamco.cd.kamcoback.upload.service.UploadService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
@Log4j2
@RequiredArgsConstructor
public class ModelMngService {
@@ -53,6 +56,12 @@ public class ModelMngService {
@Value("${file.model-tmp-dir}")
private String modelTmpDir;
@Value("${file.pt-path}")
private String ptPath;
@Value("${file.pt-FileName}")
private String ptFileName;
public Page<ModelMngDto.ModelList> findModelMgmtList(
ModelMngDto.searchReq searchReq,
LocalDate startDate,
@@ -71,11 +80,18 @@ public class ModelMngService {
}
public ApiResponseDto.ResponseObj insertModel(ModelMngDto.AddReq addReq) {
UUID uuid = UUID.randomUUID();
addReq.setUuid(uuid);
addReq.setClsModelVersion(addReq.getModelVer());
addReq.setPriority(0d);
log.info(
"insertModel start: uuid={}, fileName={}, filePath={}",
uuid,
addReq.getFileName(),
addReq.getFilePath());
ModelUploadResDto modelUploadResDto = new ModelUploadResDto();
try {
FIleChecker.unzip(addReq.getFileName(), addReq.getFilePath());
@@ -86,10 +102,21 @@ public class ModelMngService {
|| modelUploadResDto.getCdModelConfigFileName() == null
|| modelUploadResDto.getCdModelConfigFileName().isEmpty()
|| modelUploadResDto.getClsModelFileName() == null
|| modelUploadResDto.getClsModelFileName().isEmpty()) {
|| modelUploadResDto.getClsModelFileName().isEmpty()
|| modelUploadResDto.getJsonFileName() == null
|| modelUploadResDto.getJsonFileName().isEmpty()) {
log.warn(
"model file missing: uuid={}, path={}, cdModel={}, cdConfig={}, clsModel={}, jsonFileName={}",
uuid,
addReq.getFilePath(),
modelUploadResDto.getCdModelFileName(),
modelUploadResDto.getCdModelConfigFileName(),
modelUploadResDto.getClsModelFileName(),
modelUploadResDto.getJsonFileName());
return new ApiResponseDto.ResponseObj(
ApiResponseCode.NOT_FOUND, "pth,json,py파일이 모두 존재하지 않습니다");
ApiResponseCode.NOT_FOUND, "pth,pt,py,json파일이 모두 존재하지 않습니다");
}
addReq.setCdModelPath(modelUploadResDto.getCdModelPath());
@@ -101,12 +128,20 @@ public class ModelMngService {
} catch (IOException e) {
// throw new RuntimeException(e);
log.error(
"unzip or scan failed: uuid={}, fileName={}, filePath={}",
uuid,
addReq.getFileName(),
addReq.getFilePath(),
e);
return new ApiResponseDto.ResponseObj(ApiResponseCode.NOT_FOUND, "파일이 존재하지 않습니다");
}
Long modelUid = modelMngCoreService.insertModel(addReq);
log.info(
"model inserted: uuid={}, modelUid={}, modelVer={}", uuid, modelUid, addReq.getModelVer());
ModelMetricAddReq modelMetricAddReq = new ModelMetricAddReq();
modelMetricAddReq.setModelUid(modelUid);
modelMetricAddReq.setModelVerUid(modelUid);
@@ -117,7 +152,7 @@ public class ModelMngService {
modelMetricAddReq.setIou(0);
ObjectMapper mapper = new ObjectMapper();
String filePath = modelUploadResDto.getClsModelPath() + modelUploadResDto.getClsModelFileName();
String filePath = modelUploadResDto.getJsonPath() + modelUploadResDto.getJsonFileName();
String dataJson = null;
try {
dataJson = Files.readString(Path.of(filePath));
@@ -136,14 +171,28 @@ public class ModelMngService {
modelMetricAddReq.setIou(iou);
} catch (IOException e) {
log.error("metric json parse failed: uuid={}, jsonPath={}", uuid, filePath, e);
return new ApiResponseDto.ResponseObj(ApiResponseCode.NOT_FOUND, "JSON파일 오류");
}
modelMngCoreService.insertModelResultMetric(modelMetricAddReq);
log.info(
"model metric inserted: modelUid={}, f1={}, precision={}, recall={}, iou={}",
modelUid,
modelMetricAddReq.getF1Score(),
modelMetricAddReq.getPrecision(),
modelMetricAddReq.getRecall(),
modelMetricAddReq.getIou());
String zipFilePath = addReq.getFilePath() + "/" + addReq.getFileName();
boolean isDeleted = FIleChecker.deleteFile(zipFilePath);
if (!isDeleted) {
log.warn("zip file delete failed: uuid={}, path={}", uuid, zipFilePath);
}
log.info("insertModel success: uuid={}, modelUid={}", uuid, modelUid);
return new ApiResponseDto.ResponseObj(ApiResponseDto.ApiResponseCode.OK, "등록되었습니다.");
}
@@ -174,6 +223,14 @@ public class ModelMngService {
}
*/
log.info(
"end uploadChunkModelFile: res={}, resMsg={}, uuid={}, filePath={}, fileName={}",
modelUploadResDto.getRes(),
modelUploadResDto.getResMsg(),
modelUploadResDto.getUuid(),
modelUploadResDto.getFilePath(),
modelUploadResDto.getFileName());
return modelUploadResDto;
}
@@ -184,24 +241,36 @@ public class ModelMngService {
int endPos = 20;
List<Basic> files =
FIleChecker.getFilesFromAllDepth(dirPath, "*", "pth,py,json", 10, "name", startPos, endPos);
FIleChecker.getFilesFromAllDepth(
dirPath, "*", "pth,py,json", 10, "name", startPos, endPos);
boolean hasPt = false; // pt 파일 존재 여부
for (Basic dto : files) {
// 예: 파일명 출력 및 추가 작업
String foldNm = dto.getFullPath().replace(dto.getFileNm(), "");
if (dto.getExtension().equals("pth")) {
modelUploadResDto.setCdModelPath(foldNm);
modelUploadResDto.setCdModelFileName(dto.getFileNm());
} else if (dto.getExtension().equals("py")) {
modelUploadResDto.setCdModelConfigPath(foldNm);
modelUploadResDto.setCdModelConfigFileName(dto.getFileNm());
} else if (dto.getExtension().equals("json")) {
modelUploadResDto.setClsModelPath(foldNm);
modelUploadResDto.setClsModelFileName(dto.getFileNm());
switch (dto.getExtension()) {
case "pth" -> {
modelUploadResDto.setCdModelPath(foldNm);
modelUploadResDto.setCdModelFileName(dto.getFileNm());
}
case "py" -> {
modelUploadResDto.setCdModelConfigPath(foldNm);
modelUploadResDto.setCdModelConfigFileName(dto.getFileNm());
}
case "json" -> {
modelUploadResDto.setJsonPath(foldNm);
modelUploadResDto.setJsonFileName(dto.getFileNm());
}
}
}
// pt는 고정경로 등록
modelUploadResDto.setClsModelPath(ptPath);
modelUploadResDto.setClsModelFileName(ptFileName);
// int fileListPos = 0;
// int fileTotCnt = files.size();
// long fileTotSize = FIleChecker.getFileTotSize(files);

View File

@@ -113,6 +113,8 @@ public class InferenceResultCoreService {
mapSheetLearnEntity.setCreatedUid(userUtil.getId());
mapSheetLearnEntity.setMapSheetCnt(mapSheetName);
mapSheetLearnEntity.setDetectingCnt(0L);
mapSheetLearnEntity.setTotalJobs((long) detectingCnt);
// 회차는 국유인 반영할때 update로 변경됨
// mapSheetLearnEntity.setStage(
// mapSheetLearnRepository.getLearnStage(req.getCompareYyyy(), req.getTargetYyyy()));

View File

@@ -0,0 +1,294 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.enums.LayerType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq;
import com.kamco.cd.kamcoback.layer.dto.WmsDto.WmsAddDto;
import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddDto;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
import com.kamco.cd.kamcoback.postgres.repository.layer.MapLayerRepository;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MapLayerCoreService {
private final MapLayerRepository mapLayerRepository;
private final UserUtil userUtil;
private final ObjectMapper objectMapper;
/**
* 지도 레이어 관리 목록
*
* @return
*/
public List<LayerDto.Basic> getLayers(LayerDto.SearchReq searchReq) {
return mapLayerRepository.findAllLayer(searchReq);
}
/**
* 지도 레이어 상세 목록
*
* @param uuid
* @return
*/
public LayerDto.Detail getLayers(UUID uuid) {
MapLayerEntity entity =
mapLayerRepository
.findDetailByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
return entity.toDto();
}
/**
* 삭제
*
* @param uuid
*/
public void delete(UUID uuid) {
MapLayerEntity entity =
mapLayerRepository
.findDetailByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
if (LayerType.TILE.getId().equals(entity.getLayerType())) {
throw new CustomApiException("UNPROCESSABLE_ENTITY", HttpStatus.CONFLICT);
}
entity.setIsDeleted(true);
entity.setUpdatedUid(userUtil.getId());
entity.setUpdatedDttm(ZonedDateTime.now());
}
/**
* 수정
*
* @param uuid
*/
public void update(UUID uuid, LayerDto.Detail dto) {
MapLayerEntity entity =
mapLayerRepository
.findDetailByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
if (dto.getDescription() != null) {
entity.setDescription(dto.getDescription());
}
if (dto.getUrl() != null) {
entity.setUrl(dto.getUrl());
}
if (dto.getTag() != null) {
entity.setTag(dto.getTag());
}
if (dto.getMinLon() != null) {
entity.setMinLon(dto.getMinLon());
}
if (dto.getMaxLon() != null) {
entity.setMaxLon(dto.getMaxLon());
}
if (dto.getMinLat() != null) {
entity.setMinLat(dto.getMinLat());
}
if (dto.getMaxLat() != null) {
entity.setMaxLat(dto.getMaxLat());
}
if (dto.getMin() != null) {
entity.setMinZoom(dto.getMin());
}
if (dto.getMax() != null) {
entity.setMaxZoom(dto.getMax());
}
if (dto.getIsChangeMap() != null) {
entity.setIsChangeMap(dto.getIsChangeMap());
}
if (dto.getIsLabelingMap() != null) {
entity.setIsLabelingMap(dto.getIsLabelingMap());
}
entity.setUpdatedUid(userUtil.getId());
entity.setUpdatedDttm(ZonedDateTime.now());
}
/**
* 순서 수정
*
* @param dtoList
*/
public void orderUpdate(List<OrderReq> dtoList) {
if (dtoList == null || dtoList.isEmpty()) {
return;
}
List<UUID> uuids =
dtoList.stream().map(OrderReq::getUuid).filter(Objects::nonNull).distinct().toList();
if (uuids.isEmpty()) {
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
List<MapLayerEntity> entities = mapLayerRepository.findAllByUuidIn(uuids);
Map<UUID, MapLayerEntity> entityMap =
entities.stream().collect(Collectors.toMap(MapLayerEntity::getUuid, Function.identity()));
List<UUID> notFound = uuids.stream().filter(u -> !entityMap.containsKey(u)).toList();
if (!notFound.isEmpty()) {
throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND);
}
Long uid = userUtil.getId();
ZonedDateTime now = ZonedDateTime.now();
for (OrderReq dto : dtoList) {
if (dto.getOrder() == null) {
throw new CustomApiException("INVALID_REQUEST", HttpStatus.BAD_REQUEST);
}
MapLayerEntity entity = entityMap.get(dto.getUuid());
entity.setOrder(dto.getOrder());
entity.setUpdatedUid(uid);
entity.setUpdatedDttm(now);
}
}
/**
* Tile 저장
*
* @param dto
*/
public UUID saveTile(LayerDto.AddReq dto) {
LayerDto.SearchReq searchReq = new LayerDto.SearchReq();
searchReq.setLayerType(LayerType.TILE.getId());
List<LayerDto.Basic> entityList = mapLayerRepository.findAllLayer(searchReq);
if (!entityList.isEmpty()) {
throw new CustomApiException("DUPLICATE_DATA", HttpStatus.CONFLICT);
}
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setDescription(dto.getDescription());
mapLayerEntity.setUrl(dto.getUrl());
mapLayerEntity.setTag(dto.getTag());
mapLayerEntity.setMinLon(dto.getMinLon());
mapLayerEntity.setMinLat(dto.getMinLat());
mapLayerEntity.setMaxLon(dto.getMaxLon());
mapLayerEntity.setMaxLat(dto.getMaxLat());
mapLayerEntity.setMinZoom(dto.getMin());
mapLayerEntity.setMaxZoom(dto.getMax());
mapLayerEntity.setCreatedUid(userUtil.getId());
mapLayerEntity.setIsChangeMap(true);
mapLayerEntity.setIsLabelingMap(false);
mapLayerEntity.setOrder(1L);
mapLayerEntity.setLayerType(LayerType.TILE.getId());
mapLayerEntity.setUpdatedDttm(ZonedDateTime.now());
return mapLayerRepository.save(mapLayerEntity).getUuid();
}
/**
* GeoJson 저장
*
* @param addDto
* @return
*/
public UUID saveGeoJson(LayerDto.AddReq addDto) {
Long order = mapLayerRepository.findSortOrderDesc();
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setDescription(addDto.getDescription());
mapLayerEntity.setUrl(addDto.getUrl());
mapLayerEntity.setTag(addDto.getTag());
mapLayerEntity.setCreatedUid(userUtil.getId());
mapLayerEntity.setIsChangeMap(true);
mapLayerEntity.setIsLabelingMap(true);
mapLayerEntity.setLayerType(LayerType.GEOJSON.getId());
mapLayerEntity.setUpdatedDttm(ZonedDateTime.now());
mapLayerEntity.setOrder(order + 1);
return mapLayerRepository.save(mapLayerEntity).getUuid();
}
/**
* wmts 저장
*
* @param addDto
*/
public UUID saveWmts(WmtsAddDto addDto) {
Long order = mapLayerRepository.findSortOrderDesc();
String rawJson = "";
try {
rawJson = objectMapper.writeValueAsString(addDto.getWmtsLayerInfo()); // data 없는 형태로 저장
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setTitle(addDto.getTitle());
mapLayerEntity.setDescription(addDto.getDescription());
mapLayerEntity.setCreatedUid(userUtil.getId());
mapLayerEntity.setRawJson(rawJson);
mapLayerEntity.setIsChangeMap(true);
mapLayerEntity.setIsLabelingMap(true);
mapLayerEntity.setOrder(order + 1);
mapLayerEntity.setLayerType(LayerType.WMTS.getId());
mapLayerEntity.setUpdatedDttm(ZonedDateTime.now());
mapLayerEntity.setTag(addDto.getTag());
return mapLayerRepository.save(mapLayerEntity).getUuid();
}
/**
* wms 저장
*
* @param addDto
* @return
*/
public UUID saveWms(WmsAddDto addDto) {
Long order = mapLayerRepository.findSortOrderDesc();
String rawJson = "";
try {
rawJson = objectMapper.writeValueAsString(addDto.getWmsLayerInfo()); // data 없는 형태로 저장
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setTitle(addDto.getTitle());
mapLayerEntity.setDescription(addDto.getDescription());
mapLayerEntity.setCreatedUid(userUtil.getId());
mapLayerEntity.setRawJson(rawJson);
mapLayerEntity.setIsChangeMap(true);
mapLayerEntity.setIsLabelingMap(true);
mapLayerEntity.setOrder(order + 1);
mapLayerEntity.setLayerType(LayerType.WMS.getId());
mapLayerEntity.setUpdatedDttm(ZonedDateTime.now());
mapLayerEntity.setTag(addDto.getTag());
return mapLayerRepository.save(mapLayerEntity).getUuid();
}
}

View File

@@ -16,6 +16,7 @@ import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
import com.kamco.cd.kamcoback.postgres.entity.YearEntity;
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngRepository;
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngYearRepository;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid;
import java.io.IOException;
@@ -29,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -43,6 +45,7 @@ import org.springframework.transaction.annotation.Transactional;
public class MapSheetMngCoreService {
private final MapSheetMngRepository mapSheetMngRepository;
private final MapSheetMngYearRepository mapSheetMngYearRepository;
@Value("${spring.profiles.active}")
private String activeEnv;
@@ -309,15 +312,23 @@ public class MapSheetMngCoreService {
return mapSheetMngRepository.findByHstMapSheetTargetList(mngYyyy, mapIds);
}
public void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId) {
mapSheetMngRepository.updateMapSheetMngHstUploadId(hstUid, uuid, uploadId);
}
/** 변화탐지 실행 가능 비교년도 저장 */
public void saveSheetMngYear() {
mapSheetMngYearRepository.saveFileInfo();
}
/**
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy
* @param mapId
* @return
* @param mngYyyy 비교년도
* @param mapId 5k 도엽번호
* @return List<MngListCompareDto>
*/
public List<MngListCompareDto> getByHstMapSheetCompareList(int mngYyyy, List<String> mapId) {
return mapSheetMngRepository.findByHstMapSheetCompareList(mngYyyy, mapId);
return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapId);
}
;
}

View File

@@ -1,6 +1,8 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngFileEntity;
import com.kamco.cd.kamcoback.postgres.repository.mapsheet.MapSheetMngYearRepository;
import com.kamco.cd.kamcoback.postgres.repository.scheduler.MapSheetMngFileJobRepository;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto.MngHstDto;
@@ -15,6 +17,7 @@ import org.springframework.stereotype.Service;
public class MapSheetMngFileJobCoreService {
private final MapSheetMngFileJobRepository mapSheetMngFileJobRepository;
private final MapSheetMngYearRepository mapSheetMngYearRepository;
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(
MapSheetMngDto.@Valid MngSearchReq searchReq) {
@@ -69,7 +72,11 @@ public class MapSheetMngFileJobCoreService {
strtYyyy, endYyyy, mapSheetNum);
}
public void updateException5kMapSheet(String mapSheetNum) {
mapSheetMngFileJobRepository.updateException5kMapSheet(mapSheetNum);
public void updateException5kMapSheet(String mapSheetNum, CommonUseStatus commonUseStatus) {
mapSheetMngFileJobRepository.updateException5kMapSheet(mapSheetNum, commonUseStatus);
}
public void saveSheetMngYear() {
mapSheetMngYearRepository.saveFileInfo();
}
}

View File

@@ -0,0 +1,125 @@
package com.kamco.cd.kamcoback.postgres.entity;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@Getter
@Setter
@Entity
@Table(name = "tb_map_layer")
public class MapLayerEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_layer_id_gen")
@SequenceGenerator(
name = "tb_map_layer_id_gen",
sequenceName = "tb_map_layer_seq",
allocationSize = 1)
@Column(name = "id", nullable = false)
private Long id;
@Size(max = 20)
@NotNull
@Column(name = "layer_type", nullable = false, length = 20)
private String layerType;
@Size(max = 200)
@Column(name = "title", length = 200)
private String title;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "url", length = Integer.MAX_VALUE)
private String url;
@Column(name = "min_lon", precision = 10, scale = 7)
private BigDecimal minLon;
@Column(name = "min_lat", precision = 10, scale = 7)
private BigDecimal minLat;
@Column(name = "max_lon", precision = 10, scale = 7)
private BigDecimal maxLon;
@Column(name = "max_lat", precision = 10, scale = 7)
private BigDecimal maxLat;
@Column(name = "min_zoom")
private Short minZoom;
@Column(name = "max_zoom")
private Short maxZoom;
@Column(name = "raw_json", columnDefinition = "jsonb")
@JdbcTypeCode(SqlTypes.JSON)
private String rawJson;
@NotNull
@ColumnDefault("now()")
@Column(name = "created_dttm", nullable = false)
private ZonedDateTime createdDttm = ZonedDateTime.now();
@Column(name = "updated_dttm")
private ZonedDateTime updatedDttm;
@Column(name = "uuid")
private UUID uuid = UUID.randomUUID();
@Column(name = "created_uid")
private Long createdUid;
@Column(name = "updated_uid")
private Long updatedUid;
@Column(name = "is_change_map")
private Boolean isChangeMap;
@Column(name = "is_labeling_map")
private Boolean isLabelingMap;
@Column(name = "sort_order")
private Long order;
@Column(name = "tag")
private String tag;
@Column(name = "is_deleted")
private Boolean isDeleted = false;
public LayerDto.Detail toDto() {
return new LayerDto.Detail(
this.uuid,
this.layerType,
this.title,
this.description,
this.tag,
this.order,
this.isChangeMap,
this.isLabelingMap,
this.url,
this.minLon,
this.minLat,
this.maxLon,
this.maxLat,
this.minZoom,
this.maxZoom,
this.createdDttm);
}
}

View File

@@ -196,6 +196,9 @@ public class MapSheetLearnEntity {
@Column(name = "uid", nullable = false)
private String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
@Column(name = "total_jobs")
private Long totalJobs;
public InferenceResultDto.ResultList toDto() {
return new InferenceResultDto.ResultList(
this.uuid,

View File

@@ -163,6 +163,9 @@ public class MapSheetMngHstEntity extends CommonDateEntity {
@Column(name = "uuid")
private UUID uuid;
@Column(name = "upload_id")
private String uploadId;
// 파일정보 업데이트
public void updateFileInfos(Long tifSizeBytes, Long tfwSizeBytes) {
tifSizeBytes = tifSizeBytes == null ? 0L : tifSizeBytes;

View File

@@ -0,0 +1,34 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@Getter
@Setter
@Entity
@Table(name = "tb_map_sheet_mng_year_yn")
public class MapSheetMngYearYnEntity {
@EmbeddedId private MapSheetMngYearYnEntityId id;
@NotNull
@Column(name = "yn", nullable = false, length = Integer.MAX_VALUE)
private String yn;
@NotNull
@ColumnDefault("now()")
@Column(name = "created_dttm", nullable = false)
private ZonedDateTime createdDttm;
@NotNull
@ColumnDefault("now()")
@Column(name = "updated_dttm", nullable = false)
private ZonedDateTime updatedDttm;
}

View File

@@ -0,0 +1,46 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serializable;
import java.util.Objects;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.Hibernate;
@Getter
@Setter
@Embeddable
public class MapSheetMngYearYnEntityId implements Serializable {
private static final long serialVersionUID = 6282262062316057898L;
@Size(max = 20)
@NotNull
@Column(name = "map_sheet_num", nullable = false, length = 20)
private String mapSheetNum;
@NotNull
@Column(name = "mng_yyyy", nullable = false)
private Integer mngYyyy;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
return false;
}
MapSheetMngYearYnEntityId entity = (MapSheetMngYearYnEntityId) o;
return Objects.equals(this.mngYyyy, entity.mngYyyy)
&& Objects.equals(this.mapSheetNum, entity.mapSheetNum);
}
@Override
public int hashCode() {
return Objects.hash(mngYyyy, mapSheetNum);
}
}

View File

@@ -205,6 +205,9 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
mapSheetLearnEntity.m1PendingJobs,
mapSheetLearnEntity.m2PendingJobs,
mapSheetLearnEntity.m3PendingJobs,
mapSheetLearnEntity.m1RunningJobs,
mapSheetLearnEntity.m2RunningJobs,
mapSheetLearnEntity.m3RunningJobs,
mapSheetLearnEntity.m1CompletedJobs,
mapSheetLearnEntity.m2CompletedJobs,
mapSheetLearnEntity.m3CompletedJobs,
@@ -227,7 +230,8 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
mapSheetLearnEntity.mapSheetScope,
m1Model.modelVer.as("model1Ver"),
m2Model.modelVer.as("model2Ver"),
m3Model.modelVer.as("model3Ver")))
m3Model.modelVer.as("model3Ver"),
mapSheetLearnEntity.totalJobs))
.from(mapSheetLearnEntity)
.leftJoin(m1Model)
.on(m1Model.uuid.eq(mapSheetLearnEntity.m1ModelUuid))

View File

@@ -1510,10 +1510,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
labelingAssignmentEntity.analUid.eq(analEntity.getId()),
labelingAssignmentEntity.workerUid.ne(userId))
.groupBy(memberEntity.userRole, memberEntity.employeeNo, memberEntity.name)
.having(
completeCnt
.multiply(2)
.goe(totalCnt)) // 진행률 평균 이상인 것들만 조회 => percent 를 바로 쓰면 having절에 무리가 갈 수 있다고 함
// .having(
// completeCnt
// .multiply(2)
// .goe(totalCnt)) // 진행률 평균 이상인 것들만 조회 => 조건 제거
.orderBy(
completeCnt
.desc()) // TODO: 현재는 잔여건수가 제일 적은(=완료건수가 높은) 순서로 desc, 추후 도엽번호? PNU? 로 정렬하여

View File

@@ -350,7 +350,8 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
skipCnt.as("skipCnt"),
day3AgoDoneCnt.as("day3AgoDoneCnt"),
day2AgoDoneCnt.as("day2AgoDoneCnt"),
day1AgoDoneCnt.as("day1AgoDoneCnt")))
day1AgoDoneCnt.as("day1AgoDoneCnt"),
memberEntity.status))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
@@ -361,7 +362,8 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.innerJoin(memberEntity)
.on(whereSubBuilder)
.where(whereBuilder)
.groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId)
.groupBy(
memberEntity.userRole, memberEntity.name, memberEntity.userId, memberEntity.status)
.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
@@ -496,7 +498,8 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
skipCnt.as("skipCnt"),
day3AgoDoneCnt.as("day3AgoDoneCnt"),
day2AgoDoneCnt.as("day2AgoDoneCnt"),
day1AgoDoneCnt.as("day1AgoDoneCnt")))
day1AgoDoneCnt.as("day1AgoDoneCnt"),
memberEntity.status))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
@@ -507,7 +510,8 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.innerJoin(memberEntity)
.on(whereSubBuilder)
.where(whereBuilder)
.groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId)
.groupBy(
memberEntity.userRole, memberEntity.name, memberEntity.userId, memberEntity.status)
.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())

View File

@@ -0,0 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.layer;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MapLayerRepository
extends JpaRepository<MapLayerEntity, Long>, MapLayerRepositoryCustom {}

View File

@@ -0,0 +1,18 @@
package com.kamco.cd.kamcoback.postgres.repository.layer;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface MapLayerRepositoryCustom {
Long findSortOrderDesc();
List<LayerDto.Basic> findAllLayer(LayerDto.SearchReq searchReq);
Optional<MapLayerEntity> findDetailByUuid(UUID uuid);
List<MapLayerEntity> findAllByUuidIn(Collection<UUID> uuids);
}

View File

@@ -0,0 +1,87 @@
package com.kamco.cd.kamcoback.postgres.repository.layer;
import static com.kamco.cd.kamcoback.postgres.entity.QMapLayerEntity.mapLayerEntity;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class MapLayerRepositoryImpl implements MapLayerRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Long findSortOrderDesc() {
return queryFactory
.select(mapLayerEntity.order.max().coalesce(0L))
.from(mapLayerEntity)
.where(mapLayerEntity.order.isNotNull())
.fetchOne();
}
@Override
public List<LayerDto.Basic> findAllLayer(LayerDto.SearchReq searchReq) {
BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapLayerEntity.isDeleted.isFalse());
if (searchReq != null) {
if (searchReq.getTag() != null) {
whereBuilder.and(mapLayerEntity.tag.toLowerCase().eq(searchReq.getTag().toLowerCase()));
}
if (searchReq.getLayerType() != null) {
whereBuilder.and(
mapLayerEntity.layerType.toLowerCase().eq(searchReq.getLayerType().toLowerCase()));
}
}
return queryFactory
.select(
Projections.constructor(
LayerDto.Basic.class,
mapLayerEntity.uuid,
mapLayerEntity.layerType,
mapLayerEntity.description,
mapLayerEntity.tag,
mapLayerEntity.order,
mapLayerEntity.isChangeMap,
mapLayerEntity.isLabelingMap,
mapLayerEntity.createdDttm))
.from(mapLayerEntity)
.where(whereBuilder)
.orderBy(mapLayerEntity.order.asc())
.fetch();
}
@Override
public Optional<MapLayerEntity> findDetailByUuid(UUID uuid) {
return Optional.ofNullable(
queryFactory
.select(mapLayerEntity)
.from(mapLayerEntity)
.where(mapLayerEntity.uuid.eq(uuid).and(mapLayerEntity.isDeleted.isFalse()))
.orderBy(mapLayerEntity.order.asc())
.fetchOne());
}
@Override
public List<MapLayerEntity> findAllByUuidIn(Collection<UUID> uuids) {
return queryFactory
.select(mapLayerEntity)
.from(mapLayerEntity)
.where(mapLayerEntity.uuid.in(uuids).and(mapLayerEntity.isDeleted.isFalse()))
.orderBy(mapLayerEntity.order.asc())
.fetch();
}
}

View File

@@ -244,7 +244,7 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
menuEntity.menuNm.as("menuName"),
auditLogEntity.eventType.as("eventType"),
Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD HH:mm')", auditLogEntity.createdDate)
"to_char({0}, 'YYYY-MM-DD HH24:MI')", auditLogEntity.createdDate)
.as("logDateTime"),
Projections.constructor(
AuditLogDto.LogDetail.class,
@@ -308,7 +308,7 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
AuditLogDto.MenuDetail.class,
auditLogEntity.id.as("logId"),
Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD HH:mm')", auditLogEntity.createdDate)
"to_char({0}, 'YYYY-MM-DD HH24:MI')", auditLogEntity.createdDate)
.as("logDateTime"),
memberEntity.name.as("userName"),
memberEntity.employeeNo.as("loginId"),
@@ -375,7 +375,7 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
AuditLogDto.UserDetail.class,
auditLogEntity.id.as("logId"),
Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD HH:mm')", auditLogEntity.createdDate)
"to_char({0}, 'YYYY-MM-DD HH24:MI')", auditLogEntity.createdDate)
.as("logDateTime"),
menuEntity.menuNm.as("menuName"),
auditLogEntity.eventType.as("eventType"),

View File

@@ -56,7 +56,7 @@ public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
errorLogEntity.errorMessage.as("errorMessage"),
errorLogEntity.stackTrace.as("errorDetail"),
Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD')", errorLogEntity.createdDate)))
"to_char({0}, 'YYYY-MM-DD HH24:MI:SS.FF3')", errorLogEntity.createdDate)))
.from(errorLogEntity)
.leftJoin(auditLogEntity)
.on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))

View File

@@ -3,7 +3,6 @@ package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
@@ -11,6 +10,7 @@ import com.kamco.cd.kamcoback.postgres.entity.YearEntity;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.domain.Page;
public interface MapSheetMngRepositoryCustom {
@@ -66,8 +66,6 @@ public interface MapSheetMngRepositoryCustom {
List<MngListDto> findByHstMapSheetTargetList(int mngYyyy, List<String> mapIds);
List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapId);
MapSheetMngDto.MngFilesDto findByFileUidMapSheetFile(Long fileUid);
void updateHstFileSizes(Long hstUid, long tifSizeBytes, long tfwSizeBytes, long totalSizeBytes);
@@ -77,4 +75,6 @@ public interface MapSheetMngRepositoryCustom {
Page<YearEntity> getYears(YearSearchReq req);
List<ImageFeature> getSceneInference(String yyyy, List<String> mapSheetNums);
void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId);
}

View File

@@ -12,7 +12,6 @@ import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
@@ -34,6 +33,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.hibernate.query.Query;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -100,48 +100,55 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("NOTPAIR"))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(mapSheetMngHstEntity.syncState.eq("NOTPAIR")))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("DUPLICATE"))
.when(
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(mapSheetMngHstEntity.syncState.eq("NOTPAIR"))))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(mapSheetMngHstEntity.syncState.eq("DUPLICATE")))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncState
.eq("TYPEERROR")
.or(mapSheetMngHstEntity.syncState.eq("SIZEERROR")))
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(mapSheetMngHstEntity.syncState.eq("DUPLICATE"))))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncState
@@ -151,24 +158,53 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("NOFILE"))
.when(
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(
mapSheetMngHstEntity
.syncState
.eq("TYPEERROR")
.or(
mapSheetMngHstEntity.syncState.eq(
"SIZEERROR")))))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(mapSheetMngHstEntity.syncState.eq("NOFILE")))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(mapSheetMngHstEntity.syncState.eq("NOFILE"))))
.then(1L)
.otherwise(0L)
.sum(),
mapSheetMngEntity.createdDttm,
mapSheetMngHstEntity.syncEndDttm.max()))
.from(mapSheetMngEntity)
.leftJoin(mapSheetMngHstEntity)
.on(mapSheetMngEntity.mngYyyy.eq(mapSheetMngHstEntity.mngYyyy))
.leftJoin(mapInkx5kEntity)
.on(mapSheetMngHstEntity.mapSheetNum.eq(mapInkx5kEntity.mapidcdNo))
.where(whereBuilder)
// .offset(pageable.getOffset())
// .limit(pageable.getPageSize())
@@ -244,48 +280,55 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("NOTPAIR"))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(mapSheetMngHstEntity.syncState.eq("NOTPAIR")))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("DUPLICATE"))
.when(
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(mapSheetMngHstEntity.syncState.eq("NOTPAIR"))))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(mapSheetMngHstEntity.syncState.eq("DUPLICATE")))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncState
.eq("TYPEERROR")
.or(mapSheetMngHstEntity.syncState.eq("SIZEERROR")))
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(mapSheetMngHstEntity.syncState.eq("DUPLICATE"))))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncState
@@ -295,24 +338,53 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("NOFILE"))
.when(
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(
mapSheetMngHstEntity
.syncState
.eq("TYPEERROR")
.or(
mapSheetMngHstEntity.syncState.eq(
"SIZEERROR")))))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(mapSheetMngHstEntity.syncState.eq("NOFILE")))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(
mapInkx5kEntity
.useInference
.eq(CommonUseStatus.USE)
.and(
mapSheetMngHstEntity
.syncCheckState
.eq("DONE")
.and(mapSheetMngHstEntity.syncState.eq("NOFILE"))))
.then(1L)
.otherwise(0L)
.sum(),
mapSheetMngEntity.createdDttm,
mapSheetMngHstEntity.syncEndDttm.max()))
.from(mapSheetMngEntity)
.leftJoin(mapSheetMngHstEntity)
.on(mapSheetMngEntity.mngYyyy.eq(mapSheetMngHstEntity.mngYyyy))
.leftJoin(mapInkx5kEntity)
.on(mapSheetMngHstEntity.mapSheetNum.eq(mapInkx5kEntity.mapidcdNo))
.where(whereBuilder)
.groupBy(mapSheetMngEntity.mngYyyy)
.fetchOne();
@@ -388,7 +460,8 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
mapSheetMngHstEntity.syncCheckTfwFileName,
mapSheetMngHstEntity.syncCheckTifFileName,
mapSheetMngHstEntity.mapSheetPath,
mapSheetMngHstEntity.uuid))
mapSheetMngHstEntity.uuid,
mapSheetMngHstEntity.uploadId))
.from(mapSheetMngHstEntity)
.innerJoin(mapInkx5kEntity)
.on(
@@ -454,7 +527,8 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
mapSheetMngHstEntity.syncCheckTfwFileName,
mapSheetMngHstEntity.syncCheckTifFileName,
mapSheetMngHstEntity.mapSheetPath,
mapSheetMngHstEntity.uuid))
mapSheetMngHstEntity.uuid,
mapSheetMngHstEntity.uploadId))
.from(mapSheetMngHstEntity)
.innerJoin(mapInkx5kEntity)
.on(mapSheetMngHstEntity.mapSheetNum.eq(mapInkx5kEntity.mapidcdNo))
@@ -573,53 +647,6 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.fetch();
}
/**
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy
* @param mapIds
* @return
*/
@Override
public List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {
String sql =
"""
SELECT
t.map_sheet_num,
t.map_years,
COALESCE(s.target_year, 0) AS before_year
FROM public.tb_map_sheet_years_map t
LEFT JOIN LATERAL (
SELECT x::int AS target_year
FROM unnest(string_to_array(t.map_years, '>')) AS x
WHERE x::int <= :mngYyyy
ORDER BY x::int DESC
LIMIT 1
) s ON true
INNER JOIN tb_map_inkx_5k tmik
ON t.map_sheet_num = tmik.mapidcd_no AND tmik.use_inference = 'USE'
WHERE t.map_sheet_num = ANY(:mapIds)
""";
@SuppressWarnings("unchecked")
List<Object[]> rows =
em.createNativeQuery(sql)
.setParameter("mngYyyy", mngYyyy)
.setParameter("mapIds", mapIds.toArray(new String[0]))
.getResultList();
return rows.stream()
.map(
r ->
new MngListCompareDto(
(String) r[1], // map_years
(String) r[0], // map_sheet_num
((Number) r[2]).intValue() // before_year
))
.toList();
}
@Override
public List<MapSheetMngDto.MngFilesDto> findHstUidToMapSheetFileList(Long hstUid) {
BooleanBuilder whereBuilder = new BooleanBuilder();
@@ -1003,6 +1030,16 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.fetch();
}
@Override
public void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId) {
queryFactory
.update(mapSheetMngHstEntity)
.set(mapSheetMngHstEntity.uploadId, uploadId)
.set(mapSheetMngHstEntity.uuid, uuid)
.where(mapSheetMngHstEntity.hstUid.eq(hstUid))
.execute();
}
private BooleanExpression eqYearStatus(QYearEntity years, String status) {
if (status == null) {
return null;

View File

@@ -0,0 +1,9 @@
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngYearYnEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngYearYnEntityId;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MapSheetMngYearRepository
extends JpaRepository<MapSheetMngYearYnEntity, MapSheetMngYearYnEntityId>,
MapSheetMngYearRepositoryCustom {}

View File

@@ -0,0 +1,10 @@
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import java.util.List;
public interface MapSheetMngYearRepositoryCustom {
void saveFileInfo();
List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds);
}

View File

@@ -0,0 +1,101 @@
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngYearYnEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class MapSheetMngYearRepositoryImpl implements MapSheetMngYearRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final EntityManager em;
/** 변화탐지 실행 가능 비교년도 저장 */
@Override
public void saveFileInfo() {
em.createNativeQuery("TRUNCATE TABLE tb_map_sheet_mng_year_yn").executeUpdate();
String sql =
"""
WITH bounds AS (
SELECT
map_sheet_num,
MIN(mng_yyyy::int) AS min_y,
MAX(mng_yyyy::int) AS max_y
FROM tb_map_sheet_mng_files
GROUP BY map_sheet_num
),
years AS (
SELECT
b.map_sheet_num,
gs.y AS mng_yyyy
FROM bounds b
CROSS JOIN LATERAL generate_series(b.min_y, b.max_y) AS gs(y)
),
exist AS (
SELECT DISTINCT
map_sheet_num,
mng_yyyy::int AS mng_yyyy
FROM tb_map_sheet_mng_files
),
src AS (
SELECT
y.map_sheet_num,
y.mng_yyyy,
CASE
WHEN e.map_sheet_num IS NULL THEN 'N'
ELSE 'Y'
END AS yn
FROM years y
LEFT JOIN exist e
ON e.map_sheet_num = y.map_sheet_num
AND e.mng_yyyy = y.mng_yyyy
)
INSERT INTO tb_map_sheet_mng_year_yn
(map_sheet_num, mng_yyyy, yn)
SELECT
map_sheet_num,
mng_yyyy,
yn
FROM src
ON CONFLICT (map_sheet_num, mng_yyyy)
DO UPDATE SET
yn = EXCLUDED.yn,
updated_dttm = now()
""";
em.createNativeQuery(sql).executeUpdate();
}
/**
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy
* @param mapIds
* @return
*/
@Override
public List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {
QMapSheetMngYearYnEntity y = QMapSheetMngYearYnEntity.mapSheetMngYearYnEntity;
StringExpression mngYyyyStr = Expressions.stringTemplate("concat({0}, '')", mngYyyy);
return queryFactory
.select(
Projections.constructor(
MngListCompareDto.class, mngYyyyStr, y.id.mapSheetNum, y.id.mngYyyy.max()))
.from(y)
.where(y.id.mapSheetNum.in(mapIds), y.yn.eq("Y"), y.id.mngYyyy.loe(mngYyyy))
.groupBy(y.id.mapSheetNum)
.fetch();
}
}

View File

@@ -21,7 +21,7 @@ public class MemberInactiveJobRepositoryImpl implements MemberInactiveJobReposit
@Override
public List<MemberInfo> findInactiveLabelerReviewer() {
ZonedDateTime checkTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).minusDays(14);
ZonedDateTime checkTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).minusDays(28);
return queryFactory
.select(Projections.constructor(MemberInfo.class, memberEntity.id, memberEntity.employeeNo))
.from(memberEntity)

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.scheduler.dto.MapSheetMngDto.MngHstDto;
import java.util.List;
@@ -21,5 +22,5 @@ public interface MapSheetMngFileJobRepositoryCustom {
public Long findByHstMapSheetBeforeYyyyListCount(int strtYyyy, int endYyyy, String mapSheetNum);
public void updateException5kMapSheet(String mapSheetNum);
public void updateException5kMapSheet(String mapSheetNum, CommonUseStatus commonUseStatus);
}

View File

@@ -257,11 +257,12 @@ public class MapSheetMngFileJobRepositoryImpl extends QuerydslRepositorySupport
}
@Override
public void updateException5kMapSheet(String mapSheetNum) {
public void updateException5kMapSheet(String mapSheetNum, CommonUseStatus commonUseStatus) {
long updateCount =
queryFactory
.update(mapInkx5kEntity)
.set(mapInkx5kEntity.useInference, CommonUseStatus.AUTO_EXCEPT)
.set(mapInkx5kEntity.useInference, commonUseStatus)
.set(mapInkx5kEntity.modifiedDate, ZonedDateTime.now())
.where(mapInkx5kEntity.mapidcdNo.eq(mapSheetNum))
.execute();
}

View File

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.scheduler.service;
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.common.utils.FIleChecker;
import com.kamco.cd.kamcoback.common.utils.FIleChecker.Basic;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngFileJobCoreService;
@@ -91,14 +92,10 @@ public class MapSheetMngFileJobService {
mngHstDataSyncStateUpdate(item);
// 1. MngHstDto 객체의 필드 값에 접근
// hstUid = item.getHstUid();
// syncState = item.getSyncState();
srchDto.setMaxDepth(10);
srchDto.setDirPath(item.getSyncMngPath());
srchDto.setExtension("tif,tfw");
srchDto.setFileNm(item.getMapSheetNum());
// srchDto.setFileNm("34602047");
System.out.println(
"UID: "
@@ -112,8 +109,6 @@ public class MapSheetMngFileJobService {
+ " .tif,tfw");
// 도엽번호로 파일 찾기
// basicList = this.getFilesDepthAll(srchDto);
basicList =
FIleChecker.getFilesFromAllDepth(
srchDto.getDirPath(),
@@ -140,10 +135,6 @@ public class MapSheetMngFileJobService {
}
for (Basic item2 : basicList) {
// System.out.println("path: " + item2.getParentPath());
// System.out.println("path: " + item2.getFileNm());
// System.out.println("path: " + item2.getFullPath());
MngFileAddReq addReq = new MngFileAddReq();
addReq.setMngYyyy(item.getMngYyyy());
addReq.setMapSheetNum(item.getMapSheetNum());
@@ -202,11 +193,15 @@ public class MapSheetMngFileJobService {
if (syncState.isEmpty()) {
syncState = "DONE";
}
item.setSyncState(syncState);
mngHstDataSyncStateUpdate(item);
}
// 사용할 수 있는 이전 년도 도엽 테이블 저장
mapSheetMngFileJobCoreService.saveSheetMngYear();
Long notyetCnt = this.mngDataStateDoneUpdate(mngYyyy);
}
@@ -236,28 +231,26 @@ public class MapSheetMngFileJobService {
public Long mapSheetAutoExceptionUpdate(int mngYyyy, String mapSheetNum) {
// 2025년 이전 파일싱크는 무조건 이전3년이 존재하지 않으므로 자동추론제외를 진행하지 않는다.(전년도 파일이 무조건 존재하는 것으로 리턴)
if (syncAutoExceptionStartYear > mngYyyy) {
return 1L;
}
// if (syncAutoExceptionStartYear > mngYyyy) {
// return 1L;
// }
// List<String> mapSheetNums = new ArrayList<>();
// mapSheetNums.add(mapSheetNum);
int strtYyyy = mngYyyy - syncAutoExceptionBeforeYearCnt + 1;
// int strtYyyy = mngYyyy - syncAutoExceptionBeforeYearCnt + 1;
int strtYyyy = 2020;
int endYyyy = mngYyyy;
// 본년도+이전년도가 3개년인 도엽 확인
// 본년도+이전년도가 3개년인 도엽 확인 -> 2020년도부터 현재까지
Long beforeCnt =
mapSheetMngFileJobCoreService.findByHstMapSheetBeforeYyyyListCount(
strtYyyy, endYyyy, mapSheetNum);
// System.out.println("mapSheetAutoExceptionUpdate mapSheetNum == " + mapSheetNum);
// System.out.println("mapSheetAutoExceptionUpdate strtYyyy == " + strtYyyy);
// System.out.println("mapSheetAutoExceptionUpdate endYyyy == " + endYyyy);
// System.out.println("mapSheetAutoExceptionUpdate beforeCnt == " + beforeCnt);
if (beforeCnt == 0) {
System.out.println("mapSheetAutoExceptionUpdate inference == 자동추론제외");
mapSheetMngFileJobCoreService.updateException5kMapSheet(mapSheetNum);
mapSheetMngFileJobCoreService.updateException5kMapSheet(
mapSheetNum, CommonUseStatus.AUTO_EXCEPT);
} else {
// 하나라도 있으면 USE
mapSheetMngFileJobCoreService.updateException5kMapSheet(mapSheetNum, CommonUseStatus.USE);
}
return beforeCnt;

View File

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.common.service.ExternalJarRunner;
import com.kamco.cd.kamcoback.scheduler.config.ShpKeyLock;
import java.nio.file.Paths;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Async;
@@ -29,7 +30,9 @@ public class ShpPipelineService {
externalJarRunner.run(jarPath, batchId, inferenceId, "", "MERGED");
// uid 기준 shp 파일 geoserver 등록
String register = datasetDir + "/" + inferenceId + "/merge/" + inferenceId + ".shp";
String register =
Paths.get(datasetDir, inferenceId, "merge", inferenceId + ".shp").toString();
log.info("register={}", register);
externalJarRunner.run(jarPath, register, inferenceId);
// uid 기준 도엽별 shp, geojson 파일 생성

View File

@@ -43,6 +43,7 @@ public class UploadDto {
@NoArgsConstructor
@AllArgsConstructor
public static class UploadAddReq {
@Schema(description = "업로드 ID", example = "각데이터의 식별키")
private String uploadId;
@@ -144,6 +145,7 @@ public class UploadDto {
@NoArgsConstructor
@AllArgsConstructor
public static class uploadDto {
@Schema(description = "업로드 ID", example = "각데이터의 식별키")
private String uploadId;
@@ -189,6 +191,7 @@ public class UploadDto {
@NoArgsConstructor
@AllArgsConstructor
public static class UploadRes {
private String res;
private String resMsg;
private UUID uuid;
@@ -196,6 +199,7 @@ public class UploadDto {
private String fileName;
private int chunkIndex;
private int chunkTotalIndex;
private String uploadId;
public double getUploadRate() {
if (this.chunkTotalIndex == 0) {

View File

@@ -11,6 +11,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -59,7 +60,9 @@ public class UploadService {
UploadDto.UploadRes upRes = new UploadDto.UploadRes();
long datasetId = 0;
if (upAddReqDto.getDatasetId() != null) datasetId = upAddReqDto.getDatasetId();
if (upAddReqDto.getDatasetId() != null) {
datasetId = upAddReqDto.getDatasetId();
}
String uploadId = System.currentTimeMillis() + "";
// UUID uuid = UUID.randomUUID();
UUID uuid = upAddReqDto.getUuid();
@@ -85,7 +88,9 @@ public class UploadService {
// 세션 신규,중복체크(초기화 포함)
UploadDto.uploadDto dto = this.checkUploadSession(upAddReqDto, upRes);
if (!upRes.getRes().equals("success")) return upRes;
if (!upRes.getRes().equals("success")) {
return upRes;
}
status = FileUploadStatus.UPLOADING.name();
upAddReqDto.setStatus(status);
@@ -96,7 +101,9 @@ public class UploadService {
}
// 폴더 생성 및 체크
if (!checkChunkFoler(upRes, tmpDataSetDir, fianlDir)) return upRes;
if (!checkChunkFoler(upRes, tmpDataSetDir, fianlDir)) {
return upRes;
}
// chunk저장하기
if (!FIleChecker.multipartChunkSaveTo(file, tmpDataSetDir, chunkIndex)) {
@@ -104,23 +111,51 @@ public class UploadService {
upRes.setResMsg("chunkIndex:" + chunkIndex + " 업로드 애러");
}
if (chunkIndex == chunkTotalIndex) {
if (Objects.equals(chunkIndex, chunkTotalIndex)) {
upAddReqDto.setUploadId(dto.getUploadId());
upAddReqDto.setStatus(FileUploadStatus.DONE.name());
upAddReqDto.setStatus(FileUploadStatus.DONE.name()); // DONE 말고 MERGING 추천
uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto);
log.info(
"merge start: uploadId={}, fileName={}, chunkIndex={}, chunkTotalIndex={}, tmpDir={}, finalDir={}",
dto.getUploadId(),
fileName,
chunkIndex,
chunkTotalIndex,
tmpDataSetDir,
fianlDir);
try {
this.mergeChunks(tmpDataSetDir, fianlDir, fileName, chunkTotalIndex);
upAddReqDto.setUploadId(dto.getUploadId());
upAddReqDto.setStatus("MERGED");
upAddReqDto.setStatus(FileUploadStatus.MERGED.name());
uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto);
log.info(
"merge success: uploadId={}, fileName={}, outputDir={}",
dto.getUploadId(),
fileName,
fianlDir);
} catch (IOException e) {
// throw new RuntimeException(e);
// 실패 상태도 남기는 걸 강추
upAddReqDto.setStatus(FileUploadStatus.MERGE_FAIL.name());
uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto);
log.error(
"merge failed: uploadId={}, fileName={}, chunkIndex={}, chunkTotalIndex={}, tmpDir={}, finalDir={}",
dto.getUploadId(),
fileName,
chunkIndex,
chunkTotalIndex,
tmpDataSetDir,
fianlDir,
e);
upRes.setRes("fail");
upRes.setResMsg("파일Chunk 병합(merge) 애러");
upRes.setResMsg("파일Chunk 병합(merge) 오류");
return upRes;
}
}
@@ -203,7 +238,10 @@ public class UploadService {
uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto);
}
if (dto != null) upRes.setUuid(dto.getUuid());
if (dto != null) {
upRes.setUuid(dto.getUuid());
upRes.setUploadId(dto.getUploadId());
}
upRes.setChunkIndex(upAddReqDto.getChunkIndex());
upRes.setChunkTotalIndex(upAddReqDto.getChunkTotalIndex());
@@ -211,28 +249,64 @@ public class UploadService {
return dto;
}
public void mergeChunks(String tmpDir, String fianlDir, String fileName, int chunkTotalIndex)
public void mergeChunks(String tmpDir, String finalDir, String fileName, int chunkTotalIndex)
throws IOException {
long start = System.currentTimeMillis();
Path outputPath = Paths.get(finalDir, fileName);
log.info(
"mergeChunks start: fileName={}, tmpDir={}, outputPath={}, lastChunkIndex={}",
fileName,
tmpDir,
outputPath,
chunkTotalIndex);
long totalBytes = 0;
Path outputPath = Paths.get(fianlDir, fileName);
try (FileChannel outChannel =
FileChannel.open(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
FileChannel.open(
outputPath,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
for (int i = 0; i <= chunkTotalIndex; i++) {
Path chunkPath = Paths.get(tmpDir, i + "");
Path chunkPath = Paths.get(tmpDir, String.valueOf(i));
try (FileChannel inChannel = FileChannel.open(chunkPath, StandardOpenOption.READ)) {
long transferred = 0;
long size = inChannel.size();
long transferred = 0;
while (transferred < size) {
transferred += inChannel.transferTo(transferred, size - transferred, outChannel);
}
totalBytes += size;
}
// 병합 후 즉시 삭제하여 디스크 공간 확보
Files.delete(chunkPath);
}
try {
FIleChecker.deleteFolder(tmpDir);
} catch (Exception e) {
log.warn("tmpDir delete failed (merge already succeeded): tmpDir={}", tmpDir, e);
}
} catch (IOException e) {
log.error(
"mergeChunks failed: fileName={}, tmpDir={}, outputPath={}, lastChunkIndex={}",
fileName,
tmpDir,
outputPath,
chunkTotalIndex,
e);
throw e;
}
// 병합후 임시 폴더 삭제
FIleChecker.deleteFolder(tmpDir);
log.info(
"mergeChunks done: fileName={}, outputPath={}, bytes={}, elapsedMs={}",
fileName,
outputPath,
totalBytes,
(System.currentTimeMillis() - start));
}
}

View File

@@ -104,11 +104,14 @@ file:
model-tmp-dir: ${file.model-dir}tmp/
model-file-extention: pth,json,py
pt-path: /kamco-nfs/ckpt/classification/
pt-FileName: v5-best.pt
inference:
url: http://10.100.0.11:8000/jobs
batch-url: http://10.100.0.11:8000/batches
geojson-dir: /kamco-nfs/requests/
jar-path: /kamco-nfs/dataset/shp-exporter-1.0.0.jar
jar-path: /kamco-nfs/dataset/shp/shp-exporter-1.0.0.jar
inference-server-name: server1,server2,server3,server4
gukyuin:
@@ -118,3 +121,7 @@ gukyuin:
training-data:
geojson-dir: /kamco-nfs/model_output/labeling/
layer:
geoserver-url: https://kamco.geo-dev.gs.dabeeo.com
workspace: cd

View File

@@ -15,8 +15,8 @@ spring:
format_sql: true # ⚠️ 선택 - SQL 포맷팅 (가독성)
datasource:
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
#url: jdbc:postgresql://localhost:15432/kamco_cds # 로컬호스트
#url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
url: jdbc:postgresql://localhost:5432/kamco_cds # 로컬호스트
username: kamco_cds
password: kamco_cds_Q!W@E#R$
hikari:
@@ -68,22 +68,40 @@ mapsheet:
file:
sync-root-dir: D:/kamco-nfs/images/
sync-root-dir: /Users/bokmin/kamco-nfs/images/
#sync-root-dir: /kamco-nfs/images/
sync-tmp-dir: ${file.sync-root-dir}/tmp
sync-file-extention: tfw,tif
sync-auto-exception-start-year: 2024
sync-auto-exception-before-year-cnt: 3
dataset-dir: D:/kamco-nfs/dataset/
dataset-dir: /Users/bokmin/kamco-nfs/dataset/export/
#dataset-dir: /kamco-nfs/dataset/export/
dataset-tmp-dir: ${file.dataset-dir}tmp/
model-dir: D:/kamco-nfs/ckpt/model/
model-dir: /Users/bokmin/kamco-nfs/ckpt/model/
#model-dir: /kamco-nfs/ckpt/model/
model-tmp-dir: ${file.model-dir}tmp/
model-file-extention: pth,json,py
pt-path: /kamco-nfs/ckpt/classification/
pt-FileName: v5-best.pt
inference:
url: http://10.100.0.11:8000/jobs
batch-url: http://10.100.0.11:8000/batches
geojson-dir: /kamco-nfs/requests/
jar-path: jar/makeshp-1.0.0.jar
geojson-dir: /Users/bokmin/kamco-nfs/requests/
jar-path: /Users/bokmin/kamco-nfs/dataset/shp/shp-exporter-1.0.0.jar
inference-server-name: server1,server2,server3,server4
gukyuin:
#url: http://localhost:8080
url: http://192.168.2.129:5301
mast: ${gukyuin.url}/api/kcd/cdi/chn/mast
training-data:
geojson-dir: /Users/bokmin/kamco-nfs/model_output/labeling/
layer:
geoserver-url: http://localhost:9080
workspace: cd

View File

@@ -59,11 +59,14 @@ file:
model-tmp-dir: ${file.model-dir}tmp/
model-file-extention: pth,json,py
pt-path: /kamco-nfs/ckpt/classification/
pt-FileName: v5-best.pt
inference:
url: http://10.100.0.11:8000/jobs
batch-url: http://10.100.0.11:8000/batches
geojson-dir: /kamco-nfs/requests/
jar-path: /kamco-nfs/dataset/shp-exporter-1.0.0.jar
jar-path: /kamco-nfs/dataset/shp/shp-exporter-1.0.0.jar
inference-server-name: server1,server2,server3,server4
gukyuin:
@@ -73,3 +76,7 @@ gukyuin:
training-data:
geojson-dir: /kamco-nfs/model_output/labeling/
layer:
geoserver-url: https://kamco.geo-dev.gs.dabeeo.com
workspace: cd