Added functionality to export location of nearby missing pictures to GPX file and KML file (#5645)

* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Removed mapbox code1

* Removed mapbox code2

* Fixed failing tests

* Fixed failing due to merging

* Added feature to save nearby places as GPX and KML

* Fixed error caused by null
This commit is contained in:
Kanahia 2024-03-25 19:52:17 +05:30 committed by GitHub
parent dae1f2557e
commit c41940241b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 443 additions and 0 deletions

View file

@ -118,6 +118,14 @@ public class NearbyController extends MapController {
return nearbyPlacesInfo;
}
public String getPlacesAsKML(LatLng leftLatLng, LatLng rightLatLng) throws Exception {
return nearbyPlaces.getPlacesAsKML(leftLatLng, rightLatLng);
}
public String getPlacesAsGPX(LatLng leftLatLng, LatLng rightLatLng) throws Exception {
return nearbyPlaces.getPlacesAsGPX(leftLatLng, rightLatLng);
}
/**
* Prepares Place list to make their distance information update later.
*

View file

@ -119,4 +119,27 @@ public class NearbyPlaces {
.getNearbyPlaces(screenTopRight, screenBottomLeft, lang, shouldQueryForMonuments,
customQuery);
}
/**
* Runs the Wikidata query to retrieve the KML String
*
* @param leftLatLng coordinates of Left Most position
* @param rightLatLng coordinates of Right Most position
* @throws IOException if query fails
*/
public String getPlacesAsKML(LatLng leftLatLng, LatLng rightLatLng) throws Exception {
return okHttpJsonApiClient.getPlacesAsKML(leftLatLng, rightLatLng);
}
/**
* Runs the Wikidata query to retrieve the GPX String
*
* @param leftLatLng coordinates of Left Most position
* @param rightLatLng coordinates of Right Most position
* @throws IOException if query fails
*/
public String getPlacesAsGPX(LatLng leftLatLng, LatLng rightLatLng) throws Exception {
return okHttpJsonApiClient.getPlacesAsGPX(leftLatLng, rightLatLng);
}
}

View file

@ -22,6 +22,7 @@ import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Html;
@ -80,6 +81,7 @@ import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.contributions.MainActivity.ActiveFragment;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.nearby.CheckBoxTriStates;
@ -105,11 +107,15 @@ import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@ -357,6 +363,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.nearby_fragment_menu, menu);
MenuItem listMenu = menu.findItem(R.id.list_sheet);
MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx);
MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml);
listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
@ -364,6 +372,44 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
return false;
}
});
saveAsGPXButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(@NonNull MenuItem item) {
try {
IGeoPoint screenTopRight = mapView.getProjection().fromPixels(mapView.getWidth(), 0);
IGeoPoint screenBottomLeft = mapView.getProjection().fromPixels(0, mapView.getHeight());
fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(
screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0);
fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(
screenTopRight.getLatitude(), screenTopRight.getLongitude(), 0);
setProgressBarVisibility(true);
savePlacesAsGPX(screenTopRightLatLng,screenBottomLeftLatLng);
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
});
saveAsKMLButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(@NonNull MenuItem item) {
try {
IGeoPoint screenTopRight = mapView.getProjection().fromPixels(mapView.getWidth(), 0);
IGeoPoint screenBottomLeft = mapView.getProjection().fromPixels(0, mapView.getHeight());
fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(
screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0);
fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(
screenTopRight.getLatitude(), screenTopRight.getLongitude(), 0);
setProgressBarVisibility(true);
savePlacesAsKML(screenTopRightLatLng,screenBottomLeftLatLng);
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
});
}
@Override
@ -1198,6 +1244,102 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
}
private void savePlacesAsKML(LatLng latLng, LatLng nextlatLng) {
final Observable<String> savePlacesObservable = Observable
.fromCallable(() -> nearbyController
.getPlacesAsKML(latLng, nextlatLng));
compositeDisposable.add(savePlacesObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(kmlString -> {
if (kmlString != null) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date());
String fileName =
"KML_" + timeStamp + "_" + System.currentTimeMillis() + ".kml";
boolean saved = saveFile(kmlString, fileName);
setProgressBarVisibility(false);
if (saved) {
Toast.makeText(this.getContext(),
"KML file saved successfully at /Downloads/" + fileName,
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this.getContext(), "Failed to save KML file.",
Toast.LENGTH_SHORT).show();
}
}
},
throwable -> {
Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places)
+ throwable.getLocalizedMessage());
setProgressBarVisibility(false);
presenter.lockUnlockNearby(false);
setFilterState();
}));
}
private void savePlacesAsGPX(LatLng latLng, LatLng nextlatLng) {
final Observable<String> savePlacesObservable = Observable
.fromCallable(() -> nearbyController
.getPlacesAsGPX(latLng, nextlatLng));
compositeDisposable.add(savePlacesObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(gpxString -> {
if (gpxString != null) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date());
String fileName =
"GPX_" + timeStamp + "_" + System.currentTimeMillis() + ".gpx";
boolean saved = saveFile(gpxString, fileName);
setProgressBarVisibility(false);
if (saved) {
Toast.makeText(this.getContext(),
"GPX file saved successfully at /Downloads/" + fileName,
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this.getContext(), "Failed to save KML file.",
Toast.LENGTH_SHORT).show();
}
}
},
throwable -> {
Timber.d(throwable);
showErrorMessage(getString(R.string.error_fetching_nearby_places)
+ throwable.getLocalizedMessage());
setProgressBarVisibility(false);
presenter.lockUnlockNearby(false);
setFilterState();
}));
}
public static boolean saveFile(String string, String fileName) {
if (!isExternalStorageWritable()) {
return false;
}
File downloadsDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS);
File kmlFile = new File(downloadsDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(kmlFile);
fos.write(string.getBytes());
fos.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
private void populatePlacesForCurrentLocation(
final fr.free.nrw.commons.location.LatLng currentLatLng,
final fr.free.nrw.commons.location.LatLng screenTopRight,

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons.nearby.model
import com.google.gson.annotations.SerializedName
data class PlaceBindings(
@SerializedName("item") val item: Item,
@SerializedName("label") val label: Label,
@SerializedName("location") val location: Location,
@SerializedName("class") val clas: Clas
)
data class ItemsClass(
@SerializedName("head") val head: Head,
@SerializedName("results") val results: Results
)
data class Label(
@SerializedName("xml:lang") val xml: String,
@SerializedName("type") val type: String,
@SerializedName("value") val value: String
)
data class Location(
@SerializedName("datatype") val datatype: String,
@SerializedName("type") val type: String,
@SerializedName("value") val value: String
)
data class Results(
@SerializedName("bindings") val bindings: List<PlaceBindings>
)
data class Item(
@SerializedName("type") val type: String,
@SerializedName("value") val value: String
)
data class Head(
@SerializedName("vars") val vars: List<String>
)
data class Clas(
@SerializedName("type") val type: String,
@SerializedName("value") val value: String
)