ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Trouble Shooting] TMap Api 요청 도중 Api 키가 정지된 문제
    SSAFY(프로젝트)/1) relpl - Trouble Shooting 2024. 2. 12. 16:52

    0. 도로 데이터를 넣는 법

    도로 데이터를 어떻게 DB에 쌓을 수 있을까?를 고민하기 위해 여러 API를 찾던 도중

    TMap Api에 가까운 도로 찾기 Api를 발견하게 되었다.

    https://tmapapi.sktelecom.com/main.html#webservice/sample/WebSampleNearToRoad

    해당 Api를 간략히 설명하면, 지도의 한 점을 찍으면, 해당 점에서 가장 가까운 도로 데이터를 반환해주는 Api이다.

    {
        "resultData": {
            "header": {
                "laneType": 1,
                "tollLink": 0,
                "speed": 50,
                "roadName": "인동가산로",
                "oneway": 0,
                "roadCategory": 4,
                "tlinkId": "4161402",
                "linkId": "1317",
                "linkFacil": 0,
                "idxName": "65460000",
                "totalDistance": 184,
                "lane": 4
            },
            "linkPoints": [
                {
                    "location": {
                        "latitude": 36.10729809,
                        "longitude": 128.41845224
                    }
                },
                {
                    "location": {
                        "latitude": 36.10605103,
                        "longitude": 128.41959661
                    }
                },
                {
                    "location": {
                        "latitude": 36.10597049,
                        "longitude": 128.41967161
                    }
                }
            ]
        }
    }

    위와 같은 데이터가 나온다.


    그렇다면, 우리가 서비스를 하고 싶은 지역 정도만 크기를 줄이고 해당 지역을 사각형으로 자른다면?2중For문으로 해당 지역에 있는 모든 도로  데이터를 얻을 수 있다는 결론이 나왔다.(물론 중복을 제거해야 하고, 원하는 데이터만 추출해야 한다.)

    위와 같은 방식으로 api를 요청하면 되겠다고 결론이 나고 시도했지만, 난관에 봉착했다.


    1. 문제 상황

    이후 다른 팀원이 해당 Api를 요청하는 코드를 스프링에 자바 코드로 작성해주었지만

    문제는 TMap에 Api를 그냥 2중 For문으로 요청하다보니 Api key가 정지되어 버리는 상황이었다.

    (아마 디도스 공격이라 판단하고 정지시킨 것 같고, 이후 팀 내에서도 항상 장난식으로 디도스 공격이라고 얘기했다)


    2. 고민해본 내용

    다른 방법이 있었겠지만 가장 최근에 코틀린 수업을 들었고 그래서 가장 먼저 떠오른건 코루틴을 이용해 적절히 대기시간을 추가한 비동기적인 코드 처리였다.

    이렇게 한다면 TMap 입장에서도 정상적인 Api 요청으로 받아들이지 않을까..? 라는 생각이 들어

    Api를 요청하고 DB에 저장하는 코드를 자바에서 코루틴을 이용한 코틀린 코드로 변환하는 작업을 하게 되었다.


    3. 해결 방안

    그래서 어쩌다보니 코틀린 스프링을 찍어먹어보게 되는 계기가 되었다.

    물론 코틀린스러운 코드냐고 물어본다면 단언코 아니라고 말하겠지만.. 그래도 꽤나 신선한 경험이었다

     

    사실 데이터를 사용하려는 용도에 맞게 가공하느라 코드의 양은 좀 많지만,

    TMap api를 이용해 데이터를 얻고, 해당 도로의 시작점, 끝점, 도로 정보 등을 DB에 저장하는 코드 정도로만 이해하면 될 것 같다.


    controller

        @PostMapping("/insertTmapData")
        suspend fun insertTmapData(@RequestBody insertRoadRequestDto: InsertRoadRequestDto
        ): ResponseEntity<Any?> {
    
            log.info("StartPoint: ${insertRoadRequestDto.startPoint}, EndPoint: ${insertRoadRequestDto.endPoint}")
            if (insertRoadRequestDto.callApiPassword != apiPassword) return ResponseEntity.badRequest().body("패스워드 불일치")
            try {
                coroutineScope {
                    launch {
                        tmapService.getAllRoads(BigDecimal(insertRoadRequestDto.startPoint.y)
                                , BigDecimal(insertRoadRequestDto.startPoint.x)
                                , BigDecimal(insertRoadRequestDto.endPoint.y)
                                , BigDecimal(insertRoadRequestDto.endPoint.x))
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
                return ResponseEntity.internalServerError().body("${e.message}")
            }
            return ResponseEntity.ok().body(CommonResponse.OK("Tmap 데이터 저장 완료", true))
        }

    service

    suspend fun getAllRoads(startLat: BigDecimal, startLng: BigDecimal, endLat: BigDecimal, endLng: BigDecimal) {
    
            val roadSet = mutableSetOf<Long>()
            val pointHashMap = mutableMapOf<Point, Long>()
    
            var hashVal = 0L
    
            var lat = startLat
            var count = 0
            var holeCnt = ((startLat - endLat).div(BigDecimal(0.00005)) * ((endLng - startLng).div(BigDecimal(0.00005)))).toInt() + 2
            var i = 0
            var roadHashIndex = 0L
            var apiKey = getkeys().get(i++)
            coroutineScope {
                launch {
                    while (lat >= endLat) {
                        lat = lat.minus(BigDecimal(0.00005))
                        var lng = startLng;
                        while (lng <= endLng) {
                            lng = lng.plus(BigDecimal(0.00005))
                            try {
                                ++count
                                if (count % 20000 == 0) {
                                    count %= 20000
                                    apiKey = getkeys().get(i++)
                                }
                                val responseData = callTmapApi(lat.toDouble(), lng.toDouble(), apiKey)
                                val objectMapper = ObjectMapper()
                                val responseDTO: TmapApiResponseDTO = objectMapper.readValue(responseData, TmapApiResponseDTO::class.java)
                                log.info("count: ${--holeCnt}")
                                responseDTO.resultData.let {
                                    if (!roadSet.contains(responseDTO.resultData.header.linkId)) {
    
                                        roadSet.add(responseDTO.resultData.header.linkId)
                                        log.info(" lat: $lat, lng: $lng, API Call: $responseDTO\"")
                                        val tmapRoad = TmapRoad.createRoad(responseDTO, hashVal);
                                        insertTmapRoad(tmapRoad)
    
                                        val start = tmapRoad.geometry.coordinates.first()
                                        val startPoint = geometryFactory.createPoint(Coordinate(start.x, start.y))
                                        var startPointHash = -1L
    
                                        val end = tmapRoad.geometry.coordinates.last()
                                        val endPoint = geometryFactory.createPoint(Coordinate(end.x, end.y))
                                        var endPointHash = -1L
    
                                        if (!pointHashMap.contains(startPoint)) {
                                            startPointHash = roadHashIndex
                                            val pointHashEntity = PointHash.createPointHash(roadHashIndex, startPoint)
                                            insertPointHash(pointHashEntity)
                                            pointHashMap.put(startPoint, roadHashIndex++)
                                        } else {
                                            startPointHash = pointHashMap.get(startPoint)!!
                                        }
    
                                        if (!pointHashMap.contains(endPoint)) {
                                            endPointHash = roadHashIndex
                                            insertPointHash(PointHash.createPointHash(roadHashIndex, endPoint))
                                            pointHashMap.put(endPoint, roadHashIndex++)
                                        } else {
                                            endPointHash = pointHashMap.get(endPoint)!!
                                        }
                                        
                                        val roadInfo = RoadInfo.createRoadInfo(tmapRoad.tmapId, startPointHash, endPointHash, tmapRoad.totalDistance)
                                        insertRoadInfo(roadInfo)
                                    }
                                }
                            } catch (e: Exception) {
                                e.printStackTrace()
                                log.info(e.message)
                            }
                        }
    
                    }
                }
            }
        }

    4. 느낀점

    사실 해보고 가장 크게 느낀점은 생각보다 코틀린 스프링이 할만 하다는 점 이었다.그리고 결국에는 해당 코드는 DB에 데이터를 저장하는 용도만 있으므로기존 스프링에 있는 Api 관련 코드와 성격이 다르다 느껴 기존 프로젝트를 두고 따로 프로젝트를 새로 만들어 작성했지만,빼기 전에도 자바와 코틀린 코드가 섞여있는데도 코드가 잘 동작한다는 점이 신기했다.

     

    그리고 코루틴을 이용해 코드를 작성하니, 다행히도 TMap Api key가 정상적으로 계속 사용할 수 있게 되었고, 디비에도 값이 잘 들어가게 되었다.그래서 수집한 데이터의 수는 아래와 같다.


    도로의 수(PostgreSQL) : 686개


    도로에서 중복을 제거한 시작점, 끝 점의 수(PostgreSQL) : 511개


    도로들의 모든 점의 수 (MongoDB) : 2358개


    위와 같은 결과가 나오게 되었다.

    PostgreSQL에는 간략한 도로 데이터(시작점, 끝 점, 도로 길이)를 담았고

    MongoDB에는 진짜 도로 데이터(시작점, 점1, 점2, .... , 끝 점)을 넣었다.

     

    우리 프로젝트에서 가장 중요한 데이터를 쌓기 위한 고민들이었고, 다행히 잘 들어가서 좋았다.

    반응형

    댓글

Designed by Tistory.