Osmdroid: How to force a map to refresh? - android

I am displaying a large number of overlays on a map; the process takes 5-10 seconds. Rather than using a progress bar I want the overlays to appear gradually on the map over this time.
So for every 25 lines that are calculated I am trying to show them before continuing:
for (KmlRoad road :roads)
{
// 25 lines in this array
Polyline pline = new Polyline();
pline.getOutlinePaint().setColor(Color.RED);
pline.setPoints(road.points);
map.getOverlayManager().add(pline);
++roadsShown;
}
map.invalidate();
progressText.setText(roadsShown + " roads");
But nothing happens for a while and then all the overlays (several hundred of them) appear at once. The 'progressText' isn't updated, either.
I have also tried doing the calculation part in an async task, and sending an intent to the map activity for every 25 lines. It gives the same result, unless I add
SystemClock.sleep(xx);
to to async task, which seems very crude and is only guessing at how long the lines take to draw. Here's the code in the async task, which is called every time 25 lines have been calculated:
private void sendRoads(ArrayList<KmlRoad> roads25)
{
ArrayList<KmlRoad> theseRoads = new ArrayList<>(roads25);
// prepare array for next batch while we deal with this one
roads25.clear();
Intent intent = new Intent("newRoads");
intent.putParcelableArrayListExtra("newRoads", theseRoads);
LocalBroadcastManager.getInstance(activity).sendBroadcast(intent);
// procSpped is a crude assessment of how slow the device runs,
// generated by doing a repeat loop of math functions
SystemClock.sleep(2 * activity.procSpeed);
}
... and (if using the async method) the receiver in the main activity, which surrounds the line-drawing code above:
private final BroadcastReceiver onNewRoad = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<KmlRoad> roads = intent.getParcelableArrayListExtra("newRoads");
....
....
}
};
What can I do to make this show as intended?.

Its rather easy, also its just a work around....
get the current center of your map like
// MyMap is the osmdroid.MapView in the AndroidActivity
GeoPoint center = this.MyMap.getMapCenter();
and now set this center to map:
this.MyMap.getController().setCenter(center);
this forces your Map to refresh, like a manual movement would do the same
BestRegards

Related

Background rendering and UI display

