简体   繁体   中英

Efficient way to add large GeoJson to GoogleMap on Android

I have a temperature map for some area with 3 km grid. 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.

The issue is that I need to add GeoJson layer in UI thread and it takes 8-15 seconds. 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:

  1. They propose to decrease the json - I already create clusters from nearby squares with the same value
  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

I tried:

  1. map.addPolygon instead of creation of a GeoJsonLayer
  2. map addOverlay with GroundOverlay

Still I have more or less the same time when UI is frozen

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?

If you already try addPolygon() and GroundOverlay there are two possibilities left:

  1. using Tile Overlays (preferred);

  2. using custom drawing over MapView or MapFragment .

IMHO Tile Overlay is a better way due possibility of high performance TileProvider implementation. 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. 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. So, seems it is possible to create TileProvider for your case, optimal by performance and memory consumption. You can use this answer of Alex Vasilkov as first iteration.

If you choose custom drawing you should override onDraw() method for MapView or dispatchDraw() for 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):

 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:

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). And then in onCameraIdle() create and show new full polygons view. 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.).

By the way: in case of Tile Overlays moving and zooming are available "from the box". You only need to create a tricky TileProvider. There are only several tiles need to be generated for whole device screen (size of the single tile is 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). Also you can store generated tiles for future using in some cache (HashMap, LRU, etc.). 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.

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.

 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 
            }
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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