Skip to content

Commit 7f1dc6f

Browse files
authored
feat: KMZ support (#625)
* Test caching BitmapDescriptors * Update import for AndroidX * Cache scaled BitmapDescriptors BitmapDescriptorFactory.fromBitmap() copies the Bitmap on every call. Avoid duplicating Bitmaps for each Marker by caching the scaled BitmapDescriptors. * Clear Bitmap cache when done * Add doc comments * Cleanup comments * Fix marker icon scaling for screen density * Scale marker from styles * KMZ support * Check clear bitmap cache for all-offline KMZ * Fix input stream reset * Comment * Check for any .kml filename * Reduce Renderer method access scopes * Consolidate Renderer image caches into class * Add method behavior details to doc comment * Use string scale value as cache key * Add storeKmzData() * Log warning for unsupported KMZ content files
1 parent 3ca965f commit 7f1dc6f

File tree

3 files changed

+107
-14
lines changed

3 files changed

+107
-14
lines changed

library/src/main/java/com/google/maps/android/data/Renderer.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ protected void downloadFinished() {
576576
* Clear bitmap cache if no active image downloads remain. All images
577577
* should be loaded, scaled, and cached as BitmapDescriptors at this point.
578578
*/
579-
private void checkClearBitmapCache() {
579+
protected void checkClearBitmapCache() {
580580
if (mNumActiveDownloads == 0 && mImagesCache != null && !mImagesCache.bitmapCache.isEmpty()) {
581581
mImagesCache.bitmapCache.clear();
582582
}
@@ -671,7 +671,7 @@ protected void clearStylesRenderer() {
671671
}
672672

673673
/**
674-
* Stores all given data and adds it onto the map
674+
* Stores all given data
675675
*
676676
* @param styles hashmap of styles
677677
* @param styleMaps hashmap of style maps
@@ -681,7 +681,8 @@ protected void clearStylesRenderer() {
681681
*/
682682
protected void storeData(HashMap<String, KmlStyle> styles,
683683
HashMap<String, String> styleMaps,
684-
HashMap<KmlPlacemark, Object> features, ArrayList<KmlContainer> folders,
684+
HashMap<KmlPlacemark, Object> features,
685+
ArrayList<KmlContainer> folders,
685686
HashMap<KmlGroundOverlay, GroundOverlay> groundOverlays) {
686687
mStyles = styles;
687688
mStyleMaps = styleMaps;

library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java

+65-10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package com.google.maps.android.data.kml;
1717

18+
import android.graphics.Bitmap;
19+
import android.graphics.BitmapFactory;
20+
import android.util.Log;
21+
1822
import androidx.fragment.app.FragmentActivity;
1923

2024
import com.google.android.gms.maps.GoogleMap;
@@ -28,8 +32,12 @@
2832
import org.xmlpull.v1.XmlPullParserException;
2933
import org.xmlpull.v1.XmlPullParserFactory;
3034

35+
import java.io.BufferedInputStream;
3136
import java.io.IOException;
3237
import java.io.InputStream;
38+
import java.util.HashMap;
39+
import java.util.zip.ZipEntry;
40+
import java.util.zip.ZipInputStream;
3341

3442
/**
3543
* 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 {
3947
/**
4048
* Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
4149
*
50+
* Constructor may be called on a background thread, as I/O and parsing may be long-running.
51+
*
4252
* @param map GoogleMap object
43-
* @param resourceId Raw resource KML file
53+
* @param resourceId Raw resource KML or KMZ file
4454
* @param activity Activity object
4555
* @throws XmlPullParserException if file cannot be parsed
4656
* @throws IOException if I/O error
@@ -53,8 +63,10 @@ public KmlLayer(GoogleMap map, int resourceId, FragmentActivity activity)
5363
/**
5464
* Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
5565
*
66+
* Constructor may be called on a background thread, as I/O and parsing may be long-running.
67+
*
5668
* @param map GoogleMap object
57-
* @param stream InputStream containing KML file
69+
* @param stream InputStream containing KML or KMZ file
5870
* @param activity Activity object
5971
* @throws XmlPullParserException if file cannot be parsed
6072
* @throws IOException if I/O error
@@ -67,11 +79,13 @@ public KmlLayer(GoogleMap map, InputStream stream, FragmentActivity activity)
6779
/**
6880
* Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
6981
*
82+
* Constructor may be called on a background thread, as I/O and parsing may be long-running.
83+
*
7084
* Use this constructor with shared object managers in order to handle multiple layers with
7185
* their own event handlers on the map.
7286
*
7387
* @param map GoogleMap object
74-
* @param resourceId Raw resource KML file
88+
* @param resourceId Raw resource KML or KMZ file
7589
* @param activity Activity object
7690
* @param markerManager marker manager to create marker collection from
7791
* @param polygonManager polygon manager to create polygon collection from
@@ -88,11 +102,13 @@ public KmlLayer(GoogleMap map, int resourceId, FragmentActivity activity, Marker
88102
/**
89103
* Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map.
90104
*
105+
* Constructor may be called on a background thread, as I/O and parsing may be long-running.
106+
*
91107
* Use this constructor with shared object managers in order to handle multiple layers with
92108
* their own event handlers on the map.
93109
*
94110
* @param map GoogleMap object
95-
* @param stream InputStream containing KML file
111+
* @param stream InputStream containing KML or KMZ file
96112
* @param activity Activity object
97113
* @param markerManager marker manager to create marker collection from
98114
* @param polygonManager polygon manager to create polygon collection from
@@ -106,14 +122,53 @@ public KmlLayer(GoogleMap map, InputStream stream, FragmentActivity activity, Ma
106122
if (stream == null) {
107123
throw new IllegalArgumentException("KML InputStream cannot be null");
108124
}
109-
KmlRenderer mRenderer = new KmlRenderer(map, activity, markerManager, polygonManager, polylineManager, groundOverlayManager);
125+
KmlRenderer renderer = new KmlRenderer(map, activity, markerManager, polygonManager, polylineManager, groundOverlayManager);
126+
127+
BufferedInputStream bis = new BufferedInputStream(stream);
128+
bis.mark(1024);
129+
ZipInputStream zip = new ZipInputStream(bis);
130+
try {
131+
KmlParser parser = null;
132+
ZipEntry entry = zip.getNextEntry();
133+
if (entry != null) { // is a KMZ zip file
134+
HashMap<String, Bitmap> images = new HashMap<>();
135+
while (entry != null) {
136+
if (parser == null && entry.getName().toLowerCase().endsWith(".kml")) {
137+
parser = parseKml(zip);
138+
} else {
139+
Bitmap bitmap = BitmapFactory.decodeStream(zip);
140+
if (bitmap != null) {
141+
images.put(entry.getName(), bitmap);
142+
} else {
143+
Log.w("KmlLayer", "Unsupported KMZ contents file type: " + entry.getName());
144+
}
145+
}
146+
entry = zip.getNextEntry();
147+
}
148+
if (parser == null) {
149+
throw new IllegalArgumentException("KML not found in InputStream");
150+
}
151+
renderer.storeKmzData(parser.getStyles(), parser.getStyleMaps(), parser.getPlacemarks(),
152+
parser.getContainers(), parser.getGroundOverlays(), images);
153+
} else { // is a KML
154+
bis.reset();
155+
parser = parseKml(bis);
156+
renderer.storeKmlData(parser.getStyles(), parser.getStyleMaps(), parser.getPlacemarks(),
157+
parser.getContainers(), parser.getGroundOverlays());
158+
}
159+
storeRenderer(renderer);
160+
} finally {
161+
stream.close();
162+
bis.close();
163+
zip.close();
164+
}
165+
}
166+
167+
private static KmlParser parseKml(InputStream stream) throws XmlPullParserException, IOException {
110168
XmlPullParser xmlPullParser = createXmlParser(stream);
111169
KmlParser parser = new KmlParser(xmlPullParser);
112170
parser.parseKml();
113-
stream.close();
114-
mRenderer.storeKmlData(parser.getStyles(), parser.getStyleMaps(), parser.getPlacemarks(),
115-
parser.getContainers(), parser.getGroundOverlays());
116-
storeRenderer(mRenderer);
171+
return parser;
117172
}
118173

119174
/**
@@ -132,7 +187,7 @@ private static XmlPullParser createXmlParser(InputStream stream) throws XmlPullP
132187
}
133188

134189
/**
135-
* Adds the KML data to the map
190+
* Adds the KML data to the map - must be called on the main UI thread
136191
*/
137192
@Override
138193
public void addLayerToMap() {

library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java

+38-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.HashSet;
4848
import java.util.Iterator;
4949
import java.util.List;
50+
import java.util.Map;
5051
import java.util.Set;
5152

5253
/**
@@ -127,15 +128,51 @@ public void addLayerToMap() {
127128
if (!mMarkerIconsDownloaded) {
128129
downloadMarkerIcons();
129130
}
131+
// in case KMZ has no downloaded images
132+
checkClearBitmapCache();
130133
}
131134

135+
/**
136+
* Stores all given data from KML file
137+
*
138+
* @param styles hashmap of styles
139+
* @param styleMaps hashmap of style maps
140+
* @param features hashmap of features
141+
* @param folders array of containers
142+
* @param groundOverlays hashmap of ground overlays
143+
*/
132144
/*package*/ void storeKmlData(HashMap<String, KmlStyle> styles,
133145
HashMap<String, String> styleMaps,
134-
HashMap<KmlPlacemark, Object> features, ArrayList<KmlContainer> folders,
146+
HashMap<KmlPlacemark, Object> features,
147+
ArrayList<KmlContainer> folders,
135148
HashMap<KmlGroundOverlay, GroundOverlay> groundOverlays) {
149+
136150
storeData(styles, styleMaps, features, folders, groundOverlays);
137151
}
138152

153+
/**
154+
* Stores all given data from KMZ file
155+
*
156+
* @param styles hashmap of styles
157+
* @param styleMaps hashmap of style maps
158+
* @param features hashmap of features
159+
* @param folders array of containers
160+
* @param groundOverlays hashmap of ground overlays
161+
* @param images hashmap of images
162+
*/
163+
/*package*/ void storeKmzData(HashMap<String, KmlStyle> styles,
164+
HashMap<String, String> styleMaps,
165+
HashMap<KmlPlacemark, Object> features,
166+
ArrayList<KmlContainer> folders,
167+
HashMap<KmlGroundOverlay, GroundOverlay> groundOverlays,
168+
HashMap<String, Bitmap> images) {
169+
170+
storeData(styles, styleMaps, features, folders, groundOverlays);
171+
for (Map.Entry<String, Bitmap> entry : images.entrySet()) {
172+
cacheBitmap(entry.getKey(), entry.getValue());
173+
}
174+
}
175+
139176
/**
140177
* Sets the map that objects are being placed on
141178
*

0 commit comments

Comments
 (0)