com.esri.arcgis.android.samples.nearby.Nearby.java Source code

Java tutorial

Introduction

Here is the source code for com.esri.arcgis.android.samples.nearby.Nearby.java

Source

/* Copyright 2015 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.arcgis.android.samples.nearby;

import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RatingBar;
import android.widget.TextView;
import android.widget.Toast;

import com.esri.android.map.GraphicsLayer;
import com.esri.android.map.LocationDisplayManager;
import com.esri.android.map.MapView;
import com.esri.android.map.event.OnSingleTapListener;
import com.esri.android.map.event.OnStatusChangedListener;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.GeometryEngine;
import com.esri.core.geometry.LinearUnit;
import com.esri.core.geometry.MultiPoint;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.SpatialReference;
import com.esri.core.geometry.Unit;
import com.esri.core.map.CallbackListener;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.PictureMarkerSymbol;
import com.esri.core.symbol.Symbol;
import com.esri.core.tasks.geocode.Locator;
import com.esri.core.tasks.geocode.LocatorFindParameters;
import com.esri.core.tasks.geocode.LocatorGeocodeResult;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * The purpose of this sample is to use the extent of the map (either based on
 * the device GPS position, or the extent you set) to search for local services.
 * When you click on the buttons on the action bar, the respective shops in your
 * area are displayed on the map. The ArcGIS Online world locator service is
 * used.
 * Check device settings to make sure that location services are enabled for
 * the app.
 */
public class Nearby extends AppCompatActivity {

    private static final String TAG = Nearby.class.getSimpleName();

    // Define types of search.
    private enum SearchType {
        BAR, PIZZA, COFFEE,
    }

    SearchType mCurrentSearchType;
    final static double ZOOM_BY = 20;
    LinearUnit mMilesUnit = new LinearUnit(LinearUnit.Code.MILE_STATUTE);

    ProgressBar mProgress;
    MapView mMapView = null;
    SpatialReference mMapSr = null;
    GraphicsLayer mResultsLayer = null;
    PictureMarkerSymbol mCoffeeMapIcon, mBarMapIcon, mPizzaMapIcon;

    // Views to show selected search result information.
    TextView mTitleTextView;
    TextView mAddressTextView;
    TextView mPhoneTextView;
    ImageView mPhoneImageView;
    TextView mDistanceTextView;
    RatingBar mRatingBar;

    Locator mLocator;
    ArrayList<String> mFindOutFields = new ArrayList<>();

    LocationDisplayManager mLDM;

    /**
     * When the map is tapped, select the graphic at that location.
     */
    final OnSingleTapListener mapTapCallback = new OnSingleTapListener() {
        @Override
        public void onSingleTap(float x, float y) {
            // Find out if we tapped on a Graphic
            int[] graphicIDs = mResultsLayer.getGraphicIDs(x, y, 25);
            if (graphicIDs != null && graphicIDs.length > 0) {
                // If there is more than one graphic, only select the first found.
                if (graphicIDs.length > 1) {
                    int id = graphicIDs[0];
                    graphicIDs = new int[] { id };
                }

                // Only deselect the last graphic if user has tapped a new one. App
                // remains showing the last selected nearby service information,
                // as that is the main focus of the app.
                mResultsLayer.clearSelection();

                // Select the graphic
                mResultsLayer.setSelectedGraphics(graphicIDs, true);

                // Use the graphic attributes to update the information views.
                Graphic gr = mResultsLayer.getGraphic(graphicIDs[0]);
                updateContent(gr.getAttributes());
            }
        }
    };

    /**
     * When map is ready, set up the LocationDisplayManager.
     */
    final OnStatusChangedListener statusChangedListener = new OnStatusChangedListener() {

        private static final long serialVersionUID = 1L;

        @Override
        public void onStatusChanged(Object source, STATUS status) {
            if (source == mMapView && status == STATUS.INITIALIZED) {
                mMapSr = mMapView.getSpatialReference();
                if (mLDM == null) {
                    setupLocationListener();
                }
            }
        }
    };

