ArcGIS Runtime SDK for Android

Offline geocode

Screenshot of Offline Geocode App

Loading

Code

/* Copyright 2016 Esri
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.esri.arcgisruntime.sample.offlinegeocode;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.data.TileCache;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.geometry.SpatialReference;
import com.esri.arcgisruntime.layers.ArcGISTiledLayer;
import com.esri.arcgisruntime.loadable.LoadStatus;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.Basemap;
import com.esri.arcgisruntime.mapping.Viewpoint;
import com.esri.arcgisruntime.mapping.view.Callout;
import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener;
import com.esri.arcgisruntime.mapping.view.Graphic;
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay;
import com.esri.arcgisruntime.mapping.view.IdentifyGraphicsOverlayResult;
import com.esri.arcgisruntime.mapping.view.MapView;
import com.esri.arcgisruntime.symbology.PictureMarkerSymbol;
import com.esri.arcgisruntime.tasks.geocode.GeocodeParameters;
import com.esri.arcgisruntime.tasks.geocode.GeocodeResult;
import com.esri.arcgisruntime.tasks.geocode.LocatorTask;
import com.esri.arcgisruntime.tasks.geocode.ReverseGeocodeParameters;

public class MainActivity extends AppCompatActivity {

  private static final String TAG = MainActivity.class.getSimpleName();
  final int requestCode = 2;
  final String[] permission = new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE };
  private final String extern = Environment.getExternalStorageDirectory().getPath();
  ArcGISMap mMap;
  ArcGISTiledLayer tiledLayer;
  Spinner mSpinner;
  private GraphicsOverlay graphicsOverlay;
  private GeocodeParameters mGeocodeParameters;
  private PictureMarkerSymbol mPinSourceSymbol;
  private MapView mMapView;
  private LocatorTask mLocatorTask;
  private ReverseGeocodeParameters mReverseGeocodeParameters;
  private Callout mCallout;
  private SearchView mSearchview;
  private String mGraphicPointAddress;
  private Point mGraphicPoint;
  private GeocodeResult mGeocodedLocation;
  private boolean isPinSelected;
  private TextView mCalloutContent;

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

    // inflate MapView from layout
    mMapView = (MapView) findViewById(R.id.mapView);

    // Check permissions to see if failure may be due to lack of permissions.
    boolean permissionCheck = ContextCompat.checkSelfPermission(MainActivity.this, permission[0]) ==
        PackageManager.PERMISSION_GRANTED;

    if (!permissionCheck) {
      // If permissions are not already granted, request permission from the user.
      ActivityCompat.requestPermissions(MainActivity.this, permission, requestCode);
    } else { // if permission was already granted, set up offline map and geocoding, reverse geocoding and LocatorTask
      setUpOfflineMapGeocoding();
      setSearchView();
    }
    mMapView.setOnTouchListener(new MapTouchListener(getApplicationContext(), mMapView));
  }

  private void setSearchView() {

    mSearchview = (SearchView) findViewById(R.id.searchView1);
    mSearchview.setIconifiedByDefault(true);
    mSearchview.setQueryHint(getResources().getString(R.string.search_hint));
    mSearchview.setOnQueryTextListener(new OnQueryTextListener() {
      @Override
      public boolean onQueryTextSubmit(String query) {
        hideKeyboard();
        geoCodeTypedAddress(query);
        mSearchview.clearFocus();
        return true;
      }

      @Override
      public boolean onQueryTextChange(String newText) {
        return false;
      }
    });

    mSpinner = (Spinner) findViewById(R.id.spinner);
    // Create an ArrayAdapter using the string array and a default spinner layout
    final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(this,
        android.R.layout.simple_spinner_dropdown_item) {
      @Override
      public View getView(int position, View convertView, ViewGroup parent) {

        View v = super.getView(position, convertView, parent);
        if (position == getCount()) {
          mSearchview.clearFocus();
        }

        return v;
      }

      @Override
      public int getCount() {
        return super.getCount() - 1; // you dont display last item. It is used as hint.
      }

    };

    // Specify the layout to use when the list of choices appears
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    adapter.addAll(getResources().getStringArray(R.array.suggestion_items));

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
      // set vertical offset to spinner dropdown for API less than 21
      mSpinner.setDropDownVerticalOffset(80);
    }
    // Apply the adapter to the spinner
    mSpinner.setAdapter(adapter);
    mSpinner.setSelection(adapter.getCount());

    mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
      @Override
      public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (position == adapter.getCount()) {
          mSearchview.clearFocus();
        } else {
          hideKeyboard();
          mSearchview.setQuery(getResources().getStringArray(R.array.suggestion_items)[position], false);
          geoCodeTypedAddress(getResources().getStringArray(R.array.suggestion_items)[position]);
          mSearchview.setIconified(false);
          mSearchview.clearFocus();
        }
      }

      @Override
      public void onNothingSelected(AdapterView<?> parent) {
      }
    });

  }

  private void setUpOfflineMapGeocoding() {
    // create a basemap from a local tile package
    TileCache tileCache = new TileCache(extern + getResources().getString(R.string.sandiego_tpk));
    tiledLayer = new ArcGISTiledLayer(tileCache);
    Basemap basemap = new Basemap(tiledLayer);

    // create ArcGISMap with imagery basemap
    mMap = new ArcGISMap(basemap);

    mMapView.setMap(mMap);

    mMap.addDoneLoadingListener(new Runnable() {
      @Override
      public void run() {
        Point p = new Point(-117.162040, 32.718260, SpatialReference.create(4326));
        Viewpoint vp = new Viewpoint(p, 10000);
        mMapView.setViewpointAsync(vp, 3);
      }
    });

    // add a graphics overlay
    graphicsOverlay = new GraphicsOverlay();
    graphicsOverlay.setSelectionColor(Color.CYAN);
    mMapView.getGraphicsOverlays().add(graphicsOverlay);

    mGeocodeParameters = new GeocodeParameters();
    mGeocodeParameters.getResultAttributeNames().add("*");
    mGeocodeParameters.setMaxResults(1);


    //Create a picture marker symbol from an app resource
    BitmapDrawable startDrawable = (BitmapDrawable) ContextCompat.getDrawable(this, R.drawable.pin);
    mPinSourceSymbol = new PictureMarkerSymbol(startDrawable);
    mPinSourceSymbol.setHeight(90);
    mPinSourceSymbol.setWidth(20);
    mPinSourceSymbol.loadAsync();
    mPinSourceSymbol.setLeaderOffsetY(45);
    mPinSourceSymbol.setOffsetY(-48);

    mReverseGeocodeParameters = new ReverseGeocodeParameters();
    mReverseGeocodeParameters.getResultAttributeNames().add("*");
    mReverseGeocodeParameters.setOutputSpatialReference(mMap.getSpatialReference());
    mReverseGeocodeParameters.setMaxResults(1);

    mLocatorTask = new LocatorTask(extern + getResources().getString(R.string.sandiego_loc));

    mCalloutContent = new TextView(getApplicationContext());
    mCalloutContent.setTextColor(Color.BLACK);
    mCalloutContent.setTextIsSelectable(true);
  }

  /**
   * Geocode an address typed in by user
   *
   * @param address
   */
  private void geoCodeTypedAddress(final String address) {
    // Null out any previously located result
    mGeocodedLocation = null;

    // Execute async task to find the address
    mLocatorTask.addDoneLoadingListener(new Runnable() {
      @Override
      public void run() {
        if (mLocatorTask.getLoadStatus() == LoadStatus.LOADED) {
          // Call geocodeAsync passing in an address
          final ListenableFuture<List<GeocodeResult>> geocodeFuture = mLocatorTask.geocodeAsync(address,
              mGeocodeParameters);
          geocodeFuture.addDoneListener(new Runnable() {
            @Override
            public void run() {
              try {
                // Get the results of the async operation
                List<GeocodeResult> geocodeResults = geocodeFuture.get();

                if (geocodeResults.size() > 0) {
                  // Use the first result - for example
                  // display on the map
                  mGeocodedLocation = geocodeResults.get(0);
                  displaySearchResult(mGeocodedLocation.getDisplayLocation(), mGeocodedLocation.getLabel());

                } else {
                  Toast.makeText(getApplicationContext(),
                      getString(R.string.location_not_foud) + address,
                      Toast.LENGTH_LONG).show();
                }

              } catch (InterruptedException | ExecutionException e) {
                // Deal with exception...
                e.printStackTrace();
                Toast.makeText(getApplicationContext(),
                    getString(R.string.geo_locate_error),
                    Toast.LENGTH_LONG).show();

              }
              // Done processing and can remove this listener.
              geocodeFuture.removeDoneListener(this);
            }
          });

        } else {
          Log.i(TAG, "Trying to reload locator task");
          mLocatorTask.retryLoadAsync();
        }
      }
    });
    mLocatorTask.loadAsync();
  }

  /**
   * Hides soft keyboard
   */
  private void hideKeyboard() {
    mSearchview.clearFocus();
    InputMethodManager inputManager = (InputMethodManager) getApplicationContext()
        .getSystemService(Context.INPUT_METHOD_SERVICE);
    inputManager.hideSoftInputFromWindow(mSearchview.getWindowToken(), 0);
  }

  private void displaySearchResult(Point resultPoint, String address) {

    if (mMapView.getCallout().isShowing()) {
      mMapView.getCallout().dismiss();
    }
    //remove any previous graphics/search results
    //mMapView.getGraphicsOverlays().clear();
    graphicsOverlay.getGraphics().clear();
    // create graphic object for resulting location
    Graphic resultLocGraphic = new Graphic(resultPoint, mPinSourceSymbol);
    // add graphic to location layer
    graphicsOverlay.getGraphics().add(resultLocGraphic);

    // Zoom map to geocode result location
    mMapView.setViewpointAsync(new Viewpoint(resultPoint, 8000), 3);

    mGraphicPoint = resultPoint;
    mGraphicPointAddress = address;
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    // If request is cancelled, the result arrays are empty.
    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
      // Location permission was granted. This would have been triggered in response to failing to start the
      // LocationDisplay, so try starting this again.
      setUpOfflineMapGeocoding();
      setSearchView();
    } else {
      // If permission was denied, show toast to inform user what was chosen. If LocationDisplay is started again,
      // request permission UX will be shown again, option should be shown to allow never showing the UX again.
      // Alternative would be to disable functionality so request is not shown again.
      Toast.makeText(MainActivity.this, getResources().getString(R.string.storage_permission_denied), Toast
          .LENGTH_SHORT).show();

    }
  }

  @Override
  protected void onPause() {
    super.onPause();
    mMapView.pause();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mMapView.resume();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    mMapView.dispose();
  }

  private class DragTouchListener extends DefaultMapViewOnTouchListener {

    float dX, dY;

    public DragTouchListener(Context context, MapView mapView) {
      super(context, mapView);
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {

      switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:
          dX = view.getX() - event.getRawX();
          dY = view.getY() - event.getRawY();
          break;

        case MotionEvent.ACTION_MOVE:
          final int pointerIndex = MotionEventCompat.getActionIndex(event);
          final float x = MotionEventCompat.getX(event, pointerIndex);
          final float y = MotionEventCompat.getY(event, pointerIndex);
          android.graphics.Point screenPoint = new android.graphics.Point(Math.round(x), Math.round(y));
          final Point singleTapPoint = mMapView.screenToLocation(screenPoint);
          final ListenableFuture<List<GeocodeResult>> results = mLocatorTask.reverseGeocodeAsync(singleTapPoint,
              mReverseGeocodeParameters);
          graphicsOverlay.getGraphics().clear();
          Graphic resultLocGraphic = new Graphic(singleTapPoint, mPinSourceSymbol);
          resultLocGraphic.setSelected(true);
          // add graphic to location layer
          graphicsOverlay.getGraphics().add(resultLocGraphic);
          // display callout with reverse-geocode result on UI thread
          runOnUiThread(new Runnable() {
            @Override
            public void run() {
              try {
                List<GeocodeResult> geocodes = results.get();
                if (geocodes.size() > 0) {
                  // get the top result
                  GeocodeResult geocode = geocodes.get(0);
                  String detail;
                  // attributes from a click-based search
                  String street = geocode.getAttributes().get("Street").toString();
                  String city = geocode.getAttributes().get("City").toString();
                  String state = geocode.getAttributes().get("State").toString();
                  String zip = geocode.getAttributes().get("ZIP").toString();
                  detail = city + ", " + state + " " + zip;

                  String address = street + "," + detail;
                  mCalloutContent.setText(address);
                  // get callout, set content and show
                  mCallout = mMapView.getCallout();
                  mCallout.setLocation(singleTapPoint);
                  mCallout.setContent(mCalloutContent);
                  mCallout.show();

                  mGraphicPoint = singleTapPoint;
                  mGraphicPointAddress = address;
                }

              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          });

          break;
        case MotionEvent.ACTION_UP:
          if (graphicsOverlay.getGraphics().size() > 0) {
            graphicsOverlay.getGraphics().get(0).setSelected(false);
            isPinSelected = false;
            mMapView.setOnTouchListener(new MapTouchListener(getApplicationContext(), mMapView));
          }
          break;
        default:
          return false;
      }
      return true;
    }
  }

  private class MapTouchListener extends DefaultMapViewOnTouchListener {

    public MapTouchListener(Context context, MapView mapView) {
      super(context, mapView);
    }

    @Override
    public void onLongPress(MotionEvent e) {
      android.graphics.Point screenPoint = new android.graphics.Point(Math.round(e.getX()),
          Math.round(e.getY()));

      Point longPressPoint = mMapView.screenToLocation(screenPoint);

      ListenableFuture<List<GeocodeResult>> results = mLocatorTask.reverseGeocodeAsync(longPressPoint,
          mReverseGeocodeParameters);
      results.addDoneListener(new ResultsLoadedListener(results));

    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {

      if (mMapView.getCallout().isShowing()) {
        mMapView.getCallout().dismiss();
      }
      if (graphicsOverlay.getGraphics().size() > 0) {
        if (graphicsOverlay.getGraphics().get(0).isSelected()) {
          isPinSelected = false;
          graphicsOverlay.getGraphics().get(0).setSelected(false);
        }
      }
      // get the screen point where user tapped
      final android.graphics.Point screenPoint = new android.graphics.Point((int) e.getX(), (int) e.getY());

      // identify graphics on the graphics overlay
      final ListenableFuture<IdentifyGraphicsOverlayResult> identifyGraphic = mMapView
          .identifyGraphicsOverlayAsync(graphicsOverlay, screenPoint, 1.0, false, 1);

      identifyGraphic.addDoneListener(new Runnable() {
        @Override
        public void run() {
          try {
            IdentifyGraphicsOverlayResult grOverlayResult = identifyGraphic.get();
            // get the list of graphics returned by identify
            List<Graphic> graphic = grOverlayResult.getGraphics();
            // if identified graphic is not empty, start DragTouchListener
            if (!graphic.isEmpty()) {

              if (!isPinSelected) {
                isPinSelected = true;
                graphic.get(0).setSelected(true);
                Toast.makeText(getApplicationContext(),
                    getString(R.string.reverse_geocode_message),
                    Toast.LENGTH_SHORT).show();
                mMapView.setOnTouchListener(new DragTouchListener(getApplicationContext(), mMapView));
              }

              mCalloutContent.setText(mGraphicPointAddress);
              // get callout, set content and show
              mCallout = mMapView.getCallout();
              mCallout.setContent(mCalloutContent);
              mCallout.setLocation(mGraphicPoint);
              mCallout.show();
            }
          } catch (InterruptedException | ExecutionException ie) {
            ie.printStackTrace();
          }

        }
      });

      return super.onSingleTapConfirmed(e);
    }
  }

  /**
   * Updates marker and callout when new results are loaded.
   */
  private class ResultsLoadedListener implements Runnable {

    private final ListenableFuture<List<GeocodeResult>> results;

    /**
     * Constructs a runnable listener for the geocode results.
     *
     * @param results results from a {@link LocatorTask#geocodeAsync} task
     */
    ResultsLoadedListener(ListenableFuture<List<GeocodeResult>> results) {
      this.results = results;
    }

    @Override
    public void run() {

      try {
        List<GeocodeResult> geocodes = results.get();
        if (geocodes.size() > 0) {
          // get the top result
          GeocodeResult geocode = geocodes.get(0);

          // set the viewpoint to the marker
          Point location = geocode.getDisplayLocation();
          // get attributes from the result for the callout
          String title;
          String detail;
          Object matchAddr = geocode.getAttributes().get("Match_addr");
          if (matchAddr != null) {
            // attributes from a query-based search
            title = matchAddr.toString().split(",")[0];
            detail = matchAddr.toString().substring(matchAddr.toString().indexOf(",") + 1);
          } else {
            // attributes from a click-based search
            String street = geocode.getAttributes().get("Street").toString();
            String city = geocode.getAttributes().get("City").toString();
            String state = geocode.getAttributes().get("State").toString();
            String zip = geocode.getAttributes().get("ZIP").toString();
            title = street;
            detail = city + ", " + state + " " + zip;
          }

          // get attributes from the result for the callout
          HashMap<String, Object> attributes = new HashMap<>();
          attributes.put("title", title);
          attributes.put("detail", detail);

          // create the marker
          Graphic marker = new Graphic(geocode.getDisplayLocation(), attributes, mPinSourceSymbol);
          graphicsOverlay.getGraphics().clear();

          // add the markers to the graphics overlay
          graphicsOverlay.getGraphics().add(marker);

          if (isPinSelected) {
            marker.setSelected(true);
          }
          String calloutText = title + ", " + detail;
          mCalloutContent.setText(calloutText);
          // get callout, set content and show
          mCallout = mMapView.getCallout();
          mCallout.setLocation(geocode.getDisplayLocation());
          mCallout.setContent(mCalloutContent);
          mCallout.show();

          mGraphicPoint = location;
          mGraphicPointAddress = title + ", " + detail;
        }

      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}


In this topic
  1. Code