Merge pull request 'feat/infer_dev_260107' (#295) from feat/infer_dev_260107 into develop
Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/295
This commit is contained in:
@@ -42,23 +42,4 @@ public class InferenceResultShpApiController {
|
|||||||
@PathVariable Long learnId) {
|
@PathVariable Long learnId) {
|
||||||
return ApiResponseDto.createOK(inferenceResultShpService.saveInferenceResultData(learnId));
|
return ApiResponseDto.createOK(inferenceResultShpService.saveInferenceResultData(learnId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "shp 파일 생성", description = "shp 파일 생성")
|
|
||||||
@ApiResponses(
|
|
||||||
value = {
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "201",
|
|
||||||
description = "파일생성 성공",
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = InferenceResultShpDto.FileCntDto.class))),
|
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
|
||||||
})
|
|
||||||
@PostMapping("/create/{learnId}")
|
|
||||||
public ApiResponseDto<InferenceResultShpDto.FileCntDto> createShpFile(
|
|
||||||
@PathVariable Long learnId) {
|
|
||||||
return ApiResponseDto.createOK(inferenceResultShpService.createShpFile(learnId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,364 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.inference.service;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
|
|
||||||
import com.kamco.cd.kamcoback.inference.dto.WriteCnt;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.geotools.api.data.SimpleFeatureSource;
|
|
||||||
import org.geotools.api.data.SimpleFeatureStore;
|
|
||||||
import org.geotools.api.data.Transaction;
|
|
||||||
import org.geotools.api.feature.simple.SimpleFeature;
|
|
||||||
import org.geotools.api.feature.simple.SimpleFeatureType;
|
|
||||||
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
|
|
||||||
import org.geotools.data.shapefile.ShapefileDataStore;
|
|
||||||
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
|
|
||||||
import org.geotools.feature.DefaultFeatureCollection;
|
|
||||||
import org.geotools.feature.simple.SimpleFeatureBuilder;
|
|
||||||
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
|
|
||||||
import org.geotools.geojson.geom.GeometryJSON;
|
|
||||||
import org.geotools.referencing.CRS;
|
|
||||||
import org.locationtech.jts.geom.Geometry;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class GeoToolsShpWriter implements ShpWriter {
|
|
||||||
|
|
||||||
// DBF 파일 한글 깨짐 방지를 위해 EUC-KR 사용
|
|
||||||
private static final Charset DBF_CHARSET = Charset.forName("EUC-KR");
|
|
||||||
|
|
||||||
// GeoJSON 출력은 UTF-8
|
|
||||||
private static final Charset GEOJSON_CHARSET = StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
// 좌표계: Korea 2000 / Central Belt 2010
|
|
||||||
private static final String EPSG_5186 = "EPSG:5186";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SHP 파일(.shp/.shx/.dbf/.prj)을 생성한다.
|
|
||||||
*
|
|
||||||
* <p>- shpBasePath를 기준으로 파일을 생성한다. 예) /Users/kim/export/shp/1_map_2021_2022 → 1_map_2021_2022.shp
|
|
||||||
* → 1_map_2021_2022.shx → 1_map_2021_2022.dbf → 1_map_2021_2022.prj
|
|
||||||
*
|
|
||||||
* <p>- geometry 타입은 첫 번째 유효 geometry 기준으로 스키마를 생성한다. - 좌표계는 EPSG:5186으로 설정하며, .prj 파일을 직접 생성한다.
|
|
||||||
*
|
|
||||||
* @param shpBasePath 확장자를 제외한 SHP 파일 기본 경로
|
|
||||||
* @param rows 동일 그룹(stage, mapId, input1, input2)의 데이터 목록
|
|
||||||
* @return 이번 호출로 write(생성/덮어쓰기)가 수행된 파일 개수
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public WriteCnt writeShp(String shpBasePath, List<InferenceResultShpDto.Basic> rows) {
|
|
||||||
|
|
||||||
if (rows == null || rows.isEmpty()) {
|
|
||||||
return WriteCnt.zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SHP는 Geometry.class를 허용하지 않으므로
|
|
||||||
// 첫 번째 유효 geometry의 "구체 타입"을 기준으로 스키마를 생성한다.
|
|
||||||
Geometry firstGeom = firstNonNullGeometry(rows);
|
|
||||||
if (firstGeom == null) {
|
|
||||||
throw new IllegalArgumentException("SHP 생성 실패: geometry가 전부 null 입니다. path=" + shpBasePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Class<? extends Geometry> geomType = (Class<? extends Geometry>) firstGeom.getClass();
|
|
||||||
|
|
||||||
ShapefileDataStore dataStore = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
File shpFile = new File(shpBasePath + ".shp");
|
|
||||||
createDirectories(shpFile);
|
|
||||||
|
|
||||||
// EPSG:5186 CRS 로딩
|
|
||||||
CoordinateReferenceSystem crs = CRS.decode(EPSG_5186, false);
|
|
||||||
|
|
||||||
// FeatureType(schema) 생성
|
|
||||||
SimpleFeatureType schema = createSchema(geomType, crs);
|
|
||||||
|
|
||||||
// ShapefileDataStore 생성 (기존 파일이 있어도 새로 생성/overwrite 동작)
|
|
||||||
dataStore = createDataStore(shpFile, schema);
|
|
||||||
|
|
||||||
// FeatureCollection 생성
|
|
||||||
DefaultFeatureCollection collection = buildFeatureCollection(schema, rows);
|
|
||||||
|
|
||||||
// 실제 SHP 파일에 feature 쓰기
|
|
||||||
writeFeatures(dataStore, collection);
|
|
||||||
|
|
||||||
// .prj 파일 직접 생성 (EPSG:5186)
|
|
||||||
writePrjFile(shpBasePath, crs);
|
|
||||||
|
|
||||||
log.info("SHP 생성 완료: {} ({} features)", shpFile.getAbsolutePath(), collection.size());
|
|
||||||
|
|
||||||
// 덮어쓰기 포함: 이번 호출이 정상 종료되면 4개 파일 write가 발생했다고 카운트
|
|
||||||
return new WriteCnt(1, 1, 1, 1, 0);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("SHP 생성 실패: " + shpBasePath, e);
|
|
||||||
} finally {
|
|
||||||
if (dataStore != null) {
|
|
||||||
try {
|
|
||||||
dataStore.dispose();
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GeoJSON 파일(.geojson)을 생성한다.
|
|
||||||
*
|
|
||||||
* <p>- FeatureCollection 형태로 출력한다. - 최상단에 name / crs / properties를 포함한다. - 각 Feature는 polygon 단위로
|
|
||||||
* 생성된다. - geometry는 GeoTools GeometryJSON을 사용하여 직렬화한다.
|
|
||||||
*
|
|
||||||
* <p>GeoJSON 구조 예: { "type": "FeatureCollection", "name": "stage_input1_input2_mapId", "crs": {
|
|
||||||
* "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::5186" } }, "properties": { ...
|
|
||||||
* }, "features": [ ... ] }
|
|
||||||
*
|
|
||||||
* @param geoJsonPath 생성할 GeoJSON 파일의 전체 경로 (.geojson 포함)
|
|
||||||
* @param rows 동일 그룹(stage, mapId, input1, input2)의 데이터 목록
|
|
||||||
* @return 이번 호출로 write(생성/덮어쓰기)가 수행된 파일 개수
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public WriteCnt writeGeoJson(String geoJsonPath, List<InferenceResultShpDto.Basic> rows) {
|
|
||||||
|
|
||||||
if (rows == null || rows.isEmpty()) {
|
|
||||||
return WriteCnt.zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
File geoJsonFile = new File(geoJsonPath);
|
|
||||||
createDirectories(geoJsonFile);
|
|
||||||
|
|
||||||
// 그룹 공통 메타 정보는 첫 row 기준
|
|
||||||
InferenceResultShpDto.Basic first = rows.get(0);
|
|
||||||
|
|
||||||
ObjectMapper om = new ObjectMapper();
|
|
||||||
GeometryJSON gj = new GeometryJSON(15);
|
|
||||||
|
|
||||||
// FeatureCollection 루트
|
|
||||||
ObjectNode root = om.createObjectNode();
|
|
||||||
root.put("type", "FeatureCollection");
|
|
||||||
|
|
||||||
// name: stage_input1_input2_mapId
|
|
||||||
String name =
|
|
||||||
String.format(
|
|
||||||
"%d_%d_%d_%d",
|
|
||||||
first.getStage(), first.getInput1(), first.getInput2(), first.getMapId());
|
|
||||||
root.put("name", name);
|
|
||||||
|
|
||||||
// CRS (EPSG:5186)
|
|
||||||
ObjectNode crs = om.createObjectNode();
|
|
||||||
crs.put("type", "name");
|
|
||||||
ObjectNode crsProps = om.createObjectNode();
|
|
||||||
crsProps.put("name", "urn:ogc:def:crs:EPSG::5186");
|
|
||||||
crs.set("properties", crsProps);
|
|
||||||
root.set("crs", crs);
|
|
||||||
|
|
||||||
// 그룹 공통 properties
|
|
||||||
ObjectNode groupProps = om.createObjectNode();
|
|
||||||
groupProps.put("stage", first.getStage());
|
|
||||||
groupProps.put("input1", first.getInput1());
|
|
||||||
groupProps.put("input2", first.getInput2());
|
|
||||||
groupProps.put("map_id", first.getMapId());
|
|
||||||
root.set("properties", groupProps);
|
|
||||||
|
|
||||||
// features 배열
|
|
||||||
ArrayNode features = om.createArrayNode();
|
|
||||||
|
|
||||||
for (InferenceResultShpDto.Basic dto : rows) {
|
|
||||||
if (dto.getGeometry() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectNode feature = om.createObjectNode();
|
|
||||||
feature.put("type", "Feature");
|
|
||||||
|
|
||||||
// feature properties
|
|
||||||
ObjectNode p = om.createObjectNode();
|
|
||||||
p.put("polygon_id", dto.getUuid() != null ? dto.getUuid().toString() : null);
|
|
||||||
if (dto.getCdProb() != null) {
|
|
||||||
p.put("cd_prob", dto.getCdProb());
|
|
||||||
}
|
|
||||||
if (dto.getInput1() != null) {
|
|
||||||
p.put("input1", dto.getInput1());
|
|
||||||
}
|
|
||||||
if (dto.getInput2() != null) {
|
|
||||||
p.put("input2", dto.getInput2());
|
|
||||||
}
|
|
||||||
if (dto.getMapId() != null) {
|
|
||||||
p.put("map_id", dto.getMapId());
|
|
||||||
}
|
|
||||||
if (dto.getArea() != null) {
|
|
||||||
p.put("area", dto.getArea());
|
|
||||||
}
|
|
||||||
p.put("before_c", dto.getBeforeClass());
|
|
||||||
if (dto.getBeforeProbability() != null) {
|
|
||||||
p.put("before_p", dto.getBeforeProbability());
|
|
||||||
}
|
|
||||||
p.put("after_c", dto.getAfterClass());
|
|
||||||
if (dto.getAfterProbability() != null) {
|
|
||||||
p.put("after_p", dto.getAfterProbability());
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.set("properties", p);
|
|
||||||
|
|
||||||
// geometry
|
|
||||||
String geomJson = gj.toString(dto.getGeometry());
|
|
||||||
JsonNode geomNode = om.readTree(geomJson);
|
|
||||||
feature.set("geometry", geomNode);
|
|
||||||
|
|
||||||
features.add(feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
root.set("features", features);
|
|
||||||
|
|
||||||
// 파일 쓰기
|
|
||||||
try (OutputStreamWriter w =
|
|
||||||
new OutputStreamWriter(new FileOutputStream(geoJsonFile), GEOJSON_CHARSET)) {
|
|
||||||
om.writerWithDefaultPrettyPrinter().writeValue(w, root);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("GeoJSON 생성 완료: {} ({} features)", geoJsonFile.getAbsolutePath(), features.size());
|
|
||||||
|
|
||||||
// 덮어쓰기 포함: 이번 호출이 정상 종료되면 geojson 1개 write로 카운트
|
|
||||||
return new WriteCnt(0, 0, 0, 0, 1);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("GeoJSON 생성 실패: " + geoJsonPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Geometry firstNonNullGeometry(List<InferenceResultShpDto.Basic> rows) {
|
|
||||||
for (InferenceResultShpDto.Basic r : rows) {
|
|
||||||
if (r != null && r.getGeometry() != null) {
|
|
||||||
return r.getGeometry();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SimpleFeatureType createSchema(
|
|
||||||
Class<? extends Geometry> geomType, CoordinateReferenceSystem crs) {
|
|
||||||
SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
|
|
||||||
b.setName("inference_result");
|
|
||||||
b.setCRS(crs);
|
|
||||||
|
|
||||||
// geometry는 반드시 첫 컬럼
|
|
||||||
b.add("the_geom", geomType);
|
|
||||||
|
|
||||||
// DBF 컬럼 정의 (10자 제한 고려)
|
|
||||||
b.length(36).add("poly_id", String.class);
|
|
||||||
b.add("cd_prob", Double.class);
|
|
||||||
b.add("input1", Integer.class);
|
|
||||||
b.add("input2", Integer.class);
|
|
||||||
b.add("map_id", Long.class);
|
|
||||||
b.add("area", Double.class);
|
|
||||||
b.length(20).add("before_c", String.class);
|
|
||||||
b.add("before_p", Double.class);
|
|
||||||
b.length(20).add("after_c", String.class);
|
|
||||||
b.add("after_p", Double.class);
|
|
||||||
|
|
||||||
return b.buildFeatureType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* .shp .shx .dbf .fix 파일 생성 (껍데기 생성)
|
|
||||||
*
|
|
||||||
* @param shpFile
|
|
||||||
* @param schema
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private ShapefileDataStore createDataStore(File shpFile, SimpleFeatureType schema)
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
Map<String, Serializable> params = new HashMap<>();
|
|
||||||
params.put("url", shpFile.toURI().toURL());
|
|
||||||
|
|
||||||
// .fix 파일 생성 Boolean.TRUE, 미생성 Boolean.FALSE
|
|
||||||
params.put("create spatial index", Boolean.FALSE);
|
|
||||||
|
|
||||||
ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
|
|
||||||
|
|
||||||
ShapefileDataStore dataStore = (ShapefileDataStore) factory.createNewDataStore(params);
|
|
||||||
|
|
||||||
dataStore.setCharset(DBF_CHARSET);
|
|
||||||
dataStore.createSchema(schema);
|
|
||||||
|
|
||||||
return dataStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefaultFeatureCollection buildFeatureCollection(
|
|
||||||
SimpleFeatureType schema, List<InferenceResultShpDto.Basic> rows) {
|
|
||||||
DefaultFeatureCollection collection = new DefaultFeatureCollection();
|
|
||||||
SimpleFeatureBuilder builder = new SimpleFeatureBuilder(schema);
|
|
||||||
|
|
||||||
for (InferenceResultShpDto.Basic dto : rows) {
|
|
||||||
if (dto == null || dto.getGeometry() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.add(dto.getGeometry());
|
|
||||||
builder.add(dto.getUuid() != null ? dto.getUuid().toString() : null);
|
|
||||||
builder.add(dto.getCdProb() != null ? dto.getCdProb().doubleValue() : null);
|
|
||||||
builder.add(dto.getInput1());
|
|
||||||
builder.add(dto.getInput2());
|
|
||||||
builder.add(dto.getMapId());
|
|
||||||
builder.add(dto.getArea() != null ? dto.getArea().doubleValue() : null);
|
|
||||||
builder.add(dto.getBeforeClass());
|
|
||||||
builder.add(
|
|
||||||
dto.getBeforeProbability() != null ? dto.getBeforeProbability().doubleValue() : null);
|
|
||||||
builder.add(dto.getAfterClass());
|
|
||||||
builder.add(
|
|
||||||
dto.getAfterProbability() != null ? dto.getAfterProbability().doubleValue() : null);
|
|
||||||
|
|
||||||
SimpleFeature feature = builder.buildFeature(null);
|
|
||||||
collection.add(feature);
|
|
||||||
builder.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeFeatures(ShapefileDataStore dataStore, DefaultFeatureCollection collection)
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
String typeName = dataStore.getTypeNames()[0];
|
|
||||||
SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);
|
|
||||||
|
|
||||||
if (!(featureSource instanceof SimpleFeatureStore store)) {
|
|
||||||
throw new IllegalStateException("FeatureStore 생성 실패");
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setTransaction(Transaction.AUTO_COMMIT);
|
|
||||||
store.addFeatures(collection);
|
|
||||||
store.getTransaction().commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writePrjFile(String shpBasePath, CoordinateReferenceSystem crs) throws Exception {
|
|
||||||
|
|
||||||
File prjFile = new File(shpBasePath + ".prj");
|
|
||||||
createDirectories(prjFile);
|
|
||||||
|
|
||||||
Files.writeString(prjFile.toPath(), crs.toWKT(), StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createDirectories(File file) throws Exception {
|
|
||||||
File parent = file.getParentFile();
|
|
||||||
if (parent != null) {
|
|
||||||
Files.createDirectories(parent.toPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.kamco.cd.kamcoback.inference.service;
|
package com.kamco.cd.kamcoback.inference.service;
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
|
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
|
||||||
import com.kamco.cd.kamcoback.inference.dto.WriteCnt;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService;
|
import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService;
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -15,7 +13,6 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
public class InferenceResultShpService {
|
public class InferenceResultShpService {
|
||||||
|
|
||||||
private final InferenceResultShpCoreService coreService;
|
private final InferenceResultShpCoreService coreService;
|
||||||
private final ShpWriter shpWriter;
|
|
||||||
|
|
||||||
@Value("${mapsheet.shp.baseurl}")
|
@Value("${mapsheet.shp.baseurl}")
|
||||||
private String baseDir;
|
private String baseDir;
|
||||||
@@ -25,71 +22,4 @@ public class InferenceResultShpService {
|
|||||||
public InferenceResultShpDto.InferenceCntDto saveInferenceResultData(Long id) {
|
public InferenceResultShpDto.InferenceCntDto saveInferenceResultData(Long id) {
|
||||||
return coreService.buildInferenceData(id);
|
return coreService.buildInferenceData(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 분석 데이터 단위로 SHP / GeoJSON 파일을 생성한다.
|
|
||||||
*
|
|
||||||
* <p>처리 흐름: 1. 파일 미생성 상태의 분석 데이터 조회 2. 재생성을 위한 상태 초기화 3. 도형 데이터 조회 4. SHP / GeoJSON 파일 생성 5. 파일
|
|
||||||
* 생성 완료 상태 반영
|
|
||||||
*
|
|
||||||
* <p>중간 실패 시 다음 실행에서 전체 재생성된다.
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public InferenceResultShpDto.FileCntDto createShpFile(Long learnId) {
|
|
||||||
|
|
||||||
// TODO 배치 실행으로 변경 필요
|
|
||||||
int batchSize = 100;
|
|
||||||
int geomLimit = 500_000;
|
|
||||||
|
|
||||||
WriteCnt total = WriteCnt.zero();
|
|
||||||
|
|
||||||
List<Long> dataUids = coreService.findPendingDataUids(batchSize, learnId);
|
|
||||||
|
|
||||||
for (Long dataUid : dataUids) {
|
|
||||||
|
|
||||||
// 재생성을 위한 생성 상태 초기화
|
|
||||||
coreService.resetForRegenerate(dataUid);
|
|
||||||
|
|
||||||
// 추론 데이터 조회
|
|
||||||
List<InferenceResultShpDto.Basic> dtoList = coreService.loadGeomDtos(dataUid, geomLimit);
|
|
||||||
if (dtoList.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 파일명 생성 (stage_mapSheet_compare_target)
|
|
||||||
InferenceResultShpDto.Basic first = dtoList.get(0);
|
|
||||||
String baseName =
|
|
||||||
String.format(
|
|
||||||
"%d_%d_%d_%d",
|
|
||||||
first.getStage(), first.getMapId(), first.getInput1(), first.getInput2());
|
|
||||||
|
|
||||||
String baseDir2 = "/" + first.getInput1() + "_" + first.getInput2() + "/" + first.getStage();
|
|
||||||
String shpBasePath = baseDir + baseDir2 + "/shp/" + baseName;
|
|
||||||
String geoJsonPath = baseDir + baseDir2 + "/geojson/" + baseName + ".geojson";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Writer가 "이번 호출에서 write한 개수"를 반환
|
|
||||||
total = total.plus(shpWriter.writeShp(shpBasePath, dtoList));
|
|
||||||
total = total.plus(shpWriter.writeGeoJson(geoJsonPath, dtoList));
|
|
||||||
|
|
||||||
// 파일 생성 완료 상태 반영
|
|
||||||
List<Long> geoUids = dtoList.stream().map(InferenceResultShpDto.Basic::getGeoUid).toList();
|
|
||||||
coreService.markSuccess(dataUid, geoUids);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 실패 시 markSuccess 하지 않음 -> 다음 실행에서 재생성
|
|
||||||
// log.warn("파일 생성 실패: dataUid={}, baseName={}", dataUid, baseName, e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InferenceResultShpDto.FileCntDto fileCntDto = new InferenceResultShpDto.FileCntDto();
|
|
||||||
fileCntDto.setShp(total.shp());
|
|
||||||
fileCntDto.setShx(total.shx());
|
|
||||||
fileCntDto.setDbf(total.dbf());
|
|
||||||
fileCntDto.setPrj(total.prj());
|
|
||||||
fileCntDto.setGeojson(total.geojson());
|
|
||||||
|
|
||||||
return fileCntDto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.inference.service;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
|
|
||||||
import com.kamco.cd.kamcoback.inference.dto.WriteCnt;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface ShpWriter {
|
|
||||||
|
|
||||||
// SHP (.shp/.shx/.dbf/.fix)
|
|
||||||
WriteCnt writeShp(String shpBasePath, List<InferenceResultShpDto.Basic> rows);
|
|
||||||
|
|
||||||
// GeoJSON (.geojson)
|
|
||||||
WriteCnt writeGeoJson(String geoJsonPath, List<InferenceResultShpDto.Basic> rows);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.core;
|
package com.kamco.cd.kamcoback.postgres.core;
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
|
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository;
|
import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository;
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -28,46 +26,4 @@ public class InferenceResultShpCoreService {
|
|||||||
|
|
||||||
return cntDto;
|
return cntDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 파일 생성이 완료되지 않은 분석 데이터(data_uid) 목록을 조회한다. */
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public List<Long> findPendingDataUids(int limit, Long learnId) {
|
|
||||||
return repo.findPendingDataUids(limit, learnId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 분석 데이터 재생성을 위해 기존 파일 생성 상태를 초기화한다.
|
|
||||||
*
|
|
||||||
* <p>- 분석 데이터(file_created_yn)를 미생성 상태로 변경 - 해당 분석 데이터에 속한 모든 도형의 생성 상태를 미생성으로 변경
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public void resetForRegenerate(Long dataUid) {
|
|
||||||
repo.resetInferenceCreated(dataUid);
|
|
||||||
repo.resetGeomCreatedByDataUid(dataUid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 지정된 분석 데이터에 속한 도형 정보를 조회한다.
|
|
||||||
*
|
|
||||||
* <p>- 파일 미생성 상태의 도형만 대상 - geometry가 존재하는 도형만 조회
|
|
||||||
*/
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public List<InferenceResultShpDto.Basic> loadGeomDtos(Long dataUid, int limit) {
|
|
||||||
List<MapSheetAnalDataInferenceGeomEntity> entities =
|
|
||||||
repo.findGeomEntitiesByDataUid(dataUid, limit);
|
|
||||||
|
|
||||||
return entities.stream().map(InferenceResultShpDto.Basic::from).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 파일 생성이 성공한 도형 및 분석 데이터에 대해 생성 완료 상태로 갱신한다.
|
|
||||||
*
|
|
||||||
* @param dataUid 분석 데이터 UID
|
|
||||||
* @param geoUids 파일 생성이 완료된 도형 UID 목록
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public void markSuccess(Long dataUid, List<Long> geoUids) {
|
|
||||||
repo.markGeomCreatedByGeoUids(geoUids);
|
|
||||||
repo.markInferenceCreated(dataUid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.repository.Inference;
|
package com.kamco.cd.kamcoback.postgres.repository.Inference;
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -16,18 +14,6 @@ public interface InferenceResultRepositoryCustom {
|
|||||||
|
|
||||||
void upsertSttcFromInferenceResults(Long analId);
|
void upsertSttcFromInferenceResults(Long analId);
|
||||||
|
|
||||||
List<Long> findPendingDataUids(int limit, Long learnId);
|
|
||||||
|
|
||||||
int resetInferenceCreated(Long dataUid);
|
|
||||||
|
|
||||||
int markInferenceCreated(Long dataUid);
|
|
||||||
|
|
||||||
int resetGeomCreatedByDataUid(Long dataUid);
|
|
||||||
|
|
||||||
int markGeomCreatedByGeoUids(List<Long> geoUids);
|
|
||||||
|
|
||||||
List<MapSheetAnalDataInferenceGeomEntity> findGeomEntitiesByDataUid(Long dataUid, int limit);
|
|
||||||
|
|
||||||
Long getInferenceLearnIdByUuid(UUID uuid);
|
Long getInferenceLearnIdByUuid(UUID uuid);
|
||||||
|
|
||||||
public Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid);
|
public Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid);
|
||||||
|
|||||||
@@ -3,15 +3,12 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference;
|
|||||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
|
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
|
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity;
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -328,151 +325,10 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
|
|||||||
em.createNativeQuery(sql).setParameter("analUid", analUid).executeUpdate();
|
em.createNativeQuery(sql).setParameter("analUid", analUid).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===============================
|
|
||||||
// Jobs
|
|
||||||
// ===============================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 파일 생성이 완료되지 않은 분석 데이터(data_uid) 목록을 조회한다.
|
|
||||||
*
|
|
||||||
* @param limit 최대 조회 건수
|
|
||||||
* @return data_uid 목록
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<Long> findPendingDataUids(int limit, Long learnId) {
|
|
||||||
|
|
||||||
return queryFactory
|
|
||||||
.select(inferenceDataEntity.id)
|
|
||||||
.from(inferenceEntity)
|
|
||||||
.innerJoin(inferenceDataEntity)
|
|
||||||
.on(inferenceEntity.id.eq(inferenceDataEntity.analUid))
|
|
||||||
.where(
|
|
||||||
inferenceEntity
|
|
||||||
.learnId
|
|
||||||
.eq(learnId)
|
|
||||||
.and(
|
|
||||||
inferenceDataEntity
|
|
||||||
.fileCreatedYn
|
|
||||||
.isFalse()
|
|
||||||
.or(inferenceDataEntity.fileCreatedYn.isNull())))
|
|
||||||
.orderBy(inferenceDataEntity.id.asc())
|
|
||||||
.limit(limit)
|
|
||||||
.fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===============================
|
|
||||||
// Reset / Mark
|
|
||||||
// ===============================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 분석 데이터의 파일 생성 상태를 재생성 가능 상태로 초기화한다.
|
|
||||||
*
|
|
||||||
* <p>- file_created_yn = false - file_created_dttm = null
|
|
||||||
*
|
|
||||||
* @return 갱신된 행 수
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int resetInferenceCreated(Long dataUid) {
|
|
||||||
ZonedDateTime now = ZonedDateTime.now();
|
|
||||||
|
|
||||||
return (int)
|
|
||||||
queryFactory
|
|
||||||
.update(inferenceDataEntity)
|
|
||||||
.set(inferenceDataEntity.fileCreatedYn, false)
|
|
||||||
.set(inferenceDataEntity.fileCreatedDttm, (ZonedDateTime) null)
|
|
||||||
.set(inferenceDataEntity.updatedDttm, now)
|
|
||||||
.where(inferenceDataEntity.id.eq(dataUid))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 분석 데이터의 파일 생성 완료 상태를 반영한다.
|
|
||||||
*
|
|
||||||
* @return 갱신된 행 수
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int markInferenceCreated(Long dataUid) {
|
|
||||||
ZonedDateTime now = ZonedDateTime.now();
|
|
||||||
|
|
||||||
return (int)
|
|
||||||
queryFactory
|
|
||||||
.update(inferenceDataEntity)
|
|
||||||
.set(inferenceDataEntity.fileCreatedYn, true)
|
|
||||||
.set(inferenceDataEntity.fileCreatedDttm, now)
|
|
||||||
.set(inferenceDataEntity.updatedDttm, now)
|
|
||||||
.where(inferenceDataEntity.id.eq(dataUid))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 분석 데이터에 속한 모든 도형의 파일 생성 상태를 초기화한다.
|
|
||||||
*
|
|
||||||
* @return 갱신된 행 수
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int resetGeomCreatedByDataUid(Long dataUid) {
|
|
||||||
ZonedDateTime now = ZonedDateTime.now();
|
|
||||||
|
|
||||||
return (int)
|
|
||||||
queryFactory
|
|
||||||
.update(inferenceGeomEntity)
|
|
||||||
.set(inferenceGeomEntity.fileCreatedYn, false)
|
|
||||||
.set(inferenceGeomEntity.fileCreatedDttm, (ZonedDateTime) null)
|
|
||||||
.set(inferenceGeomEntity.updatedDttm, now)
|
|
||||||
.where(inferenceGeomEntity.dataUid.eq(dataUid))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 파일 생성이 완료된 도형(geo_uid)을 생성 완료 상태로 반영한다.
|
|
||||||
*
|
|
||||||
* @param geoUids 생성 완료된 도형 UID 목록
|
|
||||||
* @return 갱신된 행 수
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int markGeomCreatedByGeoUids(List<Long> geoUids) {
|
|
||||||
if (geoUids == null || geoUids.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ZonedDateTime now = ZonedDateTime.now();
|
|
||||||
|
|
||||||
return (int)
|
|
||||||
queryFactory
|
|
||||||
.update(inferenceGeomEntity)
|
|
||||||
.set(inferenceGeomEntity.fileCreatedYn, true)
|
|
||||||
.set(inferenceGeomEntity.fileCreatedDttm, now)
|
|
||||||
.set(inferenceGeomEntity.updatedDttm, now)
|
|
||||||
.where(inferenceGeomEntity.geoUid.in(geoUids))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===============================
|
// ===============================
|
||||||
// Export source (Entity only)
|
// Export source (Entity only)
|
||||||
// ===============================
|
// ===============================
|
||||||
|
|
||||||
/**
|
|
||||||
* SHP / GeoJSON 파일 생성을 위한 도형 데이터 조회
|
|
||||||
*
|
|
||||||
* <p>- 특정 분석 데이터(data_uid)에 속한 도형 - geometry 존재 - 파일 미생성 상태만 대상
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<MapSheetAnalDataInferenceGeomEntity> findGeomEntitiesByDataUid(
|
|
||||||
Long dataUid, int limit) {
|
|
||||||
return queryFactory
|
|
||||||
.selectFrom(inferenceGeomEntity)
|
|
||||||
.where(
|
|
||||||
inferenceGeomEntity.dataUid.eq(dataUid),
|
|
||||||
inferenceGeomEntity.geom.isNotNull(),
|
|
||||||
inferenceGeomEntity
|
|
||||||
.fileCreatedYn
|
|
||||||
.isFalse()
|
|
||||||
.or(inferenceGeomEntity.fileCreatedYn.isNull()))
|
|
||||||
.orderBy(inferenceGeomEntity.geoUid.asc())
|
|
||||||
.limit(limit)
|
|
||||||
.fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getInferenceLearnIdByUuid(UUID uuid) {
|
public Long getInferenceLearnIdByUuid(UUID uuid) {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.kamco.cd.kamcoback.scheduler.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
public class AsyncConfig {
|
||||||
|
|
||||||
|
@Bean(name = "shpExecutor")
|
||||||
|
public Executor shpExecutor() {
|
||||||
|
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
|
||||||
|
ex.setCorePoolSize(2);
|
||||||
|
ex.setMaxPoolSize(4);
|
||||||
|
ex.setQueueCapacity(50);
|
||||||
|
ex.setThreadNamePrefix("shp-");
|
||||||
|
ex.initialize();
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.kamco.cd.kamcoback.scheduler.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ShpKeyLock {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, ReentrantLock> locks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public boolean tryLock(String key) {
|
||||||
|
ReentrantLock lock = locks.computeIfAbsent(key, k -> new ReentrantLock());
|
||||||
|
return lock.tryLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlock(String key) {
|
||||||
|
ReentrantLock lock = locks.get(key);
|
||||||
|
if (lock != null && lock.isHeldByCurrentThread()) {
|
||||||
|
lock.unlock();
|
||||||
|
// 메모리 누수 방지(락이 비어있으면 제거)
|
||||||
|
if (!lock.hasQueuedThreads()) {
|
||||||
|
locks.remove(key, lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ import org.springframework.stereotype.Service;
|
|||||||
public class MapSheetInferenceJobService {
|
public class MapSheetInferenceJobService {
|
||||||
|
|
||||||
private final InferenceResultCoreService inferenceResultCoreService;
|
private final InferenceResultCoreService inferenceResultCoreService;
|
||||||
|
private final ShpPipelineService shpPipelineService;
|
||||||
|
|
||||||
private final ExternalHttpClient externalHttpClient;
|
private final ExternalHttpClient externalHttpClient;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final ExternalJarRunner externalJarRunner;
|
private final ExternalJarRunner externalJarRunner;
|
||||||
@@ -240,15 +242,8 @@ public class MapSheetInferenceJobService {
|
|||||||
String mapIds = sb.toString();
|
String mapIds = sb.toString();
|
||||||
String batchId = sheet.getM1BatchId() + "," + sheet.getM2BatchId() + "," + sheet.getM3BatchId();
|
String batchId = sheet.getM1BatchId() + "," + sheet.getM2BatchId() + "," + sheet.getM3BatchId();
|
||||||
|
|
||||||
// uid 기준 도엽별 shp, geojson 파일 생성
|
// shp 파일 비동기 생성
|
||||||
externalJarRunner.run(jarPath, batchId, inferenceId, mapIds);
|
shpPipelineService.runPipeline(jarPath, datasetDir, batchId, inferenceId, mapIds);
|
||||||
|
|
||||||
// uid 기준 merge shp, geojson 파일 생성
|
|
||||||
externalJarRunner.run(jarPath, batchId, inferenceId, "");
|
|
||||||
|
|
||||||
// uid 기준 도엽별 shp 파일 geoserver 등록
|
|
||||||
String register = datasetDir + "/" + inferenceId + "/" + "merge" + "/" + inferenceId + ".shp";
|
|
||||||
externalJarRunner.run(jarPath, register, inferenceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.kamco.cd.kamcoback.scheduler.service;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.service.ExternalJarRunner;
|
||||||
|
import com.kamco.cd.kamcoback.scheduler.config.ShpKeyLock;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ShpPipelineService {
|
||||||
|
|
||||||
|
private final ExternalJarRunner externalJarRunner;
|
||||||
|
private final ShpKeyLock shpKeyLock;
|
||||||
|
|
||||||
|
@Async("shpExecutor")
|
||||||
|
public void runPipeline(
|
||||||
|
String jarPath, String datasetDir, String batchId, String inferenceId, String mapIds) {
|
||||||
|
|
||||||
|
// inferenceId 기준 동시 실행 제한
|
||||||
|
if (!shpKeyLock.tryLock(inferenceId)) {
|
||||||
|
log.warn("SHP pipeline already running. inferenceId={}", inferenceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1 uid 기준 도엽별 shp, geojson 파일 생성
|
||||||
|
externalJarRunner.run(jarPath, batchId, inferenceId, mapIds);
|
||||||
|
|
||||||
|
// 2 uid 기준 merge shp, geojson 파일 생성
|
||||||
|
externalJarRunner.run(jarPath, batchId, inferenceId, "");
|
||||||
|
|
||||||
|
// 3 uid 기준 shp 파일 geoserver 등록
|
||||||
|
String register = datasetDir + "/" + inferenceId + "/merge/" + inferenceId + ".shp";
|
||||||
|
externalJarRunner.run(jarPath, register, inferenceId);
|
||||||
|
|
||||||
|
log.info("SHP pipeline finished. inferenceId={}", inferenceId);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("SHP pipeline failed. inferenceId={}", inferenceId, e);
|
||||||
|
// TODO 필요하면 실패 상태 업데이트 로직 추가
|
||||||
|
} finally {
|
||||||
|
shpKeyLock.unlock(inferenceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,11 +67,16 @@ springdoc:
|
|||||||
|
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
org:
|
root: INFO
|
||||||
springframework:
|
org.springframework.web: DEBUG
|
||||||
security: DEBUG
|
|
||||||
org.springframework.security: DEBUG
|
org.springframework.security: DEBUG
|
||||||
|
|
||||||
|
# 헬스체크 노이즈 핵심만 다운
|
||||||
|
org.springframework.security.web.FilterChainProxy: INFO
|
||||||
|
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||||
|
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||||
|
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||||
|
|
||||||
|
|
||||||
mapsheet:
|
mapsheet:
|
||||||
upload:
|
upload:
|
||||||
|
|||||||
@@ -36,12 +36,15 @@ spring:
|
|||||||
default_batch_fetch_size: 100
|
default_batch_fetch_size: 100
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
org:
|
|
||||||
springframework:
|
|
||||||
web: DEBUG
|
|
||||||
security: DEBUG
|
|
||||||
root: INFO
|
root: INFO
|
||||||
|
org.springframework.web: DEBUG
|
||||||
org.springframework.security: DEBUG
|
org.springframework.security: DEBUG
|
||||||
|
|
||||||
|
# 헬스체크 노이즈 핵심만 다운
|
||||||
|
org.springframework.security.web.FilterChainProxy: INFO
|
||||||
|
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
|
||||||
|
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
|
||||||
|
org.springframework.web.servlet.DispatcherServlet: INFO
|
||||||
# actuator
|
# actuator
|
||||||
management:
|
management:
|
||||||
health:
|
health:
|
||||||
|
|||||||
Reference in New Issue
Block a user