From acd7cf18a9553e6744ca04752d8a8ac30a3107ab Mon Sep 17 00:00:00 2001 From: Josh Erb Date: Thu, 2 May 2019 13:30:59 -0400 Subject: [PATCH] adding physical unit turf circle example --- .../mapboxandroiddemo/MainActivity.java | 8 + .../src/main/AndroidManifest.xml | 7 + .../TurfPhysicalCircleActivity.java | 367 ++++++++++++++++++ ...ctivity_lab_turf_circle_physical_units.xml | 113 ++++++ .../src/main/res/values/activity_strings.xml | 12 + .../main/res/values/descriptions_strings.xml | 5 +- .../src/main/res/values/titles_strings.xml | 1 + .../src/main/res/values/urls_strings.xml | 3 +- 8 files changed, 513 insertions(+), 3 deletions(-) create mode 100644 MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/TurfPhysicalCircleActivity.java create mode 100644 MapboxAndroidDemo/src/main/res/layout/activity_lab_turf_circle_physical_units.xml diff --git a/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java b/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java index 2e5bce8f1..566fd8a79 100644 --- a/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java +++ b/MapboxAndroidDemo/src/global/java/com/mapbox/mapboxandroiddemo/MainActivity.java @@ -72,6 +72,7 @@ import com.mapbox.mapboxandroiddemo.examples.javaservices.StaticImageActivity; import com.mapbox.mapboxandroiddemo.examples.javaservices.TilequeryActivity; import com.mapbox.mapboxandroiddemo.examples.javaservices.TurfRingActivity; +import com.mapbox.mapboxandroiddemo.examples.javaservices.TurfPhysicalCircleActivity; import com.mapbox.mapboxandroiddemo.examples.labs.AnimatedImageGifActivity; import com.mapbox.mapboxandroiddemo.examples.labs.AnimatedMarkerActivity; import com.mapbox.mapboxandroiddemo.examples.labs.CalendarIntegrationActivity; @@ -1023,6 +1024,13 @@ private void initializeModels() { null, R.string.activity_java_services_turf_ring_url, false, BuildConfig.MIN_SDK_VERSION )); + exampleItemModels.add(new ExampleItemModel( + R.id.nav_java_services, + R.string.activity_java_services_turf_physical_circle_title, + R.string.activity_java_services_turf_physical_circle_description, + new Intent(MainActivity.this, TurfPhysicalCircleActivity.class), + null, + R.string.activity_java_services_tilequery_url, false, BuildConfig.MIN_SDK_VERSION)); exampleItemModels.add(new ExampleItemModel( R.id.nav_snapshot_image_generator, diff --git a/MapboxAndroidDemo/src/main/AndroidManifest.xml b/MapboxAndroidDemo/src/main/AndroidManifest.xml index 80f0a4150..56d3f88e9 100644 --- a/MapboxAndroidDemo/src/main/AndroidManifest.xml +++ b/MapboxAndroidDemo/src/main/AndroidManifest.xml @@ -158,6 +158,13 @@ android:name="android.support.PARENT_ACTIVITY" android:value="com.mapbox.mapboxandroiddemo.MainActivity" /> + + + diff --git a/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/TurfPhysicalCircleActivity.java b/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/TurfPhysicalCircleActivity.java new file mode 100644 index 000000000..9e9f93760 --- /dev/null +++ b/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/javaservices/TurfPhysicalCircleActivity.java @@ -0,0 +1,367 @@ +package com.mapbox.mapboxandroiddemo.examples.javaservices; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.SeekBar; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.LineString; +import com.mapbox.geojson.Point; +import com.mapbox.geojson.Polygon; +import com.mapbox.mapboxandroiddemo.R; +import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.maps.Style; +import com.mapbox.mapboxsdk.style.layers.FillLayer; +import com.mapbox.mapboxsdk.style.layers.SymbolLayer; +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; +import com.mapbox.mapboxsdk.utils.BitmapUtils; +import com.mapbox.turf.TurfMeta; +import com.mapbox.turf.TurfTransformation; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillOpacity; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset; +import static com.mapbox.turf.TurfConstants.UNIT_DEGREES; +import static com.mapbox.turf.TurfConstants.UNIT_KILOMETERS; +import static com.mapbox.turf.TurfConstants.UNIT_MILES; +import static com.mapbox.turf.TurfConstants.UNIT_RADIANS; + +/** + * Use {@link TurfTransformation#circle(Point, double, int, String)} to draw a circle + * at a center coordinate with it's radius specified in physical units (i.e. "miles"). + * Default number of steps is 64 and default unit of distance is kilometers. + * More information can be found at https://github.com/mapbox/mapbox-java/blob/ + * master/services-turf/src/main/java/com/mapbox/turf/TurfTransformation.java and + * at http://turfjs.org/docs/#circle. + */ +public class TurfPhysicalCircleActivity extends AppCompatActivity implements MapboxMap.OnMapClickListener, + AdapterView.OnItemSelectedListener { + + private static final String TURF_CALCULATION_FILL_LAYER_GEOJSON_SOURCE_ID + = "TURF_CALCULATION_FILL_LAYER_GEOJSON_SOURCE_ID"; + private static final String TURF_CALCULATION_FILL_LAYER_ID = "TURF_CALCULATION_FILL_LAYER_ID"; + private static final String CIRCLE_CENTER_SOURCE_ID = "CIRCLE_CENTER_SOURCE_ID"; + private static final String CIRCLE_CENTER_ICON_ID = "CIRCLE_CENTER_ICON_ID"; + private static final String CIRCLE_CENTER_LAYER_ID = "CIRCLE_CENTER_LAYER_ID"; + private static final Point DOWNTOWN_KATHMANDU = Point.fromLngLat(85.323283875, 27.7014884022); + private static final int RADIUS_SEEKBAR_DIFFERENCE = 1; + private static final int STEPS_SEEKBAR_DIFFERENCE = 1; + private static final int STEPS_SEEKBAR_MAX = 360; + private static final int RADIUS_SEEKBAR_MAX = 500; + + // Min is 4 because LinearRings need to be made up of 4 or more coordinates. + private static final int MINIMUM_CIRCLE_STEPS = 4; + private Point lastClickPoint = DOWNTOWN_KATHMANDU; + private MapView mapView; + private MapboxMap mapboxMap; + + // Not static final because they will be adjusted by the seekbars and spinner menu + private String circleUnit = UNIT_KILOMETERS; + private int circleSteps = 180; + private int circleRadius = 100; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Mapbox access token is configured here. This needs to be called either in your application + // object or in the same activity which contains the mapview. + Mapbox.getInstance(this, getString(R.string.access_token)); + + // This contains the MapView in XML and needs to be called after the access token is configured. + setContentView(R.layout.activity_lab_turf_circle_physical_units); + + mapView = findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(@NonNull MapboxMap mapboxMap) { + mapboxMap.setStyle(new Style.Builder().fromUri(Style.MAPBOX_STREETS) + .withImage(CIRCLE_CENTER_ICON_ID, BitmapUtils.getBitmapFromDrawable( + getResources().getDrawable(R.drawable.red_marker))) + .withSource(new GeoJsonSource(CIRCLE_CENTER_SOURCE_ID, + Feature.fromGeometry(DOWNTOWN_KATHMANDU))) + .withSource(new GeoJsonSource(TURF_CALCULATION_FILL_LAYER_GEOJSON_SOURCE_ID)) + .withLayer(new SymbolLayer(CIRCLE_CENTER_LAYER_ID, + CIRCLE_CENTER_SOURCE_ID).withProperties( + iconImage(CIRCLE_CENTER_ICON_ID), + iconIgnorePlacement(true), + iconAllowOverlap(true), + iconOffset(new Float[] {0f, -4f}) + )), new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + + TurfPhysicalCircleActivity.this.mapboxMap = mapboxMap; + + initPolygonCircleFillLayer(); + + final SeekBar circleStepsSeekbar = findViewById(R.id.circle_steps_seekbar); + circleStepsSeekbar.setMax(STEPS_SEEKBAR_MAX); + circleStepsSeekbar.incrementProgressBy(STEPS_SEEKBAR_DIFFERENCE); + circleStepsSeekbar.setProgress(STEPS_SEEKBAR_MAX / 2); + + final SeekBar circleRadiusSeekbar = findViewById(R.id.circle_radius_seekbar); + circleRadiusSeekbar.setMax(RADIUS_SEEKBAR_MAX + RADIUS_SEEKBAR_DIFFERENCE); + circleRadiusSeekbar.incrementProgressBy(RADIUS_SEEKBAR_DIFFERENCE); + circleRadiusSeekbar.setProgress(RADIUS_SEEKBAR_MAX / 2); + + final TextView circleStepsTextview = findViewById(R.id.circle_steps_textview); + circleStepsTextview.setText(String.format(getString( + R.string.polygon_circle_transformation_circle_steps), + circleStepsSeekbar.getProgress())); + + final TextView circleRadiusTextView = findViewById(R.id.circle_radius_textview); + circleRadiusTextView.setText(String.format(getString( + R.string.polygon_circle_transformation_circle_radius), + circleRadiusSeekbar.getProgress())); + + drawPolygonCircle(lastClickPoint); + + circleStepsSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (progress < MINIMUM_CIRCLE_STEPS) { + seekBar.setProgress(MINIMUM_CIRCLE_STEPS); + } + adjustSteps(R.string.polygon_circle_transformation_circle_steps, circleStepsTextview, + progress < MINIMUM_CIRCLE_STEPS ? MINIMUM_CIRCLE_STEPS : progress, STEPS_SEEKBAR_DIFFERENCE); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Not needed in this example. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (seekBar.getProgress() < MINIMUM_CIRCLE_STEPS) { + seekBar.setProgress(MINIMUM_CIRCLE_STEPS); + } + adjustSteps(R.string.polygon_circle_transformation_circle_steps, circleStepsTextview, + seekBar.getProgress(), STEPS_SEEKBAR_DIFFERENCE); + } + }); + + circleRadiusSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + adjustRadius(R.string.polygon_circle_transformation_circle_radius, circleRadiusTextView, + seekBar.getProgress(), STEPS_SEEKBAR_DIFFERENCE); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Not needed in this example. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + adjustRadius(R.string.polygon_circle_transformation_circle_radius, circleRadiusTextView, + seekBar.getProgress(), STEPS_SEEKBAR_DIFFERENCE); + } + }); + mapboxMap.addOnMapClickListener(TurfPhysicalCircleActivity.this); + initDistanceUnitSpinner(); + Toast.makeText(TurfPhysicalCircleActivity.this, + getString(R.string.polygon_circle_transformation_click_map_instruction), Toast.LENGTH_SHORT).show(); + } + }); + } + }); + } + + private void adjustRadius(int string, TextView textView, int progress, int difference) { + adjustTextView(string, textView, progress, difference); + circleRadius = progress; + drawPolygonCircle(lastClickPoint); + } + + private void adjustSteps(int string, TextView textView, int progress, int difference) { + adjustTextView(string, textView, progress, difference); + circleSteps = progress; + drawPolygonCircle(lastClickPoint); + } + + private void initDistanceUnitSpinner() { + Spinner spinner = findViewById(R.id.circle_units_spinner); + spinner.setOnItemSelectedListener(this); + ArrayAdapter adapter = ArrayAdapter.createFromResource(this, + R.array.polygon_circle_transformation_circle_distance_units_array, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + } + + @Override + public void onItemSelected(AdapterView parentAdapterView, View view, int position, long id) { + String selectedUnitInSpinnerMenu = String.valueOf(parentAdapterView.getItemAtPosition(position)); + switch (selectedUnitInSpinnerMenu) { + case "Kilometers": + circleUnit = UNIT_KILOMETERS; + break; + case "Miles": + circleUnit = UNIT_MILES; + break; + case "Degrees": + circleUnit = UNIT_DEGREES; + break; + case "Radians": + circleUnit = UNIT_RADIANS; + break; + default: + circleUnit = UNIT_KILOMETERS; + } + drawPolygonCircle(lastClickPoint); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + // Empty on purpose. Not used in this example. + } + + private void adjustTextView(int string, TextView textView, int progress, int difference) { + progress = progress / difference; + progress = progress * difference; + textView.setText(String.format(getString(string), progress)); + } + + @Override + public boolean onMapClick(@NonNull LatLng mapClickLatLng) { + mapboxMap.easeCamera(CameraUpdateFactory.newLatLng(mapClickLatLng)); + lastClickPoint = Point.fromLngLat(mapClickLatLng.getLongitude(), mapClickLatLng.getLatitude()); + moveCircleCenterMarker(lastClickPoint); + drawPolygonCircle(lastClickPoint); + return true; + } + + /** + * Move the red marker icon to wherever the map was tapped on. + * + * @param circleCenter where the red marker icon will be moved to. + */ + private void moveCircleCenterMarker(Point circleCenter) { + mapboxMap.getStyle(new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + // Use Turf to calculate the Polygon's coordinates + GeoJsonSource markerSource = style.getSourceAs(CIRCLE_CENTER_SOURCE_ID); + if (markerSource != null) { + markerSource.setGeoJson(circleCenter); + } + } + }); + } + + /** + * Update the {@link FillLayer} based on the GeoJSON retrieved via + * {@link #getTurfPolygon(Point, double, int, String)}. + * + * @param circleCenter the center coordinate to be used in the Turf calculation. + */ + private void drawPolygonCircle(Point circleCenter) { + mapboxMap.getStyle(new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + // Use Turf to calculate the Polygon's coordinates + Polygon polygonArea = getTurfPolygon(circleCenter, circleRadius, circleSteps, circleUnit); + GeoJsonSource polygonCircleSource = style.getSourceAs(TURF_CALCULATION_FILL_LAYER_GEOJSON_SOURCE_ID); + if (polygonCircleSource != null) { + polygonCircleSource.setGeoJson(Polygon.fromOuterInner( + LineString.fromLngLats(TurfMeta.coordAll(polygonArea, false)))); + } + } + }); + } + + /** + * Use the Turf library {@link TurfTransformation#circle(Point, double, int, String)} method to + * retrieve a {@link Polygon} . + * + * @param centerPoint a {@link Point} which the circle will center around + * @param radius the radius of the circle + * @param steps number of steps which make up the circle parameter + * @param units one of the units found inside {@link com.mapbox.turf.TurfConstants} + * @return a {@link Polygon} which represents the newly created circle + */ + private Polygon getTurfPolygon(@NonNull Point centerPoint, @NonNull double radius, + @NonNull int steps, @NonNull String units) { + return TurfTransformation.circle(centerPoint, radius, steps, units); + } + + /** + * Add a {@link FillLayer} to display a {@link Polygon} in a the shape of a circle. + */ + private void initPolygonCircleFillLayer() { + mapboxMap.getStyle(new Style.OnStyleLoaded() { + @Override + public void onStyleLoaded(@NonNull Style style) { + // Create and style a FillLayer based on information that will come from the Turf calculation + FillLayer fillLayer = new FillLayer(TURF_CALCULATION_FILL_LAYER_ID, + TURF_CALCULATION_FILL_LAYER_GEOJSON_SOURCE_ID); + fillLayer.setProperties( + fillColor(Color.parseColor("#f5425d")), + fillOpacity(.7f)); + style.addLayerBelow(fillLayer, CIRCLE_CENTER_LAYER_ID); + } + }); + } + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + public void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } +} \ No newline at end of file diff --git a/MapboxAndroidDemo/src/main/res/layout/activity_lab_turf_circle_physical_units.xml b/MapboxAndroidDemo/src/main/res/layout/activity_lab_turf_circle_physical_units.xml new file mode 100644 index 000000000..d5e965cb9 --- /dev/null +++ b/MapboxAndroidDemo/src/main/res/layout/activity_lab_turf_circle_physical_units.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MapboxAndroidDemo/src/main/res/values/activity_strings.xml b/MapboxAndroidDemo/src/main/res/values/activity_strings.xml index 0a222bc3d..328266125 100644 --- a/MapboxAndroidDemo/src/main/res/values/activity_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/activity_strings.xml @@ -394,4 +394,16 @@ You want to live within how many minutes from your downtown Vienna office? %1$d min + + Click map to move circle + Steps: %1$d + Radius: %1$d + Unit: + + Kilometers + Miles + Degrees + Radians + + \ No newline at end of file diff --git a/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml b/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml index 4eec7122c..aaf78a837 100644 --- a/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/descriptions_strings.xml @@ -85,14 +85,15 @@ Use Mapbox Java Services to request directions. Use Mapbox\'s Optimization API to retrieve and display the quickest multi-stop route. Use Mapbox Java Services to build a url and download a static map. - Use the Matrix API to receive driving times between 2-25 different locations. + Use the Matrix API to receive driving times between 2–25 different locations. Use the Geocoding API to receive information about specific coordinates. Use the Isochrone API to receive information about how far you can travel within a given time. - Use an Android seekbar slider to retrieve info from the the Isochrone API. Display the data and adjust the camera accordingly. + Use an Android seekbar slider to retrieve info from the Isochrone API. Display the data and adjust the camera accordingly. Use the Tilequery API to search for features in a tileset. This example queries for up to 10 buildings which are within 50 meters of the single map click location. Using the polylines utility, simplify a polyline which reduces the amount of coordinates making up the polyline depending on tolerance. Match raw GPS points to the map so they aligns with the roads/pathways. Use Turf to calculate coordinates to eventually draw a ring around a center coordinate. + Use Turf to generate a circle with a radius expressed in physical units (e.g. miles, kilometers, etc). Use the traffic plugin to display live car congestion data on top of a map. Use the building plugin to easily display 3D building height Easily retrieve GeoJSON data from a url, asset, or path diff --git a/MapboxAndroidDemo/src/main/res/values/titles_strings.xml b/MapboxAndroidDemo/src/main/res/values/titles_strings.xml index 3f01d6447..aa68e5bb0 100644 --- a/MapboxAndroidDemo/src/main/res/values/titles_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/titles_strings.xml @@ -91,6 +91,7 @@ Simplify a polyline Map Matching Hollow circle + Define a circle in physical units Display real-time traffic Display buildings in 3D Change map text to device language diff --git a/MapboxAndroidDemo/src/main/res/values/urls_strings.xml b/MapboxAndroidDemo/src/main/res/values/urls_strings.xml index 9e7bacc09..72091bb2f 100644 --- a/MapboxAndroidDemo/src/main/res/values/urls_strings.xml +++ b/MapboxAndroidDemo/src/main/res/values/urls_strings.xml @@ -88,10 +88,11 @@ https://i.imgur.com/9xl3EF8.png https://i.imgur.com/eUByGvt.png https://i.imgur.com/hCVswtE.png - http://i.imgur.com/VkOCYwq.jpg + https://i.imgur.com/mjUbe2H.png http://i.imgur.com/uATgul1.png https://i.imgur.com/ig8gGnY.png https://i.imgur.com/nG8xeXH.png + https://i.imgur.com/nG8xeXH.png http://i.imgur.com/HRriOVR.png http://i.imgur.com/Vcu67UR.png https://i.imgur.com/oKHx3bv.png