본문 바로가기
개발/프로젝트-식당 웨이팅 앱 FOOD LINE

230623 실전프로젝트21 [Nest.js] 카카오맵 API에서 좌표 크롤링하기

by 코딩하는짱구 2023. 6. 23.
반응형

✅오늘 학습  Keyword

2023.06.01 - [실전프로젝트] - 230601 실전프로젝트6 [Node.js/Nest.js 공공 data저장, 좌표주입하기]

 

230601 실전프로젝트6 [Node.js/Nest.js 공공 data저장, 좌표주입하기]

✅오늘학습 Keyword 1. postgreSQL-RDS 연결 2. 대용량 csv data 저장 ✅local data와 카카오맵API 연동하기 - 겪은 문제 1. 저번 포스팅에서 기록했듯 대용량 data의 좌표를 부여하는 작업에서, 쿼터 문제가 발

veritas-crystal.tistory.com

 

 

 

✅오늘 겪은 문제

1. 프로젝트에 쓰일 최종적인 data인 csv파일에 카카오좌표를 크롤링하고 있었다. 

이 과정에서 지번주소, 도로명주소가 둘 다 카카오맵 api에 등록되지 않은 경우 좌표를 불러올 수 없는 경우가 20만건이나 발생했다. 20만건의 공공데이터를 그대로 손실하는 것은 매우 큰 손해라 생각하여 해결 방법을 생각해봤다. 좌표를 불러오지 못한 상점들의 주소는 ~~번지, 101, 102호, 지상1층 등 카카오맵 api에 정식으로 등록되있지 않은 경우가 많았고 그런 경우에는 상점의 이름으로 찾은 후 해당 좌표를 받아오는 것으로 코드를 수정했다. 코드를 수정하니 좌표들이 잘 불러와졌다~~~~이로써 더 많은 데이터를 보유할 수 있게 됌!

 

#repository

async getCoordinate(storeNmae: string, address: string): Promise<any> {
    try {
      if (!address && !storeNmae) {
        return null;
      }

      const query = address ? address : storeNmae;
      const encodedQuery = encodeURIComponent(query);

      const url = `https://dapi.kakao.com/v2/local/search/address.json?query=${encodedQuery}`;
      // const restApiKey = '800b8fe2427efbffbef3bc6fe96a5464';
      const restApiKey = `${process.env.KAKAO_REST_API_KEY}`;
      const headers = { Authorization: 'KakaoAK ' + restApiKey };

      const response = await axios.get(url, { headers });

      const result = response.data;

      if (result.documents.length !== 0) {
        const resultAddress = result.documents[0].address;
        const coordinates = [resultAddress.y, resultAddress.x];

        return coordinates;
      } else {
        return null;
      }
    } catch (error) {
      throw new Error(
        'Error fetching coordinates from Kakao API: ' + error.message,
      );
    }
  }
  //저장
  async updateCoord(lat: number, lon: number, storeId: number): Promise<any> {
    await this.stores.update(storeId, { lat, lon });
  }

 

#service

 async updateCoordinates(): Promise<void> {
    try {
      const stores = await this.storesRepository.getStoreAddressId();

      for (const store of stores) {
        const { newAddress, storeName, oldAddress, storeId } = store;

        try {
          let coordinates = await this.storesRepository.getCoordinate(
            null,
            newAddress,
          );

          if (!coordinates) {
            coordinates = await this.storesRepository.getCoordinate(
              null,
              oldAddress,
            );
          }
          if (!coordinates) {
            coordinates = await this.storesRepository.getCoordinate(
              storeName,
              null,
            );
          }

          if (!coordinates) continue;

          //La = y, Ma = x
          const lat = coordinates[0];
          const lon = coordinates[1];

          await this.storesRepository.updateCoord(lat, lon, storeId);

          console.log(
            `Updated coordinates for address: ${storeId},${storeName},${newAddress}${lat}, ${lon}`,
          );
        } catch (error) {
          console.error(
            `Error updating coordinates for address: ${storeId},${storeName},${newAddress}, ${oldAddress}`,
            error,
          );
        }
      }
    } catch (error) {
      console.error('Error occurred during database operation:', error);
    }
  }

 

