728x90

구글 제공 구글맵 샘플코드는 https://github.com/googlemaps/android-samples/tree/master 에서 다운로드 받아서 필요한 것을 구현하면 된다.


지도 객체

- 하나의 지도는 View 요소와 인터페이스 요소로 구성된다.

- View 요소는 화면에 지도를 나타내는 역할을 하며, MapFragment 와 MapView 가 여기에 해당된다.

- 지도 화면은 Fragment 및 View로 제공된다. 이 중 한가지를 Layout 에 추가하면 화면에 지도가 나타난다.

- 지도를 화면에 나타내는 방법 중 가장 권장되는 것은 MapFragment 를 사용하는 것이다.

- Fragment 를 사용하지 않고 View를 바로 사용할 수도 있다.

  MapView를 Layout에 추가하면 MapFragment 를 사용한 것과 마찬가지로 지도가 화면에 나타난다.

- MapView를 사용할 때는 반드시 MapView가 포함된 액티비티의 라이프 사이클에 맞추어 onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy(), onSaveInstanceState(), onLowMemory()를 호출해야 합니다. 단, MapView가 프래그먼트에 포함될 경우 프래그먼트의 onCreateView() 또는 onViewCreated()에서 onCreate()를, onDestroyView()에서 onDestroy()를 호출해야 합니다. 그렇지 않으면 지도가 정상적으로 동작하지 않는다.
MapFragment를 사용하면 이러한 절차가 필요하지 않으므로 MapFragment를 사용하는 것을 권장한다.



FusedLocationProviderClient

https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient 참조

private FusedLocationProviderClient mFusedLocationProviderClient;

// FusedLocationProviderClient 객체 생성
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(mContext);



현재 위치를 획득하는 걸 LocationManager service = (LocationManager) getSystemService(Context.LOCATION_SERVICE); 를 활용해볼까 하고 좀 찾아봤더니 이건 오래된 방식이라고 나온다.


GPS 수신이 불가능한 곳에 위치할 경우에는 네트워크 사업자의 위치정보를 받아서 처리하는 걸 추가하려고 하는데 아직 여기까지는 처리하지 못했다.


https://webnautes.tistory.com/1249 에 나온 예제가 너무 잘 되어 있어 거의 활용하면서 구글맵 예제와 비교하면서 수정해서 테스트 해봤다.


activity_current_place.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">

            <ImageView
                android:id="@+id/navibar_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:adjustViewBounds="false"
                android:scaleType="fitXY"
                android:src="@drawable/navibar_bg" />

            <TextView
                android:id="@+id/navibar_text"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:singleLine="true"
                android:text="No Title"
                android:textColor="@android:color/white"
                android:textSize="22dp" />

            <Button
                android:id="@+id/list_btn"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:background="@drawable/btn_playlist" />

            <Button
                android:id="@+id/home_btn"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:background="@drawable/btn_home_c" />

        </RelativeLayout>

        <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/realtimemap"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>

</LinearLayout>


CurrentPlace.java

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class CurrentPlace extends AppCompatActivity implements View.OnClickListener, OnMapReadyCallback {
    private static final String TAG = CurrentPlace.class.getSimpleName();
    Context mContext;
    TextView title;

    private GoogleMap mMap;
    private Marker currentMarker = null;

    // The entry point to the Fused Location Provider.
    private FusedLocationProviderClient mFusedLocationProviderClient; // Deprecated된 FusedLocationApi를 대체
    private LocationRequest locationRequest;
    private Location mCurrentLocatiion;

    private final LatLng mDefaultLocation = new LatLng(37.56, 126.97);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    private static final int GPS_ENABLE_REQUEST_CODE = 2001;
    private static final int UPDATE_INTERVAL_MS = 1000 * 60 * 15;  // LOG 찍어보니 이걸 주기로 하지 않는듯
    private static final int FASTEST_UPDATE_INTERVAL_MS = 1000 * 30 ; // 30초 단위로 화면 갱신

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocatiion = savedInstanceState.getParcelable(KEY_LOCATION);
            CameraPosition mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        setContentView(R.layout.activity_current_place);
        mContext = CurrentPlace.this;

        // button action...
        findViewById(R.id.list_btn).setOnClickListener(this);
        findViewById(R.id.home_btn).setOnClickListener(this);

        // title set
        title = (TextView) this.findViewById(R.id.navibar_text); // title
        title.setText("실시간 위치 표시");

        locationRequest = new LocationRequest()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) // 정확도를 최우선적으로 고려
                .setInterval(UPDATE_INTERVAL_MS) // 위치가 Update 되는 주기
                .setFastestInterval(FASTEST_UPDATE_INTERVAL_MS); // 위치 획득후 업데이트되는 주기

        LocationSettingsRequest.Builder builder =
                new LocationSettingsRequest.Builder();

        builder.addLocationRequest(locationRequest);

        // Construct a FusedLocationProviderClient.
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);

        // Build the map.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.realtimemap);
        mapFragment.getMapAsync(this);
    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mCurrentLocatiion);
            super.onSaveInstanceState(outState);
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.list_btn:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(mContext, view);
                Menu menu = popupMenu.getMenu();
                menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "Get Place");
                //menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.option_get_place:
                                //showCurrentPlace(); // 아직 이 부분 미 구현
                                break;
                        }
                        return true;
                    }
                });
                popupMenu.show();
                break;
            case R.id.home_btn:
                Intent intent = new Intent(CurrentPlace.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            default:
                break;
        }
    }

    @Override
    public void onMapReady(GoogleMap map) {
        Log.e(TAG, "onMapReady :");

        mMap = map;

        setDefaultLocation(); // GPS를 찾지 못하는 장소에 있을 경우 지도의 초기 위치가 필요함.

        getLocationPermission();

        updateLocationUI();

        getDeviceLocation();
    }

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mCurrentLocatiion = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    private void setDefaultLocation() {
        if (currentMarker != null) currentMarker.remove();

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(mDefaultLocation);
        markerOptions.title("위치정보 가져올 수 없음");
        markerOptions.snippet("위치 퍼미션과 GPS 활성 여부 확인하세요");
        markerOptions.draggable(true);
        markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(mDefaultLocation, 15);
        mMap.moveCamera(cameraUpdate);
    }

    String getCurrentAddress(LatLng latlng) {
        // 위치 정보와 지역으로부터 주소 문자열을 구한다.
        List<Address> addressList = null ;
        Geocoder geocoder = new Geocoder( this, Locale.getDefault());

        // 지오코더를 이용하여 주소 리스트를 구한다.
        try {
            addressList = geocoder.getFromLocation(latlng.latitude,latlng.longitude,1);
        } catch (IOException e) {
            Toast. makeText( this, "위치로부터 주소를 인식할 수 없습니다. 네트워크가 연결되어 있는지 확인해 주세요.", Toast.LENGTH_SHORT ).show();
            e.printStackTrace();
            return "주소 인식 불가" ;
        }

        if (addressList.size() < 1) { // 주소 리스트가 비어있는지 비어 있으면
            return "해당 위치에 주소 없음" ;
        }

        // 주소를 담는 문자열을 생성하고 리턴
        Address address = addressList.get(0);
        StringBuilder addressStringBuilder = new StringBuilder();
        for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
            addressStringBuilder.append(address.getAddressLine(i));
            if (i < address.getMaxAddressLineIndex())
                addressStringBuilder.append("\n");
        }

        return addressStringBuilder.toString();
    }

    LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);

            List<Location> locationList = locationResult.getLocations();

            if (locationList.size() > 0) {
                Location location = locationList.get(locationList.size() - 1);

                LatLng currentPosition
                        = new LatLng(location.getLatitude(), location.getLongitude());

                String markerTitle = getCurrentAddress(currentPosition);
                String markerSnippet = "위도:" + String.valueOf(location.getLatitude())
                        + " 경도:" + String.valueOf(location.getLongitude());

                Log.d(TAG, "Time :" + CurrentTime() + " onLocationResult : " + markerSnippet);

                // Update 주기를 확인해보려고 시간을 찍어보았음.
                //현재 위치에 마커 생성하고 이동
                setCurrentLocation(location, markerTitle, markerSnippet);
                mCurrentLocatiion = location;
            }
        }

    };

    private String CurrentTime(){
        Date today = new Date();
        SimpleDateFormat date = new SimpleDateFormat("yyyy/MM/dd");
        SimpleDateFormat time = new SimpleDateFormat("hh:mm:ss a");
        return time.format(today);
    }

    public void setCurrentLocation(Location location, String markerTitle, String markerSnippet) {
        if (currentMarker != null) currentMarker.remove();

        LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(currentLatLng);
        markerOptions.title(markerTitle);
        markerOptions.snippet(markerSnippet);
        markerOptions.draggable(true);

        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng);
        mMap.moveCamera(cameraUpdate);
    }

    private void getDeviceLocation() {
        try {
            if (mLocationPermissionGranted) {
                mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    private void getLocationPermission() {
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mLocationPermissionGranted) {
            Log.d(TAG, "onStart : requestLocationUpdates");
            mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, null);
            if (mMap!=null)
                mMap.setMyLocationEnabled(true);
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mFusedLocationProviderClient != null) {
            Log.d(TAG, "onStop : removeLocationUpdates");
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mFusedLocationProviderClient != null) {
            Log.d(TAG, "onDestroy : removeLocationUpdates");
            mFusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }
}


앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.link2me.android.googlemap"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        // multiDexEnabled true // minSdkVersion 이 21 이상인 경우 사용
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    //implementation 'com.google.android.gms:play-services-places:16.0.0' // OLD
    //implementation 'com.google.android.libraries.places:places:2.0.0'  // NEW
    implementation 'com.google.android.libraries.places:places-compat:2.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.android.volley:volley:1.1.1'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    implementation 'androidx.multidex:multidex:2.0.0' // minSdkVersion 이 21 이하인 경우 사용
    implementation 'com.google.android.material:material:1.2.0'
}


