간단하게 지도 클러스터링 기능 구현하기

이성준·2023년 1월 14일
0

프로젝트

목록 보기
4/5

문제

동네북 프로젝트는 지도기반 책 대여서비스이다.
그래서 만들때 지도관련 요구사항이 있었다.
첫번째는 지도가 띄어져있을때 지도 주변에 책을 팔고있는 사람이 마커로 찍혀져 있어야한다. 이고,
두번째는 마커들은 클러스터링 되어있어야한다. 이다.
일번은 어찌저찌하면되는데 이 두번째 요구사항이 문제였다. 그래서 팀원들과 클러스터링을 백 프론트 어디서 할지 상의를 했고 결론은 백엔드에서 하기로 했다. 프론트에서하면 마커가 수백개 있을때 마커를 생성하는 자바스크립트의 연산량 자체가 절대적으로 많기 때문에 느릴 수 밖에 없는데 백엔드에서 하면 서버의 높은 성능을 그대로 활용할 수 있어 더욱 유리하다.

어떻게

클러스터링을 했냐면 다방 서비스를 레퍼런스로 했다.

다방을 보면 내 생각에는 지도 중심을 바꾼다고 직사각형위치가 바뀌지않는다, 이로 봤을때 다방은 대한민국 지도 자체를 잘게 쪼개 고정된 섹터를 만들어 놓은것으로 추측된다. 그리고 그 섹터 범위 안에 포함된 마커 중 랜덤하게 하나를 선택하고 그 마커에는 범위안에 포함된 매물의 총 갯수를 보여준다. 이를 바탕으로 우리 프로젝트 입맛에 맞게 변형했다.
우리프로젝트에서는 고정된 섹터말고 지도 중심위치가 바뀔때 마다 바뀌는 유동적인 섹터를 만들었다. 왜냐하면 그게 더 구현하기 쉬울꺼라 판단했기때문이다.

구현

먼저 프론트에서 중심지의 위도 경도와 화면 전체의 가로 세로, 구역을 몇개를 나눌껀지를 파라미터로 담아서 요청한다.
예시를 들자면 http://localhost:8080/books/count?latitude=37&longitude=126&width=250&height=250&level=3

저렇게 요청이 왔으면 먼저 db에서 가로 세로 범위에 있는 모든 상인을 가져온다.

MemberQueryRepository

	public List<Location> getNearByMerchant(MerchantSearchRequest request) {
		this.latRange = Location.latRangeList(request.getLatitude(), request.getHeight(), request.getLevel());
		this.lonRange = Location.lonRangeList(request.getLongitude(), request.getWidth(), request.getLevel());

		return jpaQueryFactory.select(member.location)
			.from(member)
			.where((member.location.latitude.between(latRange.get(request.getLevel()), latRange.get(0))),
				(member.location.longitude.between(lonRange.get(0), lonRange.get(request.getLevel()))))
			.fetch();
	}
	}

범위에 해당하는 좌표를 계산하는 작업은 Location클래스에게 맡긴다. 범위에 해당하는 상인의 위치들을 받았으면 이제 이 위치들이 어느 섹터에 있는지 확인해보면서 응답값에 채워야한다.

MemberService

public List<MerchantSectorCountResponse> getSectorMerchantCounts(MerchantSearchRequest condition) {

		List<Location> sectorMerchantCounts = memberQueryRepository.getNearByMerchant(condition);

		Map<Integer, MerchantSectorCountResponse> collect = sectorMerchantCounts.stream()
			.flatMap(location ->
				IntStream.iterate(1, sector -> sector <= Math.pow(condition.getLevel(), 2), sector -> sector + 1)
					.filter(sector -> location.checkRange(condition,sector))
					.mapToObj(sector -> new MerchantSectorCountResponse(sector, location)))
			.collect(
				Collectors.toMap(MerchantSectorCountResponse::getSector,
					MerchantSectorCountResponse::increaseMerchantCount,
					(exist, newOne) -> exist.increaseMerchantCount()));

		return new ArrayList<>(collect.values());
	}

레벨이 3으로 들어왔으니까 3x3칸인 9만큼의 IntStream을 만들어준다 그리고 어느 섹터에 있는지 필터링하면서 그 섹터에 맞게 새로운 dto를 만든다 그리고 이 이중스트림을 flatMap으로 평탄화 시키고 collecters.toMap으로 섹터마다 갯수를 채워가면서 map을 만든다. 그리고 이걸 다시 ArrayList로 반환하면 우리가 의도한대로 응답을 받을 수 있다.

0개의 댓글