개발일지

react-native-nmap 기능 추가하기 본문

개발일지/React-Native

react-native-nmap 기능 추가하기

Seobe95 2022. 4. 26. 01:54
 

GitHub - QuadFlask/react-native-naver-map: 🗺️naver map for react-native

🗺️naver map for react-native. Contribute to QuadFlask/react-native-naver-map development by creating an account on GitHub.

github.com

QuadFlask님이 만드신 라이브러리,, 항상 감사하게 사용하고 있습니다ㅠㅠ

 

 

프로젝트를 진행하는 도중, 네이버 지도를 사용하게 되었다. 설치부터 우여곡절이 많았는데, 설치 및 구현 관련해서는 나중에 정리해서 포스팅하는걸로,,

 

각설하고, 내가 필요했던 기능은 다음과 같다. 1) 픽셀 당 미터 수 구하기, 2) 안드로이드에서 서브캡션이 나타나지 않는 문제 해결하기

이렇게 2개를 구현해야 했다.

 

1. 픽셀 당 미터 수 구하기

 

카메라와 투영 · 네이버 지도 안드로이드 SDK

카메라와 투영 네이버 지도 SDK는 기기 화면 건너편의 지도를 카메라로 바라보는 방식으로 지도를 표현합니다. 카메라를 이동, 확대 및 축소, 기울임, 회전시킴으로써 화면에 보이는 지도를 자유

navermaps.github.io

네이버 지도의 Android와 iOS 공식 문서를 읽으며 축척에 관한 부분을 읽고, metersPerPixel 메소드가 있는 것을 발견하여 Native코드를 조금 손봐주었다.

 

// node_modules/react-native-nmap/android/src/main/java/com/github/quadflask/react/navermap/RNNaverMapView.java

package com.github.quadflask.react.navermap;

import android.graphics.PointF;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;

import com.airbnb.android.react.maps.ViewAttacherGroup;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.naver.maps.geometry.LatLng;
import com.naver.maps.geometry.LatLngBounds;
import com.naver.maps.map.*;
import com.naver.maps.map.util.FusedLocationSource;

import java.util.ArrayList;
import java.util.List;

public class RNNaverMapView extends MapView implements OnMapReadyCallback, NaverMap.OnCameraIdleListener, NaverMap.OnMapClickListener, RNNaverMapViewProps {
    private ThemedReactContext themedReactContext;
    private FusedLocationSource locationSource;
    private NaverMap naverMap;
    private ViewAttacherGroup attacherGroup;
    private long lastTouch = 0;
    private final List<RNNaverMapFeature<?>> features = new ArrayList<>();
    
    ...
    
        // 픽셀당 미터 구하기
   @Override
    public void onCameraIdle() {
        Projection projection = naverMap.getProjection();
        // 1px 당 미터 구하기
        CameraPosition cameraPosition = naverMap.getCameraPosition();
        double metersPerPixel = projection.getMetersPerPixel(cameraPosition.target.latitude, cameraPosition.zoom);

        WritableMap param = Arguments.createMap();
        param.putDouble("latitude", cameraPosition.target.latitude);
        param.putDouble("longitude", cameraPosition.target.longitude);
        param.putDouble("zoom", cameraPosition.zoom);
        // 1px 당 미터 구하기
        param.putDouble("metersPerPixel", metersPerPixel);
        param.putArray("contentRegion", ReactUtil.toWritableArray(naverMap.getContentRegion()));
        param.putArray("coveringRegion", ReactUtil.toWritableArray(naverMap.getCoveringRegion()));

        emitEvent("onCameraChange", param);
    }
    ...
 }

기존에 구현되어 있는 onCameraIdle 함수에 공식문서에서 확인한 코드들을 넣어 pixel 당 미터수를 반환할 수 있도록 하였다.

 

iOS에서도 마찬가지로, 공식문서를 참조하여 코드를 추가하였다.

//
//  RNNaverMapView.m
//
//  Created by flask on 18/04/2019.
//  Copyright © 2019 flask. All rights reserved.
//

#import "RNNaverMapView.h"
#import <UIKit/UIKit.h>
#import <React/RCTComponent.h>
#import <React/RCTBridge.h>
#import <React/UIView+React.h>

...