최근에는 minSdkVersion 을 23 이상으로 하라는 코드가 상당히 많다.

보안이 강화되면서 minSdkVersion 버전을 올리는 것이 좋은 듯하다.

블로그 이미지

Link2Me

,
728x90

구글맵에 현재 위치 표시하기


Activity 에서 현재 위치를 표시해주는 것은 구글맵에 나온 예제를 그대로 실행하면 잘 된다.

Fragment 에서 실행하려고 시도했는데 에러가 발생한다.

Fragment 에 대한 이해 부족인 거 같아서 다른 예제를 좀 찾아서 시도해봐야 할 거 같다.


https://developers.google.com/maps/documentation/android-sdk/current-place-tutorial


AndroidManifext.xml 파일 내용은 https://link2me.tistory.com/1700 하단 내용 참조하면 된다.


activity_current_place.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MapsActivityCurrentPlace">

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


custom_info_contents.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layoutDirection="locale"
    android:orientation="vertical">
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textColor="#ff000000"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/snippet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#ff7f7f7f" />
</LinearLayout>


current_place_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/option_get_place"
        android:title="Get Place"
        app:showAsAction="always" />
</menu>


the options menu 를 사용하지 않고 별도의 타이틀바에서 처리되도록 만들기 위해서 코드를 아래 코든는 삭제했다.

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.current_place_menu, menu);
        return true;
    }
 

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }


위 코드 대신에 추가한 코드는

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.list_btn:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(mContext, view);
                Menu menu = popupMenu.getMenu();
                menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "Get Place");
                //menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.option_get_place:
                                showCurrentPlace();
                                break;
                        }
                        return true;
                    }
                });
                popupMenu.show();
                break;
            case R.id.home_btn:
                Intent intent = new Intent(MapsActivityCurrentPlace.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            default:
                break;
        }
    }




MapsActivityCurrentPlace.java

구글 맵 예제로 제공하는 사항이 코드를 구현해보면 The Google Play Services Places SDK is deprecated. 라고 나온다.

찾아보니 2019.7.29일 서비스가 종료되었단다.

해결방안은 앱 build.gradle 에서

//implementation 'com.google.android.gms:play-services-places:16.0.0' // OLD
implementation 'com.google.android.libraries.places:places-compat:2.0.0'

를 해주면 된다고 나와 컴파일 해보니 잘 된다. 아직은 OLD 로 해도 잘 되더라.


package com.link2me.android.googlemap;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.location.places.PlaceDetectionClient;
import com.google.android.gms.location.places.PlaceLikelihood;
import com.google.android.gms.location.places.PlaceLikelihoodBufferResponse;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

public class MapsActivityCurrentPlace extends AppCompatActivity implements View.OnClickListener, OnMapReadyCallback {
    // 현재 위치를 구글 지도에 표시하는 예제
    // https://developers.google.com/maps/documentation/android-sdk/current-place-tutorial 참조
    private static final String TAG = MapsActivityCurrentPlace.class.getSimpleName();
    Context mContext;
    TextView title;

    private GoogleMap mMap;
    private CameraPosition mCameraPosition;

    // The entry points to the Places API.
    private GeoDataClient mGeoDataClient;
    private PlaceDetectionClient mPlaceDetectionClient;

    // The entry point to the Fused Location Provider.
    private FusedLocationProviderClient mFusedLocationProviderClient;

    // A default location (Sydney, Australia) and default zoom to use when location permission is not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private Location mLastKnownLocation;

    // Keys for storing activity state.
    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    // Used for selecting the current place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] mLikelyPlaceNames;
    private String[] mLikelyPlaceAddresses;
    private String[] mLikelyPlaceAttributions;
    private LatLng[] mLikelyPlaceLatLngs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Retrieve location and camera position from saved instance state.
        if (savedInstanceState != null) {
            mLastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        setContentView(R.layout.activity_maps_current_place);
        mContext = MapsActivityCurrentPlace.this;

        // button action...
        findViewById(R.id.list_btn).setOnClickListener(this);
        findViewById(R.id.home_btn).setOnClickListener(this);

        // title set
        title = (TextView) this.findViewById(R.id.navibar_text); // title
        title.setText(getIntent().getExtras().getString("title"));

        // Construct a GeoDataClient.
        mGeoDataClient = Places.getGeoDataClient(this, null);

        // Construct a PlaceDetectionClient.
        mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

        // Construct a FusedLocationProviderClient.
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);