My first attempt at AndroidPlot. The data I want to plot (and update every 5 seconds when a new data point arrives) comes from an ArrayBlockingQueue of up to 720 timestamped points. I have a class that implements the XYSeries and PlotListener interfaces. It has a method updatePlotData that just extracts the data from the queue into an array:
class TempPlotSeries implements XYSeries, PlotListener {
private final static String TAG = TempPlotSeries.class.getSimpleName();
private Pair<Date, Float>[] plotArray;
void updatePlotData( ArrayBlockingQueue<Pair<Date, Float>> dataQueue ) throws InterruptedException {
synchronized ( this ) {
wait(); // don't update data until we're notified that current plot is done (& we can get lock)
plotArray = dataQueue.toArray( new Pair[0] );
if( DEBUG ) Log.d( TAG, "updatePlotData run with " + plotArray.length + " data points" );
notifyAll(); // release lock & let other threads know they can continue
}
}
// XYSeries implementation
#Override
public int size( ) {
return plotArray.length;
}
#Override
public Number getX( int index ) {
return (index - HISTORY_BUFFER_SIZE) / (60/TEMP_UPDATE_SECONDS); // e.g., -60 minutes at left edge of graph, -1/12 min at right
}
#Override
public Number getY( int index ) {
return plotArray[index].second; // the temp value
}
#Override
public String getTitle( ) {
return "Temp History";
}
// PlotListener Implementation
#Override
public void onBeforeDraw( Plot source, Canvas canvas ) {
synchronized ( this ) {
try {
wait(); // wait for data updating to finish if it's in progress on another thread
} catch ( InterruptedException e ) {
// unlikely to be interrupted?
}
}
}
// between these 2 calls the plot is redrawn
#Override
public void onAfterDraw( Plot source, Canvas canvas ) {
synchronized ( this ) {
notifyAll( ); // plot done, OK to update data
}
}
}
I don't have much experience with synchronization--does this look reasonable?
My plot setup is:
tempHistoryPlot = (XYPlot) findViewById(R.id.temp_history);
tempPlotSeries = new TempPlotSeries();
tempHistoryPlot.setRenderMode( Plot.RenderMode.USE_BACKGROUND_THREAD );
tempGraphFormatter = new LineAndPointFormatter(this, R.xml.line_point_formatter_with_labels);
tempHistoryPlot.addSeries(tempPlotSeries, tempGraphFormatter);
tempGraphWidget = tempHistoryPlot.getGraph();
(couldn't find any documentation on the purpose of getGraph() so don't know if I need it.)
I have an Observable (RxJava) that emits the entire data queue when a new sample is available (every 5 seconds). If the queue is full I discard the oldest value. Then I have:
tempPlotSeries.updatePlotData( newTempHistory );
tempHistoryPlot.redraw();
But the plot isn't drawn. When the app first launches, the "dummy" plot appears in its View, but as soon as I try to draw the plot the entire ConstraintLayout containing the XYPlot element (and other UI elements) is completely blanked. What's going on here?
Other questions: it's my understanding that any code affecting the Android UI must run on the main thread. But we're using a background thread to render the plot. How does this work? Do I perhaps need to insert a .observeOn( AndroidSchedulers.mainThread() operator in my Observable chain?
I don't have much experience with synchronization--does this look reasonable?
I don't think you need the wait() inside the synchronized block inside updatePlotData. You can also use SimpleXYSeries as a reference for how to setup synchronization of this sort.
When the app first launches, the "dummy" plot appears in its View, but as soon as I try to draw the plot the entire ConstraintLayout containing the XYPlot element (and other UI elements) is completely blanked.
I'm having trouble visualizing this. Could you add a screenshot of the "dummy" plot and the subsequent blank plot?
it's my understanding that any code affecting the Android UI must run on the main thread. But we're using a background thread to render the plot. How does this work?
The general rules of using the main thread to update the UI still exist, Androidplot is just using a technique to minimize the main thread usage during intensive rendering: A background thread is used to fill a bitmap buffer with the data to be shown, then notifies the main thread when the buffer is ready to be displayed.
Somewhat Unrelated Suggestion: Looking at your TempPlotSeries implementation, I notice that you are modeling your data as a Pair<Date, Float>[] but your getX() implementation does not make use of the Date part. It appears you're trying to model your data using what I assume is your desired display format for your domain, ie. -60 to -1/12 minutes. For simplicity I'd suggest making getX() return the Date's long epoch value instead. You can apply a display format to these values later.

Skobbler: centerMapOnCurrentPosition() centers me in Aftrica even though blue dot is correct

I am using the skobbler sdk for maps.
I use this code to center map view on current position, but even though the blue dot is at my correct current position in Los Angeles (I verify by manually going there), the map centers me at gps(0,0).
public void onCurrentPositionUpdate(SKPosition currentPosition) {
this.currentPosition = currentPosition;
mapView.reportNewGPSPosition(this.currentPosition);
if(firstPositionUpdate){
firstPositionUpdate = false;
mapView.centerMapOnCurrentPosition();
}
}
and heres my code for initializing the map:
private void initializeMapView() {
currentPositionProvider = new SKCurrentPositionProvider(getActivity());
currentPositionProvider.setCurrentPositionListener(this);
if (DemoUtils.hasGpsModule(getActivity()) && ((LocationManager)getActivity().getSystemService(Context.LOCATION_SERVICE)).isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
currentPositionProvider.requestLocationUpdates(true, true, true);
}
SKMapViewHolder mapViewGroup = (SKMapViewHolder) getView().findViewById(R.id.map_surface_holder);
mapView = mapViewGroup.getMapSurfaceView();
mapView.setMapSurfaceListener(this);
mapView.getMapSettings().setFollowerMode(SKMapSettings.SKMapFollowerMode.NONE);
mapView.getMapSettings().setMapRotationEnabled(true);
mapView.getMapSettings().setMapZoomingEnabled(true);
mapView.getMapSettings().setMapPanningEnabled(true);
mapView.getMapSettings().setZoomWithAnchorEnabled(true);
mapView.getMapSettings().setInertiaRotatingEnabled(true);
mapView.getMapSettings().setInertiaZoomingEnabled(true);
mapView.getMapSettings().setInertiaPanningEnabled(true);
SKVersioningManager.getInstance().setMapUpdateListener(this);
mapView.centerMapOnPosition(new SKCoordinate( -118.123,34.123));
//launchRouteCalculation();
}
It seems that reportNewGPSPosition(this.currentPosition); does not work instantly.
I noticed that if I simply delay the call to centerMapOnCurrentPosition(), the map is centered correctly. There are two work arounds:
-set location manually:
mapView.centerMapOnPosition(new SKCoordinate( currentPosition.getLongitude(), currentPosition.getLatitude()));
-or create a delayed runnable to call to perform the centering at a future time:
android.os.Handler handler = new android.os.Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
mapView.centerMapOnCurrentPosition();
mapView.setZoom(10);
}
},100);
note: This issue only becomes evident because of the if statement I put in to only center the map on the first update of location. Another work around would be to put a use a counter and center map on the second time the location is updated.
Also if implementing this with the mentioned if statement, don't forget to consider accuracy; it would be bad to center when the location is not accurate, and not center on a future location update which IS accurate.

