Is it possible to combine mapAreas from ArcGISOnline in one offline map on android? - arcgis

I try to create an offline map using ArcGIS Runtime SDK for Android 100.5.0. I follow preplanned workflow according the guide https://developers.arcgis.com/android/latest/guide/take-map-offline-preplanned.htm. I create mapAreas in ArcGISOnline and try to download them from device. I want to get an offline map, which contains all mapAreas together like in app maps.me(on a big map you have downloaded regions with deeper detailing), but instead I am getting an offline map made from last downloaded area. So I created mapArea "Europe" with scale world - cities and mapArea "Berlin" with scale cities - buildings (both basemaps - openstreetmaps, no feature layers) and downloaded them successfully, see 2 tpk files in a folder, but mobile_map.mmpk and package.info files only contain data related to last loaded area. Is it possible at all to get what I want, combine tpk files in one map?
My code in Kotlin:
val portal = Portal("https://www.arcgis.com/", false)
val portalItem = PortalItem(portal, itemID)
val offlineMapTask = OfflineMapTask(portalItem)
//get all of the preplanned map areas in the web map
val mapAreasFuture = offlineMapTask.preplannedMapAreasAsync
mapAreasFuture.addDoneListener {
try {
// get the list of areas
val mapAreas = mapAreasFuture.get()
val directory = getDirectory()
prepareDir(directory)
// loop through the map areas
var i = 0
for (mapArea in mapAreas) {
mapArea.loadAsync()
mapArea.addDoneLoadingListener {
val downloadJob = offlineMapTask.downloadPreplannedOfflineMap(mapArea, directory)
downloadJob.start()
downloadJob.addJobDoneListener {
i++
if (i == mapAreas.size) {
val offlineMapPackage = MobileMapPackage(path)
offlineMapPackage.addDoneLoadingListener({
if (offlineMapPackage.getLoadStatus() === LoadStatus.LOADED) {
val mobileMap = offlineMapPackage.getMaps().get(0)
myCompletionFuncToShowMap(mobileMap)
} else {
println("PACKAGING FAILED")
}
})
offlineMapPackage.loadAsync()
}
}
}
}
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}

I duplicated the question on community.esri.com and got an answer, thanks to Luke Smallwood from esri. So, yes, it is possible. The "Europe" map area has to be added as basemap layer if I set mapView.map to Berlin and also every map area has to be saved in its own directory.
Downloading areas:
val portal = Portal("https://www.arcgis.com/", false)
val portalItem = PortalItem(portal, itemID)
val offlineMapTask = OfflineMapTask(portalItem)
//get all of the preplanned map areas in the web map
val mapAreasFuture = offlineMapTask.preplannedMapAreasAsync
mapAreasFuture.addDoneListener {
try {
// get the list of areas
val mapAreas = mapAreasFuture.get()
val directory = getDirectory() //my function returns String path
// loop through the map areas
for (mapArea in mapAreas) {
mapArea.loadAsync()
mapArea.addDoneLoadingListener {
val downloadJob = offlineMapTask.downloadPreplannedOfflineMap(mapArea, directory + "/" + mapArea.portalItem.title)
downloadJob.start()
downloadJob.addJobDoneListener {
val offlineMapPackage = MobileMapPackage(directory + "/" + mapArea.portalItem.title)
offlineMapPackage.addDoneLoadingListener({
if (offlineMapPackage.getLoadStatus() != LoadStatus.LOADED) {
println("PACKAGING FAILED")
}
})
offlineMapPackage.loadAsync()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Files after downloading:
.../files/maps/Berlin/p13/129294b8-3a70-4d79-a421-24ff14cb19fc.tpk
.../files/maps/Berlin/p13/mobile_map.mmap
.../files/maps/Berlin/package.info
.../files/maps/Europa/p13/2547a985-c98f-49be-a187-5ae3b7a9da09.tpk
.../files/maps/Europa/p13/mobile_map.mmap
.../files/maps/Europa/package.info
Displaying an offline map:
val path = getDirectory() + "/Berlin"
val offlineMapPackage = MobileMapPackage(path)
offlineMapPackage.addDoneLoadingListener {
if (offlineMapPackage.getLoadStatus() == LoadStatus.LOADED && !offlineMapPackage.getMaps().isEmpty()) {
mapView.map = offlineMapPackage.getMaps().get(0)
val cache = TileCache(getDirectory() + "/Europa/p13/2547a985-c98f-49be-a187-5ae3b7a9da09.tpk")
val layer = ArcGISTiledLayer(cache)
mapView.map.basemap.baseLayers.add(layer)
mapView.map.minScale = 1.8489297737236E7
mapView.map.maxScale = 2256.994353
} else {
println("NO MAP FILES")
}
}
offlineMapPackage.loadAsync()
It is important to adjust mapView.map.minScale to include Europe levels, otherwise on device the map only allows to scale between Berlin levels. The scale levels are listed here https://www.esri.com/arcgis-blog/products/product/mapping/web-map-zoom-levels-updated/ .

Related

Task.whenAllSuccess function always gives me the same task

I have a small problem with the processing of my task. I have a list (listImg) which contains bitmaps. Now I try to upload each image to Firebase Storage through a forEach loop and then pack it in an UploadTask. In the addOnSuccessListener, however, the same image is called every time. For example, I have 3 images in the listImg. All 3 show up correctly in the loop. I pack these into the task array. However, in the Task.whenAllSuccess function, each URL of the 3 tasks leads to the same image
What is it?
downloadUrls = mutableListOf<String>()
val tasks = mutableListOf<UploadTask>()
listImg.forEach {
if(bitmap!!.byteCount != it.byteCount) {
var spaceRef = storageRef.child("objektImages/"+UUID.randomUUID().toString()+".jpg")
val bitmap = it
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos)
val data = baos.toByteArray()
var uploadTask = spaceRef.putBytes(data)
//Here log gives me different images, which is also intended
Log.d("URL:",bitmap.toString())
tasks.add(uploadTask)
}
}
Tasks.whenAllSuccess<UploadTask.TaskSnapshot>(tasks).addOnSuccessListener { taskSnapshot ->
taskSnapshot!!.forEach {
it.metadata!!.reference!!.downloadUrl.addOnSuccessListener { url->
//But here every downloadUrl leads to the same image.
downloadUrls.add(url.toString())
...
}
}
}

How to have Kotlin "Listen" when a function finish executing Successfully

This is my first time using Kotlin, I have to write a simple command-line application where it takes a list of user input strings. Valid inputs are only "Apple" or "Orange" and calculate the price (which is 60 cents and 25 cents respectively). I'm having some trouble with the 3rd requirement
"Build a service that listens for when orders are complete and sends a notification to the customer regarding its status and estimated delivery time. The Mail service subscribes to events from the Orders service and publishes the appropriate event that the customer (you) is able to read from the terminal"
this is what I have done so far
MainApp.tk
import java.util.Scanner
import kotlin.system.exitProcess;
import app.Checkout;
var shopRunning = true;
var applecount = 0;
fun main(args: Array<String>) {
while (shopRunning) {
println("Welcome to Express Store");
println("1. Checkout");
println("2. exit");
var userOption = 0;
//request the user to eneter an option
//if user eneter a options that is not valid it will keep looping til option that is enterd is accepted;
var userSeletedOption = false;
val inputScanner = Scanner(System.`in`);
while (!userSeletedOption) {
print("Select an Option: ");
userOption = inputScanner.nextInt();
//if input entered by the user is not accepted and invaliud message is printed and is promted to enter an option again.
if (userOption != 1 && userOption != 2) {
println("Invalid input detected!");
} else {
userSeletedOption = true;
}
}
if (userOption == 1) {
val checkout = Checkout();
println("We currently have apples and oranges in Stock.")
var list: MutableList<String> = ArrayList();
println(list.size);
var doneAddingToCart = false;
while(!doneAddingToCart){
print("enter name of item to be enter or exit to finish adding to the cart: ")
var item = inputScanner.next();
if(item.equals("exit")){
doneAddingToCart=true;
}
else{
list.add(item);
}
}
if(checkout.verify(list)){ //checks if list has any item that is not an apple or orange
println("Thank you for your Pruchse");
val cost = checkout.Chasher(list)
println("You bought: "+ list.toString());
print("your total is: "+ cost);//returns the total cost
exitProcess(1);//exits from the application
}
} else if (userOption == 2) {
print("Have a great day.");
exitProcess(1);
}
}
}
CheckOut.tk
class Checkout {
//checks if the user entered any invaild items
public fun verify (cart: MutableList<String>) : Boolean{
for(item in cart){
if(!item.equals("Apple") && !item.equals("Orange")){
return false;
}
}
return true;
}
public fun Chasher (cart: MutableList<String>) : Double{
var total = 0.0;
var orangecount = 0;//step 2 offers
var applecount = 0;//step 2 offers
for(item in cart){//step 1 function
if(item.equals("Apple") || item.equals("apple")){
applecount+=1;
total= total + 0.6;
}
if(item.equals("Orange") || item.equals("orange")){
orangecount +=1;
total=total +0.25;
}
}
if(orangecount ==3){//buy three for the price of 2.step 2
println("You qaulidified for our buy 3 oragnes for the price of 2 offer")
total -=0.25;
}
if(applecount ==1){//buy one aple get 1 free. step 2
println("You buy 1 apple get one free")
cart.add("Apple");
}
return total;
}
}
I don't need to send an email just send a message to the command line. Currently, I'm just printing messages (just to see if what I currently have even works). Yeah, I know there many spelling errors, english and writing was never my strongest subject
I can only provide three hints that might help you:
If you exit your program using System.exit, use 0 if the run did not have any problem. (Excerpt from JavaDoc: "The argument serves as a status code; by convention, a nonzero status code indicates abnormal termination.")
For checking equality, simply use == which corresponds to equals in Java. In your special case however, you can use item.equals("apple", ignoreCase=true) or simply item.equals("apple", true).
I'm not sure what the author of your task exactly expects as a solution.
In can imagine you are expected to use lambdas.
An example: Your could refactor your Checkout class like that:
class Checkout {
/**
* Checks if the given [cart] contains only apples and oranges,
* and calls [onSuccess].
* If also other articles are contained, [onSuccess] is not called.
*/
fun verify(cart: List<String>, onSuccess: (List<String>) -> Unit): Unit {
for (item in cart) {
if (!item.equals("apple", true) && !item.equals("Orange", true)) {
return
}
}
onSuccess(cart)
}
}
And then call
val cart = listOf("Orange", "Apple", "apple", "orange")
Checkout().verify(cart, { cart: List<String> ->
println("Thanks you for your purchase: $cart")
})
or even shorter (curly brackets are outside of parenthesis)
Checkout().verify(cart) { cart: List<String> ->
println("Thanks you for your purchase: $cart")
}
What I did here was to extract what is executed if your validation succeeds:
For that, I used a lambda function that accepts a list of articles/strings (List<String>) and returns something I ignore/don't care about -> Unit.
The advantage of that approach is that callers of your verify method can decide what to do on success at their liking because they can pass a lambda function around like any other variable. Here:
val cart = listOf("Orange", "Apple", "apple", "orange")
val onSuccess = { cart: List<String> ->
println("Thanks you for your purchase: $cart")
}
Checkout().verify(cart, onSuccess)
You could also extend Checkout to allow an observer to register.
I deliberately kept the code very simple. Normally you would allow multiple observers to register, only expose what clients are supposed to see and hide the rest, etc.
class Checkout(
val onSuccess : (List<String>) -> Unit
) {
fun verify(cart: List<String>): Unit {
for (item in cart) {
if (!item.equals("apple", true) && !item.equals("Orange", true)) {
return
}
}
onSuccess(cart)
}
}
val checkout = Checkout({ cart: List<String> ->
println("Thanks you for your purchase: $cart")
})
and then
val cart = listOf("Orange", "Apple", "apple", "orange")
checkout.verify(cart)
Be sure to check out https://play.kotlinlang.org/byExample/04_functional/01_Higher-Order%20Functions to learn more about lambda / higher-order functions.

Kotlin Wi-Fi loss/bad signal during FTP transfer

I'm nearly done creating an app that sends txt files, containing scanned data, to an ftp server.
The issue that I'm currently struggling with is: what if my Wi-Fi has terrible signal or no signal at all.
I noticed that my 'isOnline()' check works fine and that if there is no internet, it alerts the user. However a few hours ago I tested the app in the basement and noticed that when the Wi-Fi signal has no bars, it still sends the data but it gets lost somewhere along the way.
Currently the flow of the data is as follow:
user presses 'send'
check internet and if true, continue
clear content list and send
the content to viewmodel
viewmodel checks internet again before
creating the txt files
txt files get sent via FTP code below.
private fun sendTXT(result: String) {
try {
val name = "00_VER${LocalDateTime.now().format(fileNameFormatter)}.txt"
val path = getApplication<Application>().applicationContext.filesDir.path
.toString() + name
val f = File(path)
val isNewFileCreated: Boolean = f.createNewFile()
if (isNewFileCreated) {
f.writeText(result, Charsets.UTF_8)
}
val ftpClient = FTPClient()
ftpClient.addProtocolCommandListener(PrintCommandListener(PrintWriter(System.out)))
ftpClient.connect("xxx.xx.xxx.xx", 21)
val reply: Int = ftpClient.replyCode
if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect()
throw IOException("Exception in connecting to FTP Server")
}
if (ftpClient.login("username", "pass")) {
ftpClient.enterLocalPassiveMode()
ftpClient.setFileType(FTP.BINARY_FILE_TYPE)
val inp = FileInputStream(f)
var directory = "/files/input"
ftpClient.changeWorkingDirectory(directory)
val result = ftpClient.storeFile(name, inp)
inp.close()
if (result) {
ftpClient.logout()
ftpClient.disconnect()
f.delete()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun isOnline(context: Context): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val actNw =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
I'm stuck at finding a way to make sure the data gets to the server. Is there a more advanced way to check for internet connectivity?
I was considering adding all the scan objects as JSON to sharedpreferences, and if at the end of the day the user notices a scan didn't make it through, they can look up the missing scan and resend it.
However this seems very unconventional and I'm pretty sure there must be a better way to handle things.

Camerax Analyzer ImageProxy - Image Already Closed

I am using CameraX (Beta) and using Analyzer with an executor to analyze the image and draw bounds/overlay on it. Based on the documentation, I need to close the image proxy to continue to get the images into the analyze function. When I add the imageProxy.close() call, it fails with Image Already closed error. What am I missing here?
Analyzer Code:
private val successListener = OnSuccessListener<List<DetectedObject>> { papers ->
isAnalyzing.set(false)
val rectPoints= mutableListOf<Rect>()
Log.d(TAG," overlayRef Info: ${overlayRef.get()}")
for (paper in papers) {
val bounds = paper.boundingBox
val paperId = paper.trackingId
rectPoints+=bounds
Log.d(TAG, "Successful Paper Analysis - Bounds of the paper: $bounds")
Log.d(TAG," Labels found on the paper: ${paper.labels}")
}
Log.d(TAG, "Invoking pointsRectListener for : $rectPoints")
pointsRectListener?.invoke(rectPoints)
}
private val failureListener = OnFailureListener { e ->
isAnalyzing.set(false)
Log.e(TAG, "Paper analysis failure.", e)
}
#SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy?.image ?: return
Log.d(TAG,"entered analysis..analysis in progress?$isAnalyzing.get()")
if (!isAnalyzing.get()){
isAnalyzing.set(true)
Log.d(TAG,"No other analysis in progress..so starting analysis now")
val currentTimestamp = System.currentTimeMillis()
if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {
currentTimestamp - lastAnalyzedTimestamp
analysisSizeListener?.invoke(Size(imageProxy.width, imageProxy.height))
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
objectDetector.process(image)
.addOnSuccessListener(successListener)
.addOnFailureListener(failureListener)
}
}
imageProxy.close()
}
Code where I am instantiating and binding to lifecycle
paperAnalyzer=ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetRotation(rotation)
.build()
.also {
it.setAnalyzer(cameraExecutor, PaperAnalyzer( WeakReference(overlayView)).apply {
pointsRectListener = { rectPoints ->
overlayView.rectPoints = rectPoints
}
analysisSizeListener = {
updateOverlayTransform(overlayView, it)
}
}
)
}
cameraProvider.unbindAll()
try {
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, paperAnalyzer)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
It seems like the logic you're trying to accomplish looks like this.
fun analyze(imageProxy) {
if (isAnalyzing) {
imageProxy.close() // 1
} else {
val image = imageInput.fromMediaImage(...)
objectDetector.process(image)
.addOnSuccessListener(OnSuccessListener { result ->
// Do something with the result
imageProxy.close() // 2
isAnalyzing.set(false)
})
.addOnFailureListener(OnFailureListener { exception ->
// Do something with the exception
imageProxy.close() // 3
isAnalyzing.set(false)
})
}
}
I might be wrong, but it looks like you're doing 1, but not 2 and 3. Could you update your code to follow this pattern and see if you're still encountering the issue.

Kotlin: mapNotNull but log what caused null elements

When translating Java to Kotlin code, I encountered the following:
List<Content> getContent(List<Node> nodes, Map<String, Content> content) {
List<Content> result = new ArrayList<>(nodes.size());
for (Node node : nodes) {
Content content = content.get(node.getId());
if (content == null) {
logger.atSevere().log("Content %s was not found", node.getId());
continue;
}
result.add(content);
}
return result;
}
In Kotlin, this can be easily translated if we drop the logger call:
fun getContent(items: List<Node>, content: Map<String, Content): List<Content> {
val contentIds = items.mapNotNull { it.id }
return contentIds.mapNotNull { contentMap[it] }
}
I'm thinking a sequence builder might be nice here. It's also possible to separate out contentIds into two separate collections, one made up of the contentIds that were not present in contentMap, the other made up of the Content mapped to successfully. I bet there is also a better way to get a set of items in a map from a set of keys, but I haven't found the right function.
Please try the following:
fun check(items: List<Node>, content: Map<String, Content?>): List<Content>{
return items.filter{
if (content[it.id] == null){
print("content " + it.id + "was not found")
}
content[it.id] != null
}.map{content[it.id]!!}
}