        // Build the map.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }

    /**
     * Manipulates the map when it's available.
     * This callback is triggered when the map is ready to be used.
     */
    @Override
    public void onMapReady(GoogleMap map) {
        mMap = map;

        // Use a custom info window adapter to handle multiple lines of text in the info window contents.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {

            @Override
            // Return null here, so that getInfoContents() is called next.
            public View getInfoWindow(Marker arg0) {
                return null;
            }

            @Override
            public View getInfoContents(Marker marker) {
                // Inflate the layouts for the info window, title and snippet.
                View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents,
                        (FrameLayout) findViewById(R.id.map), false);

                TextView title = ((TextView) infoWindow.findViewById(R.id.title));
                title.setText(marker.getTitle());

                TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
                snippet.setText(marker.getSnippet());

                return infoWindow;
            }
        });

        // Prompt the user for permission.
        getLocationPermission();

        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();

        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }

    /**
     * Gets the current location of the device, and positions the map's camera.
     */
    private void getDeviceLocation() {
        /*
         * Get the best and most recent location of the device, which may be null in rare
         * cases when a location is not available.
         */
        try {
            if (mLocationPermissionGranted) {
                Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful()) {
                            // Set the map's camera position to the current location of the device.
                            mLastKnownLocation = task.getResult();
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(mLastKnownLocation.getLatitude(),
                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            mMap.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                            mMap.getUiSettings().setMyLocationButtonEnabled(false);
                        }
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }


    /**
     * Prompts the user for permission to use the device location.
     */
    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    /**
     * Handles the result of the request for location permissions.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }

    /**
     * Prompts the user to select the current place from a list of likely places, and shows the
     * current place on the map - provided the user has granted location permission.
     */
    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            // Get the likely places - that is, the businesses and other points of interest that
            // are the best match for the device's current location.
            @SuppressWarnings("MissingPermission") final
            Task<PlaceLikelihoodBufferResponse> placeResult =
                    mPlaceDetectionClient.getCurrentPlace(null);
            placeResult.addOnCompleteListener
                    (new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
                        @Override
                        public void onComplete(@NonNull Task<PlaceLikelihoodBufferResponse> task) {
                            if (task.isSuccessful() && task.getResult() != null) {
                                PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();

                                // Set the count, handling cases where less than 5 entries are returned.
                                int count;
                                if (likelyPlaces.getCount() < M_MAX_ENTRIES) {
                                    count = likelyPlaces.getCount();
                                } else {
                                    count = M_MAX_ENTRIES;
                                }

                                int i = 0;
                                mLikelyPlaceNames = new String[count];
                                mLikelyPlaceAddresses = new String[count];
                                mLikelyPlaceAttributions = new String[count];
                                mLikelyPlaceLatLngs = new LatLng[count];

                                for (PlaceLikelihood placeLikelihood : likelyPlaces) {
                                    // Build a list of likely places to show the user.
                                    mLikelyPlaceNames[i] = (String) placeLikelihood.getPlace().getName();
                                    mLikelyPlaceAddresses[i] = (String) placeLikelihood.getPlace()
                                            .getAddress();
                                    mLikelyPlaceAttributions[i] = (String) placeLikelihood.getPlace()
                                            .getAttributions();
                                    mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();

                                    i++;
                                    if (i > (count - 1)) {
                                        break;
                                    }
                                }

                                // Release the place likelihood buffer, to avoid memory leaks.
                                likelyPlaces.release();

                                // Show a dialog offering the user the list of likely places, and add a
                                // marker at the selected place.
                                openPlacesDialog();

                            } else {
                                Log.e(TAG, "Exception: %s", task.getException());
                            }
                        }
                    });
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");

            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title("기본 위치")
                    .position(mDefaultLocation)
                    .snippet("No places found, because location is disabled."));

            // Prompt the user for permission.
            getLocationPermission();
        }
    }

    /**
     * Displays a form allowing the user to select a place from a list of likely places.
     */
    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }

                // Add a marker for the selected place, with an info window showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));

                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };

        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle("장소를 선택하세요.")
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }

    /**
     * Updates the map's UI settings based on whether the user has granted location permission.
     */
    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.list_btn:
                // 팝업 메뉴 나오게 하는 방법
                PopupMenu popupMenu = new PopupMenu(mContext, view);
                Menu menu = popupMenu.getMenu();
                menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                //menu.add(Menu.NONE,R.id.option_get_place,Menu.NONE, "");
                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.option_get_place:
                                showCurrentPlace();
                                break;
                        }
                        return true;
                    }
                });
                popupMenu.show();
                break;
            case R.id.home_btn:
                Intent intent = new Intent(MapsActivityCurrentPlace.this, MainActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            default:
                break;
        }
    }
}



블로그 이미지

Link2Me

,
728x90

코드를 구현하다보니 파싱처리를 하는데 동일한 단어가 들어간 곳이 다른 곳에도 존재하는 걸 몰랐다.

파싱할 내용이 다르다보니 에러가 발생했다.


해결법은

try {
    int stlocation = str.indexOf("GW=");
    int endlocation = str.length();
    String temp = str.substring(stlocation + 4, endlocation).trim();
    String[] part = temp.split("\\s{1,}"); // 공백 단위로 메시지를 분리하라.
    if (part[3].length() > 4) {
        ipaddr = part[3];
    }
} catch(IndexOutOfBoundsException e) {
    System.out.println(e);
}


와 같이 try catch 문으로 해결했다.



블로그 이미지

Link2Me

,
728x90

https://developers.google.com/maps/documentation/android-sdk/get-api-key 를 참조한다.


구글 맵을 사용하기 위해서는 먼저 Google API Key 를 발급받아야 한다.


1. https://console.developers.google.com/ 에 접속한다.


2. 프로젝트를 신규로 만들거나 이미 만들어진 프로젝트를 선택한다.

   - 이미 만들어진 프로젝트 Contacts 를 선택했다.

     프로젝트를 연습한다고 신규로 계속 만들었는데 그럴 필요가 없다.


3. 왼쪽 메뉴에서 라이브러리를 선택한다.

   - Maps SDK for Android 를 선택한다.

   - 사용설정을 누른다.


4. 사용자 인증정보 만들기

    - 구글 Firebase Message 를 사용하기 위해 등록하면서 만들어진 사용자 인증키가 있더라.


   - 내용을 확인해보니 Server key 가 Firebase Cloud Message 에서 사용하는 키(이전 서버키)더라.

   - Android key 를 눌러서 선택했다.

    - 항목 추가를 누른다.

① 패키지 이름은 B를 복사하여 붙여넣기 한다.



