mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 14:53:59 +01:00 
			
		
		
		
	Convert OkHttpJsonApiClient and CategoryApi to kotlin
This commit is contained in:
		
							parent
							
								
									33548fa57d
								
							
						
					
					
						commit
						79de03964e
					
				
					 7 changed files with 633 additions and 783 deletions
				
			
		|  | @ -52,12 +52,12 @@ class CampaignsPresenter @Inject constructor( | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             okHttpJsonApiClient.campaigns |             okHttpJsonApiClient.getCampaigns() | ||||||
|                 .observeOn(mainThreadScheduler) |                 .observeOn(mainThreadScheduler) | ||||||
|                 .subscribeOn(ioScheduler) |                 .subscribeOn(ioScheduler) | ||||||
|                 .doOnSubscribe { disposable = it } |                 .doOnSubscribe { disposable = it } | ||||||
|                 .subscribe({ campaignResponseDTO -> |                 .subscribe({ campaignResponseDTO -> | ||||||
|                     val campaigns = campaignResponseDTO.campaigns?.toMutableList() |                     val campaigns = campaignResponseDTO?.campaigns?.toMutableList() | ||||||
|                     if (campaigns.isNullOrEmpty()) { |                     if (campaigns.isNullOrEmpty()) { | ||||||
|                         Timber.e("The campaigns list is empty") |                         Timber.e("The campaigns list is empty") | ||||||
|                         view!!.showCampaigns(null) |                         view!!.showCampaigns(null) | ||||||
|  |  | ||||||
|  | @ -1,99 +0,0 @@ | ||||||
| package fr.free.nrw.commons.mwapi; |  | ||||||
| 
 |  | ||||||
| import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; |  | ||||||
| 
 |  | ||||||
| import com.google.gson.Gson; |  | ||||||
| import fr.free.nrw.commons.BuildConfig; |  | ||||||
| import fr.free.nrw.commons.category.CategoryItem; |  | ||||||
| import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage; |  | ||||||
| import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse; |  | ||||||
| import io.reactivex.Single; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.LinkedHashSet; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Set; |  | ||||||
| import javax.inject.Inject; |  | ||||||
| import okhttp3.HttpUrl; |  | ||||||
| import okhttp3.OkHttpClient; |  | ||||||
| import okhttp3.Request; |  | ||||||
| import okhttp3.Response; |  | ||||||
| import okhttp3.ResponseBody; |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates |  | ||||||
|  * with nearby Commons categories. Parses the results using GSON to obtain a list of relevant |  | ||||||
|  * categories.  Note: that caller is responsible for executing the request() method on a background |  | ||||||
|  * thread. |  | ||||||
|  */ |  | ||||||
| public class CategoryApi { |  | ||||||
| 
 |  | ||||||
|     private final OkHttpClient okHttpClient; |  | ||||||
|     private final Gson gson; |  | ||||||
| 
 |  | ||||||
|     @Inject |  | ||||||
|     public CategoryApi(final OkHttpClient okHttpClient, final Gson gson) { |  | ||||||
|         this.okHttpClient = okHttpClient; |  | ||||||
|         this.gson = gson; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public Single<List<CategoryItem>> request(String coords) { |  | ||||||
|         return Single.fromCallable(() -> { |  | ||||||
|             HttpUrl apiUrl = buildUrl(coords); |  | ||||||
|             Timber.d("URL: %s", apiUrl.toString()); |  | ||||||
| 
 |  | ||||||
|             Request request = new Request.Builder().get().url(apiUrl).build(); |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             ResponseBody body = response.body(); |  | ||||||
|             if (body == null) { |  | ||||||
|                 return Collections.emptyList(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             MwQueryResponse apiResponse = gson.fromJson(body.charStream(), MwQueryResponse.class); |  | ||||||
|             Set<CategoryItem> categories = new LinkedHashSet<>(); |  | ||||||
|             if (apiResponse != null && apiResponse.query() != null && apiResponse.query().pages() != null) { |  | ||||||
|                 for (MwQueryPage page : apiResponse.query().pages()) { |  | ||||||
|                     if (page.categories() != null) { |  | ||||||
|                         for (MwQueryPage.Category category : page.categories()) { |  | ||||||
|                             categories.add(new CategoryItem(category.title().replace(CATEGORY_PREFIX, ""), "", "", false)); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return new ArrayList<>(categories); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Builds URL with image coords for MediaWiki API calls |  | ||||||
|      * Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2 |  | ||||||
|      * |  | ||||||
|      * @param coords Coordinates to build query with |  | ||||||
|      * @return URL for API query |  | ||||||
|      */ |  | ||||||
|     private HttpUrl buildUrl(final String coords) { |  | ||||||
|         return HttpUrl |  | ||||||
|                 .parse(BuildConfig.WIKIMEDIA_API_HOST) |  | ||||||
|                 .newBuilder() |  | ||||||
|                 .addQueryParameter("action", "query") |  | ||||||
|                 .addQueryParameter("prop", "categories|coordinates|pageprops") |  | ||||||
|                 .addQueryParameter("format", "json") |  | ||||||
|                 .addQueryParameter("clshow", "!hidden") |  | ||||||
|                 .addQueryParameter("coprop", "type|name|dim|country|region|globe") |  | ||||||
|                 .addQueryParameter("codistancefrompoint", coords) |  | ||||||
|                 .addQueryParameter("generator", "geosearch") |  | ||||||
|                 .addQueryParameter("ggscoord", coords) |  | ||||||
|                 .addQueryParameter("ggsradius", "10000") |  | ||||||
|                 .addQueryParameter("ggslimit", "10") |  | ||||||
|                 .addQueryParameter("ggsnamespace", "6") |  | ||||||
|                 .addQueryParameter("ggsprop", "type|name|dim|country|region|globe") |  | ||||||
|                 .addQueryParameter("ggsprimary", "all") |  | ||||||
|                 .addQueryParameter("formatversion", "2") |  | ||||||
|                 .build(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
							
								
								
									
										83
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | ||||||
|  | package fr.free.nrw.commons.mwapi | ||||||
|  | 
 | ||||||
|  | import com.google.gson.Gson | ||||||
|  | import fr.free.nrw.commons.BuildConfig | ||||||
|  | import fr.free.nrw.commons.category.CATEGORY_PREFIX | ||||||
|  | import fr.free.nrw.commons.category.CategoryItem | ||||||
|  | import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse | ||||||
|  | import io.reactivex.Single | ||||||
|  | import okhttp3.HttpUrl | ||||||
|  | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||||
|  | import okhttp3.OkHttpClient | ||||||
|  | import okhttp3.Request | ||||||
|  | import timber.log.Timber | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates | ||||||
|  |  * with nearby Commons categories. Parses the results using GSON to obtain a list of relevant | ||||||
|  |  * categories.  Note: that caller is responsible for executing the request() method on a background | ||||||
|  |  * thread. | ||||||
|  |  */ | ||||||
|  | class CategoryApi @Inject constructor( | ||||||
|  |     private val okHttpClient: OkHttpClient, | ||||||
|  |     private val gson: Gson | ||||||
|  | ) { | ||||||
|  |     private val apiUrl : HttpUrl by lazy { BuildConfig.WIKIMEDIA_API_HOST.toHttpUrlOrNull()!! } | ||||||
|  | 
 | ||||||
|  |     fun request(coords: String): Single<List<CategoryItem>> = Single.fromCallable { | ||||||
|  |         val apiUrl = buildUrl(coords) | ||||||
|  |         Timber.d("URL: %s", apiUrl.toString()) | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder().get().url(apiUrl).build() | ||||||
|  |         val response = okHttpClient.newCall(request).execute() | ||||||
|  |         val body = response.body ?: return@fromCallable emptyList<CategoryItem>() | ||||||
|  | 
 | ||||||
|  |         val apiResponse = gson.fromJson(body.charStream(), MwQueryResponse::class.java) | ||||||
|  |         val categories: MutableSet<CategoryItem> = mutableSetOf() | ||||||
|  |         if (apiResponse?.query() != null && apiResponse.query()!!.pages() != null) { | ||||||
|  |             for (page in apiResponse.query()!!.pages()!!) { | ||||||
|  |                 if (page.categories() != null) { | ||||||
|  |                     for (category in page.categories()!!) { | ||||||
|  |                         categories.add( | ||||||
|  |                             CategoryItem( | ||||||
|  |                                 name = category.title().replace(CATEGORY_PREFIX, ""), | ||||||
|  |                                 description = "", | ||||||
|  |                                 thumbnail = "", | ||||||
|  |                                 isSelected = false | ||||||
|  |                             ) | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         ArrayList<CategoryItem>(categories) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Builds URL with image coords for MediaWiki API calls | ||||||
|  |      * Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2 | ||||||
|  |      * | ||||||
|  |      * @param coords Coordinates to build query with | ||||||
|  |      * @return URL for API query | ||||||
|  |      */ | ||||||
|  |     private fun buildUrl(coords: String): HttpUrl = apiUrl.newBuilder() | ||||||
|  |         .addQueryParameter("action", "query") | ||||||
|  |         .addQueryParameter("prop", "categories|coordinates|pageprops") | ||||||
|  |         .addQueryParameter("format", "json") | ||||||
|  |         .addQueryParameter("clshow", "!hidden") | ||||||
|  |         .addQueryParameter("coprop", "type|name|dim|country|region|globe") | ||||||
|  |         .addQueryParameter("codistancefrompoint", coords) | ||||||
|  |         .addQueryParameter("generator", "geosearch") | ||||||
|  |         .addQueryParameter("ggscoord", coords) | ||||||
|  |         .addQueryParameter("ggsradius", "10000") | ||||||
|  |         .addQueryParameter("ggslimit", "10") | ||||||
|  |         .addQueryParameter("ggsnamespace", "6") | ||||||
|  |         .addQueryParameter("ggsprop", "type|name|dim|country|region|globe") | ||||||
|  |         .addQueryParameter("ggsprimary", "all") | ||||||
|  |         .addQueryParameter("formatversion", "2") | ||||||
|  |         .build() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -1,677 +0,0 @@ | ||||||
| package fr.free.nrw.commons.mwapi; |  | ||||||
| 
 |  | ||||||
| import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LEADERBOARD_END_POINT; |  | ||||||
| import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.UPDATE_AVATAR_END_POINT; |  | ||||||
| 
 |  | ||||||
| import android.text.TextUtils; |  | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| import androidx.annotation.Nullable; |  | ||||||
| import com.google.gson.Gson; |  | ||||||
| import fr.free.nrw.commons.campaigns.CampaignResponseDTO; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.DepictsClient; |  | ||||||
| import fr.free.nrw.commons.location.LatLng; |  | ||||||
| import fr.free.nrw.commons.nearby.Place; |  | ||||||
| import fr.free.nrw.commons.nearby.model.ItemsClass; |  | ||||||
| import fr.free.nrw.commons.nearby.model.NearbyResponse; |  | ||||||
| import fr.free.nrw.commons.nearby.model.NearbyResultItem; |  | ||||||
| import fr.free.nrw.commons.nearby.model.PlaceBindings; |  | ||||||
| import fr.free.nrw.commons.profile.achievements.FeaturedImages; |  | ||||||
| import fr.free.nrw.commons.profile.achievements.FeedbackResponse; |  | ||||||
| import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse; |  | ||||||
| import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse; |  | ||||||
| import fr.free.nrw.commons.upload.FileUtils; |  | ||||||
| import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; |  | ||||||
| import fr.free.nrw.commons.utils.ConfigUtils; |  | ||||||
| import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse; |  | ||||||
| import io.reactivex.Observable; |  | ||||||
| import io.reactivex.Single; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Locale; |  | ||||||
| import java.util.regex.Matcher; |  | ||||||
| import java.util.regex.Pattern; |  | ||||||
| import javax.inject.Inject; |  | ||||||
| import javax.inject.Singleton; |  | ||||||
| import okhttp3.HttpUrl; |  | ||||||
| import okhttp3.OkHttpClient; |  | ||||||
| import okhttp3.Request; |  | ||||||
| import okhttp3.Response; |  | ||||||
| import okhttp3.ResponseBody; |  | ||||||
| import org.jetbrains.annotations.NotNull; |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Test methods in ok http api client |  | ||||||
|  */ |  | ||||||
| @Singleton |  | ||||||
| public class OkHttpJsonApiClient { |  | ||||||
| 
 |  | ||||||
|     private final OkHttpClient okHttpClient; |  | ||||||
|     private final DepictsClient depictsClient; |  | ||||||
|     private final HttpUrl wikiMediaToolforgeUrl; |  | ||||||
|     private final String sparqlQueryUrl; |  | ||||||
|     private final String campaignsUrl; |  | ||||||
|     private final Gson gson; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     @Inject |  | ||||||
|     public OkHttpJsonApiClient(OkHttpClient okHttpClient, |  | ||||||
|         DepictsClient depictsClient, |  | ||||||
|         HttpUrl wikiMediaToolforgeUrl, |  | ||||||
|         String sparqlQueryUrl, |  | ||||||
|         String campaignsUrl, |  | ||||||
|         Gson gson) { |  | ||||||
|         this.okHttpClient = okHttpClient; |  | ||||||
|         this.depictsClient = depictsClient; |  | ||||||
|         this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl; |  | ||||||
|         this.sparqlQueryUrl = sparqlQueryUrl; |  | ||||||
|         this.campaignsUrl = campaignsUrl; |  | ||||||
|         this.gson = gson; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The method will gradually calls the leaderboard API and fetches the leaderboard |  | ||||||
|      * |  | ||||||
|      * @param userName username of leaderboard user |  | ||||||
|      * @param duration duration for leaderboard |  | ||||||
|      * @param category category for leaderboard |  | ||||||
|      * @param limit    page size limit for list |  | ||||||
|      * @param offset   offset for the list |  | ||||||
|      * @return LeaderboardResponse object |  | ||||||
|      */ |  | ||||||
|     @NonNull |  | ||||||
|     public Observable<LeaderboardResponse> getLeaderboard(String userName, String duration, |  | ||||||
|         String category, String limit, String offset) { |  | ||||||
|         final String fetchLeaderboardUrlTemplate = wikiMediaToolforgeUrl |  | ||||||
|             + LEADERBOARD_END_POINT; |  | ||||||
|         String url = String.format(Locale.ENGLISH, |  | ||||||
|             fetchLeaderboardUrlTemplate, |  | ||||||
|             userName, |  | ||||||
|             duration, |  | ||||||
|             category, |  | ||||||
|             limit, |  | ||||||
|             offset); |  | ||||||
|         HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); |  | ||||||
|         urlBuilder.addQueryParameter("user", userName); |  | ||||||
|         urlBuilder.addQueryParameter("duration", duration); |  | ||||||
|         urlBuilder.addQueryParameter("category", category); |  | ||||||
|         urlBuilder.addQueryParameter("limit", limit); |  | ||||||
|         urlBuilder.addQueryParameter("offset", offset); |  | ||||||
|         Timber.i("Url %s", urlBuilder.toString()); |  | ||||||
|         Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.toString()) |  | ||||||
|             .build(); |  | ||||||
|         return Observable.fromCallable(() -> { |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             if (response != null && response.body() != null && response.isSuccessful()) { |  | ||||||
|                 String json = response.body().string(); |  | ||||||
|                 if (json == null) { |  | ||||||
|                     return new LeaderboardResponse(); |  | ||||||
|                 } |  | ||||||
|                 Timber.d("Response for leaderboard is %s", json); |  | ||||||
|                 try { |  | ||||||
|                     return gson.fromJson(json, LeaderboardResponse.class); |  | ||||||
|                 } catch (Exception e) { |  | ||||||
|                     return new LeaderboardResponse(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return new LeaderboardResponse(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * This method will update the leaderboard user avatar |  | ||||||
|      * |  | ||||||
|      * @param username username to update |  | ||||||
|      * @param avatar   url of the new avatar |  | ||||||
|      * @return UpdateAvatarResponse object |  | ||||||
|      */ |  | ||||||
|     @NonNull |  | ||||||
|     public Single<UpdateAvatarResponse> setAvatar(String username, String avatar) { |  | ||||||
|         final String urlTemplate = wikiMediaToolforgeUrl |  | ||||||
|             + UPDATE_AVATAR_END_POINT; |  | ||||||
|         return Single.fromCallable(() -> { |  | ||||||
|             String url = String.format(Locale.ENGLISH, |  | ||||||
|                 urlTemplate, |  | ||||||
|                 username, |  | ||||||
|                 avatar); |  | ||||||
|             HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); |  | ||||||
|             urlBuilder.addQueryParameter("user", username); |  | ||||||
|             urlBuilder.addQueryParameter("avatar", avatar); |  | ||||||
|             Timber.i("Url %s", urlBuilder.toString()); |  | ||||||
|             Request request = new Request.Builder() |  | ||||||
|                 .url(urlBuilder.toString()) |  | ||||||
|                 .build(); |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             if (response != null && response.body() != null && response.isSuccessful()) { |  | ||||||
|                 String json = response.body().string(); |  | ||||||
|                 if (json == null) { |  | ||||||
|                     return null; |  | ||||||
|                 } |  | ||||||
|                 try { |  | ||||||
|                     return gson.fromJson(json, UpdateAvatarResponse.class); |  | ||||||
|                 } catch (Exception e) { |  | ||||||
|                     return new UpdateAvatarResponse(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return null; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NonNull |  | ||||||
|     public Single<Integer> getUploadCount(String userName) { |  | ||||||
|         HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); |  | ||||||
|         urlBuilder |  | ||||||
|             .addPathSegments("uploadsbyuser.py") |  | ||||||
|             .addQueryParameter("user", userName); |  | ||||||
| 
 |  | ||||||
|         if (ConfigUtils.isBetaFlavour()) { |  | ||||||
|             urlBuilder.addQueryParameter("labs", "commonswiki"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
| 
 |  | ||||||
|         return Single.fromCallable(() -> { |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             if (response != null && response.isSuccessful()) { |  | ||||||
|                 ResponseBody responseBody = response.body(); |  | ||||||
|                 if (null != responseBody) { |  | ||||||
|                     String responseBodyString = responseBody.string().trim(); |  | ||||||
|                     if (!TextUtils.isEmpty(responseBodyString)) { |  | ||||||
|                         try { |  | ||||||
|                             return Integer.parseInt(responseBodyString); |  | ||||||
|                         } catch (NumberFormatException e) { |  | ||||||
|                             Timber.e(e); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return 0; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NonNull |  | ||||||
|     public Single<Integer> getWikidataEdits(String userName) { |  | ||||||
|         HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); |  | ||||||
|         urlBuilder |  | ||||||
|             .addPathSegments("wikidataedits.py") |  | ||||||
|             .addQueryParameter("user", userName); |  | ||||||
| 
 |  | ||||||
|         if (ConfigUtils.isBetaFlavour()) { |  | ||||||
|             urlBuilder.addQueryParameter("labs", "commonswiki"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
| 
 |  | ||||||
|         return Single.fromCallable(() -> { |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             if (response != null && |  | ||||||
|                 response.isSuccessful() && response.body() != null) { |  | ||||||
|                 String json = response.body().string(); |  | ||||||
|                 if (json == null) { |  | ||||||
|                     return 0; |  | ||||||
|                 } |  | ||||||
|                 // Extract JSON from response |  | ||||||
|                 json = json.substring(json.indexOf('{')); |  | ||||||
|                 GetWikidataEditCountResponse countResponse = gson |  | ||||||
|                     .fromJson(json, GetWikidataEditCountResponse.class); |  | ||||||
|                 if (null != countResponse) { |  | ||||||
|                     return countResponse.getWikidataEditCount(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return 0; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * This takes userName as input, which is then used to fetch the feedback/achievements |  | ||||||
|      * statistics using OkHttp and JavaRx. This function return JSONObject |  | ||||||
|      * |  | ||||||
|      * @param userName MediaWiki user name |  | ||||||
|      * @return |  | ||||||
|      */ |  | ||||||
|     public Single<FeedbackResponse> getAchievements(String userName) { |  | ||||||
|         final String fetchAchievementUrlTemplate = |  | ||||||
|             wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki" |  | ||||||
|                 : "/feedback.py"); |  | ||||||
|         return Single.fromCallable(() -> { |  | ||||||
|             String url = String.format( |  | ||||||
|                 Locale.ENGLISH, |  | ||||||
|                 fetchAchievementUrlTemplate, |  | ||||||
|                 userName); |  | ||||||
|             HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); |  | ||||||
|             urlBuilder.addQueryParameter("user", userName); |  | ||||||
|             Request request = new Request.Builder() |  | ||||||
|                 .url(urlBuilder.toString()) |  | ||||||
|                 .build(); |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             if (response != null && response.body() != null && response.isSuccessful()) { |  | ||||||
|                 String json = response.body().string(); |  | ||||||
|                 if (json == null) { |  | ||||||
|                     return null; |  | ||||||
|                 } |  | ||||||
|                 // Extract JSON from response |  | ||||||
|                 json = json.substring(json.indexOf('{')); |  | ||||||
|                 Timber.d("Response for achievements is %s", json); |  | ||||||
|                 try { |  | ||||||
|                     return gson.fromJson(json, FeedbackResponse.class); |  | ||||||
|                 } catch (Exception e) { |  | ||||||
|                     return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, ""); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|             } |  | ||||||
|             return null; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Make API Call to get Nearby Places |  | ||||||
|      * |  | ||||||
|      * @param cur      Search lat long |  | ||||||
|      * @param language Language |  | ||||||
|      * @param radius   Search Radius |  | ||||||
|      * @return |  | ||||||
|      * @throws Exception |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius, |  | ||||||
|         final String customQuery) |  | ||||||
|         throws Exception { |  | ||||||
| 
 |  | ||||||
|         Timber.d("Fetching nearby items at radius %s", radius); |  | ||||||
|         Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); |  | ||||||
|         final String wikidataQuery; |  | ||||||
|         if (customQuery != null) { |  | ||||||
|             wikidataQuery = customQuery; |  | ||||||
|         } else { |  | ||||||
|             wikidataQuery = FileUtils.readFromResource( |  | ||||||
|                 "/queries/radius_query_for_upload_wizard.rq"); |  | ||||||
|         } |  | ||||||
|         final String query = wikidataQuery |  | ||||||
|             .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius)) |  | ||||||
|             .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude())) |  | ||||||
|             .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude())) |  | ||||||
|             .replace("${LANG}", language); |  | ||||||
| 
 |  | ||||||
|         final HttpUrl.Builder urlBuilder = HttpUrl |  | ||||||
|             .parse(sparqlQueryUrl) |  | ||||||
|             .newBuilder() |  | ||||||
|             .addQueryParameter("query", query) |  | ||||||
|             .addQueryParameter("format", "json"); |  | ||||||
| 
 |  | ||||||
|         final Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
| 
 |  | ||||||
|         final Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|         if (response.body() != null && response.isSuccessful()) { |  | ||||||
|             final String json = response.body().string(); |  | ||||||
|             final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); |  | ||||||
|             final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings(); |  | ||||||
|             final List<Place> places = new ArrayList<>(); |  | ||||||
|             for (final NearbyResultItem item : bindings) { |  | ||||||
|                 final Place placeFromNearbyItem = Place.from(item); |  | ||||||
|                 placeFromNearbyItem.setMonument(false); |  | ||||||
|                 places.add(placeFromNearbyItem); |  | ||||||
|             } |  | ||||||
|             return places; |  | ||||||
|         } |  | ||||||
|         throw new Exception(response.message()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Retrieves nearby places based on screen coordinates and optional query parameters. |  | ||||||
|      * |  | ||||||
|      * @param screenTopRight          The top right corner of the screen (latitude, longitude). |  | ||||||
|      * @param screenBottomLeft        The bottom left corner of the screen (latitude, longitude). |  | ||||||
|      * @param language                The language for the query. |  | ||||||
|      * @param shouldQueryForMonuments Flag indicating whether to include monuments in the query. |  | ||||||
|      * @param customQuery             Optional custom SPARQL query to use instead of default |  | ||||||
|      *                                queries. |  | ||||||
|      * @return A list of nearby places. |  | ||||||
|      * @throws Exception If an error occurs during the retrieval process. |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     public List<Place> getNearbyPlaces( |  | ||||||
|         final fr.free.nrw.commons.location.LatLng screenTopRight, |  | ||||||
|         final fr.free.nrw.commons.location.LatLng screenBottomLeft, final String language, |  | ||||||
|         final boolean shouldQueryForMonuments, final String customQuery) |  | ||||||
|         throws Exception { |  | ||||||
| 
 |  | ||||||
|         Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); |  | ||||||
| 
 |  | ||||||
|         final String wikidataQuery; |  | ||||||
|         if (customQuery != null) { |  | ||||||
|             wikidataQuery = customQuery; |  | ||||||
|         } else if (!shouldQueryForMonuments) { |  | ||||||
|             wikidataQuery = FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq"); |  | ||||||
|         } else { |  | ||||||
|             wikidataQuery = FileUtils.readFromResource( |  | ||||||
|                 "/queries/rectangle_query_for_nearby_monuments.rq"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final double westCornerLat = screenTopRight.getLatitude(); |  | ||||||
|         final double westCornerLong = screenTopRight.getLongitude(); |  | ||||||
|         final double eastCornerLat = screenBottomLeft.getLatitude(); |  | ||||||
|         final double eastCornerLong = screenBottomLeft.getLongitude(); |  | ||||||
| 
 |  | ||||||
|         final String query = wikidataQuery |  | ||||||
|             .replace("${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat)) |  | ||||||
|             .replace("${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong)) |  | ||||||
|             .replace("${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat)) |  | ||||||
|             .replace("${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong)) |  | ||||||
|             .replace("${LANG}", language); |  | ||||||
|         final HttpUrl.Builder urlBuilder = HttpUrl |  | ||||||
|             .parse(sparqlQueryUrl) |  | ||||||
|             .newBuilder() |  | ||||||
|             .addQueryParameter("query", query) |  | ||||||
|             .addQueryParameter("format", "json"); |  | ||||||
| 
 |  | ||||||
|         final Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
| 
 |  | ||||||
|         final Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|         if (response.body() != null && response.isSuccessful()) { |  | ||||||
|             final String json = response.body().string(); |  | ||||||
|             final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); |  | ||||||
|             final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings(); |  | ||||||
|             final List<Place> places = new ArrayList<>(); |  | ||||||
|             for (final NearbyResultItem item : bindings) { |  | ||||||
|                 final Place placeFromNearbyItem = Place.from(item); |  | ||||||
|                 if (shouldQueryForMonuments && item.getMonument() != null) { |  | ||||||
|                     placeFromNearbyItem.setMonument(true); |  | ||||||
|                 } else { |  | ||||||
|                     placeFromNearbyItem.setMonument(false); |  | ||||||
|                 } |  | ||||||
|                 places.add(placeFromNearbyItem); |  | ||||||
|             } |  | ||||||
|             return places; |  | ||||||
|         } |  | ||||||
|         throw new Exception(response.message()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Retrieves a list of places based on the provided list of places and language. |  | ||||||
|      * |  | ||||||
|      * @param placeList A list of Place objects for which to fetch information. |  | ||||||
|      * @param language  The language code to use for the query. |  | ||||||
|      * @return A list of Place objects with additional information retrieved from Wikidata, or null |  | ||||||
|      * if an error occurs. |  | ||||||
|      * @throws IOException If there is an issue with reading the resource file or executing the HTTP |  | ||||||
|      *                     request. |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     public List<Place> getPlaces( |  | ||||||
|         final List<Place> placeList, final String language) throws IOException { |  | ||||||
|         final String wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq"); |  | ||||||
|         String qids = ""; |  | ||||||
|         for (final Place place : placeList) { |  | ||||||
|             qids += "\n" + ("wd:" + place.getWikiDataEntityId()); |  | ||||||
|         } |  | ||||||
|         final String query = wikidataQuery |  | ||||||
|             .replace("${ENTITY}", qids) |  | ||||||
|             .replace("${LANG}", language); |  | ||||||
|         final HttpUrl.Builder urlBuilder = HttpUrl |  | ||||||
|             .parse(sparqlQueryUrl) |  | ||||||
|             .newBuilder() |  | ||||||
|             .addQueryParameter("query", query) |  | ||||||
|             .addQueryParameter("format", "json"); |  | ||||||
| 
 |  | ||||||
|         final Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
| 
 |  | ||||||
|         try (Response response = okHttpClient.newCall(request).execute()) { |  | ||||||
|             if (response.isSuccessful()) { |  | ||||||
|                 final String json = response.body().string(); |  | ||||||
|                 final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class); |  | ||||||
|                 final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings(); |  | ||||||
|                 final List<Place> places = new ArrayList<>(); |  | ||||||
|                 for (final NearbyResultItem item : bindings) { |  | ||||||
|                     final Place placeFromNearbyItem = Place.from(item); |  | ||||||
|                     places.add(placeFromNearbyItem); |  | ||||||
|                 } |  | ||||||
|                 return places; |  | ||||||
|             } else { |  | ||||||
|                 throw new IOException("Unexpected response code: " + response.code()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Make API Call to get Places |  | ||||||
|      * |  | ||||||
|      * @param leftLatLng  Left lat long |  | ||||||
|      * @param rightLatLng Right lat long |  | ||||||
|      * @return |  | ||||||
|      * @throws Exception |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     public String getPlacesAsKML(final LatLng leftLatLng, final LatLng rightLatLng) |  | ||||||
|         throws Exception { |  | ||||||
|         String kmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" + |  | ||||||
|             "<!--Created by Wikimedia Commons Android app -->\n" + |  | ||||||
|             "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n" + |  | ||||||
|             "    <Document>"; |  | ||||||
|         List<PlaceBindings> placeBindings = runQuery(leftLatLng, |  | ||||||
|             rightLatLng); |  | ||||||
|         if (placeBindings != null) { |  | ||||||
|             for (PlaceBindings item : placeBindings) { |  | ||||||
|                 if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) { |  | ||||||
|                     String input = item.getLocation().getValue(); |  | ||||||
|                     Pattern pattern = Pattern.compile( |  | ||||||
|                         "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"); |  | ||||||
|                     Matcher matcher = pattern.matcher(input); |  | ||||||
| 
 |  | ||||||
|                     if (matcher.find()) { |  | ||||||
|                         String longStr = matcher.group(1); |  | ||||||
|                         String latStr = matcher.group(2); |  | ||||||
|                         String itemUrl = item.getItem().getValue(); |  | ||||||
|                         String itemName = item.getLabel().getValue().replace("&", "&"); |  | ||||||
|                         String itemLatitude = latStr; |  | ||||||
|                         String itemLongitude = longStr; |  | ||||||
|                         String itemClass = item.getClas().getValue(); |  | ||||||
| 
 |  | ||||||
|                         String formattedItemName = |  | ||||||
|                             !itemClass.isEmpty() ? itemName + " (" + itemClass + ")" |  | ||||||
|                                 : itemName; |  | ||||||
| 
 |  | ||||||
|                         String kmlEntry = "\n        <Placemark>\n" + |  | ||||||
|                             "            <name>" + formattedItemName + "</name>\n" + |  | ||||||
|                             "            <description>" + itemUrl + "</description>\n" + |  | ||||||
|                             "            <Point>\n" + |  | ||||||
|                             "                <coordinates>" + itemLongitude + "," |  | ||||||
|                             + itemLatitude |  | ||||||
|                             + "</coordinates>\n" + |  | ||||||
|                             "            </Point>\n" + |  | ||||||
|                             "        </Placemark>"; |  | ||||||
|                         kmlString = kmlString + kmlEntry; |  | ||||||
|                     } else { |  | ||||||
|                         Timber.e("No match found"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         kmlString = kmlString + "\n    </Document>\n" + |  | ||||||
|             "</kml>\n"; |  | ||||||
|         return kmlString; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Make API Call to get Places |  | ||||||
|      * |  | ||||||
|      * @param leftLatLng  Left lat long |  | ||||||
|      * @param rightLatLng Right lat long |  | ||||||
|      * @return |  | ||||||
|      * @throws Exception |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     public String getPlacesAsGPX(final LatLng leftLatLng, final LatLng rightLatLng) |  | ||||||
|         throws Exception { |  | ||||||
|         String gpxString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" + |  | ||||||
|             "<gpx\n" + |  | ||||||
|             " version=\"1.0\"\n" + |  | ||||||
|             " creator=\"Wikimedia Commons Android app\"\n" + |  | ||||||
|             " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + |  | ||||||
|             " xmlns=\"http://www.topografix.com/GPX/1/0\"\n" + |  | ||||||
|             " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">" |  | ||||||
|             + "\n<bounds minlat=\"$MIN_LATITUDE\" minlon=\"$MIN_LONGITUDE\" maxlat=\"$MAX_LATITUDE\" maxlon=\"$MAX_LONGITUDE\"/>"; |  | ||||||
| 
 |  | ||||||
|         List<PlaceBindings> placeBindings = runQuery(leftLatLng, rightLatLng); |  | ||||||
|         if (placeBindings != null) { |  | ||||||
|             for (PlaceBindings item : placeBindings) { |  | ||||||
|                 if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) { |  | ||||||
|                     String input = item.getLocation().getValue(); |  | ||||||
|                     Pattern pattern = Pattern.compile( |  | ||||||
|                         "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)"); |  | ||||||
|                     Matcher matcher = pattern.matcher(input); |  | ||||||
| 
 |  | ||||||
|                     if (matcher.find()) { |  | ||||||
|                         String longStr = matcher.group(1); |  | ||||||
|                         String latStr = matcher.group(2); |  | ||||||
|                         String itemUrl = item.getItem().getValue(); |  | ||||||
|                         String itemName = item.getLabel().getValue().replace("&", "&"); |  | ||||||
|                         String itemLatitude = latStr; |  | ||||||
|                         String itemLongitude = longStr; |  | ||||||
|                         String itemClass = item.getClas().getValue(); |  | ||||||
| 
 |  | ||||||
|                         String formattedItemName = |  | ||||||
|                             !itemClass.isEmpty() ? itemName + " (" + itemClass + ")" |  | ||||||
|                                 : itemName; |  | ||||||
| 
 |  | ||||||
|                         String gpxEntry = |  | ||||||
|                             "\n    <wpt lat=\"" + itemLatitude + "\" lon=\"" + itemLongitude |  | ||||||
|                                 + "\">\n" + |  | ||||||
|                                 "        <name>" + itemName + "</name>\n" + |  | ||||||
|                                 "        <url>" + itemUrl + "</url>\n" + |  | ||||||
|                                 "    </wpt>"; |  | ||||||
|                         gpxString = gpxString + gpxEntry; |  | ||||||
| 
 |  | ||||||
|                     } else { |  | ||||||
|                         Timber.e("No match found"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|         gpxString = gpxString + "\n</gpx>"; |  | ||||||
|         return gpxString; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private List<PlaceBindings> runQuery(final LatLng currentLatLng, final LatLng nextLatLng) |  | ||||||
|         throws IOException { |  | ||||||
| 
 |  | ||||||
|         final String wikidataQuery = FileUtils.readFromResource("/queries/places_query.rq"); |  | ||||||
|         final String query = wikidataQuery |  | ||||||
|             .replace("${LONGITUDE}", |  | ||||||
|                 String.format(Locale.ROOT, "%.2f", currentLatLng.getLongitude())) |  | ||||||
|             .replace("${LATITUDE}", String.format(Locale.ROOT, "%.4f", currentLatLng.getLatitude())) |  | ||||||
|             .replace("${NEXT_LONGITUDE}", |  | ||||||
|                 String.format(Locale.ROOT, "%.4f", nextLatLng.getLongitude())) |  | ||||||
|             .replace("${NEXT_LATITUDE}", |  | ||||||
|                 String.format(Locale.ROOT, "%.4f", nextLatLng.getLatitude())); |  | ||||||
| 
 |  | ||||||
|         final HttpUrl.Builder urlBuilder = HttpUrl |  | ||||||
|             .parse(sparqlQueryUrl) |  | ||||||
|             .newBuilder() |  | ||||||
|             .addQueryParameter("query", query) |  | ||||||
|             .addQueryParameter("format", "json"); |  | ||||||
| 
 |  | ||||||
|         final Request request = new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
| 
 |  | ||||||
|         final Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|         if (response.body() != null && response.isSuccessful()) { |  | ||||||
|             final String json = response.body().string(); |  | ||||||
|             final ItemsClass item = gson.fromJson(json, ItemsClass.class); |  | ||||||
|             return item.getResults().getBindings(); |  | ||||||
|         } else { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Make API Call to get Nearby Places Implementation does not expects a custom query |  | ||||||
|      * |  | ||||||
|      * @param cur      Search lat long |  | ||||||
|      * @param language Language |  | ||||||
|      * @param radius   Search Radius |  | ||||||
|      * @return |  | ||||||
|      * @throws Exception |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius) |  | ||||||
|         throws Exception { |  | ||||||
|         return getNearbyPlaces(cur, language, radius, null); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example: |  | ||||||
|      * bridge -> suspended bridge, aqueduct, etc |  | ||||||
|      */ |  | ||||||
|     public Single<List<DepictedItem>> getChildDepictions(String qid, int startPosition, |  | ||||||
|         int limit) throws IOException { |  | ||||||
|         return depictedItemsFrom( |  | ||||||
|             sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq")); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example: |  | ||||||
|      * bridge -> suspended bridge, aqueduct, etc |  | ||||||
|      */ |  | ||||||
|     public Single<List<DepictedItem>> getParentDepictions(String qid, int startPosition, |  | ||||||
|         int limit) throws IOException { |  | ||||||
|         return depictedItemsFrom(sparqlQuery(qid, startPosition, limit, |  | ||||||
|             "/queries/parentclasses_query.rq")); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private Single<List<DepictedItem>> depictedItemsFrom(Request request) { |  | ||||||
|         return depictsClient.toDepictions(Single.fromCallable(() -> { |  | ||||||
|             try (ResponseBody body = okHttpClient.newCall(request).execute().body()) { |  | ||||||
|                 return gson.fromJson(body.string(), SparqlResponse.class); |  | ||||||
|             } |  | ||||||
|         }).doOnError(Timber::e)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     private Request sparqlQuery(String qid, int startPosition, int limit, String fileName) |  | ||||||
|         throws IOException { |  | ||||||
|         String query = FileUtils.readFromResource(fileName) |  | ||||||
|             .replace("${QID}", qid) |  | ||||||
|             .replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"") |  | ||||||
|             .replace("${LIMIT}", "" + limit) |  | ||||||
|             .replace("${OFFSET}", "" + startPosition); |  | ||||||
|         HttpUrl.Builder urlBuilder = HttpUrl |  | ||||||
|             .parse(sparqlQueryUrl) |  | ||||||
|             .newBuilder() |  | ||||||
|             .addQueryParameter("query", query) |  | ||||||
|             .addQueryParameter("format", "json"); |  | ||||||
|         return new Request.Builder() |  | ||||||
|             .url(urlBuilder.build()) |  | ||||||
|             .build(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public Single<CampaignResponseDTO> getCampaigns() { |  | ||||||
|         return Single.fromCallable(() -> { |  | ||||||
|             Request request = new Request.Builder().url(campaignsUrl) |  | ||||||
|                 .build(); |  | ||||||
|             Response response = okHttpClient.newCall(request).execute(); |  | ||||||
|             if (response != null && response.body() != null && response.isSuccessful()) { |  | ||||||
|                 String json = response.body().string(); |  | ||||||
|                 if (json == null) { |  | ||||||
|                     return null; |  | ||||||
|                 } |  | ||||||
|                 return gson.fromJson(json, CampaignResponseDTO.class); |  | ||||||
|             } |  | ||||||
|             return null; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,543 @@ | ||||||
|  | package fr.free.nrw.commons.mwapi | ||||||
|  | 
 | ||||||
|  | import android.text.TextUtils | ||||||
|  | import com.google.gson.Gson | ||||||
|  | import fr.free.nrw.commons.campaigns.CampaignResponseDTO | ||||||
|  | import fr.free.nrw.commons.explore.depictions.DepictsClient | ||||||
|  | import fr.free.nrw.commons.location.LatLng | ||||||
|  | import fr.free.nrw.commons.nearby.Place | ||||||
|  | import fr.free.nrw.commons.nearby.model.ItemsClass | ||||||
|  | import fr.free.nrw.commons.nearby.model.NearbyResponse | ||||||
|  | import fr.free.nrw.commons.nearby.model.PlaceBindings | ||||||
|  | import fr.free.nrw.commons.profile.achievements.FeaturedImages | ||||||
|  | import fr.free.nrw.commons.profile.achievements.FeedbackResponse | ||||||
|  | import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants | ||||||
|  | import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse | ||||||
|  | import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse | ||||||
|  | import fr.free.nrw.commons.upload.FileUtils | ||||||
|  | import fr.free.nrw.commons.upload.structure.depictions.DepictedItem | ||||||
|  | import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour | ||||||
|  | import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse | ||||||
|  | import io.reactivex.Observable | ||||||
|  | import io.reactivex.Single | ||||||
|  | import okhttp3.HttpUrl | ||||||
|  | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||||
|  | import okhttp3.OkHttpClient | ||||||
|  | import okhttp3.Request | ||||||
|  | import okhttp3.Response | ||||||
|  | import timber.log.Timber | ||||||
|  | import java.io.IOException | ||||||
|  | import java.util.Locale | ||||||
|  | import java.util.regex.Pattern | ||||||
|  | import javax.inject.Inject | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Test methods in ok http api client | ||||||
|  |  */ | ||||||
|  | @Singleton | ||||||
|  | class OkHttpJsonApiClient @Inject constructor( | ||||||
|  |     private val okHttpClient: OkHttpClient, | ||||||
|  |     private val depictsClient: DepictsClient, | ||||||
|  |     private val wikiMediaToolforgeUrl: HttpUrl, | ||||||
|  |     private val sparqlQueryUrl: String, | ||||||
|  |     private val campaignsUrl: String, | ||||||
|  |     private val gson: Gson | ||||||
|  | ) { | ||||||
|  |     fun getLeaderboard( | ||||||
|  |         userName: String, duration: String?, | ||||||
|  |         category: String?, limit: String?, offset: String? | ||||||
|  |     ): Observable<LeaderboardResponse> { | ||||||
|  |         val fetchLeaderboardUrlTemplate = | ||||||
|  |             wikiMediaToolforgeUrl.toString() + LeaderboardConstants.LEADERBOARD_END_POINT | ||||||
|  |         val url = String.format(Locale.ENGLISH, | ||||||
|  |             fetchLeaderboardUrlTemplate, userName, duration, category, limit, offset) | ||||||
|  |         val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() | ||||||
|  |             .addQueryParameter("user", userName) | ||||||
|  |             .addQueryParameter("duration", duration) | ||||||
|  |             .addQueryParameter("category", category) | ||||||
|  |             .addQueryParameter("limit", limit) | ||||||
|  |             .addQueryParameter("offset", offset) | ||||||
|  |         Timber.i("Url %s", urlBuilder.toString()) | ||||||
|  |         val request: Request = Request.Builder() | ||||||
|  |             .url(urlBuilder.toString()) | ||||||
|  |             .build() | ||||||
|  |         return Observable.fromCallable({ | ||||||
|  |             val response: Response = okHttpClient.newCall(request).execute() | ||||||
|  |             if (response.body != null && response.isSuccessful) { | ||||||
|  |                 val json: String = response.body!!.string() | ||||||
|  |                 Timber.d("Response for leaderboard is %s", json) | ||||||
|  |                 try { | ||||||
|  |                     return@fromCallable gson.fromJson<LeaderboardResponse>( | ||||||
|  |                         json, | ||||||
|  |                         LeaderboardResponse::class.java | ||||||
|  |                     ) | ||||||
|  |                 } catch (e: Exception) { | ||||||
|  |                     return@fromCallable LeaderboardResponse() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             LeaderboardResponse() | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun setAvatar(username: String, avatar: String?): Single<UpdateAvatarResponse?> { | ||||||
|  |         val urlTemplate = wikiMediaToolforgeUrl | ||||||
|  |             .toString() + LeaderboardConstants.UPDATE_AVATAR_END_POINT | ||||||
|  |         return Single.fromCallable<UpdateAvatarResponse?>({ | ||||||
|  |             val url = String.format(Locale.ENGLISH, urlTemplate, username, avatar) | ||||||
|  |             val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() | ||||||
|  |                 .addQueryParameter("user", username) | ||||||
|  |                 .addQueryParameter("avatar", avatar) | ||||||
|  |             Timber.i("Url %s", urlBuilder.toString()) | ||||||
|  |             val request: Request = Request.Builder() | ||||||
|  |                 .url(urlBuilder.toString()) | ||||||
|  |                 .build() | ||||||
|  |             val response: Response = okHttpClient.newCall(request).execute() | ||||||
|  |             if (response.body != null && response.isSuccessful) { | ||||||
|  |                 val json: String = response.body!!.string() ?: return@fromCallable null | ||||||
|  |                 try { | ||||||
|  |                     return@fromCallable gson.fromJson<UpdateAvatarResponse>( | ||||||
|  |                         json, | ||||||
|  |                         UpdateAvatarResponse::class.java | ||||||
|  |                     ) | ||||||
|  |                 } catch (e: Exception) { | ||||||
|  |                     return@fromCallable UpdateAvatarResponse() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             null | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getUploadCount(userName: String?): Single<Int> { | ||||||
|  |         val urlBuilder: HttpUrl.Builder = wikiMediaToolforgeUrl.newBuilder() | ||||||
|  |             .addPathSegments("uploadsbyuser.py") | ||||||
|  |             .addQueryParameter("user", userName) | ||||||
|  | 
 | ||||||
|  |         if (isBetaFlavour) { | ||||||
|  |             urlBuilder.addQueryParameter("labs", "commonswiki") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder() | ||||||
|  |             .url(urlBuilder.build()) | ||||||
|  |             .build() | ||||||
|  | 
 | ||||||
|  |         return Single.fromCallable<Int>({ | ||||||
|  |             val response: Response = okHttpClient.newCall(request).execute() | ||||||
|  |             if (response != null && response.isSuccessful) { | ||||||
|  |                 val responseBody = response.body | ||||||
|  |                 if (null != responseBody) { | ||||||
|  |                     val responseBodyString = responseBody.string().trim { it <= ' ' } | ||||||
|  |                     if (!TextUtils.isEmpty(responseBodyString)) { | ||||||
|  |                         try { | ||||||
|  |                             return@fromCallable responseBodyString.toInt() | ||||||
|  |                         } catch (e: NumberFormatException) { | ||||||
|  |                             Timber.e(e) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             0 | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getWikidataEdits(userName: String?): Single<Int> { | ||||||
|  |         val urlBuilder: HttpUrl.Builder = wikiMediaToolforgeUrl.newBuilder() | ||||||
|  |             .addPathSegments("wikidataedits.py") | ||||||
|  |             .addQueryParameter("user", userName) | ||||||
|  | 
 | ||||||
|  |         if (isBetaFlavour) { | ||||||
|  |             urlBuilder.addQueryParameter("labs", "commonswiki") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder() | ||||||
|  |             .url(urlBuilder.build()) | ||||||
|  |             .build() | ||||||
|  | 
 | ||||||
|  |         return Single.fromCallable<Int>({ | ||||||
|  |             val response: Response = okHttpClient.newCall(request).execute() | ||||||
|  |             if (response != null && response.isSuccessful && response.body != null) { | ||||||
|  |                 var json: String = response.body!!.string() | ||||||
|  |                 // Extract JSON from response | ||||||
|  |                 json = json.substring(json.indexOf('{')) | ||||||
|  |                 val countResponse = gson | ||||||
|  |                     .fromJson( | ||||||
|  |                         json, | ||||||
|  |                         GetWikidataEditCountResponse::class.java | ||||||
|  |                     ) | ||||||
|  |                 if (null != countResponse) { | ||||||
|  |                     return@fromCallable countResponse.wikidataEditCount | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             0 | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getAchievements(userName: String?): Single<FeedbackResponse?> { | ||||||
|  |         val suffix = if (isBetaFlavour) "/feedback.py?labs=commonswiki" else "/feedback.py" | ||||||
|  |         val fetchAchievementUrlTemplate = wikiMediaToolforgeUrl.toString() + suffix | ||||||
|  |         return Single.fromCallable<FeedbackResponse?>({ | ||||||
|  |             val url = String.format( | ||||||
|  |                 Locale.ENGLISH, | ||||||
|  |                 fetchAchievementUrlTemplate, | ||||||
|  |                 userName | ||||||
|  |             ) | ||||||
|  |             val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() | ||||||
|  |                 .addQueryParameter("user", userName) | ||||||
|  |             val request: Request = Request.Builder() | ||||||
|  |                 .url(urlBuilder.toString()) | ||||||
|  |                 .build() | ||||||
|  |             val response: Response = okHttpClient.newCall(request).execute() | ||||||
|  |             if (response.body != null && response.isSuccessful) { | ||||||
|  |                 var json: String = response.body!!.string() | ||||||
|  |                 // Extract JSON from response | ||||||
|  |                 json = json.substring(json.indexOf('{')) | ||||||
|  |                 Timber.d("Response for achievements is %s", json) | ||||||
|  |                 try { | ||||||
|  |                     return@fromCallable gson.fromJson<FeedbackResponse>( | ||||||
|  |                         json, | ||||||
|  |                         FeedbackResponse::class.java | ||||||
|  |                     ) | ||||||
|  |                 } catch (e: Exception) { | ||||||
|  |                     return@fromCallable FeedbackResponse(0, 0, 0, FeaturedImages(0, 0), 0, "") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             null | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @JvmOverloads | ||||||
|  |     @Throws(Exception::class) | ||||||
|  |     fun getNearbyPlaces( | ||||||
|  |         cur: LatLng, language: String, radius: Double, | ||||||
|  |         customQuery: String? = null | ||||||
|  |     ): List<Place>? { | ||||||
|  |         Timber.d("Fetching nearby items at radius %s", radius) | ||||||
|  |         Timber.d("CUSTOM_SPARQL: %s", (customQuery != null).toString()) | ||||||
|  |         val wikidataQuery: String = if (customQuery != null) { | ||||||
|  |             customQuery | ||||||
|  |         } else { | ||||||
|  |             FileUtils.readFromResource("/queries/radius_query_for_upload_wizard.rq") | ||||||
|  |         } | ||||||
|  |         val query = wikidataQuery | ||||||
|  |             .replace("\${RAD}", String.format(Locale.ROOT, "%.2f", radius)) | ||||||
|  |             .replace("\${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude)) | ||||||
|  |             .replace("\${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude)) | ||||||
|  |             .replace("\${LANG}", language) | ||||||
|  | 
 | ||||||
|  |         val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! | ||||||
|  |             .newBuilder() | ||||||
|  |             .addQueryParameter("query", query) | ||||||
|  |             .addQueryParameter("format", "json") | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder() | ||||||
|  |             .url(urlBuilder.build()) | ||||||
|  |             .build() | ||||||
|  | 
 | ||||||
|  |         val response = okHttpClient.newCall(request).execute() | ||||||
|  |         if (response.body != null && response.isSuccessful) { | ||||||
|  |             val json = response.body!!.string() | ||||||
|  |             val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java) | ||||||
|  |             val bindings = nearbyResponse.results.bindings | ||||||
|  |             val places: MutableList<Place> = ArrayList() | ||||||
|  |             for (item in bindings) { | ||||||
|  |                 val placeFromNearbyItem = Place.from(item) | ||||||
|  |                 placeFromNearbyItem.isMonument = false | ||||||
|  |                 places.add(placeFromNearbyItem) | ||||||
|  |             } | ||||||
|  |             return places | ||||||
|  |         } | ||||||
|  |         throw Exception(response.message) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(Exception::class) | ||||||
|  |     fun getNearbyPlaces( | ||||||
|  |         screenTopRight: LatLng, | ||||||
|  |         screenBottomLeft: LatLng, language: String, | ||||||
|  |         shouldQueryForMonuments: Boolean, customQuery: String? | ||||||
|  |     ): List<Place>? { | ||||||
|  |         Timber.d("CUSTOM_SPARQL: %s", (customQuery != null).toString()) | ||||||
|  | 
 | ||||||
|  |         val wikidataQuery: String = if (customQuery != null) { | ||||||
|  |             customQuery | ||||||
|  |         } else if (!shouldQueryForMonuments) { | ||||||
|  |             FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq") | ||||||
|  |         } else { | ||||||
|  |             FileUtils.readFromResource("/queries/rectangle_query_for_nearby_monuments.rq") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val westCornerLat = screenTopRight.latitude | ||||||
|  |         val westCornerLong = screenTopRight.longitude | ||||||
|  |         val eastCornerLat = screenBottomLeft.latitude | ||||||
|  |         val eastCornerLong = screenBottomLeft.longitude | ||||||
|  | 
 | ||||||
|  |         val query = wikidataQuery | ||||||
|  |             .replace("\${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat)) | ||||||
|  |             .replace("\${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong)) | ||||||
|  |             .replace("\${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat)) | ||||||
|  |             .replace("\${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong)) | ||||||
|  |             .replace("\${LANG}", language) | ||||||
|  |         val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! | ||||||
|  |             .newBuilder() | ||||||
|  |             .addQueryParameter("query", query) | ||||||
|  |             .addQueryParameter("format", "json") | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder() | ||||||
|  |             .url(urlBuilder.build()) | ||||||
|  |             .build() | ||||||
|  | 
 | ||||||
|  |         val response = okHttpClient.newCall(request).execute() | ||||||
|  |         if (response.body != null && response.isSuccessful) { | ||||||
|  |             val json = response.body!!.string() | ||||||
|  |             val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java) | ||||||
|  |             val bindings = nearbyResponse.results.bindings | ||||||
|  |             val places: MutableList<Place> = ArrayList() | ||||||
|  |             for (item in bindings) { | ||||||
|  |                 val placeFromNearbyItem = Place.from(item) | ||||||
|  |                 if (shouldQueryForMonuments && item.getMonument() != null) { | ||||||
|  |                     placeFromNearbyItem.isMonument = true | ||||||
|  |                 } else { | ||||||
|  |                     placeFromNearbyItem.isMonument = false | ||||||
|  |                 } | ||||||
|  |                 places.add(placeFromNearbyItem) | ||||||
|  |             } | ||||||
|  |             return places | ||||||
|  |         } | ||||||
|  |         throw Exception(response.message) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     fun getPlaces( | ||||||
|  |         placeList: List<Place>, language: String | ||||||
|  |     ): List<Place>? { | ||||||
|  |         val wikidataQuery = FileUtils.readFromResource("/queries/query_for_item.rq") | ||||||
|  |         var qids = "" | ||||||
|  |         for (place in placeList) { | ||||||
|  |             qids += """ | ||||||
|  | ${"wd:" + place.wikiDataEntityId}""" | ||||||
|  |         } | ||||||
|  |         val query = wikidataQuery | ||||||
|  |             .replace("\${ENTITY}", qids) | ||||||
|  |             .replace("\${LANG}", language) | ||||||
|  |         val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! | ||||||
|  |             .newBuilder() | ||||||
|  |             .addQueryParameter("query", query) | ||||||
|  |             .addQueryParameter("format", "json") | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder().url(urlBuilder.build()).build() | ||||||
|  | 
 | ||||||
|  |         okHttpClient.newCall(request).execute().use { response -> | ||||||
|  |             if (response.isSuccessful) { | ||||||
|  |                 val json = response.body!!.string() | ||||||
|  |                 val nearbyResponse = gson.fromJson(json, NearbyResponse::class.java) | ||||||
|  |                 val bindings = nearbyResponse.results.bindings | ||||||
|  |                 val places: MutableList<Place> = ArrayList() | ||||||
|  |                 for (item in bindings) { | ||||||
|  |                     val placeFromNearbyItem = Place.from(item) | ||||||
|  |                     places.add(placeFromNearbyItem) | ||||||
|  |                 } | ||||||
|  |                 return places | ||||||
|  |             } else { | ||||||
|  |                 throw IOException("Unexpected response code: " + response.code) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(Exception::class) | ||||||
|  |     fun getPlacesAsKML(leftLatLng: LatLng, rightLatLng: LatLng): String? { | ||||||
|  |         var kmlString = """<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <!--Created by Wikimedia Commons Android app --> | ||||||
|  | <kml xmlns="http://www.opengis.net/kml/2.2"> | ||||||
|  |     <Document>""" | ||||||
|  |         val placeBindings = runQuery( | ||||||
|  |             leftLatLng, | ||||||
|  |             rightLatLng | ||||||
|  |         ) | ||||||
|  |         if (placeBindings != null) { | ||||||
|  |             for ((item1, label, location, clas) in placeBindings) { | ||||||
|  |                 if (item1 != null && label != null && clas != null) { | ||||||
|  |                     val input = location.value | ||||||
|  |                     val pattern = Pattern.compile( | ||||||
|  |                         "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)" | ||||||
|  |                     ) | ||||||
|  |                     val matcher = pattern.matcher(input) | ||||||
|  | 
 | ||||||
|  |                     if (matcher.find()) { | ||||||
|  |                         val longStr = matcher.group(1) | ||||||
|  |                         val latStr = matcher.group(2) | ||||||
|  |                         val itemUrl = item1.value | ||||||
|  |                         val itemName = label.value.replace("&", "&") | ||||||
|  |                         val itemLatitude = latStr | ||||||
|  |                         val itemLongitude = longStr | ||||||
|  |                         val itemClass = clas.value | ||||||
|  | 
 | ||||||
|  |                         val formattedItemName = | ||||||
|  |                             if (!itemClass.isEmpty()) | ||||||
|  |                                 "$itemName ($itemClass)" | ||||||
|  |                             else | ||||||
|  |                                 itemName | ||||||
|  | 
 | ||||||
|  |                         val kmlEntry = (""" | ||||||
|  |         <Placemark> | ||||||
|  |             <name>$formattedItemName</name> | ||||||
|  |             <description>$itemUrl</description> | ||||||
|  |             <Point> | ||||||
|  |                 <coordinates>$itemLongitude,$itemLatitude</coordinates> | ||||||
|  |             </Point> | ||||||
|  |         </Placemark>""") | ||||||
|  |                         kmlString = kmlString + kmlEntry | ||||||
|  |                     } else { | ||||||
|  |                         Timber.e("No match found") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         kmlString = """$kmlString | ||||||
|  |     </Document> | ||||||
|  | </kml> | ||||||
|  | """ | ||||||
|  |         return kmlString | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(Exception::class) | ||||||
|  |     fun getPlacesAsGPX(leftLatLng: LatLng, rightLatLng: LatLng): String? { | ||||||
|  |         var gpxString = ("""<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <gpx | ||||||
|  |  version="1.0" | ||||||
|  |  creator="Wikimedia Commons Android app" | ||||||
|  |  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||
|  |  xmlns="http://www.topografix.com/GPX/1/0" | ||||||
|  |  xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"> | ||||||
|  | <bounds minlat="${"$"}MIN_LATITUDE" minlon="${"$"}MIN_LONGITUDE" maxlat="${"$"}MAX_LATITUDE" maxlon="${"$"}MAX_LONGITUDE"/>""") | ||||||
|  | 
 | ||||||
|  |         val placeBindings = runQuery(leftLatLng, rightLatLng) | ||||||
|  |         if (placeBindings != null) { | ||||||
|  |             for ((item1, label, location, clas) in placeBindings) { | ||||||
|  |                 if (item1 != null && label != null && clas != null) { | ||||||
|  |                     val input = location.value | ||||||
|  |                     val pattern = Pattern.compile( | ||||||
|  |                         "Point\\(([-+]?[0-9]*\\.?[0-9]+) ([-+]?[0-9]*\\.?[0-9]+)\\)" | ||||||
|  |                     ) | ||||||
|  |                     val matcher = pattern.matcher(input) | ||||||
|  | 
 | ||||||
|  |                     if (matcher.find()) { | ||||||
|  |                         val longStr = matcher.group(1) | ||||||
|  |                         val latStr = matcher.group(2) | ||||||
|  |                         val itemUrl = item1.value | ||||||
|  |                         val itemName = label.value.replace("&", "&") | ||||||
|  |                         val itemLatitude = latStr | ||||||
|  |                         val itemLongitude = longStr | ||||||
|  |                         val itemClass = clas.value | ||||||
|  | 
 | ||||||
|  |                         val formattedItemName = if (!itemClass.isEmpty()) | ||||||
|  |                             "$itemName ($itemClass)" | ||||||
|  |                         else | ||||||
|  |                             itemName | ||||||
|  | 
 | ||||||
|  |                         val gpxEntry = | ||||||
|  |                             (""" | ||||||
|  |     <wpt lat="$itemLatitude" lon="$itemLongitude"> | ||||||
|  |         <name>$itemName</name> | ||||||
|  |         <url>$itemUrl</url> | ||||||
|  |     </wpt>""") | ||||||
|  |                         gpxString = gpxString + gpxEntry | ||||||
|  |                     } else { | ||||||
|  |                         Timber.e("No match found") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         gpxString = "$gpxString\n</gpx>" | ||||||
|  |         return gpxString | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     fun getChildDepictions( | ||||||
|  |         qid: String, startPosition: Int, | ||||||
|  |         limit: Int | ||||||
|  |     ): Single<List<DepictedItem>> = | ||||||
|  |         depictedItemsFrom(sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq")) | ||||||
|  | 
 | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     fun getParentDepictions( | ||||||
|  |         qid: String, startPosition: Int, | ||||||
|  |         limit: Int | ||||||
|  |     ): Single<List<DepictedItem>> = depictedItemsFrom( | ||||||
|  |         sparqlQuery( | ||||||
|  |             qid, | ||||||
|  |             startPosition, | ||||||
|  |             limit, | ||||||
|  |             "/queries/parentclasses_query.rq" | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     fun getCampaigns(): Single<CampaignResponseDTO> { | ||||||
|  |         return Single.fromCallable<CampaignResponseDTO?>({ | ||||||
|  |             val request: Request = Request.Builder().url(campaignsUrl).build() | ||||||
|  |             val response: Response = okHttpClient.newCall(request).execute() | ||||||
|  |             if (response.body != null && response.isSuccessful) { | ||||||
|  |                 val json: String = response.body!!.string() | ||||||
|  |                 return@fromCallable gson.fromJson<CampaignResponseDTO>( | ||||||
|  |                     json, | ||||||
|  |                     CampaignResponseDTO::class.java | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             null | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun depictedItemsFrom(request: Request): Single<List<DepictedItem>> { | ||||||
|  |         return depictsClient.toDepictions(Single.fromCallable({ | ||||||
|  |             okHttpClient.newCall(request).execute().body.use { body -> | ||||||
|  |                 return@fromCallable gson.fromJson<SparqlResponse>( | ||||||
|  |                     body!!.string(), | ||||||
|  |                     SparqlResponse::class.java | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         }).doOnError({ t: Throwable? -> Timber.e(t) })) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     private fun sparqlQuery( | ||||||
|  |         qid: String, | ||||||
|  |         startPosition: Int, | ||||||
|  |         limit: Int, | ||||||
|  |         fileName: String | ||||||
|  |     ): Request { | ||||||
|  |         val query = FileUtils.readFromResource(fileName) | ||||||
|  |             .replace("\${QID}", qid) | ||||||
|  |             .replace("\${LANG}", "\"" + Locale.getDefault().language + "\"") | ||||||
|  |             .replace("\${LIMIT}", "" + limit) | ||||||
|  |             .replace("\${OFFSET}", "" + startPosition) | ||||||
|  |         val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! | ||||||
|  |             .newBuilder() | ||||||
|  |             .addQueryParameter("query", query) | ||||||
|  |             .addQueryParameter("format", "json") | ||||||
|  |         return Request.Builder().url(urlBuilder.build()).build() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     private fun runQuery(currentLatLng: LatLng, nextLatLng: LatLng): List<PlaceBindings>? { | ||||||
|  |         val wikidataQuery = FileUtils.readFromResource("/queries/places_query.rq") | ||||||
|  |         val query = wikidataQuery | ||||||
|  |             .replace("\${LONGITUDE}", String.format(Locale.ROOT, "%.2f", currentLatLng.longitude)) | ||||||
|  |             .replace("\${LATITUDE}", String.format(Locale.ROOT, "%.4f", currentLatLng.latitude)) | ||||||
|  |             .replace("\${NEXT_LONGITUDE}", String.format(Locale.ROOT, "%.4f", nextLatLng.longitude)) | ||||||
|  |             .replace("\${NEXT_LATITUDE}", String.format(Locale.ROOT, "%.4f", nextLatLng.latitude)) | ||||||
|  | 
 | ||||||
|  |         val urlBuilder: HttpUrl.Builder = sparqlQueryUrl.toHttpUrlOrNull()!! | ||||||
|  |             .newBuilder() | ||||||
|  |             .addQueryParameter("query", query) | ||||||
|  |             .addQueryParameter("format", "json") | ||||||
|  | 
 | ||||||
|  |         val request: Request = Request.Builder().url(urlBuilder.build()).build() | ||||||
|  | 
 | ||||||
|  |         val response = okHttpClient.newCall(request).execute() | ||||||
|  |         if (response.body != null && response.isSuccessful) { | ||||||
|  |             val json = response.body!!.string() | ||||||
|  |             val item = gson.fromJson(json, ItemsClass::class.java) | ||||||
|  |             return item.results.bindings | ||||||
|  |         } else { | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -194,7 +194,7 @@ class FileProcessor | ||||||
|             requireNotNull(imageCoordinates.decimalCoords) |             requireNotNull(imageCoordinates.decimalCoords) | ||||||
|             compositeDisposable.add( |             compositeDisposable.add( | ||||||
|                 apiCall |                 apiCall | ||||||
|                     .request(imageCoordinates.decimalCoords) |                     .request(imageCoordinates.decimalCoords!!) | ||||||
|                     .subscribeOn(Schedulers.io()) |                     .subscribeOn(Schedulers.io()) | ||||||
|                     .observeOn(Schedulers.io()) |                     .observeOn(Schedulers.io()) | ||||||
|                     .subscribe( |                     .subscribe( | ||||||
|  | @ -220,7 +220,7 @@ class FileProcessor | ||||||
|                 .concatMap { |                 .concatMap { | ||||||
|                     Observable.fromCallable { |                     Observable.fromCallable { | ||||||
|                         okHttpJsonApiClient.getNearbyPlaces( |                         okHttpJsonApiClient.getNearbyPlaces( | ||||||
|                             imageCoordinates.latLng, |                             imageCoordinates.latLng!!, | ||||||
|                             Locale.getDefault().language, |                             Locale.getDefault().language, | ||||||
|                             it, |                             it, | ||||||
|                         ) |                         ) | ||||||
|  |  | ||||||
|  | @ -49,13 +49,13 @@ class CampaignsPresenterTest { | ||||||
|         campaignsSingle = Single.just(campaignResponseDTO) |         campaignsSingle = Single.just(campaignResponseDTO) | ||||||
|         campaignsPresenter = CampaignsPresenter(okHttpJsonApiClient, testScheduler, testScheduler) |         campaignsPresenter = CampaignsPresenter(okHttpJsonApiClient, testScheduler, testScheduler) | ||||||
|         campaignsPresenter.onAttachView(view) |         campaignsPresenter.onAttachView(view) | ||||||
|         Mockito.`when`(okHttpJsonApiClient.campaigns).thenReturn(campaignsSingle) |         Mockito.`when`(okHttpJsonApiClient.getCampaigns()).thenReturn(campaignsSingle) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun getCampaignsTestNoCampaigns() { |     fun getCampaignsTestNoCampaigns() { | ||||||
|         campaignsPresenter.getCampaigns() |         campaignsPresenter.getCampaigns() | ||||||
|         verify(okHttpJsonApiClient).campaigns |         verify(okHttpJsonApiClient).getCampaigns() | ||||||
|         testScheduler.triggerActions() |         testScheduler.triggerActions() | ||||||
|         verify(view).showCampaigns(null) |         verify(view).showCampaigns(null) | ||||||
|     } |     } | ||||||
|  | @ -77,7 +77,7 @@ class CampaignsPresenterTest { | ||||||
|         Mockito.`when`(campaign.endDate).thenReturn(endDateString) |         Mockito.`when`(campaign.endDate).thenReturn(endDateString) | ||||||
|         Mockito.`when`(campaign.startDate).thenReturn(startDateString) |         Mockito.`when`(campaign.startDate).thenReturn(startDateString) | ||||||
|         Mockito.`when`(campaignResponseDTO.campaigns).thenReturn(campaigns) |         Mockito.`when`(campaignResponseDTO.campaigns).thenReturn(campaigns) | ||||||
|         verify(okHttpJsonApiClient).campaigns |         verify(okHttpJsonApiClient).getCampaigns() | ||||||
|         testScheduler.triggerActions() |         testScheduler.triggerActions() | ||||||
|         verify(view).showCampaigns(campaign) |         verify(view).showCampaigns(campaign) | ||||||
|     } |     } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Paul Hawke
						Paul Hawke