I'm using mapbox, with GeoJsonSource and symbollayer. When user clicks on feature it should change a color. I handle this logic with following code and it works, but it is too slow and takes several second to change icon color.
Here I configure symbol layer, add icon changelogic for 'PROPERTY_SELECTED':
mapBoxMap?.addLayer(SymbolLayer(markerStyleLayerIdentifier, markerSourceIdentifier)
.withProperties(
PropertyFactory.iconImage(markerImage),
PropertyFactory.iconAllowOverlap(false),
PropertyFactory.iconImage(match(
get(PROPERTY_SELECTED), literal(0),
literal(markerImage),
literal(markerImageSelected)
))
))
on map click features objects are update:
override fun onMapClick(point: LatLng) {
val screenPoint = mapBoxMap?.projection?.toScreenLocation(point)
var features = mapBoxMap?.queryRenderedFeatures(screenPoint
?: return, markerStyleLayerIdentifier)
if ((features ?: return).isNotEmpty()) {
var feature = features[0]
showMarkerInfo(feature)
doAsync {
var featureList = featureCollection?.features()
var id = feature.getNumberProperty(PROPERTY_STOP_ID)
if (featureList != null) {
for (i in 0 until featureList.size) {
var fId = featureList[i].getNumberProperty(PROPERTY_STOP_ID)
if (fId == id) {
featureList[i].properties()?.addProperty(PROPERTY_SELECTED, 1)
} else {
featureList[i].properties()?.addProperty(PROPERTY_SELECTED, 0)
}
}
uiThread {
refreshSource()
}
}
}
}
}
and refresh source :
private fun refreshSource() {
var source = mapBoxMap?.getSource(markerSourceIdentifier) as GeoJsonSource?
if (source != null && featureCollection != null) {
source.setGeoJson(featureCollection)
}
}
after 'refreshSource' is called , it takes several time before icon update. In my case there are 2050 features is source. Is there any better way to implement it ? Or any way to optimise this solution ?
here is a second , faster way from github answer:
var selectedLayer = mapBoxMap?.getLayer(markerSelectedStyleLayerIdentifier) as SymbolLayer?
var id = feature.getNumberProperty(PROPERTY_STOP_ID)
var selectedExpression = any(
eq(get(PROPERTY_STOP_ID), literal(id.toString()))
)
selectedLayer?.filter = selectedExpression
you can see whole issue there
https://github.com/mapbox/mapbox-java/issues/892
Related
I'm developing an Android app for recognizing augmented images and displaying videos directly over them.
I have implemented everything and it's working well but the only issue is that the anchorNode model that gets attached to the augmented image anchor isn't very stable and doesn't remain exactly in the image's frame.
I tried to follow some best practices for improving tracking and got better results but there is only one point that I'm not understanding which is "If your image will never move from its initial position (for example, a poster affixed to a wall), you can attach a global anchor to the image to increase tracking stability.", could someone help explain how to that
, please? and It would be great if I can get some tips for improving tracking evermore.
Thanks in advance.
Here is the code I'm using for tracking and creating video anchors.
override fun onSessionConfiguration(session: Session?, config: Config?) {
Log.d("ttt","onSessionConfiguration")
this.session = session
// Disable plane detection
config!!.planeFindingMode = Config.PlaneFindingMode.DISABLED
Thread().run {
database = AugmentedImageDatabase(session).also {
for(fileName in requireContext().fileList().filter { it.contains("image.jpg") }){
val file = File(requireContext().filesDir,fileName)
if(file.exists()){
it.addImage(fileName.toString().split("-")[0],BitmapFactory.decodeFile(file.absolutePath),0.25f)
}
}
}
config.setAugmentedImageDatabase(database)
}
arFragment?.setOnAugmentedImageUpdateListener { it->
run {
onAugmentedImageTrackingUpdate(it)
}
}
}
private fun onAugmentedImageTrackingUpdate(augmentedImage: AugmentedImage) {
attachVideo(augmentedImage)
}
private fun attachVideo(augmentedImage: AugmentedImage){
if (augmentedImage.trackingState == TrackingState.TRACKING
&& augmentedImage.trackingMethod == AugmentedImage.TrackingMethod.FULL_TRACKING){
if (!matrixDetected) {
matrixDetected = true
lastTracketedImageName = augmentedImage.name
anchorNode = AnchorNode(augmentedImage.createAnchor(augmentedImage.centerPose))
anchorNode!!.isSmoothed = true
texture = ExternalTexture().also {
it.surfaceTexture.setOnFrameAvailableListener {
texture!!.surfaceTexture.setOnFrameAvailableListener(null)
val model = ShapeFactory.makeCube(
Vector3(augmentedImage.extentX,0f,augmentedImage.extentZ),
Vector3(0f,0f,0f),
plainVideoMaterial
)
model.isShadowReceiver = false
model.isShadowCaster = false
val renderable = anchorNode!!.setRenderable(model)
renderable.material!!.setExternalTexture("videoTexture",texture)
fadeInVideo(renderable.material)
}
}
val videoFile = File(requireContext().filesDir,"${augmentedImage.name}-video.mp4")
mediaPlayer = MediaPlayer.create(requireContext(),Uri.fromFile(videoFile)).also {
it.isLooping = true
it.setSurface(texture!!.surface)
it.start()
}
arFragment!!.arSceneView.scene.addChild(anchorNode)
}
}else{
if(matrixDetected){
if(anchorNode != null){
arFragment!!.arSceneView.scene.removeChild(anchorNode!!)
anchorNode = null
}
viewLifecycleOwner.lifecycleScope.launch {
val result = viewModel.endArVideo(deviceID,augmentedImage.name)
if(result.isSuccessful){
Log.d("ttt","stop ar request success")
}else{
Log.d("ttt","stop ar request failed ${result.errorBody()}")
}
}
matrixDetected = false
}
mediaPlayer?.let {
it.setSurface(null)
it.stop()
it.release()
mediaPlayer = null
}
}
}
private fun fadeInVideo(material: Material) {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 500L
interpolator = LinearInterpolator()
addUpdateListener { v ->
material.setFloat("videoAlpha", v.animatedValue as Float)
}
}.start()
}
I have implemented LazyColumn with Paging, but I'm now trying to add sticky headers as well.
The stickyHeader() function is not available inside the items() scope, so I don't see how this should work.
#Composable
fun MovieList(movies: Flow<PagingData<Movie>>) {
val lazyMovieItems: LazyPagingItems<Movie> = movies.collectAsLazyPagingItems()
LazyColumn {
// TODO: Add sticky headers
items(lazyMovieItems) { movie ->
MovieItem(movie = movie!!)
}
}
}
How can I add the stickyHeaders?
#Composable
fun MovieList(movies: Flow<PagingData<Movie>>) {
val lazyMovieItems = movies.collectAsLazyPagingItems()
LazyColumn {
val itemCount = lazyMovieItems.itemCount
var lastCharacter: Char? = null
for (index in 0 until itemCount) {
// Gets item without notifying Paging of the item access,
// which would otherwise trigger page loads
val movie = lazyMovieItems.peek(index)
val character = movie?.name?.first()
if (movie !== null && character != lastCharacter) {
stickyHeader(key = character) {
MovieHeader(character)
}
}
item(key = movie?.id) {
// Gets item, triggering page loads if needed
val movieItem = lazyMovieItems[index]
Movie(movieItem)
}
lastCharacter = character
}
}
}
I am using MpAndroidChart library for android. I am able to give different color for the region below and above of the limit line. but i am getting a keyword "Stack" in legend(please check the attached screen shot).
Without limit line everything working perfectly. But if i am using limit line (red for region right of the limit line and green for otherside ) i am getting two legend and keyword "Stack" is appending along with legend.
if (yAxisValue != null) {
var progressData = progressLayoutData
val barDatasets: MutableList<IBarDataSet> = java.util.ArrayList()
for (yValues in yAxisValue) {
val entries: MutableList<BarEntry> = java.util.ArrayList()
var i = 0f
var isHaveLimitLine = false
val colorList: MutableList<Int> = java.util.ArrayList()
for (value in yValues?.dataSet) {
if(progressData?.limitLine!=null && progressData?.limitLine!=0) {
if (value > progressData?.limitLine ?: 0) {
var differenceValue = value?.toFloat() - progressData?.limitLine?.toFloat()!!
entries.add(BarEntry(i, floatArrayOf(progressData?.limitLine?.toFloat()!!, differenceValue)))
colorList.add(fhuiManager?.getParsedColor(yValues?.color,context))
colorList.add(fhuiManager?.getParsedColor(progressData?.limitExceedColor ?: "#FE2020",context))
} else {
colorList.add(fhuiManager?.getParsedColor(yValues?.color,context))
entries.add(BarEntry(i, value?.toFloat()))
}
isHaveLimitLine = true
}else{
entries.add(BarEntry(i, value?.toFloat()))
}
i++
}
val barDataSet = BarDataSet(entries, "" + yValues?.datasetName)
if(isHaveLimitLine)
barDataSet?.colors = colorList
else
barDataSet?.color = fhuiManager?.getParsedColor(yValues?.color,context)
barDataSet?.setDrawValues(false)
// add to line data set
barDatasets?.add(barDataSet)
}
return barDatasets
}
Please see the screenshot of issue here
How can I implement text completion,Like Gmail's smart compose?
I've an edit text where the user enters server address and I want to detect when they start typing the domain suffix and suggest completion.
Thanks.
First you need an algorithm to get suggestion from a given dictionary.
I've created a simple class named SuggestionManager to get suggestion from a given dictionary for a string input. Instead of returning the full match, it'll only return the remaining part of the given input. Below given a simple unit test along with full source code of the class. You can also go here to run the test online.
SuggestionManager.kt
class SuggestionManager(private val dictionary: Array<String>) {
companion object {
private val WORD_SPLIT_REGEX = Regex("[^A-Za-z0-9'\\-]")
/**
* To get reversed list
*/
private fun getReversedList(list: List<String>): MutableSet<String> {
val reversed = mutableSetOf<String>()
for (item in list.withIndex()) {
if (item.index != 0) {
val rev = list.subList(list.size - item.index, list.size).joinToString(" ")
reversed.add(rev)
}
}
// finally, add the full string
reversed.add(list.joinToString(" "))
return reversed
}
}
fun getSuggestionFor(_text: String?): String? {
var text = _text
// empty text
if (text.isNullOrBlank()) {
return null
}
// Getting last line only
if (text.contains("\n")) {
text = text.split("\n").last()
if (text.trim().isEmpty()) {
return null
}
}
// Splitting words by space
val words = text.split(WORD_SPLIT_REGEX).filter { it.isNotBlank() }
// Getting last word
val lastWord = if (text.endsWith(" ")) "${words.last()} " else words.last()
// Matching if last word exist in any dictionary
val suggestions = mutableSetOf<String>()
for (dicItem in dictionary) {
if (dicItem.contains(lastWord, true)) {
// Storing founded matches
suggestions.add(dicItem)
}
}
// Reverse ordering split-ed words
val pyramidWords = getReversedList(words)
val matchList = mutableListOf<String>()
for (pw in pyramidWords) {
for (sug in suggestions) {
// Storing suggestions starts with reversed word
if (sug.startsWith(pw, true)) {
matchList.add("$pw:$sug")
}
}
}
// Checking if second level match is not empty
if (matchList.isNotEmpty()) {
// Ordering by matched reversed word - (largest key first)
matchList.sortBy { -it.split(":")[0].length }
// Looping through in ascending order
for (m in matchList) {
val match = m.split(":")
val selPw: String = match[0]
var selSug: String = match.subList(1, match.size).joinToString(":")
// trimming to
selSug = selSug.replace(selPw, "", true)
if (text.endsWith(" ")) {
selSug = selSug.trim()
}
return selSug
}
}
return null
}
}
Unit Test
class SuggestionManagerUrlTest {
private val suggestionManager by lazy {
val dictionary = arrayOf(
"google.com",
"facebook.com",
"gmail.com",
"yahoo.com",
"192.168.354.45"
)
SuggestionManager(dictionary)
}
#Test
fun test() {
// null of empty and null input
assertNull(suggestionManager.getSuggestionFor(null))
assertNull(suggestionManager.getSuggestionFor(""))
// matching
assertEquals("ogle.com", suggestionManager.getSuggestionFor("go"))
assertEquals("book.com", suggestionManager.getSuggestionFor("face"))
assertEquals(".168.354.45", suggestionManager.getSuggestionFor("192"))
// no match
assertNull(suggestionManager.getSuggestionFor("somesite"))
}
}
Then, you'll have to set text in EditText with two colors. One for input and other for the suggestion. You may use the Html.fromHtml method to do this.
val text = "<font color=#cc0029>$input</font> <font color=#ffcc00>$suggestion</font>";
yourEditText.setText(Html.fromHtml(text));
Combining these two aspects, you can create a custom EditText.
I am new to kotlin programming. What I want is that I want to remove a particular data from a list while iterating through it, but when I am doing that my app is crashing.
for ((pos, i) in listTotal!!.withIndex()) {
if (pos != 0 && pos != listTotal!!.size - 1) {
if (paymentsAndTagsModel.tagName == i.header) {
//listTotal!!.removeAt(pos)
listTotal!!.remove(i)
}
}
}
OR
for ((pos,i) in listTotal!!.listIterator().withIndex()){
if (i.header == paymentsAndTagsModel.tagName){
listTotal!!.listIterator(pos).remove()
}
}
The exception which I am getting
java.lang.IllegalStateException
use removeAll
pushList?.removeAll { TimeUnit.MILLISECONDS.toMinutes(
System.currentTimeMillis() - it.date) > THRESHOLD }
val numbers = mutableListOf(1,2,3,4,5,6)
val numberIterator = numbers.iterator()
while (numberIterator.hasNext()) {
val integer = numberIterator.next()
if (integer < 3) {
numberIterator.remove()
}
}
It's forbidden to modify a collection through its interface while iterating over it. The only way to mutate the collection contents is to use Iterator.remove.
However using Iterators can be unwieldy and in vast majority of cases it's better to treat the collections as immutable which Kotlin encourages. You can use a filter to create a new collections like so:
listTotal = listTotal.filterIndexed { ix, element ->
ix != 0 && ix != listTotal.lastIndex && element.header == paymentsAndTagsModel.tagName
}
The answer by miensol seems perfect.
However, I don't understand the context for using the withIndex function or filteredIndex. You can use the filter function just by itself.
You don't need access to the index the list is at, if you're using
lists.
Also, I'd strongly recommend working with a data class if you already aren't. Your code would look something like this
Data Class
data class Event(
var eventCode : String,
var header : String
)
Filtering Logic
fun main(args:Array<String>){
val eventList : MutableList<Event> = mutableListOf(
Event(eventCode = "123",header = "One"),
Event(eventCode = "456",header = "Two"),
Event(eventCode = "789",header = "Three")
)
val filteredList = eventList.filter { !it.header.equals("Two") }
}
The following code works for me:
val iterator = listTotal.iterator()
for(i in iterator){
if(i.haer== paymentsAndTagsModel.tagName){
iterator.remove()
}
}
You can also read this article.
People didn't break iteration in previous posts dont know why. It can be simple but also with extensions and also for Map:
fun <T> MutableCollection<T>.removeFirst(filter: (T) -> Boolean) =
iterator().removeIf(filter)
fun <K, V> MutableMap<K, V>.removeFirst(filter: (K, V) -> Boolean) =
iterator().removeIf { filter(it.key, it.value) }
fun <T> MutableIterator<T>.removeFirst(filter: (T) -> Boolean): Boolean {
for (item in this) if (filter.invoke(item)) {
remove()
return true
}
return false
}
Use a while loop, here is the kotlin extension function:
fun <E> MutableList<E>.removeIfMatch(isMatchConsumer: (existingItem: E) -> Boolean) {
var index = 0
var lastIndex = this.size -1
while(index <= lastIndex && lastIndex >= 0){
when {
isMatchConsumer.invoke(this[index]) -> {
this.removeAt(index)
lastIndex-- // max is decreased by 1
}
else -> index++ // only increment if we do not remove
}
}
}
Typically you can use:
yourMutableCollection.removeIf { someLogic == true }
However, I'm working with an Android app that must support APIs older than 24.
In this case removeIf can't be used.
Here's a solution that is nearly identical to that implemented in Kotlin Collections that doesn't rely on Predicate.test - which is why API 24+ is required in the first place
//This function is in Kotlin Collections but only for Android API 24+
fun <E> MutableCollection<E>.removeIff(filter: (E) -> Boolean): Boolean {
var removed = false
val iterator: MutableIterator<E> = this.iterator()
while (iterator.hasNext()) {
val value = iterator.next()
if (filter.invoke(value)) {
iterator.remove()
removed = true
}
}
return removed
}
Another solution that will suit small collections. For example set of listeners in some controller.
inline fun <T> MutableCollection<T>.forEachSafe(action: (T) -> Unit) {
val listCopy = ArrayList<T>(this)
for (element: T in listCopy) {
if (this.contains(element)) {
action(element)
}
}
}
It makes sure that elements of collection can be removed safely even from outside code.