Android map performance with > 800 overlays of KML data
- by span
I have some a shape file which I have converted to a KML file that I wish to read coordinates from and then draw paths between the coordinates on a MapView. With the help of this great post: How to draw a path on a map using kml file? I have been able to read the the KML into an ArrayList of "Placemarks". This great blog post then showed how to take a list of GeoPoints and draw a path: http://djsolid.net/blog/android---draw-a-path-array-of-points-in-mapview
The example in the above post only draws one path between some points however and since I have many more paths than that I am running into some performance problems. I'm currently adding a new RouteOverlay for each of the separate paths. This results in me having over 800 overlays when they have all been added. This has a performance hit and I would love some input on what I can do to improve it.
Here are some options I have considered:
Try to add all the points to a List which then can be passed into a class that will extend Overlay. In that new class perhaps it would be possible to add and draw the paths in a single Overlay layer? I'm not sure on how to implement this though since the paths are not always intersecting and they have different start and end points. At the moment I'm adding each path which has several points to it's own list and then I add that to an Overlay. That results in over 700 overlays...
Simplify the KML or SHP. Instead of having over 700 different paths, perhaps there is someway to merge them into perhaps 100 paths or less? Since alot of paths are intersected at some point it should be possible to modify the original SHP file so that it merges all intersections. Since I have never worked with these kinds of files before I have not been able to find a way to do this in GQIS. If someone knows how to do this I would love for some input on that. Here is a link to the group of shape files if you are interested:
http://danielkvist.net/cprg_bef_cbana_polyline.shp
http://danielkvist.net/cprg_bef_cbana_polyline.shx
http://danielkvist.net/cprg_bef_cbana_polyline.dbf
http://danielkvist.net/cprg_bef_cbana_polyline.prj
Anyway, here is the code I'm using to add the Overlays. Many thanks in advance.
RoutePathOverlay.java
package net.danielkvist;
import java.util.List;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;
public class RoutePathOverlay extends Overlay
{
private int _pathColor;
private final List<GeoPoint> _points;
private boolean _drawStartEnd;
public RoutePathOverlay(List<GeoPoint> points)
{
this(points, Color.RED, false);
}
public RoutePathOverlay(List<GeoPoint> points, int pathColor, boolean drawStartEnd)
{
_points = points;
_pathColor = pathColor;
_drawStartEnd = drawStartEnd;
}
private void drawOval(Canvas canvas, Paint paint, Point point)
{
Paint ovalPaint = new Paint(paint);
ovalPaint.setStyle(Paint.Style.FILL_AND_STROKE);
ovalPaint.setStrokeWidth(2);
int _radius = 6;
RectF oval = new RectF(point.x - _radius, point.y - _radius, point.x + _radius, point.y + _radius);
canvas.drawOval(oval, ovalPaint);
}
public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when)
{
Projection projection = mapView.getProjection();
if (shadow == false && _points != null)
{
Point startPoint = null, endPoint = null;
Path path = new Path();
// We are creating the path
for (int i = 0; i < _points.size(); i++)
{
GeoPoint gPointA = _points.get(i);
Point pointA = new Point();
projection.toPixels(gPointA, pointA);
if (i == 0)
{ // This is the start point
startPoint = pointA;
path.moveTo(pointA.x, pointA.y);
}
else
{
if (i == _points.size() - 1)// This is the end point
endPoint = pointA;
path.lineTo(pointA.x, pointA.y);
}
}
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(_pathColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setAlpha(90);
if (getDrawStartEnd())
{
if (startPoint != null)
{
drawOval(canvas, paint, startPoint);
}
if (endPoint != null)
{
drawOval(canvas, paint, endPoint);
}
}
if (!path.isEmpty())
canvas.drawPath(path, paint);
}
return super.draw(canvas, mapView, shadow, when);
}
public boolean getDrawStartEnd()
{
return _drawStartEnd;
}
public void setDrawStartEnd(boolean markStartEnd)
{
_drawStartEnd = markStartEnd;
}
}
MyMapActivity
package net.danielkvist;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
public class MyMapActivity extends MapActivity
{
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MapView mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);
String url = "http://danielkvist.net/cprg_bef_cbana_polyline_simp1600.kml";
NavigationDataSet set = MapService.getNavigationDataSet(url);
drawPath(set, Color.parseColor("#6C8715"), mapView);
}
/**
* Does the actual drawing of the route, based on the geo points provided in
* the nav set
*
* @param navSet
* Navigation set bean that holds the route information, incl.
* geo pos
* @param color
* Color in which to draw the lines
* @param mMapView01
* Map view to draw onto
*/
public void drawPath(NavigationDataSet navSet, int color, MapView mMapView01)
{
ArrayList<GeoPoint> geoPoints = new ArrayList<GeoPoint>();
Collection overlaysToAddAgain = new ArrayList();
for (Iterator iter = mMapView01.getOverlays().iterator(); iter.hasNext();)
{
Object o = iter.next();
Log.d(BikeApp.APP, "overlay type: " + o.getClass().getName());
if (!RouteOverlay.class.getName().equals(o.getClass().getName()))
{
overlaysToAddAgain.add(o);
}
}
mMapView01.getOverlays().clear();
mMapView01.getOverlays().addAll(overlaysToAddAgain);
int totalNumberOfOverlaysAdded = 0;
for(Placemark placemark : navSet.getPlacemarks())
{
String path = placemark.getCoordinates();
if (path != null && path.trim().length() > 0)
{
String[] pairs = path.trim().split(" ");
String[] lngLat = pairs[0].split(","); // lngLat[0]=longitude
// lngLat[1]=latitude
// lngLat[2]=height
try
{
if(lngLat.length > 1 && !lngLat[0].equals("") && !lngLat[1].equals(""))
{
GeoPoint startGP = new GeoPoint(
(int) (Double.parseDouble(lngLat[1]) * 1E6),
(int) (Double.parseDouble(lngLat[0]) * 1E6));
GeoPoint gp1;
GeoPoint gp2 = startGP;
geoPoints = new ArrayList<GeoPoint>();
geoPoints.add(startGP);
for (int i = 1; i < pairs.length; i++)
{
lngLat = pairs[i].split(",");
gp1 = gp2;
if (lngLat.length >= 2 && gp1.getLatitudeE6() > 0
&& gp1.getLongitudeE6() > 0
&& gp2.getLatitudeE6() > 0
&& gp2.getLongitudeE6() > 0)
{
// for GeoPoint, first:latitude, second:longitude
gp2 = new GeoPoint(
(int) (Double.parseDouble(lngLat[1]) * 1E6),
(int) (Double.parseDouble(lngLat[0]) * 1E6));
if (gp2.getLatitudeE6() != 22200000)
{
geoPoints.add(gp2);
}
}
}
totalNumberOfOverlaysAdded++;
mMapView01.getOverlays().add(new RoutePathOverlay(geoPoints));
}
}
catch (NumberFormatException e)
{
Log.e(BikeApp.APP, "Cannot draw route.", e);
}
}
}
Log.d(BikeApp.APP, "Total overlays: " + totalNumberOfOverlaysAdded);
mMapView01.setEnabled(true);
}
@Override
protected boolean isRouteDisplayed()
{
// TODO Auto-generated method stub
return false;
}
}
Edit: There are of course some more files I'm using but that I have not posted. You can download the complete Eclipse project here: http://danielkvist.net/se.zip