mym0404/react-native-naver-map

겹치는 마커 처리(클러스터)에 사용법에 대하여 질문

ParkJongJoon7128 opened this issue · 4 comments

크롤링한 데이터를 기반으로 지도맵에 마커를 뿌려 보여줄려고 하는데, 적은양의 데이터가 아니라서 마커가 많아 겹치는 부분이 많습니다.
때문에 이를 방지하고자 라이브러리에서 클러스터(cluster) 기능을 제공해주고 있어서 사용하고 있는데,
구현을 하고 앱을 실행해보니 클러스터 기능이 적용이 안되고, 마커만 적용되고 있어 질문합니다.

공식문서에서 사용된 예제에서는 클러스터가 사용되어 정상적으로 동작하는것으로 보이는데, 혹시 제 코드에서 미흡하게 구현한 부분이 있으면 조언을 받고 싶어 코드와 시연 영상과 함께 이슈 글을 남깁니다.

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-09-23.at.14.08.00.mp4

제가 구현중인 코드입니다.

import {
  Camera,
  ClusterMarkerProp,
  MapType,
  NaverMapMarkerOverlay,
  NaverMapView,
  NaverMapViewRef,
  Region,
} from '@mj-studio/react-native-naver-map';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { View } from 'react-native';
import { getRestaurants } from '../utils/API/LocationAPI';
import { Restaurant } from '../utils/data/type';

const Cameras = {
  KNU: {
    latitude: 37.271855,
    longitude: 127.127626,
    zoom: 14,
  },
} satisfies Record<string, Camera>;

const Regions = {
  KNU_Region: {
    latitude: 37.271855,
    longitude: 127.127626,
    latitudeDelta: 0.01,
    longitudeDelta: 0.01,
  },
} satisfies Record<string, Region>;

const MapTypes = [
  'Basic',
  'Navi',
  'Satellite',
  'Hybrid',
  'Terrain',
  'NaviHybrid',
  'None',
] satisfies MapType[];

const LocationMapScreen = () => {
  // Logic
  const ref = useRef<NaverMapViewRef>(null);
  const [restaurants, setRestaurants] = useState<Restaurant[]>([]);
  const [indoor, setIndoor] = useState(false);
  const [mapType, setMapType] = useState<MapType>(MapTypes[0]!);
  const [lightness, setLightness] = useState(0);
  const [compass, setCompass] = useState(true);
  const [scaleBar, setScaleBar] = useState(true);
  const [zoomControls, setZoomControls] = useState(true);
  const [indoorLevelPicker, setIndoorLevelPicker] = useState(true);
  const [myLocation, setMyLocation] = useState(true);
  const [hash, setHash] = useState(0);
  const [camera, setCamera] = useState(Cameras.KNU);

  const clusters = useMemo<
    {
      markers: ClusterMarkerProp[];
      screenDistance?: number;
      minZoom?: number;
      maxZoom?: number;
      animate?: boolean;
      width?: number;
      height?: number;
    }[]
  >(() => {
    return restaurants.map(i => {
      return {
        width: 200,
        height: 200,
        markers: restaurants.map<ClusterMarkerProp>(
          j =>
            ({
              image: {
                httpUri: `https://picsum.photos/seed/${hash}-${i}-${j}/32/32`,
              },
              width: 100,
              height: 100,
              latitude: Cameras.KNU.latitude + Math.random() + 1.5,
              longitude: Cameras.KNU.longitude + Math.random() + 1.5,
              identifier: `${hash}-${i}-${j}`,
            } satisfies ClusterMarkerProp),
        ),
      };
    });
  }, [hash]);

  useEffect(() => {
    getRestaurants(setRestaurants);
  }, []);

  // View
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: 'white',
      }}>
      <NaverMapView
        style={{flex: 1}}
        ref={ref}
        initialCamera={{
          latitude: 37.271855,
          longitude: 127.127626,
          zoom: 15,
        }}
        locale="ko"
        layerGroups={{
          BUILDING: true,
          BICYCLE: false,
          CADASTRAL: false,
          MOUNTAIN: false,
          TRAFFIC: false,
          TRANSIT: false,
        }}
        mapType={mapType}
        initialRegion={Regions.KNU_Region}
        camera={camera}
        isIndoorEnabled={indoor}
        isShowCompass={compass}
        isShowIndoorLevelPicker={indoorLevelPicker}
        isShowScaleBar={scaleBar}
        isShowZoomControls={zoomControls}
        isShowLocationButton={myLocation}
        lightness={lightness}
        clusters={clusters}
        onInitialized={() => console.log('initialized!')}
        onTapClusterLeaf={({markerIdentifier}) => {
          console.log('onTapClusterLeaf', markerIdentifier);
        }}>
        {restaurants.map((restaurant, index) => (
          <NaverMapMarkerOverlay
            key={restaurant.id}
            latitude={parseFloat(restaurant.y)}
            longitude={parseFloat(restaurant.x)}
            onTap={() => console.log(`Tapped on: ${restaurant.name}`)}
            anchor={{x: 0.5, y: 1}}
            width={20}
            height={20}>
            <View style={{width: 50, height: 50, backgroundColor: 'red'}} />
          </NaverMapMarkerOverlay>
        ))}
      </NaverMapView>
    </View>
  );
};

export default LocationMapScreen;

