├── .github └── workflows │ ├── build_test.yml │ └── deploy_snapshot.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle ├── gradle-maven-push.gradle ├── gradle.properties └── src │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── geolocate_user.js │ │ ├── google_map.html │ │ ├── images │ │ │ ├── marker-icon.png │ │ │ └── marker-shadow.png │ │ ├── leaflet_1.3.1 │ │ │ ├── leaflet.css │ │ │ └── leaflet.js │ │ ├── leaflet_map.html │ │ ├── mapbox.html │ │ └── proj4leaflet.min.js │ ├── java │ │ └── com │ │ │ └── airbnb │ │ │ └── android │ │ │ └── airmapview │ │ │ ├── AirGoogleMapOptions.java │ │ │ ├── AirMapGeoJsonLayer.java │ │ │ ├── AirMapInterface.java │ │ │ ├── AirMapMarker.java │ │ │ ├── AirMapPolygon.java │ │ │ ├── AirMapPolyline.java │ │ │ ├── AirMapType.java │ │ │ ├── AirMapUtils.java │ │ │ ├── AirMapView.java │ │ │ ├── AirMapViewBuilder.java │ │ │ ├── AirMapViewTypes.java │ │ │ ├── DefaultAirMapViewBuilder.java │ │ │ ├── FixedWebView.java │ │ │ ├── GoogleChinaMapType.java │ │ │ ├── GoogleChinaWebViewMapFragment.java │ │ │ ├── GoogleWebMapType.java │ │ │ ├── GoogleWebViewMapFragment.java │ │ │ ├── LeafletBaiduMapType.java │ │ │ ├── LeafletDivIcon.java │ │ │ ├── LeafletGaodeMapType.java │ │ │ ├── LeafletGoogleChinaMapType.java │ │ │ ├── LeafletGoogleMapType.java │ │ │ ├── LeafletMapType.java │ │ │ ├── LeafletWebViewMapFragment.java │ │ │ ├── MapLaidOutCheck.kt │ │ │ ├── MapType.java │ │ │ ├── MapboxWebMapType.java │ │ │ ├── MapboxWebMapViewBuilder.java │ │ │ ├── MapboxWebViewMapFragment.java │ │ │ ├── NativeAirMapViewBuilder.java │ │ │ ├── NativeGoogleMapFragment.java │ │ │ ├── RuntimePermissionUtils.java │ │ │ ├── WebAirMapViewBuilder.java │ │ │ ├── WebViewMapFragment.java │ │ │ └── listeners │ │ │ ├── InfoWindowCreator.java │ │ │ ├── OnCameraChangeListener.java │ │ │ ├── OnCameraMoveListener.java │ │ │ ├── OnInfoWindowClickListener.java │ │ │ ├── OnLatLngScreenLocationCallback.java │ │ │ ├── OnMapBoundsCallback.java │ │ │ ├── OnMapClickListener.java │ │ │ ├── OnMapInitializedListener.java │ │ │ ├── OnMapLoadedListener.java │ │ │ ├── OnMapMarkerClickListener.java │ │ │ ├── OnMapMarkerDragListener.java │ │ │ └── OnSnapshotReadyListener.java │ └── res │ │ ├── layout │ │ ├── fragment_webview.xml │ │ └── map_view.xml │ │ └── values │ │ ├── dimens.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── airbnb │ └── android │ └── airmapview │ ├── AirMapTypeTest.java │ ├── DefaultAirMapViewBuilderTest.java │ ├── NativeAirMapViewBuilderTest.java │ └── WebAirMapViewBuilderTest.java ├── proguard-rules.pro ├── sample ├── .gitignore ├── aars │ └── amazon-maps-api-2.0.aar ├── build.gradle ├── debug.keystore ├── proguard-rules.pro └── src │ ├── debug │ └── res │ │ └── values │ │ └── google_maps_api.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── airbnb │ │ │ └── airmapview │ │ │ └── sample │ │ │ ├── LogsAdapter.java │ │ │ ├── MainActivity.java │ │ │ └── Util.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── list_item_bitmap.xml │ │ └── list_item_text.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── icon_location_pin.png │ │ ├── raw │ │ └── google.json │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── release │ └── res │ └── values │ └── google_maps_api.xml ├── screenshots ├── google_maps_v2.png └── google_web_maps.png └── settings.gradle /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build/Test 2 | 3 | on: 4 | # Trigger on every pull request 5 | pull_request: 6 | # This is reused in deploy_snapshot.yml 7 | workflow_call: 8 | jobs: 9 | build-test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout AirMapView 13 | uses: actions/checkout@v2 14 | - name: Build/Test AirMapView 15 | run: ./gradlew check -------------------------------------------------------------------------------- /.github/workflows/deploy_snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Deploy snapshot 2 | 3 | on: 4 | # Trigger ONLY on push to master branch 5 | push: 6 | branches: 7 | - master 8 | jobs: 9 | build-test: 10 | uses: airbnb/AirMapView/.github/workflows/build_test.yml@master 11 | deploy-snapshot: 12 | needs: build-test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout AirMapView 16 | uses: actions/checkout@v2 17 | - uses: actions/setup-java@v2 18 | with: 19 | distribution: liberica 20 | java-version: '8' 21 | - name: Create sytem gradle.properties 22 | env: 23 | GRADLE_PROPERTIES: ${{ secrets.GRADLE_PROPERTIES }} 24 | shell: bash 25 | run: | 26 | mkdir -p ~/.gradle/ 27 | echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV 28 | echo "${GRADLE_PROPERTIES}" > ~/.gradle/gradle.properties 29 | - name: Deploy new snapshot of AirMapView 30 | shell: bash 31 | run: ./gradlew uploadArchives 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ant 2 | bin 3 | build 4 | gen 5 | out 6 | lib 7 | 8 | # intellij 9 | .idea 10 | *.iml 11 | 12 | # eclipse 13 | .classpath 14 | .project 15 | .settings 16 | .DS_Store 17 | local.properties 18 | .gradle -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.8.0 Aug 7, 2018 2 | 3 | * Added support for marker z-index (PR #126) 4 | 5 | # 1.7.0 Jul 10, 2018 6 | 7 | * Added Leaflet map type which supports Google, Baidu and Gaode map (PR #123) 8 | 9 | # 1.6.0 Jan 16, 2018 10 | 11 | * Fix enabling location and show location button (PR #117) 12 | 13 | # 1.3.4 April 6, 2016 14 | 15 | * Adds an interface to get a bitmap snapshot of an AirMapView (PR #86) 16 | 17 | # 1.3.3 March 28, 2016 18 | 19 | * Web map bug fixes (PR #76) 20 | * Use `AirMapMarker` in marker click listeners and info window creator. (PR #67) 21 | * Added some `toBuilder()` to `AirMapMarker` and `bitmapDescriptor(BitmapDescriptor)` to `AirMapMarker.Builder` (PR #84) 22 | 23 | # 1.3.2 February 19, 2016 24 | 25 | * Added support for drawing GeoJson layers (PR #80) 26 | * Disable file access on Webview and prevent content success (PR #72) 27 | 28 | # 1.3.1 January 6, 2016 29 | 30 | * Added support for drawing polygons (PR #64) 31 | * Fixed runtime permission for Android M (PR #65) 32 | * Fixed drawing markers for China map type (PR #68) 33 | 34 | # 1.3.0 September 28, 2015 35 | 36 | * Android M support 37 | * Mapbox support (PR #49) 38 | * Fix google map info window size (PR #53) 39 | 40 | # 1.2.0 41 | 42 | * Updates project dependencies (compile sdk version, build tools, appcompat and play services) to latest 43 | * Adds snippet field to AirMapMarker (PR #39) 44 | * Add geolocation support for tracking user location (PR #45) 45 | 46 | # 1.1.5 47 | 48 | * Add the ability to enable/disable the map toolbar (#37) 49 | * Updates appcompat GMS dependencies. Removes ic_launcher icon from library code (#38) 50 | 51 | # 1.1.4 52 | 53 | * Fix getScreenLocation for web maps 54 | 55 | # 1.1.3 56 | 57 | * Remove Amazon AAR temporarily until we make it an optional dependency 58 | 59 | # 1.1.2 60 | 61 | * Adds the ability to retrieve the screen location from a Lat/Lng 62 | * You can now set a Bitmap icon in the `setIcon()`, currently only supported by Native Google Maps. 63 | 64 | # 1.1.1 65 | 66 | * Initial open source release 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # AirMapView is an Open Source Project 2 | 3 | (borrowed from the excellent [node-levelup CONTRIBUTING.md](https://github.com/rvagg/node-levelup/blob/master/CONTRIBUTING.md) 4 | 5 | ----------------------------------------- 6 | 7 | ## What? 8 | 9 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 10 | 11 | ## Rules 12 | 13 | There are a few basic ground-rules for contributors: 14 | 15 | 1. **No `--force` pushes** or modifying the Git history in any way. 16 | 1. **Non-master branches** ought to be used for ongoing work. 17 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 18 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 19 | 1. Contributors should attempt to adhere to the prevailing code-style. 20 | 1. Please follow the [Google Java Code Style Guide](https://google-styleguide.googlecode.com/svn/trunk/javaguide.html). You can import the [XML scheme](https://code.google.com/p/google-styleguide/source/browse/trunk/intellij-java-google-style.xml) and import it into Android Studio by copying it into the ``~/Library/Preferences/AndroidStudio/codestyles`` directory. 21 | 22 | ## Releases 23 | 24 | Declaring formal releases remains the prerogative of the project maintainer. 25 | 26 | ## Changes to this arrangement 27 | 28 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Airbnb 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AirMapView 2 | 3 | [![Build Status](https://travis-ci.org/airbnb/AirMapView.svg)](https://travis-ci.org/airbnb/AirMapView) 4 | 5 | AirMapView is a view abstraction that enables interactive maps 6 | for devices with and without Google Play Services. It is built 7 | to support multiple native map providers including Google Maps V2 and soon Amazon Maps V2. 8 | If a device does not have any supported native map provider, AirMapView 9 | will fallback to a web based map provider (currently Google Maps). Easy to integrate, it is a drop-in replacement for the Google Maps V2 package. AirMapView's original author is [Nick Adams](https://github.com/nwadams). 10 | 11 | * [Features](#features) 12 | * [Download](#download) 13 | * [How to Use](#how-to-use) 14 | * [Sample App](#sample-app) 15 | 16 | ## Features 17 | 18 | * Google Maps V2 19 | * Swap map providers at runtime 20 | * Web based maps for devices without Google Play Services 21 | 22 | ![](screenshots/google_maps_v2.png) 23 | ![](screenshots/google_web_maps.png) 24 | 25 | 26 | 27 | ## Download 28 | 29 | Grab via Gradle: 30 | 31 | ```groovy 32 | compile 'com.airbnb.android:airmapview:1.8.0' 33 | ``` 34 | 35 | Snapshots of the development version are available in [Sonatype's `snapshots` repository](https://oss.sonatype.org/content/repositories/snapshots/). 36 | 37 | ## Sample App 38 | The project includes a sample app which uses AirMapView. The sample app allows toggling between map providers, exemplifies adding map markers, and displays various callback information. The sample project can be built manually or you can [download the APK](https://www.dropbox.com/s/8gcxn2ouc44t53x/AirMapView-Sample.apk?dl=0). 39 | 40 | ## How to Use 41 | 42 | 1. Define `AirMapView` in your layout file 43 | ```xml 44 | 48 | ``` 49 | 50 | 1. Initialize in code 51 | ```java 52 | mapView = (AirMapView) findViewById(R.id.map_view); 53 | mapView.initialize(getSupportFragmentManager()); 54 | ``` 55 | 56 | 1. Add markers/polylines/polygons 57 | ```java 58 | map.addMarker(new AirMapMarker(latLng, markerId) 59 | .setTitle("Airbnb HQ") 60 | .setIconId(R.drawable.icon_location_pin)); 61 | ``` 62 | 63 | ## Mapbox Web setup 64 | To use Mapbox Web maps in AirMapView, you'll need to [sign up for a free account with Mapbox](https://www.mapbox.com/signup/). From there you'll use an [Access Token](https://www.mapbox.com/help/define-access-token/) and [Map ID](https://www.mapbox.com/help/define-map-id/) in your AirMapView app. They're are then included in your app's `AndroidManifest.xml` file as `meta-data` fields. 65 | 66 | ```xml 67 | 70 | 73 | ``` 74 | 75 | ## Native Google Maps setup 76 | 77 | With AirMapView, to support native Google maps using the [Google Maps v2](https://developers.google.com/maps/documentation/android/) SDK you will still need to set up the Google Maps SDK as described [here](https://developers.google.com/maps/documentation/android/start#getting_the_google_maps_android_api_v2). Follow all the instructions except the one about adding a map since AirMapView takes care of that for you. See the sample app for more information about how to set up the maps SDK. 78 | 79 | License 80 | -------- 81 | 82 | Copyright 2015 Airbnb, Inc. 83 | 84 | Licensed under the Apache License, Version 2.0 (the "License"); 85 | you may not use this file except in compliance with the License. 86 | You may obtain a copy of the License at 87 | 88 | http://www.apache.org/licenses/LICENSE-2.0 89 | 90 | Unless required by applicable law or agreed to in writing, software 91 | distributed under the License is distributed on an "AS IS" BASIS, 92 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 93 | See the License for the specific language governing permissions and 94 | limitations under the License. 95 | 96 | 97 | [1]: http://airbnb.github.io/airbnb/AirMapView/ 98 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing 2 | ======== 3 | 4 | 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 5 | 2. Update the `CHANGELOG.md` for the impending release. 6 | 3. Update the `README.md` with the new version. 7 | 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 8 | 5. `git tag -a X.Y.X -m "Version X.Y.Z"` (where X.Y.Z is the new version) 9 | 6. `./gradlew clean uploadArchives` 10 | 7. Update the `gradle.properties` to the next SNAPSHOT version. 11 | 8. `git commit -am "Prepare next development version."` 12 | 9. `git push && git push --tags` 13 | 10. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.40' 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.4.1' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | VERSION_CODE=16 21 | VERSION_NAME=2.1.1-SNAPSHOT 22 | GROUP=com.airbnb.android 23 | 24 | POM_DESCRIPTION=A view abstraction to provide a map user interface with varying underlying map providers. 25 | POM_URL=https://github.com/airbnb/airmapview/tree/new-scv 26 | POM_SCM_URL=https://github.com/airbnb/airmapview/tree/new-scv 27 | POM_SCM_CONNECTION=scm:git@github.com:airbnb/airmapview.git 28 | POM_SCM_DEV_CONNECTION=scm:git@github.com:airbnb/airmapview.git 29 | POM_LICENSE_NAME=Apache License 2.0 30 | POM_LICENSE_URL=https://github.com/airbnb/airmapview/blob/master/LICENSE 31 | POM_LICENSE_DIST=repo 32 | POM_DEVELOPER_ID=airbnb 33 | POM_DEVELOPER_NAME=Airbnb 34 | android.useAndroidX=true 35 | android.enableJetifier=true 36 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/AirMapView/533d6818ad4e6119a1aa7360e6868b5d990b9934/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply from: 'gradle-maven-push.gradle' 5 | 6 | android { 7 | compileSdkVersion 28 8 | 9 | defaultConfig { 10 | minSdkVersion 14 11 | targetSdkVersion 28 12 | } 13 | 14 | compileOptions { 15 | sourceCompatibility JavaVersion.VERSION_1_8 16 | targetCompatibility JavaVersion.VERSION_1_8 17 | } 18 | } 19 | 20 | dependencies { 21 | api 'com.google.android.gms:play-services-maps:17.0.0' 22 | api 'com.google.maps.android:android-maps-utils:0.4' 23 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 24 | implementation "androidx.core:core-ktx:1.0.2" 25 | 26 | testImplementation 'junit:junit:4.12' 27 | testImplementation 'org.mockito:mockito-core:1.10.19' 28 | testImplementation 'org.hamcrest:hamcrest-integration:1.3' 29 | testImplementation 'org.hamcrest:hamcrest-core:1.3' 30 | testImplementation 'org.hamcrest:hamcrest-library:1.3' 31 | } 32 | 33 | if (JavaVersion.current().isJava8Compatible()) { 34 | allprojects { 35 | tasks.withType(Javadoc) { 36 | options.addStringOption('Xdoclint:none', '-quiet') 37 | } 38 | } 39 | } 40 | repositories { 41 | google() 42 | mavenCentral() 43 | } 44 | -------------------------------------------------------------------------------- /library/gradle-maven-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENSE_NAME 74 | url POM_LICENSE_URL 75 | distribution POM_LICENSE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | signing { 91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 92 | sign configurations.archives 93 | } 94 | 95 | task androidJavadocs(type: Javadoc) { 96 | source = android.sourceSets.main.java.srcDirs 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | excludes = ['**/*.kt'] 99 | } 100 | 101 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 102 | classifier = 'javadoc' 103 | from androidJavadocs.destinationDir 104 | } 105 | 106 | task androidSourcesJar(type: Jar) { 107 | classifier = 'sources' 108 | from android.sourceSets.main.java.sourceFiles 109 | } 110 | 111 | artifacts { 112 | archives androidSourcesJar 113 | archives androidJavadocsJar 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=AirMapView library 2 | POM_ARTIFACT_ID=airmapview 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/assets/geolocate_user.js: -------------------------------------------------------------------------------- 1 | // Credit goes to @ChadKillingsworth for his awesome implementation: https://github.com/ChadKillingsworth/geolocation-marker 2 | (function(){'use strict';var d,e=this;function g(a,b,c){return a.call.apply(a.bind,arguments)}function k(a,b,c){if(!a)throw Error();if(2this.f())return;this.b.setMap(this.h());this.b.bindTo("position",this);this.a.bindTo("center",this,"position");this.a.bindTo("radius",this,"accuracy")}this.i()!=a.coords.accuracy&&google.maps.MVCObject.prototype.set.call(this,"accuracy",a.coords.accuracy);!c&&null!=this.d()&&this.d().equals(b)||google.maps.MVCObject.prototype.set.call(this,"position", 8 | b)};d.r=function(){navigator.geolocation&&(this.g=navigator.geolocation.watchPosition(n(this.q,this),n(this.l,this),this.j()))};d.l=function(a){google.maps.event.trigger(this,"geolocation_error",a)};d.c=function(a,b){for(var c in b)!0!==r[c]&&(a[c]=b[c]);return a};p("GeolocationMarker",q);p("GeolocationMarker.prototype.getAccuracy",q.prototype.i);p("GeolocationMarker.prototype.getBounds",q.prototype.k);p("GeolocationMarker.prototype.getMap",q.prototype.h); 9 | p("GeolocationMarker.prototype.getMinimumAccuracy",q.prototype.f);p("GeolocationMarker.prototype.getPosition",q.prototype.d);p("GeolocationMarker.prototype.getPositionOptions",q.prototype.j);p("GeolocationMarker.prototype.setCircleOptions",q.prototype.m);p("GeolocationMarker.prototype.setMap",q.prototype.e);p("GeolocationMarker.prototype.setMarkerOptions",q.prototype.n);p("GeolocationMarker.prototype.setMinimumAccuracy",q.prototype.o);p("GeolocationMarker.prototype.setPositionOptions",q.prototype.p); 10 | }).call(window) 11 | //# sourceMappingURL=geolocationmarker-compiled.js.map -------------------------------------------------------------------------------- /library/src/main/assets/google_map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 320 | 321 | 322 |
323 | 324 | 325 | -------------------------------------------------------------------------------- /library/src/main/assets/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/AirMapView/533d6818ad4e6119a1aa7360e6868b5d990b9934/library/src/main/assets/images/marker-icon.png -------------------------------------------------------------------------------- /library/src/main/assets/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/AirMapView/533d6818ad4e6119a1aa7360e6868b5d990b9934/library/src/main/assets/images/marker-shadow.png -------------------------------------------------------------------------------- /library/src/main/assets/leaflet_1.3.1/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 29 | .leaflet-safari .leaflet-tile { 30 | image-rendering: -webkit-optimize-contrast; 31 | } 32 | /* hack that prevents hw layers "stretching" when loading new tiles */ 33 | .leaflet-safari .leaflet-tile-container { 34 | width: 1600px; 35 | height: 1600px; 36 | -webkit-transform-origin: 0 0; 37 | } 38 | .leaflet-marker-icon, 39 | .leaflet-marker-shadow { 40 | display: block; 41 | } 42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 44 | .leaflet-container .leaflet-overlay-pane svg, 45 | .leaflet-container .leaflet-marker-pane img, 46 | .leaflet-container .leaflet-shadow-pane img, 47 | .leaflet-container .leaflet-tile-pane img, 48 | .leaflet-container img.leaflet-image-layer { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | 53 | .leaflet-container.leaflet-touch-zoom { 54 | -ms-touch-action: pan-x pan-y; 55 | touch-action: pan-x pan-y; 56 | } 57 | .leaflet-container.leaflet-touch-drag { 58 | -ms-touch-action: pinch-zoom; 59 | /* Fallback for FF which doesn't support pinch-zoom */ 60 | touch-action: none; 61 | touch-action: pinch-zoom; 62 | } 63 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 64 | -ms-touch-action: none; 65 | touch-action: none; 66 | } 67 | .leaflet-container { 68 | -webkit-tap-highlight-color: transparent; 69 | } 70 | .leaflet-container a { 71 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 72 | } 73 | .leaflet-tile { 74 | filter: inherit; 75 | visibility: hidden; 76 | } 77 | .leaflet-tile-loaded { 78 | visibility: inherit; 79 | } 80 | .leaflet-zoom-box { 81 | width: 0; 82 | height: 0; 83 | -moz-box-sizing: border-box; 84 | box-sizing: border-box; 85 | z-index: 800; 86 | } 87 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 88 | .leaflet-overlay-pane svg { 89 | -moz-user-select: none; 90 | } 91 | 92 | .leaflet-pane { z-index: 400; } 93 | 94 | .leaflet-tile-pane { z-index: 200; } 95 | .leaflet-overlay-pane { z-index: 400; } 96 | .leaflet-shadow-pane { z-index: 500; } 97 | .leaflet-marker-pane { z-index: 600; } 98 | .leaflet-tooltip-pane { z-index: 650; } 99 | .leaflet-popup-pane { z-index: 700; } 100 | 101 | .leaflet-map-pane canvas { z-index: 100; } 102 | .leaflet-map-pane svg { z-index: 200; } 103 | 104 | .leaflet-vml-shape { 105 | width: 1px; 106 | height: 1px; 107 | } 108 | .lvml { 109 | behavior: url(#default#VML); 110 | display: inline-block; 111 | position: absolute; 112 | } 113 | 114 | 115 | /* control positioning */ 116 | 117 | .leaflet-control { 118 | position: relative; 119 | z-index: 800; 120 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 121 | pointer-events: auto; 122 | } 123 | .leaflet-top, 124 | .leaflet-bottom { 125 | position: absolute; 126 | z-index: 1000; 127 | pointer-events: none; 128 | } 129 | .leaflet-top { 130 | top: 0; 131 | } 132 | .leaflet-right { 133 | right: 0; 134 | } 135 | .leaflet-bottom { 136 | bottom: 0; 137 | } 138 | .leaflet-left { 139 | left: 0; 140 | } 141 | .leaflet-control { 142 | float: left; 143 | clear: both; 144 | } 145 | .leaflet-right .leaflet-control { 146 | float: right; 147 | } 148 | .leaflet-top .leaflet-control { 149 | margin-top: 10px; 150 | } 151 | .leaflet-bottom .leaflet-control { 152 | margin-bottom: 10px; 153 | } 154 | .leaflet-left .leaflet-control { 155 | margin-left: 10px; 156 | } 157 | .leaflet-right .leaflet-control { 158 | margin-right: 10px; 159 | } 160 | 161 | 162 | /* zoom and fade animations */ 163 | 164 | .leaflet-fade-anim .leaflet-tile { 165 | will-change: opacity; 166 | } 167 | .leaflet-fade-anim .leaflet-popup { 168 | opacity: 0; 169 | -webkit-transition: opacity 0.2s linear; 170 | -moz-transition: opacity 0.2s linear; 171 | -o-transition: opacity 0.2s linear; 172 | transition: opacity 0.2s linear; 173 | } 174 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 175 | opacity: 1; 176 | } 177 | .leaflet-zoom-animated { 178 | -webkit-transform-origin: 0 0; 179 | -ms-transform-origin: 0 0; 180 | transform-origin: 0 0; 181 | } 182 | .leaflet-zoom-anim .leaflet-zoom-animated { 183 | will-change: transform; 184 | } 185 | .leaflet-zoom-anim .leaflet-zoom-animated { 186 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 187 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 188 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 189 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 190 | } 191 | .leaflet-zoom-anim .leaflet-tile, 192 | .leaflet-pan-anim .leaflet-tile { 193 | -webkit-transition: none; 194 | -moz-transition: none; 195 | -o-transition: none; 196 | transition: none; 197 | } 198 | 199 | .leaflet-zoom-anim .leaflet-zoom-hide { 200 | visibility: hidden; 201 | } 202 | 203 | 204 | /* cursors */ 205 | 206 | .leaflet-interactive { 207 | cursor: pointer; 208 | } 209 | .leaflet-grab { 210 | cursor: -webkit-grab; 211 | cursor: -moz-grab; 212 | } 213 | .leaflet-crosshair, 214 | .leaflet-crosshair .leaflet-interactive { 215 | cursor: crosshair; 216 | } 217 | .leaflet-popup-pane, 218 | .leaflet-control { 219 | cursor: auto; 220 | } 221 | .leaflet-dragging .leaflet-grab, 222 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 223 | .leaflet-dragging .leaflet-marker-draggable { 224 | cursor: move; 225 | cursor: -webkit-grabbing; 226 | cursor: -moz-grabbing; 227 | } 228 | 229 | /* marker & overlays interactivity */ 230 | .leaflet-marker-icon, 231 | .leaflet-marker-shadow, 232 | .leaflet-image-layer, 233 | .leaflet-pane > svg path, 234 | .leaflet-tile-container { 235 | pointer-events: none; 236 | } 237 | 238 | .leaflet-marker-icon.leaflet-interactive, 239 | .leaflet-image-layer.leaflet-interactive, 240 | .leaflet-pane > svg path.leaflet-interactive { 241 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 242 | pointer-events: auto; 243 | } 244 | 245 | /* visual tweaks */ 246 | 247 | .leaflet-container { 248 | background: #ddd; 249 | outline: 0; 250 | } 251 | .leaflet-container a { 252 | color: #0078A8; 253 | } 254 | .leaflet-container a.leaflet-active { 255 | outline: 2px solid orange; 256 | } 257 | .leaflet-zoom-box { 258 | border: 2px dotted #38f; 259 | background: rgba(255,255,255,0.5); 260 | } 261 | 262 | 263 | /* general typography */ 264 | .leaflet-container { 265 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 266 | } 267 | 268 | 269 | /* general toolbar styles */ 270 | 271 | .leaflet-bar { 272 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 273 | border-radius: 4px; 274 | } 275 | .leaflet-bar a, 276 | .leaflet-bar a:hover { 277 | background-color: #fff; 278 | border-bottom: 1px solid #ccc; 279 | width: 26px; 280 | height: 26px; 281 | line-height: 26px; 282 | display: block; 283 | text-align: center; 284 | text-decoration: none; 285 | color: black; 286 | } 287 | .leaflet-bar a, 288 | .leaflet-control-layers-toggle { 289 | background-position: 50% 50%; 290 | background-repeat: no-repeat; 291 | display: block; 292 | } 293 | .leaflet-bar a:hover { 294 | background-color: #f4f4f4; 295 | } 296 | .leaflet-bar a:first-child { 297 | border-top-left-radius: 4px; 298 | border-top-right-radius: 4px; 299 | } 300 | .leaflet-bar a:last-child { 301 | border-bottom-left-radius: 4px; 302 | border-bottom-right-radius: 4px; 303 | border-bottom: none; 304 | } 305 | .leaflet-bar a.leaflet-disabled { 306 | cursor: default; 307 | background-color: #f4f4f4; 308 | color: #bbb; 309 | } 310 | 311 | .leaflet-touch .leaflet-bar a { 312 | width: 30px; 313 | height: 30px; 314 | line-height: 30px; 315 | } 316 | .leaflet-touch .leaflet-bar a:first-child { 317 | border-top-left-radius: 2px; 318 | border-top-right-radius: 2px; 319 | } 320 | .leaflet-touch .leaflet-bar a:last-child { 321 | border-bottom-left-radius: 2px; 322 | border-bottom-right-radius: 2px; 323 | } 324 | 325 | /* zoom control */ 326 | 327 | .leaflet-control-zoom-in, 328 | .leaflet-control-zoom-out { 329 | font: bold 18px 'Lucida Console', Monaco, monospace; 330 | text-indent: 1px; 331 | } 332 | 333 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 334 | font-size: 22px; 335 | } 336 | 337 | 338 | /* layers control */ 339 | 340 | .leaflet-control-layers { 341 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 342 | background: #fff; 343 | border-radius: 5px; 344 | } 345 | .leaflet-control-layers-toggle { 346 | background-image: url(images/layers.png); 347 | width: 36px; 348 | height: 36px; 349 | } 350 | .leaflet-retina .leaflet-control-layers-toggle { 351 | background-image: url(images/layers-2x.png); 352 | background-size: 26px 26px; 353 | } 354 | .leaflet-touch .leaflet-control-layers-toggle { 355 | width: 44px; 356 | height: 44px; 357 | } 358 | .leaflet-control-layers .leaflet-control-layers-list, 359 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 360 | display: none; 361 | } 362 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 363 | display: block; 364 | position: relative; 365 | } 366 | .leaflet-control-layers-expanded { 367 | padding: 6px 10px 6px 6px; 368 | color: #333; 369 | background: #fff; 370 | } 371 | .leaflet-control-layers-scrollbar { 372 | overflow-y: scroll; 373 | overflow-x: hidden; 374 | padding-right: 5px; 375 | } 376 | .leaflet-control-layers-selector { 377 | margin-top: 2px; 378 | position: relative; 379 | top: 1px; 380 | } 381 | .leaflet-control-layers label { 382 | display: block; 383 | } 384 | .leaflet-control-layers-separator { 385 | height: 0; 386 | border-top: 1px solid #ddd; 387 | margin: 5px -10px 5px -6px; 388 | } 389 | 390 | /* Default icon URLs */ 391 | .leaflet-default-icon-path { 392 | background-image: url(images/marker-icon.png); 393 | } 394 | 395 | 396 | /* attribution and scale controls */ 397 | 398 | .leaflet-container .leaflet-control-attribution { 399 | background: #fff; 400 | background: rgba(255, 255, 255, 0.7); 401 | margin: 0; 402 | } 403 | .leaflet-control-attribution, 404 | .leaflet-control-scale-line { 405 | padding: 0 5px; 406 | color: #333; 407 | } 408 | .leaflet-control-attribution a { 409 | text-decoration: none; 410 | } 411 | .leaflet-control-attribution a:hover { 412 | text-decoration: underline; 413 | } 414 | .leaflet-container .leaflet-control-attribution, 415 | .leaflet-container .leaflet-control-scale { 416 | font-size: 11px; 417 | } 418 | .leaflet-left .leaflet-control-scale { 419 | margin-left: 5px; 420 | } 421 | .leaflet-bottom .leaflet-control-scale { 422 | margin-bottom: 5px; 423 | } 424 | .leaflet-control-scale-line { 425 | border: 2px solid #777; 426 | border-top: none; 427 | line-height: 1.1; 428 | padding: 2px 5px 1px; 429 | font-size: 11px; 430 | white-space: nowrap; 431 | overflow: hidden; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | 435 | background: #fff; 436 | background: rgba(255, 255, 255, 0.5); 437 | } 438 | .leaflet-control-scale-line:not(:first-child) { 439 | border-top: 2px solid #777; 440 | border-bottom: none; 441 | margin-top: -2px; 442 | } 443 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 444 | border-bottom: 2px solid #777; 445 | } 446 | 447 | .leaflet-touch .leaflet-control-attribution, 448 | .leaflet-touch .leaflet-control-layers, 449 | .leaflet-touch .leaflet-bar { 450 | box-shadow: none; 451 | } 452 | .leaflet-touch .leaflet-control-layers, 453 | .leaflet-touch .leaflet-bar { 454 | border: 2px solid rgba(0,0,0,0.2); 455 | background-clip: padding-box; 456 | } 457 | 458 | 459 | /* popup */ 460 | 461 | .leaflet-popup { 462 | position: absolute; 463 | text-align: center; 464 | margin-bottom: 20px; 465 | } 466 | .leaflet-popup-content-wrapper { 467 | padding: 1px; 468 | text-align: left; 469 | border-radius: 12px; 470 | } 471 | .leaflet-popup-content { 472 | margin: 13px 19px; 473 | line-height: 1.4; 474 | } 475 | .leaflet-popup-content p { 476 | margin: 18px 0; 477 | } 478 | .leaflet-popup-tip-container { 479 | width: 40px; 480 | height: 20px; 481 | position: absolute; 482 | left: 50%; 483 | margin-left: -20px; 484 | overflow: hidden; 485 | pointer-events: none; 486 | } 487 | .leaflet-popup-tip { 488 | width: 17px; 489 | height: 17px; 490 | padding: 1px; 491 | 492 | margin: -10px auto 0; 493 | 494 | -webkit-transform: rotate(45deg); 495 | -moz-transform: rotate(45deg); 496 | -ms-transform: rotate(45deg); 497 | -o-transform: rotate(45deg); 498 | transform: rotate(45deg); 499 | } 500 | .leaflet-popup-content-wrapper, 501 | .leaflet-popup-tip { 502 | background: white; 503 | color: #333; 504 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 505 | } 506 | .leaflet-container a.leaflet-popup-close-button { 507 | position: absolute; 508 | top: 0; 509 | right: 0; 510 | padding: 4px 4px 0 0; 511 | border: none; 512 | text-align: center; 513 | width: 18px; 514 | height: 14px; 515 | font: 16px/14px Tahoma, Verdana, sans-serif; 516 | color: #c3c3c3; 517 | text-decoration: none; 518 | font-weight: bold; 519 | background: transparent; 520 | } 521 | .leaflet-container a.leaflet-popup-close-button:hover { 522 | color: #999; 523 | } 524 | .leaflet-popup-scrolled { 525 | overflow: auto; 526 | border-bottom: 1px solid #ddd; 527 | border-top: 1px solid #ddd; 528 | } 529 | 530 | .leaflet-oldie .leaflet-popup-content-wrapper { 531 | zoom: 1; 532 | } 533 | .leaflet-oldie .leaflet-popup-tip { 534 | width: 24px; 535 | margin: 0 auto; 536 | 537 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 538 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 539 | } 540 | .leaflet-oldie .leaflet-popup-tip-container { 541 | margin-top: -1px; 542 | } 543 | 544 | .leaflet-oldie .leaflet-control-zoom, 545 | .leaflet-oldie .leaflet-control-layers, 546 | .leaflet-oldie .leaflet-popup-content-wrapper, 547 | .leaflet-oldie .leaflet-popup-tip { 548 | border: 1px solid #999; 549 | } 550 | 551 | 552 | /* div icon */ 553 | 554 | .leaflet-div-icon { 555 | background: #fff; 556 | border: 1px solid #666; 557 | } 558 | 559 | 560 | /* Tooltip */ 561 | /* Base styles for the element that has a tooltip */ 562 | .leaflet-tooltip { 563 | position: absolute; 564 | padding: 6px; 565 | background-color: #fff; 566 | border: 1px solid #fff; 567 | border-radius: 3px; 568 | color: #222; 569 | white-space: nowrap; 570 | -webkit-user-select: none; 571 | -moz-user-select: none; 572 | -ms-user-select: none; 573 | user-select: none; 574 | pointer-events: none; 575 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 576 | } 577 | .leaflet-tooltip.leaflet-clickable { 578 | cursor: pointer; 579 | pointer-events: auto; 580 | } 581 | .leaflet-tooltip-top:before, 582 | .leaflet-tooltip-bottom:before, 583 | .leaflet-tooltip-left:before, 584 | .leaflet-tooltip-right:before { 585 | position: absolute; 586 | pointer-events: none; 587 | border: 6px solid transparent; 588 | background: transparent; 589 | content: ""; 590 | } 591 | 592 | /* Directions */ 593 | 594 | .leaflet-tooltip-bottom { 595 | margin-top: 6px; 596 | } 597 | .leaflet-tooltip-top { 598 | margin-top: -6px; 599 | } 600 | .leaflet-tooltip-bottom:before, 601 | .leaflet-tooltip-top:before { 602 | left: 50%; 603 | margin-left: -6px; 604 | } 605 | .leaflet-tooltip-top:before { 606 | bottom: 0; 607 | margin-bottom: -12px; 608 | border-top-color: #fff; 609 | } 610 | .leaflet-tooltip-bottom:before { 611 | top: 0; 612 | margin-top: -12px; 613 | margin-left: -6px; 614 | border-bottom-color: #fff; 615 | } 616 | .leaflet-tooltip-left { 617 | margin-left: -6px; 618 | } 619 | .leaflet-tooltip-right { 620 | margin-left: 6px; 621 | } 622 | .leaflet-tooltip-left:before, 623 | .leaflet-tooltip-right:before { 624 | top: 50%; 625 | margin-top: -6px; 626 | } 627 | .leaflet-tooltip-left:before { 628 | right: 0; 629 | margin-right: -12px; 630 | border-left-color: #fff; 631 | } 632 | .leaflet-tooltip-right:before { 633 | left: 0; 634 | margin-left: -12px; 635 | border-right-color: #fff; 636 | } 637 | -------------------------------------------------------------------------------- /library/src/main/assets/mapbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirGoogleMapOptions.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.google.android.gms.maps.GoogleMapOptions; 6 | import com.google.android.gms.maps.model.CameraPosition; 7 | 8 | /** 9 | * Wrapper for the {@link GoogleMapOptions} class, which is final. 10 | */ 11 | public class AirGoogleMapOptions { 12 | 13 | private final GoogleMapOptions options; 14 | 15 | public AirGoogleMapOptions(GoogleMapOptions options) { 16 | this.options = options; 17 | } 18 | 19 | public AirGoogleMapOptions zOrderOnTop(boolean zOrderOnTop) { 20 | options.zOrderOnTop(zOrderOnTop); 21 | return this; 22 | } 23 | 24 | public AirGoogleMapOptions useViewLifecycleInFragment(boolean useViewLifecycleInFragment) { 25 | options.useViewLifecycleInFragment(useViewLifecycleInFragment); 26 | return this; 27 | } 28 | 29 | public AirGoogleMapOptions mapType(int mapType) { 30 | options.mapType(mapType); 31 | return this; 32 | } 33 | 34 | public AirGoogleMapOptions camera(CameraPosition camera) { 35 | options.camera(camera); 36 | return this; 37 | } 38 | 39 | public AirGoogleMapOptions zoomControlsEnabled(boolean enabled) { 40 | options.zoomControlsEnabled(enabled); 41 | return this; 42 | } 43 | 44 | public AirGoogleMapOptions compassEnabled(boolean enabled) { 45 | options.compassEnabled(enabled); 46 | return this; 47 | } 48 | 49 | public AirGoogleMapOptions scrollGesturesEnabled(boolean enabled) { 50 | options.scrollGesturesEnabled(enabled); 51 | return this; 52 | } 53 | 54 | public AirGoogleMapOptions zoomGesturesEnabled(boolean enabled) { 55 | options.zoomGesturesEnabled(enabled); 56 | return this; 57 | } 58 | 59 | public AirGoogleMapOptions tiltGesturesEnabled(boolean enabled) { 60 | options.tiltGesturesEnabled(enabled); 61 | return this; 62 | } 63 | 64 | public AirGoogleMapOptions rotateGesturesEnabled(boolean enabled) { 65 | options.rotateGesturesEnabled(enabled); 66 | return this; 67 | } 68 | 69 | public AirGoogleMapOptions liteMode(boolean enabled) { 70 | options.liteMode(enabled); 71 | return this; 72 | } 73 | 74 | public AirGoogleMapOptions mapToolbarEnabled(boolean enabled) { 75 | options.mapToolbarEnabled(enabled); 76 | return this; 77 | } 78 | 79 | public Boolean getZOrderOnTop() { 80 | return options.getZOrderOnTop(); 81 | } 82 | 83 | public Boolean getUseViewLifecycleInFragment() { 84 | return options.getUseViewLifecycleInFragment(); 85 | } 86 | 87 | public int getMapType() { 88 | return options.getMapType(); 89 | } 90 | 91 | public CameraPosition getCamera() { 92 | return options.getCamera(); 93 | } 94 | 95 | public Boolean getZoomControlsEnabled() { 96 | return options.getZoomControlsEnabled(); 97 | } 98 | 99 | public Boolean getCompassEnabled() { 100 | return options.getCompassEnabled(); 101 | } 102 | 103 | public Boolean getScrollGesturesEnabled() { 104 | return options.getScrollGesturesEnabled(); 105 | } 106 | 107 | public Boolean getZoomGesturesEnabled() { 108 | return options.getZoomGesturesEnabled(); 109 | } 110 | 111 | public Boolean getTiltGesturesEnabled() { 112 | return options.getTiltGesturesEnabled(); 113 | } 114 | 115 | public Boolean getRotateGesturesEnabled() { 116 | return options.getRotateGesturesEnabled(); 117 | } 118 | 119 | public Boolean getLiteMode() { 120 | return options.getLiteMode(); 121 | } 122 | 123 | public Boolean getMapToolbarEnabled() { 124 | return options.getMapToolbarEnabled(); 125 | } 126 | 127 | public Bundle toBundle() { 128 | Bundle args = new Bundle(); 129 | // this is internal to SupportMapFragment 130 | args.putParcelable("MapOptions", options); 131 | return args; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapGeoJsonLayer.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import androidx.annotation.ColorInt; 4 | import com.google.maps.android.geojson.GeoJsonPolygonStyle; 5 | 6 | public class AirMapGeoJsonLayer { 7 | protected final String geoJson; 8 | protected final float strokeWidth; 9 | protected final int strokeColor; 10 | protected final int fillColor; 11 | 12 | private AirMapGeoJsonLayer(String geoJson, float strokeWidth, int strokeColor, int fillColor) { 13 | this.geoJson = geoJson; 14 | this.strokeWidth = strokeWidth; 15 | this.strokeColor = strokeColor; 16 | this.fillColor = fillColor; 17 | } 18 | 19 | public static class Builder { 20 | 21 | private final String json; 22 | 23 | // init with default styles 24 | private final GeoJsonPolygonStyle style = new GeoJsonPolygonStyle(); 25 | 26 | public Builder(String json) { 27 | this.json = json; 28 | } 29 | 30 | public Builder fillColor(@ColorInt int color) { 31 | style.setFillColor(color); 32 | return this; 33 | } 34 | 35 | public Builder strokeColor(@ColorInt int color) { 36 | style.setStrokeColor(color); 37 | return this; 38 | } 39 | 40 | public Builder strokeWidth(float width) { 41 | style.setStrokeWidth(width); 42 | return this; 43 | } 44 | 45 | public AirMapGeoJsonLayer build() { 46 | return new AirMapGeoJsonLayer( 47 | json, style.getStrokeWidth(), style.getStrokeColor(), style.getFillColor()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapInterface.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import com.airbnb.android.airmapview.listeners.InfoWindowCreator; 4 | import com.airbnb.android.airmapview.listeners.OnCameraChangeListener; 5 | import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener; 6 | import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback; 7 | import com.airbnb.android.airmapview.listeners.OnMapBoundsCallback; 8 | import com.airbnb.android.airmapview.listeners.OnMapClickListener; 9 | import com.airbnb.android.airmapview.listeners.OnMapLoadedListener; 10 | import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener; 11 | import com.airbnb.android.airmapview.listeners.OnMapMarkerDragListener; 12 | import com.airbnb.android.airmapview.listeners.OnSnapshotReadyListener; 13 | import com.google.android.gms.maps.GoogleMap; 14 | import com.google.android.gms.maps.model.LatLng; 15 | import com.google.android.gms.maps.model.LatLngBounds; 16 | 17 | import org.json.JSONException; 18 | 19 | public interface AirMapInterface { 20 | 21 | int CIRCLE_FILL_COLOR = 0xFF00D1C1; 22 | int CIRCLE_BORDER_COLOR = 0xFF000000; 23 | int CIRCLE_BORDER_WIDTH = 0; 24 | 25 | /** @return true if the map is fully loaded/initialized. */ 26 | boolean isInitialized(); 27 | 28 | /** Clear all markers from the map */ 29 | void clearMarkers(); 30 | 31 | /** 32 | * Add the given marker to the map 33 | * 34 | * @param marker {@link AirMapMarker} instance to add 35 | */ 36 | void addMarker(AirMapMarker marker); 37 | 38 | /** 39 | * Move the marker to the given coordinates 40 | * 41 | * @param marker {@link AirMapMarker} instance to move 42 | * @param to {@link LatLng} new destination of the marker 43 | */ 44 | void moveMarker(AirMapMarker marker, LatLng to); 45 | 46 | /** 47 | * Remove the given marker from the map 48 | * 49 | * @param marker {@link AirMapMarker} instance to remove 50 | */ 51 | void removeMarker(AirMapMarker marker); 52 | 53 | /** 54 | * Set the callback for info window click events 55 | * 56 | * @param listener {@link com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener} 57 | * instance 58 | */ 59 | void setOnInfoWindowClickListener(OnInfoWindowClickListener listener); 60 | 61 | /** 62 | * Specific to Google Play Services maps. Sets the {@link GoogleMap.InfoWindowAdapter} and {@link 63 | * com.airbnb.android.airmapview.listeners.InfoWindowCreator} 64 | */ 65 | void setInfoWindowCreator(GoogleMap.InfoWindowAdapter adapter, InfoWindowCreator creator); 66 | 67 | /** Draw a circle at the given LatLng, with the given radius */ 68 | void drawCircle(LatLng latLng, int radius); 69 | 70 | /** Draw a circle at the given LatLng, with the given radius and stroke width */ 71 | void drawCircle(LatLng latLng, int radius, int borderColor); 72 | 73 | /** Draw a circle at the given LatLng, with the given radius, stroke width, and stroke color */ 74 | void drawCircle(LatLng latLng, int radius, int borderColor, int borderWidth); 75 | 76 | /** 77 | * Draw a circle at the given LatLng, with the given radius, stroke width, stroke and fill colors 78 | */ 79 | void drawCircle(LatLng latLng, int radius, int borderColor, int borderWidth, int fillColor); 80 | 81 | /** 82 | * Returns the map screen bounds to the supplied 83 | * {@link com.airbnb.android.airmapview.listeners.OnMapBoundsCallback} 84 | */ 85 | void getMapScreenBounds(OnMapBoundsCallback callback); 86 | 87 | /** 88 | * Returns the point coordinates of the LatLng in the container to the supplied 89 | * {@link OnLatLngScreenLocationCallback} 90 | */ 91 | void getScreenLocation(LatLng latLng, OnLatLngScreenLocationCallback callback); 92 | 93 | /** Sets the given {@link LatLngBounds} on the map with the specified padding */ 94 | void setCenter(LatLngBounds latLngBounds, int boundsPadding); 95 | 96 | /** Set the map zoom level */ 97 | void setZoom(int zoom); 98 | 99 | /** 100 | * Animate the map to center the given {@link LatLng}. Web maps will currently only center the map 101 | * (no animation). 102 | */ 103 | void animateCenter(LatLng latLng); 104 | 105 | /** Center the map to the given {@link LatLng} */ 106 | void setCenter(LatLng latLng); 107 | 108 | /** @return {@link LatLng} of the center of the map */ 109 | LatLng getCenter(); 110 | 111 | /** @return the zoom level of the map */ 112 | int getZoom(); 113 | 114 | /** Register a callback to be invoked when the camera of the map has changed */ 115 | void setOnCameraChangeListener(OnCameraChangeListener onCameraChangeListener); 116 | 117 | void setOnMapLoadedListener(OnMapLoadedListener onMapLoadedListener); 118 | 119 | /** 120 | * Set the center of the map, and zoom level 121 | * 122 | * @param latLng the {@link LatLng} to set as center 123 | * @param zoom the zoom level 124 | */ 125 | void setCenterZoom(LatLng latLng, int zoom); 126 | 127 | /** 128 | * Animate the center of the map to the given location and zoom level 129 | * 130 | * @param latLng the {@link LatLng} to animate to center 131 | * @param zoom the zoom level 132 | */ 133 | void animateCenterZoom(LatLng latLng, int zoom); 134 | 135 | /** 136 | * Register a callback to be invoked when a map marker is clicked 137 | * 138 | * @param listener {@link com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener} 139 | * callback 140 | */ 141 | void setOnMarkerClickListener(OnMapMarkerClickListener listener); 142 | 143 | /** 144 | * Register a callback to be invoked when a map marker is dragged 145 | * 146 | * @param listener {@link com.airbnb.android.airmapview.listeners.OnMapMarkerDragListener} 147 | * callback 148 | */ 149 | void setOnMarkerDragListener(OnMapMarkerDragListener listener); 150 | 151 | /** 152 | * Register a callback to be invoked when the map is clicked 153 | * 154 | * @param listener {@link com.airbnb.android.airmapview.listeners.OnMapClickListener} callback 155 | */ 156 | void setOnMapClickListener(OnMapClickListener listener); 157 | 158 | /** Set the map's padding. Currently only works with Google Play Services maps. */ 159 | void setPadding(int left, int top, int right, int bottom); 160 | 161 | /** Enable an indicator for the user's location on the map. */ 162 | void setMyLocationEnabled(boolean enabled); 163 | 164 | /** Check if the user location is being tracked and shown on te map. */ 165 | boolean isMyLocationEnabled(); 166 | 167 | /** Enable a button for centering on user location button. Works with GooglePlay Services maps. */ 168 | void setMyLocationButtonEnabled(boolean enabled); 169 | 170 | /** Enable a toolbar that displays various context-dependent actions. */ 171 | void setMapToolbarEnabled(boolean enabled); 172 | 173 | /** 174 | * Add the given polyline to the map 175 | * 176 | * @param polyline {@link AirMapPolyline} instance to add 177 | */ 178 | void addPolyline(AirMapPolyline polyline); 179 | 180 | /** 181 | * Remove the given {@link AirMapPolyline} 182 | * 183 | * @param polyline the {@link AirMapPolyline} to remove 184 | */ 185 | void removePolyline(AirMapPolyline polyline); 186 | 187 | /** Sets the type of map tiles that should be displayed */ 188 | void setMapType(MapType type); 189 | 190 | /** 191 | * Getting called when runtime location permissions got granted. Any action needing location 192 | * permissions should be executed here. 193 | */ 194 | void onLocationPermissionsGranted(); 195 | 196 | /** 197 | * Add the given polygon to the map 198 | * 199 | * @param polygon {@link AirMapPolygon} instance to add 200 | */ 201 | void addPolygon(AirMapPolygon polygon); 202 | 203 | /** 204 | * Remove the given {@link AirMapPolygon} 205 | * 206 | * @param polygon the {@link AirMapPolygon} to remove 207 | */ 208 | void removePolygon(AirMapPolygon polygon); 209 | 210 | /** 211 | * Adds a GeoJson layer to the map. Currently only supports adding one layer. 212 | * Note: this layer is automatically removed when the map view is destroyed. 213 | * 214 | * @param layer An {@link AirMapGeoJsonLayer} layer with GeoJson and optional styling attributes 215 | */ 216 | void setGeoJsonLayer(AirMapGeoJsonLayer layer) throws JSONException; 217 | 218 | /** Remove GeoJson layer from map, if any. */ 219 | void clearGeoJsonLayer(); 220 | 221 | /** Get a Bitmap snapshot of the current */ 222 | void getSnapshot(OnSnapshotReadyListener listener); 223 | } 224 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapMarker.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import androidx.annotation.Nullable; 6 | import com.google.android.gms.maps.model.BitmapDescriptor; 7 | import com.google.android.gms.maps.model.BitmapDescriptorFactory; 8 | import com.google.android.gms.maps.model.LatLng; 9 | import com.google.android.gms.maps.model.Marker; 10 | import com.google.android.gms.maps.model.MarkerOptions; 11 | 12 | /** 13 | * Wrapper around {@link MarkerOptions}. Keeps record of data needed to display map markers, as 14 | * well as an object T associated with the marker. 15 | */ 16 | public class AirMapMarker { 17 | 18 | private final T object; 19 | private final long id; 20 | private final MarkerOptions markerOptions; 21 | private Marker marker; 22 | // uses a simple
element instead of an image, only available in leaflet map 23 | private final @Nullable LeafletDivIcon divIcon; 24 | 25 | private AirMapMarker(T object, long id, MarkerOptions markerOptions) { 26 | this(object, id, markerOptions, null); 27 | } 28 | 29 | private AirMapMarker(T object, long id, @Nullable LeafletDivIcon divIcon) { 30 | this(object, id, new MarkerOptions(), divIcon); 31 | } 32 | 33 | private AirMapMarker(T object, long id, MarkerOptions markerOptions, 34 | @Nullable LeafletDivIcon divIcon) { 35 | this.object = object; 36 | this.id = id; 37 | this.markerOptions = markerOptions; 38 | this.divIcon = divIcon; 39 | } 40 | 41 | public T object() { 42 | return object; 43 | } 44 | 45 | public long getId() { 46 | return id; 47 | } 48 | 49 | public LatLng getLatLng() { 50 | return markerOptions.getPosition(); 51 | } 52 | 53 | void setLatLng(LatLng latLng) { 54 | markerOptions.position(latLng); 55 | } 56 | 57 | public String getTitle() { 58 | return markerOptions.getTitle(); 59 | } 60 | 61 | public String getSnippet() { 62 | return markerOptions.getSnippet(); 63 | } 64 | 65 | public @Nullable LeafletDivIcon getDivIcon() { 66 | return divIcon; 67 | } 68 | 69 | public MarkerOptions getMarkerOptions() { 70 | return markerOptions; 71 | } 72 | 73 | /** Sets a marker associated to this object */ 74 | void setGoogleMarker(Marker marker) { 75 | this.marker = marker; 76 | } 77 | 78 | public Builder toBuilder() { 79 | Builder builder = new Builder() 80 | .id(id) 81 | .object(object) 82 | .position(markerOptions.getPosition()) 83 | .alpha(markerOptions.getAlpha()) 84 | .anchor(markerOptions.getAnchorU(), markerOptions.getAnchorV()) 85 | .bitmapDescriptor(markerOptions.getIcon()) 86 | .infoWindowAnchor(markerOptions.getInfoWindowAnchorU(), 87 | markerOptions.getInfoWindowAnchorV()) 88 | .snippet(markerOptions.getSnippet()) 89 | .title(markerOptions.getTitle()) 90 | .draggable(markerOptions.isDraggable()) 91 | .visible(markerOptions.isVisible()) 92 | .alpha(markerOptions.getAlpha()) 93 | .rotation(markerOptions.getRotation()) 94 | .flat(markerOptions.isFlat()); 95 | if (divIcon != null) { 96 | builder.divIconHtml(divIcon.getHtml()) 97 | .divIconWidth(divIcon.getWidth()) 98 | .divIconHeight(divIcon.getHeight()); 99 | } 100 | return builder; 101 | } 102 | 103 | public Marker getMarker() { 104 | return marker; 105 | } 106 | 107 | public static class Builder { 108 | private T object; 109 | private long id; 110 | private final MarkerOptions markerOptions = new MarkerOptions(); 111 | private String divIconHtml; 112 | private int divIconHeight; 113 | private int divIconWidth; 114 | 115 | public Builder() { 116 | } 117 | 118 | public Builder object(T object) { 119 | this.object = object; 120 | return this; 121 | } 122 | 123 | public Builder id(long id) { 124 | this.id = id; 125 | return this; 126 | } 127 | 128 | public Builder position(LatLng position) { 129 | markerOptions.position(position); 130 | return this; 131 | } 132 | 133 | public Builder anchor(float u, float v) { 134 | markerOptions.anchor(u, v); 135 | return this; 136 | } 137 | 138 | public Builder infoWindowAnchor(float u, float v) { 139 | markerOptions.infoWindowAnchor(u, v); 140 | return this; 141 | } 142 | 143 | public Builder title(String title) { 144 | markerOptions.title(title); 145 | return this; 146 | } 147 | 148 | public Builder divIconHtml(String divIconHtml) { 149 | this.divIconHtml = divIconHtml; 150 | return this; 151 | } 152 | 153 | public Builder divIconWidth(int width) { 154 | this.divIconWidth = width; 155 | return this; 156 | } 157 | 158 | public Builder divIconHeight(int height) { 159 | this.divIconHeight = height; 160 | return this; 161 | } 162 | 163 | public Builder snippet(String snippet) { 164 | markerOptions.snippet(snippet); 165 | return this; 166 | } 167 | 168 | public Builder iconId(int iconId) { 169 | try { 170 | markerOptions.icon(BitmapDescriptorFactory.fromResource(iconId)); 171 | } catch (NullPointerException ignored) { 172 | // google play services is not available 173 | } 174 | return this; 175 | } 176 | 177 | public Builder bitmap(Bitmap bitmap) { 178 | try { 179 | bitmapDescriptor(BitmapDescriptorFactory.fromBitmap(bitmap)); 180 | } catch (NullPointerException ignored) { 181 | // google play services is not available 182 | } 183 | return this; 184 | } 185 | 186 | public Builder bitmapDescriptor(BitmapDescriptor bitmap) { 187 | markerOptions.icon(bitmap); 188 | return this; 189 | } 190 | 191 | public Builder draggable(boolean draggable) { 192 | markerOptions.draggable(draggable); 193 | return this; 194 | } 195 | 196 | public Builder visible(boolean visible) { 197 | markerOptions.visible(visible); 198 | return this; 199 | } 200 | 201 | public Builder flat(boolean flat) { 202 | markerOptions.flat(flat); 203 | return this; 204 | } 205 | 206 | public Builder rotation(float rotation) { 207 | markerOptions.rotation(rotation); 208 | return this; 209 | } 210 | 211 | public Builder alpha(float alpha) { 212 | markerOptions.alpha(alpha); 213 | return this; 214 | } 215 | 216 | public Builder zIndex(float zIndex) { 217 | markerOptions.zIndex(zIndex); 218 | return this; 219 | } 220 | 221 | public AirMapMarker build() { 222 | return new AirMapMarker<>(object, id, markerOptions, 223 | divIconHtml == null ? null : 224 | new LeafletDivIcon(divIconHtml, divIconWidth, divIconHeight)); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapPolygon.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.graphics.Color; 4 | 5 | import androidx.annotation.NonNull; 6 | import com.google.android.gms.maps.model.LatLng; 7 | import com.google.android.gms.maps.model.Polygon; 8 | import com.google.android.gms.maps.model.PolygonOptions; 9 | 10 | public class AirMapPolygon { 11 | 12 | private static final int STROKE_WIDTH = 1; 13 | private static final int STROKE_COLOR = Color.BLUE; 14 | 15 | private final T object; 16 | private final long id; 17 | private final PolygonOptions polygonOptions; 18 | private Polygon googlePolygon; 19 | 20 | public AirMapPolygon(T object, long id, PolygonOptions polygonOptions) { 21 | this.object = object; 22 | this.id = id; 23 | this.polygonOptions = polygonOptions; 24 | } 25 | 26 | public T getObject() { 27 | return object; 28 | } 29 | 30 | public long getId() { 31 | return id; 32 | } 33 | 34 | public PolygonOptions getPolygonOptions() { 35 | return polygonOptions; 36 | } 37 | 38 | public Polygon getGooglePolygon() { 39 | return googlePolygon; 40 | } 41 | 42 | public void setGooglePolygon(Polygon googlePolygon) { 43 | this.googlePolygon = googlePolygon; 44 | } 45 | 46 | public static class Builder { 47 | private final PolygonOptions polygonOptions = new PolygonOptions(); 48 | private T object; 49 | private long id; 50 | 51 | public Builder() { 52 | polygonOptions.strokeWidth(STROKE_WIDTH); 53 | polygonOptions.strokeColor(STROKE_COLOR); 54 | } 55 | 56 | public Builder object(T object) { 57 | this.object = object; 58 | return this; 59 | } 60 | 61 | public Builder id(long id) { 62 | this.id = id; 63 | return this; 64 | } 65 | 66 | public Builder strokeColor(int color) { 67 | polygonOptions.strokeColor(color); 68 | return this; 69 | } 70 | 71 | public Builder strokeWidth(float width) { 72 | this.polygonOptions.strokeWidth(width); 73 | return this; 74 | } 75 | 76 | public Builder fillColor(int color) { 77 | this.polygonOptions.fillColor(color); 78 | return this; 79 | } 80 | 81 | public Builder geodesic(boolean geodesic) { 82 | this.polygonOptions.geodesic(geodesic); 83 | return this; 84 | } 85 | 86 | public Builder zIndex(float zIndex) { 87 | this.polygonOptions.zIndex(zIndex); 88 | return this; 89 | } 90 | 91 | public Builder visible(boolean visible) { 92 | this.polygonOptions.visible(visible); 93 | return this; 94 | } 95 | 96 | public Builder add(LatLng point) { 97 | this.polygonOptions.add(point); 98 | return this; 99 | } 100 | 101 | public Builder add(LatLng... points) { 102 | this.polygonOptions.add(points); 103 | return this; 104 | } 105 | 106 | public Builder addAll(@NonNull Iterable points) { 107 | this.polygonOptions.addAll(points); 108 | return this; 109 | } 110 | 111 | public Builder addHole(@NonNull Iterable points) { 112 | this.polygonOptions.addHole(points); 113 | return this; 114 | } 115 | 116 | public AirMapPolygon build() { 117 | return new AirMapPolygon<>(object, id, polygonOptions); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapPolyline.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.google.android.gms.maps.GoogleMap; 6 | import com.google.android.gms.maps.model.LatLng; 7 | import com.google.android.gms.maps.model.Polyline; 8 | import com.google.android.gms.maps.model.PolylineOptions; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Helper class for keeping record of data needed to display a polyline, as well as an optional 14 | * object T associated with the polyline. 15 | */ 16 | public class AirMapPolyline { 17 | 18 | private static final int STROKE_WIDTH = 1; 19 | private static final int STROKE_COLOR = Color.BLUE; 20 | 21 | private T object; 22 | private int strokeWidth; 23 | private long id; 24 | private List points; 25 | private String title; 26 | private int strokeColor; 27 | private Polyline googlePolyline; 28 | 29 | public AirMapPolyline(List points, long id) { 30 | this(null, points, id); 31 | } 32 | 33 | public AirMapPolyline(T object, List points, long id) { 34 | this(object, points, id, STROKE_WIDTH, STROKE_COLOR); 35 | } 36 | 37 | public AirMapPolyline(T object, List points, long id, int strokeWidth, int strokeColor) { 38 | this.object = object; 39 | this.points = points; 40 | this.id = id; 41 | this.strokeWidth = strokeWidth; 42 | this.strokeColor = strokeColor; 43 | } 44 | 45 | public long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(long id) { 50 | this.id = id; 51 | } 52 | 53 | public List getPoints() { 54 | return points; 55 | } 56 | 57 | public void setPoints(List points) { 58 | this.points = points; 59 | } 60 | 61 | public String getTitle() { 62 | return title; 63 | } 64 | 65 | public void setTitle(String title) { 66 | this.title = title; 67 | } 68 | 69 | public T getObject() { 70 | return object; 71 | } 72 | 73 | public void setObject(T object) { 74 | this.object = object; 75 | } 76 | 77 | public int getStrokeWidth() { 78 | return strokeWidth; 79 | } 80 | 81 | public int getStrokeColor() { 82 | return strokeColor; 83 | } 84 | 85 | /** 86 | * Add this polyline to the given {@link GoogleMap} instance 87 | * 88 | * @param googleMap the {@link GoogleMap} instance to which the polyline will be added 89 | */ 90 | public void addToGoogleMap(GoogleMap googleMap) { 91 | // add the polyline and keep a reference so it can be removed 92 | googlePolyline = googleMap.addPolyline(new PolylineOptions() 93 | .addAll(points) 94 | .width(strokeWidth) 95 | .color(strokeColor)); 96 | } 97 | 98 | /** 99 | * Remove this polyline from a GoogleMap (if it was added). 100 | * 101 | * @return true if the {@link Polyline} was removed 102 | */ 103 | public boolean removeFromGoogleMap() { 104 | if (googlePolyline != null) { 105 | googlePolyline.remove(); 106 | return true; 107 | } 108 | return false; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | 6 | import java.util.Locale; 7 | 8 | /** Defines maps to be used with {@link com.airbnb.android.airmapview.WebViewMapFragment} */ 9 | public class AirMapType { 10 | 11 | private static final String ARG_MAP_DOMAIN = "map_domain"; 12 | private static final String ARG_FILE_NAME = "map_file_name"; 13 | private static final String ARG_MAP_URL = "map_url"; 14 | private final String fileName; 15 | private final String mapUrl; 16 | private final String domain; 17 | 18 | public AirMapType(String fileName, String mapUrl, String domain) { 19 | this.fileName = fileName; 20 | this.mapUrl = mapUrl; 21 | this.domain = domain; 22 | } 23 | 24 | /** @return the name of the HTML file in /assets */ 25 | String getFileName() { 26 | return fileName; 27 | } 28 | 29 | /** @return the base URL for a maps API */ 30 | String getMapUrl() { 31 | return mapUrl; 32 | } 33 | 34 | /** @return domain of the maps API to use */ 35 | String getDomain() { 36 | return domain; 37 | } 38 | 39 | public Bundle toBundle() { 40 | return toBundle(new Bundle()); 41 | } 42 | 43 | public Bundle toBundle(Bundle bundle) { 44 | bundle.putString(ARG_MAP_DOMAIN, getDomain()); 45 | bundle.putString(ARG_MAP_URL, getMapUrl()); 46 | bundle.putString(ARG_FILE_NAME, getFileName()); 47 | return bundle; 48 | } 49 | 50 | public static AirMapType fromBundle(Bundle bundle) { 51 | return new AirMapType( 52 | bundle.getString(ARG_FILE_NAME, ""), 53 | bundle.getString(ARG_MAP_URL, ""), 54 | bundle.getString(ARG_MAP_DOMAIN, "")); 55 | } 56 | 57 | public String getMapData(Resources resources) { 58 | return AirMapUtils.getStringFromFile(resources, fileName) 59 | .replace("MAPURL", mapUrl) 60 | .replace("LANGTOKEN", Locale.getDefault().getLanguage()) 61 | .replace("REGIONTOKEN", Locale.getDefault().getCountry()); 62 | } 63 | 64 | @SuppressWarnings("RedundantIfStatement") 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) { 68 | return true; 69 | } 70 | 71 | if (o == null || !(o instanceof AirMapType)) { 72 | return false; 73 | } 74 | 75 | AirMapType that = (AirMapType) o; 76 | 77 | if (domain != null ? !domain.equals(that.domain) : that.domain != null) { 78 | return false; 79 | } 80 | 81 | if (fileName != null ? !fileName.equals(that.fileName) : that.fileName != null) { 82 | return false; 83 | } 84 | 85 | if (mapUrl != null ? !mapUrl.equals(that.mapUrl) : that.mapUrl != null) { 86 | return false; 87 | } 88 | 89 | return true; 90 | } 91 | 92 | @Override public int hashCode() { 93 | int result = fileName != null ? fileName.hashCode() : 0; 94 | result = 31 * result + (mapUrl != null ? mapUrl.hashCode() : 0); 95 | result = 31 * result + (domain != null ? domain.hashCode() : 0); 96 | return result; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapUtils.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.content.res.Resources; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | public class AirMapUtils { 11 | 12 | public static String getStringFromFile(Resources resources, String filePath) { 13 | try { 14 | InputStream is = resources.getAssets().open(filePath); 15 | String ret = convertStreamToString(is); 16 | is.close(); 17 | return ret; 18 | } catch (IOException e) { 19 | throw new RuntimeException("unable to load asset " + filePath); 20 | } 21 | } 22 | 23 | public static String convertStreamToString(InputStream is) throws IOException { 24 | BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 25 | StringBuilder sb = new StringBuilder(); 26 | String line; 27 | while ((line = reader.readLine()) != null) { 28 | sb.append(line).append("\n"); 29 | } 30 | reader.close(); 31 | return sb.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapView.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.view.MotionEvent; 7 | import android.widget.FrameLayout; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.fragment.app.Fragment; 11 | import androidx.fragment.app.FragmentManager; 12 | import com.airbnb.android.airmapview.listeners.InfoWindowCreator; 13 | import com.airbnb.android.airmapview.listeners.OnCameraChangeListener; 14 | import com.airbnb.android.airmapview.listeners.OnCameraMoveListener; 15 | import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener; 16 | import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback; 17 | import com.airbnb.android.airmapview.listeners.OnMapBoundsCallback; 18 | import com.airbnb.android.airmapview.listeners.OnMapClickListener; 19 | import com.airbnb.android.airmapview.listeners.OnMapInitializedListener; 20 | import com.airbnb.android.airmapview.listeners.OnMapLoadedListener; 21 | import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener; 22 | import com.airbnb.android.airmapview.listeners.OnMapMarkerDragListener; 23 | import com.google.android.gms.maps.GoogleMap; 24 | import com.google.android.gms.maps.model.LatLng; 25 | import com.google.android.gms.maps.model.LatLngBounds; 26 | import com.google.android.gms.maps.model.Marker; 27 | 28 | import org.json.JSONException; 29 | 30 | public class AirMapView extends FrameLayout 31 | implements OnCameraChangeListener, OnMapClickListener, OnMapMarkerDragListener, 32 | OnMapMarkerClickListener, OnMapLoadedListener, OnInfoWindowClickListener { 33 | 34 | private static final int INVALID_ZOOM = -1; 35 | 36 | protected AirMapInterface mapInterface; 37 | private OnCameraMoveListener onCameraMoveListener; 38 | private OnCameraChangeListener onCameraChangeListener; 39 | private boolean mOnCameraMoveTriggered; 40 | private OnMapInitializedListener onMapInitializedListener; 41 | private OnMapMarkerClickListener onMapMarkerClickListener; 42 | private OnMapMarkerDragListener onMapMarkerDragListener; 43 | private OnMapClickListener onMapClickListener; 44 | private OnInfoWindowClickListener onInfoWindowClickListener; 45 | 46 | public AirMapView(Context context) { 47 | super(context); 48 | inflateView(); 49 | } 50 | 51 | public AirMapView(Context context, AttributeSet attrs) { 52 | super(context, attrs); 53 | inflateView(); 54 | } 55 | 56 | public AirMapView(Context context, AttributeSet attrs, int defStyle) { 57 | super(context, attrs, defStyle); 58 | inflateView(); 59 | } 60 | 61 | private void inflateView() { 62 | LayoutInflater.from(getContext()).inflate(R.layout.map_view, this); 63 | } 64 | 65 | public void initialize(FragmentManager fragmentManager, AirMapInterface mapInterface) { 66 | if (mapInterface == null || fragmentManager == null) { 67 | throw new IllegalArgumentException("Either mapInterface or fragmentManager is null"); 68 | } 69 | 70 | this.mapInterface = mapInterface; 71 | this.mapInterface.setOnMapLoadedListener(this); 72 | 73 | fragmentManager.beginTransaction() 74 | .replace(getId(), (Fragment) this.mapInterface) 75 | .commit(); 76 | 77 | fragmentManager.executePendingTransactions(); 78 | } 79 | 80 | /** 81 | * Used for initialization of the underlying map provider. 82 | * 83 | * @param fragmentManager required for initialization 84 | */ 85 | public void initialize(FragmentManager fragmentManager) { 86 | AirMapInterface mapInterface = (AirMapInterface) 87 | fragmentManager.findFragmentById(R.id.map_frame); 88 | 89 | if (mapInterface != null) { 90 | initialize(fragmentManager, mapInterface); 91 | } else { 92 | initialize(fragmentManager, new DefaultAirMapViewBuilder(getContext()).builder().build()); 93 | } 94 | } 95 | 96 | public void setOnMapInitializedListener(OnMapInitializedListener mapInitializedListener) { 97 | onMapInitializedListener = mapInitializedListener; 98 | } 99 | 100 | @Override public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { 101 | if (ev.getAction() == MotionEvent.ACTION_MOVE) { 102 | if (onCameraMoveListener != null && !mOnCameraMoveTriggered) { 103 | onCameraMoveListener.onCameraMove(); 104 | mOnCameraMoveTriggered = true; 105 | } 106 | } else if (ev.getAction() == MotionEvent.ACTION_UP) { 107 | mOnCameraMoveTriggered = false; 108 | } 109 | 110 | return super.dispatchTouchEvent(ev); 111 | } 112 | 113 | public void setOnCameraChangeListener(OnCameraChangeListener onCameraChangeListener) { 114 | this.onCameraChangeListener = onCameraChangeListener; 115 | } 116 | 117 | /** 118 | * Sets the map {@link com.airbnb.android.airmapview.listeners.OnCameraMoveListener} 119 | * 120 | * @param onCameraMoveListener The OnCameraMoveListener to be set 121 | */ 122 | public void setOnCameraMoveListener(OnCameraMoveListener onCameraMoveListener) { 123 | this.onCameraMoveListener = onCameraMoveListener; 124 | } 125 | 126 | public final AirMapInterface getMapInterface() { 127 | return mapInterface; 128 | } 129 | 130 | public void onDestroyView() { 131 | if (isInitialized()) { 132 | mapInterface.setMyLocationEnabled(false); 133 | } 134 | } 135 | 136 | public int getZoom() { 137 | if (isInitialized()) { 138 | return mapInterface.getZoom(); 139 | } 140 | 141 | return INVALID_ZOOM; 142 | } 143 | 144 | public LatLng getCenter() { 145 | if (isInitialized()) { 146 | return mapInterface.getCenter(); 147 | } 148 | return null; 149 | } 150 | 151 | public boolean setCenter(LatLng latLng) { 152 | if (isInitialized()) { 153 | mapInterface.setCenter(latLng); 154 | return true; 155 | } 156 | return false; 157 | } 158 | 159 | public boolean animateCenter(LatLng latLng) { 160 | if (isInitialized()) { 161 | mapInterface.animateCenter(latLng); 162 | return true; 163 | } 164 | return false; 165 | } 166 | 167 | public boolean setZoom(int zoom) { 168 | if (isInitialized()) { 169 | mapInterface.setZoom(zoom); 170 | return true; 171 | } 172 | return false; 173 | } 174 | 175 | public boolean setCenterZoom(LatLng latLng, int zoom) { 176 | if (isInitialized()) { 177 | mapInterface.setCenterZoom(latLng, zoom); 178 | return true; 179 | } 180 | return false; 181 | } 182 | 183 | public boolean animateCenterZoom(LatLng latLng, int zoom) { 184 | if (isInitialized()) { 185 | mapInterface.animateCenterZoom(latLng, zoom); 186 | return true; 187 | } 188 | return false; 189 | } 190 | 191 | public boolean setBounds(LatLngBounds latLngBounds, int boundsPadding) { 192 | if (isInitialized()) { 193 | mapInterface.setCenter(latLngBounds, boundsPadding); 194 | return true; 195 | } 196 | return false; 197 | } 198 | 199 | public void getScreenBounds(OnMapBoundsCallback callback) { 200 | if (isInitialized()) { 201 | mapInterface.getMapScreenBounds(callback); 202 | } 203 | } 204 | 205 | public void getMapMarkerScreenLocation(LatLng latLng, OnLatLngScreenLocationCallback callback) { 206 | if (isInitialized()) { 207 | mapInterface.getScreenLocation(latLng, callback); 208 | } 209 | } 210 | 211 | public void drawCircle(LatLng latLng, int radius) { 212 | if (isInitialized()) { 213 | mapInterface.drawCircle(latLng, radius); 214 | } 215 | } 216 | 217 | public void drawCircle(LatLng latLng, int radius, int strokeColor) { 218 | if (isInitialized()) { 219 | mapInterface.drawCircle(latLng, radius, strokeColor); 220 | } 221 | } 222 | 223 | public void drawCircle(LatLng latLng, int radius, int strokeColor, int strokeWidth) { 224 | if (isInitialized()) { 225 | mapInterface.drawCircle(latLng, radius, strokeColor, strokeWidth); 226 | } 227 | } 228 | 229 | public void drawCircle(LatLng latLng, int radius, int strokeColor, int strokeWidth, 230 | int fillColor) { 231 | if (isInitialized()) { 232 | mapInterface.drawCircle(latLng, radius, strokeColor, strokeWidth, fillColor); 233 | } 234 | } 235 | 236 | public void setPadding(int left, int top, int right, int bottom) { 237 | if (isInitialized()) { 238 | mapInterface.setPadding(left, top, right, bottom); 239 | } 240 | } 241 | 242 | public void setOnMarkerClickListener(OnMapMarkerClickListener listener) { 243 | onMapMarkerClickListener = listener; 244 | } 245 | 246 | public void setOnMarkerDragListener(OnMapMarkerDragListener listener) { 247 | onMapMarkerDragListener = listener; 248 | } 249 | 250 | public void setOnMapClickListener(OnMapClickListener listener) { 251 | onMapClickListener = listener; 252 | } 253 | 254 | public void setInfoWindowAdapter(GoogleMap.InfoWindowAdapter adapter, InfoWindowCreator creator) { 255 | if (isInitialized()) { 256 | mapInterface.setInfoWindowCreator(adapter, creator); 257 | } 258 | } 259 | 260 | public void setOnInfoWindowClickListener(OnInfoWindowClickListener listener) { 261 | onInfoWindowClickListener = listener; 262 | } 263 | 264 | public void clearMarkers() { 265 | if (isInitialized()) { 266 | mapInterface.clearMarkers(); 267 | } 268 | } 269 | 270 | public boolean addPolyline(AirMapPolyline polyline) { 271 | if (isInitialized()) { 272 | mapInterface.addPolyline(polyline); 273 | return true; 274 | } 275 | return false; 276 | } 277 | 278 | public void setMapType(MapType mapType) { 279 | mapInterface.setMapType(mapType); 280 | } 281 | 282 | public boolean removePolyline(AirMapPolyline polyline) { 283 | if (isInitialized()) { 284 | mapInterface.removePolyline(polyline); 285 | return true; 286 | } 287 | return false; 288 | } 289 | 290 | public boolean addPolygon(AirMapPolygon polygon) { 291 | if (isInitialized()) { 292 | mapInterface.addPolygon(polygon); 293 | return true; 294 | } 295 | return false; 296 | } 297 | 298 | public boolean removePolygon(AirMapPolygon polygon) { 299 | if (isInitialized()) { 300 | mapInterface.removePolygon(polygon); 301 | return true; 302 | } 303 | return false; 304 | } 305 | 306 | public void setGeoJsonLayer(AirMapGeoJsonLayer layer) throws JSONException { 307 | if (!isInitialized()) { 308 | return; 309 | } 310 | mapInterface.setGeoJsonLayer(layer); 311 | } 312 | 313 | public void clearGeoJsonLayer() { 314 | if (!isInitialized()) { 315 | return; 316 | } 317 | mapInterface.clearGeoJsonLayer(); 318 | } 319 | 320 | public boolean isInitialized() { 321 | return mapInterface != null && mapInterface.isInitialized(); 322 | } 323 | 324 | public boolean addMarker(AirMapMarker marker) { 325 | if (isInitialized()) { 326 | mapInterface.addMarker(marker); 327 | return true; 328 | } 329 | return false; 330 | } 331 | 332 | public boolean removeMarker(AirMapMarker marker) { 333 | if (isInitialized()) { 334 | mapInterface.removeMarker(marker); 335 | return true; 336 | } 337 | return false; 338 | } 339 | 340 | public boolean moveMarker(AirMapMarker marker, LatLng to) { 341 | if (isInitialized()) { 342 | mapInterface.moveMarker(marker, to); 343 | return true; 344 | } 345 | return false; 346 | } 347 | 348 | public void setMyLocationEnabled(boolean trackUserLocation) { 349 | mapInterface.setMyLocationEnabled(trackUserLocation); 350 | } 351 | 352 | public void setMyLocationButtonEnabled(boolean enabled) { 353 | mapInterface.setMyLocationButtonEnabled(enabled); 354 | } 355 | 356 | @Override public void onCameraChanged(LatLng latLng, int zoom) { 357 | if (onCameraChangeListener != null) { 358 | onCameraChangeListener.onCameraChanged(latLng, zoom); 359 | } 360 | } 361 | 362 | @Override public void onMapClick(LatLng latLng) { 363 | if (onMapClickListener != null) { 364 | onMapClickListener.onMapClick(latLng); 365 | } 366 | } 367 | 368 | @Override public boolean onMapMarkerClick(AirMapMarker airMarker) { 369 | if (onMapMarkerClickListener != null) { 370 | return onMapMarkerClickListener.onMapMarkerClick(airMarker); 371 | } else { 372 | return false; 373 | } 374 | } 375 | 376 | @Override public void onMapMarkerDragStart(Marker marker) { 377 | if (onMapMarkerDragListener != null) { 378 | onMapMarkerDragListener.onMapMarkerDragStart(marker); 379 | } 380 | } 381 | 382 | @Override public void onMapMarkerDrag(Marker marker) { 383 | if (onMapMarkerDragListener != null) { 384 | onMapMarkerDragListener.onMapMarkerDrag(marker); 385 | } 386 | } 387 | 388 | @Override public void onMapMarkerDragEnd(Marker marker) { 389 | if (onMapMarkerDragListener != null) { 390 | onMapMarkerDragListener.onMapMarkerDragEnd(marker); 391 | } 392 | } 393 | 394 | @Override public void onMapMarkerDragStart(long id, LatLng latLng) { 395 | if (onMapMarkerDragListener != null) { 396 | onMapMarkerDragListener.onMapMarkerDragStart(id, latLng); 397 | } 398 | } 399 | 400 | @Override public void onMapMarkerDrag(long id, LatLng latLng) { 401 | if (onMapMarkerDragListener != null) { 402 | onMapMarkerDragListener.onMapMarkerDrag(id, latLng); 403 | } 404 | } 405 | 406 | @Override public void onMapMarkerDragEnd(long id, LatLng latLng) { 407 | if (onMapMarkerDragListener != null) { 408 | onMapMarkerDragListener.onMapMarkerDragEnd(id, latLng); 409 | } 410 | } 411 | 412 | @Override public void onMapLoaded() { 413 | if (isInitialized()) { 414 | mapInterface.setOnCameraChangeListener(this); 415 | mapInterface.setOnMapClickListener(this); 416 | mapInterface.setOnMarkerClickListener(this); 417 | mapInterface.setOnMarkerDragListener(this); 418 | mapInterface.setOnInfoWindowClickListener(this); 419 | 420 | if (onMapInitializedListener != null) { 421 | // only send map Initialized callback if map initialized successfully and is laid out 422 | // initialization can fail if the map leaves the screen before it loads 423 | MapLaidOutCheckKt.doWhenMapIsLaidOut(this, () -> onMapInitializedListener.onMapInitialized()); 424 | } 425 | } 426 | } 427 | 428 | @Override public void onInfoWindowClick(AirMapMarker airMarker) { 429 | if (onInfoWindowClickListener != null) { 430 | onInfoWindowClickListener.onInfoWindowClick(airMarker); 431 | } 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapViewBuilder.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public interface AirMapViewBuilder { 4 | 5 | AirMapViewBuilder withOptions(Q arg); 6 | 7 | T build(); 8 | } 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/AirMapViewTypes.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | /** 4 | * Lists all available AirMapView implementations. Types are listed in order of preference. 5 | */ 6 | public enum AirMapViewTypes { 7 | NATIVE, WEB 8 | } 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/DefaultAirMapViewBuilder.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Bundle; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | 10 | import com.google.android.gms.common.ConnectionResult; 11 | import com.google.android.gms.common.GooglePlayServicesUtil; 12 | 13 | /** 14 | * Use this class to request an AirMapView builder. 15 | */ 16 | public class DefaultAirMapViewBuilder { 17 | 18 | private static final String TAG = DefaultAirMapViewBuilder.class.getSimpleName(); 19 | private final boolean isNativeMapSupported; 20 | private final Context context; 21 | 22 | /** 23 | * Default {@link DefaultAirMapViewBuilder} constructor. 24 | * 25 | * @param context The application context. 26 | */ 27 | public DefaultAirMapViewBuilder(Context context) { 28 | this(context, checkNativeMapSupported(context)); 29 | } 30 | 31 | /** 32 | * @param isNativeMapSupported Whether or not Google Play services is available on the 33 | * device. If you set this to true and it is not available, 34 | * bad things can happen. 35 | */ 36 | public DefaultAirMapViewBuilder(Context context, boolean isNativeMapSupported) { 37 | this.isNativeMapSupported = isNativeMapSupported; 38 | this.context = context; 39 | } 40 | 41 | /** 42 | * Returns the first/default supported AirMapView implementation in order of preference, as 43 | * defined by {@link AirMapViewTypes}. 44 | */ 45 | public AirMapViewBuilder builder() { 46 | if (isNativeMapSupported) { 47 | return new NativeAirMapViewBuilder(); 48 | } 49 | return getWebMapViewBuilder(); 50 | } 51 | 52 | /** 53 | * Returns the AirMapView implementation as requested by the mapType argument. Use this method if 54 | * you need to request a specific AirMapView implementation that is not necessarily the preferred 55 | * type. For example, you can use it to explicit request a web-based map implementation. 56 | * 57 | * @param mapType Map type for the requested AirMapView implementation. 58 | * @return An {@link AirMapViewBuilder} for the requested {@link AirMapViewTypes} mapType. 59 | */ 60 | public AirMapViewBuilder builder(AirMapViewTypes mapType) { 61 | switch (mapType) { 62 | case NATIVE: 63 | if (isNativeMapSupported) { 64 | return new NativeAirMapViewBuilder(); 65 | } 66 | break; 67 | case WEB: 68 | return getWebMapViewBuilder(); 69 | } 70 | throw new UnsupportedOperationException("Requested map type is not supported"); 71 | } 72 | 73 | /** 74 | * Decides what the Map Web provider should be used and generates a builder for it. 75 | * 76 | * @return The AirMapViewBuilder for the selected Map Web provider. 77 | */ 78 | private AirMapViewBuilder getWebMapViewBuilder() { 79 | if (context != null) { 80 | try { 81 | ApplicationInfo ai = context.getPackageManager() 82 | .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); 83 | Bundle bundle = ai.metaData; 84 | String accessToken = bundle.getString("com.mapbox.ACCESS_TOKEN"); 85 | String mapId = bundle.getString("com.mapbox.MAP_ID"); 86 | 87 | if (!TextUtils.isEmpty(accessToken) && !TextUtils.isEmpty(mapId)) { 88 | return new MapboxWebMapViewBuilder(accessToken, mapId); 89 | } 90 | } catch (PackageManager.NameNotFoundException e) { 91 | Log.e(TAG, "Failed to load Mapbox access token and map id", e); 92 | } 93 | } 94 | return new WebAirMapViewBuilder(); 95 | } 96 | 97 | private static boolean checkNativeMapSupported(Context context) { 98 | return isGooglePlayServicesAvailable(context); 99 | } 100 | 101 | private static boolean isGooglePlayServicesAvailable(Context context) { 102 | return GooglePlayServicesUtil. 103 | isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/FixedWebView.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.content.Context; 4 | import android.content.res.Configuration; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.webkit.WebView; 8 | 9 | /** 10 | * FIXME: Remove this class when AppCompat bug is fixed. 11 | * In short, AppCompat 1.1.0 makes WebView crashes on Android 5.0~5.1.1 (Lollipop). 12 | * See https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview. 13 | */ 14 | public class FixedWebView extends WebView { 15 | public FixedWebView(Context context) { 16 | super(fixedContext(context)); 17 | } 18 | 19 | public FixedWebView(Context context, AttributeSet attrs) { 20 | super(fixedContext(context), attrs); 21 | } 22 | 23 | public FixedWebView(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(fixedContext(context), attrs, defStyleAttr); 25 | } 26 | 27 | private static Context fixedContext(Context context) { 28 | if (Build.VERSION_CODES.LOLLIPOP == Build.VERSION.SDK_INT || 29 | Build.VERSION_CODES.LOLLIPOP_MR1 == Build.VERSION.SDK_INT) { 30 | return context.createConfigurationContext(new Configuration()); 31 | } 32 | return context; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/GoogleChinaMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class GoogleChinaMapType extends AirMapType { 4 | 5 | public GoogleChinaMapType() { 6 | super("google_map.html", "http://ditu.google.cn/maps/api/js", "www.google.cn"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/GoogleChinaWebViewMapFragment.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class GoogleChinaWebViewMapFragment extends GoogleWebViewMapFragment { 4 | public static GoogleChinaWebViewMapFragment newInstance(AirMapType mapType) { 5 | return (GoogleChinaWebViewMapFragment) new GoogleChinaWebViewMapFragment() 6 | .setArguments(mapType); 7 | } 8 | 9 | @Override 10 | protected boolean isChinaMode() { 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/GoogleWebMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class GoogleWebMapType extends AirMapType { 4 | 5 | public GoogleWebMapType() { 6 | super("google_map.html", "https://maps.googleapis.com/maps/api/js", "www.googleapis.com"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/GoogleWebViewMapFragment.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import java.util.Locale; 4 | 5 | public class GoogleWebViewMapFragment extends WebViewMapFragment { 6 | public static GoogleWebViewMapFragment newInstance(AirMapType mapType) { 7 | return (GoogleWebViewMapFragment) new GoogleWebViewMapFragment().setArguments(mapType); 8 | } 9 | 10 | @Override public void setMapType(MapType type) { 11 | String webType = null; 12 | switch (type) { 13 | case MAP_TYPE_NORMAL: 14 | webType = "google.maps.MapTypeId.ROADMAP"; 15 | break; 16 | case MAP_TYPE_SATELLITE: 17 | webType = "google.maps.MapTypeId.SATELLITE"; 18 | break; 19 | case MAP_TYPE_TERRAIN: 20 | webType = "google.maps.MapTypeId.TERRAIN"; 21 | break; 22 | } 23 | webView.loadUrl(String.format(Locale.US, "javascript:setMapTypeId(%1$s);", webType)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletBaiduMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class LeafletBaiduMapType extends LeafletMapType { 4 | 5 | public LeafletBaiduMapType() { 6 | super("Baidu"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletDivIcon.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class LeafletDivIcon { 4 | private final String html; 5 | private final int width; 6 | private final int height; 7 | 8 | public LeafletDivIcon(String html, int width, int height) { 9 | this.html = html; 10 | this.width = width; 11 | this.height = height; 12 | } 13 | 14 | public String getHtml() { 15 | return html; 16 | } 17 | 18 | public int getWidth() { 19 | return width; 20 | } 21 | 22 | public int getHeight() { 23 | return height; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletGaodeMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class LeafletGaodeMapType extends LeafletMapType { 4 | 5 | public LeafletGaodeMapType() { 6 | super("Gaode"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletGoogleChinaMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class LeafletGoogleChinaMapType extends LeafletMapType { 4 | 5 | public LeafletGoogleChinaMapType() { 6 | super("GoogleChina"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletGoogleMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public class LeafletGoogleMapType extends LeafletMapType { 4 | 5 | public LeafletGoogleMapType() { 6 | super("Google"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public abstract class LeafletMapType extends AirMapType { 4 | 5 | // For leaflet, we define some map provider in leaflet_map.html file. 6 | // So need to supply a provider name here. (like Google, GoogleChina, Baidu, Gaode) 7 | public LeafletMapType(String mapProvider) { 8 | super("leaflet_map.html", mapProvider, ""); 9 | } 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/LeafletWebViewMapFragment.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | 5 | import java.util.Locale; 6 | 7 | public class LeafletWebViewMapFragment extends WebViewMapFragment { 8 | public static LeafletWebViewMapFragment newInstance(AirMapType mapType) { 9 | return (LeafletWebViewMapFragment) new LeafletWebViewMapFragment().setArguments(mapType); 10 | } 11 | 12 | @Override 13 | public void setMapType(MapType type) { 14 | String webType = null; 15 | switch (type) { 16 | case MAP_TYPE_NORMAL: 17 | webType = "Normal"; 18 | break; 19 | case MAP_TYPE_SATELLITE: 20 | webType = "Satellite"; 21 | break; 22 | case MAP_TYPE_TERRAIN: 23 | webType = "Terrain"; 24 | break; 25 | } 26 | webView.loadUrl(String.format(Locale.US, "javascript:setMapTypeId('%1$s');", webType)); 27 | } 28 | 29 | @Override public void addMarker(AirMapMarker marker) { 30 | if (marker == null || marker.getDivIcon() == null || marker.getDivIcon().getHtml() == null) { 31 | super.addMarker(marker); 32 | } else { 33 | LatLng latLng = marker.getLatLng(); 34 | markers.put(marker.getId(), marker); 35 | webView.loadUrl( 36 | String.format(Locale.US, 37 | "javascript:addMarkerWithId(%1$f, %2$f, %3$d, '%4$s', '%5$s', %6$b, '%7$s', %8$d, " + 38 | "%9$d);", 39 | latLng.latitude, latLng.longitude, marker.getId(), marker.getTitle(), 40 | marker.getSnippet(), marker.getMarkerOptions().isDraggable(), 41 | marker.getDivIcon().getHtml(), marker.getDivIcon().getWidth(), 42 | marker.getDivIcon().getHeight())); 43 | } 44 | } 45 | 46 | @Override 47 | public void setCenterZoom(LatLng latLng, int zoom) { 48 | webView.loadUrl(String.format(Locale.US, "javascript:centerZoomMap(%1$f, %2$f, %3$d);", latLng.latitude, 49 | latLng.longitude, zoom)); 50 | } 51 | 52 | @Override 53 | public void animateCenter(LatLng latLng) { 54 | webView.loadUrl(String.format(Locale.US, "javascript:animateCenterMap(%1$f, %2$f);", latLng.latitude, 55 | latLng.longitude)); 56 | } 57 | 58 | @Override 59 | public void animateCenterZoom(LatLng latLng, int zoom) { 60 | webView.loadUrl(String.format(Locale.US, "javascript:animateCenterZoomMap(%1$f, %2$f, %3$d);", latLng.latitude, 61 | latLng.longitude, zoom)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/MapLaidOutCheck.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview 2 | 3 | import androidx.core.view.doOnLayout 4 | 5 | fun AirMapView.doWhenMapIsLaidOut(runnable: Runnable) = doOnLayout { runnable.run() } -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/MapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | public enum MapType { 4 | MAP_TYPE_NORMAL, MAP_TYPE_SATELLITE, MAP_TYPE_TERRAIN 5 | } 6 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/MapboxWebMapType.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | 6 | public class MapboxWebMapType extends AirMapType { 7 | private final String mapId; 8 | private final String accessToken; 9 | 10 | protected static final String ARG_MAPBOX_ACCESS_TOKEN = "MAPBOX_ACCESS_TOKEN"; 11 | protected static final String ARG_MAPBOX_MAPID = "MAPBOX_MAPID"; 12 | 13 | /** 14 | * Primary Constructor 15 | * 16 | * @param accessToken Mapbox Access Token 17 | * @param mapId Mapbox Map Id 18 | */ 19 | public MapboxWebMapType(String accessToken, String mapId) { 20 | super("mapbox.html", "https://api.tiles.mapbox.com/mapbox.js/v2.2.1", "www.mapbox.com"); 21 | this.accessToken = accessToken; 22 | this.mapId = mapId; 23 | } 24 | 25 | /** 26 | * Private Constructor used for Bundle Serialization 27 | * 28 | * @param fileName File Name 29 | * @param mapUrl Map URL 30 | * @param domain Map Domain 31 | * @param accessToken Mapbox Access Token 32 | * @param mapId Mapbox Map Id 33 | */ 34 | private MapboxWebMapType(String fileName, String mapUrl, String domain, 35 | String accessToken, String mapId) { 36 | super(fileName, mapUrl, domain); 37 | this.accessToken = accessToken; 38 | this.mapId = mapId; 39 | } 40 | 41 | public Bundle toBundle(Bundle bundle) { 42 | super.toBundle(bundle); 43 | bundle.putString(ARG_MAPBOX_ACCESS_TOKEN, accessToken); 44 | bundle.putString(ARG_MAPBOX_MAPID, mapId); 45 | return bundle; 46 | } 47 | 48 | public static MapboxWebMapType fromBundle(Bundle bundle) { 49 | AirMapType airMapType = AirMapType.fromBundle(bundle); 50 | String mapboxAccessToken = bundle.getString(ARG_MAPBOX_ACCESS_TOKEN, ""); 51 | String mapboxMapId = bundle.getString(ARG_MAPBOX_MAPID, ""); 52 | return new MapboxWebMapType(airMapType.getFileName(), airMapType.getMapUrl(), 53 | airMapType.getDomain(), mapboxAccessToken, mapboxMapId); 54 | } 55 | 56 | @Override 57 | public String getMapData(Resources resources) { 58 | String mapData = super.getMapData(resources); 59 | 60 | mapData = mapData.replace(ARG_MAPBOX_ACCESS_TOKEN, accessToken); 61 | mapData = mapData.replace(ARG_MAPBOX_MAPID, mapId); 62 | 63 | return mapData; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/MapboxWebMapViewBuilder.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | /** 4 | * AirMapView map that uses the web based Mapbox implementation. 5 | */ 6 | public class MapboxWebMapViewBuilder implements AirMapViewBuilder { 7 | 8 | private AirMapType options; 9 | private final String accessToken; 10 | private final String mapId; 11 | 12 | /** 13 | * Constructor 14 | * 15 | * @param accessToken Mapbox Access Token 16 | * @param mapId Mapbox Map Id 17 | */ 18 | public MapboxWebMapViewBuilder(String accessToken, String mapId) { 19 | super(); 20 | this.accessToken = accessToken; 21 | this.mapId = mapId; 22 | } 23 | 24 | @Override 25 | public AirMapViewBuilder withOptions(AirMapType options) { 26 | this.options = options; 27 | return this; 28 | } 29 | 30 | /** 31 | * Build the map fragment with the requested options 32 | * 33 | * @return The {@link WebViewMapFragment} map fragment. 34 | */ 35 | @Override 36 | public WebViewMapFragment build() { 37 | if (options == null) { 38 | options = new MapboxWebMapType(accessToken, mapId); 39 | } 40 | if (options instanceof MapboxWebMapType) { 41 | return MapboxWebViewMapFragment.newInstance(options); 42 | } 43 | throw new IllegalStateException("Unable to build MapboxWebMapViewFragment." + 44 | " options == '" + options + "'"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/MapboxWebViewMapFragment.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.Locale; 9 | 10 | public class MapboxWebViewMapFragment extends WebViewMapFragment { 11 | 12 | public static MapboxWebViewMapFragment newInstance(AirMapType mapType) { 13 | return (MapboxWebViewMapFragment) new MapboxWebViewMapFragment().setArguments(mapType); 14 | } 15 | 16 | @Override public View onCreateView( 17 | LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 18 | 19 | View view = super.onCreateView(inflater, container, savedInstanceState); 20 | 21 | MapboxWebMapType mapType = MapboxWebMapType.fromBundle(getArguments()); 22 | webView.loadDataWithBaseURL(mapType.getDomain(), mapType.getMapData(getResources()), 23 | "text/html", "base64", null); 24 | 25 | return view; 26 | } 27 | 28 | @Override public void setMapType(MapType type) { 29 | String mapBoxType = null; 30 | switch (type) { 31 | case MAP_TYPE_NORMAL: 32 | mapBoxType = "mapbox.streets"; 33 | break; 34 | case MAP_TYPE_SATELLITE: 35 | mapBoxType = "mapbox.satellite"; 36 | break; 37 | case MAP_TYPE_TERRAIN: 38 | mapBoxType = "mapbox.outdoors"; 39 | break; 40 | } 41 | webView.loadUrl(String.format(Locale.US, "javascript:setMapTypeId(\"%1$s\");", mapBoxType)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/NativeAirMapViewBuilder.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import com.google.android.gms.maps.GoogleMapOptions; 4 | 5 | /** 6 | * AirMapView map that uses the native Google Maps implementation. IMPORTANT: In order to use this, 7 | * Google Play Services needs to be installed on the device. 8 | */ 9 | public class NativeAirMapViewBuilder 10 | implements AirMapViewBuilder { 11 | 12 | private AirGoogleMapOptions options; 13 | 14 | @Override public AirMapViewBuilder withOptions( 15 | AirGoogleMapOptions options) { 16 | this.options = options; 17 | return this; 18 | } 19 | 20 | /** 21 | * Build the map fragment with the requested options 22 | * 23 | * @return The {@link NativeGoogleMapFragment} map fragment. 24 | */ 25 | @Override public NativeGoogleMapFragment build() { 26 | if (options == null) { 27 | options = new AirGoogleMapOptions(new GoogleMapOptions()); 28 | } 29 | return NativeGoogleMapFragment.newInstance(options); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/NativeGoogleMapFragment.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Point; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import androidx.annotation.NonNull; 11 | import com.airbnb.android.airmapview.listeners.InfoWindowCreator; 12 | import com.airbnb.android.airmapview.listeners.OnCameraChangeListener; 13 | import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener; 14 | import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback; 15 | import com.airbnb.android.airmapview.listeners.OnMapBoundsCallback; 16 | import com.airbnb.android.airmapview.listeners.OnMapClickListener; 17 | import com.airbnb.android.airmapview.listeners.OnMapLoadedListener; 18 | import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener; 19 | import com.airbnb.android.airmapview.listeners.OnMapMarkerDragListener; 20 | import com.airbnb.android.airmapview.listeners.OnSnapshotReadyListener; 21 | import com.google.android.gms.maps.CameraUpdateFactory; 22 | import com.google.android.gms.maps.GoogleMap; 23 | import com.google.android.gms.maps.OnMapReadyCallback; 24 | import com.google.android.gms.maps.Projection; 25 | import com.google.android.gms.maps.SupportMapFragment; 26 | import com.google.android.gms.maps.UiSettings; 27 | import com.google.android.gms.maps.model.CameraPosition; 28 | import com.google.android.gms.maps.model.CircleOptions; 29 | import com.google.android.gms.maps.model.LatLng; 30 | import com.google.android.gms.maps.model.LatLngBounds; 31 | import com.google.android.gms.maps.model.Marker; 32 | import com.google.android.gms.maps.model.Polygon; 33 | import com.google.maps.android.geojson.GeoJsonLayer; 34 | import com.google.maps.android.geojson.GeoJsonPolygonStyle; 35 | 36 | import org.json.JSONException; 37 | import org.json.JSONObject; 38 | 39 | import java.util.HashMap; 40 | import java.util.Map; 41 | 42 | public class NativeGoogleMapFragment extends SupportMapFragment implements AirMapInterface { 43 | private GoogleMap googleMap; 44 | private OnMapLoadedListener onMapLoadedListener; 45 | private boolean myLocationEnabled; 46 | private GeoJsonLayer layerOnMap; 47 | private final Map> markers = new HashMap<>(); 48 | 49 | public static NativeGoogleMapFragment newInstance(AirGoogleMapOptions options) { 50 | return new NativeGoogleMapFragment().setArguments(options); 51 | } 52 | 53 | public NativeGoogleMapFragment setArguments(AirGoogleMapOptions options) { 54 | setArguments(options.toBundle()); 55 | return this; 56 | } 57 | 58 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, 59 | Bundle savedInstanceState) { 60 | View v = super.onCreateView(inflater, container, savedInstanceState); 61 | 62 | init(); 63 | 64 | return v; 65 | } 66 | 67 | public void init() { 68 | getMapAsync(new OnMapReadyCallback() { 69 | @Override public void onMapReady(GoogleMap googleMap) { 70 | if (googleMap != null && getActivity() != null) { 71 | NativeGoogleMapFragment.this.googleMap = googleMap; 72 | UiSettings settings = NativeGoogleMapFragment.this.googleMap.getUiSettings(); 73 | settings.setZoomControlsEnabled(false); 74 | settings.setMyLocationButtonEnabled(false); 75 | setMyLocationEnabled(myLocationEnabled); 76 | 77 | if (onMapLoadedListener != null) { 78 | onMapLoadedListener.onMapLoaded(); 79 | } 80 | } 81 | } 82 | }); 83 | } 84 | 85 | @Override public boolean isInitialized() { 86 | return googleMap != null && getActivity() != null; 87 | } 88 | 89 | @Override public void clearMarkers() { 90 | markers.clear(); 91 | googleMap.clear(); 92 | } 93 | 94 | @Override public void addMarker(AirMapMarker airMarker) { 95 | Marker marker = googleMap.addMarker(airMarker.getMarkerOptions()); 96 | airMarker.setGoogleMarker(marker); 97 | markers.put(marker, airMarker); 98 | } 99 | 100 | @Override public void moveMarker(AirMapMarker marker, LatLng to) { 101 | marker.setLatLng(to); 102 | marker.getMarker().setPosition(to); 103 | } 104 | 105 | @Override public void removeMarker(AirMapMarker marker) { 106 | Marker nativeMarker = marker.getMarker(); 107 | if (nativeMarker != null) { 108 | nativeMarker.remove(); 109 | markers.remove(nativeMarker); 110 | } 111 | } 112 | 113 | @Override public void setOnInfoWindowClickListener(final OnInfoWindowClickListener listener) { 114 | googleMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { 115 | @Override public void onInfoWindowClick(Marker marker) { 116 | AirMapMarker airMarker = markers.get(marker); 117 | if (airMarker != null) { 118 | listener.onInfoWindowClick(airMarker); 119 | } 120 | } 121 | }); 122 | } 123 | 124 | @Override public void setInfoWindowCreator(GoogleMap.InfoWindowAdapter adapter, 125 | InfoWindowCreator creator) { 126 | googleMap.setInfoWindowAdapter(adapter); 127 | } 128 | 129 | @Override public void drawCircle(LatLng latLng, int radius) { 130 | drawCircle(latLng, radius, CIRCLE_BORDER_COLOR); 131 | } 132 | 133 | @Override public void drawCircle(LatLng latLng, int radius, int borderColor) { 134 | drawCircle(latLng, radius, borderColor, CIRCLE_BORDER_WIDTH); 135 | } 136 | 137 | @Override public void drawCircle(LatLng latLng, int radius, int borderColor, int borderWidth) { 138 | drawCircle(latLng, radius, borderColor, borderWidth, CIRCLE_FILL_COLOR); 139 | } 140 | 141 | @Override public void drawCircle(LatLng latLng, int radius, int borderColor, int borderWidth, 142 | int fillColor) { 143 | googleMap.addCircle(new CircleOptions() 144 | .center(latLng) 145 | .strokeColor(borderColor) 146 | .strokeWidth(borderWidth) 147 | .fillColor(fillColor) 148 | .radius(radius)); 149 | } 150 | 151 | @Override public void getMapScreenBounds(OnMapBoundsCallback callback) { 152 | final Projection projection = googleMap.getProjection(); 153 | int hOffset = getResources().getDimensionPixelOffset(R.dimen.map_horizontal_padding); 154 | int vOffset = getResources().getDimensionPixelOffset(R.dimen.map_vertical_padding); 155 | 156 | LatLngBounds.Builder builder = LatLngBounds.builder(); 157 | builder.include(projection.fromScreenLocation(new Point(hOffset, vOffset))); // top-left 158 | builder.include(projection.fromScreenLocation( 159 | new Point(getView().getWidth() - hOffset, vOffset))); // top-right 160 | builder.include(projection.fromScreenLocation( 161 | new Point(hOffset, getView().getHeight() - vOffset))); // bottom-left 162 | builder.include(projection.fromScreenLocation(new Point(getView().getWidth() - hOffset, 163 | getView().getHeight() - vOffset))); // bottom-right 164 | 165 | callback.onMapBoundsReady(builder.build()); 166 | } 167 | 168 | @Override public void getScreenLocation(LatLng latLng, OnLatLngScreenLocationCallback callback) { 169 | callback.onLatLngScreenLocationReady(googleMap.getProjection().toScreenLocation(latLng)); 170 | } 171 | 172 | @Override public void setCenter(LatLngBounds latLngBounds, int boundsPadding) { 173 | googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, boundsPadding)); 174 | } 175 | 176 | @Override public void setZoom(int zoom) { 177 | googleMap.animateCamera( 178 | CameraUpdateFactory.newLatLngZoom(googleMap.getCameraPosition().target, zoom)); 179 | } 180 | 181 | @Override public void animateCenter(LatLng latLng) { 182 | googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); 183 | } 184 | 185 | @Override public void setCenter(LatLng latLng) { 186 | googleMap.moveCamera(CameraUpdateFactory.newLatLng(latLng)); 187 | } 188 | 189 | @Override public LatLng getCenter() { 190 | return googleMap.getCameraPosition().target; 191 | } 192 | 193 | @Override public int getZoom() { 194 | return (int) googleMap.getCameraPosition().zoom; 195 | } 196 | 197 | @Override 198 | public void setOnCameraChangeListener(final OnCameraChangeListener onCameraChangeListener) { 199 | googleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() { 200 | @Override public void onCameraChange(CameraPosition cameraPosition) { 201 | // camera change can occur programmatically. 202 | if (isResumed()) { 203 | onCameraChangeListener.onCameraChanged(cameraPosition.target, (int) cameraPosition.zoom); 204 | } 205 | } 206 | }); 207 | } 208 | 209 | @Override public void setOnMapLoadedListener(OnMapLoadedListener onMapLoadedListener) { 210 | this.onMapLoadedListener = onMapLoadedListener; 211 | } 212 | 213 | @Override public void setCenterZoom(LatLng latLng, int zoom) { 214 | googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom)); 215 | } 216 | 217 | @Override public void animateCenterZoom(LatLng latLng, int zoom) { 218 | googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom)); 219 | } 220 | 221 | @Override public void setOnMarkerClickListener(final OnMapMarkerClickListener listener) { 222 | googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { 223 | @Override public boolean onMarkerClick(Marker marker) { 224 | AirMapMarker airMarker = markers.get(marker); 225 | if (airMarker != null) { 226 | return listener.onMapMarkerClick(airMarker); 227 | } 228 | return false; 229 | } 230 | }); 231 | } 232 | 233 | @Override public void setOnMarkerDragListener(final OnMapMarkerDragListener listener) { 234 | if (listener == null) { 235 | googleMap.setOnMarkerDragListener(null); 236 | return; 237 | } 238 | googleMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() { 239 | @Override 240 | public void onMarkerDragStart(Marker marker) { 241 | listener.onMapMarkerDragStart(marker); 242 | } 243 | 244 | @Override 245 | public void onMarkerDrag(Marker marker) { 246 | listener.onMapMarkerDrag(marker); 247 | } 248 | 249 | @Override 250 | public void onMarkerDragEnd(Marker marker) { 251 | listener.onMapMarkerDragEnd(marker); 252 | } 253 | }); 254 | } 255 | 256 | @Override public void setOnMapClickListener(final OnMapClickListener listener) { 257 | googleMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { 258 | @Override 259 | public void onMapClick(LatLng latLng) { 260 | listener.onMapClick(latLng); 261 | } 262 | }); 263 | } 264 | 265 | @Override public void setPadding(int left, int top, int right, int bottom) { 266 | googleMap.setPadding(left, top, right, bottom); 267 | } 268 | 269 | @Override public void setMyLocationEnabled(boolean enabled) { 270 | if (myLocationEnabled != enabled) { 271 | myLocationEnabled = enabled; 272 | if (!RuntimePermissionUtils.checkLocationPermissions(getActivity(), this)) { 273 | myLocationEnabled = false; 274 | } 275 | } 276 | } 277 | 278 | @Override public void onLocationPermissionsGranted() { 279 | //noinspection MissingPermission 280 | googleMap.setMyLocationEnabled(myLocationEnabled); 281 | } 282 | 283 | @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 284 | @NonNull int[] grantResults) { 285 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 286 | RuntimePermissionUtils.onRequestPermissionsResult(this, requestCode, grantResults); 287 | } 288 | 289 | @Override public boolean isMyLocationEnabled() { 290 | return googleMap.isMyLocationEnabled(); 291 | } 292 | 293 | @Override public void setMyLocationButtonEnabled(boolean enabled) { 294 | googleMap.getUiSettings().setMyLocationButtonEnabled(enabled); 295 | } 296 | 297 | @Override public void setMapToolbarEnabled(boolean enabled) { 298 | googleMap.getUiSettings().setMapToolbarEnabled(enabled); 299 | } 300 | 301 | @Override public void addPolyline(AirMapPolyline polyline) { 302 | polyline.addToGoogleMap(googleMap); 303 | } 304 | 305 | @Override public void removePolyline(AirMapPolyline polyline) { 306 | polyline.removeFromGoogleMap(); 307 | } 308 | 309 | @Override public void addPolygon(AirMapPolygon polygon) { 310 | Polygon googlePolygon = googleMap.addPolygon(polygon.getPolygonOptions()); 311 | polygon.setGooglePolygon(googlePolygon); 312 | } 313 | 314 | @Override public void removePolygon(AirMapPolygon polygon) { 315 | Polygon nativePolygon = polygon.getGooglePolygon(); 316 | if (nativePolygon != null) { 317 | nativePolygon.remove(); 318 | } 319 | } 320 | 321 | @Override public void setMapType(MapType type) { 322 | int nativeType = 0; 323 | switch (type) { 324 | case MAP_TYPE_NORMAL: 325 | nativeType = GoogleMap.MAP_TYPE_NORMAL; 326 | break; 327 | case MAP_TYPE_SATELLITE: 328 | nativeType = GoogleMap.MAP_TYPE_SATELLITE; 329 | break; 330 | case MAP_TYPE_TERRAIN: 331 | nativeType = GoogleMap.MAP_TYPE_TERRAIN; 332 | break; 333 | } 334 | googleMap.setMapType(nativeType); 335 | } 336 | 337 | /** 338 | * This method will return the google map if initialized. Will return null otherwise 339 | * 340 | * @return returns google map if initialized 341 | */ 342 | public GoogleMap getGoogleMap() { 343 | return googleMap; 344 | } 345 | 346 | @Override 347 | public void setGeoJsonLayer(final AirMapGeoJsonLayer airMapGeoJsonLayer) throws JSONException { 348 | // clear any existing layers 349 | clearGeoJsonLayer(); 350 | 351 | layerOnMap = new GeoJsonLayer(googleMap, new JSONObject(airMapGeoJsonLayer.geoJson)); 352 | GeoJsonPolygonStyle style = layerOnMap.getDefaultPolygonStyle(); 353 | style.setStrokeColor(airMapGeoJsonLayer.strokeColor); 354 | style.setStrokeWidth(airMapGeoJsonLayer.strokeWidth); 355 | style.setFillColor(airMapGeoJsonLayer.fillColor); 356 | layerOnMap.addLayerToMap(); 357 | } 358 | 359 | @Override public void clearGeoJsonLayer() { 360 | if (layerOnMap == null) { 361 | return; 362 | } 363 | layerOnMap.removeLayerFromMap(); 364 | layerOnMap = null; 365 | } 366 | 367 | @Override public void getSnapshot(final OnSnapshotReadyListener listener) { 368 | getGoogleMap().snapshot(new GoogleMap.SnapshotReadyCallback() { 369 | @Override public void onSnapshotReady(Bitmap bitmap) { 370 | listener.onSnapshotReady(bitmap); 371 | } 372 | }); 373 | } 374 | 375 | @Override public void onDestroyView() { 376 | clearGeoJsonLayer(); 377 | super.onDestroyView(); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/RuntimePermissionUtils.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import androidx.core.app.ActivityCompat; 8 | 9 | import static androidx.core.content.PermissionChecker.checkSelfPermission; 10 | 11 | /** 12 | * Utility class that handles runtime permissions 13 | */ 14 | final class RuntimePermissionUtils { 15 | 16 | private RuntimePermissionUtils() { 17 | } 18 | 19 | private static final byte LOCATION_PERMISSION_REQUEST_CODE = 1; 20 | private static final String[] LOCATION_PERMISSIONS = 21 | new String[] { "android.permission.ACCESS_FINE_LOCATION", 22 | "android.permission.ACCESS_COARSE_LOCATION" }; 23 | 24 | /** 25 | * Verifies that any of the given permissions have been granted. 26 | * 27 | * @return returns true if all permissions have been granted. 28 | */ 29 | private static boolean verifyPermissions(int... grantResults) { 30 | for (int result : grantResults) { 31 | if (result != PackageManager.PERMISSION_GRANTED) { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | 38 | /** 39 | * Returns true if the context has access to any given permissions. 40 | */ 41 | private static boolean hasSelfPermissions(Context context, String... permissions) { 42 | for (String permission : permissions) { 43 | if (checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | /** 51 | * Checks given permissions are needed to show rationale. 52 | * 53 | * @return returns true if one of the permission is needed to show rationale. 54 | */ 55 | static boolean shouldShowRequestPermissionRationale(Activity activity, String... permissions) { 56 | for (String permission : permissions) { 57 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { 58 | return true; 59 | } 60 | } 61 | return false; 62 | } 63 | 64 | /** 65 | * Check if any location permissions are granted, and invoke onLocationPermissionsGranted() in the 66 | * callback if granted. It will ask users for location permissions and invoke the same callback if 67 | * no permission was granted and got granted at runtime. 68 | * 69 | * @param airMapInterface the callback interface if permission is granted. 70 | */ 71 | static boolean checkLocationPermissions(Activity targetActivity, 72 | AirMapInterface airMapInterface) { 73 | if (hasSelfPermissions(targetActivity, LOCATION_PERMISSIONS)) { 74 | airMapInterface.onLocationPermissionsGranted(); 75 | return true; 76 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 77 | targetActivity.requestPermissions(LOCATION_PERMISSIONS, LOCATION_PERMISSION_REQUEST_CODE); 78 | } 79 | //else don't have location permissions in pre M, don't do anything. 80 | return false; 81 | } 82 | 83 | /** 84 | * Dispatch actions based off requested permission results.
85 | * Further actions like 86 | * 1> Rationale: showing a snack bar to explain why the permissions are needed and 87 | * 2> Denied: adding airMapInterface.onLocationPermissionsDenied() 88 | * should be added here if needed. 89 | */ 90 | static void onRequestPermissionsResult(AirMapInterface airMapInterface, int requestCode, 91 | int[] grantResults) { 92 | switch (requestCode) { 93 | case LOCATION_PERMISSION_REQUEST_CODE: 94 | if (verifyPermissions(grantResults)) { 95 | airMapInterface.onLocationPermissionsGranted(); 96 | } 97 | break; 98 | default: 99 | break; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/WebAirMapViewBuilder.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | /** 4 | * AirMapView map that uses the web based Google Maps implementation. 5 | */ 6 | public class WebAirMapViewBuilder implements AirMapViewBuilder { 7 | 8 | private AirMapType options; 9 | 10 | @Override 11 | public AirMapViewBuilder withOptions(AirMapType options) { 12 | this.options = options; 13 | return this; 14 | } 15 | 16 | /** 17 | * Build the map fragment with the requested options 18 | * 19 | * @return The {@link WebViewMapFragment} map fragment. 20 | */ 21 | @Override public WebViewMapFragment build() { 22 | if (options == null) { 23 | options = new GoogleWebMapType(); 24 | } 25 | if (options instanceof GoogleWebMapType) { 26 | return GoogleWebViewMapFragment.newInstance(options); 27 | } 28 | if (options instanceof GoogleChinaMapType) { 29 | return GoogleChinaWebViewMapFragment.newInstance(options); 30 | } 31 | if (options instanceof LeafletMapType) { 32 | return LeafletWebViewMapFragment.newInstance(options); 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/InfoWindowCreator.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import android.view.View; 4 | 5 | import com.airbnb.android.airmapview.AirMapMarker; 6 | 7 | public interface InfoWindowCreator { 8 | View createInfoWindow(AirMapMarker airMarker); 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnCameraChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | 5 | public interface OnCameraChangeListener { 6 | void onCameraChanged(LatLng latLng, int zoom); 7 | } 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnCameraMoveListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | /** 4 | * This event is triggered once as soon as the map camera starts moving and then is not triggered 5 | * again until the next time the user moves the map camera again. This is handled by AirMapView 6 | * instead of the actual GoogleMap implementation since this is not supported by them. 7 | */ 8 | public interface OnCameraMoveListener { 9 | void onCameraMove(); 10 | } 11 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnInfoWindowClickListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import com.airbnb.android.airmapview.AirMapMarker; 4 | 5 | public interface OnInfoWindowClickListener { 6 | void onInfoWindowClick(AirMapMarker airMarker); 7 | } 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnLatLngScreenLocationCallback.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import android.graphics.Point; 4 | 5 | public interface OnLatLngScreenLocationCallback { 6 | void onLatLngScreenLocationReady(Point point); 7 | } 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnMapBoundsCallback.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import com.google.android.gms.maps.model.LatLngBounds; 4 | 5 | public interface OnMapBoundsCallback { 6 | void onMapBoundsReady(LatLngBounds bounds); 7 | } 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnMapClickListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | 5 | public interface OnMapClickListener { 6 | void onMapClick(LatLng latLng); 7 | } 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnMapInitializedListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | public interface OnMapInitializedListener { 4 | void onMapInitialized(); 5 | } 6 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnMapLoadedListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | public interface OnMapLoadedListener { 4 | void onMapLoaded(); 5 | } 6 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnMapMarkerClickListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import com.airbnb.android.airmapview.AirMapMarker; 4 | 5 | public interface OnMapMarkerClickListener { 6 | 7 | /* 8 | * Called when an airMarker has been clicked or tapped. 9 | * Return true if the listener has consumed the event (i.e., the default behavior should not occur); 10 | * false otherwise (i.e., the default behavior should occur). 11 | * The default behavior is for the camera to move to the marker and an info window to appear. 12 | * See: https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMarkerClickListener 13 | * */ 14 | boolean onMapMarkerClick(AirMapMarker airMarker); 15 | } 16 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnMapMarkerDragListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import com.google.android.gms.maps.model.LatLng; 4 | import com.google.android.gms.maps.model.Marker; 5 | 6 | public interface OnMapMarkerDragListener { 7 | void onMapMarkerDragStart(Marker marker); 8 | 9 | void onMapMarkerDrag(Marker marker); 10 | 11 | void onMapMarkerDragEnd(Marker marker); 12 | 13 | void onMapMarkerDragStart(long id, LatLng latLng); 14 | 15 | void onMapMarkerDrag(long id, LatLng latLng); 16 | 17 | void onMapMarkerDragEnd(long id, LatLng latLng); 18 | } 19 | -------------------------------------------------------------------------------- /library/src/main/java/com/airbnb/android/airmapview/listeners/OnSnapshotReadyListener.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview.listeners; 2 | 3 | import android.graphics.Bitmap; 4 | import androidx.annotation.Nullable; 5 | 6 | public interface OnSnapshotReadyListener { 7 | void onSnapshotReady(@Nullable Bitmap bitmap); 8 | } 9 | -------------------------------------------------------------------------------- /library/src/main/res/layout/fragment_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/layout/map_view.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20dp 4 | 40dp 5 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AirMapView 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/airbnb/android/airmapview/AirMapTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.hamcrest.core.IsEqual; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertThat; 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.verify; 11 | import static org.mockito.Mockito.when; 12 | 13 | public class AirMapTypeTest { 14 | 15 | @Test public void shouldConvertToBundle() { 16 | Bundle bundle = mock(Bundle.class); 17 | AirMapType mapType = new GoogleWebMapType(); 18 | mapType.toBundle(bundle); 19 | 20 | verify(bundle).putString("map_domain", mapType.getDomain()); 21 | verify(bundle).putString("map_url", mapType.getMapUrl()); 22 | verify(bundle).putString("map_file_name", mapType.getFileName()); 23 | } 24 | 25 | @Test public void shouldConstructFromBundle() { 26 | GoogleWebMapType mapType = new GoogleWebMapType(); 27 | Bundle bundle = mock(Bundle.class); 28 | 29 | when(bundle.getString("map_domain", "")).thenReturn(mapType.getDomain()); 30 | when(bundle.getString("map_url", "")).thenReturn(mapType.getMapUrl()); 31 | when(bundle.getString("map_file_name", "")).thenReturn(mapType.getFileName()); 32 | 33 | assertThat(AirMapType.fromBundle(bundle), IsEqual.equalTo(mapType)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /library/src/test/java/com/airbnb/android/airmapview/DefaultAirMapViewBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.instanceOf; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class DefaultAirMapViewBuilderTest { 10 | 11 | @Before public void setUp() { 12 | } 13 | 14 | @Test public void shouldReturnNativeAirMapViewByDefault() { 15 | DefaultAirMapViewBuilder factory = new DefaultAirMapViewBuilder(null, true); 16 | assertThat(factory.builder(), instanceOf(NativeAirMapViewBuilder.class)); 17 | } 18 | 19 | @Test public void shouldReturnWebAirMapViewIfDefaultNotSupported() { 20 | DefaultAirMapViewBuilder factory = new DefaultAirMapViewBuilder(null, false); 21 | assertThat(factory.builder(), instanceOf(WebAirMapViewBuilder.class)); 22 | } 23 | 24 | @Test public void shouldReturnNativeAirMapViewWhenRequestedExplicitly() { 25 | DefaultAirMapViewBuilder factory = new DefaultAirMapViewBuilder(null, true); 26 | AirMapViewBuilder builder = factory.builder(AirMapViewTypes.NATIVE); 27 | assertThat(builder, instanceOf(NativeAirMapViewBuilder.class)); 28 | } 29 | 30 | @Test(expected = UnsupportedOperationException.class) 31 | public void shouldThrowWhenRequestedNativeWebViewAndNotSupported() { 32 | DefaultAirMapViewBuilder factory = new DefaultAirMapViewBuilder(null, false); 33 | factory.builder(AirMapViewTypes.NATIVE); 34 | } 35 | 36 | @Test public void shouldReturnWebAirMapViewWhenRequestedExplicitly() { 37 | DefaultAirMapViewBuilder factory = new DefaultAirMapViewBuilder(null, false); 38 | AirMapViewBuilder builder = factory.builder(AirMapViewTypes.WEB); 39 | assertThat(builder, instanceOf(WebAirMapViewBuilder.class)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /library/src/test/java/com/airbnb/android/airmapview/NativeAirMapViewBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.instanceOf; 8 | import static org.junit.Assert.assertThat; 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.when; 11 | 12 | public class NativeAirMapViewBuilderTest { 13 | 14 | @Test public void shouldBuildNativeAirMapView() { 15 | NativeAirMapViewBuilder builder = new NativeAirMapViewBuilder(); 16 | AirGoogleMapOptions options = mock(AirGoogleMapOptions.class); 17 | when(options.toBundle()).thenReturn(new Bundle()); 18 | assertThat(builder.withOptions(options).build(), instanceOf(NativeGoogleMapFragment.class)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/src/test/java/com/airbnb/android/airmapview/WebAirMapViewBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.android.airmapview; 2 | 3 | import android.os.Bundle; 4 | 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | 8 | import static org.hamcrest.CoreMatchers.instanceOf; 9 | import static org.junit.Assert.assertThat; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.when; 12 | 13 | public class WebAirMapViewBuilderTest { 14 | 15 | @Test @Ignore("Can't really test this right now since we can' mock the Bundle") 16 | public void shouldReturnGoogleWebAirMapViewByDefault() { 17 | WebAirMapViewBuilder factory = new WebAirMapViewBuilder(); 18 | assertThat(factory.build(), instanceOf(GoogleWebViewMapFragment.class)); 19 | } 20 | 21 | @Test public void shouldBuildGoogleWebAirMapViewWithOptions() { 22 | WebAirMapViewBuilder factory = new WebAirMapViewBuilder(); 23 | GoogleWebMapType mapType = mock(GoogleWebMapType.class); 24 | when(mapType.toBundle()).thenReturn(new Bundle()); 25 | assertThat(factory.withOptions(mapType).build(), instanceOf(GoogleWebViewMapFragment.class)); 26 | } 27 | 28 | @Test public void shouldBuildGoogleChinaWebAirMapViewWithOptions() { 29 | WebAirMapViewBuilder factory = new WebAirMapViewBuilder(); 30 | GoogleChinaMapType mapType = mock(GoogleChinaMapType.class); 31 | when(mapType.toBundle()).thenReturn(new Bundle()); 32 | assertThat(factory.withOptions(mapType).build(), 33 | instanceOf(GoogleChinaWebViewMapFragment.class)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/eric/asdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/aars/amazon-maps-api-2.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/AirMapView/533d6818ad4e6119a1aa7360e6868b5d990b9934/sample/aars/amazon-maps-api-2.0.aar -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | applicationId "com.airbnb.airmapview.sample" 8 | minSdkVersion 21 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | multiDexEnabled true 13 | } 14 | lintOptions { 15 | disable 'GradleCompatible' 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_1_8 20 | targetCompatibility JavaVersion.VERSION_1_8 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation project(':library') 26 | implementation 'androidx.multidex:multidex:2.0.1' 27 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 28 | implementation 'androidx.appcompat:appcompat:1.0.2' 29 | implementation 'com.google.android.gms:play-services-maps:17.0.0' 30 | } 31 | -------------------------------------------------------------------------------- /sample/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/AirMapView/533d6818ad4e6119a1aa7360e6868b5d990b9934/sample/debug.keystore -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/felipe_lima/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/debug/res/values/google_maps_api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AIzaSyDxxG08-MZlZV772EMqzliBRP9b1o_rGvg 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 21 | 22 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 46 | 49 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /sample/src/main/java/com/airbnb/airmapview/sample/LogsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.airmapview.sample; 2 | 3 | import android.graphics.Bitmap; 4 | import androidx.annotation.IntDef; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.RetentionPolicy; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class LogsAdapter extends RecyclerView.Adapter { 17 | @IntDef({VIEW_TYPE_STRING, VIEW_TYPE_BITMAP}) 18 | @Retention(RetentionPolicy.SOURCE) 19 | private @interface ViewType { } 20 | private static final int VIEW_TYPE_STRING = 1; 21 | private static final int VIEW_TYPE_BITMAP = 2; 22 | 23 | private final List logs = new ArrayList<>(); 24 | 25 | public void addString(String string) { 26 | logs.add(string); 27 | notifyItemInserted(logs.size() - 1); 28 | } 29 | 30 | public void addBitmap(Bitmap bitmap) { 31 | logs.add(bitmap); 32 | notifyItemInserted(logs.size() - 1); 33 | } 34 | 35 | public void clearLogs() { 36 | logs.clear(); 37 | notifyDataSetChanged(); 38 | } 39 | 40 | @Override 41 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 42 | switch (viewType) { 43 | case VIEW_TYPE_STRING: 44 | return new StringViewHolder(parent); 45 | case VIEW_TYPE_BITMAP: 46 | return new BitmapViewHolder(parent); 47 | default: 48 | throw new IllegalArgumentException("Can't make ViewHolder of type " + viewType); 49 | } 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 54 | Object log = logs.get(position); 55 | 56 | switch (holder.getItemViewType()) { 57 | case VIEW_TYPE_STRING: 58 | ((StringViewHolder) holder).bind((String) log); 59 | break; 60 | case VIEW_TYPE_BITMAP: 61 | ((BitmapViewHolder) holder).bind((Bitmap) log); 62 | break; 63 | default: 64 | throw new IllegalArgumentException("Can't bind view holder of type " + holder.getItemViewType()); 65 | } 66 | } 67 | 68 | @ViewType 69 | @Override 70 | public int getItemViewType(int position) { 71 | Object log = logs.get(position); 72 | if (log instanceof String) { 73 | return VIEW_TYPE_STRING; 74 | } else if (log instanceof Bitmap) { 75 | return VIEW_TYPE_BITMAP; 76 | } else { 77 | throw new IllegalArgumentException("Unknown object of type " + log.getClass()); 78 | } 79 | } 80 | 81 | @Override 82 | public int getItemCount() { 83 | return logs.size(); 84 | } 85 | 86 | private static final class StringViewHolder extends RecyclerView.ViewHolder { 87 | 88 | public StringViewHolder(ViewGroup parent) { 89 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_text, parent, false)); 90 | } 91 | 92 | public void bind(String string) { 93 | ((TextView) itemView).setText(string); 94 | } 95 | } 96 | 97 | private static final class BitmapViewHolder extends RecyclerView.ViewHolder { 98 | 99 | public BitmapViewHolder(ViewGroup parent) { 100 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_bitmap, parent, false)); 101 | } 102 | 103 | public void bind(Bitmap bitmap) { 104 | ((ImageView) itemView).setImageBitmap(bitmap); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /sample/src/main/java/com/airbnb/airmapview/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.airmapview.sample; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Point; 5 | import android.os.Bundle; 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | import androidx.recyclerview.widget.LinearLayoutManager; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.Button; 16 | import android.widget.Toast; 17 | 18 | import com.airbnb.android.airmapview.AirMapGeoJsonLayer; 19 | import com.airbnb.android.airmapview.AirMapInterface; 20 | import com.airbnb.android.airmapview.AirMapMarker; 21 | import com.airbnb.android.airmapview.AirMapPolygon; 22 | import com.airbnb.android.airmapview.AirMapPolyline; 23 | import com.airbnb.android.airmapview.AirMapView; 24 | import com.airbnb.android.airmapview.AirMapViewTypes; 25 | import com.airbnb.android.airmapview.DefaultAirMapViewBuilder; 26 | import com.airbnb.android.airmapview.GoogleChinaMapType; 27 | import com.airbnb.android.airmapview.LeafletBaiduMapType; 28 | import com.airbnb.android.airmapview.LeafletGaodeMapType; 29 | import com.airbnb.android.airmapview.LeafletGoogleChinaMapType; 30 | import com.airbnb.android.airmapview.LeafletGoogleMapType; 31 | import com.airbnb.android.airmapview.LeafletWebViewMapFragment; 32 | import com.airbnb.android.airmapview.MapType; 33 | import com.airbnb.android.airmapview.WebAirMapViewBuilder; 34 | import com.airbnb.android.airmapview.listeners.OnCameraChangeListener; 35 | import com.airbnb.android.airmapview.listeners.OnCameraMoveListener; 36 | import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener; 37 | import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback; 38 | import com.airbnb.android.airmapview.listeners.OnMapClickListener; 39 | import com.airbnb.android.airmapview.listeners.OnMapInitializedListener; 40 | import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener; 41 | import com.airbnb.android.airmapview.listeners.OnSnapshotReadyListener; 42 | import com.google.android.gms.maps.model.LatLng; 43 | 44 | import org.json.JSONException; 45 | 46 | import java.util.Arrays; 47 | 48 | public class MainActivity extends AppCompatActivity 49 | implements OnCameraChangeListener, OnMapInitializedListener, 50 | OnMapClickListener, OnCameraMoveListener, OnMapMarkerClickListener, 51 | OnInfoWindowClickListener, OnLatLngScreenLocationCallback { 52 | 53 | private final LogsAdapter adapter = new LogsAdapter(); 54 | 55 | private static final String TAG = MainActivity.class.getSimpleName(); 56 | private AirMapView map; 57 | private DefaultAirMapViewBuilder mapViewBuilder; 58 | private RecyclerView logsRecyclerView; 59 | private View bottomToolsView; 60 | 61 | @Override protected void onCreate(Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.activity_main); 64 | 65 | mapViewBuilder = new DefaultAirMapViewBuilder(this); 66 | map = findViewById(R.id.map); 67 | bottomToolsView = findViewById(R.id.bottom_tools); 68 | logsRecyclerView = findViewById(R.id.logs); 69 | ((LinearLayoutManager) logsRecyclerView.getLayoutManager()).setReverseLayout(true); 70 | logsRecyclerView.setAdapter(adapter); 71 | Button btnMapTypeNormal = findViewById(R.id.btnMapTypeNormal); 72 | Button btnMapTypeSattelite = findViewById(R.id.btnMapTypeSattelite); 73 | Button btnMapTypeTerrain = findViewById(R.id.btnMapTypeTerrain); 74 | 75 | btnMapTypeNormal.setOnClickListener(new View.OnClickListener() { 76 | @Override public void onClick(@NonNull View v) { 77 | map.setMapType(MapType.MAP_TYPE_NORMAL); 78 | } 79 | }); 80 | 81 | btnMapTypeSattelite.setOnClickListener(new View.OnClickListener() { 82 | @Override public void onClick(@NonNull View v) { 83 | map.setMapType(MapType.MAP_TYPE_SATELLITE); 84 | } 85 | }); 86 | 87 | btnMapTypeTerrain.setOnClickListener(new View.OnClickListener() { 88 | @Override public void onClick(@NonNull View v) { 89 | map.setMapType(MapType.MAP_TYPE_TERRAIN); 90 | } 91 | }); 92 | 93 | map.setOnMapClickListener(this); 94 | map.setOnCameraChangeListener(this); 95 | map.setOnCameraMoveListener(this); 96 | map.setOnMarkerClickListener(this); 97 | map.setOnMapInitializedListener(this); 98 | map.setOnInfoWindowClickListener(this); 99 | map.initialize(getSupportFragmentManager()); 100 | } 101 | 102 | @Override public boolean onCreateOptionsMenu(Menu menu) { 103 | // Inflate the menu; this adds items to the action bar if it is present. 104 | getMenuInflater().inflate(R.menu.menu_main, menu); 105 | return true; 106 | } 107 | 108 | @Override public boolean onOptionsItemSelected(MenuItem item) { 109 | int id = item.getItemId(); 110 | 111 | AirMapInterface airMapInterface = null; 112 | 113 | switch (id) { 114 | case R.id.action_native_map: 115 | try { 116 | airMapInterface = mapViewBuilder.builder(AirMapViewTypes.NATIVE).build(); 117 | } catch (UnsupportedOperationException e) { 118 | Toast.makeText(this, "Sorry, native Google Maps are not supported by this device. " + 119 | "Please make sure you have Google Play Services installed.", 120 | Toast.LENGTH_SHORT).show(); 121 | } 122 | break; 123 | case R.id.action_mapbox_map: 124 | airMapInterface = mapViewBuilder.builder(AirMapViewTypes.WEB).build(); 125 | break; 126 | case R.id.action_google_web_map: 127 | // force Google Web maps since otherwise AirMapViewTypes.WEB returns MapBox by default. 128 | airMapInterface = new WebAirMapViewBuilder().build(); 129 | break; 130 | case R.id.action_google_china_web_map: 131 | airMapInterface = new WebAirMapViewBuilder().withOptions(new GoogleChinaMapType()).build(); 132 | break; 133 | case R.id.action_leaflet_google_web_map: 134 | airMapInterface = new WebAirMapViewBuilder().withOptions(new LeafletGoogleMapType()).build(); 135 | break; 136 | case R.id.action_leaflet_google_china_web_map: 137 | airMapInterface = new WebAirMapViewBuilder().withOptions(new LeafletGoogleChinaMapType()).build(); 138 | break; 139 | case R.id.action_baidu_web_map: 140 | airMapInterface = new WebAirMapViewBuilder().withOptions(new LeafletBaiduMapType()).build(); 141 | break; 142 | case R.id.action_gaode_web_map: 143 | airMapInterface = new WebAirMapViewBuilder().withOptions(new LeafletGaodeMapType()).build(); 144 | break; 145 | case R.id.action_clear_logs: 146 | adapter.clearLogs(); 147 | break; 148 | case R.id.add_geojson_layer: 149 | // Draws a layer on top of Australia 150 | String geoJsonString = Util.readFromRawResource(this, R.raw.google); 151 | AirMapGeoJsonLayer layer = new AirMapGeoJsonLayer.Builder(geoJsonString) 152 | .strokeColor(getResources().getColor(android.R.color.holo_green_dark)) 153 | .strokeWidth(10) 154 | .fillColor(getResources().getColor(android.R.color.holo_green_light)) 155 | .build(); 156 | try { 157 | map.getMapInterface().setGeoJsonLayer(layer); 158 | } catch (JSONException e) { 159 | Log.e(TAG, "Failed to add GeoJson layer", e); 160 | } 161 | 162 | break; 163 | case R.id.remove_geojson_layer: 164 | map.getMapInterface().clearGeoJsonLayer(); 165 | break; 166 | case R.id.take_snapshot: 167 | map.getMapInterface().getSnapshot(new OnSnapshotReadyListener() { 168 | @Override 169 | public void onSnapshotReady(@Nullable Bitmap bitmap) { 170 | if (bitmap != null) { 171 | appendBitmap(bitmap); 172 | } else { 173 | appendLog("Null bitmap"); 174 | } 175 | } 176 | }); 177 | break; 178 | case R.id.enable_location: 179 | map.setMyLocationEnabled(true); 180 | break; 181 | case R.id.disable_location: 182 | map.setMyLocationEnabled(false); 183 | case R.id.add_padding: 184 | map.setPadding(0, 0, 0, bottomToolsView.getHeight()); 185 | break; 186 | case R.id.center_map: { 187 | LatLng wfcLatLng = new LatLng(39.918786, 116.459273); 188 | map.setCenter(wfcLatLng); 189 | break; 190 | } 191 | case R.id.animateCenter: { 192 | LatLng wfcLatLng = new LatLng(39.918786, 116.459273); 193 | map.animateCenter(wfcLatLng); 194 | break; 195 | } 196 | case R.id.zoom: 197 | map.setZoom(15); 198 | break; 199 | case R.id.animateCenterZoom: { 200 | LatLng wfcLatLng = new LatLng(39.918786, 116.459273); 201 | map.animateCenterZoom(wfcLatLng, 15); 202 | break; 203 | } 204 | default: 205 | break; 206 | } 207 | 208 | if (airMapInterface != null) { 209 | map.initialize(getSupportFragmentManager(), airMapInterface); 210 | } 211 | 212 | return super.onOptionsItemSelected(item); 213 | } 214 | 215 | @Override public void onCameraChanged(LatLng latLng, int zoom) { 216 | appendLog("Map onCameraChanged triggered with lat: " + latLng.latitude + ", lng: " 217 | + latLng.longitude); 218 | } 219 | 220 | @Override public void onMapInitialized() { 221 | appendLog("Map onMapInitialized triggered"); 222 | if (map.getMapInterface() instanceof LeafletWebViewMapFragment) { 223 | // Baidu map is unavailable in the US, so we show points in China. 224 | final LatLng wfcLatLng = new LatLng(39.918786, 116.459273); 225 | addMarker("WFC", wfcLatLng, 1); 226 | addMarker("Chaoyang Gate", new LatLng(39.923823, 116.433666), 2); 227 | String image = ""; 228 | addMarker("Dongzhi Gate", new LatLng(39.941823, 116.426319), 3, image, 25, 41); 229 | map.animateCenterZoom(wfcLatLng, 11); 230 | 231 | // Add Polylines 232 | LatLng[] latLngs = { 233 | new LatLng(39.918786, 116.459273), 234 | new LatLng(39.923823, 116.433666), 235 | new LatLng(39.919635, 116.448831) }; 236 | 237 | map.addPolyline(new AirMapPolyline(Arrays.asList(latLngs), 5)); 238 | 239 | LatLng[] polygonLatLngs = { 240 | new LatLng(39.902896, 116.42792), 241 | new LatLng(39.902896, 116.43892), 242 | new LatLng(39.913896, 116.43892), 243 | new LatLng(39.913896, 116.42792) 244 | }; 245 | map.addPolygon(new AirMapPolygon.Builder().add(polygonLatLngs).strokeWidth(3.f).build()); 246 | 247 | // Add Circle 248 | map.drawCircle(new LatLng(39.919635, 116.448831), 1000); 249 | } else { 250 | final LatLng airbnbLatLng = new LatLng(37.771883, -122.405224); 251 | addMarker("Airbnb HQ", airbnbLatLng, 1); 252 | addMarker("Performance Bikes", new LatLng(37.773975, -122.40205), 2); 253 | addMarker("REI", new LatLng(37.772127, -122.404411), 3); 254 | addMarker("Mapbox", new LatLng(37.77572, -122.41354), 4); 255 | map.animateCenterZoom(airbnbLatLng, 10); 256 | 257 | // Add Polylines 258 | LatLng[] latLngs = { 259 | new LatLng(37.77977, -122.38937), 260 | new LatLng(37.77811, -122.39160), 261 | new LatLng(37.77787, -122.38864) }; 262 | 263 | map.addPolyline(new AirMapPolyline(Arrays.asList(latLngs), 5)); 264 | 265 | // Add Polygons 266 | LatLng[] polygonLatLngs = { 267 | new LatLng(37.784, -122.405), 268 | new LatLng(37.784, -122.406), 269 | new LatLng(37.785, -122.406), 270 | new LatLng(37.785, -122.405) 271 | }; 272 | map.addPolygon(new AirMapPolygon.Builder().add(polygonLatLngs).strokeWidth(3.f).build()); 273 | 274 | // Add Circle 275 | map.drawCircle(new LatLng(37.78443, -122.40805), 1000); 276 | 277 | // enable my location 278 | map.setMyLocationEnabled(false); 279 | } 280 | } 281 | 282 | private void addMarker(String title, LatLng latLng, int id) { 283 | addMarker(title, latLng, id, null, 0, 0); 284 | } 285 | 286 | private void addMarker(String title, LatLng latLng, int id, 287 | String divIconHtml, int iconWidth, int iconHeight) { 288 | map.addMarker(new AirMapMarker.Builder() 289 | .id(id) 290 | .position(latLng) 291 | .title(title) 292 | .iconId(R.mipmap.icon_location_pin) 293 | .divIconHtml(divIconHtml) 294 | .divIconWidth(iconWidth) 295 | .divIconHeight(iconHeight) 296 | .build()); 297 | } 298 | 299 | @Override public void onMapClick(LatLng latLng) { 300 | if (latLng != null) { 301 | appendLog( 302 | "Map onMapClick triggered with lat: " + latLng.latitude + ", lng: " 303 | + latLng.longitude); 304 | 305 | map.getMapInterface().getScreenLocation(latLng, this); 306 | } else { 307 | appendLog("Map onMapClick triggered with null latLng"); 308 | } 309 | } 310 | 311 | @Override public void onCameraMove() { 312 | appendLog("Map onCameraMove triggered"); 313 | } 314 | 315 | private void appendLog(String msg) { 316 | adapter.addString(msg); 317 | logsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); 318 | } 319 | 320 | private void appendBitmap(Bitmap bitmap) { 321 | adapter.addBitmap(bitmap); 322 | logsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); 323 | } 324 | 325 | @Override public boolean onMapMarkerClick(AirMapMarker airMarker) { 326 | appendLog("Map onMapMarkerClick triggered with id " + airMarker.getId()); 327 | return false; 328 | } 329 | 330 | @Override public void onInfoWindowClick(AirMapMarker airMarker) { 331 | appendLog("Map onInfoWindowClick triggered with id " + airMarker.getId()); 332 | } 333 | 334 | @Override public void onLatLngScreenLocationReady(Point point) { 335 | appendLog("LatLng location on screen (x,y): (" + point.x + "," + point.y + ")"); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /sample/src/main/java/com/airbnb/airmapview/sample/Util.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.airmapview.sample; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.StringWriter; 10 | import java.io.Writer; 11 | 12 | public class Util { 13 | 14 | private static final String LOGTAG = "Util"; 15 | 16 | public static String readFromRawResource(Context context, int resourceId) { 17 | InputStream resourceReader = context.getResources().openRawResource(resourceId); 18 | Writer writer = new StringWriter(); 19 | try { 20 | BufferedReader reader = new BufferedReader(new InputStreamReader(resourceReader, "UTF-8")); 21 | String line = reader.readLine(); 22 | while (line != null) { 23 | writer.write(line); 24 | line = reader.readLine(); 25 | } 26 | } catch (Exception e) { 27 | Log.e(LOGTAG, "Unhandled exception while reading from resource", e); 28 | } finally { 29 | try { 30 | resourceReader.close(); 31 | } catch (Exception e) { 32 | Log.e(LOGTAG, "Unhandled exception while closing resource", e); 33 | } 34 | } 35 | 36 | return writer.toString(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | 19 | 20 | 26 | 27 | 30 |