Android maps v2 becomes low quality when map area is automatically moved

I am simulating a car moving on a pre-recorded path on Android maps v2. When I zoom on the path by hand, it works great, but when I move the camera over the path with mMap.animateCamera(), it doesn't load the visible map area, I only get a very pixelated, low quality map. If I touch the screen and move the map or zoom a little, then it loads again this part.
How can I achieve, that it always loads clearly the visible part?
EDIT:
I added an example image: this is what I see when I don't touch the map. After I touch it, it becomes clear (similar to the bottom left part).
EDIT2:
I have an idea, that this is because Google want's to prevent the map to be cached by quickly moving the camera over an area. Is it possible, that this is the cause of this issue? (The map is showing Budapest, Hungary, which part of the map you can not download for offline use...) But here I only want to show the animation and place markers, I only need the visible area to be cached - are there any way to workaround this behaviour?
EDIT3:
The animation code:
new Thread(new Runnable() {
#Override
public void run() {
// ... Calculating and sending new location, in an infinite loop with sleeps for timing
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location.getLongitude())));
}
});
}
}).start();
Finally found a solution. I was able to recreate your problem using the code you provided. I replaced your code with the following and it worked for me.
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// ... Calculating and sending new location, in an infinite loop with sleeps for timing
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mMap.animateCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location.getLongitude())));
}
});
}
}, 0, 2000);
Just remove your code for sleeping and replace the last argument in scheduleAtFixedRate (the 2000) with whatever value you were using for sleeping.
I had the same issue. Since it did not happen when not animating the camera, it had to be something related to that.
Apperently the camera has to be able to finish its operation, before it will update the background/roads etc.
Since my app updates the position every second and the map needs about 2 seconds to complete I end up with pixelated roads and no surroundings at all.
The solution is to use one of overloads of animateCamera:
public final void animateCamera (CameraUpdate update, int durationMs,
GoogleMap.CancelableCallback callback)
Moves the map according to the update with an animation over a
specified duration, and calls an optional callback on completion. See
CameraUpdateFactory for a set of updates.
I used for my case a duration of 900msec, so the animation is done before it receives a new location.
To get the callback to work you need to implement GoogleMap.CancelableCallback to your class. This requires you to add two overrides:
#Override
public void onFinish() {
}
#Override
public void onCancel() {
}
They are not required to get the problem solved, altough you are free to add extra logic there.
The call to update the camera can look like this:
cameraPosition = new CameraPosition.Builder()
.target(current)
.zoom(zoomLevel)
.bearing(bearing)
.tilt(tilt)
.build();
CameraUpdate update = CameraUpdateFactory.newCameraPosition(cameraPosition);
map.animateCamera(update, 900, this);

Google Maps v2 addPolygon() plugs up UI thread so my progress dialog doesn't animate

