package com.lemoulinstudio.bikefriend;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.text.TextPaint;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.PopupWindow;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.lemoulinstudio.bikefriend.db.BikeStation;
import com.lemoulinstudio.bikefriend.db.DataSourceEnum;
import com.lemoulinstudio.bikefriend.preference.BikefriendPreferences_;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.res.DrawableRes;
import org.androidannotations.annotations.res.StringRes;
import org.androidannotations.annotations.sharedpreferences.Pref;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@EFragment
public class GoogleMapFragment extends SupportMapFragment implements BikeStationListener {
private boolean displayBicyclesOnMarkers;
private Map<DataSourceEnum, List<Marker>> dataSourceToMarkers;
private Map<Integer, BitmapDescriptor> numberToMarkerBitmapDescriptor;
private Map<Object, List<Marker>> dataSourceWcToMarkers;
private Map<Object, List<Marker>> dataSourceDrinkableWaterToMarkers;
private TextPaint textPaint;
// private boolean displayMapStationLayer;
// private boolean displayMapWCLayer;
// private boolean displayMapDrinkableWaterLayer;
public GoogleMapFragment() {
dataSourceToMarkers = new EnumMap<DataSourceEnum, List<Marker>>(DataSourceEnum.class);
for (DataSourceEnum dataSource : DataSourceEnum.values()) {
dataSourceToMarkers.put(dataSource, new ArrayList<Marker>());
}
numberToMarkerBitmapDescriptor = new HashMap<Integer, BitmapDescriptor>();
dataSourceWcToMarkers = new HashMap();
dataSourceWcToMarkers.put(new Object(), new ArrayList());
dataSourceDrinkableWaterToMarkers = new HashMap();
dataSourceDrinkableWaterToMarkers.put(new Object(), new ArrayList());
}
@Pref
protected BikefriendPreferences_ preferences;
@Bean
protected BikeStationProviderRepository bikeStationProviderRepository;
@Bean
protected StationInfoWindowAdapter siwa;
@DrawableRes(R.drawable.map_marker_green)
protected Drawable markerDrawableGreen;
@DrawableRes(R.drawable.map_marker_yellow)
protected Drawable markerDrawableYellow;
@DrawableRes(R.drawable.map_marker_red)
protected Drawable markerDrawableRed;
@StringRes(R.string.message_network_not_available)
protected String messageNetworkNotAvailable;
@StringRes(R.string.message_server_not_reachable_format)
protected String messageServerNotReachable;
@StringRes(R.string.message_parse_error_format)
protected String messageDataParseError;
@StringRes(R.string.map_popup_layer_stations)
protected String messageLayerStations;
@StringRes(R.string.map_popup_layer_toilets)
protected String messageLayerWC;
@StringRes(R.string.map_popup_layer_drinkable_water)
protected String messageLayerDrinkableWater;
@AfterViews
protected void setupViews() {
final GoogleMap map = getMap();
map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
map.setMyLocationEnabled(true);
map.setInfoWindowAdapter(siwa);
map.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() {
@Override
public void onInfoWindowClick(Marker marker) {
BikeStation bikeStation = siwa.getBikeStation(marker);
bikeStation.isPreferred = !bikeStation.isPreferred;
try {bikeStationProviderRepository.updateDbFromMem(bikeStation);}
catch (SQLException e) {}
marker.hideInfoWindow();
marker.showInfoWindow();
}
});
map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
public void onCameraChange(CameraPosition cp) {
LatLngBounds visibleRegion = map.getProjection().getVisibleRegion().latLngBounds;
for (BikeStationProvider bikeStationProvider : bikeStationProviderRepository.getBikeStationProviders()) {
LatLngBounds providerBounds = bikeStationProvider.getBounds();
if (Utils.intersects(visibleRegion, providerBounds)) {
bikeStationProvider.notifyStationsAreWatched();
}
}
}
});
textPaint = new TextPaint();
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(markerDrawableGreen.getIntrinsicHeight() * 0.5f);
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
}
private BitmapDescriptor getMarkerBitmapDescriptor(int number) {
// We put a hard maximum of 99 on the number, flash mobs are not our typical users.
number = Math.min(number, 99);
if (!numberToMarkerBitmapDescriptor.containsKey(number)) {
numberToMarkerBitmapDescriptor.put(number,
BitmapDescriptorFactory.fromBitmap(createMarkerBitmap(number)));
}
return numberToMarkerBitmapDescriptor.get(number);
}
private Bitmap createMarkerBitmap(int number) {
Drawable markerDrawable = number >= 5 ? markerDrawableGreen :
number > 0 ? markerDrawableYellow : markerDrawableRed;
int width = markerDrawable.getIntrinsicWidth();
int height = markerDrawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
String text = "" + number;
markerDrawable.setBounds(0, 0, width, height);
markerDrawable.draw(canvas);
Rect textBounds = new Rect();
textPaint.getTextBounds(text, 0, text.length(), textBounds);
canvas.drawText(text,
width / 2,
(height + textBounds.height()) / 2,
textPaint);
return bitmap;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.map, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.menu_bicycle).setVisible(displayBicyclesOnMarkers);
menu.findItem(R.id.menu_parking).setVisible(!displayBicyclesOnMarkers);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh: {
if (Utils.isNetworkAvailable(getActivity())) {
LatLngBounds visibleRegion = getMap().getProjection().getVisibleRegion().latLngBounds;
for (BikeStationProvider bikeStationProvider : bikeStationProviderRepository.getBikeStationProviders()) {
LatLngBounds providerBounds = bikeStationProvider.getBounds();
if (Utils.intersects(visibleRegion, providerBounds)) {
bikeStationProvider.updateData();
}
}
}
else {
Toast.makeText(getActivity(), messageNetworkNotAvailable, Toast.LENGTH_LONG).show();
}
return true;
}
case R.id.menu_bicycle:
case R.id.menu_parking: {
// The user wants to switch between displaying available bicycles and available parkings.
displayBicyclesOnMarkers = !displayBicyclesOnMarkers;
getActivity().supportInvalidateOptionsMenu();
// displayMapStationLayer = true;
recreateAllTheMarkers();
return true;
}
case R.id.menu_layers: {
// // Select layers to display on map
// LayoutInflater inflater = LayoutInflater.from(this.getActivity());
// View popup = inflater.inflate(R.layout.fragment_layer, null, false);
//
// final CheckBox checkBoxStationsUi = (CheckBox)popup.findViewById(R.id.checkBoxStationLayer);
// checkBoxStationsUi.setChecked(displayMapStationLayer);
// checkBoxStationsUi.setText(messageLayerStations);
// checkBoxStationsUi.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
// public void onCheckedChanged(android.widget.CompoundButton compoundButton, boolean checked) {
// displayMapStationLayer = checked;
// if (checked) {
// GoogleMapFragment.this.recreateAllTheMarkers();
// }
// else {
// GoogleMapFragment.this.removeAllTheBikeStationMarkers();
// }
// }}
// );
//
// final CheckBox checkBoxWCUi = (CheckBox)popup.findViewById(R.id.checkBoxWCLayer);
// checkBoxWCUi.setChecked(displayMapWCLayer);
// checkBoxWCUi.setText(messageLayerWC);
// checkBoxWCUi.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
// public void onCheckedChanged(android.widget.CompoundButton compoundButton, boolean checked) {
// displayMapWCLayer = checked;
// if (checked) {
// GoogleMapFragment.this.recreateAllTheWCMarkers();
// }
// else {
// GoogleMapFragment.this.removeAllTheWCMarkers();
// }
// }}
// );
//
// final CheckBox checkBoxDrinkableWaterUi = (CheckBox)popup.findViewById(R.id.checkBoxDrinkableWater);
// checkBoxDrinkableWaterUi.setChecked(displayMapDrinkableWaterLayer);
// checkBoxDrinkableWaterUi.setText(messageLayerDrinkableWater);
// checkBoxDrinkableWaterUi.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
// public void onCheckedChanged(android.widget.CompoundButton compoundButton, boolean checked) {
// displayMapDrinkableWaterLayer = checked;
// if (checked) {
// GoogleMapFragment.this.recreateAllTheDrinkableWaterMarkers();
// } else {
// GoogleMapFragment.this.removeAllTheDrinkableWaterMarkers();
// }
// }}
// );
//
// View parentView = this.getView();
// final PopupWindow popupUi = new PopupWindow(getActivity());
// popup.measure(View.MeasureSpec.UNSPECIFIED,View.MeasureSpec.UNSPECIFIED);
// popupUi.setContentView(popup);
// popupUi.setWidth(popup.getMeasuredWidth());
// popupUi.setHeight(popup.getMeasuredHeight());
// popupUi.setOutsideTouchable(true);
// popupUi.setFocusable(true);
// popupUi.showAtLocation(parentView, Gravity.CENTER, 0, 0);
// popupUi.update();
// popupUi.setOnDismissListener(new PopupWindow.OnDismissListener() {
// @Override
// public void onDismiss() {
// popupUi.dismiss();
// }
// });
// return true;
}
case R.id.menu_place_taipei: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.YouBike_Taipei).getBounds());
return true;
}
case R.id.menu_place_new_taipei: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.YouBike_NewTaipei).getBounds());
return true;
}
case R.id.menu_place_taoyuan: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.YouBike_Taoyuan).getBounds());
return true;
}
case R.id.menu_place_hsinchu: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.YouBike_Hsinchu).getBounds());
return true;
}
case R.id.menu_place_taichung: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.YouBike_Taichung).getBounds());
return true;
}
case R.id.menu_place_changhua: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.YouBike_Changhua).getBounds());
return true;
}
case R.id.menu_place_kaohsiung: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.CityBike_Kaohsiung).getBounds());
return true;
}
case R.id.menu_place_pingtung: {
animateCameraToBoundingBox(bikeStationProviderRepository
.getBikeStationProvider(DataSourceEnum.PingtungBike_Pingtung).getBounds());
return true;
}
case R.id.menu_place_taiwan: {
animateCameraToBoundingBox(new LatLngBounds(
new LatLng(21.885012f, 119.877213f),
new LatLng(25.210294f, 122.180730f)));
return true;
}
default: {
return super.onOptionsItemSelected(item);
}
}
}
private void animateCameraToBoundingBox(LatLngBounds bounds) {
// Log.i("bikefriend", String.format("bounds = [%ff, %ff, %ff, %ff]",
// bounds.southwest.latitude, bounds.southwest.longitude,
// bounds.northeast.latitude, bounds.northeast.longitude));
// LatLngBounds cameraBounds = getMap().getProjection().getVisibleRegion().latLngBounds;
// Log.i("bikefriend", String.format("cameraBounds = [%ff, %ff, %ff, %ff]",
// cameraBounds.southwest.latitude, cameraBounds.southwest.longitude,
// cameraBounds.northeast.latitude, cameraBounds.northeast.longitude));
getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
}
@Override
public void onServerNotReachable(BikeStationProvider bikeStationProvider) {
String placeName = getActivity().getString(bikeStationProvider.getDataSourceEnum().placeNameRes);
String message = String.format(messageServerNotReachable, placeName);
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
}
@Override
public void onParseError(BikeStationProvider bikeStationProvider) {
String placeName = getActivity().getString(bikeStationProvider.getDataSourceEnum().placeNameRes);
String message = String.format(messageDataParseError, placeName);
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
}
@Override
public void onBikeStationUpdated(BikeStationProvider bikeStationProvider) {
// if (!displayMapStationLayer) {
// // Prevents displaying markers due to update triggered implicitly from onStart's registerForBikeStationUpdates
// return;
// }
List<Marker> markers = dataSourceToMarkers.get(bikeStationProvider.getDataSourceEnum());
removeAllMarkers(markers);
GoogleMap map = getMap();
Collection<BikeStation> bikeStations = bikeStationProvider.getBikeStations();
synchronized (bikeStations) {
for (BikeStation station : bikeStations) {
MarkerOptions markerOptions = new MarkerOptions()
.position(new LatLng(station.latitude, station.longitude))
.icon(getMarkerBitmapDescriptor(displayBicyclesOnMarkers ?
station.nbBicycles : station.nbEmptySlots));
Marker marker = map.addMarker(markerOptions);
markers.add(marker);
siwa.bindMarkerToStation(marker, station);
}
}
}
public void onWCUpdated(Object provider) {
List<Marker> markers = dataSourceWcToMarkers.values().iterator().next(); //FIXME: No proper datasource
removeAllMarkers(markers);
GoogleMap map = getMap();
Collection<float[] /* latitude/longitude*/> stations = new ArrayList<float[]>();
// FIXME Dummy data
float coordinate1[] = {25.028662f, 121.56612f};
float coordinate2[] = {25.034937f, 121.557662f};
stations.add(coordinate1);
stations.add(coordinate2);
// end
synchronized (stations) {
for (float[] station : stations) {
MarkerOptions markerOptions = new MarkerOptions()
.position(new LatLng(station[0], station[1]))
.icon(getMarkerBitmapDescriptor(5));
Marker marker = map.addMarker(markerOptions);
markers.add(marker);
}
}
}
public void onDrinkableWaterUpdated(Object provider) {
List<Marker> markers = dataSourceDrinkableWaterToMarkers.values().iterator().next(); //FIXME: No proper datasource
removeAllMarkers(markers);
GoogleMap map = getMap();
Collection<float[] /* latitude/longitude*/> stations = new ArrayList<float[]>();
// FIXME Dummy data
float coordinate1[] = {25.128662f, 121.56612f};
float coordinate2[] = {25.234937f, 121.557662f};
stations.add(coordinate1);
stations.add(coordinate2);
// end
synchronized (stations) {
for (float[] station : stations) {
MarkerOptions markerOptions = new MarkerOptions()
.position(new LatLng(station[0], station[1]))
.icon(getMarkerBitmapDescriptor(2));
Marker marker = map.addMarker(markerOptions);
markers.add(marker);
}
}
}
@Override
public void onResume() {
super.onResume();
// Restores the state of the camera on the map.
CameraPosition cameraPosition = new CameraPosition(
new LatLng(preferences.cameraTargetLat().get(),
preferences.cameraTargetLng().get()),
preferences.cameraZoom().get(),
preferences.cameraTilt().get(),
preferences.cameraBearing().get());
getMap().moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
// Restore Map Layers state
// displayMapStationLayer = preferences.mapStationsLayer().get();
// displayMapWCLayer = preferences.mapWCLayer().get();
// displayMapDrinkableWaterLayer = preferences.mapDrinkableWaterLayer().get();
displayBicyclesOnMarkers = preferences.displayBicyclesOnMarkers().get();
recreateAllTheMarkers();
// // Display layers as per user preference at first
// if (displayMapStationLayer) {
// recreateAllTheMarkers();
// }
// if (displayMapWCLayer) {
// recreateAllTheWCMarkers();
// }
// if (displayMapDrinkableWaterLayer) {
// recreateAllTheDrinkableWaterMarkers();
// }
}
// private void removeAllBikeStationsMarkers(List<Marker> markers) {
// // Remove the old markers.
// for (Marker marker : markers) {
// marker.remove();
// siwa.unbindMarker(marker);
// }
// markers.clear();
// }
private void removeAllMarkers(List<Marker> markers) {
// Remove the old markers.
for (Marker marker : markers) {
marker.remove();
}
markers.clear();
}
private void recreateAllTheMarkers() {
// Initializes the markers on the map, according to the bike stations already available (in memory or db).
for (BikeStationProvider bikeStationProvider : bikeStationProviderRepository.getBikeStationProviders()) {
onBikeStationUpdated(bikeStationProvider);
}
}
// private void removeAllTheBikeStationMarkers() {
// for (List<Marker> markers : dataSourceToMarkers.values()) {
// removeAllMarkers(markers);
// }
// }
// private void recreateAllTheWCMarkers() {
// // Initializes the markers on the map, according to the WCs already available (in memory or db).
// onWCUpdated(null);
// }
// private void removeAllTheWCMarkers() {
// List<Marker> markers = dataSourceWcToMarkers.values().iterator().next();
// removeAllMarkers(markers);
// }
// private void recreateAllTheDrinkableWaterMarkers() {
// // Initializes the markers on the map, according to the water drinkable source already available (in memory or db).
// onDrinkableWaterUpdated(null);
// }
// private void removeAllTheDrinkableWaterMarkers() {
// List<Marker> markers = dataSourceDrinkableWaterToMarkers.values().iterator().next();
// removeAllMarkers(markers);
// }
public void onPause() {
super.onPause();
// Saves the state of the camera on the map.
CameraPosition cameraPosition = getMap().getCameraPosition();
preferences.edit()
.cameraTargetLat().put((float) cameraPosition.target.latitude)
.cameraTargetLng().put((float) cameraPosition.target.longitude)
.cameraZoom().put(cameraPosition.zoom)
.cameraTilt().put(cameraPosition.tilt)
.cameraBearing().put(cameraPosition.bearing)
.displayBicyclesOnMarkers().put(displayBicyclesOnMarkers)
// .mapStationsLayer().put(displayMapStationLayer)
// .mapWCLayer().put(displayMapWCLayer)
// .mapDrinkableWaterLayer().put(displayMapDrinkableWaterLayer)
.apply();
}
@Override
public void onStart() {
super.onStart();
bikeStationProviderRepository.registerForBikeStationUpdates(this);
}
@Override
public void onStop() {
super.onStop();
bikeStationProviderRepository.unregisterForBikeStationUpdates(this);
siwa.unbindAllMarkers();
}
@UiThread
public void showStation(BikeStation bikeStation) {
// Scrolls the camera to show the station.
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(bikeStation.latitude, bikeStation.longitude),
14));
// Opens the info windows.
Marker marker = siwa.getMarker(bikeStation);
if (marker != null) {
marker.showInfoWindow();
}
}
}