GIS (공간정보)

스프링 SHP 파일로 테이블 발행하기

keartt 2024. 9. 3. 17:52
반응형

__스프링에서 shp 파일을 테이블로 생성 by geotools __
shp to postgis(postgres) table by spring__


전자정부 + 자바 1.8 + 메이븐 기준

 

원하는 로직

  1. shp 파일들어있는 zip 파일들을 받는다
  2. 해당 shp 파일을 읽고 postgres (postgis) 테이블로 발행한다

pom.xml (egov 기준)

  1. geotools 가져오기 위해서는 pom.xml 에서 repositories 주소를 추가해줘야함
<properties>
        <spring.maven.artifact.version>4.2.4.RELEASE</spring.maven.artifact.version>
        <egovframework.rte.version>3.7.0</egovframework.rte.version>
        <geotools.version>20.5</geotools.version>
    </properties>

<repositories>
        <repository>
            <id>GeoSolutions</id>
            <url>https://maven.geo-solutions.it/</url>
        </repository>
        <repository>
            <id>GeoSolutions2</id>
            <url>https://repo.osgeo.org/repository/geotools-releases/</url>
        </repository>
        <repository>
            <id>mvn2</id>
            <url>http://repo1.maven.org/maven2/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>egovframe</id>
            <url>http://www.egovframe.go.kr/maven/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>egovframe2</id>
            <url>http://maven.egovframe.kr:8080/maven/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
  1. geotools 가져오기
 <dependency>
            <groupId>org.geotools</groupId>
            <artifactId>gt-main</artifactId>
            <version>${geotools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.geotools</groupId>
            <artifactId>gt-shapefile</artifactId>
            <version>${geotools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.geotools.jdbc</groupId>
            <artifactId>gt-jdbc-postgis</artifactId>
            <version>${geotools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.geotools</groupId>
            <artifactId>gt-epsg-hsql</artifactId>
            <version>${geotools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bgee.log4jdbc-log4j2</groupId>
            <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
            <version>1.16</version>
        </dependency>

 

주의

  1. jts 버전이 다르면 테이블만 생성되고 값이 안들어감
<dependency>
            <groupId>org.locationtech.jts</groupId>
            <artifactId>jts-core</artifactId>
            <version>1.16.0</version> <!-- 버전 바꾸면 안댐 -->
        </dependency>
  1. 자바 8 이하는 아래꺼 꼭 추가해줘야함
    (이거 없으면 작동안함 이거때매 개 삽질함, 자바 8 이상을 인식못하는 그 문제가 있었음)
<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

@Controller

    @Resource(name = "Geoservice")
    private GeoService service;

    @Description("SHP to DB ,DB to GeoLyr")
    @PostMapping("/uploadShp.do")
    @ResponseBody
    public List<String> uploadShp(MultipartHttpServletRequest multiRequest) throws IOException {
        return service.insertShpToGeo(multiRequest);
    }

 


@Service

파일 리스트들을 받고, 해당 zip 파일명으로 하나씩 테이블을 생성하고
성공, 실패 목록을 리턴

    @Override
    public List<String> insertShpToGeo(MultipartHttpServletRequest multiRequest) throws IOException {
        List<MultipartFile> fileList = multiRequest.getFiles("file");
        List<String> fail = new ArrayList<>();
        for (MultipartFile multipartFile : fileList) {
            String fileNm = FilenameUtils.removeExtension(multipartFile.getOriginalFilename());
            String successNm = createTableByShp(multipartFile, fileNm);
            if (successNm == null) {
                fail.add(multipartFile.getOriginalFilename());
            } 
        }
        if (fail.isEmpty()) fail.add("전부성공" + cnt);
        return fail;
    }

1. shp 로 테이블 생성

  • 좌표계는 5186 으로 변환해서 생성
    • 테이블의 좌표계가 있고 원하는 좌표계가 아닐경우에만 5186으로 변환
    • 테이블의 좌표계가 없을경우 그냥 5186 으로 생성
private String createTableByShp(MultipartFile zipFile, String fileNm) throws IOException {
        String result = fileNm;
        DataStore dataStore = null;
        ShapefileDataStore fileDataStore = null;
        Path tempDir = null;
        String epsg = "EPSG:5186"; // 목표좌표계

        Map<String, Object> params = getPostgisInfo(globalProperties);
        DefaultTransaction transaction = new DefaultTransaction("createTableTransaction");

        try {
            Map<String, Object> fileData = unZipShp(zipFile);
            tempDir = (Path) fileData.get("tempDir");
            Path shpFilePath = (Path) fileData.get("shpFilePath");

            dataStore = DataStoreFinder.getDataStore(params);
            if (dataStore == null) {
                throw new IOException("dataStore is null");
            }
            // 파일을 통해 데이터 스토어 만들고 인코딩 설정후 스키마 가져옴
            fileDataStore = new ShapefileDataStore(shpFilePath.toFile().toURI().toURL());
            fileDataStore.setCharset(detectCharset(shpFilePath));

            SimpleFeatureType featureSchema = fileDataStore.getFeatureSource().getSchema();
            // geomType 재반환

            // 가져온 스키마 기반으로 피쳐타입 생성하기
            SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
            builder.setName(fileNm);

            builder.setCRS(CRS.decode(epsg)); // 목표 좌표계 설정
            // 기존 속성 추가
            for (AttributeDescriptor attribute : featureSchema.getAttributeDescriptors()) {
                if (attribute instanceof GeometryDescriptor) {
                    GeometryDescriptor geomDesc = (GeometryDescriptor) attribute;
                    builder.add(geomDesc.getLocalName(), geomDesc.getType().getBinding());
                } else {
                    builder.add(attribute.getName().getLocalPart(), attribute.getType().getBinding());
                }
            }

            // 추가한 속성들로 새로운 스키마 생성하고 테이블 만들기
            SimpleFeatureType newSchema = builder.buildFeatureType();
            dataStore.createSchema(newSchema);
            // 만든 테이블의 피쳐 스토어 얻어오기
            SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore.getFeatureSource(fileNm);
            featureStore.setTransaction(transaction);

            // 좌표계 변환 및 피쳐 스토어에 값 insert
            CoordinateReferenceSystem targetCRS = CRS.decode(epsg, true);
            CoordinateReferenceSystem sourceCRS = featureSchema.getCoordinateReferenceSystem();
            String srs = CRS.toSRS(sourceCRS);

            boolean needTrans = false;
            if (sourceCRS != null) {
                needTrans = !srs.equals(epsg);
            }
            if (needTrans) {
                MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
                List<SimpleFeature> newFeatures = new ArrayList<>();
                try (SimpleFeatureIterator iter = fileDataStore.getFeatureSource().getFeatures().features()) {
                    while (iter.hasNext()) {
                        SimpleFeature feature = iter.next();
                        SimpleFeatureBuilder newBuilder = new SimpleFeatureBuilder(newSchema);
                        for (AttributeDescriptor descriptor : featureSchema.getAttributeDescriptors()) {
                            Object value = feature.getAttribute(descriptor.getName());
                            if (value instanceof Geometry) {
                                value = JTS.transform((Geometry) value, transform);
                            }
                            newBuilder.set(descriptor.getName().getLocalPart(), value);
                        }
                        SimpleFeature newFeature = newBuilder.buildFeature(null);
                        newFeatures.add(newFeature);
                    }
                }
                SimpleFeatureCollection collection = new ListFeatureCollection(newSchema, newFeatures);
                featureStore.addFeatures(collection);
            } else {
                featureStore.addFeatures(fileDataStore.getFeatureSource().getFeatures());
            }
            transaction.commit();

        } catch (FactoryException | TransformException | RuntimeException | IOException e) {
            System.out.println(e);
            result = null;
            transaction.rollback();
        } finally {
            transaction.close();
            dataStore.dispose();
            fileDataStore.dispose();
            deleteTemp(tempDir);
        }
        return result;
    }

2. postgis 연결 설정

    private Map<String, Object> getPostgisInfo(Properties properties) {
        // 아래는 본인의 상황에 맞게 설정하세요

        Map<String, Object> postgisInfo = new HashMap<>();
        postgisInfo.put("dbtype", "postgis");
        postgisInfo.put("host", "localhost");
        postgisInfo.put("port", 5432);
        postgisInfo.put("schema", "public");
        postgisInfo.put("database", "postgres");
        postgisInfo.put("user", "postgres");
        postgisInfo.put("passwd", "1234");

        return postgisInfo;
    }

3. 유틸

  1. 인코딩 UTF-8
private Charset detectCharset(Path shpFilePath) throws IOException {
        Path cpgFilePath = Paths.get(shpFilePath.toString().replace(".shp", ".cpg"));
        if (Files.exists(cpgFilePath)) {
            String charsetName = new String(Files.readAllBytes(cpgFilePath)).trim();
            return Charset.forName(charsetName);
        }
        // Default to UTF-8 if no .cpg file is found
        return Charset.forName("UTF-8");
    }
  1. zip 파일 압축해제 및 임시파일 삭제
private Map<String, Object> unZipShp(MultipartFile zipFile) throws IOException {
        Path tempPath = null;
        File convFile = null;
        try {
            tempPath = Files.createTempDirectory("shp-upload");

            convFile = File.createTempFile("temp", null);
            zipFile.transferTo(convFile);

            String shpFileName = null;
            Path shpFilePath = null;

            try (InputStream is = new FileInputStream(convFile);
                 //UTF-8
                 ArchiveInputStream ais = new ZipArchiveInputStream(is, "EUC-KR")) {

                ArchiveEntry entry;
                while ((entry = ais.getNextEntry()) != null) {
                    String entryName = entry.getName();
                    Path filePath = tempPath.resolve(entryName);
                    if (entry.isDirectory()) {
                        Files.createDirectories(filePath);
                    } else {
                        try (OutputStream os = Files.newOutputStream(filePath)) {
                            IOUtils.copy(ais, os);
                        }
                    }
                    if (entryName.toLowerCase().endsWith(".shp")) {
                        shpFileName = filePath.getFileName().toString();
                        shpFilePath = tempPath.resolve(shpFileName);
                    }
                }
            }

            Map<String, Object> result = new HashMap<>();
            result.put("tempDir", tempPath);
            result.put("shpFilePath", shpFilePath);
            result.put("shpFileName", shpFileName);

            return result;
        } catch (IOException | RuntimeException e) {
            deleteTemp(tempPath);
            throw e;
        } finally {
            if (convFile != null) {
                convFile.delete();
            }
        }
    }
    private void deleteTemp(Path tempDir) throws IOException {
        if (tempDir == null) {
            return;
        }
        // 디렉토리가 빈 디렉토리인지 확인
        try (Stream<Path> stream = Files.list(tempDir)) {
            if (stream.findFirst().isPresent()) {
                Files.walk(tempDir)
                        .sorted(Comparator.reverseOrder())
                        .map(Path::toFile)
                        .forEach(File::delete);
            }
        }
        Files.deleteIfExists(tempDir);
    }

 


참고
https://velog.io/@dailylifecoding/playing-around-with-geotools
https://spatiumwdev.tistory.com/16

반응형