I am working on an application in which I would like to be able to load up to 10,000 polygons (40,000 lat/lng pairs) onto Google Maps v2 and the performance is mostly acceptable. But since it takes about 20 seconds to load, I need to have a progress indicator so it doesn't look like the app has locked up.
I believe the core issue is that the map.addPolygon(polygonOptions) call which I must repeat 10,000 times and must run on the UI thread plugs up the UI thread such that the progress dialog doesn't get any time to update it's UI. Here is an approximation of my code:
#Override
protected void onCreate(.....) {
progressDialog = new ProgressDialog(AllLayersActivity.this);
progressDialog.setMessage("Processing files:");
progressDialog.setTitle("Loading data");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setProgress(0);
progressDialog.setMax(1);
progressDialog.show();
progressDialog.incrementProgressBy(1);
for(String filename : intent.getStringArrayListExtra(DbxService.PARAM_FILES)) {
if(filename.toLowerCase(Locale.getDefault()).endsWith(".shp")) {
dbxService.getFileContents(new DbxPath(pathToField + filename));
}
}
}
BroadcastReceiver for dbxService.getFileContents() {
new Thread(new Runnable() {
#Override
public void run() {
byte[] fileContents = loadFromBroadcastIntent(.....);
progressDialog.setMax(progressDialog.getMax() + getNumberOfParts(fileContents));
// bunch of other processing
for(Part part : getParts(fileContents) {
final PolygonOptions polygonOptions = new PolygonOptions();
// processing stuff
polygonOptions.addAll(latLngList);
runOnUiThread(new Runnable() {
public void run() {
map.addPolygon(polygonOptions);
progressDialog.incrementProgressBy(1);
if(progressDialog.getProgress() == progressDialog.getMax()) {
progressDialog.dismiss();
}
}
});
}
}
}).start();
}
I know that increasing the max of ProgressDialog on the fly probably looks kind of weird but I think it should work well in practice as the progressDialog.setMax(progressDialog.getMax() + parts); line runs many times less than the progressDialog.incrementProgressBy(1); lines, always adds more before the dialog could possibly falsely get to 100%, and should be increased to its final value quite quickly because the map.addPolygon(polygonOptions); line is by far the slowest thing I'm doing.
So, what can I do to allow the progress bar to animate nicely?
I would have just added this as a comment but I have under 50 rep so I can't yet. With that said I have an app that can add up to 10,000 Markers on the map and I run into the same issue. I tried adding the Markers in another thread but adding items to the map must be done in the UI thread which is the same thread as the progressDialog. So the short answer is No. If someone out there smarter than me knows of away to do this in an AsyncTask or Runnable then please LET ME KNOW TOO!

mylocationoverlay disappears first time it find location

I am trying to setup a mylocationoverlay. Unfortunately, it is acting quite strangely. It works fine, except it does not appear until after I leave the MapActivity and come back in my application. Initially the map appears and there is a blue circle while it is getting a fine location. However, instead of resolving to a point, the circle just disappears.
My Code looks like this:
onResume() {
myLocation = new MyLocationOverlay(getActivity(), mp);
myLocation.enableMyLocation();
myLocation.runOnFirstFix(new Runnable(){
public void run() {
map.getOverlays().clear();
map.getOverlays().add(myLocation);
map.postInvalidate();
}
}
}
onPause() {
myLocation.disableMyLocation();
layout.removeView(map);
map = null;
}
Does anyone have any thoughts on what might be happening here? Since this is pretty much verbatim what all the examples online look like, I might add that I am testing this on a motorolla atrix running 2.3.4.
Edit : Let me take you through your code:
onResume() {
// First time: draw a circle somewhere here. There is no GPS fix yet, so no dot.
// Second time: The dot from the previous fix exists, so you get a circle and dot.
myLocation = new MyLocationOverlay(getActivity(), mp);
myLocation.enableMyLocation();
myLocation.runOnFirstFix(new Runnable(){
public void run() {
// First time: removes the circle and draws a dot.
// Second time: removes the circle and dot, and draw a new dot.
map.getOverlays().clear();
map.getOverlays().add(myLocation);
map.postInvalidate();
}
}
}
map.getOverlays().clear(); removes the circle
use remove() instead to remove the overlay(s) that you don't want, instead of clearing them all.
Remember to call map.invalidate(); whenever you need to force a redraw

Categories

Resources