Back-End/work

geojson을 스프링을 통해서 MySQL에 넣어보자

Meluu_ 2025. 10. 1. 19:33

https://github.com/vuski/admdongkor

 

GitHub - vuski/admdongkor: 대한민국 행정동 경계 파일

대한민국 행정동 경계 파일. Contribute to vuski/admdongkor development by creating an account on GitHub.

github.com

우리나라 행정동 경계는 이미 다른 분이 만드셔서 이를 활용하였다.

 

json 파일을 열어서 분석해보자 

제일 위의 사직동을 예시로 보겠다.

{
  "type": "FeatureCollection",
  "name": "20250401",
  "crs": {
    "type": "name",
    "properties": {
      "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
    }
  },
  "features": [
    {
      "type": "Feature",
      "properties": {
        "adm_nm": "서울특별시 종로구 사직동",
        "adm_cd2": "1111053000",
        "sgg": "11110",
        "sido": "11",
        "sidonm": "서울특별시",
        "sggnm": "종로구",
        "adm_cd": "11010530"
      },
      "geometry": {
        "type": "MultiPolygon",
        "coordinates": [
          [
            [
              [126.976888842748167, 37.575650779448786],
              [126.977034498877501, 37.569194530054553],
              [126.975974728212492, 37.569336299425771],
              [126.975374709912543, 37.569315567021562],
              [126.974331935623255, 37.569261800517531],
              [126.969048370018541, 37.568194417708327],
              [126.968544936033837, 37.568427679612753],
              [126.966649959821197, 37.569491655206583],
              [126.966281750244846, 37.569700734798694],
              [126.966097327080405, 37.569856509723706],
              [126.965728529225771, 37.570183936115114],
              [126.965926998221278, 37.570318805686206],
              [126.96601094018429, 37.571548395577473],
              [126.963659220521961, 37.575174660660373],
              [126.963086004345101, 37.576485920015543],
              [126.962840990511978, 37.57666158609274],
              [126.962810410472628, 37.579448809656768],
              [126.967424315843317, 37.579601537124489],
              [126.967421763026508, 37.579263521441646],
              [126.967430060184597, 37.579192577998604],
              [126.967457090095607, 37.578975250585437],
              [126.968066046996256, 37.578246780467879],
              [126.968955116954774, 37.577935262340283],
              [126.969212842969057, 37.577935299309388],
              [126.969414538865792, 37.578121124142172],
              [126.969664426694706, 37.578531136682216],
              [126.969667219148718, 37.578736205134931],
              [126.969668773533087, 37.578992879009881],
              [126.969669499103631, 37.57911252674959],
              [126.969904573616262, 37.579301753628727],
              [126.97135197544759, 37.57951327793981],
              [126.973819257844539, 37.579372140302624],
              [126.973917363383421, 37.578487073041011],
              [126.973939619980882, 37.578240429978088],
              [126.974331538357575, 37.575749906299862],
              [126.975803789978045, 37.57564946882421],
              [126.976888842748167, 37.575650779448786]
            ]
          ]
        ]
      }
    }
  ]
}

features 에 우리가 원하는 행정동의 데이터와 경계 좌표 데이터가 있음을 알 수 있다.

다만 적위도 좌표이며, WKT으로 변환해서 저장할 필요가 있다는 것을 인지하면 된다. 

 

클래스로 바로 매핑할 것이기에 클래스를 짜준다.

public class FeatureCollection {
    private String type;
    private String name;
    private Crs crs;
    private List<Feature> features;
}
public class Crs {
    private String type;
    private Properties properties;
}
public class Feature {
    private String type;
    private Geometry geometry;
    private FeatureProperties properties;
}
public class Properties {
    private String name;
}
public class FeatureProperties {
    @JsonProperty("adm_nm")
    private String admNm;

    @JsonProperty("adm_cd2")
    private String admCd2;

    @JsonProperty("sgg")
    private String sgg;

    @JsonProperty("sido")
    private String sido;

    @JsonProperty("sidonm")
    private String sidonm;

    @JsonProperty("sggnm")
    private String sggnm;

    @JsonProperty("adm_cd")
    private String admCd;
}

 

경계

public class Geometry {
    private String type;
    private List<List<List<List<Double>>>> coordinates;
}

 

이렇게 클래스를 짤 수 있다.

 

 

mysql에 저장하는 로직 작성

필자는 json 파싱으로 objectMapper를 사용한다. 

File file = new File("C:\\Users\\User\\Downloads\\HangJeongDong_ver20250401.geojson");

ObjectMapper mapper = new ObjectMapper();

FeatureCollection collection = mapper.readValue(file, FeatureCollection.class);
String sql = "insert into region_dong values(null, :adm_nm, :adm_cd, :adm_cd2, :sgg, :sido, :sidonm, :sggnm, :dongnm, ST_GeomFromText(:geom, 4326))";

파일을 읽고 objectmapper로 위에서 만든 클래스로 매핑한다음

sql을 짜준다.

 

여기서 중요한건 클래스로 매핑받은 coordinates의 경우 WKT로 변환해서 넣어줘야한다.

또한 SRID를 WGS84(4326)으로 맞춰준다. 

 

for문을 돌면서 각 지역의 feature를 돌면서 insert 쿼리를 날린다. 

for (Feature feature : collection.getFeatures()) {
    FeatureProperties properties = feature.getProperties();
    Geometry geometry = feature.getGeometry();
    String[] split = properties.getAdmNm().split(" ");

    List<List<Double>> list = geometry.getCoordinates().get(0).get(0);
    MapSqlParameterSource param = new MapSqlParameterSource();

    param.addValue("adm_nm", properties.getAdmNm());
    param.addValue("adm_cd", properties.getAdmCd());
    param.addValue("adm_cd2", properties.getAdmCd2());
    param.addValue("sgg", properties.getSgg());
    param.addValue("sido", properties.getSido());
    param.addValue("sidonm", properties.getSidonm());
    param.addValue("sggnm", properties.getSggnm());
    param.addValue("dongnm", split[split.length - 1]);
	
    // POLYGON으로 변경, 위도-적도 순으로 저장
    String wkt = "POLYGON((" +
            list.stream().map(x -> x.get(1) + " " + x.get(0))
                            .collect(Collectors.joining(", ")) +
            "))";

    param.addValue("geom", wkt);

    batchParams.add(param);
}

// 배치 insert
template.batchUpdate(sql, batchParams.toArray(new SqlParameterSource[0]));

 

 

 

잘 저장됨을 확인할 수 있다.

 

 

공간데이터 쿼리 날려보기

SELECT dongnm '행정동 이름', 
	ST_Area(geom) '면적' ,
	ST_AsText(geom) 'WKT' 
FROM danggeun.region_dong;

 

 

 

피드백

JsonNode node = mapper.readTree(file);
JsonNode fe = node.get("features");

Feature[] features = mapper.readValue(fe.toString(), Feature[].class);

굳이 모든 클래스를 만들 필요없이 이렇게 특정 key만 읽고 해당 클래스만 만들면된다.