Android Geocoder反向地理编码

时间:2020-02-23 14:28:56  来源:igfitidea点击:

Android Geocoder类用于反向地理编码,即从Google Map上的位置检索地址。
如果您不知道如何在Android应用程序中使用Google Maps,请先阅读android google maps教程,然后再继续。

Android Geocoder

Android" Geocoder"类用于地理编码以及反向地理编码。
地理编码是指将街道地址或者任何地址转换为纬度和经度。
反向地理编码是指将纬度和经度转换为其相应的街道地址。

"地址"类有助于获取该位置的街道地址,位置,子位置,城市,国家/地区地标等特征。

使用以上两个类,我们将在应用程序中的Google地图上获取当前标记地址。
让我们从android反向地理编码示例项目开始。

Android Geocoder反向地理编码项目结构

我们需要Google Maps和Google Places API。
因此,如下所示,将它们添加到" build.gradle"文件中。

apply plugin: 'com.android.application'

allprojects {
  repositories {
      jcenter()
      maven {
          url "https://maven.google.com"
      }
  }
}

android {
  compileSdkVersion 26
  buildToolsVersion "26.0.1"
  defaultConfig {
      applicationId "com.theitroad.reversegeocoding"
      minSdkVersion 16
      targetSdkVersion 26
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  }
  buildTypes {
      release {
          minifyEnabled false
          proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      }
  }
}

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
      exclude group: 'com.android.support', module: 'support-annotations'
  })
  compile 'com.android.support:appcompat-v7:26.0.1'
  compile 'com.android.support:cardview-v7:26.0.1'
  compile 'com.android.support.constraint:constraint-layout:1.0.2'
  compile 'com.google.android.gms:play-services-maps:11.0.4'
  compile 'com.google.android.gms:play-services-places:11.0.4'
  testCompile 'junit:junit:4.12'
}