    /**
     * When user touches phone number, send this to the dialler using an intent.
     */
    final View.OnTouchListener callTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            String num = mPhoneTextView.getText().toString();
            Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + num));
            startActivity(intent);
            return true;
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mMapView = (MapView) findViewById(R.id.map);
        mMapView.setOnStatusChangedListener(statusChangedListener);
        mMapView.setOnSingleTapListener(mapTapCallback);

        mTitleTextView = (TextView) findViewById(R.id.titleTextView);
        mTitleTextView.setText(R.string.startup_caption);
        mAddressTextView = (TextView) findViewById(R.id.addressTextView);
        mPhoneTextView = (TextView) findViewById(R.id.phoneTextView);
        mPhoneTextView.setOnTouchListener(callTouchListener);
        mPhoneImageView = (ImageView) findViewById(R.id.callImageView);
        mPhoneImageView.setOnTouchListener(callTouchListener);
        mDistanceTextView = (TextView) findViewById(R.id.distanceTextView);
        mRatingBar = (RatingBar) findViewById(R.id.ratingBar);
        mProgress = (ProgressBar) findViewById(R.id.findProgress);

        mResultsLayer = new GraphicsLayer();
        mResultsLayer.setSelectionColorWidth(6);
        mMapView.addLayer(mResultsLayer);

        mCoffeeMapIcon = new PictureMarkerSymbol(getApplicationContext(),
                ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_local_cafe_black));
        mPizzaMapIcon = new PictureMarkerSymbol(getApplicationContext(),
                ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_local_pizza_black));
        mBarMapIcon = new PictureMarkerSymbol(getApplicationContext(),
                ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_local_drink_black));

        setupLocator();
        setupLocationListener();
    }

    private void setupLocator() {
        // Parameterless constructor - uses the Esri world geocoding service.
        mLocator = Locator.createOnlineLocator();

        // Set up the outFields parameter for the search.
        mFindOutFields.add(getResources().getString(R.string.result_title));
        mFindOutFields.add(getResources().getString(R.string.result_type));
        mFindOutFields.add(getResources().getString(R.string.result_address));
        mFindOutFields.add(getResources().getString(R.string.result_phone));
        mFindOutFields.add(getResources().getString(R.string.result_distance));
    }

    private void setupLocationListener() {
        if ((mMapView != null) && (mMapView.isLoaded())) {
            mLDM = mMapView.getLocationDisplayManager();
            mLDM.setLocationListener(new LocationListener() {

                boolean locationChanged = false;

                // Zooms to the current location when first GPS fix arrives.
                @Override
                public void onLocationChanged(Location loc) {
                    if (!locationChanged) {
                        locationChanged = true;
                        zoomToLocation(loc);

                        // After zooming, turn on the Location pan mode to show the location
                        // symbol. This will disable as soon as you interact with the map.
                        mLDM.setAutoPanMode(LocationDisplayManager.AutoPanMode.LOCATION);
                    }
                }

                @Override
                public void onProviderDisabled(String arg0) {
                }

                @Override
                public void onProviderEnabled(String arg0) {
                }

                @Override
                public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
                }
            });

            mLDM.start();
        }
    }

    /**
     * Zoom to location using a specific size of extent.
     *
     * @param loc  the location to center the MapView at
     */
    private void zoomToLocation(Location loc) {
        Point mapPoint = getAsPoint(loc);
        Unit mapUnit = mMapSr.getUnit();
        double zoomFactor = Unit.convertUnits(ZOOM_BY, Unit.create(LinearUnit.Code.MILE_US), mapUnit);
        Envelope zoomExtent = new Envelope(mapPoint, zoomFactor, zoomFactor);
        mMapView.setExtent(zoomExtent);
    }

    /**
     * Performs a find using the Locator, for a specific type of business.
     *
     * @param searchFor A string containing the type of business to search for.
     */
    private void doFindNearbyAsync(String searchFor) {

        final CallbackListener<List<LocatorGeocodeResult>> findCallback = new CallbackListener<List<LocatorGeocodeResult>>() {

            @Override
            public void onError(Throwable e) {
                setProgressOnUIThread(false);

                // Log the error
                Log.e(TAG, "No Results Found");
                Log.e(TAG, e.getMessage());
                // Indicate to user we cannot show results in this area.
                showToastOnUiThread("Error searching for results");
            }

            @Override
            public void onCallback(List<LocatorGeocodeResult> results) {

                // remove any previous graphics
                mResultsLayer.removeAll();

                // Use a Multipoint as a simple way to set total extent.
                MultiPoint fullExtent = new MultiPoint();

                if (results.size() > 0) {
                    // Set specific symbols and selection color for each type of search.
                    Symbol symbol = null;
                    if (mCurrentSearchType == SearchType.BAR) {
                        mResultsLayer.setSelectionColor(getResources().getColor(R.color.beer_selection));
                        symbol = mBarMapIcon;
                    } else if (mCurrentSearchType == SearchType.PIZZA) {
                        mResultsLayer.setSelectionColor(getResources().getColor(R.color.pizza_selection));
                        symbol = mPizzaMapIcon;
                    } else if (mCurrentSearchType == SearchType.COFFEE) {
                        mResultsLayer.setSelectionColor(getResources().getColor(R.color.coffee_selection));
                        symbol = mCoffeeMapIcon;
                    }

                    // For each result, create a Graphic, using result attributes as
                    // graphic attributes.
                    for (LocatorGeocodeResult result : results) {
                        Point resultPoint = result.getLocation();
                        HashMap<String, Object> attrMap = new HashMap<String, Object>(result.getAttributes());
                        mResultsLayer.addGraphic(new Graphic(resultPoint, symbol, attrMap));
                        fullExtent.add(resultPoint);
                    }
                    // Zoom to the full extent
                    mMapView.setExtent(fullExtent, 100);
                }
                // Update the UI with the result information.
                setResultCount(results.size(), mCurrentSearchType);
            }
        };

        try {
            setProgressOnUIThread(true);

            // Get the current map extent.
            Envelope currExt = new Envelope();
            mMapView.getExtent().queryEnvelope(currExt);

            // Set up locator parameters based on the extent, search type, and the
            // outfields set previously.
            LocatorFindParameters fParams = new LocatorFindParameters(searchFor);
            fParams.setSearchExtent(currExt, mMapSr);
            fParams.setOutSR(mMapSr);
            fParams.setOutFields(mFindOutFields);

            // If LocationDisplayManger has a current location, set this to increase
            // priority and return a distance value in the results.
            if ((mLDM != null) && (mLDM.getLocation() != null)) {
                Point currentPoint = getAsPoint(mLDM.getLocation());
                fParams.setLocation(currentPoint, mMapSr);
            }

            // Call find, passing in the callback above.
            mLocator.find(fParams, findCallback);
        } catch (Exception e) {
            // Update UI and report any errors.
            setProgressOnUIThread(false);

            // Log the error
            Log.e(TAG, "No Results Found");
            Log.e(TAG, e.getMessage());

            // Indicate to user we cannot show results in this area.
            showToastOnUiThread("Error searching for results");
        }
    }

    private static String getRating() {
        // Randomized ratings could be replaced by a ratings service from a third
        // party.
        Random r = new Random();
        return String.valueOf(1 + (r.nextFloat() * 4));
    }

    /**
     * Update user interface with result set information. Ensure this can be
     * called from either background or UI thread by performing any actions on
     * Views within a runnable on the UI thread.
     *
     * @param resultCount  number of results in the result set
     * @param searchType  type of business searched for
     */
    private void setResultCount(int resultCount, SearchType searchType) {
        String searchTypeMessage = "";

        switch (searchType) {
        case COFFEE:
            searchTypeMessage = getResources().getString(R.string.results_coffee);
            break;
        case PIZZA:
            searchTypeMessage = getResources().getString(R.string.results_pizza);
            break;
        case BAR:
            searchTypeMessage = getResources().getString(R.string.results_bar);
            break;
        }

        final String message = String.format("Found %d %s", resultCount, searchTypeMessage);

        runOnUiThread(new Runnable() {
            public void run() {
                mProgress.setIndeterminate(false);
                mTitleTextView.setText(message);
                mAddressTextView.setText("");
                mPhoneTextView.setText("");
                mPhoneImageView.setImageDrawable(null);
                mDistanceTextView.setText("");
                mRatingBar.setVisibility(View.GONE);
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_layout, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
        case R.id.bar:
            if (mMapView.isLoaded()) {
                clearCurrentResults();
                mCurrentSearchType = SearchType.BAR;
                try {
                    doFindNearbyAsync(getResources().getString(R.string.bar_query));
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
            return true;

        case R.id.pizza:
            if (mMapView.isLoaded()) {
                mCurrentSearchType = SearchType.PIZZA;
                try {
                    doFindNearbyAsync(getResources().getString(R.string.pizza_query));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            return true;

        case R.id.coffee:
            if (mMapView.isLoaded()) {
                mCurrentSearchType = SearchType.COFFEE;
                try {
                    doFindNearbyAsync(getResources().getString(R.string.coffee_query));
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

            return true;

        case R.id.locate:
            if (mMapView.isLoaded()) {
                // If LocationDisplayManager has a fix, pan to that location. If no
                // fix yet, this will happen when the first fix arrives, due to
                // callback set up previously.
                if ((mLDM != null) && (mLDM.getLocation() != null)) {
                    // Keep current scale and go to current location, if there is one.
                    mLDM.setAutoPanMode(LocationDisplayManager.AutoPanMode.LOCATION);
                }
            }

            return true;
        default:
            return super.onOptionsItemSelected(item);

        }
    }

    private void clearCurrentResults() {
        if (mResultsLayer != null) {
            mResultsLayer.removeAll();
        }
        mTitleTextView.setText("");
        mAddressTextView.setText("");
        mPhoneTextView.setText("");
        mPhoneImageView.setImageDrawable(null);
        mDistanceTextView.setText("");
        mRatingBar.setRating(0);
        mRatingBar.setVisibility(View.GONE);
    }

    private Point getAsPoint(Location loc) {
        Point wgsPoint = new Point(loc.getLongitude(), loc.getLatitude());
        return (Point) GeometryEngine.project(wgsPoint, SpatialReference.create(4326), mMapSr);
    }

    public void updateContent(Map<String, Object> attributes) {
        // This is called from UI thread (MapTap listener)

        String title = attributes.get(getResources().getString(R.string.result_title)).toString();
        mTitleTextView.setText(title);

        String address = attributes.get(getResources().getString(R.string.result_address)).toString();
        mAddressTextView.setText(address);

        String distance = attributes.get(getResources().getString(R.string.result_distance)).toString();
        double meters = Double.parseDouble(distance);
        if (meters > 0) {
            if (mDistanceTextView.getVisibility() != View.VISIBLE) {
                mDistanceTextView.setVisibility(View.VISIBLE);
            }
            double miles = mMilesUnit.convertFromMeters(meters);
            mDistanceTextView.setText(String.format("%.2f %s", miles, getResources().getString(R.string.miles)));
        } else {
            mDistanceTextView.setVisibility(View.GONE);
        }

        String phone = attributes.get(getResources().getString(R.string.result_phone)).toString();
        mPhoneTextView.setText(phone);
        mPhoneImageView
                .setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_action_call));

        Float rating = Float.parseFloat(getRating());
        mRatingBar.setRating(rating);
        mRatingBar.setVisibility(View.VISIBLE);
    }

    public void showToastOnUiThread(final String message) {
        runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(Nearby.this, message, Toast.LENGTH_SHORT).show();
            }
        });
    }

    public void setProgressOnUIThread(final boolean isIndeterminate) {
        runOnUiThread(new Runnable() {
            public void run() {
                mProgress.setIndeterminate(isIndeterminate);
            }
        });
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMapView.pause();
        if (mLDM != null) {
            mLDM.pause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        mMapView.unpause();
        if (mLDM != null) {
            mLDM.resume();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mLDM != null) {
            mLDM.stop();
        }
    }

}