diff --git a/ios/.gitignore b/ios/.gitignore
index f4702997..8882d557 100644
--- a/ios/.gitignore
+++ b/ios/.gitignore
@@ -1,5 +1,5 @@
App/build
-App/Pods
+# App/Pods
App/output
App/App/public
DerivedData
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/LICENSE b/ios/App/Pods/Google-Maps-iOS-Utils/LICENSE
new file mode 100644
index 00000000..37ec93a1
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/README.md b/ios/App/Pods/Google-Maps-iOS-Utils/README.md
new file mode 100644
index 00000000..9b638a14
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/README.md
@@ -0,0 +1,174 @@
+![Run unit tests](https://github.com/googlemaps/google-maps-ios-utils/workflows/Run%20unit%20tests/badge.svg)
+[![pod](https://img.shields.io/cocoapods/v/Google-Maps-iOS-Utils.svg)](https://cocoapods.org/pods/Google-Maps-iOS-Utils)
+![GitHub contributors](https://img.shields.io/github/contributors/googlemaps/google-maps-ios-utils)
+![Apache-2.0](https://img.shields.io/badge/license-Apache-blue)
+
+Google Maps SDK for iOS Utility Library
+=======================================
+
+## Description
+
+This open-source library contains classes that are useful for a wide
+range of applications using the [Google Maps SDK for iOS][sdk].
+
+- **Geometry libraries** - [KML and GeoJSON rendering][geometry-rendering]
+- **Geometry utilities** - Handy spherical [geometry utility][geometry-utils] functions
+- **Heatmaps** - [Heatmap rendering][heatmap-rendering]
+- **Marker clustering** — handles the display of a large number of points
+- **Marker customization** - [display custom markers][customizing-markers]
+- **Quadtree data structure** - indexes 2D geometry points and performs
+2D range queries
+
+
+
+## Requirements
+
+- iOS 13.0+
+- [Maps SDK for iOS][sdk] (see [Releases](https://github.com/googlemaps/google-maps-ios-utils/releases) for minimum compatible version)
+
+## Installation
+
+1. [Include the `GoogleMaps` dependency](https://developers.google.com/maps/documentation/ios-sdk/config#download-sdk) using one of the available installation options (CocoaPods, XCFramework, Carthage (for v6.2.1 and earlier) or manual).
+
+1. Add this utility library using one of the methods below:
+
+### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
+
+In your `Podfile`:
+
+```ruby
+use_frameworks!
+
+target 'TARGET_NAME' do
+ pod 'Google-Maps-iOS-Utils', '4.2.2'
+end
+```
+
+Replace `TARGET_NAME` and then, in the `Podfile` directory, type:
+
+```bash
+pod install
+```
+
+### [Swift Package Manager](https://github.com/apple/swift-package-manager)
+
+**Note**: This feature is only available with Swift 5.3 (Xcode 12) or later.
+
+Add the following to your `dependencies` value of your `Package.swift` file.
+
+```
+dependencies: [
+ .package(
+ url: "https://github.com/googlemaps/google-maps-ios-utils.git",
+ .upToNextMinor(from: "4.2.2")
+ )
+]
+```
+
+### [Carthage](https://github.com/Carthage/Carthage)
+
+
+Only supported if using Maps SDK v6.2.1 or earlier
+
+In your `Cartfile`:
+
+```
+github "googlemaps/google-maps-ios-utils" ~> 4.1.0
+```
+
+See the [Carthage doc] for further installation instructions.
+
+
+## Sample App
+
+See the README for the Swift and Objective-C samples apps in [/samples](samples).
+
+## Documentation
+
+Read documentation about this utility library on [developers.google.com][devsite-guide] or within the [/docs](docs) directory.
+
+## Usage
+
+### Clustering markers
+
+```swift
+import GoogleMaps
+import GoogleMapsUtils
+
+class MarkerClustering: UIViewController, GMSMapViewDelegate {
+ private var mapView: GMSMapView!
+ private var clusterManager: GMUClusterManager!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Set up the cluster manager with the supplied icon generator and
+ // renderer.
+ let iconGenerator = GMUDefaultClusterIconGenerator()
+ let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
+ let renderer = GMUDefaultClusterRenderer(mapView: mapView,
+ clusterIconGenerator: iconGenerator)
+ clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm,
+ renderer: renderer)
+
+ // Register self to listen to GMSMapViewDelegate events.
+ clusterManager.setMapDelegate(self)
+ // ...
+ }
+ // ...
+}
+
+let markerArray = [marker1, marker2, marker3, marker4] // define your own markers
+clusterManager.add(markerArray)
+
+clusterManager.cluster()
+```
+
+### Displaying KML data
+
+```swift
+import GoogleMaps
+import GoogleMapsUtils
+
+func renderKml() {
+ // Parse KML
+ let path: String = // Path to your KML file...
+ let kmlUrl = URL(fileURLWithPath: path)
+ let kmlParser = GMUKmlParser(url: kmlUrl)
+ kmlParser.parse()
+
+ // Render parsed KML
+ let renderer = GMUGeometryRenderer(
+ map: mapView,
+ geometries: kmlParser.placemarks,
+ styles: kmlParser.styles,
+ styleMaps: kmlParser.styleMaps
+ )
+ renderer.render()
+}
+```
+
+## Contributing
+
+Contributions are welcome and encouraged. Please see the [contributing guide][contributing] for guidance.
+
+## Support
+
+This library is offered via an open source [license]. It is not governed by the Google Maps Platform [Support Technical Support Services Guidelines](https://cloud.google.com/maps-platform/terms/tssg), the [SLA](https://cloud.google.com/maps-platform/terms/sla), or the [Deprecation Policy](https://cloud.google.com/maps-platform/terms) (however, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service).
+
+This library adheres to [semantic versioning](https://semver.org/) to indicate when backwards-incompatible changes are introduced. Accordingly, while the library is in version 0.x, backwards-incompatible changes may be introduced at any time.
+
+If you find a bug, or have a feature request, please file an [issue] on GitHub. If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our [developer community channels](https://developers.google.com/maps/developer-community) such as our [Discord server].
+
+[Discord server]: https://discord.gg/9fwRNWg
+[Carthage doc]: docs/Carthage.md
+[contributing]: CONTRIBUTING.md
+[code of conduct]: CODE_OF_CONDUCT.md
+[devsite-guide]: https://developers.google.com/maps/documentation/ios-sdk/utility/
+[sdk]: https://developers.google.com/maps/documentation/ios-sdk
+[issue]: https://github.com/googlemaps/google-maps-ios-utils/issues
+[license]: LICENSE
+[customizing-markers]: docs/CustomMarkers.md
+[geometry-rendering]: docs/GeometryRendering.md
+[heatmap-rendering]: docs/HeatmapRendering.md
+[geometry-utils]: docs/GeometryUtils.md
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUClusterAlgorithm.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUClusterAlgorithm.h
new file mode 100644
index 00000000..e6637f52
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUClusterAlgorithm.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUCluster.h"
+#import "GMUClusterItem.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Generic protocol for arranging cluster items into groups.
+ */
+@protocol GMUClusterAlgorithm
+
+- (void)addItems:(NSArray> *)items;
+
+/**
+ * Removes an item.
+ */
+- (void)removeItem:(id)item;
+
+/**
+ * Clears all items.
+ */
+- (void)clearItems;
+
+/**
+ * Returns the set of clusters of the added items.
+ */
+- (NSArray> *)clustersAtZoom:(float)zoom;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUGridBasedClusterAlgorithm.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUGridBasedClusterAlgorithm.h
new file mode 100644
index 00000000..47fb1fc5
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUGridBasedClusterAlgorithm.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUClusterAlgorithm.h"
+
+/**
+ * A simple algorithm which devides the map into a grid where a cell has fixed dimension in
+ * screen space.
+ */
+@interface GMUGridBasedClusterAlgorithm : NSObject
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUGridBasedClusterAlgorithm.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUGridBasedClusterAlgorithm.m
new file mode 100644
index 00000000..bfad5092
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUGridBasedClusterAlgorithm.m
@@ -0,0 +1,79 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GMUGridBasedClusterAlgorithm.h"
+
+#import
+
+#import "GMUStaticCluster.h"
+#import "GMUClusterItem.h"
+
+// Grid cell dimension in pixels to keep clusters about 100 pixels apart on screen.
+static const NSUInteger kGMUGridCellSizePoints = 100;
+
+@implementation GMUGridBasedClusterAlgorithm {
+ NSMutableArray> *_items;
+}
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _items = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)addItems:(NSArray> *)items {
+ [_items addObjectsFromArray:items];
+}
+
+- (void)removeItem:(id)item {
+ [_items removeObject:item];
+}
+
+- (void)clearItems {
+ [_items removeAllObjects];
+}
+
+- (NSArray> *)clustersAtZoom:(float)zoom {
+ NSMutableDictionary> *clusters = [[NSMutableDictionary alloc] init];
+
+ // Divide the whole map into a numCells x numCells grid and assign items to them.
+ long numCells = (long)ceil(256 * pow(2, zoom) / kGMUGridCellSizePoints);
+ for (id item in _items) {
+ GMSMapPoint point = GMSProject(item.position);
+ long col = (long)(numCells * (1.0 + point.x) / 2); // point.x is in [-1, 1] range
+ long row = (long)(numCells * (1.0 + point.y) / 2); // point.y is in [-1, 1] range
+ long index = numCells * row + col;
+ NSNumber *cellKey = [NSNumber numberWithLong:index];
+ GMUStaticCluster *cluster = clusters[cellKey];
+ if (cluster == nil) {
+ // Normalize cluster's centroid to center of the cell.
+ GMSMapPoint point2 = {(double)(col + 0.5) * 2.0 / numCells - 1,
+ (double)(row + 0.5) * 2.0 / numCells - 1};
+ CLLocationCoordinate2D position = GMSUnproject(point2);
+ cluster = [[GMUStaticCluster alloc] initWithPosition:position];
+ clusters[cellKey] = cluster;
+ }
+ [cluster addItem:item];
+ }
+ return [clusters allValues];
+}
+
+@end
+
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUNonHierarchicalDistanceBasedAlgorithm.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUNonHierarchicalDistanceBasedAlgorithm.h
new file mode 100644
index 00000000..1c8fdf13
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUNonHierarchicalDistanceBasedAlgorithm.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUClusterAlgorithm.h"
+
+/**
+ * A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not
+ * hierarchical.
+ * High level algorithm:
+ * 1. Iterate over items in the order they were added (candidate clusters).
+ * 2. Create a cluster with the center of the item.
+ * 3. Add all items that are within a certain distance to the cluster.
+ * 4. Move any items out of an existing cluster if they are closer to another cluster.
+ * 5. Remove those items from the list of candidate clusters.
+ * Clusters have the center of the first element (not the centroid of the items within it).
+ */
+@interface GMUNonHierarchicalDistanceBasedAlgorithm : NSObject
+
+/**
+ * Initializes this GMUNonHierarchicalDistanceBasedAlgorithm with clusterDistancePoints for
+ * the distance it uses to cluster items (default is 100).
+ */
+- (instancetype)initWithClusterDistancePoints:(NSUInteger)clusterDistancePoints;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUNonHierarchicalDistanceBasedAlgorithm.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUNonHierarchicalDistanceBasedAlgorithm.m
new file mode 100644
index 00000000..c227fea3
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUNonHierarchicalDistanceBasedAlgorithm.m
@@ -0,0 +1,198 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GMUNonHierarchicalDistanceBasedAlgorithm.h"
+
+#import
+
+#import "GMUStaticCluster.h"
+#import "GMUClusterItem.h"
+#import "GMUWrappingDictionaryKey.h"
+#import "GQTPointQuadTree.h"
+
+static const NSUInteger kGMUDefaultClusterDistancePoints = 100;
+static const double kGMUMapPointWidth = 2.0; // MapPoint is in a [-1,1]x[-1,1] space.
+
+#pragma mark Utilities Classes
+
+@interface GMUClusterItemQuadItem : NSObject
+
+@property(nonatomic, readonly) id clusterItem;
+
+- (instancetype)initWithClusterItem:(id)clusterItem;
+
+@end
+
+@implementation GMUClusterItemQuadItem {
+ id _clusterItem;
+ GQTPoint _clusterItemPoint;
+}
+
+- (instancetype)initWithClusterItem:(id)clusterItem {
+ if ((self = [super init])) {
+ _clusterItem = clusterItem;
+ GMSMapPoint point = GMSProject(clusterItem.position);
+ _clusterItemPoint.x = point.x;
+ _clusterItemPoint.y = point.y;
+ }
+ return self;
+}
+
+- (GQTPoint)point {
+ return _clusterItemPoint;
+}
+
+// Forwards hash to clusterItem.
+- (NSUInteger)hash {
+ return [_clusterItem hash];
+}
+
+// Forwards isEqual to clusterItem.
+- (BOOL)isEqual:(id)object {
+ if (self == object) return YES;
+
+ if ([object class] != [self class]) return NO;
+
+ GMUClusterItemQuadItem *other = (GMUClusterItemQuadItem *)object;
+ return [_clusterItem isEqual:other->_clusterItem];
+}
+
+@end
+
+#pragma mark GMUNonHierarchicalDistanceBasedAlgorithm
+
+@implementation GMUNonHierarchicalDistanceBasedAlgorithm {
+ NSMutableArray> *_items;
+ GQTPointQuadTree *_quadTree;
+ NSUInteger _clusterDistancePoints;
+}
+
+- (instancetype)init {
+ return [self initWithClusterDistancePoints:kGMUDefaultClusterDistancePoints];
+}
+
+- (instancetype)initWithClusterDistancePoints:(NSUInteger)clusterDistancePoints {
+ if ((self = [super init])) {
+ _items = [[NSMutableArray alloc] init];
+ GQTBounds bounds = {-1, -1, 1, 1};
+ _quadTree = [[GQTPointQuadTree alloc] initWithBounds:bounds];
+ _clusterDistancePoints = clusterDistancePoints;
+ }
+ return self;
+}
+
+- (void)addItems:(NSArray> *)items {
+ [_items addObjectsFromArray:items];
+ for (id item in items) {
+ GMUClusterItemQuadItem *quadItem = [[GMUClusterItemQuadItem alloc] initWithClusterItem:item];
+ [_quadTree add:quadItem];
+ }
+}
+
+/**
+ * Removes an item.
+ */
+- (void)removeItem:(id)item {
+ [_items removeObject:item];
+
+ GMUClusterItemQuadItem *quadItem = [[GMUClusterItemQuadItem alloc] initWithClusterItem:item];
+ // This should remove the corresponding quad item since GMUClusterItemQuadItem forwards its hash
+ // and isEqual to the underlying item.
+ [_quadTree remove:quadItem];
+}
+
+/**
+ * Clears all items.
+ */
+- (void)clearItems {
+ [_items removeAllObjects];
+ [_quadTree clear];
+}
+
+/**
+ * Returns the set of clusters of the added items.
+ */
+- (NSArray> *)clustersAtZoom:(float)zoom {
+ NSMutableArray> *clusters = [[NSMutableArray alloc] init];
+ NSMutableDictionary> *itemToClusterMap =
+ [[NSMutableDictionary alloc] init];
+ NSMutableDictionary *itemToClusterDistanceMap =
+ [[NSMutableDictionary alloc] init];
+ NSMutableSet> *processedItems = [[NSMutableSet alloc] init];
+
+ for (id item in _items) {
+ if ([processedItems containsObject:item]) continue;
+
+ GMUStaticCluster *cluster = [[GMUStaticCluster alloc] initWithPosition:item.position];
+
+ GMSMapPoint point = GMSProject(item.position);
+
+ // Query for items within a fixed point distance from the current item to make up a cluster
+ // around it.
+ double radius = _clusterDistancePoints * kGMUMapPointWidth / pow(2.0, zoom + 8.0);
+ GQTBounds bounds = {point.x - radius, point.y - radius, point.x + radius, point.y + radius};
+ NSArray *nearbyItems = [_quadTree searchWithBounds:bounds];
+ for (GMUClusterItemQuadItem *quadItem in nearbyItems) {
+ id nearbyItem = quadItem.clusterItem;
+ [processedItems addObject:nearbyItem];
+ GMSMapPoint nearbyItemPoint = GMSProject(nearbyItem.position);
+ GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:nearbyItem];
+
+ NSNumber *existingDistance = [itemToClusterDistanceMap objectForKey:key];
+ double distanceSquared = [self distanceSquaredBetweenPointA:point andPointB:nearbyItemPoint];
+ if (existingDistance != nil) {
+ if ([existingDistance doubleValue] < distanceSquared) {
+ // Already belongs to a closer cluster.
+ continue;
+ }
+ GMUStaticCluster *existingCluster = [itemToClusterMap objectForKey:key];
+ [existingCluster removeItem:nearbyItem];
+ }
+ NSNumber *number = [NSNumber numberWithDouble:distanceSquared];
+ [itemToClusterDistanceMap setObject:number forKey:key];
+ [itemToClusterMap setObject:cluster forKey:key];
+ [cluster addItem:nearbyItem];
+ }
+ [clusters addObject:cluster];
+ }
+ NSAssert(itemToClusterDistanceMap.count == _items.count,
+ @"All items should be mapped to a distance");
+ NSAssert(itemToClusterMap.count == _items.count,
+ @"All items should be mapped to a cluster");
+
+#if DEBUG
+ NSUInteger totalCount = 0;
+ for (id cluster in clusters) {
+ totalCount += cluster.count;
+ }
+ NSAssert(_items.count == totalCount, @"All clusters combined should make up original item set");
+#endif
+ return clusters;
+}
+
+#pragma mark Private
+
+- (double)distanceSquaredBetweenPointA:(GMSMapPoint)pointA andPointB:(GMSMapPoint)pointB {
+ double deltaX = pointA.x - pointB.x;
+ double deltaY = pointA.y - pointB.y;
+ return deltaX * deltaX + deltaY * deltaY;
+}
+
+@end
+
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUSimpleClusterAlgorithm.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUSimpleClusterAlgorithm.h
new file mode 100644
index 00000000..05d0a601
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUSimpleClusterAlgorithm.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUClusterAlgorithm.h"
+
+/**
+ * Not for production: used for experimenting with new clustering algorithms only.
+ */
+@interface GMUSimpleClusterAlgorithm : NSObject
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUSimpleClusterAlgorithm.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUSimpleClusterAlgorithm.m
new file mode 100644
index 00000000..eb0ce6d1
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUSimpleClusterAlgorithm.m
@@ -0,0 +1,71 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GMUSimpleClusterAlgorithm.h"
+
+#import "GMUStaticCluster.h"
+#import "GMUClusterItem.h"
+
+static const NSUInteger kClusterCount = 10;
+
+@implementation GMUSimpleClusterAlgorithm {
+ NSMutableArray> *_items;
+}
+
+- (instancetype)init {
+ if ((self = [super init])) {
+ _items = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)addItems:(NSArray> *)items {
+ [_items addObjectsFromArray:items];
+}
+
+- (void)removeItem:(id)item {
+ [_items removeObject:item];
+}
+
+- (void)clearItems {
+ [_items removeAllObjects];
+}
+
+- (NSArray> *)clustersAtZoom:(float)zoom {
+ NSMutableArray> *clusters =
+ [[NSMutableArray alloc] initWithCapacity:kClusterCount];
+
+ for (int i = 0; i < kClusterCount; ++i) {
+ if (i >= _items.count) break;
+ id item = _items[i];
+ [clusters addObject:[[GMUStaticCluster alloc] initWithPosition:item.position]];
+ }
+
+ NSUInteger clusterIndex = 0;
+ for (int i = kClusterCount; i < _items.count; ++i) {
+ id item = _items[i];
+ GMUStaticCluster *cluster = clusters[clusterIndex % kClusterCount];
+ [cluster addItem:item];
+ ++clusterIndex;
+ }
+ return clusters;
+}
+
+@end
+
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUWrappingDictionaryKey.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUWrappingDictionaryKey.h
new file mode 100644
index 00000000..bd47ec7e
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUWrappingDictionaryKey.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+/**
+ * Wraps an object which does not implement NSCopying to be used as NSDictionary keys.
+ * This class will forward -hash and -isEqual methods to the underlying object.
+ */
+@interface GMUWrappingDictionaryKey : NSObject
+
+- (instancetype)initWithObject:(id)object;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUWrappingDictionaryKey.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUWrappingDictionaryKey.m
new file mode 100644
index 00000000..9632b745
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/Algo/GMUWrappingDictionaryKey.m
@@ -0,0 +1,57 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GMUWrappingDictionaryKey.h"
+
+@implementation GMUWrappingDictionaryKey {
+ id _object;
+}
+
+- (instancetype)initWithObject:(id)object {
+ if ((self = [super init])) {
+ _object = object;
+ }
+ return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+ GMUWrappingDictionaryKey *newKey = [[self class] allocWithZone:zone];
+ if (newKey) {
+ newKey->_object = _object;
+ }
+ return newKey;
+}
+
+// Forwards hash to _object.
+- (NSUInteger)hash {
+ return [_object hash];
+}
+
+// Forwards isEqual to _object.
+- (BOOL)isEqual:(id)object {
+ if (self == object) return YES;
+
+ if ([object class] != [self class]) return NO;
+
+ GMUWrappingDictionaryKey *other = (GMUWrappingDictionaryKey *)object;
+ return [_object isEqual:other->_object];
+}
+
+@end
+
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMSMarker+GMUClusteritem.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMSMarker+GMUClusteritem.h
new file mode 100644
index 00000000..d3104177
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMSMarker+GMUClusteritem.h
@@ -0,0 +1,15 @@
+//
+// NSObject+GMSMarker_GMUClusteritem.h
+// DevApp
+//
+// Created by Alex Muramoto on 5/7/20.
+// Copyright © 2020 Google. All rights reserved.
+//
+
+#import
+#import
+#import "GMUClusterItem.h"
+
+@interface GMSMarker (GMSMarker_GMUClusteritem)
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMSMarker+GMUClusteritem.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMSMarker+GMUClusteritem.m
new file mode 100644
index 00000000..9e416b7c
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMSMarker+GMUClusteritem.m
@@ -0,0 +1,13 @@
+//
+// NSObject+GMSMarker_GMUClusteritem.m
+// DevApp
+//
+// Created by Alex Muramoto on 5/7/20.
+// Copyright © 2020 Google. All rights reserved.
+//
+
+#import "GMSMarker+GMUClusteritem.h"
+
+@implementation GMSMarker (GMSMarker_GMUClusteritem)
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUCluster.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUCluster.h
new file mode 100644
index 00000000..47945609
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUCluster.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+#import
+
+#import "GMUClusterItem.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Defines a generic cluster object.
+ */
+@protocol GMUCluster
+
+/**
+ * Returns the position of the cluster.
+ */
+@property(nonatomic, readonly) CLLocationCoordinate2D position;
+
+/**
+ * Returns the number of items in the cluster.
+ */
+@property(nonatomic, readonly) NSUInteger count;
+
+/**
+ * Returns a copy of the list of items in the cluster.
+ */
+@property(nonatomic, readonly) NSArray> *items;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterItem.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterItem.h
new file mode 100644
index 00000000..a1cb6f7a
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterItem.h
@@ -0,0 +1,33 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+/**
+ * This protocol defines the contract for a cluster item.
+ */
+@protocol GMUClusterItem
+
+/**
+ * Returns the position of the item.
+ */
+@property(nonatomic, readonly) CLLocationCoordinate2D position;
+
+@optional
+@property(nonatomic, copy, nullable) NSString* title;
+
+@property(nonatomic, copy, nullable) NSString* snippet;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager+Testing.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager+Testing.h
new file mode 100644
index 00000000..5b7ef78e
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager+Testing.h
@@ -0,0 +1,28 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUClusterManager.h"
+
+/**
+ * Extensions for testing purposes only.
+ */
+@interface GMUClusterManager (Testing)
+
+/**
+ * Returns in number of cluster requests.
+ */
+- (NSUInteger)clusterRequestCount;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager.h
new file mode 100644
index 00000000..bcbc515f
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager.h
@@ -0,0 +1,138 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import
+
+#import "GMUClusterAlgorithm.h"
+#import "GMUClusterItem.h"
+#import "GMUClusterRenderer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GMUClusterManager;
+
+/**
+ * Delegate for events on the GMUClusterManager.
+ */
+@protocol GMUClusterManagerDelegate
+
+@optional
+
+/**
+ * Called when the user taps on a cluster marker.
+ * @return YES if this delegate handled the tap event,
+ * and NO to pass this tap event to other handlers.
+ */
+- (BOOL)clusterManager:(GMUClusterManager *)clusterManager didTapCluster:(id)cluster;
+
+/**
+ * Called when the user taps on a cluster item marker.
+ * @return YES if this delegate handled the tap event,
+ * and NO to pass this tap event to other handlers.
+ */
+- (BOOL)clusterManager:(GMUClusterManager *)clusterManager
+ didTapClusterItem:(id)clusterItem;
+
+@end
+
+/**
+ * This class groups many items on a map based on zoom level.
+ * Cluster items should be added to the map via this class.
+ */
+@interface GMUClusterManager : NSObject
+
+/**
+ * The default initializer is not available. Use initWithMap:algorithm:renderer instead.
+ */
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Returns a new instance of the GMUClusterManager class defined by it's |algorithm| and |renderer|.
+ */
+- (instancetype)initWithMap:(GMSMapView *)mapView
+ algorithm:(id)algorithm
+ renderer:(id)renderer NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Returns the clustering algorithm.
+ */
+@property(nonatomic, readonly) id algorithm;
+
+/**
+ * GMUClusterManager |delegate|.
+ * To set it use the setDelegate:mapDelegate: method.
+ */
+@property(nonatomic, readonly, weak, nullable) id delegate;
+
+/**
+ * The GMSMapViewDelegate delegate that map events are being forwarded to.
+ * To set it use the setDelegate:mapDelegate: method.
+ */
+@property(nonatomic, readonly, weak, nullable) id mapDelegate;
+
+/**
+ * Sets a |mapDelegate| to listen to forwarded map events.
+ */
+- (void)setMapDelegate:(id _Nullable)mapDelegate;
+
+/**
+ * Sets GMUClusterManagerDelegate |delegate| and optionally
+ * provides a |mapDelegate| to listen to forwarded map events.
+ *
+ * NOTES: This method changes the |delegate| property of the
+ * managed |mapView| to this object, intercepting events that
+ * the GMUClusterManager wants to action or rebroadcast
+ * to the GMUClusterManagerDelegate. Any remaining events are
+ * then forwarded to the new |mapDelegate| provided here.
+ *
+ * EXAMPLE: [clusterManager setDelegate:self mapDelegate:_map.delegate];
+ * In this example self will receive type-safe GMUClusterManagerDelegate
+ * events and other map events will be forwarded to the current map delegate.
+ */
+- (void)setDelegate:(id _Nullable)delegate
+ mapDelegate:(id _Nullable)mapDelegate;
+
+/**
+ * Adds a cluster item to the collection.
+ */
+- (void)addItem:(id)item;
+
+/**
+ * Adds multiple cluster items to the collection.
+ */
+- (void)addItems:(NSArray> *)items;
+
+/**
+ * Removes a cluster item from the collection.
+ */
+- (void)removeItem:(id)item;
+
+/**
+ * Removes all items from the collection.
+ */
+- (void)clearItems;
+
+/**
+ * Called to arrange items into groups.
+ * - This method will be automatically invoked when the map's zoom level changes.
+ * - Manually invoke this method when new items have been added to rearrange items.
+ */
+- (void)cluster;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager.m
new file mode 100644
index 00000000..8232d7c8
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUClusterManager.m
@@ -0,0 +1,285 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUClusterManager+Testing.h"
+
+#import "GMUClusterRenderer.h"
+#import "GMUSimpleClusterAlgorithm.h"
+
+static NSString *const kGMUCameraKeyPath = @"camera";
+
+// How long to wait for a cluster request before actually performing the clustering operation
+// to avoid continuous clustering when the camera is moving which can affect performance.
+static const double kGMUClusterWaitIntervalSeconds = 0.2;
+
+@implementation GMUClusterManager {
+ // The map view that this object is associated with.
+ GMSMapView *_mapView;
+
+ // Position of the camera on the previous cluster invocation.
+ GMSCameraPosition *_previousCamera;
+
+ // Tracks number of cluster requests so that we can safely ignore stale (redundant) ones.
+ NSUInteger _clusterRequestCount;
+
+ // Renderer.
+ id _renderer;
+}
+
+- (instancetype)initWithMap:(GMSMapView *)mapView
+ algorithm:(id)algorithm
+ renderer:(id)renderer {
+ if ((self = [super init])) {
+ _algorithm = [[GMUSimpleClusterAlgorithm alloc] init];
+ _mapView = mapView;
+ _previousCamera = _mapView.camera;
+ _algorithm = algorithm;
+ _renderer = renderer;
+
+ [_mapView addObserver:self
+ forKeyPath:kGMUCameraKeyPath
+ options:NSKeyValueObservingOptionNew
+ context:nil];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [_mapView removeObserver:self forKeyPath:kGMUCameraKeyPath];
+}
+
+- (void) setMapDelegate:(id _Nullable)mapDelegate {
+ _mapView.delegate = self;
+ _mapDelegate = mapDelegate;
+}
+
+- (void)setDelegate:(id)delegate
+ mapDelegate:(id _Nullable)mapDelegate {
+ _delegate = delegate;
+ _mapView.delegate = self;
+ _mapDelegate = mapDelegate;
+}
+
+- (void)addItem:(id)item {
+ [_algorithm addItems:[[NSMutableArray alloc] initWithObjects:item, nil]];
+}
+
+- (void)addItems:(NSArray> *)items {
+ [_algorithm addItems:items];
+}
+
+- (void)removeItem:(id)item {
+ [_algorithm removeItem:item];
+}
+
+- (void)clearItems {
+ [_algorithm clearItems];
+ [self requestCluster];
+}
+
+- (void)cluster {
+ NSUInteger integralZoom = (NSUInteger)floorf(_mapView.camera.zoom + 0.5f);
+ NSArray> *clusters = [_algorithm clustersAtZoom:integralZoom];
+ [_renderer renderClusters:clusters];
+ _previousCamera = _mapView.camera;
+}
+
+#pragma mark GMSMapViewDelegate
+
+- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
+ if ([_delegate respondsToSelector:@selector(clusterManager:didTapCluster:)] &&
+ [marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
+ id cluster = marker.userData;
+ if ([_delegate clusterManager:self didTapCluster:cluster]) {
+ return YES;
+ }
+ }
+
+ if ([_delegate respondsToSelector:@selector(clusterManager:didTapClusterItem:)] &&
+ [marker.userData conformsToProtocol:@protocol(GMUClusterItem)]) {
+ id clusterItem = marker.userData;
+ if ([_delegate clusterManager:self didTapClusterItem:clusterItem]) {
+ return YES;
+ }
+ }
+
+ // Forward to _mapDelegate as a fallback.
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didTapMarker:)]) {
+ return [_mapDelegate mapView:mapView didTapMarker:marker];
+ }
+
+ return NO;
+}
+
+#pragma mark Delegate Forwards
+
+- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:willMove:)]) {
+ [_mapDelegate mapView:mapView willMove:gesture];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didChangeCameraPosition:)]) {
+ [_mapDelegate mapView:mapView didChangeCameraPosition:position];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:idleAtCameraPosition:)]) {
+ [_mapDelegate mapView:mapView idleAtCameraPosition:position];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didTapAtCoordinate:)]) {
+ [_mapDelegate mapView:mapView didTapAtCoordinate:coordinate];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didLongPressAtCoordinate:)]) {
+ [_mapDelegate mapView:mapView didLongPressAtCoordinate:coordinate];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didTapInfoWindowOfMarker:)]) {
+ [_mapDelegate mapView:mapView didTapInfoWindowOfMarker:marker];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didLongPressInfoWindowOfMarker:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didLongPressInfoWindowOfMarker:)]) {
+ [_mapDelegate mapView:mapView didLongPressInfoWindowOfMarker:marker];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didTapOverlay:)]) {
+ [_mapDelegate mapView:mapView didTapOverlay:overlay];
+ }
+}
+
+- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:markerInfoWindow:)]) {
+ return [_mapDelegate mapView:mapView markerInfoWindow:marker];
+ }
+ return nil;
+}
+
+- (void)mapView:(GMSMapView *)mapView
+ didTapPOIWithPlaceID:(NSString *)placeID
+ name:(NSString *)name
+ location:(CLLocationCoordinate2D)location {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didTapPOIWithPlaceID:name:location:)]) {
+ [_mapDelegate mapView:mapView didTapPOIWithPlaceID:placeID name:name location:location];
+ }
+}
+
+- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:markerInfoContents:)]) {
+ return [_mapDelegate mapView:mapView markerInfoContents:marker];
+ }
+ return nil;
+}
+
+- (void)mapView:(GMSMapView *)mapView didCloseInfoWindowOfMarker:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didCloseInfoWindowOfMarker:)]) {
+ [_mapDelegate mapView:mapView didCloseInfoWindowOfMarker:marker];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didBeginDraggingMarker:)]) {
+ [_mapDelegate mapView:mapView didBeginDraggingMarker:marker];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didEndDraggingMarker:)]) {
+ [_mapDelegate mapView:mapView didEndDraggingMarker:marker];
+ }
+}
+
+- (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker {
+ if ([_mapDelegate respondsToSelector:@selector(mapView:didDragMarker:)]) {
+ [_mapDelegate mapView:mapView didDragMarker:marker];
+ }
+}
+
+- (BOOL)didTapMyLocationButtonForMapView:(GMSMapView *)mapView {
+ if ([_mapDelegate respondsToSelector:@selector(didTapMyLocationButtonForMapView:)]) {
+ return [_mapDelegate didTapMyLocationButtonForMapView:mapView];
+ }
+ return NO;
+}
+
+- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
+ if ([_mapDelegate respondsToSelector:@selector(mapViewDidStartTileRendering:)]) {
+ [_mapDelegate mapViewDidStartTileRendering:mapView];
+ }
+}
+
+- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
+ if ([_mapDelegate respondsToSelector:@selector(mapViewDidFinishTileRendering:)]) {
+ [_mapDelegate mapViewDidFinishTileRendering:mapView];
+ }
+}
+
+#pragma mark Testing
+
+- (NSUInteger)clusterRequestCount {
+ return _clusterRequestCount;
+}
+
+#pragma mark Private
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context {
+ GMSCameraPosition *camera = _mapView.camera;
+ NSUInteger previousIntegralZoom = (NSUInteger)floorf(_previousCamera.zoom + 0.5f);
+ NSUInteger currentIntegralZoom = (NSUInteger)floorf(camera.zoom + 0.5f);
+ if (previousIntegralZoom != currentIntegralZoom) {
+ [self requestCluster];
+ } else {
+ [_renderer update];
+ }
+}
+
+- (void)requestCluster {
+ __weak GMUClusterManager *weakSelf = self;
+ ++_clusterRequestCount;
+ NSUInteger requestNumber = _clusterRequestCount;
+ dispatch_after(
+ dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kGMUClusterWaitIntervalSeconds * NSEC_PER_SEC)),
+ dispatch_get_main_queue(), ^{
+ GMUClusterManager *strongSelf = weakSelf;
+ if (strongSelf == nil) {
+ return;
+ }
+
+ // Ignore if there are newer requests.
+ if (requestNumber != strongSelf->_clusterRequestCount) {
+ return;
+ }
+ [strongSelf cluster];
+ });
+}
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUStaticCluster.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUStaticCluster.h
new file mode 100644
index 00000000..9a142105
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUStaticCluster.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUCluster.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Defines a cluster where its position is fixed upon construction.
+ */
+@interface GMUStaticCluster : NSObject
+
+/**
+ * The default initializer is not available. Use initWithPosition: instead.
+ */
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Returns a new instance of the GMUStaticCluster class defined by it's position.
+ */
+- (instancetype)initWithPosition:(CLLocationCoordinate2D)position NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Returns the position of the cluster.
+ */
+@property(nonatomic, readonly) CLLocationCoordinate2D position;
+
+/**
+ * Returns the number of items in the cluster.
+ */
+@property(nonatomic, readonly) NSUInteger count;
+
+/**
+ * Returns a copy of the list of items in the cluster.
+ */
+@property(nonatomic, readonly) NSArray> *items;
+
+/**
+ * Adds an item to the cluster.
+ */
+- (void)addItem:(id)item;
+
+/**
+ * Removes an item to the cluster.
+ */
+- (void)removeItem:(id)item;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUStaticCluster.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUStaticCluster.m
new file mode 100644
index 00000000..4fde3d83
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/GMUStaticCluster.m
@@ -0,0 +1,50 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GMUStaticCluster.h"
+
+@implementation GMUStaticCluster {
+ NSMutableArray> *_items;
+}
+
+- (instancetype)initWithPosition:(CLLocationCoordinate2D)position {
+ if ((self = [super init])) {
+ _items = [[NSMutableArray alloc] init];
+ _position = position;
+ }
+ return self;
+}
+
+- (NSUInteger)count {
+ return _items.count;
+}
+
+- (NSArray> *)items {
+ return [_items copy];
+}
+
+- (void)addItem:(id)item {
+ [_items addObject:item];
+}
+
+- (void)removeItem:(id)item {
+ [_items removeObject:item];
+}
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUClusterIconGenerator.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUClusterIconGenerator.h
new file mode 100644
index 00000000..36df0611
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUClusterIconGenerator.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+#import
+
+/**
+ * Defines a contract for cluster icon generation.
+ */
+@protocol GMUClusterIconGenerator
+
+/**
+ * Generates an icon with the given size.
+ */
+- (UIImage *)iconForSize:(NSUInteger)size;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUClusterRenderer.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUClusterRenderer.h
new file mode 100644
index 00000000..e7e06b58
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUClusterRenderer.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUCluster.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Defines a common contract for a cluster renderer.
+ */
+@protocol GMUClusterRenderer
+
+// Renders a list of clusters.
+- (void)renderClusters:(NSArray> *)clusters;
+
+// Notifies renderer that the viewport has changed and renderer needs to update.
+// For example new clusters may become visible and need to be shown on map.
+- (void)update;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator+Testing.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator+Testing.h
new file mode 100644
index 00000000..e48bd539
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator+Testing.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUDefaultClusterIconGenerator.h"
+
+/* Extensions for testing purposes only. */
+@interface GMUDefaultClusterIconGenerator (Testing)
+
+/* Draws |text| on top of an |image| and returns the resultant image. */
+- (UIImage *)iconForText:(NSString *)text withBaseImage:(UIImage *)image;
+
+/**
+ * Draws |text| on top of a circle whose background color is determined by |bucketIndex|
+ * and returns the resultant image.
+ */
+- (UIImage *)iconForText:(NSString *)text withBucketIndex:(NSUInteger)bucketIndex;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator.h
new file mode 100644
index 00000000..86814fcd
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+#import
+
+#import "GMUClusterIconGenerator.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class places clusters into range-based buckets of size to avoid having too many distinct
+ * cluster icons. For example a small cluster of 1 to 9 items will have a icon with a text label
+ * of 1 to 9. Whereas clusters with a size of 100 to 199 items will be placed in the 100+ bucket
+ * and have the '100+' icon shown.
+ * This caches already generated icons for performance reasons.
+ */
+@interface GMUDefaultClusterIconGenerator : NSObject
+
+/**
+ * Initializes the object with default buckets and auto generated background images.
+ */
+- (instancetype)init;
+
+/**
+ * Initializes the object with given |buckets| and auto generated background images.
+ */
+- (instancetype)initWithBuckets:(NSArray *)buckets;
+
+/**
+ * Initializes the class with a list of buckets and the corresponding background images.
+ * The backgroundImages array should ideally be big enough to hold the cluster label.
+ * Notes:
+ * - |buckets| should be strictly increasing. For example: @[@10, @20, @100, @1000].
+ * - |buckets| and |backgroundImages| must have equal non zero lengths.
+ */
+- (instancetype)initWithBuckets:(NSArray *)buckets
+ backgroundImages:(NSArray *)backgroundImages;
+
+/**
+ * Initializes the class with a list of buckets and the corresponding background colors.
+ *
+ * Notes:
+ * - |buckets| should be strictly increasing. For example: @[@10, @20, @100, @1000].
+ * - |buckets| and |backgroundColors| must have equal non zero lengths.
+ */
+- (instancetype)initWithBuckets:(NSArray *)buckets
+ backgroundColors:(NSArray *)backgroundColors;
+
+/**
+ * Generates an icon with the given size.
+ */
+- (UIImage *)iconForSize:(NSUInteger)size;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator.m
new file mode 100644
index 00000000..a33b019c
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterIconGenerator.m
@@ -0,0 +1,212 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GMUDefaultClusterIconGenerator+Testing.h"
+
+#define UIColorFromHEX(hexValue) \
+ [UIColor colorWithRed:((CGFloat)((hexValue & 0xff0000) >> 16)) / 255.0 \
+ green:((CGFloat)((hexValue & 0x00ff00) >> 8)) / 255.0 \
+ blue:((CGFloat)((hexValue & 0x0000ff) >> 0)) / 255.0 \
+ alpha:1.0]
+
+// Default bucket background colors when no background images are set.
+static NSArray *kGMUBucketBackgroundColors;
+
+@implementation GMUDefaultClusterIconGenerator {
+ NSCache *_iconCache;
+ NSArray *_buckets;
+ NSArray *_backgroundImages;
+ NSArray *_backgroundColors;
+}
+
++ (void)initialize {
+ kGMUBucketBackgroundColors = @[
+ UIColorFromHEX(0x0099cc),
+ UIColorFromHEX(0x669900),
+ UIColorFromHEX(0xff8800),
+ UIColorFromHEX(0xcc0000),
+ UIColorFromHEX(0x9933cc),
+ ];
+}
+
+- (instancetype)init {
+ if ((self = [super init]) != nil) {
+ _iconCache = [[NSCache alloc] init];
+ _buckets = @[ @10, @50, @100, @200, @1000 ];
+ _backgroundColors = [kGMUBucketBackgroundColors copy];
+ }
+ return self;
+}
+
+- (instancetype)initWithBuckets:(NSArray *)buckets
+ backgroundImages:(NSArray *)backgroundImages {
+ if ((self = [self initWithBuckets:buckets]) != nil) {
+ if (buckets.count != backgroundImages.count) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"buckets' size: %lu is not equal to backgroundImages' size: %lu",
+ (unsigned long)buckets.count, (unsigned long)backgroundImages.count];
+ }
+
+ _backgroundImages = [backgroundImages copy];
+ }
+ return self;
+}
+
+- (instancetype)initWithBuckets:(NSArray *)buckets
+ backgroundColors:(NSArray *)backgroundColors {
+ if ((self = [self initWithBuckets:buckets]) != nil) {
+ if (buckets.count != backgroundColors.count) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"buckets' size: %lu is not equal to backgroundColors' size: %lu",
+ (unsigned long) buckets.count, (unsigned long) backgroundColors.count];
+ }
+
+ _backgroundColors = [backgroundColors copy];
+ }
+ return self;
+}
+
+- (instancetype)initWithBuckets:(NSArray *)buckets {
+ if ((self = [self init]) != nil) {
+ if (buckets.count == 0) {
+ [NSException raise:NSInvalidArgumentException format:@"buckets are empty"];
+ }
+ for (int i = 0; i < buckets.count; ++i) {
+ if (buckets[i].longLongValue <= 0) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"buckets have non positive values"];
+ }
+ }
+ for (int i = 0; i < buckets.count - 1; ++i) {
+ if (buckets[i].longLongValue >= buckets[i+1].longLongValue) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"buckets are not strictly increasing"];
+ }
+ }
+ _buckets = [buckets copy];
+ }
+ return self;
+}
+
+- (UIImage *)iconForSize:(NSUInteger)size {
+ NSUInteger bucketIndex = [self bucketIndexForSize:size];
+ NSString *text;
+
+ // If size is smaller to first bucket size, use the size as is otherwise round it down to the
+ // nearest bucket to limit the number of cluster icons we need to generate.
+ if (size < _buckets[0].unsignedLongValue) {
+ text = [NSString stringWithFormat:@"%ld", (unsigned long)size];
+ } else {
+ text = [NSString stringWithFormat:@"%ld+", _buckets[bucketIndex].unsignedLongValue];
+ }
+ if (_backgroundImages != nil) {
+ UIImage *image = _backgroundImages[bucketIndex];
+ return [self iconForText:text withBaseImage:image];
+ }
+ return [self iconForText:text withBucketIndex:bucketIndex];
+}
+
+#pragma mark Private
+
+// Finds the smallest bucket which is greater than |size|. If none exists return the last bucket
+// index (i.e |_buckets.count - 1|).
+- (NSUInteger)bucketIndexForSize:(NSUInteger)size {
+ NSUInteger index = 0;
+ while (index + 1 < _buckets.count && _buckets[index + 1].unsignedLongValue <= size) {
+ ++index;
+ }
+ return index;
+}
+
+- (UIImage *)iconForText:(NSString *)text withBaseImage:(UIImage *)image {
+ UIImage *icon = [_iconCache objectForKey:text];
+ if (icon != nil) {
+ return icon;
+ }
+
+ UIFont *font = [UIFont boldSystemFontOfSize:12];
+ CGSize size = image.size;
+ UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
+ [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
+ CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
+
+ NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+ paragraphStyle.alignment = NSTextAlignmentCenter;
+ NSDictionary *attributes = @{
+ NSFontAttributeName : font,
+ NSParagraphStyleAttributeName : paragraphStyle,
+ NSForegroundColorAttributeName : [UIColor whiteColor]
+ };
+ CGSize textSize = [text sizeWithAttributes:attributes];
+ CGRect textRect = CGRectInset(rect, (rect.size.width - textSize.width) / 2,
+ (rect.size.height - textSize.height) / 2);
+ [text drawInRect:CGRectIntegral(textRect) withAttributes:attributes];
+
+ UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ [_iconCache setObject:newImage forKey:text];
+ return newImage;
+}
+
+- (UIImage *)iconForText:(NSString *)text withBucketIndex:(NSUInteger)bucketIndex {
+ UIImage *icon = [_iconCache objectForKey:text];
+ if (icon != nil) {
+ return icon;
+ }
+
+ UIFont *font = [UIFont boldSystemFontOfSize:14];
+ NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+ paragraphStyle.alignment = NSTextAlignmentCenter;
+ NSDictionary *attributes = @{
+ NSFontAttributeName : font,
+ NSParagraphStyleAttributeName : paragraphStyle,
+ NSForegroundColorAttributeName : [UIColor whiteColor]
+ };
+ CGSize textSize = [text sizeWithAttributes:attributes];
+
+ // Create an image context with a square shape to contain the text (with more padding for
+ // larger buckets).
+ CGFloat rectDimension = MAX(20, MAX(textSize.width, textSize.height)) + 3 * bucketIndex + 6;
+ CGRect rect = CGRectMake(0.f, 0.f, rectDimension, rectDimension);
+ UIGraphicsBeginImageContext(rect.size);
+
+ // Draw background circle.
+ UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
+ CGContextSaveGState(ctx);
+ bucketIndex = MIN(bucketIndex, _backgroundColors.count - 1);
+ UIColor *backColor = _backgroundColors[bucketIndex];
+ CGContextSetFillColorWithColor(ctx, backColor.CGColor);
+ CGContextFillEllipseInRect(ctx, rect);
+ CGContextRestoreGState(ctx);
+
+ // Draw text.
+ [[UIColor whiteColor] set];
+ CGRect textRect = CGRectInset(rect, (rect.size.width - textSize.width) / 2,
+ (rect.size.height - textSize.height) / 2);
+ [text drawInRect:CGRectIntegral(textRect) withAttributes:attributes];
+ UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ [_iconCache setObject:newImage forKey:text];
+ return newImage;
+}
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer+Testing.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer+Testing.h
new file mode 100644
index 00000000..e43491e8
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer+Testing.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2020 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUDefaultClusterRenderer.h"
+
+/* Extensions for testing purposes only. */
+@interface GMUDefaultClusterRenderer (Testing)
+
+- (NSArray> *)visibleClustersFromClusters:(NSArray> *)clusters;
+
+- (void)clearMarkersAnimated:(NSArray *)markers;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer.h
new file mode 100644
index 00000000..c9657080
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer.h
@@ -0,0 +1,162 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import "GMUClusterRenderer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GMSMapView;
+@class GMSMarker;
+
+@protocol GMUCluster;
+@protocol GMUClusterIconGenerator;
+@protocol GMUClusterRenderer;
+
+/**
+ * Delegate for id to provide extra functionality to the default
+ * renderer.
+ */
+@protocol GMUClusterRendererDelegate
+
+@optional
+
+/**
+ * Returns a marker for an |object|. The |object| can be either an id
+ * or an id. Use this delegate to control of the life cycle of
+ * the marker. Any properties set on the returned marker will be honoured except
+ * for: .position, .icon, .groundAnchor, .zIndex and .userData. To customize
+ * these properties use renderer:willRenderMarker.
+ * Note that changing a marker's position is not recommended because it will
+ * interfere with the marker animation.
+ */
+- (nullable GMSMarker *)renderer:(id)renderer markerForObject:(id)object;
+
+/**
+ * Raised when a marker (for a cluster or an item) is about to be added to the map.
+ * Use the marker.userData property to check whether it is a cluster marker or an
+ * item marker.
+ */
+- (void)renderer:(id)renderer willRenderMarker:(GMSMarker *)marker;
+
+/**
+ * Raised when a marker (for a cluster or an item) has just been added to the map
+ * and animation has been added.
+ * Use the marker.userData property to check whether it is a cluster marker or an
+ * item marker.
+ */
+- (void)renderer:(id)renderer didRenderMarker:(GMSMarker *)marker;
+
+@end
+
+/**
+ * Default cluster renderer which shows clusters as markers with specialized icons.
+ * There is logic to decide whether to expand a cluster or not depending on the number of
+ * items or the zoom level.
+ * There is also some performance optimization where only clusters within the visisble
+ * region are shown.
+ */
+@interface GMUDefaultClusterRenderer : NSObject
+
+/**
+ * Creates a new cluster renderer with a given map view and icon generator.
+ *
+ * @param mapView The map view to use.
+ * @param iconGenerator The icon generator to use. Can be subclassed if required.
+ */
+- (instancetype)initWithMapView:(GMSMapView *)mapView
+ clusterIconGenerator:(id)iconGenerator;
+
+/**
+ * Animates the clusters to achieve splitting (when zooming in) and merging
+ * (when zooming out) effects:
+ * - splitting large clusters into smaller ones when zooming in.
+ * - merging small clusters into bigger ones when zooming out.
+ *
+ * NOTES: the position to animate to/from for each cluster is heuristically
+ * calculated by finding the first overlapping cluster. This means that:
+ * - when zooming in:
+ * if a cluster on a higher zoom level is made from multiple clusters on
+ * a lower zoom level the split will only animate the new cluster from
+ * one of them.
+ * - when zooming out:
+ * if a cluster on a higher zoom level is split into multiple parts to join
+ * multiple clusters at a lower zoom level, the merge will only animate
+ * the old cluster into one of them.
+ * Because of these limitations, the actual cluster sizes may not add up, for
+ * example people may see 3 clusters of size 3, 4, 5 joining to make up a cluster
+ * of only 8 for non-hierachical clusters. And vice versa, a cluster of 8 may
+ * split into 3 clusters of size 3, 4, 5. For hierarchical clusters, the numbers
+ * should add up however.
+ *
+ * Defaults to YES.
+ */
+@property(nonatomic) BOOL animatesClusters;
+
+/**
+ * Determines the minimum number of cluster items inside a cluster.
+ * Clusters smaller than this threshold will be expanded.
+ *
+ * Defaults to 4.
+ */
+@property(nonatomic) NSUInteger minimumClusterSize;
+
+/**
+ * Sets the maximium zoom level of the map on which the clustering
+ * should be applied. At zooms above this level, clusters will be expanded.
+ * This is to prevent cases where items are so close to each other than they
+ * are always grouped.
+ *
+ * Defaults to 20.
+ */
+@property(nonatomic) NSUInteger maximumClusterZoom;
+
+/**
+ * Sets the animation duration for marker splitting/merging effects.
+ * Measured in seconds.
+ *
+ * Defaults to 0.5.
+ */
+@property(nonatomic) double animationDuration;
+
+/**
+ * Allows setting a zIndex value for the clusters. This becomes useful
+ * when using multiple cluster data sets on the map and require a predictable
+ * way of displaying multiple sets with a predictable layering order.
+ *
+ * If no specific zIndex is not specified during the initialization, the
+ * default zIndex is '1'. Larger zIndex values are drawn over lower ones
+ * similar to the zIndex value of GMSMarkers.
+ */
+@property(nonatomic) int zIndex;
+
+/** Sets to further customize the renderer. */
+@property(nonatomic, nullable, weak) id delegate;
+
+/**
+ * Returns currently active markers.
+ */
+@property(nonatomic, readonly) NSArray *markers;
+
+/**
+ * If returns NO, cluster items will be expanded and rendered as normal markers.
+ * Subclass can override this method to provide custom logic.
+ */
+- (BOOL)shouldRenderAsCluster:(id)cluster atZoom:(float)zoom;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer.m
new file mode 100644
index 00000000..1906828b
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Clustering/View/GMUDefaultClusterRenderer.m
@@ -0,0 +1,413 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import
+
+#import "GMUDefaultClusterRenderer.h"
+#import "GMUClusterIconGenerator.h"
+#import "GMUWrappingDictionaryKey.h"
+
+// Clusters smaller than this threshold will be expanded.
+static const NSUInteger kGMUMinClusterSize = 4;
+
+// At zooms above this level, clusters will be expanded.
+// This is to prevent cases where items are so close to each other than they are always grouped.
+static const float kGMUMaxClusterZoom = 20;
+
+// Animation duration for marker splitting/merging effects.
+static const double kGMUAnimationDuration = 0.5; // seconds.
+
+@implementation GMUDefaultClusterRenderer {
+ // Map view to render clusters on.
+ __weak GMSMapView *_mapView;
+
+ // Collection of markers added to the map.
+ NSMutableArray *_mutableMarkers;
+
+ // Icon generator used to create cluster icon.
+ id _clusterIconGenerator;
+
+ // Current clusters being rendered.
+ NSArray> *_clusters;
+
+ // Tracks clusters that have been rendered to the map.
+ NSMutableSet *_renderedClusters;
+
+ // Tracks cluster items that have been rendered to the map.
+ NSMutableSet *_renderedClusterItems;
+
+ // Stores previous zoom level to determine zooming direction (in/out).
+ float _previousZoom;
+
+ // Lookup map from cluster item to an old cluster.
+ NSMutableDictionary> *_itemToOldClusterMap;
+
+ // Lookup map from cluster item to a new cluster.
+ NSMutableDictionary> *_itemToNewClusterMap;
+}
+
+- (instancetype)initWithMapView:(GMSMapView *)mapView
+ clusterIconGenerator:(id)iconGenerator {
+ if ((self = [super init])) {
+ _mapView = mapView;
+ _mutableMarkers = [[NSMutableArray alloc] init];
+ _clusterIconGenerator = iconGenerator;
+ _renderedClusters = [[NSMutableSet alloc] init];
+ _renderedClusterItems = [[NSMutableSet alloc] init];
+ _animatesClusters = YES;
+ _minimumClusterSize = kGMUMinClusterSize;
+ _maximumClusterZoom = kGMUMaxClusterZoom;
+ _animationDuration = kGMUAnimationDuration;
+
+ _zIndex = 1;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self clear];
+}
+
+- (BOOL)shouldRenderAsCluster:(id)cluster atZoom:(float)zoom {
+ return cluster.count >= _minimumClusterSize && zoom <= _maximumClusterZoom;
+}
+
+#pragma mark GMUClusterRenderer
+
+- (void)renderClusters:(NSArray> *)clusters {
+ [_renderedClusters removeAllObjects];
+ [_renderedClusterItems removeAllObjects];
+
+ if (_animatesClusters) {
+ [self renderAnimatedClusters:clusters];
+ } else {
+ // No animation, just remove existing markers and add new ones.
+ _clusters = [clusters copy];
+ [self clearMarkers:_mutableMarkers];
+ _mutableMarkers = [[NSMutableArray alloc] init];
+ [self addOrUpdateClusters:clusters animated:NO];
+ }
+}
+
+- (void)renderAnimatedClusters:(NSArray> *)clusters {
+ float zoom = _mapView.camera.zoom;
+ BOOL isZoomingIn = zoom > _previousZoom;
+
+ [self prepareClustersForAnimation:clusters isZoomingIn:isZoomingIn];
+
+ _previousZoom = zoom;
+
+ _clusters = [clusters copy];
+
+ NSMutableArray *existingMarkers = _mutableMarkers;
+ _mutableMarkers = [[NSMutableArray alloc] init];
+
+ [self addOrUpdateClusters:clusters animated:isZoomingIn];
+
+ // If the marker was re-added, remove from existingMarkers which will be cleared
+ for (GMSMarker *visibleMarker in _mutableMarkers) {
+ [existingMarkers removeObject:visibleMarker];
+ }
+
+ if (isZoomingIn) {
+ [self clearMarkers:existingMarkers];
+ } else {
+ [self clearMarkersAnimated:existingMarkers];
+ }
+}
+
+- (void)clearMarkersAnimated:(NSArray *)markers {
+ // Remove existing markers: animate to nearest new cluster.
+ GMSCoordinateBounds *visibleBounds =
+ [[GMSCoordinateBounds alloc] initWithRegion:[_mapView.projection visibleRegion]];
+
+ for (GMSMarker *marker in markers) {
+ // If the marker for the attached userData has just been added, do not perform animation.
+ if ([_renderedClusterItems containsObject:marker.userData]) {
+ marker.map = nil;
+ continue;
+ }
+ // If the marker is outside the visible view port, do not perform animation.
+ if (![visibleBounds containsCoordinate:marker.position]) {
+ marker.map = nil;
+ continue;
+ }
+
+ // Find a candidate cluster to animate to.
+ id toCluster = nil;
+ if ([marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
+ id cluster = marker.userData;
+ toCluster = [self overlappingClusterForCluster:cluster itemMap:_itemToNewClusterMap];
+ } else {
+ GMUWrappingDictionaryKey *key =
+ [[GMUWrappingDictionaryKey alloc] initWithObject:marker.userData];
+ toCluster = [_itemToNewClusterMap objectForKey:key];
+ }
+ // If there is not near by cluster to animate to, do not perform animation.
+ if (toCluster == nil) {
+ marker.map = nil;
+ continue;
+ }
+
+ // All is good, perform the animation.
+ [CATransaction begin];
+ [CATransaction setAnimationDuration:_animationDuration];
+ CLLocationCoordinate2D toPosition = toCluster.position;
+ marker.layer.latitude = toPosition.latitude;
+ marker.layer.longitude = toPosition.longitude;
+ [CATransaction commit];
+ }
+
+ // Clears existing markers after animation has presumably ended.
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, _animationDuration * NSEC_PER_SEC),
+ dispatch_get_main_queue(), ^{
+ [self clearMarkers:markers];
+ });
+}
+
+// Called when camera is changed to reevaluate if new clusters need to be displayed because
+// they become visible.
+- (void)update {
+ [self addOrUpdateClusters:_clusters animated:NO];
+}
+
+- (NSArray *)markers {
+ return [_mutableMarkers copy];
+}
+
+#pragma mark Private
+
+// Builds lookup map for item to old clusters, new clusters.
+- (void)prepareClustersForAnimation:(NSArray> *)newClusters
+ isZoomingIn:(BOOL)isZoomingIn {
+ float zoom = _mapView.camera.zoom;
+
+ if (isZoomingIn) {
+ _itemToOldClusterMap =
+ [[NSMutableDictionary> alloc] init];
+ for (id cluster in _clusters) {
+ if (![self shouldRenderAsCluster:cluster atZoom:zoom]
+ && ![self shouldRenderAsCluster:cluster atZoom:_previousZoom]) {
+ continue;
+ }
+ for (id clusterItem in cluster.items) {
+ GMUWrappingDictionaryKey *key =
+ [[GMUWrappingDictionaryKey alloc] initWithObject:clusterItem];
+ [_itemToOldClusterMap setObject:cluster forKey:key];
+ }
+ }
+ _itemToNewClusterMap = nil;
+ } else {
+ _itemToOldClusterMap = nil;
+ _itemToNewClusterMap =
+ [[NSMutableDictionary> alloc] init];
+ for (id cluster in newClusters) {
+ if (![self shouldRenderAsCluster:cluster atZoom:zoom]) continue;
+ for (id clusterItem in cluster.items) {
+ GMUWrappingDictionaryKey *key =
+ [[GMUWrappingDictionaryKey alloc] initWithObject:clusterItem];
+ [_itemToNewClusterMap setObject:cluster forKey:key];
+ }
+ }
+ }
+}
+
+// Goes through each cluster |clusters| and add a marker for it if it is:
+// - inside the visible region of the camera.
+// - not yet already added.
+- (void)addOrUpdateClusters:(NSArray> *)clusters animated:(BOOL)animated {
+ GMSCoordinateBounds *visibleBounds =
+ [[GMSCoordinateBounds alloc] initWithRegion:[_mapView.projection visibleRegion]];
+
+ for (id cluster in clusters) {
+ if ([_renderedClusters containsObject:cluster]) continue;
+
+ BOOL shouldShowCluster = [visibleBounds containsCoordinate:cluster.position];
+ BOOL shouldRenderAsCluster = [self shouldRenderAsCluster:cluster atZoom: _mapView.camera.zoom];
+
+ if (!shouldShowCluster) {
+ for (id item in cluster.items) {
+ if (!shouldRenderAsCluster && [visibleBounds containsCoordinate:item.position]) {
+ shouldShowCluster = YES;
+ break;
+ }
+ if (animated) {
+ GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:item];
+ id oldCluster = [_itemToOldClusterMap objectForKey:key];
+ if (oldCluster != nil && [visibleBounds containsCoordinate:oldCluster.position]) {
+ shouldShowCluster = YES;
+ break;
+ }
+ }
+ }
+ }
+ if (shouldShowCluster) {
+ [self renderCluster:cluster animated:animated];
+ }
+ }
+}
+
+- (void)renderCluster:(id)cluster animated:(BOOL)animated {
+ float zoom = _mapView.camera.zoom;
+ if ([self shouldRenderAsCluster:cluster atZoom:zoom]) {
+ CLLocationCoordinate2D fromPosition = kCLLocationCoordinate2DInvalid;
+ if (animated) {
+ id fromCluster =
+ [self overlappingClusterForCluster:cluster itemMap:_itemToOldClusterMap];
+ animated = fromCluster != nil;
+ fromPosition = fromCluster.position;
+ }
+
+ UIImage *icon = [_clusterIconGenerator iconForSize:cluster.count];
+ GMSMarker *marker = [self markerWithPosition:cluster.position
+ from:fromPosition
+ userData:cluster
+ clusterIcon:icon
+ animated:animated];
+ [_mutableMarkers addObject:marker];
+ } else {
+ for (id item in cluster.items) {
+ GMSMarker *marker;
+ if ([item class] == [GMSMarker class]) {
+ marker = (GMSMarker *)item;
+ marker.map = _mapView;
+ } else {
+ CLLocationCoordinate2D fromPosition = kCLLocationCoordinate2DInvalid;
+ BOOL shouldAnimate = animated;
+ if (shouldAnimate) {
+ GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:item];
+ id fromCluster = [_itemToOldClusterMap objectForKey:key];
+ shouldAnimate = fromCluster != nil;
+ fromPosition = fromCluster.position;
+ }
+ marker = [self markerWithPosition:item.position
+ from:fromPosition
+ userData:item
+ clusterIcon:nil
+ animated:shouldAnimate];
+ if ([item respondsToSelector:@selector(title)]) {
+ marker.title = item.title;
+ }
+ if ([item respondsToSelector:@selector(snippet)]) {
+ marker.snippet = item.snippet;
+ }
+ }
+ [_mutableMarkers addObject:marker];
+ [_renderedClusterItems addObject:item];
+ }
+ }
+ [_renderedClusters addObject:cluster];
+}
+
+- (GMSMarker *)markerForObject:(id)object {
+ GMSMarker *marker;
+ if ([_delegate respondsToSelector:@selector(renderer:markerForObject:)]) {
+ marker = [_delegate renderer:self markerForObject:object];
+ }
+ return marker ?: [[GMSMarker alloc] init];
+}
+
+// Returns a marker at final position of |position| with attached |userData|.
+// If animated is YES, animates from the closest point from |points|.
+- (GMSMarker *)markerWithPosition:(CLLocationCoordinate2D)position
+ from:(CLLocationCoordinate2D)from
+ userData:(id)userData
+ clusterIcon:(UIImage *)clusterIcon
+ animated:(BOOL)animated {
+ GMSMarker *marker = [self markerForObject:userData];
+ CLLocationCoordinate2D initialPosition = animated ? from : position;
+ marker.position = initialPosition;
+ marker.userData = userData;
+ if (clusterIcon != nil) {
+ marker.icon = clusterIcon;
+ marker.groundAnchor = CGPointMake(0.5, 0.5);
+ }
+ marker.zIndex = _zIndex;
+
+ if ([_delegate respondsToSelector:@selector(renderer:willRenderMarker:)]) {
+ [_delegate renderer:self willRenderMarker:marker];
+ }
+ marker.map = _mapView;
+
+ if (animated) {
+ [CATransaction begin];
+ [CATransaction setAnimationDuration:_animationDuration];
+ marker.layer.latitude = position.latitude;
+ marker.layer.longitude = position.longitude;
+ [CATransaction commit];
+ }
+
+ if ([_delegate respondsToSelector:@selector(renderer:didRenderMarker:)]) {
+ [_delegate renderer:self didRenderMarker:marker];
+ }
+ return marker;
+}
+
+// Returns clusters which should be rendered and is inside the camera visible region.
+- (NSArray> *)visibleClustersFromClusters:(NSArray> *)clusters {
+ NSMutableArray *visibleClusters = [[NSMutableArray alloc] init];
+ float zoom = _mapView.camera.zoom;
+ GMSCoordinateBounds *visibleBounds =
+ [[GMSCoordinateBounds alloc] initWithRegion:[_mapView.projection visibleRegion]];
+ for (id cluster in clusters) {
+ if (![visibleBounds containsCoordinate:cluster.position]) continue;
+ if (![self shouldRenderAsCluster:cluster atZoom:zoom]) continue;
+ [visibleClusters addObject:cluster];
+ }
+ return visibleClusters;
+}
+
+// Returns the first cluster in |itemMap| that shares a common item with the input |cluster|.
+// Used for heuristically finding candidate cluster to animate to/from.
+- (id)overlappingClusterForCluster:
+ (id)cluster
+ itemMap:(NSDictionary> *)itemMap {
+ id found = nil;
+ for (id item in cluster.items) {
+ GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:item];
+ id candidate = [itemMap objectForKey:key];
+ if (candidate != nil) {
+ found = candidate;
+ break;
+ }
+ }
+ return found;
+}
+
+// Removes all existing markers from the attached map.
+- (void)clear {
+ [self clearMarkers:_mutableMarkers];
+ [_mutableMarkers removeAllObjects];
+ [_renderedClusters removeAllObjects];
+ [_renderedClusterItems removeAllObjects];
+ [_itemToNewClusterMap removeAllObjects];
+ [_itemToOldClusterMap removeAllObjects];
+ _clusters = nil;
+}
+
+- (void)clearMarkers:(NSArray *)markers {
+ for (GMSMarker *marker in markers) {
+ if ([marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
+ marker.userData = nil;
+ }
+ marker.map = nil;
+ }
+}
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeoJSONParser.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeoJSONParser.h
new file mode 100644
index 00000000..904e6e49
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeoJSONParser.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol GMUGeometryContainer;
+
+/**
+ * Instances of this class parse GeoJSON data. The parsed features are stored in NSArray objects
+ * which can then be passed to a GMUGeometryRenderer to display on a Google Map.
+ */
+@interface GMUGeoJSONParser : NSObject
+
+/**
+ * The features parsed from the GeoJSON file.
+ */
+@property(nonatomic, readonly) NSArray> *features;
+
+/**
+ * Initializes a GMUGeoJSONParser with GeoJSON data contained in a URL.
+ *
+ * @param url The url containing GeoJSON data.
+ */
+- (instancetype)initWithURL:(NSURL *)url;
+
+/**
+ * Initializes a GMUGeoJSONParser with GeoJSON data.
+ *
+ * @param data The GeoJSON data.
+ */
+- (instancetype)initWithData:(NSData *)data;
+
+/**
+ * Initializes a GMUGeoJSONParser with GeoJSON data contained in an input stream.
+ *
+ * @param stream The stream to use to access GeoJSON data.
+ */
+- (instancetype)initWithStream:(NSInputStream *)stream;
+
+/**
+ * Parses the stored GeoJSON data.
+ */
+- (void)parse;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeoJSONParser.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeoJSONParser.m
new file mode 100644
index 00000000..33364ad8
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeoJSONParser.m
@@ -0,0 +1,334 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUGeoJSONParser.h"
+
+#import
+
+#import
+
+#import "GMUFeature.h"
+#import "GMUGeometryCollection.h"
+#import "GMULineString.h"
+#import "GMUPoint.h"
+#import "GMUPolygon.h"
+
+static NSString *const kGMUTypeMember = @"type";
+static NSString *const kGMUIdMember = @"id";
+static NSString *const kGMUGeometryMember = @"geometry";
+static NSString *const kGMUGeometriesMember = @"geometries";
+static NSString *const kGMUPropertiesMember = @"properties";
+static NSString *const kGMUBoundingBoxMember = @"bbox";
+static NSString *const kGMUCoordinatesMember = @"coordinates";
+static NSString *const kGMUFeaturesMember = @"features";
+static NSString *const kGMUFeatureValue = @"Feature";
+static NSString *const kGMUFeatureCollectionValue = @"FeatureCollection";
+static NSString *const kGMUPointValue = @"Point";
+static NSString *const kGMUMultiPointValue = @"MultiPoint";
+static NSString *const kGMULineStringValue = @"LineString";
+static NSString *const kGMUMultiLineStringValue = @"MultiLineString";
+static NSString *const kGMUPolygonValue = @"Polygon";
+static NSString *const kGMUMultiPolygonValue = @"MultiPolygon";
+static NSString *const kGMUGeometryCollectionValue = @"GeometryCollection";
+static NSString *const kGMUGeometryRegex =
+ @"^(Point|MultiPoint|LineString|MultiLineString|Polygon|MultiPolygon|GeometryCollection)$";
+
+@implementation GMUGeoJSONParser {
+ /**
+ * The data object containing the GeoJSON to be parsed.
+ */
+ NSData *_data;
+
+ /**
+ * The stream containing the GeoJSON to be parsed.
+ */
+ NSInputStream *_stream;
+
+ /**
+ * The parsed GeoJSON file.
+ */
+ NSDictionary *_JSONDict;
+
+ /**
+ * The list of parsed Features.
+ */
+ NSMutableArray *_features;
+
+ /**
+ * The bounding box for a FeatureCollection. This will only be set when parsing a
+ * FeatureCollection.
+ */
+ GMSCoordinateBounds *_boundingBox;
+
+ /**
+ * The format that a geometry element may take.
+ */
+ NSRegularExpression *_geometryRegex;
+
+ /**
+ * The format that a multigeometry element may take.
+ */
+ NSRegularExpression *_multiGeometryRegex;
+
+ /**
+ * Whether the parser has completed parsing the input file.
+ */
+ BOOL _isParsed;
+}
+
+- (instancetype)initWithData:(NSData *)data {
+ if (self = [super init]) {
+ _data = data;
+ [self sharedInit];
+ }
+ return self;
+}
+
+- (instancetype)initWithStream:(NSInputStream *)stream {
+ if (self = [super init]) {
+ _stream = stream;
+ [self sharedInit];
+ }
+ return self;
+}
+
+- (instancetype)initWithURL:(NSURL *)url {
+ if (self = [super init]) {
+ _data = [[NSData alloc] initWithContentsOfURL:url];
+ [self sharedInit];
+ }
+ return self;
+}
+
+- (void)sharedInit {
+ _features = [[NSMutableArray alloc] init];
+ _geometryRegex = [NSRegularExpression regularExpressionWithPattern:kGMUGeometryRegex
+ options:0
+ error:nil];
+}
+
+- (NSArray *)features {
+ return _features;
+}
+
+- (void)parse {
+ if (_isParsed) {
+ return;
+ }
+ if (_data) {
+ _JSONDict = [NSJSONSerialization JSONObjectWithData:_data options:0 error:nil];
+ } else if (_stream) {
+ [_stream open];
+ _JSONDict = [NSJSONSerialization JSONObjectWithStream:_stream options:0 error:nil];
+ [_stream close];
+ }
+ if (!_JSONDict || ![_JSONDict isKindOfClass:[NSDictionary class]]) {
+ return;
+ }
+ NSString *type = [_JSONDict objectForKey:kGMUTypeMember];
+ if (type == nil) {
+ return;
+ }
+ GMUFeature *feature;
+ if ([type isEqual:kGMUFeatureValue]) {
+ feature = [self featureFromDict:_JSONDict];
+ if (feature) {
+ [_features addObject:feature];
+ }
+ } else if ([type isEqual:kGMUFeatureCollectionValue]) {
+ NSArray *featureCollection = [self featureCollectionFromDict:_JSONDict];
+ if (featureCollection) {
+ [_features addObjectsFromArray:featureCollection];
+ }
+ } else if ([_geometryRegex firstMatchInString:type
+ options:0
+ range:NSMakeRange(0, [type length])]) {
+ feature = [self featureFromGeometryDict:_JSONDict];
+ if (feature) {
+ [_features addObject:feature];
+ }
+ }
+ _isParsed = true;
+}
+
+- (GMUFeature *)featureFromDict:(NSDictionary *)feature {
+ id geometry;
+ NSString *identifier = [feature objectForKey:kGMUIdMember];
+ GMSCoordinateBounds *boundingBox;
+ NSDictionary *properties = [feature objectForKey:kGMUPropertiesMember];
+ if ([feature objectForKey:kGMUGeometryMember]) {
+ geometry = [self geometryFromDict:[feature objectForKey:kGMUGeometryMember]];
+ }
+ if (_boundingBox) {
+ boundingBox = _boundingBox;
+ } else if ([feature objectForKey:kGMUBoundingBoxMember]) {
+ boundingBox = [self boundingBoxFromCoordinates:[feature objectForKey:kGMUBoundingBoxMember]];
+ }
+ return [[GMUFeature alloc] initWithGeometry:geometry
+ identifier:identifier
+ properties:properties
+ boundingBox:boundingBox];
+}
+
+- (NSArray *)featureCollectionFromDict:(NSDictionary *)features {
+ NSMutableArray *parsedFeatures = [[NSMutableArray alloc] init];
+ if ([features objectForKey:kGMUBoundingBoxMember]) {
+ _boundingBox = [self boundingBoxFromCoordinates:[features objectForKey:kGMUBoundingBoxMember]];
+ }
+ NSArray *geoJSONFeatures = [features objectForKey:kGMUFeaturesMember];
+ for (NSDictionary *feature in geoJSONFeatures) {
+ if ([[feature objectForKey:kGMUTypeMember] isEqual:kGMUFeatureValue]) {
+ GMUFeature *parsedFeature = [self featureFromDict:feature];
+ if (parsedFeature) {
+ [parsedFeatures addObject:parsedFeature];
+ }
+ }
+ }
+ return parsedFeatures;
+}
+
+/**
+ * Creates a GMSCoordinateBounds object from a set of coordinates.
+ *
+ * @param coordinates The coordinates for the bounding box in the order west, south, east, north.
+ * @return A bounding box with the specified coordinates.
+ */
+- (GMSCoordinateBounds *)boundingBoxFromCoordinates:(NSArray *)coordinates {
+ CLLocationCoordinate2D southWest =
+ CLLocationCoordinate2DMake([coordinates[1] doubleValue], [coordinates[0] doubleValue]);
+ CLLocationCoordinate2D northEast =
+ CLLocationCoordinate2DMake([coordinates[3] doubleValue], [coordinates[2] doubleValue]);
+ return [[GMSCoordinateBounds alloc] initWithCoordinate:northEast coordinate:southWest];
+}
+
+- (id)geometryFromDict:(NSDictionary *)dict {
+ NSString *geometryType = [dict objectForKey:kGMUTypeMember];
+ NSArray *geometryArray;
+ if ([geometryType isEqual:kGMUGeometryCollectionValue]) {
+ geometryArray = [dict objectForKey:kGMUGeometriesMember];
+ } else if ([geometryType isEqual:kGMUGeometriesMember]) {
+ geometryArray = [dict objectForKey:kGMUGeometryCollectionValue];
+ } else if ([_geometryRegex firstMatchInString:geometryType
+ options:0
+ range:NSMakeRange(0, [geometryType length])]) {
+ geometryArray = [dict objectForKey:kGMUCoordinatesMember];
+ } else {
+ return nil;
+ }
+ return [self geometryWithGeometryType:geometryType geometryArray:geometryArray];
+}
+
+- (GMUFeature *)featureFromGeometryDict:(NSDictionary *)JSONGeometry {
+ id geometry = [self geometryFromDict:JSONGeometry];
+ if (geometry) {
+ return [[GMUFeature alloc] initWithGeometry:geometry
+ identifier:nil
+ properties:nil
+ boundingBox:nil];
+ }
+ return nil;
+}
+
+- (id)geometryWithGeometryType:(NSString *)geometryType
+ geometryArray:(NSArray *)geometryArray {
+ if ([geometryType isEqual:kGMUPointValue]) {
+ return [self pointWithCoordinate:geometryArray];
+ } else if ([geometryType isEqual:kGMUMultiPointValue]) {
+ return [self multiPointWithCoordinates:geometryArray];
+ } else if ([geometryType isEqual:kGMULineStringValue]) {
+ return [self lineStringWithCoordinates:geometryArray];
+ } else if ([geometryType isEqual:kGMUMultiLineStringValue]) {
+ return [self multiLineStringWithCoordinates:geometryArray];
+ } else if ([geometryType isEqual:kGMUPolygonValue]) {
+ return [self polygonWithCoordinates:geometryArray];
+ } else if ([geometryType isEqual:kGMUMultiPolygonValue]) {
+ return [self multiPolygonWithCoordinates:geometryArray];
+ } else if ([geometryType isEqual:kGMUGeometryCollectionValue]) {
+ return [self geometryCollectionWithGeometries:geometryArray];
+ }
+ return nil;
+}
+
+- (GMUPoint *)pointWithCoordinate:(NSArray *)coordinate {
+ return [[GMUPoint alloc] initWithCoordinate:[self locationFromCoordinate:coordinate].coordinate];
+}
+
+- (GMUGeometryCollection *)multiPointWithCoordinates:(NSArray *)coordinates {
+ NSMutableArray *points = [[NSMutableArray alloc] init];
+ for (NSArray *coordinate in coordinates) {
+ [points addObject:[self pointWithCoordinate:coordinate]];
+ }
+ return [[GMUGeometryCollection alloc] initWithGeometries:points];
+}
+
+- (GMULineString *)lineStringWithCoordinates:(NSArray *)coordinates {
+ GMSPath *path = [self pathFromCoordinateArray:coordinates];
+ return [[GMULineString alloc] initWithPath:path];
+}
+
+- (GMUGeometryCollection *)multiLineStringWithCoordinates:(NSArray *)coordinates {
+ NSMutableArray *lineStrings = [[NSMutableArray alloc] init];
+ for (NSArray *coordinate in coordinates) {
+ [lineStrings addObject:[self lineStringWithCoordinates:coordinate]];
+ }
+ return [[GMUGeometryCollection alloc] initWithGeometries:lineStrings];
+}
+
+- (GMUPolygon *)polygonWithCoordinates:(NSArray *)coordinates {
+ NSArray *pathArray = [self pathArrayFromCoordinateArrays:coordinates];
+ return [[GMUPolygon alloc] initWithPaths:pathArray];
+}
+
+- (GMUGeometryCollection *)multiPolygonWithCoordinates:(NSArray *)coordinates {
+ NSMutableArray *polygons = [[NSMutableArray alloc] init];
+ for (NSArray *coordinate in coordinates) {
+ [polygons addObject:[self polygonWithCoordinates:coordinate]];
+ }
+ return [[GMUGeometryCollection alloc] initWithGeometries:polygons];
+}
+
+- (GMUGeometryCollection *)geometryCollectionWithGeometries:(NSArray *)geometries {
+ NSMutableArray *elements = [[NSMutableArray alloc] init];
+ for (NSDictionary *geometry in geometries) {
+ id parsedGeometry = [self geometryFromDict:geometry];
+ if (parsedGeometry) {
+ [elements addObject:parsedGeometry];
+ }
+ }
+ return [[GMUGeometryCollection alloc] initWithGeometries:elements];
+}
+
+- (CLLocation *)locationFromCoordinate:(NSArray *)coordinate {
+ return [[CLLocation alloc] initWithLatitude:[coordinate[1] doubleValue]
+ longitude:[coordinate[0] doubleValue]];
+}
+
+- (GMSPath *)pathFromCoordinateArray:(NSArray *)coordinates {
+ GMSMutablePath *path = [[GMSMutablePath alloc] init];
+ for (NSArray *coordinate in coordinates) {
+ [path addCoordinate:[self locationFromCoordinate:coordinate].coordinate];
+ }
+ return path;
+}
+
+- (NSArray *)pathArrayFromCoordinateArrays:(NSArray *)coordinates {
+ NSMutableArray *parsedPaths = [[NSMutableArray alloc] init];
+ for (NSArray *coordinateArray in coordinates) {
+ [parsedPaths addObject:[self pathFromCoordinateArray:coordinateArray]];
+ }
+ return parsedPaths;
+}
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer+Testing.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer+Testing.h
new file mode 100644
index 00000000..f5132e6c
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer+Testing.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUGeometryRenderer.h"
+
+/* Extensions for testing purposes only. */
+@interface GMUGeometryRenderer (Testing)
+
+- (NSArray *)mapOverlays;
++ (UIImage *)imageFromPath:(NSString *)path;
+- (GMUStyle *)getStyleFromStyleMaps:(NSString *)styleUrl;
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer.h
new file mode 100644
index 00000000..17016af4
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer.h
@@ -0,0 +1,76 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+#import
+
+#import "GMUGeometryContainer.h"
+#import "GMUStyle.h"
+#import "GMUStyleMap.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Instances of this class render geometries generated by a GMUKMLParser or
+ * GMUGeoJSONParser object. These geometries can have specified style information
+ * applied to them when being rendered.
+ */
+@interface GMUGeometryRenderer : NSObject
+
+/**
+ * Initializes a new renderer.
+ *
+ * @param map the Google Map layer to render the geometries onto.
+ * @param geometries the geometries to be rendered.
+ */
+- (instancetype)initWithMap:(GMSMapView *)map
+ geometries:(NSArray> *)geometries;
+/**
+ * Initializes a new renderer.
+ *
+ * @param map the Google Map layer to render the geometries onto.
+ * @param geometries the geometries to be rendered.
+ * @param styles the styles to be applied to the geometries.
+ */
+- (instancetype)initWithMap:(GMSMapView *)map
+ geometries:(NSArray> *)geometries
+ styles:(NSArray *_Nullable)styles;
+/**
+ * Initializes a new renderer.
+ *
+ * @param map the Google Map layer to render the geometries onto.
+ * @param geometries the geometries to be rendered.
+ * @param styles the styles to be applied to the geometries.
+ * @param styleMaps the styleMaps to be applied to the geometries
+ */
+- (instancetype)initWithMap:(GMSMapView *)map
+ geometries:(NSArray> *)geometries
+ styles:(NSArray * _Nullable)styles
+ styleMaps:(NSArray *_Nullable)styleMaps;
+/**
+ * Renders the geometries onto the Google Map.
+ */
+- (void)render;
+
+/**
+ * Removes the rendered geometries from the Google Map. Markup that was not added by the renderer is
+ * preserved.
+ */
+- (void)clear;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer.m
new file mode 100644
index 00000000..40032880
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUGeometryRenderer.m
@@ -0,0 +1,331 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUGeometryRenderer.h"
+
+#import "GMUFeature.h"
+#import "GMUGeometryCollection.h"
+#import "GMUGroundOverlay.h"
+#import "GMULineString.h"
+#import "GMUPlacemark.h"
+#import "GMUPoint.h"
+#import "GMUPolygon.h"
+#import "GMUStyle.h"
+
+static NSString *const kStyleMapDefaultState = @"normal";
+
+@implementation GMUGeometryRenderer {
+ NSMutableArray *_mapOverlays;
+
+ /**
+ * The Google Map to render the placemarks onto.
+ */
+ __weak GMSMapView *_map;
+
+ /**
+ * The list of parsed geometries to render onto the map.
+ */
+ NSArray> *_geometryContainers;
+
+ /**
+ * The list of parsed styles to be applied to the placemarks.
+ */
+ NSDictionary *_styles;
+
+ /**
+ * The list of parsed style maps to be applied to the placemarks.
+ */
+ NSDictionary *_styleMaps;
+
+ /**
+ * The dispatch queue used to download images for ground overlays and point icons.
+ */
+ dispatch_queue_t _queue;
+
+ /**
+ * Whether the map has been marked as cleared.
+ */
+ BOOL _isMapCleared;
+}
+
+- (instancetype)initWithMap:(GMSMapView *)map
+ geometries:(NSArray> *)geometries {
+ return [self initWithMap:map geometries:geometries styles:nil];
+}
+
+- (instancetype)initWithMap:(GMSMapView *)map
+ geometries:(NSArray> *)geometries
+ styles:(NSArray *)styles {
+ return [self initWithMap:map geometries:geometries styles:styles styleMaps:nil];
+}
+
+- (instancetype)initWithMap:(GMSMapView *)map
+ geometries:(NSArray> *)geometries
+ styles:(NSArray *)styles
+ styleMaps:(NSArray *)styleMaps {
+ if (self = [super init]) {
+ _map = map;
+ _geometryContainers = geometries;
+ _styles = [[self class] stylesDictionaryFromArray:styles];
+ _styleMaps = [[self class] styleMapsDictionaryFromArray: styleMaps];
+ _mapOverlays = [[NSMutableArray alloc] init];
+ _queue = dispatch_queue_create("com.google.gmsutils", DISPATCH_QUEUE_CONCURRENT);
+ }
+ return self;
+}
+
+- (void)render {
+ _isMapCleared = NO;
+ [self renderGeometryContainers:_geometryContainers];
+}
+
+- (void)clear {
+ _isMapCleared = YES;
+ for (GMSOverlay *overlay in _mapOverlays) {
+ overlay.map = nil;
+ }
+ [_mapOverlays removeAllObjects];
+}
+
+- (NSArray *)mapOverlays {
+ return _mapOverlays;
+}
+
++ (NSDictionary *)stylesDictionaryFromArray:(NSArray *)styles {
+ NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:styles.count];
+ for (GMUStyle *style in styles) {
+ [dict setObject:style forKey:style.styleID];
+ }
+ return dict;
+}
+
++ (NSDictionary *)styleMapsDictionaryFromArray:(NSArray *)styleMaps {
+ NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:styleMaps.count];
+ for (GMUStyleMap *styleMap in styleMaps) {
+ [dict setObject:styleMap forKey:styleMap.styleMapId];
+ }
+ return dict;
+}
+
++ (UIImage *)imageFromPath:(NSString *)path {
+ // URLWithString returns nil for a path formatted as a local file reference.
+ NSURL *url = [NSURL URLWithString:path];
+
+ NSData *data;
+ if (url) {
+ // Get the image data from an external file.
+ data = [NSData dataWithContentsOfURL:url];
+ } else {
+ // Get the image data from a local file.
+ data = [NSData dataWithContentsOfFile:path];
+ }
+ return [UIImage imageWithData:data];
+}
+
+- (GMUStyle *)getStyleFromStyleMaps:(NSString *)styleUrl {
+ GMUStyleMap *styleMap = [_styleMaps objectForKey:styleUrl];
+ if (styleMap) {
+ for (GMUPair *pair in styleMap.pairs) {
+ if ([pair.key isEqual:kStyleMapDefaultState]) {
+ return [_styles objectForKey:pair.styleUrl];
+ }
+ }
+ }
+ return nil;
+}
+
+- (void)renderGeometryContainers:(NSArray> *)containers {
+ for (id container in containers) {
+ GMUStyle *style = container.style;
+ if (!style && [container isKindOfClass:[GMUPlacemark class]]) {
+ GMUPlacemark *placemark = container;
+ style = [_styles objectForKey:placemark.styleUrl];
+ // If not found, look it up in one of the StyleMaps
+ style = style ?: [self getStyleFromStyleMaps:placemark.styleUrl];
+ }
+ [self renderGeometryContainer:container style:style];
+ }
+}
+
+- (void)renderGeometryContainer:(id)container style:(GMUStyle *)style {
+ id geometry = container.geometry;
+ if ([geometry isKindOfClass:[GMUGeometryCollection class]]) {
+ [self renderMultiGeometry:geometry container:container style:style];
+ } else {
+ [self renderGeometry:geometry container:container style:style];
+ }
+}
+
+- (void)renderGeometry:(id)geometry
+ container:(id)container
+ style:(GMUStyle *)style {
+ if ([geometry isKindOfClass:[GMUPoint class]]) {
+ [self renderPoint:geometry container:container style:style];
+ } else if ([geometry isKindOfClass:[GMULineString class]]) {
+ [self renderLineString:geometry container:container style:style];
+ } else if ([geometry isKindOfClass:[GMUPolygon class]]) {
+ [self renderPolygon:geometry container:container style:style];
+ } else if ([geometry isKindOfClass:[GMUGroundOverlay class]]) {
+ [self renderGroundOverlay:geometry placemark:container style:style];
+ }
+}
+
+- (void)renderPoint:(GMUPoint *)point
+ container:(id)container
+ style:(GMUStyle *)style {
+ CLLocationCoordinate2D coordinate = point.coordinate;
+ GMSMarker *marker = [GMSMarker markerWithPosition:coordinate];
+ marker.tappable = true;
+ if ([container isKindOfClass:[GMUPlacemark class]]) {
+ GMUPlacemark *placemark = container;
+ marker.title = style.title ?: placemark.title;
+ marker.snippet = placemark.snippet;
+ } else {
+ marker.title = style.title;
+ }
+ if (style.anchor.x && style.anchor.y) {
+ marker.groundAnchor = style.anchor;
+ }
+ if (style.heading) {
+ marker.rotation = style.heading;
+ }
+ if (style.iconUrl) {
+ __weak GMSMarker *weakMarker = marker;
+ __weak GMSMapView *weakMap = _map;
+ dispatch_async(_queue, ^{
+ UIImage *image = [[self class] imageFromPath:style.iconUrl];
+ image = [UIImage imageWithCGImage:image.CGImage
+ scale:(image.scale * style.scale)
+ orientation:image.imageOrientation];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ GMSMarker *strongMarker = weakMarker;
+ GMSMapView *strongMap = weakMap;
+ strongMarker.icon = image;
+ if (!self->_isMapCleared) {
+ strongMarker.map = strongMap;
+ }
+ });
+ });
+ } else {
+ marker.map = _map;
+ }
+ [_mapOverlays addObject:marker];
+}
+
+- (void)renderLineString:(GMULineString *)lineString
+ container:(id)container
+ style:(GMUStyle *)style {
+ GMSPolyline *line = [GMSPolyline polylineWithPath:lineString.path];
+ if (style.width) {
+ line.strokeWidth = style.width;
+ }
+ if (style.strokeColor) {
+ line.strokeColor = style.strokeColor;
+ }
+ if ([container isKindOfClass:[GMUPlacemark class]]) {
+ GMUPlacemark *placemark = container;
+ line.title = placemark.title;
+ }
+ line.tappable = true;
+ line.map = _map;
+ [_mapOverlays addObject:line];
+}
+
+- (void)renderPolygon:(GMUPolygon *)polygon
+ container:(id)container
+ style:(GMUStyle *)style {
+ GMSPath *outerBoundaries = polygon.paths.firstObject;
+ NSArray *innerBoundaries = [[NSArray alloc] init];
+ if (polygon.paths.count > 1) {
+ innerBoundaries = [polygon.paths subarrayWithRange:NSMakeRange(1, polygon.paths.count - 1)];
+ }
+ NSMutableArray *holes = [[NSMutableArray alloc] init];
+ for (GMSPath *hole in innerBoundaries) {
+ [holes addObject:hole];
+ }
+ GMSPolygon *poly = [GMSPolygon polygonWithPath:outerBoundaries];
+ if (style.hasFill && style.fillColor) {
+ poly.fillColor = style.fillColor;
+ }
+ if (style.hasStroke) {
+ if (style.strokeColor) {
+ poly.strokeColor = style.strokeColor;
+ }
+ if (style.width) {
+ poly.strokeWidth = style.width;
+ }
+ }
+ if (holes.count) {
+ poly.holes = holes;
+ }
+ if ([container isKindOfClass:[GMUPlacemark class]]) {
+ GMUPlacemark *placemark = container;
+ poly.title = placemark.title;
+ }
+ poly.tappable = true;
+ poly.map = _map;
+ [_mapOverlays addObject:poly];
+}
+
+- (void)renderGroundOverlay:(GMUGroundOverlay *)overlay
+ placemark:(GMUPlacemark *)placemark
+ style:(GMUStyle *)style {
+ CLLocationCoordinate2D northEast = overlay.northEast;
+ CLLocationCoordinate2D southWest = overlay.southWest;
+ CLLocationDegrees centerLatitude = (northEast.latitude + southWest.latitude) / 2.0;
+ CLLocationDegrees centerLongitude = (northEast.longitude + southWest.longitude) / 2.0;
+ if (northEast.longitude < southWest.longitude) {
+ if (centerLongitude >= 0) {
+ centerLongitude -= 180;
+ } else {
+ centerLongitude += 180;
+ }
+ }
+ CLLocationCoordinate2D center = CLLocationCoordinate2DMake(centerLatitude, centerLongitude);
+ GMSCoordinateBounds *northEastBounds = [[GMSCoordinateBounds alloc] initWithCoordinate:northEast
+ coordinate:center];
+ GMSCoordinateBounds *southWestBounds = [[GMSCoordinateBounds alloc] initWithCoordinate:southWest
+ coordinate:center];
+ GMSCoordinateBounds *bounds = [northEastBounds includingBounds:southWestBounds];
+ GMSGroundOverlay *groundOverlay = [GMSGroundOverlay groundOverlayWithBounds:bounds icon:nil];
+ groundOverlay.tappable = true;
+ groundOverlay.zIndex = overlay.zIndex;
+ groundOverlay.bearing = overlay.rotation;
+ __weak GMSGroundOverlay *weakGroundOverlay = groundOverlay;
+ __weak GMSMapView *weakMap = _map;
+ dispatch_async(_queue, ^{
+ UIImage *image = [[self class] imageFromPath:overlay.href];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ GMSGroundOverlay *strongGroundOverlay = weakGroundOverlay;
+ GMSMapView *strongMap = weakMap;
+ strongGroundOverlay.icon = image;
+ if (!self->_isMapCleared) {
+ strongGroundOverlay.map = strongMap;
+ }
+ });
+ });
+ [_mapOverlays addObject:groundOverlay];
+}
+
+- (void)renderMultiGeometry:(id)geometry
+ container:(id)container
+ style:(GMUStyle *)style {
+ GMUGeometryCollection *multiGeometry = geometry;
+ for (id singleGeometry in multiGeometry.geometries) {
+ [self renderGeometry:singleGeometry container:container style:style];
+ }
+}
+
+@end
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUKMLParser.h b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUKMLParser.h
new file mode 100644
index 00000000..f5e12c91
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUKMLParser.h
@@ -0,0 +1,75 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+#import
+
+#import
+
+#import "GMUStyleMap.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GMUStyle;
+@protocol GMUGeometryContainer;
+
+/**
+ * Instances of this class parse KML documents in an event-driven manner. The
+ * parsed placemarks and styles are stored in NSArray objects which can then be
+ * passed to a GMUGeometryRenderer to display on a Google Map.
+ */
+@interface GMUKMLParser : NSObject
+
+/**
+ * The placemarks parsed from the KML file.
+ */
+@property(nonatomic, readonly) NSArray> *placemarks;
+
+/**
+ * The styles parsed from the KML file.
+ */
+@property(nonatomic, readonly) NSArray *styles;
+
+@property(nonatomic, readonly) NSArray *styleMaps;
+
+/**
+ * Parses the stored KML document.
+ */
+- (void)parse;
+
+/**
+ * Initializes a KMLParser with a KML file contained in a URL.
+ *
+ * @param url The url containing the KML file.
+ */
+- (instancetype)initWithURL:(NSURL *)url;
+
+/**
+ * Initializes a KMLParser with a KML file contained in a data file.
+ *
+ * @param data The data file containing the contents of a KML file.
+ */
+- (instancetype)initWithData:(NSData *)data;
+
+/**
+ * Initializes a KMLParser with a KML file contained in an input stream.
+ *
+ * @param stream The stream to use to access the KML file.
+ */
+- (instancetype)initWithStream:(NSInputStream *)stream;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUKMLParser.m b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUKMLParser.m
new file mode 100644
index 00000000..02018025
--- /dev/null
+++ b/ios/App/Pods/Google-Maps-iOS-Utils/src/Geometry/GMUKMLParser.m
@@ -0,0 +1,721 @@
+/* Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "GMUKMLParser.h"
+
+#import "GMUGeometry.h"
+#import "GMUGeometryCollection.h"
+#import "GMUGroundOverlay.h"
+#import "GMULineString.h"
+#import "GMUPlacemark.h"
+#import "GMUPoint.h"
+#import "GMUPolygon.h"
+#import "GMUStyle.h"
+#import "GMUPair.h"
+#import "GMUStyleMap.h"
+
+static NSString *const kGMUPlacemarkElementName = @"Placemark";
+static NSString *const kGMUGroundOverlayElementName = @"GroundOverlay";
+static NSString *const kGMUStyleElementName = @"Style";
+static NSString *const kGMUStyleMapElementName = @"StyleMap";
+static NSString *const kGMULineStyleElementName = @"LineStyle";
+static NSString *const kGMUPointElementName = @"Point";
+static NSString *const kGMULineStringElementName = @"LineString";
+static NSString *const kGMUPolygonElementName = @"Polygon";
+static NSString *const kGMUMultiGeometryElementName = @"MultiGeometry";
+static NSString *const kGMUInnerBoundariesAttributeName = @"innerBoundaries";
+static NSString *const kGMUOuterBoundariesAttributeName = @"outerBoundaries";
+static NSString *const kGMUHotspotElementName = @"hotSpot";
+static NSString *const kGMUCoordinatesElementName = @"coordinates";
+static NSString *const kGMURandomAttributeValue = @"random";
+static NSString *const kGMUFractionAttributeValue = @"fraction";
+static NSString *const kGMUNameElementName = @"name";
+static NSString *const kGMUDescriptionElementName = @"description";
+static NSString *const kGMURotationElementName = @"rotation";
+static NSString *const kGMUStyleUrlElementName = @"styleUrl";
+static NSString *const kGMUDrawOrderElementName = @"drawOrder";
+static NSString *const kGMUNorthElementName = @"north";
+static NSString *const kGMUEastElementName = @"east";
+static NSString *const kGMUSouthElementName = @"south";
+static NSString *const kGMUWestElementName = @"west";
+static NSString *const kGMUZIndexElementName = @"ZIndex";
+static NSString *const kGMUHrefElementName = @"href";
+static NSString *const kGMUTextElementName = @"text";
+static NSString *const kGMUScaleElementName = @"scale";
+static NSString *const kGMUXAttributeName = @"x";
+static NSString *const kGMUYAttributeName = @"y";
+static NSString *const kGMUXUnitsElementName = @"xunits";
+static NSString *const kGMUYUnitsElementName = @"yunits";
+static NSString *const kGMUIdAttributeName = @"id";
+static NSString *const kGMUOuterBoundaryIsElementName = @"outerBoundaryIs";
+static NSString *const kGMUHeadingElementName = @"heading";
+static NSString *const kGMUFillElementName = @"fill";
+static NSString *const kGMUOutlineElementName = @"outline";
+static NSString *const kGMUWidthElementName = @"width";
+static NSString *const kGMUColorElementName = @"color";
+static NSString *const kGMUColorModeElementName = @"colorMode";
+static NSString *const kGMUPairElementName = @"Pair";
+static NSString *const kGMUKeyAttributeValue = @"key";
+static NSString *const kGMUPairAttributeRegex = @"^(key|styleUrl)$";
+static NSString *const kGMUGeometryRegex = @"^(Point|LineString|Polygon|MultiGeometry)$";
+static NSString *const kGMUGeometryAttributeRegex =
+ @"^(coordinates|name|description|rotation|drawOrder|href|styleUrl)$";
+static NSString *const kGMUCompassRegex = @"^(north|east|south|west)$";
+static NSString *const kGMUBoundaryRegex = @"^(outerBoundaryIs|innerBoundaryIs)$";
+static NSString *const kGMUStyleRegex = @"^(Style|StyleMap|LineStyle|Pair)$";
+static NSString *const kGMUStyleAttributeRegex =
+ @"^(text|scale|heading|fill|outline|width|color|colorMode)$";
+static NSString *const kGMUStyleUrlRegex = @"#.+";
+
+/**
+ * Stores the current state of the parser with regards to the type of KML node that is being
+ * processed.
+ */
+typedef NS_OPTIONS(NSUInteger, GMUParserState) {
+ kGMUParserStatePlacemark = 1 << 0,
+ kGMUParserStateOuterBoundary = 1 << 1,
+ kGMUParserStateMultiGeometry = 1 << 2,
+ kGMUParserStateStyle = 1 << 3,
+ kGMUParserStateStyleMap = 1 << 4,
+ kGMUParserStateLineStyle = 1 << 5,
+ kGMUParserStatePair = 1 << 6,
+ kGMUParserStateLeafNode = 1 << 7,
+};
+
+@interface GMUKMLParser ()
+
+@end
+
+@implementation GMUKMLParser {
+ /**
+ * The XML parser used to read the specified document.
+ */
+ NSXMLParser *_parser;
+
+ /**
+ * The format that a geometry element may take.
+ */
+ NSRegularExpression *_geometryRegex;
+
+ /**
+ * The format that a compass coordinate element may take.
+ */
+ NSRegularExpression *_compassRegex;
+
+ /**
+ * The format that a boundary element may take.
+ */
+ NSRegularExpression *_boundaryRegex;
+
+ /**
+ * The format that a style element may take.
+ */
+ NSRegularExpression *_styleRegex;
+
+ /**
+ * The format that a style attribute element may take.
+ */
+ NSRegularExpression *_styleAttributeRegex;
+
+ /**
+ * The format that a style url element may take.
+ */
+ NSRegularExpression *_styleUrlRegex;
+
+ /**
+ * The format that a geometry attribute element may take.
+ */
+ NSRegularExpression *_geometryAttributeRegex;
+
+ /**
+ * The format that a pair in a style map may take
+ */
+ NSRegularExpression *_pairAttributeRegex;
+
+ /**
+ * The list of placemarks that have been parsed.
+ */
+ NSMutableArray *_placemarks;
+
+ /**
+ * The list of styles that have been parsed.
+ */
+ NSMutableArray *_styles;
+
+ /**
+ * The list of styles maps that have been parsed.
+ */
+ NSMutableArray *_styleMaps;
+
+ /**
+ * The list of pairs that currently parsed style map contains
+ */
+ NSMutableArray *_pairs;
+
+ /**
+ * The characters contained within the element being parsed.
+ */
+ NSMutableString *_characters;
+
+ /**
+ * The properties to be propagated into the KMLPlacemark object being parsed.
+ */
+ id _geometry;
+ NSMutableArray> *_geometries;
+ NSString *_title;
+ NSString *_snippet;
+ GMUStyle *_inlineStyle;
+ NSString *_styleUrl;
+
+ /**
+ * The properties to be propagated into the KMLStyle object being parsed.
+ */
+ NSString *_styleID;
+ UIColor *_strokeColor;
+ UIColor *_fillColor;
+ CGFloat _width;
+ CGFloat _scale;
+ CGFloat _heading;
+ CGPoint _anchor;
+ NSString *_strokeColorMode;
+ NSString *_fillColorMode;
+ NSString *_iconUrl;
+ NSString *_styleTitle;
+ BOOL _hasFill;
+ BOOL _hasStroke;
+
+ /**
+ * The properties to be propagated into the KMLElement object being parsed.
+ */
+ NSMutableDictionary *_attributes;
+ NSString *_geometryType;
+
+ /**
+ * The properties to be propagated into the KMLPair object being parsed.
+ */
+ NSString *_key;
+
+ /**
+ * The current state of the parser.
+ */
+ GMUParserState _parserState;
+}
+
+- (instancetype)initWithParser:(NSXMLParser *)parser {
+ if (self = [super init]) {
+ _parser = parser;
+ _placemarks = [[NSMutableArray alloc] init];
+ _styles = [[NSMutableArray alloc] init];
+ _styleMaps = [[NSMutableArray alloc] init];
+ _pairs = [[NSMutableArray alloc] init];
+ _geometries = [[NSMutableArray alloc] init];
+ _attributes = [[NSMutableDictionary alloc] init];
+
+ _geometryRegex = [NSRegularExpression regularExpressionWithPattern:kGMUGeometryRegex
+ options:0
+ error:nil];
+ _compassRegex = [NSRegularExpression regularExpressionWithPattern:kGMUCompassRegex
+ options:0
+ error:nil];
+ _boundaryRegex = [NSRegularExpression regularExpressionWithPattern:kGMUBoundaryRegex
+ options:0
+ error:nil];
+ _styleRegex = [NSRegularExpression regularExpressionWithPattern:kGMUStyleRegex
+ options:0
+ error:nil];
+ _styleAttributeRegex = [NSRegularExpression regularExpressionWithPattern:kGMUStyleAttributeRegex
+ options:0
+ error:nil];
+ _geometryAttributeRegex =
+ [NSRegularExpression regularExpressionWithPattern:kGMUGeometryAttributeRegex
+ options:0
+ error:nil];
+ _styleUrlRegex = [NSRegularExpression regularExpressionWithPattern:kGMUStyleUrlRegex
+ options:0
+ error:nil];
+
+ _pairAttributeRegex = [NSRegularExpression regularExpressionWithPattern:kGMUPairAttributeRegex
+ options:0
+ error:nil];
+ _hasFill = YES;
+ _hasStroke = YES;
+ [_parser setDelegate:self];
+ }
+ return self;
+}
+
+- (instancetype)initWithURL:(NSURL *)url {
+ return [self initWithParser:[[NSXMLParser alloc] initWithContentsOfURL:url]];
+}
+
+- (instancetype)initWithData:(NSData *)data {
+ return [self initWithParser:[[NSXMLParser alloc] initWithData:data]];
+}
+
+- (instancetype)initWithStream:(NSInputStream *)stream {
+ return [self initWithParser:[[NSXMLParser alloc] initWithStream:stream]];
+}
+
+- (NSArray