② SHA-1 인증서 디지털 지문

     - A를 눌러서 복사한다.

     - Android Studio 가 설치된 경로에 있는 jre/bin 폴더에 keytool.exe 파일이 존재한다.

     - 이 폴더에서 cmd 창 상태로 전환해서 A 복사한 걸 붙여넣기 하고 엔터키를 친다.

     - SHA-1 키를 복사하여 붙여넣기 한다.

       만약 Drag 가 안된다면 파일로 저장해서 Copy & Paste 하면 된다.

       회사 컴과 집 컴이 각각 다르다면 SHA-1 이 다를 것이므로 항목 추가로 각각 등록해서 테스트하면 된다.

      - 패키지 이름과 SHA-1 인증서 디지털 지문을 복사하여 붙여넣기를 한 다음 완료를 누른다

      - 저장을 누르면 Key 발급 준비가 된 것이다.


이제 바로 위의 A를 눌러서 복사한 API 키를

AndroidManifest.xml 파일에 구글 API Key를 등록한다.

키는 임의로 변경한 것이 있으므로 실제 키와는 다르다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.link2me.android.googlemap">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><!-- GPS -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><!-- WiFi or mobile -->

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MapsActivityCurrentPlace"></activity>

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="AIzaSyAgfzQZqx7nFVFhwKAhviwRP5Rmi9bmZPE" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


앱 build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation 'com.google.android.gms:play-services-places:16.0.0'
    implementation 'com.google.android.libraries.places:places:2.0.0'

}
 


이제 구글맵을 사용할 준비는 끝났다.

블로그 이미지

Link2Me

,
728x90

자바(Java) 음력 일자 변환 라이브러리 설치법은 https://link2me.tistory.com/1695 를 참조하면 된다.

음력에 대한 자세한 내용을 알고 싶다면 http://oops.org/project/manse/https://github.com/OOPS-ORG-PHP/Lunar 를 읽어보면 도움이 될 것이다. 고영창 씨께서 고생해서 자료와 알고리즘을 만들었던 거 같다.

음력 앱, 프로그램 등을 테스트하다보면 음력일자가 약간 틀리게 나오는 경우도 있다. 정확한 데이터를 기준으로 해야 하는데, 데이터의 기준이 이것이 정확하다고 하는 걸 찾는게 어려운 거 같다.

음력 데이터를 MySQL 버전으로 DB화를 해 놓은 걸 SQLite 로 포팅하는 걸 시도해봐야 하나 고민되기도 한다.


자바(Java)에서 사용하는 라이브러리가 맞다는 가정하에 이 코드를 기준으로 음력과 양력을 변환하여 법정공휴일 처리를 하도록 했다. 음력을 지원하는 앱을 실행해보면서 2019년도 추석, 설 데이터, 어린이날 대체공휴일 등이 맞는 걸로 봐서 음력 데이터가 정확한 거 같아서 아래 코드를 노가다 수준으로 구현해서 테스트 했다.


법정공휴일 처리를 위해서 Holidays Class 를 만들었다.

ArrayList를 사용해서 입력 연도에 해당되는 법정공휴일 처리를 하려고 한다.

public class Holidays implements Comparable<Holidays>{
    // ArrayList의 type이 Comparable을 implements한 경우에만 sort 메소드의 정렬 기능을 사용할 수 있다
    private String year; // 연도
    private String date; // 월일
    private String name; // 휴일 명칭

    public Holidays() {
    }

    public Holidays(String year, String date, String name) {
        this.year = year;
        this.date = date;
        this.name = name;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Holidays o) {
        return this.date.compareTo(o.date);
    }
}


이제 법정공휴일 양력과 음력을 처리할 ArrayList<Holidays> holidayArray(String yyyy) 메소드를 만들었다.

ArrayList return 메소드 만드는 예제는 https://link2me.tistory.com/1698 를 참조하면 된다.

사용법은 ArrayList<Holidays> arr = LunarCalendar.holidayArray("2019"); 로 결과를 얻어서 처리하면 된다.

import com.ibm.icu.util.ChineseCalendar;
import com.tistory.link2me.item.Holidays;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;