2. 사용자 위치를 기반으로 주변 식당을 탐색하는 api에서 식당 data에 rating, currentwaiting 까지 잘 추가 되었다.

하지만 sort된 결과가 빈 배열로 출력되는 에러가 있었다. 

 //주변식당탐색
  async searchRestaurants(
    southWestLatitude: number,
    southWestLongitude: number,
    northEastLatitude: number,
    northEastLongitude: number,
    sortBy?: 'distance' | 'name' | 'waitingCnt' | 'waitingCnt2' | 'rating',
  ): Promise<{ 근처식당목록: searchRestaurantsDto[] }> {
    const restaurants = await this.storesRepository.findAll();

    const restaurantsWithinRadius = restaurants.filter((restaurant) => {
      const withinLatitudeRange =
        Number(restaurant.lat) >= southWestLatitude &&
        Number(restaurant.lat) <= northEastLatitude;
      const withinLongitudeRange =
        Number(restaurant.lon) >= southWestLongitude &&
        Number(restaurant.lon) <= northEastLongitude;

      return withinLatitudeRange && withinLongitudeRange;
    });
    console.log(restaurantsWithinRadius);
    // 거리 계산 로직
    const calculateDistance = (
      source: { latitude: number; longitude: number },
      target: { latitude: number; longitude: number },
    ): number => {
      const latDiff = Math.abs(source.latitude - target.latitude);
      const lngDiff = Math.abs(source.longitude - target.longitude);
      const approximateDistance = Math.floor(
        latDiff * 111000 + lngDiff * 111000,
      );
      return approximateDistance;
    };

    //user위치에 따른 거리값을 모든 sort조건에 포함시켜준다
    //calculateDistance로 얻은 distance 값을 출력값에 포함시켜준다
    const userLocation = {
      latitude: southWestLatitude,
      longitude: southWestLongitude,
    };

    const restaurantsResult: searchRestaurantsDto[] = [];

    restaurantsWithinRadius.forEach(async (restaurant) => {
      const distance = calculateDistance(userLocation, {
        latitude: Number(restaurant.lat),
        longitude: Number(restaurant.lon),
      });

      const storesHashes = await this.redisClient.hgetall(
        `store:${restaurant.storeId}`,
      );

      let currentWaitingCnt: string;
      let rating: string;

      if (!storesHashes.currentWaitingCnt) {
        currentWaitingCnt = '0';
        rating = '0';
      }

      restaurantsResult.push({
        ...restaurant,
        distance: distance,
        currentWaitingCnt: Number(currentWaitingCnt),
        rating: Number(rating),
      });
      console.log(
        restaurantsResult,
        'restaurants에 currentwaitingCnt, rating 적용하여 출력 준비됌',
      );
    });

    //정렬로직모음
    restaurantsResult.sort((a, b) => {
      if (sortBy === 'distance') {
        return (a.distance || 0) - (b.distance || 0);
      } else if (sortBy === 'name') {
        return a.storeName.toUpperCase() < b.storeName.toUpperCase() ? -1 : 1;
      } else if (sortBy === 'waitingCnt') {
        return a.currentWaitingCnt - b.currentWaitingCnt;
      } else if (sortBy === 'waitingCnt2') {
        return b.currentWaitingCnt - a.currentWaitingCnt;
      }
      return 0;
    });
    console.log(restaurantsResult);
    return { 근처식당목록: restaurantsResult };
  }

위 코드에서 restaurantsResult.push 작업을 거친 restaurantsResult까지는 콘솔에 잘 찍히는 것을 확인했는데, 

콘솔에 찍히는 data가 무한대로 반복되어 찍히고 있었다. 이 부분에서 비동기 문제와 관련있지 않을까 추측함. 

 

 

 

 

 

 

✅오늘 알게된 점 및 추후 학습 계획

잠결에 이 문제를 생각하다가 불현듯 스친 생각이라 진짜 눈 뜨자마자 코드짰다. 

뭔가 생각나면 바로바로 적용해봐야겠다.

반응형