Java tutorial
/* Copyright 1995-2013 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. * * For additional information, contact: * Environmental Systems Research Institute, Inc. * Attn: Contracts Dept * 380 New York Street * Redlands, California, USA 92373 * * email: contracts@esri.com * */ package com.esri.android.rt.map; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.location.Location; import android.location.LocationListener; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.GridView; import android.widget.Toast; import com.arcgis.android.app.map.R; import com.esri.android.map.GraphicsLayer; import com.esri.android.map.LocationService; import com.esri.android.map.MapView; import com.esri.android.map.ags.ArcGISFeatureLayer; import com.esri.android.map.event.OnLongPressListener; import com.esri.android.map.event.OnStatusChangedListener; import com.esri.android.map.popup.Popup; import com.esri.android.rt.location.DirectionsActivity; import com.esri.android.rt.location.ReverseGeocoding; import com.esri.android.rt.map.PopupFragment.OnEditListener; import com.esri.core.geometry.Envelope; import com.esri.core.geometry.Geometry; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.LinearUnit; import com.esri.core.geometry.Point; import com.esri.core.geometry.Polyline; import com.esri.core.geometry.SpatialReference; import com.esri.core.geometry.Unit; import com.esri.core.map.CallbackListener; import com.esri.core.map.FeatureEditResult; import com.esri.core.map.Graphic; import com.esri.core.portal.Portal; import com.esri.core.portal.PortalGroup; import com.esri.core.portal.PortalInfo; import com.esri.core.portal.PortalItem; import com.esri.core.portal.PortalItemType; import com.esri.core.portal.PortalQueryParams; import com.esri.core.portal.PortalQueryParams.PortalQuerySortOrder; import com.esri.core.portal.PortalQueryResultSet; import com.esri.core.symbol.PictureMarkerSymbol; import com.esri.core.symbol.SimpleLineSymbol; import com.esri.core.symbol.SimpleMarkerSymbol; import com.esri.core.symbol.TextSymbol; import com.esri.core.tasks.ags.geocode.Locator; import com.esri.core.tasks.ags.geocode.LocatorFindParameters; import com.esri.core.tasks.ags.geocode.LocatorGeocodeResult; import com.esri.core.tasks.ags.na.NAFeaturesAsFeature; import com.esri.core.tasks.ags.na.Route; import com.esri.core.tasks.ags.na.RoutingParameters; import com.esri.core.tasks.ags.na.RoutingResult; import com.esri.core.tasks.ags.na.RoutingTask; import com.esri.core.tasks.ags.na.StopGraphic; /** * Entry point into the Maps App. * */ public class MapsApp extends FragmentActivity implements OnEditListener { // map definitions static MapView mMapView = null; // Recreation webmap URL String recWebMapURL; int basemap; // Geocoding definitions Locator locator; LocatorGeocodeResult geocodeResult; GeocoderTask mGeocode; // graphics layer to show geocode result GraphicsLayer locationLayer; // GPS Location definitions Point mLocation = null; // The circle area specified by search_radius and input lat/lon serves // searching purpose. It is also used to construct the extent which // map zooms to after the first GPS fix is retrieved. final static double SEARCH_RADIUS = 10; // Spatial references used for projecting points final SpatialReference wm = SpatialReference.create(102100); final SpatialReference egs = SpatialReference.create(4326); // create UI components static ProgressDialog dialog; // Edit text box for entering search items EditText searchText; GridView gridView; BasemapsAdapter bAdapter; ArrayList<BasemapItem> itemDataList; Portal portal; PortalQueryResultSet<PortalItem> queryResultSet; // Strings for routing String startText; String endText; Point routePnt; // routing result definition RoutingResult routeResult; // route definition Route route; String routeSummary; // graphics layer to show routes GraphicsLayer routeLayer; // bundle to get routing parameters back to UI Bundle extras; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retrieve the map and initial extent from recreation webmap recWebMapURL = getString(R.string.rec_webmap_url); mMapView = new MapView(this, recWebMapURL, "", ""); // set the content view to the map // setContentView(mMapView); setMapView(mMapView); // attribute app and pan across dateline addAttributes(); // Zoom to device location and accept intent from route layout mMapView.setOnStatusChangedListener(new OnStatusChangedListener() { private static final long serialVersionUID = 1L; @Override public void onStatusChanged(Object source, STATUS status) { if (source == mMapView && status == STATUS.INITIALIZED) { // add search and routing layers addGraphicLayers(); // start location service LocationService ls = mMapView.getLocationService(); ls.setAutoPan(false); ls.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; double locy = loc.getLatitude(); double locx = loc.getLongitude(); Point wgspoint = new Point(locx, locy); mLocation = (Point) GeometryEngine.project(wgspoint, SpatialReference.create(4326), mMapView.getSpatialReference()); Unit mapUnit = mMapView.getSpatialReference().getUnit(); double zoomWidth = Unit.convertUnits(SEARCH_RADIUS, Unit.create(LinearUnit.Code.MILE_US), mapUnit); Envelope zoomExtent = new Envelope(mLocation, zoomWidth, zoomWidth); mMapView.setExtent(zoomExtent); extras = getIntent().getExtras(); if (extras != null) { startText = extras.getString("start"); endText = extras.getString("end"); basemap = extras.getInt("basemap"); // route start and end points route(startText, endText); } } } @Override public void onProviderDisabled(String arg0) { } @Override public void onProviderEnabled(String arg0) { } @Override public void onStatusChanged(String arg0, int arg1, Bundle arg2) { } }); ls.start(); } } }); mMapView.setOnLongPressListener(new OnLongPressListener() { private static final long serialVersionUID = 1L; @Override public void onLongPress(float x, float y) { Point mapPoint = mMapView.toMapPoint(x, y); new ReverseGeocoding(MapsApp.this, mMapView).execute(mapPoint); } }); } public void setMapView(MapView mapView) { mMapView = mapView; mMapView.setOnSingleTapListener(new SingleTapListener(mMapView)); setContentView(mMapView); } private void addAttributes() { // attribute ESRI logo to map mMapView.setEsriLogoVisible(true); // enable map to wrap around date line mMapView.enableWrapAround(true); } private void addGraphicLayers() { // Add location layer locationLayer = new GraphicsLayer(); mMapView.addLayer(locationLayer); // Add the route graphic layer (shows the full route) routeLayer = new GraphicsLayer(); mMapView.addLayer(routeLayer); } @Override public boolean onCreateOptionsMenu(Menu menu) { // inflate basemaps action bar menu getMenuInflater().inflate(R.menu.basemap_menu, menu); // create action view from basemap menu View searchRef = menu.findItem(R.id.menu_search).getActionView(); // get a reference to EditText field searchText = (EditText) searchRef.findViewById(R.id.searchText); // return return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // handle basemap item selection switch (item.getItemId()) { case R.id.route: Intent directionsIntent = new Intent(MapsApp.this, DirectionsActivity.class); directionsIntent.putExtra("basemap", basemap); startActivity(directionsIntent); return true; case R.id.basemaps: // inflate grid layout with basemap options LayoutInflater inflator = LayoutInflater.from(getApplicationContext()); gridView = (GridView) inflator.inflate(R.layout.grid_layout, null); // array list to hold basemap items itemDataList = new ArrayList<BasemapItem>(); // create the custom basemap adapter and send basemap items and // mapview to switch basemaps bAdapter = new BasemapsAdapter(MapsApp.this, itemDataList, mMapView, mMapView.getExtent()); // set the adapter to the gridview gridView.setAdapter(bAdapter); // pull up the gridview setContentView(gridView); // populate the gridview with available basemaps new BasemapSearch().execute(); return true; default: return super.onOptionsItemSelected(item); } } /** * Submit address for place search * * @param view */ public void locate(View view) { // hide virtual keyboard InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); // remove any previous graphics and callouts locationLayer.removeAll(); // remove any previous routes routeLayer.removeAll(); // obtain address from text box String address = searchText.getText().toString(); // set parameters to support the find operation for a geocoding service setSearchParams(address); } /** * Set up the search parameters to execute the Geocoder task * * @param address */ private void setSearchParams(String address) { // create Locator parameters from single line address string LocatorFindParameters findParams = new LocatorFindParameters(address); // set the search extent to extent of map // SpatialReference inSR = mMapView.getSpatialReference(); // Envelope searchExtent = mMapView.getMapBoundaryExtent(); // Use the centre of the current map extent as the find location point findParams.setLocation(mMapView.getCenter(), mMapView.getSpatialReference()); // calculate distance for find operation Envelope mapExtent = new Envelope(); mMapView.getExtent().queryEnvelope(mapExtent); // assume map is in metres, other units wont work // double current envelope double distance = (mapExtent != null && mapExtent.getWidth() > 0) ? mapExtent.getWidth() * 2 : 10000; findParams.setDistance(distance); findParams.setMaxLocations(2); // set address spatial reference to match map findParams.setOutSR(mMapView.getSpatialReference()); // execute async task to geocode address mGeocode = new GeocoderTask(this); mGeocode.execute(findParams); } /** * Submit start and end point for routing * * @param start * @param end */ public void route(String start, String end) { // remove any previous graphics and callouts locationLayer.removeAll(); // remove any previous routes routeLayer.removeAll(); // set parameters to geocode address for points setRouteParams(start, end); } /** * Set up Route Parameters to execute RouteTask * * @param start * @param end */ @SuppressWarnings("unchecked") // http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000217.html private void setRouteParams(String start, String end) { // create a list of start end point params LocatorFindParameters routeStartParams = new LocatorFindParameters(start); LocatorFindParameters routeEndParams = new LocatorFindParameters(end); List<LocatorFindParameters> routeParams = new ArrayList<LocatorFindParameters>(); // add params to list routeParams.add(routeStartParams); routeParams.add(routeEndParams); // run asych route task new RouteTask().execute(routeParams); } @Override public void onDelete(ArcGISFeatureLayer fl, Popup popup) { // Commit deletion to server Graphic gr = popup.getGraphic(); if (gr == null) return; fl.applyEdits(null, new Graphic[] { gr }, null, new EditCallbackListener(this, fl, popup, true, "Deleting feature")); // Dismiss popup this.getSupportFragmentManager().popBackStack(); } @Override public void onEdit(ArcGISFeatureLayer fl, Popup popup) { // Set popup into editing mode popup.setEditMode(true); // refresh menu items this.invalidateOptionsMenu(); } @Override public void onSave(ArcGISFeatureLayer fl, Popup popup) { // Commit edits to server Graphic gr = popup.getGraphic(); if (gr != null) { Map<String, Object> attributes = gr.getAttributes(); Map<String, Object> updatedAttrs = popup.getUpdatedAttributes(); for (Entry<String, Object> entry : updatedAttrs.entrySet()) { attributes.put(entry.getKey(), entry.getValue()); } Graphic newgr = new Graphic(gr.getGeometry(), null, attributes, null); fl.applyEdits(null, null, new Graphic[] { newgr }, new EditCallbackListener(this, fl, popup, true, "Saving feature")); } // Dismiss popup this.getSupportFragmentManager().popBackStack(); } @Override protected void onDestroy() { super.onDestroy(); if (mGeocode != null) { mGeocode.mActivity = null; } } @Override protected void onPause() { super.onPause(); } @Override protected void onResume() { super.onResume(); } /* * AsyncTask to geocode an address to a point location Draw resulting point * location on the map with matching address */ private class GeocoderTask extends AsyncTask<LocatorFindParameters, Void, List<LocatorGeocodeResult>> { WeakReference<MapsApp> mActivity; GeocoderTask(MapsApp activity) { mActivity = new WeakReference<MapsApp>(activity); } @Override protected void onPreExecute() { // show progress dialog while geocoding address dialog = ProgressDialog.show(mMapView.getContext(), "Geocoder", "Searching for address ..."); } // The result of geocode task is passed as a parameter to map the // results @Override protected void onPostExecute(List<LocatorGeocodeResult> result) { if (dialog.isShowing()) { dialog.dismiss(); } // The result of geocode task is passed as a parameter to map the // results if (result == null || result.size() == 0) { // update UI with notice that no results were found Toast toast = Toast.makeText(MapsApp.this, "No result found.", Toast.LENGTH_LONG); toast.show(); } else { // get first result in the list // update global result geocodeResult = result.get(0); // get return geometry from geocode result Geometry resultLocGeom = geocodeResult.getLocation(); // create marker symbol to represent location SimpleMarkerSymbol resultSymbol = new SimpleMarkerSymbol(Color.BLACK, 20, SimpleMarkerSymbol.STYLE.SQUARE); // create graphic object for resulting location Graphic resultLocation = new Graphic(resultLocGeom, resultSymbol); // add graphic to location layer locationLayer.addGraphic(resultLocation); // create text symbol for return address TextSymbol resultAddress = new TextSymbol(12, geocodeResult.getAddress(), Color.BLACK); // create offset for text resultAddress.setOffsetX(10); resultAddress.setOffsetY(50); // create a graphic object for address text Graphic resultText = new Graphic(resultLocGeom, resultAddress); // add address text graphic to location graphics layer locationLayer.addGraphic(resultText); // zoom to geocode result mMapView.zoomToResolution(geocodeResult.getLocation(), 2); } } @Override protected List<LocatorGeocodeResult> doInBackground(LocatorFindParameters... params) { // create results object and set to null List<LocatorGeocodeResult> results = null; // set the geocode service locator = new Locator(); try { // pass address to find method to return point representing // address results = locator.find(params[0]); } catch (Exception e) { e.printStackTrace(); } // return the resulting point(s) return results; } } private class RouteTask extends AsyncTask<List<LocatorFindParameters>, Void, RoutingResult> { @Override protected void onPreExecute() { // show progress dialog while geocoding address dialog = ProgressDialog.show(mMapView.getContext(), "Routing", "Searching for route ..."); } @Override protected void onPostExecute(RoutingResult result) { if (dialog.isShowing()) { dialog.dismiss(); } // The result of geocode task is passed as a parameter to map the // results if (result == null) { // update UI with notice that no results were found Toast toast = Toast.makeText(MapsApp.this, "No result found.", Toast.LENGTH_LONG); toast.show(); } else { route = result.getRoutes().get(0); // Symbols for the route and the destination SimpleLineSymbol routeSymbol = new SimpleLineSymbol(Color.BLUE, 3); PictureMarkerSymbol destinationSymbol = new PictureMarkerSymbol( getResources().getDrawable(R.drawable.stat_finish)); // graphic to mark route Graphic routeGraphic = new Graphic(route.getRoute().getGeometry(), routeSymbol); Graphic endGraphic = new Graphic(((Polyline) routeGraphic.getGeometry()) .getPoint(((Polyline) routeGraphic.getGeometry()).getPointCount() - 1), destinationSymbol); // Get the full route summary and set it as our current label routeLayer.addGraphics(new Graphic[] { routeGraphic, endGraphic }); // Zoom to the extent of the entire route with a padding mMapView.setExtent(route.getEnvelope(), 100); } } @Override protected RoutingResult doInBackground(List<LocatorFindParameters>... params) { // define route objects List<LocatorGeocodeResult> geocodeStartResult = null; List<LocatorGeocodeResult> geocodeEndResult = null; Point startPoint = null; Point endPoint = null; // parse LocatorFindParameters LocatorFindParameters startParam = params[0].get(0); LocatorFindParameters endParam = params[0].get(1); // create a new locator to geocode start/end points Locator locator = new Locator(); try { // if GPS then location known and can be reprojected if (startParam.getText().equals("My Location")) { // startPoint = mLocation; startPoint = (Point) GeometryEngine.project(mLocation, wm, egs); } else { // if not GPS than we need to geocode and get location geocodeStartResult = locator.find(startParam); startPoint = geocodeStartResult.get(0).getLocation(); } // geocode the destination geocodeEndResult = locator.find(endParam); endPoint = geocodeEndResult.get(0).getLocation(); } catch (Exception e) { e.printStackTrace(); } // build routing parameters RoutingParameters routeParams = new RoutingParameters(); NAFeaturesAsFeature routeFAF = new NAFeaturesAsFeature(); // Create the stop points StopGraphic sgStart = new StopGraphic(startPoint); StopGraphic sgEnd = new StopGraphic(endPoint); routeFAF.setFeatures(new Graphic[] { sgStart, sgEnd }); routeFAF.setCompressedRequest(true); routeParams.setStops(routeFAF); routeParams.setOutSpatialReference(mMapView.getSpatialReference()); // Create a new routing task pointing to an // NAService RoutingTask routingTask = new RoutingTask(getString(R.string.routingservice_url)); try { // Solve the route routeResult = routingTask.solve(routeParams); } catch (Exception e) { e.printStackTrace(); } return routeResult; } } private class BasemapSearch extends AsyncTask<Void, Void, Boolean> { @Override protected void onPreExecute() { // show progress dialog while searching basemaps dialog = ProgressDialog.show(mMapView.getContext(), "Basemaps View", "Searching Basemaps ..."); } @Override protected Boolean doInBackground(Void... params) { try { fetchBasemapsItems(); } catch (Exception e) { e.printStackTrace(); } return true; } @Override protected void onPostExecute(Boolean update) { // dismiss dialog if (dialog.isShowing()) { dialog.dismiss(); } if (update == true) { // update the adapter bAdapter.notifyDataSetChanged(); } } private void fetchBasemapsItems() throws Exception { // GIST > https://gist.github.com/doneill/5499642 // Open default portal String url = "http://www.arcgis.com"; portal = new Portal(url, null); // get the information provided by portal PortalInfo portalInfo = portal.fetchPortalInfo(); // get query to determine which basemap gallery group should be used // in client String basemapGalleryGroupQuery = portalInfo.getBasemapGalleryGroupQuery(); // create a PortalQueryParams from the basemap query PortalQueryParams portalQueryParams = new PortalQueryParams(basemapGalleryGroupQuery); // allow public search for basemaps portalQueryParams.setCanSearchPublic(true); // find groups for basemaps PortalQueryResultSet<PortalGroup> results = portal.findGroups(portalQueryParams); // get the basemap group List<PortalGroup> groupResults = results.getResults(); // Get the portal items if (groupResults != null && groupResults.size() > 0) { PortalQueryParams queryParams = new PortalQueryParams(); queryParams.setCanSearchPublic(true); queryParams.setLimit(15); String groupID = groupResults.get(0).getGroupId(); queryParams.setQuery(PortalItemType.WEBMAP, groupID, null); queryParams.setSortField("name").setSortOrder(PortalQuerySortOrder.ASCENDING); // can't run on UI thread queryResultSet = portal.findItems(queryParams); for (PortalItem item : queryResultSet.getResults()) { byte[] data = item.fetchThumbnail(); if (data != null) { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); BasemapItem portalItemData = new BasemapItem(item, bitmap); Log.i("TAG", "Item id = " + item.getTitle()); itemDataList.add(portalItemData); } } } else { Log.i("TAG", "portal group empty"); } } } // Handle callback on committing edits to server private class EditCallbackListener implements CallbackListener<FeatureEditResult[][]> { private String operation = "Operation "; private ArcGISFeatureLayer featureLayer = null; private boolean existingFeature = true; private Popup popup = null; private Context context; public EditCallbackListener(Context context, ArcGISFeatureLayer featureLayer, Popup popup, boolean existingFeature, String msg) { this.operation = msg; this.featureLayer = featureLayer; this.existingFeature = existingFeature; this.popup = popup; this.context = context; } @Override public void onCallback(FeatureEditResult[][] objs) { if (featureLayer == null || !featureLayer.isInitialized() || !featureLayer.isEditable()) return; runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, operation + " succeeded!", Toast.LENGTH_SHORT).show(); } }); if (objs[1] == null || objs[1].length <= 0) { // Save attachments to the server if newly added attachments // exist. // Retrieve object id of the feature int oid; if (existingFeature) { oid = objs[2][0].getObjectId(); } else { oid = objs[0][0].getObjectId(); } // Get newly added attachments List<File> attachments = popup.getAddedAttachments(); if (attachments != null && attachments.size() > 0) { for (File attachment : attachments) { // Save newly added attachment based on the object id of // the feature. featureLayer.addAttachment(oid, attachment, new CallbackListener<FeatureEditResult>() { @Override public void onError(Throwable e) { // Failed to save new attachments. runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, "Adding attachment failed!", Toast.LENGTH_SHORT) .show(); } }); } @Override public void onCallback(FeatureEditResult arg0) { // New attachments have been saved. runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, "Adding attachment succeeded!.", Toast.LENGTH_SHORT) .show(); } }); } }); } } // Delete attachments if some attachments have been mark as // delete. // Get ids of attachments which are marked as delete. List<Integer> attachmentIDs = popup.getDeletedAttachmentIDs(); if (attachmentIDs != null && attachmentIDs.size() > 0) { int[] ids = new int[attachmentIDs.size()]; for (int i = 0; i < attachmentIDs.size(); i++) { ids[i] = attachmentIDs.get(i); } // Delete attachments featureLayer.deleteAttachments(oid, ids, new CallbackListener<FeatureEditResult[]>() { @Override public void onError(Throwable e) { // Failed to delete attachments runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, "Deleting attachment failed!", Toast.LENGTH_SHORT) .show(); } }); } @Override public void onCallback(FeatureEditResult[] objs) { // Attachments have been removed. runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, "Deleting attachment succeeded!", Toast.LENGTH_SHORT) .show(); } }); } }); } } } @Override public void onError(Throwable e) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, operation + " failed!", Toast.LENGTH_SHORT).show(); } }); } } }