public class LunarCalendar {
    static ArrayList<Holidays> holidaysArrayList = new ArrayList<>();

    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    public static String Lunar2Solar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));

        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }

    /**
     * 양력날짜를 음력날짜로 변환
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력날짜 (yyyyMMdd)
     */
    public static String Solar2Lunar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim() ;
        if( date.length() != 8 ) {
            if( date.length() == 4 )
                date = date + "0101" ;
            else if( date.length() == 6 )
                date = date + "01" ;
            else if( date.length() > 8 )
                date = date.substring(0,8) ;
            else
                return "" ;
        }

        cal.set( Calendar.YEAR, Integer.parseInt(date.substring(0,4)) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(date.substring(4,6))-1 ) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)) ) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString() ;
    }


    public static ArrayList<Holidays> holidayArray(String yyyy){
        holidaysArrayList.clear(); // 데이터 초기화
        // 양력 휴일
        addHolidaysItem(yyyy,"0101" ,"신정");
        addHolidaysItem(yyyy,"0301" ,"삼일절");
        addHolidaysItem(yyyy,"0505" ,"어린이날");
        addHolidaysItem(yyyy,"0606" ,"현충일" );
        addHolidaysItem(yyyy,"0815" ,"광복절");
        addHolidaysItem(yyyy,"1003" ,"개천절");
        addHolidaysItem(yyyy,"1009" ,"한글날");
        addHolidaysItem(yyyy,"1225" ,"성탄절");

        // 음력 휴일
        String prev_seol = String.valueOf(Integer.parseInt(Lunar2Solar(yyyy+"0101")) -1);
        addHolidaysItem(yyyy,prev_seol.substring(4) ,"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0101"),"설날");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0102"),"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0408"),"석탄일");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0814"),"");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0815"),"추석");
        addHolidaysItem(yyyy,SolarDays(yyyy, "0816"),"");

        try {
            // 어린이날 대체공휴일 검사 : 어린이날은 토요일, 일요일인 경우 그 다음 평일을 대체공유일로 지정
            int childDayChk = WeekendValue(yyyy+"0505");
            if(childDayChk == 1) addHolidaysItem(yyyy,"0506" ,"대체공휴일");
            if(childDayChk == 7) addHolidaysItem(yyyy,"0507" ,"대체공휴일");

            // 설날 대체공휴일 검사
            if(WeekendValue(Lunar2Solar(yyyy+"0101"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0101"))==2) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0102"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0103"),"대체공휴일");

            // 추석 대체공휴일 검사
            if(WeekendValue(Lunar2Solar(yyyy+"0814"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0815"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
            if(WeekendValue(Lunar2Solar(yyyy+"0816"))==1) addHolidaysItem(yyyy,SolarDays(yyyy, "0817"),"대체공휴일");
        } catch (ParseException e) {
            e.printStackTrace();
        }

        Collections.sort(holidaysArrayList); // 오름차순 정렬

        return holidaysArrayList;
    }

    private static String SolarDays(String yyyy, String date){
        return Lunar2Solar(yyyy+date).substring(4);
    }

    private static void addHolidaysItem(String year, String date, String name ){
        Holidays item = new Holidays();
        item.setYear(year);
        item.setDate(date);
        item.setName(name);
        holidaysArrayList.add(item);
    }

    private static int WeekendValue(String date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar cal = Calendar.getInstance();
        cal.setTime(sdf.parse(date));
        return cal.get(Calendar.DAY_OF_WEEK);
        // Calendar.SUNDAY : 1
        // Calendar.SATURDAY : 7
    }

}


사용 샘플 예제

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.tistory.link2me.item.Holidays;
import com.tistory.link2me.util.Lunar2Solar;
import com.tistory.link2me.util.LunarCalendar;

import java.util.ArrayList;
import java.util.Calendar;

public class CalendarActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calendar);

        System.out.println("Calendar.SUNDAY : "+Calendar.SUNDAY);
        System.out.println("Calendar.SATURDAY : "+Calendar.SATURDAY);

        ArrayList<Holidays> arr = LunarCalendar.holidayArray("2019");
        for(int i=0; i < arr.size();i++){
            System.out.println(arr.get(i).getYear() +"년 " + arr.get(i).getDate() +", " + arr.get(i).getName() );
        }

    }
}


결과 Logcat 화면


블로그 이미지

Link2Me

,
728x90

ArrayList 를 리턴 받는 함수를 만드는 방법이다.


import java.util.ArrayList;

public class Test {
    public ArrayList<Integer> myNumbers() {
        ArrayList<Integer> numbers = new ArrayList<Integer>();
        numbers.add(3);
        numbers.add(5);
        numbers.add(8);
        numbers.add(11);
        return (numbers);

    }
}


public class Main {

    public static void main(String[] args) {
        Test test = new Test();
        ArrayList<Integer> arr = test.myNumbers();
    }

}

'안드로이드 > Java 문법' 카테고리의 다른 글

Convert HashMap To ArrayList  (0) 2019.10.21
[Java] Convert Month String to Integer  (0) 2019.10.14
자바 null 비교, String 비교, 정수 비교  (0) 2019.09.20
공백으로 문자열 분리  (0) 2019.09.11
Java 제네릭(Generic)  (0) 2019.08.27
블로그 이미지

Link2Me

,
728x90

[ 태음력의 조정 ]
    235회합월은 일수로는 6940일(days)이다(235 x 29.530588일 = 6939.68818일).
    6940일의 1/19은 6940일 / 19 = 365.263157894737일(365 + 1/4 + 1/76일)이다.
    365.263157894737일은 12회합월(synodic months)인
    12 x 29.530588일 = 354.367056일보다 10.896101894737일(365.263157894737 - 354.367056) 더 크다.
    이것이 뜻하는 바는 음력으로 1년이 지나면 태양력과는 약 11일의 차이가 난다는 것을 의미한다.
    따라서 음력으로 3년이 지나면 태양력과는 약 33일의 차이가 나게 된다.
    따라서 3년마다 음력에 1달을 추가하며 13개의 음력 달들이 있게 하면,
    태음력은 어느 정도 근사하게 태양력과 일치하게 된다.
    태양력 19년 동안에 이런 작업을 총 7번(19년/3년 = 6.33) 행하면 된다.
    이런 이유로, 메톤 주기를 19년 7윤법이라고도 하였다.
 
메톤은 19태양년을 6,940일로 보고,
이것을 큰달(30일) 125개월과 작은달(29일) 110개월의 총 235개월 6,940일로 배분하였다.
이 방법은 평년(12개월) 12년과 윤년(13개월) 7년의 총 235개월로
태음력 달력을 구성하는 것과 동일하므로 19년 7윤법이라고도 하였다.

메톤주기의 존재를 알 수 있는 예로,
특정한 양력 날짜와 그에 대응하는 음력 날짜는 19년이 지나면 다시 일치한다는 사실을 찾을 수 있다.

하지만 정확히 맞물리는 건 아니라서 하루 정도 어긋나기도 한다. 


음력 날짜는 어느 경도를 기준으로 했느냐에 따라 하루 정도 차이가 있어서 때려 맞춰야 한다는 얘기가 있다.

한국천문연구원에서 계산한 2050년까지 음양력 자료들이 수록된 만세력을 기준으로 음력 날짜를 맞추면 될 거 같다.

음력을 알아내는 일정한 규칙은 없다고 한다.

아래 변환 코드로 내 생년월일, 딸래미 생년월일을 양력 → 음력 변환을 했을 때는 정확하게 결과가 도출되었다.


검색한 걸 참고하면 도움될 거 같아서 링크를 걸어둔다.

http://lifesci.net/pod/bbs/board.php?bo_table=B01&wr_id=1917



PHP 달력 만들기 https://link2me.tistory.com/1545 코드에 있는 함수를 자바 코드로 변환을 했다.

자동 변환이 아니라 코드에 맞게 함수를 찾아서 수정했다.

음력이 지원되는 Custom 달력을 만들어 볼 목적으로 코드를 변환하여 테스트 해보는 중이다.

PHP 원본 코드와 Java 로 변환한 코드를 같이 첨부했다.

Java 변환 코드는 Android 에서 활용하기 위한 목적이라 Android Studio 에서 작성했다.


PHP 코드

lun2sol.php



Lunar2Solar.java

Lunar2Solar.java

Lunar2Solar.java


자바 음력/양력 변환 코드

public class Lunar2Solar {
    // 음력 데이터 (평달 - 작은달 :1,  큰달:2 )
    // (윤달이 있는 달 - 평달이 작고 윤달도 작으면 :3 , 평달이 작고 윤달이 크면 : 4)
    // (윤달이 있는 달 - 평달이 크고 윤달이 작으면 :5,  평달과 윤달이 모두 크면 : 6)
    final static String[] LunarInfo = new String[]
            {"1212122322121","1212121221220","1121121222120","2112132122122","2112112121220",
                "2121211212120","2212321121212","2122121121210","2122121212120","1232122121212",
                "1212121221220","1121123221222","1121121212220","1212112121220","2121231212121",
                "2221211212120","1221212121210","2123221212121","2121212212120","1211212232212",
                "1211212122210","2121121212220","1212132112212","2212112112210","2212211212120",
                "1221412121212","1212122121210","2112212122120","1231212122212","1211212122210",
                "2121123122122","2121121122120","2212112112120","2212231212112","2122121212120",
                "1212122121210","2132122122121","2112121222120","1211212322122","1211211221220",
                "2121121121220","2122132112122","1221212121120","2121221212110","2122321221212",
                "1121212212210","2112121221220","1231211221222","1211211212220","1221123121221",
                "2221121121210","2221212112120","1221241212112","1212212212120","1121212212210",
                "2114121212221","2112112122210","2211211412212","2211211212120","2212121121210",
                "2212214112121","2122122121120","1212122122120","1121412122122","1121121222120",
                "2112112122120","2231211212122","2121211212120","2212121321212","2122121121210",
                "2122121212120","1212142121212","1211221221220","1121121221220","2114112121222",
                "1212112121220","2121211232122","1221211212120","1221212121210","2121223212121",
                "2121212212120","1211212212210","2121321212221","2121121212220","1212112112210",
                "2223211211221","2212211212120","1221212321212","1212122121210","2112212122120",
                "1211232122212","1211212122210","2121121122210","2212312112212","2212112112120",
                "2212121232112","2122121212110","2212122121210","2112124122121","2112121221220",
                "1211211221220","2121321122122","2121121121220","2122112112322","1221212112120",
                "1221221212110","2122123221212","1121212212210","2112121221220","1211231212222",
                "1211211212220","1221121121220","1223212112121","2221212112120","1221221232112",
                "1212212122120","1121212212210","2112132212221","2112112122210","2211211212210",
                "2221321121212","2212121121210","2212212112120","1232212122112","1212122122110",
                "2121212322122","1121121222120","2112112122120","2211231212122","2121211212120",
                "2122121121210","2124212112121","2122121212120","1212121223212","1211212221220",
                "1121121221220","2112132121222","1212112121220","2121211212120","2122321121212",
                "1221212121210","2121221212120","1232121221212","1211212212210","2121123212221",
                "2121121212220","1212112112220","1221231211221","2212211211220","1212212121210",
                "2123212212121","2112122122120","1211212322212","1211212122210","2121121122120",
                "2212114112122","2212112112120","2212121211210","2212232121211","2122122121210",
                "2112122122120","1231212122212","1211211221220","2121121321222","2121121121220",
                "2122112112120","2122141211212","1221221212110","2121221221210","2114121221221"};

    final static int[] arrayLDAY = new int[]{31,0,31,30,31,30,31,31,30,31,30,31};

    public static String lun2sol(String yyyymmdd) {
        int YunMonthFlag = 0;
        int gf_lun2sol = 0;
        int gf_yun = 0;
        int leap = 0;
        int syear = 0;
        int smonth = 0;
        int sday = 0;

        int getYEAR = Integer.parseInt(yyyymmdd.substring(0,4));
        int getMONTH = Integer.parseInt(yyyymmdd.substring(4,6));
        int getDAY = Integer.parseInt(yyyymmdd.substring(6,8));

        String arrayYUKSTR="甲-乙-丙-丁-戊-己-庚-辛-壬-癸";
        String[] arrayYUK = arrayYUKSTR.split("-");

        String arrayGAPSTR="子-丑-寅-卯-辰-巳-午-未-申-酉-戌-亥";
        String[] arrayGAP = arrayGAPSTR.split("-");

        String arrayDDISTR="쥐-소-범-토끼-용-뱀-말-양-원숭이-닭-개-돼지";
        String[] arrayDDI = arrayDDISTR.split("-");

        String arrayWEEKSTR="일-월-화-수-목-금-토";
        String[] arrayWEEK = arrayWEEKSTR.split("-");

        if (getYEAR <= 1881 || getYEAR >= 2050) { //년수가 해당일자를 넘는 경우
            YunMonthFlag = 0;
        }

        if (getMONTH > 12) { // 달수가 13이 넘는 경우
            YunMonthFlag = 0;
        }

        int m1 = getYEAR - 1881;
        if (Integer.parseInt(LunarInfo[m1].substring(12,13)) == 0) { // 윤달이 없는 해임
            YunMonthFlag = 0;
        } else {
            if (Integer.parseInt(LunarInfo[m1].substring(getMONTH,getMONTH+1)) > 2) {
                YunMonthFlag = 1;
            } else {
                YunMonthFlag = 0;
            }
        }

        m1 = -1;
        int td = 0;

        if (getYEAR > 1881 && getYEAR < 2050) {
            m1 = getYEAR - 1882;
            for (int i=0; i <= m1; i++) {
                for (int j=0; j <= 12; j++) {
                    td = td + Integer.parseInt(LunarInfo[i].substring(j,j+1));
                }
                if (Integer.parseInt(LunarInfo[i].substring(12,13)) == 0) {
                    td = td + 336;
                } else {
                    td = td + 362;
                }
            }
        } else {
            gf_lun2sol = 0;
        }

        m1++;
        int n2 = getMONTH - 1;
        int m2 = -1;

        while(true) {
            m2++;
            if (Integer.parseInt(LunarInfo[m1].substring(m2,m2+1)) > 2) {
                td = td + 26 + Integer.parseInt(LunarInfo[m1].substring(m2,m2+1));
                n2++;
            } else {
                if (m2 == n2) {
                    if (gf_yun == 1) {
                        td = td + 28 + Integer.parseInt(LunarInfo[m1].substring(m2,m2+1));
                    }
                    break;
                } else {
                    td = td + 28 + Integer.parseInt(LunarInfo[m1].substring(m2,m2+1));
                }
            }
        }

        td = td + getDAY + 29;
        m1 = 1880;
        while(true) {
            m1++;
            if (m1 % 400 == 0 || m1 % 100 != 0 && m1 % 4 == 0) {
                leap = 1;
            } else {
                leap = 0;
            }

            if (leap == 1) {
                m2 = 366;
            } else {
                m2 = 365;
            }

            if (td < m2) break;
            td = td - m2;
        }
        syear = m1;
        arrayLDAY[1] = m2 - 337;

        m1 = 0;

        while(true) {
            m1++;
            if (td <= arrayLDAY[m1-1]) {
                break;
            }
            td = td - arrayLDAY[m1-1];
        }
        smonth = m1;
        sday = td;
        int y = syear - 1;
        td = (int)(y*365) + (int)(y/4) - (int)(y/100) + (int)(y/400);

        if (syear % 400 == 0 || syear % 100 != 0 && syear % 4 == 0) {
            leap = 1;
        } else {
            leap = 0;
        }

        if (leap == 1) {
            arrayLDAY[1] = 29;
        } else {
            arrayLDAY[1] = 28;
        }
        for (int i=0; i <= smonth-2; i++) {
            td = td + arrayLDAY[i];
        }
        td = td + sday;
        int w = td % 7;

        String sweek = arrayWEEK[w];
        gf_lun2sol = 1;

        return String.valueOf(syear+String.format("%02d", smonth)+String.format("%02d", sday));
    }

    public static String sol2lun(String yyyymmdd) {
        int gf_sol2lun = 0;
        int gf_yun = 0;

        int getYEAR = Integer.parseInt(yyyymmdd.substring(0,4));
        int getMONTH = Integer.parseInt(yyyymmdd.substring(4,6));
        int getDAY = Integer.parseInt(yyyymmdd.substring(6,8));

        String arrayYUKSTR="甲-乙-丙-丁-戊-己-庚-辛-壬-癸";
        String[] arrayYUK = arrayYUKSTR.split("-");

        String arrayGAPSTR="子-丑-寅-卯-辰-巳-午-未-申-酉-戌-亥";
        String[] arrayGAP = arrayGAPSTR.split("-");

        String arrayDDISTR="쥐-소-범-토끼-용-뱀-말-양-원숭이-닭-개-돼지";
        String[] arrayDDI = arrayDDISTR.split("-");

        String arrayWEEKSTR="일-월-화-수-목-금-토";
        String[] arrayWEEK = arrayWEEKSTR.split("-");


        Long[] dt = new Long[LunarInfo.length];
        for (int i = 0; i < LunarInfo.length; i++) {
            dt[i] = Long.parseLong(LunarInfo[i]);
        }
        for (int i=0; i <= 168; i++) {
            dt[i] = Long.valueOf(0);
            for (int j=0;j < 12;j++) {
                switch (Integer.parseInt(LunarInfo[i].substring(j,j+1))) {
                    case 1:
                        dt[i] += 29;
                        break;
                    case 3:
                        dt[i] += 29;
                        break;
                    case 2:
                        dt[i] += 30;
                        break;
                    case 4:
                        dt[i] += 30;
                        break;
                }
            }

            switch (Integer.parseInt(LunarInfo[i].substring(12,13))) {
                case 0:
                    break;
                case 1:
                    dt[i] += 29;
                    break;
                case 3:
                    dt[i] += 29;
                    break;
                case 2:
                    dt[i] += 30;
                    break;
                case 4:
                    dt[i] += 30;
                    break;
            }
        }

        int td1 = 1880 * 365 + (int)(1880/4) - (int)(1880/100) + (int)(1880/400) + 30;
        int k11 = getYEAR - 1;

        int td2 = k11 * 365 + (int)(k11/4) - (int)(k11/100) + (int)(k11/400);

        if (getYEAR % 400 == 0 || getYEAR % 100 != 0 && getYEAR % 4 == 0) {
            arrayLDAY[1] = 29;
        } else {
            arrayLDAY[1] = 28;
        }

        if (getMONTH > 13) {
            gf_sol2lun = 0;
        }

        if (getDAY > arrayLDAY[getMONTH-1]) {
            gf_sol2lun = 0;
        }

        for (int i=0;i <= getMONTH-2;i++) {
            td2 += arrayLDAY[i];
        }

        td2 += getDAY;
        int td = td2 - td1 + 1;
        Long td0 = dt[0];

        int jcount = 0;
        int ryear = 0;
        int m1 = 0;
        int m2 = 0;
        int i = 0;
        for (i=0; i <= 168; i++) {
            if (td <= td0) {
                break;
            }
            td0 += dt[i + 1];
        }
        ryear = i + 1881;
        td0 -= dt[i];
        td -= td0;

        if (Integer.parseInt(LunarInfo[i].substring(12,13)) == 0) {
            jcount = 11;
        } else {
            jcount = 12;
        }

        for (int j=0;j <= jcount;j++) { // 달수 check, 윤달 > 2 (by harcoon)
            if (Integer.parseInt(LunarInfo[i].substring(j,j+1)) <= 2) {
                m2++;
                m1 = Integer.parseInt(LunarInfo[i].substring(j,j+1)) + 28;
                gf_yun = 0;
            } else {
                m1 = Integer.parseInt(LunarInfo[i].substring(j,j+1)) + 26;
                gf_yun = 1;
            }
            if (td <= m1) {
                break;
            }
            td = td - m1;
        }

        int k1=(ryear+6) % 10;
        String syuk = arrayYUK[k1];
        int k2=(ryear+8) % 12;
        String sgap = arrayGAP[k2];
        String sddi = arrayDDI[k2];

        gf_sol2lun = 1;

        return String.valueOf(ryear+String.format("%02d", m2)+String.format("%02d", td)+"|"+syuk+sgap+"년|"+sddi+"띠");
    }

}


샘플 코드

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.tistory.link2me.util.LunarCalendar;
import java.util.Calendar;

public class CalendarActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calendar);

        System.out.println("이순신 음력 => 양력 : "+ Lunar2Solar.lun2sol("19961112"));
        System.out.println("홍길동 양력 => 음력 : "+ Lunar2Solar.sol2lun("20010125"));
    }

}
 


