diff --git a/library/src/main/java/com/google/maps/android/data/Renderer.java b/library/src/main/java/com/google/maps/android/data/Renderer.java index a271acfbc..ccf4b9992 100644 --- a/library/src/main/java/com/google/maps/android/data/Renderer.java +++ b/library/src/main/java/com/google/maps/android/data/Renderer.java @@ -576,7 +576,7 @@ protected void downloadFinished() { * Clear bitmap cache if no active image downloads remain. All images * should be loaded, scaled, and cached as BitmapDescriptors at this point. */ - private void checkClearBitmapCache() { + protected void checkClearBitmapCache() { if (mNumActiveDownloads == 0 && mImagesCache != null && !mImagesCache.bitmapCache.isEmpty()) { mImagesCache.bitmapCache.clear(); } @@ -671,7 +671,7 @@ protected void clearStylesRenderer() { } /** - * Stores all given data and adds it onto the map + * Stores all given data * * @param styles hashmap of styles * @param styleMaps hashmap of style maps @@ -681,7 +681,8 @@ protected void clearStylesRenderer() { */ protected void storeData(HashMap styles, HashMap styleMaps, - HashMap features, ArrayList folders, + HashMap features, + ArrayList folders, HashMap groundOverlays) { mStyles = styles; mStyleMaps = styleMaps; diff --git a/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java b/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java index 039d25908..b11ecb78f 100644 --- a/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java +++ b/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java @@ -15,6 +15,10 @@ */ package com.google.maps.android.data.kml; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + import androidx.fragment.app.FragmentActivity; import com.google.android.gms.maps.GoogleMap; @@ -28,8 +32,12 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /** * Document class allows for users to input their KML data and output it onto the map @@ -39,8 +47,10 @@ public class KmlLayer extends Layer { /** * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map. * + * Constructor may be called on a background thread, as I/O and parsing may be long-running. + * * @param map GoogleMap object - * @param resourceId Raw resource KML file + * @param resourceId Raw resource KML or KMZ file * @param activity Activity object * @throws XmlPullParserException if file cannot be parsed * @throws IOException if I/O error @@ -53,8 +63,10 @@ public KmlLayer(GoogleMap map, int resourceId, FragmentActivity activity) /** * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map. * + * Constructor may be called on a background thread, as I/O and parsing may be long-running. + * * @param map GoogleMap object - * @param stream InputStream containing KML file + * @param stream InputStream containing KML or KMZ file * @param activity Activity object * @throws XmlPullParserException if file cannot be parsed * @throws IOException if I/O error @@ -67,11 +79,13 @@ public KmlLayer(GoogleMap map, InputStream stream, FragmentActivity activity) /** * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map. * + * Constructor may be called on a background thread, as I/O and parsing may be long-running. + * * Use this constructor with shared object managers in order to handle multiple layers with * their own event handlers on the map. * * @param map GoogleMap object - * @param resourceId Raw resource KML file + * @param resourceId Raw resource KML or KMZ file * @param activity Activity object * @param markerManager marker manager to create marker collection from * @param polygonManager polygon manager to create polygon collection from @@ -88,11 +102,13 @@ public KmlLayer(GoogleMap map, int resourceId, FragmentActivity activity, Marker /** * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map. * + * Constructor may be called on a background thread, as I/O and parsing may be long-running. + * * Use this constructor with shared object managers in order to handle multiple layers with * their own event handlers on the map. * * @param map GoogleMap object - * @param stream InputStream containing KML file + * @param stream InputStream containing KML or KMZ file * @param activity Activity object * @param markerManager marker manager to create marker collection from * @param polygonManager polygon manager to create polygon collection from @@ -106,14 +122,53 @@ public KmlLayer(GoogleMap map, InputStream stream, FragmentActivity activity, Ma if (stream == null) { throw new IllegalArgumentException("KML InputStream cannot be null"); } - KmlRenderer mRenderer = new KmlRenderer(map, activity, markerManager, polygonManager, polylineManager, groundOverlayManager); + KmlRenderer renderer = new KmlRenderer(map, activity, markerManager, polygonManager, polylineManager, groundOverlayManager); + + BufferedInputStream bis = new BufferedInputStream(stream); + bis.mark(1024); + ZipInputStream zip = new ZipInputStream(bis); + try { + KmlParser parser = null; + ZipEntry entry = zip.getNextEntry(); + if (entry != null) { // is a KMZ zip file + HashMap images = new HashMap<>(); + while (entry != null) { + if (parser == null && entry.getName().toLowerCase().endsWith(".kml")) { + parser = parseKml(zip); + } else { + Bitmap bitmap = BitmapFactory.decodeStream(zip); + if (bitmap != null) { + images.put(entry.getName(), bitmap); + } else { + Log.w("KmlLayer", "Unsupported KMZ contents file type: " + entry.getName()); + } + } + entry = zip.getNextEntry(); + } + if (parser == null) { + throw new IllegalArgumentException("KML not found in InputStream"); + } + renderer.storeKmzData(parser.getStyles(), parser.getStyleMaps(), parser.getPlacemarks(), + parser.getContainers(), parser.getGroundOverlays(), images); + } else { // is a KML + bis.reset(); + parser = parseKml(bis); + renderer.storeKmlData(parser.getStyles(), parser.getStyleMaps(), parser.getPlacemarks(), + parser.getContainers(), parser.getGroundOverlays()); + } + storeRenderer(renderer); + } finally { + stream.close(); + bis.close(); + zip.close(); + } + } + + private static KmlParser parseKml(InputStream stream) throws XmlPullParserException, IOException { XmlPullParser xmlPullParser = createXmlParser(stream); KmlParser parser = new KmlParser(xmlPullParser); parser.parseKml(); - stream.close(); - mRenderer.storeKmlData(parser.getStyles(), parser.getStyleMaps(), parser.getPlacemarks(), - parser.getContainers(), parser.getGroundOverlays()); - storeRenderer(mRenderer); + return parser; } /** @@ -132,7 +187,7 @@ private static XmlPullParser createXmlParser(InputStream stream) throws XmlPullP } /** - * Adds the KML data to the map + * Adds the KML data to the map - must be called on the main UI thread */ @Override public void addLayerToMap() { diff --git a/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java b/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java index 3f512a713..0cb34a9ae 100644 --- a/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java +++ b/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java @@ -47,6 +47,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -127,15 +128,51 @@ public void addLayerToMap() { if (!mMarkerIconsDownloaded) { downloadMarkerIcons(); } + // in case KMZ has no downloaded images + checkClearBitmapCache(); } + /** + * Stores all given data from KML file + * + * @param styles hashmap of styles + * @param styleMaps hashmap of style maps + * @param features hashmap of features + * @param folders array of containers + * @param groundOverlays hashmap of ground overlays + */ /*package*/ void storeKmlData(HashMap styles, HashMap styleMaps, - HashMap features, ArrayList folders, + HashMap features, + ArrayList folders, HashMap groundOverlays) { + storeData(styles, styleMaps, features, folders, groundOverlays); } + /** + * Stores all given data from KMZ file + * + * @param styles hashmap of styles + * @param styleMaps hashmap of style maps + * @param features hashmap of features + * @param folders array of containers + * @param groundOverlays hashmap of ground overlays + * @param images hashmap of images + */ + /*package*/ void storeKmzData(HashMap styles, + HashMap styleMaps, + HashMap features, + ArrayList folders, + HashMap groundOverlays, + HashMap images) { + + storeData(styles, styleMaps, features, folders, groundOverlays); + for (Map.Entry entry : images.entrySet()) { + cacheBitmap(entry.getKey(), entry.getValue()); + } + } + /** * Sets the map that objects are being placed on *