简体   繁体   English

将大型 GeoJson 添加到 Android 上的 GoogleMap 的有效方法

[英]Efficient way to add large GeoJson to GoogleMap on Android

I have a temperature map for some area with 3 km grid.我有一个 3 公里网格区域的温度图。 Ie I have about few thousands of polygons with color.即我有大约几千个带颜色的多边形。

I'm trying to show them on Google Map on android in my kotlin application.我试图在我的 kotlin 应用程序中在 android 上的谷歌地图上显示它们。

The issue is that I need to add GeoJson layer in UI thread and it takes 8-15 seconds.问题是我需要在 UI 线程中添加 GeoJson 层,这需要 8-15 秒。 Ie all this time the application is frozen即所有这些时间应用程序都被冻结

There are a lot of old answers on StackOverflow about this issue but they all are not relevant for me: StackOverflow 上有很多关于这个问题的旧答案,但它们都与我无关:

  1. They propose to decrease the json - I already create clusters from nearby squares with the same value他们建议减少 json - 我已经从附近的正方形创建了具有相同值的集群
  2. They propose to use MapView and overlays - but in the latest google map API there is no MapView and ability to add overlays in background thread他们建议使用 MapView 和叠加层 - 但在最新的谷歌地图 API 中没有 MapView 和在后台线程中添加叠加层的能力

I tried:我试过:

  1. map.addPolygon instead of creation of a GeoJsonLayer map.addPolygon 而不是创建 GeoJsonLayer
  2. map addOverlay with GroundOverlay使用 GroundOverlay 映射 addOverlay

Still I have more or less the same time when UI is frozen当 UI 被冻结时,我仍然或多或少有相同的时间

How can I manage this issue?我该如何管理这个问题? Is there any way to create a map from thousands of colorful rectangles in a background thread and then show it immediately in UI thread?有没有办法在后台线程中从数千个彩色矩形创建地图,然后立即在 UI 线程中显示它?

If you already try addPolygon() and GroundOverlay there are two possibilities left:如果您已经尝试过addPolygon()GroundOverlay则还有两种可能性:

  1. using Tile Overlays (preferred);使用Tile Overlays (首选);

  2. using custom drawing over MapView or MapFragment .MapViewMapFragment使用自定义绘图。

IMHO Tile Overlay is a better way due possibility of high performance TileProvider implementation.恕我直言,Tile Overlay 是一种更好的方法,因为可能实现高性能TileProvider For example, you can create tiles for "low" zoom levels and "current" (level that should be shown to user at the beginning) zoom level and store them in array ( HashMap , etc.) or file system path ..\\zoom_level\\x\\y\\tile.png if there are a lot of tiles.例如,您可以为“低”缩放级别和“当前”(应在开始时向用户显示的级别)缩放级别创建图块,并将它们存储在数组( HashMap等)或文件系统路径..\\zoom_level\\x\\y\\tile.png如果有很多图块。 And more "detailed" tiles you can create "on the fly" (in separate thread) when it needs to be shown, and then also store them for future using (if needed).当需要显示时,您可以“即时”(在单独的线程中)创建更多“详细”图块,然后还可以存储它们以备将来使用(如果需要)。 Of course, you need custom module for fast GeoJson reading (something like Jackson) and rendering it to the .png tiles.当然,您需要自定义模块来快速读取GeoJson (类似于 Jackson)并将其渲染到.png瓦片。 So, seems it is possible to create TileProvider for your case, optimal by performance and memory consumption.因此,似乎可以为您的案例创建TileProvider ,通过性能和内存消耗进行优化。 You can use this answer of Alex Vasilkov as first iteration.您可以使用Alex Vasilkov 的这个答案作为第一次迭代。

If you choose custom drawing you should override onDraw() method for MapView or dispatchDraw() for MapFragment .如果您选择自定义绘图,您应该覆盖MapView onDraw()方法或MapFragment dispatchDraw() MapFragment like in this answer .就像在这个答案中一样 In that case you can control all of the process, but that way is more complex for implementation.在这种情况下,您可以控制所有流程,但这种方式实施起来更加复杂。

Update:更新:

