Android Google Maps API中的数千个多边形是主线程的负担
我正在开发一个应用程序,从一个文本文件中从给定的GPS位置绘制运动路径。 到目前为止,我已经成功地加载了数据并绘制了路径。 我的解决方案是,路径必须绘制为矩形,因为它包含数据(例如颜色和宽度)并可单击。一条线做不到这一点。此外,当它转弯时,这些矩形之间也有一个三角形间隙,所以我用三角形填充它。路径必须是双倍的,因为我有另一个数据要显示在第二层上。这意味着,要为较长的路径绘制大量多边形 我在15公里的旅程中尝试过这个,它需要1000行gps数据。 当我解析文件时,它为两个层总共绘制了5000个多边形 我的问题是,当它绘制多达1000个多边形的形状时,应用程序会变得迟钝,没有响应。如果我让线程休眠1秒钟,它看起来就可以了。但是速度越快,它就没有反应了 我一直在网上寻找这个解决方案,并能找到它。 仅供参考,我已经创建了另一个线程来处理文本文件。 我还缩小了问题的范围,让应用程序在不绘制多边形的情况下进行处理,并且处理过程是平滑的。 我已经读到没有其他方法来处理主线程之外的多边形 更新: 我使用Asynctask后台从文本文件中读取一行,将其解析为包含纬度、经度、值1、值2的数组。 然后大量的计算就在那里发生了。 完成每一行后,我将对象发送到onProgressUpdate,以使用标记、多段线和形状更新UI线程 这是我的任务Android Google Maps API中的数千个多边形是主线程的负担,android,api,maps,polygons,Android,Api,Maps,Polygons,我正在开发一个应用程序,从一个文本文件中从给定的GPS位置绘制运动路径。 到目前为止,我已经成功地加载了数据并绘制了路径。 我的解决方案是,路径必须绘制为矩形,因为它包含数据(例如颜色和宽度)并可单击。一条线做不到这一点。此外,当它转弯时,这些矩形之间也有一个三角形间隙,所以我用三角形填充它。路径必须是双倍的,因为我有另一个数据要显示在第二层上。这意味着,要为较长的路径绘制大量多边形 我在15公里的旅程中尝试过这个,它需要1000行gps数据。 当我解析文件时,它为两个层总共绘制了5000个多边
private class DrawPathAsync extends AsyncTask<File, Object, Void>
{
FileInputStream is;
BufferedReader reader;
@Override
protected Void doInBackground(File... params) {
File sFile = params[0];
Integer count;
String line = "";
double radius = 8; //8 meter
double distance;
double Heading_y;
int kaler, gkaler;
double apprate, gi;
if (sFile.exists()) {
try {
is = new FileInputStream(sFile);
reader = new BufferedReader(new InputStreamReader(is));
reader.readLine(); // this will read the first line
while ((line = reader.readLine()) != null) {
String[] valuesArray = line.split("\\s*,\\s*");
Float bearing = (float) 0;
Double lat = Double.parseDouble(valuesArray[1]);
Double lng = Double.parseDouble(valuesArray[2]);
LatLng latlng = new LatLng(lat, lng);
LatLng center = latlng;
apprate = Double.parseDouble(valuesArray[3]);
if (apprate >=0 && apprate < 80) {
kaler = appcolor1;
} else if (apprate >=80 && apprate < 100) {
kaler = appcolor2;
} else if (apprate >=100 && apprate < 120) {
kaler = appcolor3;
} else if (apprate >=120 && apprate < 140) {
kaler = appcolor4;
} else if (apprate >=140 && apprate < 160) {
kaler = appcolor5;
} else if (apprate >=160 && apprate <= 200) {
kaler = appcolor6;
} else {
kaler = appcolor7;
}
if (points.size()== 2) {
points.remove(0);
points.add(latlng);
} else {
points.add(latlng);
}
//recheck
if (points.size() == 2) {
distance = SphericalUtil.computeDistanceBetween(center, points.get(0));
LatLng pt1 = points.get(0);
LatLng pt2 = latlng;
bearing = (float) SphericalUtil.computeHeading(pt1, pt2);
if (bearing < 0) {
bearing = bearing + 360;
}
LatLng x = SphericalUtil.computeOffset(center, radius, bearing - 90);
LatLng y = SphericalUtil.computeOffset(center, radius, bearing + 90);
LatLng a = SphericalUtil.computeOffset(x, distance, bearing + 180);
LatLng b = SphericalUtil.computeOffset(y, distance, bearing + 180);
MarkerPoint mp = new MarkerPoint();
mp.latlng = latlng;
mp.bearing = bearing;
Rect rc = new Rect();
rc.a = a;
rc.b = b;
rc.x = x;
rc.y = y;
rc.kaler = kaler;
rc.pt2 = pt2;
publishProgress(mp, rc);
}
Thread.sleep(50);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(Object... values) {
MarkerPoint mp = (MarkerPoint) values[0];
Rect rc = (Rect) values[1];
LatLng latlng = mp.latlng;
BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.mipmap.pointer);
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(latlng);
markerOptions.icon(icon);
markerOptions.rotation(mp.bearing);
mMap.moveCamera(CameraUpdateFactory.newLatLng(latlng));
marker1.remove();
marker1 = mMap.addMarker(markerOptions);
if (points.size() > 1) {
path = mMap.addPolyline(new PolylineOptions().add(points.get(0)).add(points.get(1)).color(Color.BLUE).width(5));
lines.add(path);
}
PolygonOptions options = new PolygonOptions()
.fillColor(rc.kaler)
.strokeWidth(0)
.strokeColor(Color.TRANSPARENT);
options.add(rc.x);
options.add(rc.y);
options.add(rc.b);
options.add(rc.a);
rect = mMap.addPolygon(options);
rects.add(rect);
if (tripoints.size() == 3) {
tripoints.add(rc.a);
tripoints.add(rc.b);
} else {
tripoints.add(rc.pt2);
tripoints.add(rc.x);
tripoints.add(rc.y);
}
//check
//round 2, if triponts = 5 create triangle
if (tripoints.size() == 5) {
PolygonOptions options2 = new PolygonOptions()
.fillColor(rc.kaler)
.strokeWidth(0)
.strokeColor(Color.TRANSPARENT);
options2.add(tripoints.get(0));
options2.add(tripoints.get(2));
options2.add(tripoints.get(4));
t1 = mMap.addPolygon(options2);
tris.add(t1);
PolygonOptions options3 = new PolygonOptions()
.fillColor(rc.kaler)
.strokeWidth(0)
.strokeColor(Color.TRANSPARENT);
options3.add(tripoints.get(0));
options3.add(tripoints.get(1));
options3.add(tripoints.get(3));
t2 = mMap.addPolygon(options3);
tris.add(t2);
tripoints.clear();
tripoints.add(rc.pt2);
tripoints.add(rc.x);
tripoints.add(rc.y);
}
}
@Override
protected void onPostExecute(Void result) {
}
}
私有类DrawPathAsync扩展了AsyncTask
{
FileInputStream是;
缓冲读取器;
@凌驾
受保护的Void doInBackground(文件…参数){
文件sFile=params[0];
整数计数;
字符串行=”;
双半径=8;//8米
双倍距离;
双标题;
内特卡勒,卡勒;
双贴壁,gi;
if(sFile.exists()){
试一试{
is=新文件输入流(sFile);
reader=新的BufferedReader(新的InputStreamReader(is));
reader.readLine();//这将读取第一行
而((line=reader.readLine())!=null){
字符串[]值ray=line.split(“\\s*,\\s*”);
浮动轴承=(浮动)0;
Double lat=Double.parseDouble(valuesArray[1]);
Double lng=Double.parseDouble(valuesArray[2]);
LatLng LatLng=新LatLng(lat,lng);
LatLng中心=LatLng;
apprate=Double.parseDouble(valuesArray[3]);
如果(平均值>=0&&A平均值<80){
kaler=appcolor1;
}如果(apprate>=80&&apprate<100){
kaler=appcolor2;
}如果(apprate>=100&&apprate<120){
kaler=appcolor3;
}如果(apprate>=120&&apprate<140){
kaler=4;
}如果(apprate>=140&&apprate<160){
kaler=5;
}否则如果(通知>=160和通知1){
path=mMap.addPolyline(新的PolylineOptions().add(points.get(0)).add(points.get(1)).color(color.BLUE).width(5));
行。添加(路径);
}
polygonooptions选项=新polygonooptions()
.fillColor(rc.kaler)
.冲程宽度(0)
.strokeColor(颜色.透明);
选项。添加(rc.x);
选项。添加(rc.y);
增加(rc.b);
增加(rc.a);
rect=mMap.addPolygon(选项);
rects.add(rect);
如果(三点大小()==3){
加上三点(rc.a);
添加三点(rc.b);
}否则{
添加三点(rc.pt2);
三点相加(rc.x);
三点相加(rc.y);
}
//检查
//第2轮,如果triponts=5,则创建三角形
如果(三点大小()==5){
PolygonOptions options2=新PolygonOptions()
.fillColor(rc.kaler)
.冲程宽度(0)
.strokeColor(颜色.透明);
选项2.添加(三点。获取(0));
选项2.添加(三点获取(2));
选项2.添加(三点。获取(4));
t1=mMap.addPolygon(选项2);
三加一(t1);
polygonooptions选项3=新的polygonooptions()
.fillColor(rc.kaler)
.冲程宽度(0)
.strokeColor(颜色.透明);
选项3.添加(三点。获取(0));
选项3.添加(三点获取(1));
选项3.添加(三点获取(3));
t2=mMap.addPolygon(选项3);
三加二(t2);
三点清除();
添加三点(rc.pt2);
三点相加(rc.x);
三点相加(rc.y);
}
}
@凌驾
受保护的void onPostExecute(void结果){
}
}
希望有人能分享一些技巧和解决方案。我已经有一段时间遇到了相同的问题,经过广泛的研究,我已经将这个问题隔离到Google Maps SDK本身。在我的案例中,一个有效的解决方案是使用A并在正确的地理坐标处绘制自定义点/线/多边形。我找到了这个库,它调用y这样做: 通过一些调整,我能够创建一个渲染线程,该线程将绘图/过滤部分从主UI线程中移除,并且仅在完成时更新地面覆盖。此外,我添加了一个简单但快速的算法来搜索当前可见的形状。通过这种方法,您可以获得一些额外的好处:
- 仅当前视图中的对象
/** * Singleton class that handle polygons showing on map. * This class is singleton because one instance in enough and operation that this class do it is * UI Thread operation so must call in ui thread. * This class only show polygons that are in map viewport and handle shapes. * * @version 1.3 */ public class PolygonRenderer implements Runnable { // Update interval in millisecond private final static long UPDATE_INTERVAL = 500; // Single instance of this class private static PolygonRenderer instance; private Thread thread; // Keep last update time in millisecond private long lastUpdate; // Used to stop thread private boolean stopFlag; // Used to pause thread private boolean pauseFlag; private final Object pauseLock; private LatLngBounds bounds; private float zoom; private List<PolygonWrapper> polygons; private GoogleMap map; private PolygonRenderer() { this.stopFlag = false; this.pauseFlag = false; this.pauseLock = new Object(); } public static synchronized PolygonRenderer getInstance() { if (instance == null) instance = new PolygonRenderer(); return instance; } /** * Stop polygons refreshing on map */ public synchronized void stop() { stopFlag = true; if (thread != null) { thread.interrupt(); thread = null; } } /** * Pause running thread */ public synchronized void onPause() { pauseFlag = true; } /** * Resume thread running */ public synchronized void onResume() { pauseFlag = false; pauseLock.notifyAll(); } /** * Create new polygon wrapper and add it to polygons list. * * @param activity context activity of map * @param id id of polygon * @param geometry data of polygon such as points * @param polygons list af all polygons * @see PolygonWrapper for more info about polygon wrapper. */ public synchronized void createPolygons(Activity activity, String id, String geometry, List<PolygonWrapper> polygons) { try { // Read polygon data (coordinates) WKTReader wkt = new WKTReader(); if (geometry.contains("MULTIPOLYGON")) { org.locationtech.jts.geom.MultiPolygon multiPolygon = (org.locationtech.jts.geom.MultiPolygon) wkt.read(geometry); // Gets each polygon of a multipolygon for(int i = 0; i < multiPolygon.getNumGeometries(); i++) { org.locationtech.jts.geom.Polygon polygon = (org.locationtech.jts.geom.Polygon) multiPolygon.getGeometryN(i); // Create polygon options PolygonOptions options = new PolygonOptions(); options.strokeWidth(8); options.clickable(true); // Gets each polygon outer coordinates ArrayList<LatLng> outer = new ArrayList<>(); Coordinate[] outerCoordinates = polygon.getExteriorRing().getCoordinates(); for (Coordinate outerCoordinate : outerCoordinates) outer.add(new LatLng(outerCoordinate.y, outerCoordinate.x)); options.addAll(outer); // Getting each polygon interior coordinates (hole) if they exist if(polygon.getNumInteriorRing() > 0){ for(int j = 0; j < polygon.getNumInteriorRing(); j++){ ArrayList<LatLng> inner = new ArrayList<>(); Coordinate[] innerCoordinates = polygon.getInteriorRingN(j).getCoordinates(); for (Coordinate innerCoordinate : innerCoordinates) inner.add(new LatLng(innerCoordinate.y, innerCoordinate.x)); options.addHole(inner); } } // Create and add polygon wrapper polygons.add(new PolygonWrapper(activity, id, options, PolygonWrapper.Behavior.PART_SHOWING)); } } else { org.locationtech.jts.geom.Polygon polygon = (org.locationtech.jts.geom.Polygon) wkt.read(geometry); // Create polygon options PolygonOptions options = new PolygonOptions(); options.strokeWidth(8); options.clickable(true); // Gets polygon outer coordinates ArrayList<LatLng> outer = new ArrayList<>(); Coordinate[] outerCoordinates = polygon.getExteriorRing().getCoordinates(); for (Coordinate outerCoordinate : outerCoordinates) outer.add(new LatLng(outerCoordinate.y, outerCoordinate.x)); options.addAll(outer); // Getting polygon interior coordinates (hole) if they exist if(polygon.getNumInteriorRing() > 0){ for(int j = 0; j < polygon.getNumInteriorRing(); j++){ ArrayList<LatLng> inner = new ArrayList<>(); Coordinate[] innerCoordinates = polygon.getInteriorRingN(j).getCoordinates(); for (Coordinate innerCoordinate : innerCoordinates) inner.add(new LatLng(innerCoordinate.y, innerCoordinate.x)); options.addHole(inner); } } // Create and add polygon wrapper polygons.add(new PolygonWrapper(activity, id, options, PolygonWrapper.Behavior.PART_SHOWING)); } } catch (org.locationtech.jts.io.ParseException e) { e.printStackTrace(); } } /** * Update visible polygons on map based on locating in map viewport. * Also map zoom is important in showing polygons, because of polygons count on map in low zooms. * We remove some very small polygons in low zoom. * This operations is require to prevent app not responding when polygons are too many. * Polygons that are not in viewport will be remove from map. * This method must be call in onCameraMove event to get map new bounds and zoom. * Operations will be done in new thread. Thread change polygons visibility continuously. * * @param map map that polygons must be shown on it * @param polygons list of all polygons */ public synchronized void updatePolygons(GoogleMap map, List<PolygonWrapper> polygons) { // Limit update interval long time = SystemClock.elapsedRealtime(); if (time - lastUpdate < UPDATE_INTERVAL) return; // Update last update time lastUpdate = time; // Bounds and zoom should get in ui thread. so we get them out of thread this.bounds = map.getProjection().getVisibleRegion().latLngBounds; this.zoom = map.getCameraPosition().zoom; // We have only one thread and if it is created so we don't need recreate it if (thread != null) return; // Create and run thread this.map = map; this.polygons = polygons; this.stopFlag = false; thread = new Thread(this); thread.start(); } @Override public void run() { while (!stopFlag) { // Call by try-catch to prevent unwanted exception and thread stopping try { // Pause implementation synchronized (pauseLock) { while (pauseFlag) { try { pauseLock.wait(); } catch (InterruptedException ignored) { } } } // Update visible polygons on map based on map viewport for (PolygonWrapper polygon : polygons) { // Remove polygons that are invisible in given zoom from map if (isVisibleWithZoom(polygon, zoom)) { if (polygon.isAddedToMap()) { polygon.removeFromMap(); sleep(); } continue; } // Hide out of map viewport polygons if (polygon.isWithin(bounds) && !polygon.isAddedToMap()) { polygon.addToMap(map); sleep(); } else if (!polygon.isWithin(bounds)) { polygon.removeFromMap(); sleep(); } } } catch (Exception ignored) { } } } private boolean isVisibleWithZoom(PolygonWrapper polygon, float zoom) { // Compute area of polygon double area = SphericalUtil.computeArea(polygon.getOptions().getPoints()); return ( (zoom <= 11 && area <= 1000) || // Don't show polygons with area <= 1000 when zoom is <= 11 (map bounds has great area) (zoom > 11 && zoom <= 12 && area <= 500) || // Don't show polygons with area <= 500 when zoom is between 11 and 12 (zoom > 12 && zoom <= 13 && area <= 250) || // Don't show polygons with area <= 250 when zoom is between 12 and 13 (zoom > 13 && zoom <= 13.5 && area <= 200) || // Don't show polygons with area <= 200 when zoom is between 13 and 13.5 (zoom > 13.5 && zoom <= 14 && area <= 150) || // Don't show polygons with area <= 150 when zoom is between 13.5 and 14 (map bounds has small area) (zoom > 14 && zoom <= 14.5 && area <= 100) // Don't show polygons with area <= 100 when zoom is between 14 and 14.5 (map bounds has small area) ); } /** * Thread sleep allow ui thread to show views and doesn't hang up. * Call this method everywhere ui thread action is performing. */ private void sleep() throws InterruptedException { Thread.sleep(8); } }
/** * Wrapper class for polygon. * See https://stackoverflow.com/questions/36439031/determine-if-polygon-is-within-map-bounds for more info. * * @version 1.1 * */ public class PolygonWrapper { private final String id; private final Behavior behavior; private final LatLng northWest, northEast, southEast, southWest; private final Activity activity; private Polygon polygon; private PolygonOptions options; public void addToMap(GoogleMap map) { activity.runOnUiThread(() -> { if (isAddedToMap()) removeFromMap(); polygon = map.addPolygon(options); }); } public void removeFromMap() { activity.runOnUiThread(() -> { if (isAddedToMap()) { polygon.remove(); polygon = null; } }); } public PolygonWrapper(Activity activity, String id, PolygonOptions options, Behavior behavior) { this.activity = activity; this.id = id; this.options = options; this.behavior = behavior; Double north = null, west = null, south = null, east = null; for (LatLng latLng : options.getPoints()) { if (north == null || latLng.latitude > north) north = latLng.latitude; if (west == null || latLng.longitude < west) west = latLng.longitude; if (south == null || latLng.latitude < south) south = latLng.latitude; if (east == null || latLng.longitude > east) east = latLng.longitude; } northWest = new LatLng(north, west); northEast = new LatLng(north, east); southEast = new LatLng(south, east); southWest = new LatLng(south, west); } public String getId() { return id; } public PolygonOptions getOptions() { return options; } public Polygon getPolygon() { return polygon; } public PolygonOptions buildBordersRectPolygonOptions() { final PolygonOptions rvalue = new PolygonOptions(); rvalue.add(northWest); rvalue.add(northEast); rvalue.add(southEast); rvalue.add(southWest); rvalue.add(northWest); rvalue.fillColor(0x6A00FFFF); rvalue.strokeColor(0x6AFF0000); rvalue.strokeWidth(1f); return rvalue; } public boolean isWithin(LatLngBounds bounds) { boolean within = false; switch (behavior) { case FULL_SHOWING: if (bounds.contains(northWest) && bounds.contains(southEast)) within = true; break; case PART_SHOWING: if (bounds.contains(northWest) || bounds.contains(southEast) || bounds.contains(northEast) || bounds.contains(southWest)) { within = true; } else if (northEast.latitude > bounds.southwest.latitude && northEast.longitude > bounds.southwest.longitude && southWest.latitude < bounds.northeast.latitude && southWest.longitude < bounds.northeast.longitude) { within = true; } break; } return within; } public boolean isAddedToMap() { return polygon != null; } public enum Behavior { FULL_SHOWING, PART_SHOWING } }
map.setOnCameraMoveListener(() -> { // Update visible polygons on map PolygonRenderer.getInstance().updatePolygons(map, polygons); });
@Override protected void onStop() { // Stop polygons renderer class on activity stop PolygonRenderer.getInstance().stop(); super.onStop(); }