Logcat

I/System.out: 이순신 음력 => 양력 : 19961222
I/System.out: 홍길동 양력 => 음력 : 2001|01|02|신사년|뱀띠

이 음력/양력 변환 코드가 자바 버전으로 돌아다니는 것은 없는거 같다.

PHP 코드를 자바로 변환한 것이지만 나름 유용하게 사용할 수 있을 것으로 본다.


이 글이 도움 되었다면 XX 클릭 해주세요. 좋은 자료 올리는데 큰 힘이 됩니다.

블로그 이미지

Link2Me

,
728x90
MySQL 로 된 데이터를 SQLite 로 변환하기 위해서 assets 를 이용할 까 해봤는데, 데이터 크기가 5MB가 넘어서 어플의 용량이 커지는 문제가 발생하고 변환하는데 시간도 걸리고 해서 포기했다.

결국에는 양력/음력 변환을 제대로 해주는 메소드를 잘 만들거나, 기존에 만들어진 다른 코드를 변환해서 이용하는 방법이 최선인 거 같다.
제대로 된 Java 라이브러리가 어디에 있는데 못찾는 것인지, 공개를 안해서 못 구하는 것인지는 모르겠다.


자바에서 제공하는 라이브러리를 이용하여 변환하는 방법을 시도했다.
인터넷 검색하면 가장 많이 나오는 방법이다.