- (void)mapViewIdle:(nonnull NMFMapView *)mapView {
	// 픽셀 당 미터 수 구하기
    NMFProjection *projection = mapView.projection;
    CLLocationDistance metersPerPixel = [projection metersPerPixelAtLatitude:round(mapView.cameraPosition.target.lat) zoom: mapView.cameraPosition.zoom];
  if (((RNNaverMapView*)self).onCameraChange != nil)
    ((RNNaverMapView*)self).onCameraChange(@{
      @"latitude"      : @(mapView.cameraPosition.target.lat),
      @"longitude"     : @(mapView.cameraPosition.target.lng),
      @"zoom"          : @(mapView.cameraPosition.zoom),
      @"metersPerPixel": @(metersPerPixel),
      @"contentRegion" : pointsToJson(mapView.contentRegion.exteriorRing.points),
      @"coveringRegion": pointsToJson(mapView.coveringRegion.exteriorRing.points),
    });
}


...

2. 안드로이드 subCaption 설정하기

지도기능을 사용하는 도중 마커의 subCaption을 사용해야 했는데, iOS의 경우에는 subCaption이 잘 작동되었지만, Android의 경우에는 작동이 되지 않았다.

 

이 또한 네이버 지도 안드로이드 공식문서를 확인하여 코드를 수정하였다.

// RNNaverMapMarker.java

...

public class RNNaverMapMarker extends ClickableRNNaverMapFeature<Marker> implements TrackableView {
    private final DraweeHolder<GenericDraweeHierarchy> imageHolder;
    private boolean animated = false;
    private int duration = 500;
    private TimeInterpolator easingFunction;
    
    ...
    
    
    public void setCaption(String text, int textSize, int color, int haloColor, Align... aligns) {
        feature.setCaptionText(text);
        feature.setCaptionTextSize(textSize);
        feature.setCaptionColor(color);
        feature.setCaptionHaloColor(haloColor);
        feature.setCaptionAligns(aligns);
    }

    public void removeCaption() {
        feature.setCaptionText("");
    }
    
    // 서브캡션 생성
    public void setSubCaption(String text, int textSize, int color, int haloColor) {
        feature.setSubCaptionText(text);
        feature.setSubCaptionTextSize(textSize);
        feature.setSubCaptionColor(color);
        feature.setSubCaptionHaloColor(haloColor);
    }

    // 서브캡션 지우기
    public void removeSubCaption() {
        feature.setSubCaptionText("");
    }
    
    ...
  
  }

setCaption의 양식과, 공식문서의 지원 범위를 확인하여 subCaption을 설정하고 사라질 수 있도록 코드를 추가했다.

 

또한, react-native에서 넘어오는 설정값들로 subCaption을 설정해야 하기 때문에, RNNaverMapMarkerManager.java 파일도 수정해준다.

...

public class RNNaverMapMarkerManager extends EventEmittableViewGroupManager<RNNaverMapMarker> {
    private static final Align DEFAULT_CAPTION_ALIGN = Align.Bottom;

    private final DisplayMetrics metrics;

    public RNNaverMapMarkerManager(ReactApplicationContext reactContext) {
        super(reactContext);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            metrics = new DisplayMetrics();
            ((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay()
                    .getRealMetrics(metrics);
        } else {
            metrics = reactContext.getResources().getDisplayMetrics();
        }
    }
    
    ...
    
    // Javascript에서 넘어오는 subCaption 설정 값 연동
    @ReactProp(name = "subCaption")
    public void setSubCaption(RNNaverMapMarker view, ReadableMap map) {
        if (map == null || !map.hasKey("text")) {
            view.removeSubCaption();
            return;
        }

        // 네이버 맵 안드로이드 SDK(https://navermaps.github.io/android-map-sdk/guide-ko/5-2.html) 기준 서브캡션 설정 값을 기준으로 작성
        String text = map.getString("text");
        int textSize = map.hasKey("textSize") ? map.getInt("textSize") : 16;
        int color = map.hasKey("color") ? parseColorString(map.getString("color")) : Color.BLACK;
        int haloColor = map.hasKey("haloColor") ? parseColorString(map.getString("haloColor")) : Color.WHITE;

        view.setSubCaption(text, textSize, color, haloColor);
    }
    
    ...
    
  }