예제의 App.tsx의 코드를 그대로 쓰신 것으로 보입니다. clusters 변수가 hash가 변할 때만 변경되도록 설정되어 있으니 useMemo 를 지워보시면 될것같습니다.

현재 저는 서버로 요청을 보내 받아온 결과값을 restaurants 라는 state 변수에 저장하여 marker로 뿌려주고 있습니다(빨간 사각형이 응답값으로 내려온 데이터 좌표값들).

이 state 변수와 연관지어 clusters 를 보여줄순 없나요? 지도를 축소했을때는 겹치는 마커만큼 갯수를 clusters로 표현하고, 확대하면 clusters가 벗겨져 마커를 띄우게 하고 싶습니다.

현재 구현한 코드입니다.

import { formatJson, generateArray } from '@mj-studio/js-util';
import {
  Camera,
  ClusterMarkerProp,
  MapType,
  NaverMapMarkerOverlay,
  NaverMapView,
  NaverMapViewRef,
  Region,
} from '@mj-studio/react-native-naver-map';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { View } from 'react-native';
import { getRestaurants } from '../utils/API/LocationAPI';
import { Restaurant } from '../utils/data/type';

const Cameras = {
  KNU: {
    latitude: 37.271855,
    longitude: 127.127626,
    zoom: 14,
  },
} satisfies Record<string, Camera>;

const Regions = {
  KNU_Region: {
    latitude: 37.271855,
    longitude: 127.127626,
    latitudeDelta: 0.01,
    longitudeDelta: 0.01,
  },
} satisfies Record<string, Region>;

const MapTypes = [
  'Basic',
  'Navi',
  'Satellite',
  'Hybrid',
  'Terrain',
  'NaviHybrid',
  'None',
] satisfies MapType[];

const LocationMapScreen = () => {
  // Logic
  const ref = useRef<NaverMapViewRef>(null);
  const [restaurants, setRestaurants] = useState<Restaurant[]>([]);
  const [indoor, setIndoor] = useState(false);
  const [mapType, setMapType] = useState<MapType>(MapTypes[0]!);
  const [lightness, setLightness] = useState(0);
  const [compass, setCompass] = useState(true);
  const [scaleBar, setScaleBar] = useState(true);
  const [zoomControls, setZoomControls] = useState(true);
  const [indoorLevelPicker, setIndoorLevelPicker] = useState(true);
  const [myLocation, setMyLocation] = useState(true);
  const [hash, setHash] = useState(0);
  const [camera, setCamera] = useState(Cameras.KNU);

  const clusters = useMemo<
    {
      markers: ClusterMarkerProp[];
      screenDistance?: number;
      minZoom?: number;
      maxZoom?: number;
      animate?: boolean;
      width?: number;
      height?: number;
    }[]
  >(() => {
    return generateArray(5).map(i => {
      return {
        width: 200,
        height: 200,
        markers: generateArray(restaurants.length).map<ClusterMarkerProp>(
          j =>
            ({
              image: {
                httpUri: `https://picsum.photos/seed/${hash}-${i}-${j}/32/32`,
              },
              width: 100,
              height: 100,
              latitude: Cameras.KNU.latitude,
              longitude: Cameras.KNU.longitude,
              identifier: `${hash}-${i}-${j}`,
            } satisfies ClusterMarkerProp),
        ),
      };
    });
  }, [hash]);

  useEffect(() => {
    getRestaurants(setRestaurants);
  }, []);

  // View
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: 'white',
      }}>
      <NaverMapView
        style={{flex: 1}}
        ref={ref}
        initialCamera={{
          latitude: 37.271855,
          longitude: 127.127626,
          zoom: 15,
        }}
        locale="ko"
        layerGroups={{
          BUILDING: true,
          BICYCLE: false,
          CADASTRAL: false,
          MOUNTAIN: false,
          TRAFFIC: false,
          TRANSIT: false,
        }}
        mapType={mapType}
        initialRegion={Regions.KNU_Region}
        camera={camera}
        isIndoorEnabled={indoor}
        isShowCompass={compass}
        isShowIndoorLevelPicker={indoorLevelPicker}
        isShowScaleBar={scaleBar}
        isShowZoomControls={zoomControls}
        isShowLocationButton={myLocation}
        lightness={lightness}
        clusters={clusters}
        onInitialized={() => console.log('initialized!')}
        onTapClusterLeaf={({markerIdentifier}) => {
          console.log('onTapClusterLeaf', markerIdentifier);
        }}
        onTapMap={(args) => console.log(`Map Tapped: ${formatJson(args)}`)}
        >
        {restaurants.map((restaurant, index) => (
          <NaverMapMarkerOverlay
            key={restaurant.id}
            latitude={parseFloat(restaurant.y)}
            longitude={parseFloat(restaurant.x)}
            onTap={() => console.log(`Tapped on: ${restaurant.name}`)}
            anchor={{x: 0.5, y: 1}}
            width={20}
            height={20}>
            <View style={{width: 50, height: 50, backgroundColor: 'red'}} />
          </NaverMapMarkerOverlay>
        ))}
      </NaverMapView>
    </View>
  );
};

export default LocationMapScreen;

@mym0404 클러스터링 사용법에 대해서 궁금합니다. 예제 코드를 확인하고 구현해보려 하나 이해가 되지 않아 질문합니다.

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 2 days.