/* * Copyright (C) 2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo Flow. * * Akvo Flow is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Akvo Flow is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Akvo Flow. If not, see <http://www.gnu.org/licenses/>. */ package org.akvo.flow.ui.map; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; 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 java.util.ArrayList; import java.util.List; public abstract class Feature { protected static final int POINT_SIZE_DEFAULT = 40;// Default marker size (px). protected static final int POINT_SIZE_SELECTED = 60;// Selected marker size (px). protected static final int POINT_COLOR_DEFAULT = 0xEE736357; protected static final int POINT_COLOR_ACTIVE = 0xFFE27C00; protected static final int POINT_COLOR_SELECTED = 0xFF00A79D; protected static final int POINT_COLOR_FILL = 0x55FFFFFF; protected static final int STROKE_COLOR_DEFAULT = 0xEE736357; protected static final int STROKE_COLOR_SELECTED = 0xFF736357; protected boolean mSelected; protected Marker mSelectedMarker; protected GoogleMap mMap; protected List<LatLng> mPoints; protected List<Marker> mMarkers; protected List<Property> mProperties; private static final BitmapDescriptor MARKER_DISABLED; private static final BitmapDescriptor MARKER_ENABLED; private static final BitmapDescriptor MARKER_SELECTED; private static final BitmapDescriptor MARKER_HIGHLIGHTED; static { MARKER_DISABLED = getMarkerBitmapDescriptor(PointStatus.DISABLED); MARKER_ENABLED = getMarkerBitmapDescriptor(PointStatus.ENABLED); MARKER_SELECTED = getMarkerBitmapDescriptor(PointStatus.SELECTED); MARKER_HIGHLIGHTED = getMarkerBitmapDescriptor(PointStatus.HIGHLIGHTED); } private enum PointStatus { DISABLED, // Unselected Feature. Normal mode. ENABLED, // Selected Feature, but marker is not selected (just 'active'). SELECTED, // Currently selected marker. HIGHLIGHTED // Highlighted point, used to show the descendant of the selected point. } public Feature(GoogleMap map) { mMap = map; mPoints = new ArrayList<>(); mMarkers = new ArrayList<>(); mProperties = new ArrayList<>(); } public abstract int getTitle(); public abstract String geoGeometryType(); public abstract boolean highlightNext(int position); public boolean contains(Marker marker) { return mMarkers.contains(marker); } public List<LatLng> getPoints() { return mPoints; } /** * Add point to the map. A new marker will be created based on the point, * and the underlying overlays recomputed. * @param point LatLng value of the new point. */ public void addPoint(LatLng point) { Marker marker = mMap.addMarker(new MarkerOptions() .position(point) .title(String.format("lat/lng: %.5f, %.5f", point.latitude, point.longitude)) .anchor(0.5f, 0.5f) .draggable(false) .icon(MARKER_DISABLED)); // Insert new point just after the currently selected marker (if any) if (mSelectedMarker != null) { int index = mMarkers.indexOf(mSelectedMarker) + 1; mMarkers.add(index, marker); mPoints.add(index, point); } else { mMarkers.add(marker); mPoints.add(point); } setSelected(mSelected, marker); } /** * Delete selected point. */ public void removePoint() { if (mSelectedMarker == null) { return; } int index = mMarkers.indexOf(mSelectedMarker); mSelectedMarker.remove(); mPoints.remove(index); mMarkers.remove(index); } /** * Delete the whole feature from the map. */ public void delete() { for (Marker marker : mMarkers) { marker.remove(); } mMarkers.clear(); mPoints.clear(); } public void onDrag(Marker marker) { int index = mMarkers.indexOf(marker); if (index == -1) { return; } mPoints.remove(index); mPoints.add(index, marker.getPosition()); invalidate(); } public void setSelected(boolean selected, Marker marker) { if (mSelectedMarker != null) { mSelectedMarker.setDraggable(false); } mSelected = selected; mSelectedMarker = selected ? marker: null; if (mSelectedMarker != null) { mSelectedMarker.setDraggable(true); } invalidate(); } /** * Recompute geoshape and redraw the corresponding markers. Subclasses should add any extra * step to this process by overriding this method. */ protected void invalidate() { // Recompute icons, depending on point status long selected = -1, next = -1; if (mSelected && mSelectedMarker != null && mMarkers.contains(mSelectedMarker)) { selected = mMarkers.indexOf(mSelectedMarker); next = selected > -1 ? (selected + 1) % mMarkers.size() : mMarkers.size() - 1; } for (int i=0; i<mMarkers.size(); i++) { Marker marker = mMarkers.get(i); if (!mSelected) { marker.setIcon(MARKER_DISABLED); } else if (i == selected) { marker.setIcon(MARKER_SELECTED); marker.showInfoWindow(); } else if (i == next && highlightNext(i)) { marker.setIcon(MARKER_HIGHLIGHTED); } else { marker.setIcon(MARKER_ENABLED); } } // Compute properties mProperties.clear(); String count = String.valueOf(mMarkers.size()); mProperties.add(new Property("pointCount", count, "Point Count", count)); } public List<Property> getProperties() { return mProperties; } public void load(List<LatLng> points) { for (LatLng point : points) { addPoint(point); } } /** * Programmatically creates Bitmap based on point status * @param status PointStatus of the feature point. * @return BitmapDescriptor of the newly created Bitmap. */ protected static BitmapDescriptor getMarkerBitmapDescriptor(PointStatus status) { int size, color; switch (status) { case SELECTED: size = POINT_SIZE_SELECTED; color = POINT_COLOR_SELECTED; break; case HIGHLIGHTED: size = POINT_SIZE_DEFAULT; color = POINT_COLOR_SELECTED; break; case ENABLED: size = POINT_SIZE_DEFAULT; color = POINT_COLOR_ACTIVE; break; case DISABLED: default: size = POINT_SIZE_DEFAULT; color = POINT_COLOR_DEFAULT; } Bitmap bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); Paint solid = new Paint(); solid.setColor(color); solid.setAntiAlias(true); Paint fill = new Paint(); fill.setAntiAlias(true); fill.setColor(POINT_COLOR_FILL); final float center = size / 2f; canvas.drawCircle(center, center, center, solid);// Outer circle canvas.drawCircle(center, center, center * 0.9f, fill);// Fill circle canvas.drawCircle(center, center, center * 0.25f, solid);// Inner circle return BitmapDescriptorFactory.fromBitmap(bmp); } public static class Property { public String mKey; public String mValue; public String mDisplayName; public String mDisplayValue; public Property(String key, String value, String displayName, String displayValue) { mKey = key; mValue = value; mDisplayName = displayName; mDisplayValue = displayValue; } } }