I'd previously managed to incorporate OpenStreetMaps into my application using osmdroid-android-3.0.1.jar with the great assistance of the answer to my question
Porting a Google Maps app to Osmdroid - problem with overlay
I've now upgraded to using the 3.0.3 jar and can't get my overlay to display.
My app can switch display from Google Maps to OSM and display the same overlay consisting of lines and texts on top of either. The alternative displays each run in their own activity for now, as the classes are similar but do not have absolutely identical methods, or at least they didn't have in 3.0.1. (Ultimately I'd like to use the Osmdroid wrapper jar to combine them in one activity and reduce the duplicate code, but that's a question for a later date) Right now I'd like to get the same functionality as I had with the now deprecated 3.0.1 jar using the new 3.0.3 version.
With the new jar, I still get the mapview displayed OK but the overlay has disappeared. I've had to make some changes to the code, as the 3.0.1 onDraw() method has now been replaced with draw() (just like Google) in the MapOverlay extends org.osmdroid.views.overlay.Overlay class.
All the code in the previous onDraw() (now draw) method has been copied verbatim from the answer to the question referred to above, It worked fine, although I confess to not fully understanding the concepts of worldsize, bounding boxes and the transformation described.
I notice that the method
final Point upperLeft = org.osmdroid.views.util.Mercator
.projectGeoPoint(boundingBox.getLatNorthE6(), boundingBox.getLonWestE6(),
zoomLevel + tileZoom, null);
is now deprecated, and I had to remove tileZoom and
final int tileZoom = projection.getTileMapZoom();
to get it to compile.
When I get to the code in the draw() method, I can see in the debugger that all the data necessary to draw the overlay is still present and correct. The drawing is done by lines such as canvas.drawLine(....) and canvas.drawText(....). I've not used the extra parameter to the function (boolean shadow) at all.
My redrawOverlay() method remains as:
private void redrawOverlay() {
mGpt = mMapVw.getMapCenter();
if (mmapOverlay == null)
mmapOverlay = new MapOverlay(this);
mmapOverlay.setEnabled(true);
List<Overlay> listOfOverlays = mMapVw.getOverlays();
int ovlSize = listOfOverlays.size();
if (ovlSize > 1)
listOfOverlays.remove(1);
listOfOverlays.add(mmapOverlay);
mMapVw.invalidate();
}
(The Google listofOverlays.clear() shouldn't be used as osmdroid 3.0.1 had element 0 as the map itself - hence the remove(1))
I'm wondering what I have to do to modify the existing 3.0.1 code to work with 3.0.3? I'm hoping that one of the authors of might read this question.
Update
By adapting the Minimap overaly as suggested in the answer to the question referred to above, the draw() method for drawing an overlay from top left to bottom right, now becomes:
#Override
protected void draw(Canvas pC, MapView pOsmv, boolean shadow) {
if(shadow)
return;
Paint lp3;
lp3 = new Paint();
lp3.setColor(Color.RED);
lp3.setAntiAlias(true);
lp3.setStyle(Style.STROKE);
lp3.setStrokeWidth(1);
lp3.setTextAlign(Paint.Align.LEFT);
lp3.setTextSize(12);
// Calculate the half-world size
final Rect viewportRect = new Rect();
final Projection projection = pOsmv.getProjection();
final int zoomLevel = projection.getZoomLevel();
int mWorldSize_2 = TileSystem.MapSize(zoomLevel) / 2;
// Save the Mercator coordinates of what is on the screen
viewportRect.set(projection.getScreenRect());
// DON'T set offset with either of below
//viewportRect.offset(-mWorldSize_2, -mWorldSize_2);
//viewportRect.offset(mWorldSize_2, mWorldSize_2);
// Draw a line from one corner to the other
pC.drawLine(viewportRect.left, viewportRect.top, viewportRect.right, viewportRect.bottom, lp3);
}
This works OK
Let me try to suggest a few things:
In the new jar build, calling getOverlays().clear() will no longer remove the map tile overlay. Calling remove(1) may be incorrect now since the map tile overlay is no longer in position 0.
The projection has a method that will get you the screen coordinate rectangle of what is currently on the screen. You can check the coordinates you use to draw your lines with against this rectangle to make sure they intercept it.
Take a look at the new TileSystem class. Anything that was deprecated can be recreated using these functions.
Related
I'm developing a App which display a Google map and a bunch of markers on it. There's a lot of markers so I divided them in smaller groups and display only those, which are in some bounds depending on the current position of the camera.
To do that I'm using the GoogleMap.OnCameraIdleListener. First I remove the listener, do my calculations and drawing and then I restore the listener to the Fragment containing my map:
#Override
public void onCameraIdle() {
mMap.setOnCameraIdleListener(null);
clearMap();
findTheMarkersInBounds();
displayTheMarkers();
mMap.setOnCameraIdleListener(this);
}
This way I only draw the markers I need to display and the performance is way better then having 1000 markers on the map at once. I also draw about the same number of polylines but that's not the point now.
For some strange reasons, after some panning and zooming the maps doesn't respond anymore. Can't zoom it nor pan it. App displays a dialog that it is not responding and I should wait or close the app. No erros are displayed in logcat. I can't exactly tell when this happens. Sometimes after the first pan, sometimes I can move around 2-3 minutes. Same thing happens on the emulator and on the physical device.
Anyone experienced something like this? Thanks!
Or am I approaching this the wrong way? How else should I optimize the map to display about 1000 markers and polylines. (The markers have text on them, so it can't be the same Bitmap and all of the polylines can have different colors and need to be clickable, so I can't combine them into one large polyline)
EDIT. A little more info about my methods:
After all the marker positions are loaded from the internal database, I do a for-loop through all of them and based on their position and I place them to the corresponding region. Its an 2D array of lists.
My whole area is divided to 32x32 smaller rectangular areas. When I'm searching for the markers to display, I determine which region is in view and display only those markers, which are in this area.
This way I don't need to loop over all of the markers.
My methods (very simplified) look like this:
ArrayList<MarkerObject> markersToDisplay = new ArrayList<MarkerObject>();
private void findTheMarkersInBounds() {
markersToDisplay.clear();
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
int[] regionCoordinates = getRegionCoordinates(bounds); // i, j coordinates of my regions [0..31][0..31]
markersToDisplay.addAll(subdividedMarkers[regionCoordinates[0]][regionCoordinates[1]]);
}
private void drawMarkers() {
if ((markersToDisplay != null) && (markersToDisplay.size() > 0)) {
for (int i=0; i<markersToDisplay.size(); i++) {
MarkerObject mo = markersToDisplay.get(i);
LatLng position = new LatLng(mo.gpsLat, mo.gpsLon);
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title));
GroundOverlay m = mMap.addGroundOverlay(groundOverlayOptions.image(bitmapDescriptor).position(position, 75));
m.setClickable(true);
}
}
}
It is hard to help you without source code of findTheMarkersInBounds() and displayTheMarkers(), but seems, you need different approach to increase performance, for example:
improve your findTheMarkersInBounds() logic if it possible;
runfindTheMarkersInBounds() in separate thread and show not all markers in same time, but one by one (or bunch of 10..20 at one time) during findTheMarkersInBounds() searching;
improve your displayTheMarkers() if it possible, actually may be use custom drawing on canvas (like in this answer) instead of creating thousands Marker objects.
For question updates:
Small improvements (first, because they are used for main):
pass approximately max size of markersToDisplay as constructor parameter:
ArrayList<MarkerObject> markersToDisplay = new ArrayList<MarkerObject>(1000);
Instead for (int i=0; i<markersToDisplay.size(); i++) {
use for (MarkerObject mo: markersToDisplay) {
Do not create LatLng position every time, create it once and store in MarkerObject fields.
Main improvement:
This lines are the source of issues:
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title));
GroundOverlay m = mMap.addGroundOverlay(groundOverlayOptions.image(bitmapDescriptor).position(position, 75));
IMHO using Ground Overlays for thousands of markers showing is bad idea. Ground Overlay is for several "user" maps showing over default Google Map (like local plan of Park or Zoo details). Use custom drawing on canvas like on link above. But if you decide to use Ground Overlays - do not recreate them every time: create it once, store references to them in MarkerObject and reuse:
// once when marker created (just example)
mo.overlayOptions = new GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title)))
.position(mo.position, 75))
.setClickable(true);
...
// in your drawMarkers() - just add:
...
for (MarkerObject mo: markersToDisplay) {
if (mo.overlayOptions == null) {
mo.overlayOptions = createOverlayOptionsForThisMarker();
}
mMap.addGroundOverlay(mo.overlayOptions)
}
But IMHO - get rid of thousands of Ground Overlays at all - use custom drawing on canvas.
After further investigation and communication with the google maps android tech support we came to a solution. There's a bug in the GroundOverlay.setZIndex() method.
All you have to do is to update to the newest API version. The bug is not present anymore in Google Maps SDK v3.1.
At this moment it is in Beta, but the migration is pretty straightforward.
I want to create a map from some opengl code that I wrote :
In order to do that I though about taking a screen shot of the upperview of the gl screen.
Yet I cant seem to find how to do that...
any suggestions?
Similar problem has been solved in OSG example code here.
First you need to set your view such that you are looking at center from the TOP VIEW.
osg::Vec3 center = scene->getBound().center();
double radius = scene->getBound().radius();
view->getCamera()->setViewMatrixAsLookAt( center - lookDir*(radius*3.0), center, up );
view->getCamera()->setProjectionMatrixAsPerspective(
30.0f, static_cast<double>(width)/static_cast<double>(height), 1.0f, 10000.0f );
Then, you need to use some OS specific API to do similar to logic below:
osgViewer::ScreenCaptureHandler* scrn = new osgViewer::ScreenCaptureHandler();
osgViewer::ScreenCaptureHandler::WriteToFile* captureOper = new osgViewer::ScreenCaptureHandler::WriteToFile(tmpStr.m_szBuffer, "png");
scrn->setCaptureOperation(captureOper);
scrn->captureNextFrame(*_viewer);
_viewer->frame();
Of course if you are not using OSG then you need to find equivalent APIs (of library you are using) to achieve the same task.
Currently, I am adding a list of annotations to a mapview with code similar to the following:
// Add to map view
SKAnnotation annotation = new SKAnnotation(i++);
annotation.getLocation().setLongitude(result.longitude);
annotation.getLocation().setLatitude(result.latitude);
annotation.setMininumZoomLevel(1);
annotation.setAnnotationType(SKAnnotation.SK_ANNOTATION_TYPE_PURPLE);
mapView.addAnnotation(annotation, SKAnimationSettings.ANIMATION_POP_OUT);
Yet whenever I view the annotations on the map, they disappear after I zoom out to anything under zoom level 4.0. Looking at the docs for the Annotation class (as well as confirming in the code), I see that the default zoom level is set to 4, yet it seems that my call to .setMinimumZoomLevel is ignored.
Does anyone have any insight into what is happening or if this might be a known bug within the SDK?
I'm using Skobbler 2.5 on Android.
Thanks for any help in the matter!
Based off Ando's comment on the original question and referencing the documentation here, I updated the code to use the workaround he described to allow annotations to show up down to zoom level 2.
Original code:
SKAnnotation annotation = new SKAnnotation(i++);
annotation.getLocation().setLongitude(result.longitude);
annotation.getLocation().setLatitude(result.latitude);
annotation.setMininumZoomLevel(1); // Note: this does not work
annotation.setAnnotationType(SKAnnotation.SK_ANNOTATION_TYPE_PURPLE);
mapView.addAnnotation(annotation, SKAnimationSettings.ANIMATION_POP_OUT);
Updated code:
SKAnnotation annotation = new SKAnnotation(i++);
annotation.getLocation().setLongitude(result.longitude);
annotation.getLocation().setLatitude(result.latitude);
annotation.setMininumZoomLevel(2);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
if (metrics.densityDpi < DisplayMetrics.DENSITY_HIGH) {
annotation.setImagePath(SKMaps.getInstance().getMapInitSettings().
getMapResourcesPath() + "/.Common/icon_greypin#2x.png");
// set the size of the image in pixel
annotation.setImageSize(128);
} else {
annotation.setImagePath(SKMaps.getInstance().getMapInitSettings().
getMapResourcesPath()+ "/.Common/icon_greypin#3x.png");
// set the size of the image in pixels
annotation.setImageSize(256);
}
mapView.addAnnotation(annotation, SKAnimationSettings.ANIMATION_POP_OUT);
A couple things to note:
.setImagePath() and .setImageSize() are both deprecated methods in the latest SDK even though they're still referenced in the documentation above. Not sure if that means there is another alternative to displaying images via an absolute path approach, or if they're simply phasing this functionality out.
In my particular example, we're using the purple pin to display annotations, but the absolute path file name for that pin is actually called icon_greypin. It looks like the other pin image file name are named appropriately however.
Anyways, this served as a solution for my particular problem until the SDK is updated, so I'm marking it as the answer and I hope it helps someone else! Thanks to Ando for the step in the right direction!
I make online map and try to add touchable paths/tracks (I change their color when user touches them). On one map I have 6-7 PathOverlays with added onDown event handling:
private class PathOverlayExtended extends PathOverlay
{
public PathOverlayExtended(int color, Context ctx, long trackId, HistoryDetailFragment currentFragment) {
super(color, ctx);
trackIndex = trackId;
fragment = currentFragment;
}
private long trackIndex;
private HistoryDetailFragment fragment;
#Override
public boolean onDown(final MotionEvent event, final MapView mapView) {
fragment.onRoadClicked(trackIndex);
return super.onDown(event,mapView);
}
}
Then I touch one path on screen, it catches event and proceeds through every path. Important: it always starts from the same path (the one added to the Olerlays at the end).
When I replace "return super.onDown(event,mapView)" with "return true", only the last path catches the event and this is not the one I touch (but the one added to the Overlays at the end).
How to check/distinct which Path I touched?
I implemented something similar for detecting a touch on filled Polygons.
It's using Android Regions.
The principle is to "put" the Path that has been drawn in a "Region":
region.setPath(mPath, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
Then you check if the touched point is in this region with:
region.contains(point.x, point.y);
No idea how this "contains" method is implemented, but it works, and seems quite efficient. Magic. I imagine it should also work for polylines.
You can look at the full code here:
http://code.google.com/p/osmbonuspack/source/browse/trunk/OSMBonusPack/src/org/osmdroid/bonuspack/overlays/Polygon.java
I couldn't find fine solution to my problem, so in the end, I decided to do it in the following way.
Firstly, I made my own PathOverlayExtended class which iherits PathOverlay. Then I added some variables - bounds of the path region (maximum and minimum latitude and longitude).
Secondly, I checkced if tap coordinates fits in theese bounds. That way, I get only those paths which can be understanded as related to my tap.
In the end, I checked distances from tap coordinates to every line segment and chose the smallest one. That's it.
I used viesturz's answer, which helped me very much:
https://code.google.com/p/osmdroid/issues/detail?id=36
Thanks for all answers!
I have a path() which is created from a json object. I pass the JSON object to my overlay object in my constructor.
Objects that extend Overlay always call draw() any time the map is moved, rendered, touched, anything. Even if your overlay isn't visible or nearby or anything.
I want to generate my path object in a for loop, actually a nested loop as the json object contains nested json arrays. This has a high theoretical computation time, so I don't want to do it in the draw() method. Instead I tried to do the logic in my constructor, to make the path() file, and then call that path file only once in the draw() method where required here canvas.drawPath(path, mPaint);
Unfortunately, when I create my path in the constructor, it does not pan with the map. But when I create it, the exact same code, in the draw() method, it does have the desired functionality: a path that fences a portion of the map.
The problem is that the draw() method will then call my double for loop over and over again, and the performance hit is obvious and debilitating. Even putting the loop in new Thread() within draw() does not help performance. Running it in the constructor would be ideal, but then the path does not pan with the map.
similarly putting a private boolean flip within the draw() method, to make the desired code only run, does not work either. the path will not appear on the map unless it is being constantly redrawn, which is too arduous of a task.
The problem with other answers on this site regarding this issue is that people are making squares, circles and images, which only require one call within draw(), not a loop to generate the path.
suggestions? something about the draw() method helps overlays stick to the map, how can I run my for loop only once
A few considerations for you:
you should not be overriding the draw() method. The Overlay class already takes care of the whole panning operation for you. Read the docs that you'll find it.
the new API is a few thousand times easier to use. With a simple code such as:
PolylineOptions p = new new PolylineOptions().width(3).color(0xFFEE8888);
for( //... first for .... ){
for( // .... how many you'll need to nest... ){
// compute your JSON and get lat/long pair
p.add(new LatLng(lat, lon));
}
}
getMap().addPolyline(p);
and let the map class deal with the panning and zooming.
Although you have already accept an answer, here goes the approach I use for this. In the example code bellow, I´m using the old MapView, but the concept should work in any version. I'm also using it with mapforges (with minimum adjustments).
Concept
Build you path on first draw() call, and record position of point(0)
on next draw() if point(0) position changed, map has moved. Offset ´path` by same value before draw it.
If zoom changed, recreate the pathobject.
Performance
With a 10.000 points path in a medium range device it takes about 2 ms per cached draw() call. When path needs to be rebuild (when zoom changed) it takes about 80 ms.
Of cource you can also cache the different path zoom levels, trading a litle more performance by some more memory.
Example Code
The draw()method only checks if there is a zoom change (if so ask the path to be rebuild) and if map has moved (if so offset path) and finally draws the path.
#Override
public void draw(Canvas canvas, MapView mapview, boolean shadow) {
super.draw(canvas, mapview, shadow);
if(shadow) return;
if(mp.getPoints() == null || mp.getPoints().size() < 2) return;
Projection projection = mapview.getProjection();
int lonSpanNew = projection.fromPixels(0,mapview.getHeight()/2).getLongitudeE6() -
projection.fromPixels(mapview.getWidth(),mapview.getHeight()/2).getLongitudeE6();
if(lonSpanNew != pathInitialLonSpan)
pathBuild();
else{ //check if path need to be offset
projection.toPixels(mp.getPoints().get(0), p1);
if(p1.x != pathInitialPoint.x || p1.y != pathInitialPoint.y){
path.offset(p1.x - pathInitialPoint.x, p1.y - pathInitialPoint.y);
pathInitialPoint.x = p1.x;
pathInitialPoint.y = p1.y;
}
}
canvas.drawPath(path, paint);
}
The path has to be built every time zoom changes. The zoom change detection is done using pathInitialLonSpan as getZoomLevel() is not shyncronous with map zoom animation.
private void pathBuild(){
path.rewind();
if(mp.getPoints() == null || mp.getPoints().size() < 2) return;
Projection projection = mapView.getProjection();
pathInitialLonSpan = projection.fromPixels(0,mapView.getHeight()/2).getLongitudeE6() -
projection.fromPixels(mapView.getWidth(),mapView.getHeight()/2).getLongitudeE6();
projection.toPixels(mp.getPoints().get(0), pathInitialPoint);
path.moveTo(pathInitialPoint.x,pathInitialPoint.y);
for(int i=1; i<mp.getPoints().size(); i++){
projection.toPixels(mp.getPoints().get(i), p1);
int distance2 = (pPrev.x - p1.x) * (pPrev.x - p1.x) + (pPrev.y - p1.y) * (pPrev.y - p1.y);
if(distance2 > 9){
path.lineTo(p1.x,p1.y);
pPrev.set(p1.x, p1.y);
}
}
Regards.