1. https://github.com/unicode-org/icu/releases/tag/release-4-8-2 에서 jar 파일을 다운로드 한다.

http://site.icu-project.org/download/48#ICU4J-Download 페이지의 중간 정도에 Version: 4.8.2
Release Date: 2019-04-11 라고 나온다.


2. 다운로드 받은 파일 icu4j-4_8_2.jar 를 libs 폴더로 복사하거나 옮긴다.


3. dependencies 에 추가한다.

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    implementation files('libs/icu4j-4_8_2.jar')
}

* 자동 추가 방법은 Android Studio 왼쪽 메뉴에서 Project 로 선택하고 해당 파일에서 마우스 우클릭을 하면 Add As Library... 를 선택하여 추가하면 된다. 직접 dependencies 에 적어주고 Sync Now를 해도 된다.


4. 음력/양력 변환 코드

import com.ibm.icu.util.ChineseCalendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;

public class LunarCalendar {
    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    public static String Lunar2Solar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));

        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }

    /**
     * 양력날짜를 음력날짜로 변환
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력날짜 (yyyyMMdd)
     */
    public static String Solar2Lunar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim() ;
        if( date.length() != 8 ) {
            if( date.length() == 4 )
                date = date + "0101" ;
            else if( date.length() == 6 )
                date = date + "01" ;
            else if( date.length() > 8 )
                date = date.substring(0,8) ;
            else
                return "" ;
        }

        cal.set( Calendar.YEAR, Integer.parseInt(date.substring(0,4)) ) ;
        cal.set( Calendar.MONTH, Integer.parseInt(date.substring(4,6))-1 ) ;
        cal.set( Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)) ) ;

        cc.setTimeInMillis( cal.getTimeInMillis() ) ;

        // ChinessCalendar.YEAR 는 1~60 까지의 값만 가지고 ,
        // ChinessCalendar.EXTENDED_YEAR 는 Calendar.YEAR 값과 2637 만큼의 차이를 가진다.
        int y = cc.get(ChineseCalendar.EXTENDED_YEAR)-2637 ;
        int m = cc.get(ChineseCalendar.MONTH)+1 ;
        int d = cc.get(ChineseCalendar.DAY_OF_MONTH) ;

        StringBuffer ret = new StringBuffer() ;
        if( y < 1000 )          ret.append( "0" ) ;
        else if( y < 100 )      ret.append( "00" ) ;
        else if( y < 10 )       ret.append( "000" ) ;
        ret.append( y ) ;

        if( m < 10 ) ret.append( "0" ) ;
        ret.append( m ) ;

        if( d < 10 ) ret.append( "0" ) ;
        ret.append( d ) ;

        return ret.toString() ;
    }
}


5. 샘플 코드

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.tistory.link2me.util.LunarCalendar;
import java.util.Calendar;

public class CalendarActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calendar);

        System.out.println( LunarCalendar.Solar2Lunar("20010715") ) ; // 양력을 음력으로 바꾸기
        System.out.println( LunarCalendar.Lunar2Solar("20010527") ) ; // 음력을 양력으로 바꾸기
    }


이 라이브러리를 이용하는 방법은 간단하기는 한데 정교한 맛이 없는 거 같다.

윤달인지 여부, 간지 등을 표현할 수가 없는 점이 좀 아쉽다.


그래서 구글 검색을 하여 Java Lunar Calendar (Lunar Calendar) Tools 를 찾아봤더니

https://www.cnblogs.com/harry335/p/4861598.html 가 검색된다.

이걸 테스트해봤더니 음력일자 결과가 제대로 반환되지 않고 하루가 틀리게 나온다.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Ca03 {
    private int year;
    private int month;
    private int day;
    private boolean leap;
    final static String chineseNumber[] = { "1", "2", "3", "4", "5", "6", "7","8", "9", "10", "11", "12" };
    static SimpleDateFormat chineseDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
    final static long[] lunarInfo = new long[] { 0x04bd8, 0x04ae0, 0x0a570,
            0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
            0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0,
            0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50,
            0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566,
            0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0,
            0x1c8d7, 0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4,
            0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550,
            0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950,
            0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260,
            0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0,
            0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
            0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40,
            0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3,
            0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960,
            0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0,
            0x092d0, 0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9,
            0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954, 0x06aa0,
            0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65,
            0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0,
            0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2,
            0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0 };

    // ====== 传回农历 y年的总天数
    final private static int yearDays(int y) {
        int i, sum = 348;
        for (i = 0x8000; i > 0x8; i >>= 1) {
            if ((lunarInfo[y - 1900] & i) != 0)
                sum += 1;
        }
        return (sum + leapDays(y));
    }

    // ====== 传回农历 y年闰月的天数
    final private static int leapDays(int y) {
        if (leapMonth(y) != 0) {
            if ((lunarInfo[y - 1900] & 0x10000) != 0)
                return 30;
            else
                return 29;
        } else
            return 0;
    }

    // ====== 传回农历 y年闰哪个月 1-12 , 没闰传回 0
    final private static int leapMonth(int y) {
        return (int) (lunarInfo[y - 1900] & 0xf);
    }

    // ====== 传回农历 y年m月的总天数
    final private static int monthDays(int y, int m) {
        if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0)
            return 29;
        else
            return 30;
    }

    // ====== 传回农历 y年的生肖
    final public String animalsYear() {
        final String[] Animals = new String[] { "쥐", "소", "호랑이", "토끼", "용", "뱀", "말", "양", "원숭이", "닭", "개", "돼지"};
        return Animals[(year - 4) % 12];
    }

    // ====== 传入 月日的offset 传回干支, 0=甲子
    final private static String cyclicalm(int num) {
        final String[] Gan = new String[] { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
        final String[] Zhi = new String[] { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
        return (Gan[num % 10] + Zhi[num % 12]);
    }

    // ====== 传入 offset 传回干支, 0=甲子
    final public String cyclical() {
        int num = year - 1900 + 36;
        return (cyclicalm(num));
    }

    public Ca03(Calendar cal) {
        int yearCyl, monCyl, dayCyl;
        int leapMonth = 0;
        Date baseDate = null;
        try {
            baseDate = chineseDateFormat.parse("1900年1月31日");
        } catch (ParseException e) {
            e.printStackTrace(); // To change body of catch statement use
                                    // Options | File Templates.
        }

        // 求出和1900年1月31日相差的天数
        int offset = (int) ((cal.getTime().getTime() - baseDate.getTime()) / 86400000L);
        dayCyl = offset + 40;
        monCyl = 14;

        // 用offset减去每农历年的天数
        // 计算当天是农历第几天
        // i最终结果是农历的年份
        // offset是当年的第几天
        int iYear, daysOfYear = 0;
        for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) {
            daysOfYear = yearDays(iYear);
            offset -= daysOfYear;
            monCyl += 12;
        }
        if (offset < 0) {
            offset += daysOfYear;
            iYear--;
            monCyl -= 12;
        }
        // 农历年份
        year = iYear;

        yearCyl = iYear - 1864;
        leapMonth = leapMonth(iYear); // 闰哪个月,1-12
        leap = false;

        // 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
        int iMonth, daysOfMonth = 0;
        for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
            // 闰月
            if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) {
                --iMonth;
                leap = true;
                daysOfMonth = leapDays(year);
            } else
                daysOfMonth = monthDays(year, iMonth);

            offset -= daysOfMonth;
            // 解除闰月
            if (leap && iMonth == (leapMonth + 1))
                leap = false;
            if (!leap)
                monCyl++;
        }
        // offset为0时,并且刚才计算的月份是闰月,要校正
        if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
            if (leap) {
                leap = false;
            } else {
                leap = true;
                --iMonth;
                --monCyl;
            }
        }
        // offset小于0时,也要校正
        if (offset < 0) {
            offset += daysOfMonth;
            --iMonth;
            --monCyl;
        }
        month = iMonth;
        day = offset + 1;
    }

    public static String getChinaDayString(int day) {
        String chineseTen[] = { "初", "十", "廿", "卅" };
        int n = day % 10 == 0 ? 9 : day % 10 - 1;
        if (day > 30)
            return "";
        if (day == 10)
            return "初十";
        else
            return chineseTen[day / 10] + chineseNumber[n];
    }

    public String toString() {
        return year + "年 " + (leap ? "闰" : "") + chineseNumber[month - 1] + "月 " + day +"日";
    }
   
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        cal.setTimeZone(TimeZone.getDefault());
        System.out.println("양력(오늘):" + sdf.format(cal.getTime()));
       
        int year = 2001;
        int month = 1;
        int date = 25;
        cal.set(year, month-1, date);

        Ca03 lunar = new Ca03(cal);
        System.out.print("음력(오늘):");
        System.out.print(lunar.year + "年 ");
        System.out.print(lunar.month + "月 ");
        System.out.print(lunar.day+"日");

        System.out.println("\n*************\n" + lunar.animalsYear());
        System.out.println(lunar.cyclical());
        System.out.println(lunar);
    }

}



PHP 로 된 만세력 변환 코드가 올려져 있는 거 같다.

https://github.com/OOPS-ORG-PHP/Lunar/releases

이 코드를 변환해 보지는 않았다.


다음에는 PHP 에서 음력/양력 변환하는 코드를 자바로 변환해본 것을 올릴 것이다.

블로그 이미지

Link2Me

,