package com.jasonette.seed.Component; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.DisplayMetrics; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.jasonette.seed.Core.JasonViewActivity; import com.jasonette.seed.Helper.JasonHelper; import org.json.JSONArray; import org.json.JSONObject; public class JasonMapComponent { static int EQUATOR_LENGTH = 40075004; public static View build(View view, final JSONObject component, final JSONObject parent, final Context context) { if(view == null){ try { MapsInitializer.initialize(context); GoogleMapOptions options = new GoogleMapOptions(); LatLng latlng = new LatLng(0, 0); if(component.has("region")) { latlng = getCoordinates(component.getJSONObject("region")); } options.camera(new CameraPosition(latlng, 16, 0, 0)); JSONObject style = component.getJSONObject("style"); if (style.has("type")) { switch ((String) style.get("type")) { case "satellite": options.mapType(GoogleMap.MAP_TYPE_SATELLITE); break; case "hybrid": options.mapType(GoogleMap.MAP_TYPE_HYBRID); break; case "terrain": options.mapType(GoogleMap.MAP_TYPE_TERRAIN); break; default: options.mapType(GoogleMap.MAP_TYPE_NORMAL); } } else { options.mapType(GoogleMap.MAP_TYPE_NORMAL); } MapView mapview = new MapView(context, options); mapview.onCreate(null); // Trigger onCreate ((JasonViewActivity)context).addListViewOnItemTouchListener(touchListener); // Add pins when the map is ready mapview.getMapAsync(new MapReadyHandler(component, mapview, context)); return mapview; } catch (Exception err) { Log.d("Error", err.toString()); } } else { try { JasonComponent.build(view, component, parent, context); JasonHelper.style(component, context); JasonComponent.addListener(view, context); view.requestLayout(); ((MapView)view).onResume(); // Trigger onResume return view; } catch (Exception err){ Log.d("Error", err.toString()); } } return new View(context); } private static LatLng getCoordinates(JSONObject position) { // Calculate latitude and longitude double latitude = 0.0; double longitude = 0.0; try { String[] r = position.getString("coord").split(","); if (r.length == 2) { latitude = Double.parseDouble(r[0]); longitude = Double.parseDouble(r[1]); } } catch (Exception err) { Log.d("Error", err.toString()); } return new LatLng(latitude, longitude); } static class MapReadyHandler implements OnMapReadyCallback { private JSONObject component; private MapView view; private Context context; MapReadyHandler(JSONObject component, MapView view, Context context) { this.component = component; this.view = view; this.context = context; } private double getZoomForMetersWide(final double meters, final double width, final double lat) { // Converts metes wide to a zoom level for a google map, got this nice formula from // http://stackoverflow.com/a/21034310/1034194 final double latAdjust = Math.cos(Math.PI * lat / 180.0); final double arg = EQUATOR_LENGTH * width * latAdjust / (meters * 256.0); return Math.log(arg) / Math.log(2.0); } @Override public void onMapReady(GoogleMap map) { try { // Add pins to the map if (component.has("pins")) { JSONArray pins = component.getJSONArray("pins"); for (int i = 0; i < pins.length(); i++) { JSONObject pin = pins.getJSONObject(i); MarkerOptions options = new MarkerOptions(); options.position(getCoordinates(pin)); if (pin.has("title")) { options.title(pin.getString("title")); } if (pin.has("description")) { options.snippet(pin.getString("description")); } Marker marker = map.addMarker(options); if (pin.has("style")) { JSONObject style = pin.getJSONObject("style"); if (style.has("selected") && style.getBoolean("selected")) { marker.showInfoWindow(); } } } } // Move the camera to the zoom level that shows at least the desired region if(component.has("region")) { JSONObject region = component.getJSONObject("region"); if(region.has("width") && region.has("height")) { double width = region.getDouble("width"); double height = region.getDouble("height"); JasonViewActivity activity = ((JasonViewActivity) context); DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); double viewWidth = view.getLayoutParams().width / metrics.density; double viewHeight = view.getLayoutParams().height / metrics.density; double meters = width; if(height > width && viewHeight > viewWidth) { // Widen the zoom in order to see the requested height meters = height; } double lat = map.getCameraPosition().target.latitude; float zoom = (float)getZoomForMetersWide(meters, viewWidth, lat); map.moveCamera(CameraUpdateFactory.zoomTo(zoom)); } } } catch (Exception err) { Log.d("Error", err.toString()); } } } // Intercept touch events on the RecyclerView to make sure they don't interfere with map moves static RecyclerView.SimpleOnItemTouchListener touchListener = new RecyclerView.SimpleOnItemTouchListener() { // Intercept touch events on the recycler view, and if they are over a mapview, make sure // to let the mapview handle them @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { LinearLayout layout = (LinearLayout)rv.findChildViewUnder(e.getX(),e.getY()); if(layout != null) { for(int i=0; i<layout.getChildCount(); i++) { View child= layout.getChildAt(i); // Weed out non-map views ASAP if (child.getClass().equals(MapView.class)) { int left = layout.getLeft() + child.getLeft(); int right = layout.getLeft() + child.getRight(); int top = layout.getTop() + child.getTop(); int bottom = layout.getTop() + child.getBottom(); if(e.getX() > left && e.getX() < right && e.getY() > top && e.getY() < bottom) { switch (e.getActionMasked()) { // Pressed on map: stop listview from scrolling case MotionEvent.ACTION_DOWN: rv.requestDisallowInterceptTouchEvent(true); break; // Released on map or cancelled: listview can be normal again case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: rv.requestDisallowInterceptTouchEvent(false); break; } } } } } return false; } }; }