从Google Developer Console获取相关的API密钥,并将其添加到ʻAndroidManifest.xml`文件的元数据中。
如果您不知道如何在应用程序中集成Google API,请先阅读本教程,然后再继续。

Android反向地理编码代码

下面给出了" activity_main.xml"布局的代码。

<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:card_view="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.CardView
      android:id="@+id/cardView"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="12dp"
      card_view:cardCornerRadius="0dp"
      card_view:cardElevation="0dp">

      <RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_gravity="center">

          <TextView
              android:id="@+id/txtLocationAddress"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignParentTop="true"
              android:layout_centerHorizontal="true"
              android:ellipsize="marquee"
              android:focusable="true"
              android:focusableInTouchMode="true"
              android:gravity="center"
              android:marqueeRepeatLimit="marquee_forever"
              android:maxLines="1"
              android:padding="16dp"
              android:scrollHorizontally="true"
              android:text="Current Marker Address" 

      </RelativeLayout>

  </android.support.v7.widget.CardView>

  <FrameLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <fragment
          android:id="@+id/mapFragment"
          android:name="com.google.android.gms.maps.SupportMapFragment"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:tag="tag_map_fragment" 

      <FrameLayout
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center">

          <ImageView
              android:id="@+id/centerMarker"
              android:layout_width="48dp"
              android:layout_height="48dp"
              android:layout_gravity="center"
              android:src="@drawable/black_marker" 

      </FrameLayout>

  </FrameLayout>

</RelativeLayout>

我们的布局包含一个CardView,其中包含一个TextView,最终将显示当前地址。
标记放置在屏幕中央的ImageView中。

下面给出了MainActivity.java类的代码。

package com.theitroad.reversegeocoding;

import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlaceAutocomplete;
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.LatLng;

import java.io.IOException;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

  TextView txtLocationAddress;
  SupportMapFragment mapFragment;
  GoogleMap map;
  LatLng center;
  CardView cardView;
  private static final int PLACE_AUTOCOMPLETE_REQUEST_CODE = 1;

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

      txtLocationAddress = findViewById(R.id.txtLocationAddress);
      txtLocationAddress.setEllipsize(TextUtils.TruncateAt.MARQUEE);
      txtLocationAddress.setSingleLine(true);
      txtLocationAddress.setMarqueeRepeatLimit(-1);
      txtLocationAddress.setSelected(true);

      cardView = findViewById(R.id.cardView);

      cardView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              try {
                  Intent intent =
                          new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
                                  .build(MainActivity.this);
                  startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE);
              } catch (GooglePlayServicesRepairableException e) {
                  printToast("Google Play Service Repair");
              } catch (GooglePlayServicesNotAvailableException e) {
                  printToast("Google Play Service Not Available");
              }
          }
      });

      mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
      mapFragment.getMapAsync(new OnMapReadyCallback() {
          @Override
          public void onMapReady(GoogleMap googleMap) {
              map = googleMap;
              map.getUiSettings().setZoomControlsEnabled(true);
              LatLng latLng = new LatLng(20.5937, 78.9629);
              map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16));
              initCameraIdle();
          }
      });
  }

  private void initCameraIdle() {
      map.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
          @Override
          public void onCameraIdle() {
              center = map.getCameraPosition().target;
              getAddressFromLocation(center.latitude, center.longitude);
          }
      });
  }

  private void getAddressFromLocation(double latitude, double longitude) {

      Geocoder geocoder = new Geocoder(this, Locale.ENGLISH);

      try {
          List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);

          if (addresses.size() > 0) {
              Address fetchedAddress = addresses.get(0);
              StringBuilder strAddress = new StringBuilder();
              for (int i = 0; i < fetchedAddress.getMaxAddressLineIndex(); i++) {
                  strAddress.append(fetchedAddress.getAddressLine(i)).append(" ");
              }

              txtLocationAddress.setText(strAddress.toString());

          } else {
              txtLocationAddress.setText("Searching Current Address");
          }

      } catch (IOException e) {
          e.printStackTrace();
          printToast("Could not get address..!");
      }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) {
          if (resultCode == RESULT_OK) {
              Place place = PlaceAutocomplete.getPlace(this, data);
              if (!place.getAddress().toString().contains(place.getName())) {
                  txtLocationAddress.setText(place.getName() + ", " + place.getAddress());
              } else {
                  txtLocationAddress.setText(place.getAddress());
              }

              CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(place.getLatLng(), 16);
              map.animateCamera(cameraUpdate);

          } else if (resultCode == PlaceAutocomplete.RESULT_ERROR) {
              printToast("Error in retrieving place info");

          }
      }
  }

  private void printToast(String message) {
      Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
  }
}

让我们分解上面的代码,看看它能做什么。

  • 我们已通过编程方式在TextView上设置了属性setEllipize()和setMarqueeRepeatLimit(),因为在xml中设置相同的属性不会再在所有设备上滚动文本。
    如果内容长度大于TextView的宽度,则上述方法可使TextView的内容水平滚动。

  • setMarqueeRepeatLimit()中传递-1使其永远滚动。

  • 使用SupportMapFragment显示Google Map。
    将getMapAsync()回调分配给该片段。
    存在Google Play服务时会触发此回调。

  • onMapReady()方法是显示Google Map并返回GoogleMap类的非空实例的方法。
    我们将其分配给实例变量" map"。

map.getUiSettings()。
setZoomControlsEnabled(true)用于设置屏幕上的缩放控件。

  • map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng,16))将地图移到latLng中指定的位置(旧金山的某些位置!)。
    相机缩放到屏幕中心放置标记的位置。

  • setOnCameraIdleListener将侦听地图上的移动/拖动。
    当运动结束时,方法onCameraIdle()被触发。
    其中我们检索地图中心的纬度和经度(因为该标记位于此位置),并将其传递给方法" getAddressFromLocation()",该方法最终将进行反向地理编码。

  • 方法getFromLocation(double latitude,double longitude,int maxResults)`返回当前位置的地址列表。

  • 在"地址"对象中,方法" getAddressLine(int index)"返回由给定索引编号的地址行;如果不存在地址,则返回null。

  • 我们将该地址附加到StringBuilder,该字符串最终显示在TextView中。

  • getMaxAddressLineIndex()返回当前用于指定地址行的最大索引。

  • 地址的其他一些重要细节也可以检索。
    下面列出了其中一些:getThoroughfare():这是一条街道,其中包含交付点。
    如果不存在,则返回null。

  • getSubThoroughfare():当一个城镇中存在一个以上的通途名称时,subThoroughfare名称将用于其他信息。

  • getLocality()和getSublocality():返回地址的位置,例如," Mountain View"和子位置(如果可用)或者返回null。

  • getFeatureName():返回最近的地标名称(如果有)或者为null。

  • getCountryName()getCountryCode():分别返回国家名称和国家代码。

  • 点击" CardView"会从Google Places API启动PlaceAutoComplete服务。

  • 我们使用PlaceAutocomplete.IntentBuilder来创建Intent并将Mode作为全屏传递。

  • 所选位置将返回到onActivityResult()中,该结果最终将显示在TextView中。
    " Place"的纬度和经度以" place.getLatLng()"的形式返回,最终将其放大到屏幕中心。