我正在开发一个应用程序,用于从文本文件中的给定GPS位置绘制移动路径。 到目前为止,我已成功加载数据并绘制路径。 我的解决方案是,路径必须绘制为矩形,因为它包含数据(例如颜色和宽度)和可点击。一条线无法做到这一点。此外,这些矩形在转弯时也存在三角形间隙,所以我用三角形填充它。并且路径必须是双倍的,因为我有另一个数据要显示在第二层上。这意味着,要为更长的路径绘制许多多边形。
我在15公里的路程上试过这个,需要1000行gps数据。 当我解析文件时,它为两个图层总共绘制了多达5000个多边形。
我的问题是,当它绘制多达1000个多边形的形状时,应用程序变得迟钝,没有响应。如果我让线程休眠1秒,似乎没问题。但速度越来越快,它没有响应。
我一直在网上阅读这个解决方案并且可以找到它。 Fyi,我已经创建了另一个线程来处理文本文件。 我还通过让app在不绘制多边形的情况下完成整个过程来缩小问题范围,并且过程非常顺利。 我已经读过没有其他方法可以处理主线程之外的多边形。
更新: 我正在使用Asynctask background从textfile中读取一行,将其解析为包含Latitude,Longitude,Value1,Value2的数组。 然后在那里进行大量计算。 完成每一行后,我将对象发送到onProgressUpdate以更新带有标记,折线和形状的UI线程。
这是我的Asynctask
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) {
}
}
希望有人可以分享一些提示和解决方案。
答案 0 :(得分:2)
一段时间以来,我一直遇到相同的问题,经过广泛的研究,我将问题隔离到Google Maps SDK本身。在我的情况下,一种有效的解决方案是使用GroundOverlay并在正确的地理坐标处绘制自定义点/线/多边形。我发现这个库基本上是这样做的:
https://github.com/antoniocarlon/richmaps
通过一些调整,我能够创建一个渲染线程,该线程将绘图/过滤部分从主UI线程中删除,并仅在完成时更新GroundOverlay。另外,我添加了一个简单但快速的算法来搜索当前可见的形状。通过这种方法,您可以获得一些好处:
答案 1 :(得分:0)
我知道现在回答这个问题为时已晚,但我写出了我的解决方案。我希望这会有所帮助。 我创建了一个“PolygonRenderer”类,它不断更新地图上的多边形。 我还创建了一个“PolygonWrapper”类,其中包含有关多边形的更多信息,例如是否将多边形添加到地图中,以及更多功能,如 removeFromMap() 和 addToMap()
实际上我使多边形延迟加载,并且只会显示视口中的多边形而其他多边形将被删除。通过改变地图相机的位置,可见的多边形将会改变。
PolygonRenderer 类:
/**
* 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);
}
}
和 PolygonWrapper 类:
/**
* 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();
}
在这段代码中,我也通过缩放更改来更改多边形的可见性。但如果多边形具有比例并且适用于不同的缩放级别,则此选项会更好。
我还使用了 jts-core 来获取多边形数据。
通过这种方式,我在地图上处理了大约 2000-4000 个多边形。
答案 2 :(得分:0)
与 Polygons 相比,GroundOverlay 显示出显着的性能提升,但在生产方面仍然滞后。
TileOverlay 是正确的解决方案。很难找到,但是一旦你到了那里,the homepage of TileOverlay docs 提到的很清楚
<块引用>当您想将大量图像添加到 地图,通常覆盖较大的地理区域。相比之下,地 当您希望将单个图像固定在某个区域时,叠加层很有用 地图。
虽然the homepage of GroundOverlays documentations说同样的话
<块引用>如果您想添加涵盖大部分内容的广泛图像 地图,您应该考虑使用 Tile 叠加层。
令人遗憾的是,Maps Polygon Android 文档没有提及此类建议,因此这是一个鸡和蛋的问题。请注意,数以千计的多边形在 iOS 上运行良好!