Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: KMZ support #625

Merged
merged 24 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
186195f
Test caching BitmapDescriptors
jeffdgr8 Mar 27, 2017
8e2011a
Update import for AndroidX
jeffdgr8 Oct 13, 2019
6b25de1
Cache scaled BitmapDescriptors
jeffdgr8 Jan 29, 2020
731334f
Clear Bitmap cache when done
jeffdgr8 Jan 29, 2020
6865f13
Add doc comments
jeffdgr8 Jan 29, 2020
f7c71f2
Cleanup comments
jeffdgr8 Jan 29, 2020
30a8940
Fix marker icon scaling for screen density
jeffdgr8 Jan 29, 2020
fcfb833
Scale marker from styles
jeffdgr8 Jan 29, 2020
7550b61
KMZ support
jeffdgr8 Jan 29, 2020
e25546e
Check clear bitmap cache for all-offline KMZ
jeffdgr8 Jan 29, 2020
ddba065
Fix input stream reset
jeffdgr8 Jan 29, 2020
1977299
Comment
jeffdgr8 Jan 29, 2020
832954f
Check for any .kml filename
jeffdgr8 Jan 30, 2020
ec4280d
Merge branch 'master' into kml-bitmap-caching
jeffdgr8 Jan 31, 2020
07aabf5
Merge branch 'master' into kml-bitmap-caching
jeffdgr8 Feb 5, 2020
19cb117
Merge branch 'kml-bitmap-caching' into kmz-support
jeffdgr8 Feb 5, 2020
084c0ca
Reduce Renderer method access scopes
jeffdgr8 Feb 13, 2020
e9d8f24
Consolidate Renderer image caches into class
jeffdgr8 Feb 13, 2020
f99a787
Add method behavior details to doc comment
jeffdgr8 Feb 13, 2020
f9bd8ba
Use string scale value as cache key
jeffdgr8 Feb 13, 2020
4fccc05
Merge branch 'kml-bitmap-caching' into kmz-support
jeffdgr8 Feb 13, 2020
06de5c7
Merge branch 'master' into kmz-support
jeffdgr8 Feb 14, 2020
482c748
Add storeKmzData()
jeffdgr8 Feb 18, 2020
3d1d0e7
Log warning for unsupported KMZ content files
jeffdgr8 Feb 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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
Expand All @@ -681,7 +681,8 @@ protected void clearStylesRenderer() {
*/
protected void storeData(HashMap<String, KmlStyle> styles,
HashMap<String, String> styleMaps,
HashMap<KmlPlacemark, Object> features, ArrayList<KmlContainer> folders,
HashMap<KmlPlacemark, Object> features,
ArrayList<KmlContainer> folders,
HashMap<KmlGroundOverlay, GroundOverlay> groundOverlays) {
mStyles = styles;
mStyleMaps = styleMaps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be simpler if another constructor was created, with the following signature, to handle the KMZ files explicitly:

public KmlLayer(GoogleMap map, ZipInputStream stream, FragmentActivity activity, MarkerManager markerManager, PolygonManager polygonManager, PolylineManager polylineManager, GroundOverlayManager groundOverlayManager)

This way, it's left to the caller to use the correct constructor for their use-case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that. Since we can differentiate between the two types with the logic I implemented, I thought a single universal constructor was simpler for the caller to use. Also, since there are two constructors currently, the other supports opening a KML by resource ID, how could we implement opening a KMZ by resource ID? We'd have to imply the resource ID is either a KML or KMZ or we'd need the same logic to differentiate between them and call the appropriate stream constructor anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point about invoking by resource ID. Let's stick to this implementation.

* @param activity Activity object
* @param markerManager marker manager to create marker collection from
* @param polygonManager polygon manager to create polygon collection from
Expand All @@ -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<String, Bitmap> images = new HashMap<>();
while (entry != null) {
if (parser == null && entry.getName().toLowerCase().endsWith(".kml")) {
parser = parseKml(zip);
} else {
Bitmap bitmap = BitmapFactory.decodeStream(zip);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since other file types might also be used (e.g. .mp3), it would be good to check that an image is used and if not, log via Log.w that the file type is not supported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this handle subdirectories?

sample.kmz
 - doc.kml
 - image.png
 - dir/image2.png
 - dir/image3.png
 - dir/dir2/image4.png
 ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, subdirectories are handled. The name includes the full file path within the zip archive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added warning log: 3d1d0e7

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

/**
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
Expand Down Expand Up @@ -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<String, KmlStyle> styles,
HashMap<String, String> styleMaps,
HashMap<KmlPlacemark, Object> features, ArrayList<KmlContainer> folders,
HashMap<KmlPlacemark, Object> features,
ArrayList<KmlContainer> folders,
HashMap<KmlGroundOverlay, GroundOverlay> 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<String, KmlStyle> styles,
HashMap<String, String> styleMaps,
HashMap<KmlPlacemark, Object> features,
ArrayList<KmlContainer> folders,
HashMap<KmlGroundOverlay, GroundOverlay> groundOverlays,
HashMap<String, Bitmap> images) {

storeData(styles, styleMaps, features, folders, groundOverlays);
for (Map.Entry<String, Bitmap> entry : images.entrySet()) {
cacheBitmap(entry.getKey(), entry.getValue());
}
}

/**
* Sets the map that objects are being placed on
*
Expand Down