From f793042927adc7aa6bf4726b3205915fec66987e Mon Sep 17 00:00:00 2001 From: teddy Date: Thu, 29 Jan 2026 10:27:08 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B6=94=EB=A1=A0=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/common/enums/LayerType.java | 9 + .../kamcoback/layer/LayerApiController.java | 150 +++++++++- .../cd/kamcoback/layer/dto/GeoJsonDto.java | 18 ++ .../cd/kamcoback/layer/dto/LayerDto.java | 192 ++++++++++++ .../kamco/cd/kamcoback/layer/dto/WmsDto.java | 30 ++ .../cd/kamcoback/layer/dto/WmsLayerInfo.java | 172 +++++++++++ .../kamco/cd/kamcoback/layer/dto/WmtsDto.java | 2 + .../kamcoback/layer/service/LayerService.java | 162 ++++++++++ .../kamcoback/layer/service/TileService.java | 10 - .../kamcoback/layer/service/WmsService.java | 246 ++++++++++++++- .../kamcoback/layer/service/WmtsService.java | 24 -- .../model/service/ModelMngService.java | 27 +- .../postgres/core/MapLayerCoreService.java | 281 ++++++++++++++++-- .../postgres/entity/MapLayerEntity.java | 31 +- .../layer/MapLayerRepositoryCustom.java | 12 +- .../layer/MapLayerRepositoryImpl.java | 70 ++++- .../scheduler/service/ShpPipelineService.java | 5 +- src/main/resources/application-dev.yml | 3 + src/main/resources/application-local.yml | 32 +- src/main/resources/application-prod.yml | 3 + 20 files changed, 1378 insertions(+), 101 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/layer/dto/GeoJsonDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsDto.java create mode 100755 src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/layer/service/LayerService.java delete mode 100644 src/main/java/com/kamco/cd/kamcoback/layer/service/TileService.java diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/LayerType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/LayerType.java index 2340b3b9..99816656 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/LayerType.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/LayerType.java @@ -2,6 +2,7 @@ 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; @@ -25,4 +26,12 @@ public enum LayerType implements EnumType { public String getText() { return desc; } + + public static Optional from(String type) { + try { + return Optional.of(LayerType.valueOf(type)); + } catch (Exception e) { + return Optional.empty(); + } + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java b/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java index f3eed1cf..e407e92d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/LayerApiController.java @@ -1,11 +1,11 @@ package com.kamco.cd.kamcoback.layer; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; -import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddReqDto; -import com.kamco.cd.kamcoback.layer.dto.WmtsLayerInfo; -import com.kamco.cd.kamcoback.layer.service.WmtsService; +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.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -13,11 +13,16 @@ 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") @@ -26,7 +31,126 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/layer") public class LayerApiController { - private final WmtsService wmtsService; + 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> getLayers( + @RequestParam(required = false) String tag) { + LayerDto.SearchReq searchReq = new SearchReq(); + searchReq.setTag(tag); + List 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 save( + @PathVariable String layerType, @RequestBody LayerDto.AddReq dto) { + return ApiResponseDto.ok(layerService.saveLayers(layerType, dto)); + } + + @PutMapping("/order") + public ApiResponseDto updateOrder(@RequestBody List 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 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 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 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( @@ -43,10 +167,10 @@ public class LayerApiController { }) @GetMapping("/wmts/tile") public ApiResponseDto> getWmtsTile() { - return ApiResponseDto.ok(wmtsService.getTile()); + return ApiResponseDto.ok(layerService.getWmtsTile()); } - @Operation(summary = "wmts tile 상세 조회", description = "wmts tile 상세 조회 api") + @Operation(summary = "wms tile 조회", description = "wms tile 조회 api") @ApiResponses( value = { @ApiResponse( @@ -55,16 +179,12 @@ public class LayerApiController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = WmtsLayerInfo.class))), + array = @ArraySchema(schema = @Schema(implementation = String.class)))), @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) - @PostMapping("/wmts") - public ApiResponseDto getWmtsTileDetail( - @Parameter(description = "선택한 tile", example = "959022EFCAA448D1A325FA7B8ABEA10D") - @RequestBody - WmtsAddReqDto dto) { - wmtsService.save(dto); - return ApiResponseDto.ok(null); + @GetMapping("/wms/tile") + public ApiResponseDto> getWmsTile() { + return ApiResponseDto.ok(layerService.getWmsTitle()); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/GeoJsonDto.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/GeoJsonDto.java new file mode 100644 index 00000000..012bb792 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/GeoJsonDto.java @@ -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; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java new file mode 100644 index 00000000..77c9012b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/LayerDto.java @@ -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; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsDto.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsDto.java new file mode 100644 index 00000000..28a03534 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsDto.java @@ -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; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java new file mode 100755 index 00000000..5fbf0647 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmsLayerInfo.java @@ -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 keywords; + private BoundingBox boundingBox; + private List 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 getKeywords() { + return keywords; + } + + public void setKeywords(List 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 getCrs() { + return crs; + } + + public void setCrs(List 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 + + '}'; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsDto.java b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsDto.java index 9092c607..09987a32 100644 --- a/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/dto/WmtsDto.java @@ -14,6 +14,7 @@ public class WmtsDto { public static class WmtsAddReqDto { private String title; private String description; + private String tag; } @Getter @@ -24,5 +25,6 @@ public class WmtsDto { private WmtsLayerInfo wmtsLayerInfo; private String title; private String description; + private String tag; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/service/LayerService.java b/src/main/java/com/kamco/cd/kamcoback/layer/service/LayerService.java new file mode 100644 index 00000000..68db7085 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/layer/service/LayerService.java @@ -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 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 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 + */ + public List getWmtsTile() { + return wmtsService.getTile(); + } + + /** + * wms title 조회 + * + * @return + */ + public List 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); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/service/TileService.java b/src/main/java/com/kamco/cd/kamcoback/layer/service/TileService.java deleted file mode 100644 index ad32eb34..00000000 --- a/src/main/java/com/kamco/cd/kamcoback/layer/service/TileService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.kamco.cd.kamcoback.layer.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class TileService {} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/service/WmsService.java b/src/main/java/com/kamco/cd/kamcoback/layer/service/WmsService.java index c86d8590..504bc9a0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/layer/service/WmsService.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/service/WmsService.java @@ -1,10 +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 {} +public class WmsService { + @Value("${layer.geoserver-url}") + private String geoserverUrl; + + @Value("${layer.workspace}") + private String workspace; + + public List getTile() { + List layers; + + try { + layers = getAllLayers(geoserverUrl, workspace); + } catch (IOException | ParserConfigurationException | SAXException e) { + throw new CustomApiException("INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR); + } + + List 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 getAllLayers(String baseUrl, String workspace) + throws IOException, ParserConfigurationException, SAXException { + String capabilitiesUrl = buildGetCapabilitiesUrl(baseUrl, workspace); + Document doc = fetchAndParseXml(capabilitiesUrl); + + List 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; // 찾지 못한 경우 + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/layer/service/WmtsService.java b/src/main/java/com/kamco/cd/kamcoback/layer/service/WmtsService.java index 8a3d2cf2..f0f73d16 100644 --- a/src/main/java/com/kamco/cd/kamcoback/layer/service/WmtsService.java +++ b/src/main/java/com/kamco/cd/kamcoback/layer/service/WmtsService.java @@ -1,7 +1,5 @@ package com.kamco.cd.kamcoback.layer.service; -import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddDto; -import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddReqDto; import com.kamco.cd.kamcoback.layer.dto.WmtsLayerInfo; import com.kamco.cd.kamcoback.postgres.core.MapLayerCoreService; import java.net.URL; @@ -38,11 +36,6 @@ public class WmtsService { private static final String WMTS_GEOSERVER_URL = "/geoserver/"; private static final String WMTS_CAPABILITIES_URL = "/gwc/service/wmts?REQUEST=GetCapabilities"; - /** - * tile 조회 - * - * @return List - */ public List getTile() { List layers = getAllLayers(geoserverUrl, workspace); List titles = new ArrayList<>(); @@ -53,23 +46,6 @@ public class WmtsService { return titles; } - /** - * 선택 tile 저장 - * - * @param dto - */ - @Transactional - public void save(WmtsAddReqDto dto) { - // 선택한 tile 상세정보 조회 - WmtsLayerInfo info = getDetail(dto.getTitle()); - - WmtsAddDto addDto = new WmtsAddDto(); - addDto.setWmtsLayerInfo(info); - addDto.setDescription(dto.getDescription()); - addDto.setTitle(dto.getTitle()); - mapLayerCoreService.save(addDto); - } - public WmtsLayerInfo getDetail(String tile) { return getLayerInfoByTitle(geoserverUrl, workspace, tile); } diff --git a/src/main/java/com/kamco/cd/kamcoback/model/service/ModelMngService.java b/src/main/java/com/kamco/cd/kamcoback/model/service/ModelMngService.java index 14d2a9af..e7ec0279 100644 --- a/src/main/java/com/kamco/cd/kamcoback/model/service/ModelMngService.java +++ b/src/main/java/com/kamco/cd/kamcoback/model/service/ModelMngService.java @@ -56,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 findModelMgmtList( ModelMngDto.searchReq searchReq, LocalDate startDate, @@ -236,7 +242,7 @@ public class ModelMngService { List files = FIleChecker.getFilesFromAllDepth( - dirPath, "*", "pth,py,pt,json", 10, "name", startPos, endPos); + dirPath, "*", "pth,py,json", 10, "name", startPos, endPos); boolean hasPt = false; // pt 파일 존재 여부 @@ -253,11 +259,6 @@ public class ModelMngService { modelUploadResDto.setCdModelConfigPath(foldNm); modelUploadResDto.setCdModelConfigFileName(dto.getFileNm()); } - case "pt" -> { - modelUploadResDto.setClsModelPath(foldNm); - modelUploadResDto.setClsModelFileName(dto.getFileNm()); - hasPt = true; - } case "json" -> { modelUploadResDto.setJsonPath(foldNm); modelUploadResDto.setJsonFileName(dto.getFileNm()); @@ -265,18 +266,10 @@ public class ModelMngService { } } - if (!hasPt) { + // pt는 고정경로 등록 + modelUploadResDto.setClsModelPath(ptPath); + modelUploadResDto.setClsModelFileName(ptFileName); - String defaultPath = "/kamco-nfs/ckpt/classification/"; - String defaultFileName = "v5-best.pt"; - - Path ptPath = Paths.get(defaultPath, defaultFileName); - - if (Files.exists(ptPath)) { - modelUploadResDto.setClsModelPath(defaultPath); - modelUploadResDto.setClsModelFileName(defaultFileName); - } - } // int fileListPos = 0; // int fileTotCnt = files.size(); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapLayerCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapLayerCoreService.java index 16f7c259..7b2b3011 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapLayerCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapLayerCoreService.java @@ -3,11 +3,23 @@ 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 @@ -17,33 +29,266 @@ public class MapLayerCoreService { private final UserUtil userUtil; private final ObjectMapper objectMapper; + /** + * 지도 레이어 관리 목록 + * + * @return + */ + public List 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 dtoList) { + if (dtoList == null || dtoList.isEmpty()) { + return; + } + + List uuids = + dtoList.stream().map(OrderReq::getUuid).filter(Objects::nonNull).distinct().toList(); + + if (uuids.isEmpty()) { + throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); + } + + List entities = mapLayerRepository.findAllByUuidIn(uuids); + + Map entityMap = + entities.stream().collect(Collectors.toMap(MapLayerEntity::getUuid, Function.identity())); + + List 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 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 void save(WmtsAddDto addDto) { - Long order = 20L; - MapLayerEntity entity = mapLayerRepository.findSortOrderDesc().orElse(null); - - if (entity != null) { - order = entity.getOrder() == null ? order : entity.getOrder() + 10; - } + public UUID saveWmts(WmtsAddDto addDto) { + Long order = mapLayerRepository.findSortOrderDesc(); + String rawJson = ""; try { - String rawJson = objectMapper.writeValueAsString(addDto.getWmtsLayerInfo()); // data 없는 형태로 저장 - 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); - mapLayerEntity.setLayerType(LayerType.WMTS.getId()); - mapLayerRepository.save(mapLayerEntity); + 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(); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapLayerEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapLayerEntity.java index df915a5a..fe0d1d81 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapLayerEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapLayerEntity.java @@ -1,5 +1,6 @@ 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; @@ -73,10 +74,10 @@ public class MapLayerEntity { @NotNull @ColumnDefault("now()") @Column(name = "created_dttm", nullable = false) - private ZonedDateTime createdAt = ZonedDateTime.now(); + private ZonedDateTime createdDttm = ZonedDateTime.now(); @Column(name = "updated_dttm") - private ZonedDateTime updatedAt; + private ZonedDateTime updatedDttm; @Column(name = "uuid") private UUID uuid = UUID.randomUUID(); @@ -95,4 +96,30 @@ public class MapLayerEntity { @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); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryCustom.java index a17a16ed..3bcf5b72 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryCustom.java @@ -1,8 +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 { - Optional findSortOrderDesc(); + Long findSortOrderDesc(); + + List findAllLayer(LayerDto.SearchReq searchReq); + + Optional findDetailByUuid(UUID uuid); + + List findAllByUuidIn(Collection uuids); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryImpl.java index 155ba7f6..58f83231 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/layer/MapLayerRepositoryImpl.java @@ -2,9 +2,15 @@ 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; @@ -15,13 +21,67 @@ public class MapLayerRepositoryImpl implements MapLayerRepositoryCustom { private final JPAQueryFactory queryFactory; @Override - public Optional findSortOrderDesc() { + public Long findSortOrderDesc() { + return queryFactory + .select(mapLayerEntity.order.max().coalesce(0L)) + .from(mapLayerEntity) + .where(mapLayerEntity.order.isNotNull()) + .fetchOne(); + } + + @Override + public List 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 findDetailByUuid(UUID uuid) { return Optional.ofNullable( queryFactory - .selectFrom(mapLayerEntity) - .where(mapLayerEntity.order.isNotNull()) - .orderBy(mapLayerEntity.order.desc()) - .limit(1) + .select(mapLayerEntity) + .from(mapLayerEntity) + .where(mapLayerEntity.uuid.eq(uuid).and(mapLayerEntity.isDeleted.isFalse())) + .orderBy(mapLayerEntity.order.asc()) .fetchOne()); } + + @Override + public List findAllByUuidIn(Collection uuids) { + return queryFactory + .select(mapLayerEntity) + .from(mapLayerEntity) + .where(mapLayerEntity.uuid.in(uuids).and(mapLayerEntity.isDeleted.isFalse())) + .orderBy(mapLayerEntity.order.asc()) + .fetch(); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/scheduler/service/ShpPipelineService.java b/src/main/java/com/kamco/cd/kamcoback/scheduler/service/ShpPipelineService.java index dd4bcd1d..33aa8f9a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/scheduler/service/ShpPipelineService.java +++ b/src/main/java/com/kamco/cd/kamcoback/scheduler/service/ShpPipelineService.java @@ -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 파일 생성 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 64638c76..cd917c7d 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -104,6 +104,9 @@ 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 diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 43c22520..5409635f 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -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 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 15b63b18..e5608458 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -59,6 +59,9 @@ 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