You can implement action for onCameraMove() , like in this answer (some tricky passing of GoogleMap object used there):您可以为onCameraMove()实现操作,就像在这个答案中一样(在那里使用的GoogleMap对象的一些棘手传递):

 public class RadarMapView extends MapView implements OnMapReadyCallback { private OnMapReadyCallback mMapReadyCallback; private GoogleMap mGoogleMap; private Marker mMarker; private Paint mPaintRadar; public RadarMapView(@NonNull Context context) { super(context); init(); } public RadarMapView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public RadarMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public RadarMapView(@NonNull Context context, @Nullable GoogleMapOptions options) { super(context, options); init(); } @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); canvas.save(); drawRadarOverTheMap(canvas); canvas.restore(); } private void drawRadarOverTheMap(Canvas canvas) { if (mGoogleMap == null) { return; } final float centerX = getX() + getWidth() / 2; final float centerY = getY() + getHeight() / 2; canvas.drawCircle(centerX, centerY, 150, mPaintRadar); canvas.drawCircle(centerX, centerY, 300, mPaintRadar); canvas.drawCircle(centerX, centerY, 450, mPaintRadar); } private void init() { setWillNotDraw(false); mPaintRadar = new Paint(); mPaintRadar.setColor(Color.GREEN); mPaintRadar.setStyle(Paint.Style.STROKE); mPaintRadar.setStrokeWidth(10); } @Override public void getMapAsync(OnMapReadyCallback callback) { mMapReadyCallback = callback; super.getMapAsync(this); } @Override public void onMapReady(GoogleMap googleMap) { mGoogleMap = googleMap; mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() { @Override public void onCameraMove() { invalidate(); // NB! Exactly this line you need } }); if (mMapReadyCallback != null) { mMapReadyCallback.onMapReady(googleMap); } } }

Update #2:更新#2:

You can make "screenshot" (not exactly screenshot, but create image of polyigons on the bitmap) of current view of polygons and move it in onCameraMove() (not redraw all polygons).您可以制作多边形当前视图的“屏幕截图”(不完全是屏幕截图,而是在位图上创建多边形图像)并将其移动到onCameraMove() (不重绘所有多边形)。 And then in onCameraIdle() create and show new full polygons view.然后在onCameraIdle()创建并显示新的完整多边形视图。 Also, you can create bitmap slightly bigger then map screen view (for zooming out and scrolling properly).此外,您可以创建比地图屏幕视图稍大的位图(用于缩小和正确滚动)。 Or you can "skip" some of the onCameraMove() calls (eg call invalidate() once per 3 onCameraMove() calls etc.).或者您可以“跳过”一些onCameraMove()调用(例如,每 3 个onCameraMove()调用调用一次invalidate()等)。

By the way: in case of Tile Overlays moving and zooming are available "from the box".顺便说一句:在 Tile Overlays 移动和缩放的情况下,可以“从框中”使用。 You only need to create a tricky TileProvider.您只需要创建一个棘手的 TileProvider。 There are only several tiles need to be generated for whole device screen (size of the single tile is 256x256).整个设备屏幕只需要生成几个图块(单个图块的大小为 256x256)。 So, you can generate tiles for current screen, for currentZoomLevel-1, for currentZoomLevel+1 (in case of zooming) and + 2 (or 3) tiles to the left, right, top and bottom (in case of scrolling).因此,您可以为当前屏幕、currentZoomLevel-1、currentZoomLevel+1(在缩放的情况下)和左侧、右侧、顶部和底部(在滚动的情况下)生成 +2(或 3)个图块。 Also you can store generated tiles for future using in some cache (HashMap, LRU, etc.).您还可以存储生成的切片以备将来在某些缓存(HashMap、LRU 等)中使用。 And you can generate "extra" (not currently visible) tiles in separate threads.您可以在单独的线程中生成“额外”(当前不可见)的图块。

i suggest to not create these few thousands of polygons one shot as this will affect time and performance but instead you can create only near polygons to shown location on the fly making use of GoogleMap.OnCameraMoveListener and GoogleMap.OnCameraIdleListener.我建议不要一次性创建这几千个多边形,因为这会影响时间和性能,但您可以使用 GoogleMap.OnCameraMoveListener 和 GoogleMap.OnCameraIdleListener 仅创建附近的多边形来动态显示位置。

Edit:- "near polygons to shown location on the fly" i mean create only polygons that the user currently see ie within the current visible region boundaries of map :- you can get it by编辑:-“在飞行中显示位置附近的多边形”我的意思是只创建用户当前看到的多边形,即在地图的当前可见区域边界内:-您可以通过

googleMap.projection.visibleRegion.latLngBounds

"GoogleMap.OnCameraMoveListener and GoogleMap.OnCameraIdleListenerwhat", I mean you have two approaches here first, you update the map with polygons within the visible boundaries when user stop scrolling the map "setOnCameraIdleListener" or the second one is the use setOnCameraMoveStartedListener to update the map with the polygons within the visible boundaries each time the user start scrolling the map. “GoogleMap.OnCameraMoveListener 和 GoogleMap.OnCameraIdleListenerwhat”,我的意思是你在这里有两种方法,当用户停止滚动地图“setOnCameraIdleListener”或第二个是使用 setOnCameraMoveStartedListener 更新地图时,你可以使用可见边界内的多边形更新地图每次用户开始滚动地图时,多边形都在可见边界内。

 override fun onMapReady(googleMap: GoogleMap?) {
        googleMap ?: return
        with(googleMap) {
            setMinZoomPreference(9f)
            setOnCameraIdleListener {
             // first approach user stopped scrolling so update the map with polygons within the boundaries 
            }
            setOnCameraMoveStartedListener {
             // second approach user started scrolling so update the map with polygons within the boundaries 
            }
        }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM