Skip to content

Commit

Permalink
Add image information modal to image viewer
Browse files Browse the repository at this point in the history
Fixes #2059
  • Loading branch information
simonpoole committed Nov 8, 2023
1 parent 86ed419 commit 8dde9f8
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 149 deletions.
2 changes: 1 addition & 1 deletion src/main/java/de/blau/android/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,7 @@ private void processIntents() {
Uri uri = intent.getData();
if (!index.deletePhoto(this, uri)) {
String path = ContentResolverUtil.getPath(this, uri);
if (path != null && !index.deletePhoto(this, path)) {
if (path == null || !index.deletePhoto(this, path)) {
Log.e(DEBUG_TAG, "deleting " + uri + " from index failed");
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/de/blau/android/contract/Urls.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private Urls() {
public static final String DEFAULT_OAM_SERVER = "https://api.openaerialmap.org/";

// these are only configurable for testing
public static final String DEFAULT_MAPILLARY_IMAGES_V4 = "https://graph.mapillary.com/%s?access_token=%s&fields=thumb_2048_url,computed_geometry";
public static final String DEFAULT_MAPILLARY_IMAGES_V4 = "https://graph.mapillary.com/%s?access_token=%s&fields=thumb_2048_url,computed_geometry,computed_compass_angle,captured_at";
public static final String DEFAULT_MAPILLARY_SEQUENCES_URL_V4 = "https://graph.mapillary.com/image_ids?sequence_id=%s&access_token=%s&fields=id";

public static final String DEFAULT_OSM_WIKI = "https://wiki.openstreetmap.org/";
Expand Down
25 changes: 2 additions & 23 deletions src/main/java/de/blau/android/dialogs/FeatureInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ protected View createView(@Nullable ViewGroup container) {

Util.sharePosition(getActivity(), new double[] { p.longitude(), p.latitude() }, null);
});
tl.addView(TableLayoutUtils.createRowWithButton(activity, R.string.location_lon_label, prettyPrint(p.longitude()), button, tp));
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lat_label, prettyPrint(p.latitude()), tp));
tl.addView(TableLayoutUtils.createRowWithButton(activity, R.string.location_lon_label, prettyPrintCoord(p.longitude()), button, tp));
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lat_label, prettyPrintCoord(p.latitude()), tp));
}
}
tl.addView(TableLayoutUtils.divider(activity));
Expand All @@ -220,25 +220,4 @@ protected View createView(@Nullable ViewGroup container) {
}
return sv;
}

/**
* Get the string resource formated as an italic string
*
* @param resId String resource id
* @return a Spanned containing the string
*/
private Spanned toItalic(int resId) {
return Util.fromHtml("<i>" + getString(resId) + "</i>");
}

/**
* Pretty print a coordinate value
*
* @param coord the coordinate in WGS84
* @return a reasonable looking string representation
*/
@NonNull
private static String prettyPrint(double coord) {
return String.format(Locale.US, "%.7f", coord) + "°";
}
}
147 changes: 147 additions & 0 deletions src/main/java/de/blau/android/dialogs/ImageInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package de.blau.android.dialogs;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import android.widget.TableLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.app.AppCompatDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import de.blau.android.R;
import de.blau.android.listener.DoNothingListener;
import de.blau.android.photos.Photo;
import de.blau.android.util.ContentResolverUtil;
import de.blau.android.util.DateFormatter;
import de.blau.android.util.InfoDialogFragment;
import de.blau.android.util.ThemeUtils;

/**
* Very simple dialog fragment to display some info on a GeoJSON element
*
* @author simon
*
*/
public class ImageInfo extends InfoDialogFragment {

private static final String DEBUG_TAG = ImageInfo.class.getName();

private static final String URI_KEY = "uri";

private static final String TAG = "fragment_image_info";

private Uri uri = null;
private SimpleDateFormat dateFormat = DateFormatter.getUtcFormat("yyyy-MM-dd HH:mm:ssZ");

/**
* Show an info dialog for an image
*
* @param activity the calling Activity
* @param uriString the uri of the image as a string
*/
public static void showDialog(@NonNull FragmentActivity activity, @NonNull String uriString) {
dismissDialog(activity);
try {
FragmentManager fm = activity.getSupportFragmentManager();
ImageInfo elementInfoFragment = newInstance(uriString);
elementInfoFragment.show(fm, TAG);
} catch (IllegalStateException isex) {
Log.e(DEBUG_TAG, "showDialog", isex);
}
}

/**
* Dismiss the dialog
*
* @param activity the calling Activity
*/
private static void dismissDialog(@NonNull FragmentActivity activity) {
de.blau.android.dialogs.Util.dismissDialog(activity, TAG);
}

/**
* Create a new instance of the FeatureInfo dialog
*
* @param feature Feature to display the info on
*
* @return an instance of ElementInfo
*/
@NonNull
private static ImageInfo newInstance(@NonNull String uriString) {
ImageInfo f = new ImageInfo();

Bundle args = new Bundle();
args.putString(URI_KEY, uriString);

f.setArguments(args);
f.setShowsDialog(true);

return f;
}

@Override
public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
String uriString = getArguments().getString(URI_KEY);
try {
uri = Uri.parse(uriString);
} catch (Exception e) {
Log.e(DEBUG_TAG, "Unable to parse uri " + uriString);
}
Builder builder = new AlertDialog.Builder(getActivity());
DoNothingListener doNothingListener = new DoNothingListener();
builder.setPositiveButton(R.string.done, doNothingListener);
builder.setTitle(R.string.image_information_title);
builder.setView(createView(null));
return builder.create();
}

@Override
protected View createView(@Nullable ViewGroup container) {
FragmentActivity activity = getActivity();
LayoutInflater inflater = ThemeUtils.getLayoutInflater(activity);
ScrollView sv = (ScrollView) inflater.inflate(R.layout.element_info_view, container, false);
TableLayout tl = (TableLayout) sv.findViewById(R.id.element_info_vertical_layout);

TableLayout.LayoutParams tp = new TableLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tp.setMargins(10, 2, 10, 2);

if (uri != null) {
tl.setColumnShrinkable(1, true);
try {
Photo image = new Photo(getContext(), uri, null);
tl.addView(TableLayoutUtils.createRow(activity, ContentResolverUtil.getPath(getContext(), uri), null, tp));
long size = ContentResolverUtil.getSizeColumn(getContext(), uri);
if (size > -1) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.file_size, getString(R.string.file_size_kB, size / 1024), tp));
}
String creator = image.getCreator();
if (creator != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.created_by, creator, tp));
}
Long captureDate = image.getCaptureDate();
if (captureDate != null) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.capture_date, dateFormat.format(new Date(captureDate)), tp));
}
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lon_label, prettyPrintCoord(image.getLon() / 1E7D), tp));
tl.addView(TableLayoutUtils.createRow(activity, R.string.location_lat_label, prettyPrintCoord(image.getLat() / 1E7D), tp));
if (image.hasDirection()) {
tl.addView(TableLayoutUtils.createRow(activity, R.string.direction, String.format(Locale.US, "%3d°", image.getDirection()), tp));
}
} catch (Exception ex) {
Log.e(DEBUG_TAG, "Exception displaying image meta data " + ex.getMessage());
}
}
return sv;
}
}
103 changes: 66 additions & 37 deletions src/main/java/de/blau/android/layer/mapillary/MapillaryLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.FragmentActivity;
import de.blau.android.App;
import de.blau.android.Main;
import de.blau.android.R;
import de.blau.android.contract.FileExtensions;
import de.blau.android.contract.MimeTypes;
import de.blau.android.contract.Schemes;
import de.blau.android.dialogs.ImageInfo;
import de.blau.android.osm.OsmXml;
import de.blau.android.util.ExecutorTask;
import de.blau.android.util.FileUtil;
Expand All @@ -55,9 +57,11 @@ class MapillaryLoader extends ImageLoader {

private static final int IMAGERY_LOAD_THREADS = 3;

private static final String COORDINATES_FIELD = "coordinates";
private static final String COMPUTED_GEOMETRY_FIELD = "computed_geometry";
private static final String THUMB_2048_URL_FIELD = "thumb_2048_url";
private static final String COORDINATES_FIELD = "coordinates";
private static final String COMPUTED_GEOMETRY_FIELD = "computed_geometry";
private static final String COMPUTED_COMPASS_ANGLE_FIELD = "computed_compass_angle";
private static final String CAPTURED_AT_FIELD = "captured_at";
private static final String THUMB_2048_URL_FIELD = "thumb_2048_url";

private static final String JPG = "." + FileExtensions.JPG;

Expand Down Expand Up @@ -118,22 +122,20 @@ public void load(SubsamplingScaleImageView view, String key) {
throw new IOException("Download of " + key + " failed with " + mapillaryCallResponse.code() + " " + mapillaryCallResponse.message());
}
try (ResponseBody responseBody = mapillaryCallResponse.body(); InputStream inputStream = responseBody.byteStream()) {
if (inputStream != null) {
JsonElement root = JsonParser.parseReader(new BufferedReader(new InputStreamReader(inputStream, Charset.forName(OsmXml.UTF_8))));
if (root.isJsonObject() && ((JsonObject) root).has(THUMB_2048_URL_FIELD)) {
loadImage(key, imageFile, client, ((JsonObject) root).get(COMPUTED_GEOMETRY_FIELD),
((JsonObject) root).get(THUMB_2048_URL_FIELD).getAsString());
} else {
throw new IOException("Unexpected / missing response");
}
if (inputStream == null) {
throw new IOException("No InputStream");
}
JsonElement root = JsonParser.parseReader(new BufferedReader(new InputStreamReader(inputStream, Charset.forName(OsmXml.UTF_8))));
if (!root.isJsonObject() || !((JsonObject) root).has(THUMB_2048_URL_FIELD)) {
throw new IOException("Unexpected / missing response");
}
loadImage(key, imageFile, client, (JsonObject) root, ((JsonObject) root).get(THUMB_2048_URL_FIELD).getAsString());
}
setImage(view, imageFile);
pruneCache();
} catch (IOException e) {
Log.e(DEBUG_TAG, e.getMessage());
return;
}
setImage(view, imageFile);
pruneCache();
});
} catch (RejectedExecutionException rjee) {
Log.e(DEBUG_TAG, "Execution rejected " + rjee.getMessage());
Expand Down Expand Up @@ -163,33 +165,48 @@ protected Void doInBackground(Void arg) {
* @param url image url
* @throws IOException if download or writing has issues
*/
private void loadImage(@NonNull String key, @NonNull File imageFile, @NonNull OkHttpClient client, @Nullable JsonElement point, @NonNull String url)
private void loadImage(@NonNull String key, @NonNull File imageFile, @NonNull OkHttpClient client, JsonObject meta, @NonNull String url)
throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
try (ResponseBody responseBody = response.body(); InputStream inputStream = responseBody.byteStream()) {
if (inputStream != null) {
try (FileOutputStream fileOutput = new FileOutputStream(imageFile)) {
byte[] buffer = new byte[1024];
int bufferLength = 0;
while ((bufferLength = inputStream.read(buffer)) > 0) {
fileOutput.write(buffer, 0, bufferLength);
}
}
if (point instanceof JsonObject && imageFile.length() > 0) {
JsonElement coords = ((JsonObject) point).get(COORDINATES_FIELD);
if (coords instanceof JsonArray && ((JsonArray) coords).size() == 2) {
ExifInterface exif = new ExifInterface(imageFile);
double lat = ((JsonArray) coords).get(1).getAsDouble();
double lon = ((JsonArray) coords).get(0).getAsDouble();
exif.setLatLong(lat, lon);
exif.saveAttributes();
coordinates.put(key, new double[] { lat, lon });
}
}
if (!response.isSuccessful()) {
throw new IOException("Download failed " + response.message());
}
try (ResponseBody responseBody = response.body(); InputStream inputStream = responseBody.byteStream()) {
if (inputStream == null) {
throw new IOException("Download failed no InputStream");
}
try (FileOutputStream fileOutput = new FileOutputStream(imageFile)) {
byte[] buffer = new byte[1024];
int bufferLength = 0;
while ((bufferLength = inputStream.read(buffer)) > 0) {
fileOutput.write(buffer, 0, bufferLength);
}
}
JsonElement point = meta.get(COMPUTED_GEOMETRY_FIELD);
if (!(point instanceof JsonObject) || imageFile.length() == 0) {
throw new IOException("No geometry for image or image empty");
}
JsonElement coords = ((JsonObject) point).get(COORDINATES_FIELD);
if (!(coords instanceof JsonArray) || ((JsonArray) coords).size() != 2) {
throw new IOException("No geometry for image");
}
ExifInterface exif = new ExifInterface(imageFile);
double lat = ((JsonArray) coords).get(1).getAsDouble();
double lon = ((JsonArray) coords).get(0).getAsDouble();
exif.setLatLong(lat, lon);
JsonElement angleElement = meta.get(COMPUTED_COMPASS_ANGLE_FIELD);
if (angleElement instanceof JsonPrimitive) {
float angle = angleElement.getAsFloat();
exif.setAttribute(ExifInterface.TAG_GPS_IMG_DIRECTION, Integer.toString((int) (angle * 100)) + "/100");
exif.setAttribute(ExifInterface.TAG_GPS_IMG_DIRECTION_REF, ExifInterface.GPS_DIRECTION_MAGNETIC);
}
JsonElement capturedAt = meta.get(CAPTURED_AT_FIELD);
if (capturedAt instanceof JsonPrimitive) {
exif.setDateTime(capturedAt.getAsLong());
}
exif.saveAttributes();
coordinates.put(key, new double[] { lat, lon });
}
}

Expand Down Expand Up @@ -231,4 +248,16 @@ public void share(Context context, String key) {
ScreenMessage.toastTopError(context, context.getString(R.string.toast_error_accessing_photo, key));
}
}

@Override
public boolean supportsInfo() {
return true;
}

@Override
public void info(@NonNull FragmentActivity activity, @NonNull String uri) {
Uri f = FileProvider.getUriForFile(activity, activity.getString(R.string.content_provider), new File(cacheDir, uri + JPG));
ImageInfo.showDialog(activity, f.toString());

}
}

0 comments on commit 8dde9f8

Please sign in to comment.